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 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
- ## Two ways to install
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cli-five",
3
- "version": "0.2.17",
3
+ "version": "0.2.19",
4
4
  "description": "Code Like I'm Five — scaffold a 5-agent AI team (GitHub Copilot or OpenCode) into any repo.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -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
- log.warn(`Multiple project names found (${name.sources.map((s) => s.source).join(', ')}) — asking.`);
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
- log.warn(`Multiple descriptions found (${oneLiner.sources.map((s) => s.source).join(', ')}) — asking.`);
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):`);
@@ -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 defaultProvider = providerForPlatform(platform, args.provider);
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 };