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-loop",
"name": "clone-labs",
"metadata": {
"description": "Clone Loop Claude Code plugins."
},
Expand All @@ -8,7 +8,7 @@
},
"plugins": [
{
"name": "clone",
"name": "clone-loop",
"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",
"name": "clone-loop",
"version": "0.14.3",
"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
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,14 @@ Or install manually from your shell:

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

PowerShell:

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

Open your agent and run:
Expand All @@ -100,7 +100,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-loop && claude plugin update clone@clone-loop`.
To update later: `claude plugin marketplace update clone-labs && claude plugin update clone-loop@clone-labs`.

## Commands

Expand Down Expand Up @@ -231,6 +231,14 @@ configuration:
- `hooks/codex-hooks.json` runs the Codex Stop hook that injects confident
Clone-predicted next prompts.

Install the Codex marketplace from this repo:

```bash
codex plugin marketplace add cloneisyou/clone-loop --ref main
```

Then install or enable `clone-loop@clone-loop` from Codex's `/plugins` UI.

First run `clone-setup` in Codex. It enables `[features].plugin_hooks = true`
in `~/.codex/config.toml`, reports the effective Clone API key source, and
leaves a one-time `config.toml.clone-loop.bak` backup before changing an
Expand Down Expand Up @@ -268,7 +276,7 @@ claude plugin validate .
> data against the demo fallback.

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

## License
Expand Down
35 changes: 25 additions & 10 deletions scripts/clone-auth.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ 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-clone-loop')
return join(homedir(), '.claude', 'plugins', 'data', 'clone-loop-clone-labs')
}

function legacyPluginDataDirs() {
return [
join(homedir(), '.claude', 'plugins', 'data', 'clone-clone-loop'),
join(homedir(), '.claude', 'plugins', 'data', 'clone-clone-labs'),
]
}

export function pluginDataDir(env = process.env) {
Expand All @@ -25,6 +32,12 @@ export function authFilePath(env = process.env) {
return dir ? join(dir, AUTH_FILE_NAME) : ''
}

function authFileCandidates(env = process.env) {
const primary = authFilePath(env)
if (isPluginDataDirInjected(env)) return primary ? [primary] : []
return [primary, ...legacyPluginDataDirs().map((dir) => join(dir, AUTH_FILE_NAME))].filter(Boolean)
}

export function maskToken(token) {
const value = String(token || '').trim()
if (!value) return ''
Expand All @@ -33,16 +46,18 @@ export function maskToken(token) {
}

export function readPluginConfigToken(env = process.env) {
const file = authFilePath(env)
if (!file || !existsSync(file)) return null

try {
const parsed = JSON.parse(readFileSync(file, 'utf8'))
const token = String(parsed.clone_api_token || '').trim()
return token || null
} catch {
return null
for (const file of authFileCandidates(env)) {
if (!existsSync(file)) continue

try {
const parsed = JSON.parse(readFileSync(file, 'utf8'))
const token = String(parsed.clone_api_token || '').trim()
if (token) return token
} catch {
// Keep checking legacy auth files.
}
}
return null
}

export function writePluginConfigToken(token, env = process.env) {
Expand Down
8 changes: 4 additions & 4 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,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-loop if it already exists."
"${CLAUDE_BIN}" plugin marketplace update clone-loop || true
echo "Marketplace add did not complete; refreshing clone-labs if it already exists."
"${CLAUDE_BIN}" plugin marketplace update clone-labs || true
fi

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

echo
Expand Down
10 changes: 5 additions & 5 deletions tests/repo-identity.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ const ignoredDirs = new Set(['.git', '.claude', '.codex', 'node_modules'])
const oldSlug = ['clone', 'claude', 'plugin'].join('-')
const oldRepo = `cloneisyou/${oldSlug}`
const oldRawRepo = ['raw.githubusercontent.com', 'cloneisyou', oldSlug].join('/')
const oldMarketplace = ['clone', 'labs'].join('-')

function escapeRegExp(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
Expand Down Expand Up @@ -39,14 +38,15 @@ describe('repository identity', () => {
assert.match(haystack, /cloneisyou\/clone-loop/)
assert.doesNotMatch(haystack, new RegExp(escapeRegExp(oldRepo)))
assert.doesNotMatch(haystack, new RegExp(escapeRegExp(oldRawRepo)))
assert.doesNotMatch(haystack, new RegExp(escapeRegExp(oldMarketplace)))
})

it('publishes the Claude plugin marketplace as clone-loop', () => {
it('publishes the Claude plugin as clone-loop in the clone-labs 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-loop')
assert.equal(marketplace.plugins[0].name, 'clone')
assert.equal(marketplace.name, 'clone-labs')
assert.equal(marketplace.plugins[0].name, 'clone-loop')
assert.equal(manifest.name, 'clone-loop')
})

it('uses clone-loop for package and Clone MCP client identity', () => {
Expand Down