Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "clone-labs",
"name": "clone-loop",
"metadata": {
"description": "Clone Loop Claude Code plugins."
},
Expand All @@ -8,7 +8,7 @@
},
"plugins": [
{
"name": "clone-loop",
"name": "clone-labs",
"source": "./",
"description": "Clone Loop for Claude Code. An automation loop powered by Clone MCP next-prompt prediction."
}
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "clone-loop",
"name": "clone-labs",
"version": "0.14.11",
"description": "Clone Loop for Claude Code. Runs iterative development loops, manages Clone API keys, predicts the next user prompt, and can answer AskUserQuestion during active loops.",
"author": {
Expand Down
19 changes: 13 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,24 +66,30 @@ Paste this into your agent after install:
/clone:loop "Run tests and fix any failures" --max-iterations 5
```

Install - one command, Claude CLI auto-detected:
Install on macOS/Linux:

```bash
curl -fsSL https://raw.githubusercontent.com/cloneisyou/clone-loop/main/scripts/install.sh | bash
```

Install on Windows PowerShell:

```powershell
irm https://raw.githubusercontent.com/cloneisyou/clone-loop/main/scripts/install.ps1 | iex
```

Or install manually from your shell:

```bash
claude plugin marketplace add cloneisyou/clone-loop@main
claude plugin install clone-loop@clone-labs --scope user
claude plugin install clone-labs@clone-loop --scope user
```

PowerShell:

```powershell
claude.exe plugin marketplace add cloneisyou/clone-loop@main
claude.exe plugin install clone-loop@clone-labs --scope user
claude.exe plugin install clone-labs@clone-loop --scope user
```

Open your agent and run:
Expand All @@ -101,7 +107,7 @@ Cancel anytime with `/clone:cancel-loop`.
> For private memory and your own prediction quality, set `CLONE_API_TOKEN`
> and run `/clone:api-key import-env`.

To update later: `claude plugin marketplace update clone-labs && claude plugin update clone-loop@clone-labs`.
To update later: `claude plugin marketplace update clone-loop && claude plugin update clone-labs@clone-loop`.

## Commands

Expand Down Expand Up @@ -288,7 +294,8 @@ equivalent AskUserQuestion tool hook event.
## Requirements

Claude Code or Codex with plugin support, plus Node.js on `PATH`. Windows:
PowerShell or cmd, no Git Bash needed.
use PowerShell (`install.ps1`) or the manual `claude.exe` commands; Git Bash is
not required.

## Development

Expand All @@ -304,7 +311,7 @@ claude plugin validate .
> data against the demo fallback.

> [!NOTE]
> The `clone-labs` marketplace is hosted from this repo — not the official
> The `clone-loop` marketplace is hosted from this repo — not the official
> Anthropic `claude-plugins-official` marketplace.

## License
Expand Down
4 changes: 3 additions & 1 deletion scripts/clone-auth.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ export const DEMO_TOKEN = 'clone_yc-reviewer-public-demo-2026'
export const AUTH_FILE_NAME = 'auth.local.json'

export function defaultPluginDataDir() {
return join(homedir(), '.claude', 'plugins', 'data', 'clone-loop-clone-labs')
return join(homedir(), '.claude', 'plugins', 'data', 'clone-labs-clone-loop')
}

function legacyPluginDataDirs() {
return [
join(homedir(), '.claude', 'plugins', 'data', 'clone-loop-clone-loop'),
join(homedir(), '.claude', 'plugins', 'data', 'clone-loop-clone-labs'),
join(homedir(), '.claude', 'plugins', 'data', 'clone-clone-loop'),
join(homedir(), '.claude', 'plugins', 'data', 'clone-clone-labs'),
]
Expand Down
70 changes: 70 additions & 0 deletions scripts/install.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
$ErrorActionPreference = "Stop"

$GitHubRepo = "cloneisyou/clone-loop"
$MarketplaceName = "clone-loop"
$PluginRef = "clone-labs@clone-loop"

$ClaudeCommand = Get-Command claude.exe -ErrorAction SilentlyContinue
if (-not $ClaudeCommand) {
$ClaudeCommand = Get-Command claude -ErrorAction SilentlyContinue
}

if (-not $ClaudeCommand) {
Write-Error "Clone install failed: Claude Code CLI was not found on PATH. Install Claude Code, then rerun this installer."
exit 1
}
Comment on lines +12 to +15
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using exit in a script executed via Invoke-Expression (iex) or dot-sourcing will terminate the entire PowerShell process, closing the user's terminal window. This is a poor user experience if the CLI is not found.

Additionally, since $ErrorActionPreference = "Stop" is set, calling Write-Error will throw a terminating exception with a noisy stack trace. Using Write-Host with -ForegroundColor Red followed by return provides a much cleaner, user-friendly error message and exits the script gracefully without closing the terminal.

if (-not $ClaudeCommand) {
  Write-Host "Clone install failed: Claude Code CLI was not found on PATH. Install Claude Code, then rerun this installer." -ForegroundColor Red
  return
}


$ClaudeBin = $ClaudeCommand.Source
Write-Host "Installing Clone with $ClaudeBin..."

& $ClaudeBin plugin marketplace add "$GitHubRepo@main"
if ($LASTEXITCODE -ne 0) {
Write-Host "Marketplace add did not complete; refreshing $MarketplaceName if it already exists."
& $ClaudeBin plugin marketplace update $MarketplaceName
if ($LASTEXITCODE -ne 0) {
Write-Error "Clone install failed: could not add or update the $MarketplaceName marketplace."
exit 1
}
}

& $ClaudeBin plugin install $PluginRef --scope user
if ($LASTEXITCODE -ne 0) {
Write-Host "Install did not complete; trying plugin update for an existing install."
& $ClaudeBin plugin update $PluginRef
if ($LASTEXITCODE -ne 0) {
Write-Error "Clone install failed: could not install or update $PluginRef."
exit 1
}
}
Comment on lines +17 to +38
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In PowerShell, executing a CommandInfo object directly using the call operator & is more robust than extracting and executing its .Source string. If .Source contains spaces or represents an alias/function, executing it as a raw string can sometimes lead to resolution issues or require extra quoting.

We can execute & $ClaudeCommand directly, and stringify $ClaudeCommand (or use $ClaudeCommand.Source) only when printing the path to the host.

Write-Host "Installing Clone with $($ClaudeCommand.Source)..."

& $ClaudeCommand plugin marketplace add "$GitHubRepo@main"
if ($LASTEXITCODE -ne 0) {
  Write-Host "Marketplace add did not complete; refreshing $MarketplaceName if it already exists."
  & $ClaudeCommand plugin marketplace update $MarketplaceName
}

& $ClaudeCommand plugin install $PluginRef --scope user
if ($LASTEXITCODE -ne 0) {
  Write-Host "Install did not complete; trying plugin update for an existing install."
  & $ClaudeCommand plugin update $PluginRef
}


Write-Host ""
$GhCommand = Get-Command gh -ErrorAction SilentlyContinue
if ($GhCommand) {
$PreviousErrorActionPreference = $ErrorActionPreference
$ErrorActionPreference = "Continue"
try {
& $GhCommand.Source repo star $GitHubRepo *> $null
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similarly, execute the $GhCommand object directly instead of $GhCommand.Source to ensure robust execution regardless of whether gh is resolved as an application, alias, or function.

    & $GhCommand repo star $GitHubRepo *> $null

$StarExitCode = $LASTEXITCODE
} catch {
$StarExitCode = 1
} finally {
$ErrorActionPreference = $PreviousErrorActionPreference
}

if ($StarExitCode -eq 0) {
Write-Host "Starred $GitHubRepo."
} else {
Write-Host "Could not star automatically. Check GitHub CLI authentication with: gh auth status"
}
} else {
Write-Host "Skipping GitHub star because GitHub CLI is not installed."
}

Write-Host ""
Write-Host "Clone is installed."
Write-Host ""
Write-Host "Open your agent and paste:"
Write-Host '/clone:loop "Run tests and fix any failures" --max-iterations 5'
Write-Host ""
Write-Host "Optional API key setup:"
Write-Host "/clone:api-key status"
10 changes: 6 additions & 4 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
set -euo pipefail

GITHUB_REPO="cloneisyou/clone-loop"
MARKETPLACE_NAME="clone-loop"
PLUGIN_REF="clone-labs@clone-loop"

if command -v claude >/dev/null 2>&1; then
CLAUDE_BIN="claude"
Expand All @@ -16,13 +18,13 @@ fi
echo "Installing Clone with ${CLAUDE_BIN}..."

if ! "${CLAUDE_BIN}" plugin marketplace add "${GITHUB_REPO}@main"; then
echo "Marketplace add did not complete; refreshing clone-labs if it already exists."
"${CLAUDE_BIN}" plugin marketplace update clone-labs || true
echo "Marketplace add did not complete; refreshing ${MARKETPLACE_NAME} if it already exists."
"${CLAUDE_BIN}" plugin marketplace update "${MARKETPLACE_NAME}" || true
fi

if ! "${CLAUDE_BIN}" plugin install clone-loop@clone-labs --scope user; then
if ! "${CLAUDE_BIN}" plugin install "${PLUGIN_REF}" --scope user; then
echo "Install did not complete; trying plugin update for an existing install."
"${CLAUDE_BIN}" plugin update clone-loop@clone-labs
"${CLAUDE_BIN}" plugin update "${PLUGIN_REF}"
fi

echo
Expand Down
14 changes: 10 additions & 4 deletions tests/repo-identity.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ describe('repository identity', () => {
assert.doesNotMatch(haystack, new RegExp(escapeRegExp(oldRawRepo)))
})

it('publishes the Claude plugin as clone-loop in the clone-labs marketplace', () => {
it('publishes the Claude plugin as clone-labs in the clone-loop marketplace', () => {
const marketplace = JSON.parse(readFileSync(join(root, '.claude-plugin', 'marketplace.json'), 'utf8'))
const manifest = JSON.parse(readFileSync(join(root, '.claude-plugin', 'plugin.json'), 'utf8'))

assert.equal(marketplace.name, 'clone-labs')
assert.equal(marketplace.plugins[0].name, 'clone-loop')
assert.equal(manifest.name, 'clone-loop')
assert.equal(marketplace.name, 'clone-loop')
assert.equal(marketplace.plugins[0].name, 'clone-labs')
assert.equal(manifest.name, 'clone-labs')
})

it('uses clone-loop for package and Clone MCP client identity', () => {
Expand All @@ -66,12 +66,18 @@ describe('repository identity', () => {

it('installer can automatically star clone-loop through GitHub CLI', () => {
const installer = readFileSync(join(root, 'scripts', 'install.sh'), 'utf8')
const powershellInstaller = readFileSync(join(root, 'scripts', 'install.ps1'), 'utf8')

assert.match(installer, /GITHUB_REPO="cloneisyou\/clone-loop"/)
assert.match(installer, /PLUGIN_REF="clone-labs@clone-loop"/)
assert.match(installer, /plugin marketplace update "\$\{MARKETPLACE_NAME\}"/)
assert.match(installer, /gh repo star "\$\{GITHUB_REPO\}"/)
assert.doesNotMatch(installer, /Star .* now\?/)
assert.doesNotMatch(installer, /STAR_REPLY/)
assert.doesNotMatch(installer, /echo " gh repo star/)
assert.match(powershellInstaller, /\$GitHubRepo = "cloneisyou\/clone-loop"/)
assert.match(powershellInstaller, /\$PluginRef = "clone-labs@clone-loop"/)
assert.match(powershellInstaller, /repo star \$GitHubRepo/)
})

it('keeps shell installers LF-only so bash can parse them on every OS', () => {
Expand Down