cli-five 0.2.17 → 0.2.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -1
- package/package.json +1 -1
- package/src/commands/init.mjs +28 -4
- package/src/steps/platform.mjs +27 -15
- package/src/util/auth.mjs +85 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **Code Like I'm Five** — scaffold a 5-agent AI team into any repo.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Three ways to install
|
|
6
6
|
|
|
7
7
|
### Full setup (recommended for teams)
|
|
8
8
|
|
|
@@ -27,6 +27,21 @@ copilot plugin install idusortus/cli-five
|
|
|
27
27
|
|
|
28
28
|
Installs the 5 agents to your Copilot profile. No project config, no interview — just the agents with the same autonomous contracts shipped by the scaffolded templates.
|
|
29
29
|
|
|
30
|
+
### Let an agent do it (prompt)
|
|
31
|
+
|
|
32
|
+
Paste a ready-made prompt into any repo and have your agent install *and verify* cli-five there:
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
Install cli-five in this repo for OpenCode and verify it works. Preview with
|
|
36
|
+
`npx -y cli-five@latest init --target opencode --dry-run --yes` first, then run it
|
|
37
|
+
without --dry-run. Confirm all five agents appear in `opencode debug agents`, run a
|
|
38
|
+
live orchestrator->planner delegation smoke test, run `npx -y cli-five@latest doctor`,
|
|
39
|
+
gitignore STATE.md/agent-diary.md/histories/, and leave everything staged without
|
|
40
|
+
committing. Report what you verified.
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The full version — platform detection, provider/auth checks, collision preview, stop conditions, and what to report back — is in **[docs/INSTALL_PROMPT.md](docs/INSTALL_PROMPT.md)**. It is generic: use it in any repository, Copilot or OpenCode.
|
|
44
|
+
|
|
30
45
|
## Supported platforms
|
|
31
46
|
|
|
32
47
|
cli-five can scaffold for:
|
|
@@ -212,6 +227,8 @@ During `init --full-interview` you can choose the model provider and optionally
|
|
|
212
227
|
| OpenCode Zen | OpenCode | `opencode/gpt-5.3-codex` |
|
|
213
228
|
| OpenCode Go | OpenCode | `opencode-go/qwen3.8-max` |
|
|
214
229
|
|
|
230
|
+
**On OpenCode, the default provider is auth-aware.** cli-five checks which provider you are actually authenticated for (`opencode auth list`) and uses it instead of blindly defaulting to OpenCode Zen. If you are authenticated for Go only, `init --target opencode` writes Go models automatically — no `--provider` flag needed. If nothing is detected, or the selected provider does not match your auth, `init` prints a warning telling you how to fix it, so you do not end up with agent files whose models silently fail.
|
|
231
|
+
|
|
215
232
|
`--yes` uses the provider's defaults. `--provider opencode-go --yes` skips the provider prompt.
|
|
216
233
|
|
|
217
234
|
### Copilot cost modes
|
package/package.json
CHANGED
package/src/commands/init.mjs
CHANGED
|
@@ -13,6 +13,7 @@ import { choosePlatform, chooseModels, resolveCodegraphDefault } from '../steps/
|
|
|
13
13
|
import { isGitRepo, gitInit } from '../util/git.mjs';
|
|
14
14
|
import { platformLabel } from '../util/platforms.mjs';
|
|
15
15
|
import { autoProjectInfo } from '../util/project.mjs';
|
|
16
|
+
import { detectAuthenticatedProviders } from '../util/auth.mjs';
|
|
16
17
|
|
|
17
18
|
export async function init(args) {
|
|
18
19
|
const cwd = args.cwd;
|
|
@@ -100,7 +101,7 @@ export async function init(args) {
|
|
|
100
101
|
} else {
|
|
101
102
|
log.step('4/8 Project info');
|
|
102
103
|
docHints = autoProjectInfo(cwd);
|
|
103
|
-
logAutoProjectInfo(docHints);
|
|
104
|
+
logAutoProjectInfo(docHints, { yes: args.yes });
|
|
104
105
|
|
|
105
106
|
log.step('5/8 Model configuration');
|
|
106
107
|
// Minimal path uses provider defaults without prompting (still honours --provider).
|
|
@@ -169,20 +170,30 @@ export async function init(args) {
|
|
|
169
170
|
}
|
|
170
171
|
|
|
171
172
|
/** Log which project fields were auto-pulled from the workspace. */
|
|
172
|
-
function logAutoProjectInfo(info) {
|
|
173
|
+
function logAutoProjectInfo(info, { yes = false } = {}) {
|
|
173
174
|
const name = info?.name || {};
|
|
174
175
|
const oneLiner = info?.oneLiner || {};
|
|
175
176
|
|
|
176
177
|
if (name.value && !name.ambiguous) {
|
|
177
178
|
log.info(`Name: ${kleur.bold(name.value)} ${kleur.gray(`(${name.sources[0].source})`)}`);
|
|
178
179
|
} else if (name.ambiguous) {
|
|
179
|
-
|
|
180
|
+
const picks = name.sources.map((s) => `${s.source}="${s.value}"`).join(', ');
|
|
181
|
+
if (yes) {
|
|
182
|
+
log.warn(`Multiple project names found (${picks}); --yes picked "${name.value}".`);
|
|
183
|
+
} else {
|
|
184
|
+
log.warn(`Multiple project names found (${picks}) — asking.`);
|
|
185
|
+
}
|
|
180
186
|
}
|
|
181
187
|
|
|
182
188
|
if (oneLiner.value && !oneLiner.ambiguous) {
|
|
183
189
|
log.info(`Tagline: ${oneLiner.value} ${kleur.gray(`(${oneLiner.sources[0].source})`)}`);
|
|
184
190
|
} else if (oneLiner.ambiguous) {
|
|
185
|
-
|
|
191
|
+
const picks = oneLiner.sources.map((s) => `${s.source}="${s.value}"`).join(', ');
|
|
192
|
+
if (yes) {
|
|
193
|
+
log.warn(`Multiple descriptions found (${picks}); --yes picked ${oneLiner.sources[0].source}.`);
|
|
194
|
+
} else {
|
|
195
|
+
log.warn(`Multiple descriptions found (${picks}) — asking.`);
|
|
196
|
+
}
|
|
186
197
|
}
|
|
187
198
|
}
|
|
188
199
|
|
|
@@ -208,6 +219,19 @@ function printNextSteps(answers, { generatedInstructions = false } = {}) {
|
|
|
208
219
|
log.raw(` 3. Run OpenCode from this directory:`);
|
|
209
220
|
log.raw(kleur.gray(` opencode`));
|
|
210
221
|
log.raw(` 4. Select ${kleur.bold('Orchestrator')} and describe what you want built.`);
|
|
222
|
+
|
|
223
|
+
// A provider the user is not authenticated for produces agent files whose
|
|
224
|
+
// models silently fail. Surface that here rather than at first agent use.
|
|
225
|
+
const authed = detectAuthenticatedProviders();
|
|
226
|
+
const provider = answers.provider;
|
|
227
|
+
if (authed.length > 0 && !authed.includes(provider)) {
|
|
228
|
+
log.raw('');
|
|
229
|
+
log.warn(`Models use "${provider}", but you are authenticated for: ${authed.join(', ')}.`);
|
|
230
|
+
log.dim(` Authenticate with \`opencode auth login\`, or re-run with --provider ${authed[0]}.`);
|
|
231
|
+
} else if (authed.length === 0) {
|
|
232
|
+
log.raw('');
|
|
233
|
+
log.dim(` Make sure you are authenticated (\`opencode auth login\`) for provider "${provider}".`);
|
|
234
|
+
}
|
|
211
235
|
} else {
|
|
212
236
|
log.raw(` 1. Open this folder in VS Code Insiders.`);
|
|
213
237
|
log.raw(` 2. Enable Copilot subagent invocations (settings.json):`);
|
package/src/steps/platform.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
platformLabel,
|
|
10
10
|
} from '../util/platforms.mjs';
|
|
11
11
|
import { log } from '../util/log.mjs';
|
|
12
|
+
import { detectAuthenticatedProviders, preferredProviderForAuth } from '../util/auth.mjs';
|
|
12
13
|
import {
|
|
13
14
|
agentNames,
|
|
14
15
|
getDefaultModelMap,
|
|
@@ -118,10 +119,17 @@ export function detectExistingPlatform(cwd) {
|
|
|
118
119
|
* override per-agent models.
|
|
119
120
|
*/
|
|
120
121
|
export async function chooseModels(platform, args) {
|
|
121
|
-
// Defaults for the platform.
|
|
122
|
-
const
|
|
122
|
+
// Defaults for the platform, refined by what the user is actually authed for.
|
|
123
|
+
const authedProvider = platform === PLATFORM_OPENCODE && !args.provider
|
|
124
|
+
? preferredProviderForAuth(platform)
|
|
125
|
+
: null;
|
|
126
|
+
const defaultProvider = providerForPlatform(platform, args.provider || authedProvider);
|
|
123
127
|
const defaults = getDefaultModelMap(defaultProvider);
|
|
124
128
|
|
|
129
|
+
if (authedProvider && authedProvider !== PROVIDER_ZEN) {
|
|
130
|
+
log.dim(`Detected authenticated OpenCode provider: ${providerLabel(authedProvider)} (using it instead of the ${providerLabel(PROVIDER_ZEN)} default).`);
|
|
131
|
+
}
|
|
132
|
+
|
|
125
133
|
if (args.yes) {
|
|
126
134
|
return {
|
|
127
135
|
provider: defaultProvider,
|
|
@@ -206,23 +214,27 @@ async function chooseProvider(platform, cliProvider) {
|
|
|
206
214
|
return PROVIDER_COPILOT;
|
|
207
215
|
}
|
|
208
216
|
|
|
217
|
+
const authed = detectAuthenticatedProviders();
|
|
218
|
+
const isAuthed = (p) => authed.includes(p);
|
|
219
|
+
const annotate = (p, label, description) => ({
|
|
220
|
+
title: isAuthed(p) ? `${label} ${kleur.green('(authenticated)')}` : label,
|
|
221
|
+
value: p,
|
|
222
|
+
description,
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const choices = [
|
|
226
|
+
annotate(PROVIDER_ZEN, providerLabel(PROVIDER_ZEN), 'Curated, tested models via OpenCode Zen'),
|
|
227
|
+
annotate(PROVIDER_GO, 'OpenCode Go', 'Low-cost open-coding model subscription'),
|
|
228
|
+
];
|
|
229
|
+
// Put an authenticated provider first so the default is one that works.
|
|
230
|
+
const initial = Math.max(0, choices.findIndex((c) => isAuthed(c.value)));
|
|
231
|
+
|
|
209
232
|
const { provider } = await prompts({
|
|
210
233
|
type: 'select',
|
|
211
234
|
name: 'provider',
|
|
212
235
|
message: 'OpenCode model provider',
|
|
213
|
-
choices
|
|
214
|
-
|
|
215
|
-
title: providerLabel(PROVIDER_ZEN),
|
|
216
|
-
value: PROVIDER_ZEN,
|
|
217
|
-
description: 'Curated, tested models via OpenCode Zen',
|
|
218
|
-
},
|
|
219
|
-
{
|
|
220
|
-
title: 'OpenCode Go',
|
|
221
|
-
value: 'opencode-go',
|
|
222
|
-
description: 'Low-cost open-coding model subscription',
|
|
223
|
-
},
|
|
224
|
-
],
|
|
225
|
-
initial: 0,
|
|
236
|
+
choices,
|
|
237
|
+
initial,
|
|
226
238
|
});
|
|
227
239
|
|
|
228
240
|
if (!provider) {
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { PROVIDER_ZEN, PROVIDER_GO } from './models.mjs';
|
|
6
|
+
|
|
7
|
+
// OpenCode auth provider id -> cli-five provider id.
|
|
8
|
+
// Zen authenticates as `opencode`; Go as `opencode-go`.
|
|
9
|
+
const AUTH_ID_TO_PROVIDER = {
|
|
10
|
+
opencode: PROVIDER_ZEN,
|
|
11
|
+
'opencode-go': PROVIDER_GO,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Detect which OpenCode model providers the user is actually authenticated for.
|
|
16
|
+
*
|
|
17
|
+
* Best-effort and side-effect free: shells out to `opencode auth list --format json`
|
|
18
|
+
* and falls back to reading OpenCode's auth.json. Returns an array of cli-five
|
|
19
|
+
* provider ids (e.g. ['opencode-go']); empty when nothing is detected.
|
|
20
|
+
*
|
|
21
|
+
* Never throws — a missing OpenCode CLI or unreadable auth must not break init.
|
|
22
|
+
*/
|
|
23
|
+
export function detectAuthenticatedProviders() {
|
|
24
|
+
for (const ids of [authFromCli(), authFromFile()]) {
|
|
25
|
+
if (ids && ids.length > 0) return ids;
|
|
26
|
+
}
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function authFromCli() {
|
|
31
|
+
try {
|
|
32
|
+
const out = execFileSync('opencode', ['auth', 'list', '--format', 'json'], {
|
|
33
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
34
|
+
encoding: 'utf8',
|
|
35
|
+
timeout: 5000,
|
|
36
|
+
});
|
|
37
|
+
const parsed = JSON.parse(out);
|
|
38
|
+
if (!Array.isArray(parsed)) return null;
|
|
39
|
+
return parsed
|
|
40
|
+
.map((entry) => AUTH_ID_TO_PROVIDER[entry?.id])
|
|
41
|
+
.filter(Boolean);
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function authFromFile() {
|
|
48
|
+
const candidates = [
|
|
49
|
+
process.env.OPENCODE_AUTH_FILE,
|
|
50
|
+
join(homedir(), '.local', 'share', 'opencode', 'auth.json'),
|
|
51
|
+
join(process.env.XDG_DATA_HOME || '', 'opencode', 'auth.json'),
|
|
52
|
+
].filter(Boolean);
|
|
53
|
+
|
|
54
|
+
for (const path of candidates) {
|
|
55
|
+
if (!existsSync(path)) continue;
|
|
56
|
+
try {
|
|
57
|
+
const data = JSON.parse(readFileSync(path, 'utf8'));
|
|
58
|
+
return Object.keys(data)
|
|
59
|
+
.map((id) => AUTH_ID_TO_PROVIDER[id])
|
|
60
|
+
.filter(Boolean);
|
|
61
|
+
} catch {
|
|
62
|
+
/* try next */
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Pick the best provider for a platform given what the user is authenticated for.
|
|
70
|
+
*
|
|
71
|
+
* For OpenCode: prefer the authenticated provider when there is one; when the
|
|
72
|
+
* user has only Go (or only Zen), use it instead of the blind Zen default.
|
|
73
|
+
* Returns null when no preference can be inferred (caller keeps its default).
|
|
74
|
+
*/
|
|
75
|
+
export function preferredProviderForAuth(platform) {
|
|
76
|
+
if (platform !== 'opencode') return null;
|
|
77
|
+
const authed = detectAuthenticatedProviders();
|
|
78
|
+
if (authed.length === 0) return null;
|
|
79
|
+
// Prefer Go when both are present only if Zen is absent — otherwise Zen.
|
|
80
|
+
if (authed.includes(PROVIDER_ZEN)) return PROVIDER_ZEN;
|
|
81
|
+
if (authed.includes(PROVIDER_GO)) return PROVIDER_GO;
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const __testables = { authFromCli, authFromFile, AUTH_ID_TO_PROVIDER };
|