@phnx-labs/agents-cli 1.22.22 → 1.22.23
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/CHANGELOG.md +209 -0
- package/README.md +8 -2
- package/dist/commands/doctor.js +15 -7
- package/dist/commands/exec.js +20 -6
- package/dist/commands/focus.d.ts +76 -4
- package/dist/commands/focus.js +219 -40
- package/dist/commands/fork.d.ts +20 -2
- package/dist/commands/fork.js +91 -64
- package/dist/commands/go.d.ts +25 -0
- package/dist/commands/go.js +63 -2
- package/dist/commands/harness-wizard.d.ts +206 -0
- package/dist/commands/harness-wizard.js +403 -0
- package/dist/commands/harness.d.ts +12 -0
- package/dist/commands/harness.js +97 -101
- package/dist/commands/resume.js +10 -3
- package/dist/commands/secrets.js +25 -30
- package/dist/commands/sessions-resume.d.ts +21 -3
- package/dist/commands/sessions-resume.js +50 -11
- package/dist/commands/sessions.d.ts +81 -5
- package/dist/commands/sessions.js +325 -66
- package/dist/commands/watchdog.js +13 -2
- package/dist/lib/agents.d.ts +1 -1
- package/dist/lib/agents.js +132 -0
- package/dist/lib/codex-policy.d.ts +17 -0
- package/dist/lib/codex-policy.js +48 -0
- package/dist/lib/crabbox/lease.d.ts +25 -0
- package/dist/lib/crabbox/lease.js +62 -0
- package/dist/lib/daemon.js +70 -0
- package/dist/lib/exec.d.ts +4 -0
- package/dist/lib/exec.js +88 -54
- package/dist/lib/feed-broadcast.d.ts +1 -20
- package/dist/lib/feed-broadcast.js +31 -1
- package/dist/lib/hooks.js +12 -2
- package/dist/lib/mcp.js +44 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +1 -5
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +2 -15
- package/dist/lib/models.d.ts +0 -5
- package/dist/lib/models.js +48 -0
- package/dist/lib/plugin-marketplace.js +9 -0
- package/dist/lib/pricing/prices.json +119 -92
- package/dist/lib/pricing/table.js +13 -0
- package/dist/lib/remote-agents-json.d.ts +29 -1
- package/dist/lib/remote-agents-json.js +47 -10
- package/dist/lib/resources/mcp.js +2 -0
- package/dist/lib/resources/permissions.js +3 -0
- package/dist/lib/resources/types.d.ts +2 -1
- package/dist/lib/runner.js +28 -19
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -2
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -13
- package/dist/lib/secrets/index.d.ts +15 -1
- package/dist/lib/secrets/index.js +118 -49
- package/dist/lib/secrets/reaper.d.ts +87 -0
- package/dist/lib/secrets/reaper.js +184 -0
- package/dist/lib/secrets/remote.d.ts +29 -0
- package/dist/lib/secrets/remote.js +37 -1
- package/dist/lib/session/active.d.ts +36 -1
- package/dist/lib/session/active.js +60 -19
- package/dist/lib/session/actor-sidecar.d.ts +14 -0
- package/dist/lib/session/actor-sidecar.js +67 -4
- package/dist/lib/session/db.d.ts +1 -1
- package/dist/lib/session/db.js +32 -1
- package/dist/lib/session/discover.js +168 -0
- package/dist/lib/session/parse.d.ts +10 -0
- package/dist/lib/session/parse.js +98 -0
- package/dist/lib/session/remote-list.d.ts +10 -1
- package/dist/lib/session/remote-list.js +2 -8
- package/dist/lib/session/remote.d.ts +57 -3
- package/dist/lib/session/remote.js +90 -26
- package/dist/lib/session/resume-command.d.ts +6 -0
- package/dist/lib/session/resume-command.js +8 -0
- package/dist/lib/session/session-cache.d.ts +173 -0
- package/dist/lib/session/session-cache.js +399 -0
- package/dist/lib/session/types.d.ts +1 -1
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/session/width.d.ts +1 -1
- package/dist/lib/session/width.js +12 -2
- package/dist/lib/shims.d.ts +2 -2
- package/dist/lib/shims.js +40 -5
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/types.d.ts +1 -1
- package/dist/lib/usage.d.ts +13 -0
- package/dist/lib/usage.js +215 -0
- package/dist/lib/versions.js +13 -2
- package/package.json +1 -1
- package/dist/bin/agents +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared interactive step engine for `agents harness` create + edit.
|
|
3
|
+
*
|
|
4
|
+
* A single engine drives BOTH the create wizard (`agents harness add`/`fork`,
|
|
5
|
+
* previously `runHarnessWizard`) and the new edit wizard (`agents harness edit`,
|
|
6
|
+
* which was flag-only before). A "step" is a self-contained unit — decide whether
|
|
7
|
+
* it runs for the current draft, then prompt/validate/apply — so the two modes
|
|
8
|
+
* differ only in their step list, not in the runner.
|
|
9
|
+
*
|
|
10
|
+
* Design goals (RUSH-2219, parent RUSH-2218):
|
|
11
|
+
* - One runner, two modes. `create` builds a new harness from a source; `edit`
|
|
12
|
+
* loads an existing profile and re-asks each field pre-filled with its value.
|
|
13
|
+
* - Every step is skippable. A flag that already supplied the value pre-fills
|
|
14
|
+
* the draft and its step is not re-asked — so non-interactive scripting via
|
|
15
|
+
* flags is unchanged and the wizard only asks for what is missing.
|
|
16
|
+
* - Pure and injectable. The engine talks to the user through {@link WizardIO},
|
|
17
|
+
* an injected seam. Tests drive a scripted fake IO and assert which steps ran,
|
|
18
|
+
* with no TTY. {@link defaultWizardIO} is the production driver over
|
|
19
|
+
* `@inquirer/prompts`.
|
|
20
|
+
* - Typed extension points, not stubs. The three sibling subtasks plug in via
|
|
21
|
+
* {@link WizardHooks} without editing the engine: RUSH-2220 (model catalog +
|
|
22
|
+
* secrets surface) via `pickModel`, RUSH-2221 (connection test) via
|
|
23
|
+
* `connectionTest`, RUSH-2222 (edit matrix) via `editable`. Each hook is a
|
|
24
|
+
* real no-op extension point — absent, the scaffold falls back to today's
|
|
25
|
+
* behavior (free-text model, no test, resolver-sourced editability). None of
|
|
26
|
+
* them fakes a result. RUSH-2223 (cross-host portability, `fork --to-host`)
|
|
27
|
+
* extends the create source step; the seam is `HarnessDraft.toHost`.
|
|
28
|
+
*/
|
|
29
|
+
import chalk from 'chalk';
|
|
30
|
+
import { baseUrlEnvKeyForHost, authEnvKeyForHost, modelEnvKeyForHost, validateProfileName, listProfiles, } from '../lib/profiles.js';
|
|
31
|
+
import { listPresets, getPreset } from '../lib/profiles-presets.js';
|
|
32
|
+
import { listBundles } from '../lib/secrets/bundles.js';
|
|
33
|
+
import { AGENTS, ALL_AGENT_IDS, isSelfUpdatingAgent, resolveAgentName } from '../lib/agents.js';
|
|
34
|
+
/**
|
|
35
|
+
* Resolver-sourced editability, the scaffold default behind the RUSH-2222 seam.
|
|
36
|
+
* Every field is read from the same maps the run-time resolver uses
|
|
37
|
+
* (`baseUrlEnvKeyForHost` / `authEnvKeyForHost` / `isSelfUpdatingAgent`), never a
|
|
38
|
+
* table hardcoded alongside them — so the wizard's enable/disable can never drift
|
|
39
|
+
* from what a run actually honors (repo rule: the capability table stays truthful,
|
|
40
|
+
* in lockstep with the code). RUSH-2222 may replace this via {@link WizardHooks.editable}
|
|
41
|
+
* to add per-param reasons; it must stay sourced from the resolver.
|
|
42
|
+
*/
|
|
43
|
+
export function defaultEditable(host) {
|
|
44
|
+
return {
|
|
45
|
+
model: true,
|
|
46
|
+
baseUrl: baseUrlEnvKeyForHost(host) !== null,
|
|
47
|
+
auth: authEnvKeyForHost(host) !== null,
|
|
48
|
+
version: !isSelfUpdatingAgent(host),
|
|
49
|
+
fallback: true,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** Resolve the host CLI a fork `source` runs under (the host `buildFork` will use). */
|
|
53
|
+
export function hostForSource(source) {
|
|
54
|
+
if (!source)
|
|
55
|
+
return undefined;
|
|
56
|
+
const native = resolveAgentName(source);
|
|
57
|
+
if (native)
|
|
58
|
+
return native;
|
|
59
|
+
// An existing custom harness: its own host.
|
|
60
|
+
const custom = listProfiles().find((p) => p.name === source);
|
|
61
|
+
return custom?.host.agent;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The engine. Walk the steps in order; for each, ask `decide` what to do, then
|
|
65
|
+
* prompt only when it says `'run'`. A `{ disabled }` decision surfaces the reason
|
|
66
|
+
* and moves on; `'skip'` is silent. Returns the finished draft.
|
|
67
|
+
*/
|
|
68
|
+
export async function runWizardSteps(steps, draft, io, hooks = {}) {
|
|
69
|
+
for (const step of steps) {
|
|
70
|
+
const decision = step.decide(draft);
|
|
71
|
+
if (decision === 'run') {
|
|
72
|
+
await step.run(io, draft, hooks);
|
|
73
|
+
}
|
|
74
|
+
else if (decision !== 'skip') {
|
|
75
|
+
io.note(chalk.gray(`${step.id}: ${decision.disabled}`));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return draft;
|
|
79
|
+
}
|
|
80
|
+
// --- shared prompt fragments ------------------------------------------------
|
|
81
|
+
const NO_AUTH = '__none__';
|
|
82
|
+
const CUSTOM = '__custom__';
|
|
83
|
+
const TYPE_NOW = 'type';
|
|
84
|
+
const FROM_SECRETS = 'secrets';
|
|
85
|
+
const KEEP = '__keep__';
|
|
86
|
+
/** The first `_MODEL`-suffixed env var in a preset's static env block, if any. */
|
|
87
|
+
function presetModel(preset) {
|
|
88
|
+
return Object.entries(preset.env).find(([k]) => k.endsWith('_MODEL'))?.[1];
|
|
89
|
+
}
|
|
90
|
+
/** Providers offered in the custom-auth select, deduped from the preset catalog. */
|
|
91
|
+
function knownProviders() {
|
|
92
|
+
return [...new Set(listPresets().map((p) => p.provider))];
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Prompt for a key source when a provider needs one: type it now, or copy it from
|
|
96
|
+
* an existing agents secrets bundle. Sets `draft.fromSecrets` for the bundle path;
|
|
97
|
+
* the type-now path is handled downstream (ensureProviderToken), unchanged. This
|
|
98
|
+
* is today's behavior, lifted verbatim; RUSH-2220 enriches the bundle browse.
|
|
99
|
+
*/
|
|
100
|
+
async function askKeySource(io, draft, provider) {
|
|
101
|
+
const bundles = listBundles();
|
|
102
|
+
const source = bundles.length > 0
|
|
103
|
+
? await io.select({
|
|
104
|
+
message: `How should '${provider}' get its key?`,
|
|
105
|
+
choices: [
|
|
106
|
+
{ name: 'Type a key now', value: TYPE_NOW },
|
|
107
|
+
{ name: 'Use an existing agents secrets bundle', value: FROM_SECRETS },
|
|
108
|
+
],
|
|
109
|
+
})
|
|
110
|
+
: TYPE_NOW;
|
|
111
|
+
if (source !== FROM_SECRETS)
|
|
112
|
+
return;
|
|
113
|
+
const bundleName = await io.select({
|
|
114
|
+
message: 'Bundle',
|
|
115
|
+
choices: bundles.map((b) => ({
|
|
116
|
+
name: b.description ? `${b.name} ${chalk.gray(b.description)}` : b.name,
|
|
117
|
+
value: b.name,
|
|
118
|
+
})),
|
|
119
|
+
});
|
|
120
|
+
const bundle = bundles.find((b) => b.name === bundleName);
|
|
121
|
+
const keys = Object.keys(bundle.vars);
|
|
122
|
+
const key = keys.length === 1
|
|
123
|
+
? keys[0]
|
|
124
|
+
: await io.select({ message: 'Key', choices: keys.map((k) => ({ name: k, value: k })) });
|
|
125
|
+
draft.fromSecrets = `${bundleName}:${key}`;
|
|
126
|
+
}
|
|
127
|
+
/** Free-text model prompt, unless RUSH-2220's catalog hook picks one first. */
|
|
128
|
+
async function askModel(io, draft, hooks, current) {
|
|
129
|
+
if (hooks.pickModel) {
|
|
130
|
+
const picked = await hooks.pickModel(io, draft.host, draft.version ?? draft.original?.host.version, current);
|
|
131
|
+
if (picked !== null)
|
|
132
|
+
return picked;
|
|
133
|
+
}
|
|
134
|
+
return io.input({ message: 'Model id', default: current });
|
|
135
|
+
}
|
|
136
|
+
// --- create steps -----------------------------------------------------------
|
|
137
|
+
/**
|
|
138
|
+
* The create step list — the shape of `agents harness add`/`fork`. Faithful to
|
|
139
|
+
* the previous `runHarnessWizard` sequence (source → preset|custom → model →
|
|
140
|
+
* provider → base URL → name → key source), re-expressed as engine steps so the
|
|
141
|
+
* sibling subtasks can gate/replace individual steps. Produces the same
|
|
142
|
+
* `{ source, name, opts }` draft the fork flow already persists.
|
|
143
|
+
*/
|
|
144
|
+
export function createSteps() {
|
|
145
|
+
return [
|
|
146
|
+
{
|
|
147
|
+
id: 'source',
|
|
148
|
+
decide: (d) => (d.source ? 'skip' : 'run'),
|
|
149
|
+
async run(io, d) {
|
|
150
|
+
const customNames = listProfiles().map((p) => p.name);
|
|
151
|
+
d.source = await io.select({
|
|
152
|
+
message: 'Fork from',
|
|
153
|
+
choices: [
|
|
154
|
+
...ALL_AGENT_IDS.map((id) => ({ name: `${AGENTS[id].name} ${chalk.gray('(native)')}`, value: id })),
|
|
155
|
+
...customNames.map((n) => ({ name: `${n} ${chalk.gray('(custom harness)')}`, value: n })),
|
|
156
|
+
],
|
|
157
|
+
});
|
|
158
|
+
d.host = hostForSource(d.source) ?? d.host;
|
|
159
|
+
d.defaultName = d.source;
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: 'preset',
|
|
164
|
+
decide: (d) => (d.custom || d.preset !== undefined || d.model !== undefined ? 'skip' : 'run'),
|
|
165
|
+
async run(io, d) {
|
|
166
|
+
const presets = listPresets();
|
|
167
|
+
const choice = await io.select({
|
|
168
|
+
message: 'Preset',
|
|
169
|
+
choices: [
|
|
170
|
+
...presets.map((p) => ({ name: `${p.name} ${chalk.gray(p.description.slice(0, 60))}`, value: p.name })),
|
|
171
|
+
{ name: 'Build custom (host + model + provider)', value: CUSTOM },
|
|
172
|
+
],
|
|
173
|
+
});
|
|
174
|
+
if (choice === CUSTOM) {
|
|
175
|
+
d.custom = true;
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const preset = getPreset(choice);
|
|
179
|
+
d.preset = preset.name;
|
|
180
|
+
d.model = presetModel(preset);
|
|
181
|
+
d.baseUrl = preset.env.ANTHROPIC_BASE_URL || preset.env.OPENAI_BASE_URL || undefined;
|
|
182
|
+
d.authProvider = preset.authOptional ? undefined : preset.provider;
|
|
183
|
+
d.providerAsked = true;
|
|
184
|
+
// Pre-fill the name with the preset's own name (e.g. 'deepseek'), not a
|
|
185
|
+
// model detail, so users aren't nudged toward baking one into the identity.
|
|
186
|
+
d.defaultName = preset.name;
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: 'model',
|
|
191
|
+
decide: (d) => (d.custom && d.model === undefined ? 'run' : 'skip'),
|
|
192
|
+
async run(io, d, hooks) {
|
|
193
|
+
d.model = await askModel(io, d, hooks);
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
id: 'provider',
|
|
198
|
+
decide: (d) => (d.custom && !d.providerAsked ? 'run' : 'skip'),
|
|
199
|
+
async run(io, d) {
|
|
200
|
+
const choice = await io.select({
|
|
201
|
+
message: 'Provider',
|
|
202
|
+
choices: [
|
|
203
|
+
...knownProviders().map((p) => ({ name: p, value: p })),
|
|
204
|
+
{ name: 'no auth / host manages its own login', value: NO_AUTH },
|
|
205
|
+
],
|
|
206
|
+
});
|
|
207
|
+
d.authProvider = choice === NO_AUTH ? undefined : choice;
|
|
208
|
+
d.providerAsked = true;
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
id: 'baseUrl',
|
|
213
|
+
decide: (d) => {
|
|
214
|
+
if (!d.custom || d.baseUrl !== undefined)
|
|
215
|
+
return 'skip';
|
|
216
|
+
// Endpoint slot is a function of the host's API format (§3.4): only the
|
|
217
|
+
// Anthropic/OpenAI-compatible hosts carry one. Skipping the prompt for the
|
|
218
|
+
// rest replaces the old silent-drop (`profileFromHostModel` discards a
|
|
219
|
+
// base URL the host can't honor) with an explicit reason.
|
|
220
|
+
const host = d.host ?? hostForSource(d.source);
|
|
221
|
+
if (host && baseUrlEnvKeyForHost(host) === null) {
|
|
222
|
+
return { disabled: `host '${host}' has no custom-endpoint slot — base URL not applicable` };
|
|
223
|
+
}
|
|
224
|
+
return 'run';
|
|
225
|
+
},
|
|
226
|
+
async run(io, d) {
|
|
227
|
+
const url = await io.input({ message: 'Base URL (optional)', default: '' });
|
|
228
|
+
d.baseUrl = url || undefined;
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
id: 'name',
|
|
233
|
+
decide: (d) => (d.name ? 'skip' : 'run'),
|
|
234
|
+
async run(io, d) {
|
|
235
|
+
d.name = await io.input({
|
|
236
|
+
message: 'Harness name',
|
|
237
|
+
default: d.defaultName,
|
|
238
|
+
validate: (v) => {
|
|
239
|
+
try {
|
|
240
|
+
validateProfileName(v);
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
return err.message;
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
id: 'auth',
|
|
252
|
+
decide: (d) => (d.authProvider && d.fromSecrets === undefined ? 'run' : 'skip'),
|
|
253
|
+
async run(io, d) {
|
|
254
|
+
await askKeySource(io, d, d.authProvider);
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
connectionTestStep(),
|
|
258
|
+
];
|
|
259
|
+
}
|
|
260
|
+
// --- edit steps -------------------------------------------------------------
|
|
261
|
+
/** The raw model id currently pinned on a profile (not the display label). */
|
|
262
|
+
function currentModel(p) {
|
|
263
|
+
return p.env[modelEnvKeyForHost(p.host.agent)];
|
|
264
|
+
}
|
|
265
|
+
/** The raw base URL currently pinned on a profile, if the host carries one. */
|
|
266
|
+
function currentBaseUrl(p) {
|
|
267
|
+
const key = baseUrlEnvKeyForHost(p.host.agent);
|
|
268
|
+
return key ? p.env[key] : undefined;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* The edit step list — `agents harness edit <name>` with no flags on a TTY. Each
|
|
272
|
+
* step is pre-filled with the profile's current value and gated by the host's
|
|
273
|
+
* editability matrix (RUSH-2222 seam): an unsupported param reads as disabled with
|
|
274
|
+
* a reason instead of being silently accepted. Records into the same draft, which
|
|
275
|
+
* `runEditWizard` maps to the `EditOptions` shape the flag path already persists.
|
|
276
|
+
*/
|
|
277
|
+
export function editSteps(original) {
|
|
278
|
+
const host = original.host.agent;
|
|
279
|
+
const editableFor = (hooks) => (hooks.editable ?? defaultEditable)(host);
|
|
280
|
+
// decide() has no access to hooks, so gate on the resolver default; a hook that
|
|
281
|
+
// narrows editability further is applied inside run(). The scaffold's default is
|
|
282
|
+
// the resolver truth, which is what the matrix subtask builds on.
|
|
283
|
+
const cap = defaultEditable(host);
|
|
284
|
+
return [
|
|
285
|
+
{
|
|
286
|
+
id: 'model',
|
|
287
|
+
decide: () => (cap.model ? 'run' : { disabled: `host '${host}' does not support a pinned model` }),
|
|
288
|
+
async run(io, d, hooks) {
|
|
289
|
+
if (!editableFor(hooks).model)
|
|
290
|
+
return;
|
|
291
|
+
d.model = await askModel(io, d, hooks, currentModel(original));
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
id: 'baseUrl',
|
|
296
|
+
decide: () => (cap.baseUrl ? 'run' : { disabled: `host '${host}' has no custom-endpoint slot` }),
|
|
297
|
+
async run(io, d, hooks) {
|
|
298
|
+
if (!editableFor(hooks).baseUrl)
|
|
299
|
+
return;
|
|
300
|
+
const url = await io.input({ message: 'Base URL', default: currentBaseUrl(original) ?? '' });
|
|
301
|
+
d.baseUrl = url || '';
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
id: 'auth',
|
|
306
|
+
decide: () => (cap.auth ? 'run' : { disabled: `host '${host}' manages its own login — no auth to edit` }),
|
|
307
|
+
async run(io, d, hooks) {
|
|
308
|
+
if (!editableFor(hooks).auth)
|
|
309
|
+
return;
|
|
310
|
+
const bundles = listBundles();
|
|
311
|
+
const choices = [
|
|
312
|
+
{ name: 'Leave auth unchanged', value: KEEP },
|
|
313
|
+
{ name: 'Repoint to a provider (enter a key)', value: TYPE_NOW },
|
|
314
|
+
];
|
|
315
|
+
if (bundles.length > 0)
|
|
316
|
+
choices.push({ name: 'Copy a key from an agents secrets bundle', value: FROM_SECRETS });
|
|
317
|
+
const choice = await io.select({ message: 'Auth', choices });
|
|
318
|
+
if (choice === KEEP)
|
|
319
|
+
return;
|
|
320
|
+
const provider = await io.input({
|
|
321
|
+
message: 'Provider',
|
|
322
|
+
default: original.provider,
|
|
323
|
+
validate: (v) => (v.trim() ? true : 'Provider is required'),
|
|
324
|
+
});
|
|
325
|
+
d.authProvider = provider;
|
|
326
|
+
if (choice === FROM_SECRETS)
|
|
327
|
+
await askKeySource(io, d, provider);
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
id: 'version',
|
|
332
|
+
decide: () => cap.version ? 'run' : { disabled: `host '${host}' self-updates — its version can't be pinned` },
|
|
333
|
+
async run(io, d, hooks) {
|
|
334
|
+
if (!editableFor(hooks).version)
|
|
335
|
+
return;
|
|
336
|
+
d.version = await io.input({
|
|
337
|
+
message: 'Host CLI version (blank to unpin)',
|
|
338
|
+
default: original.host.version ?? '',
|
|
339
|
+
});
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
id: 'fallback',
|
|
344
|
+
decide: () => (cap.fallback ? 'run' : 'skip'),
|
|
345
|
+
async run(io, d) {
|
|
346
|
+
d.fallbackModel = await io.input({
|
|
347
|
+
message: 'Fallback model (same-host rate-limit retry; blank for none)',
|
|
348
|
+
default: original.fallback_model ?? '',
|
|
349
|
+
});
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
id: 'description',
|
|
354
|
+
decide: () => 'run',
|
|
355
|
+
async run(io, d) {
|
|
356
|
+
d.description = await io.input({ message: 'Description', default: original.description ?? '' });
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
connectionTestStep(),
|
|
360
|
+
];
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* The connection-test step (RUSH-2221 seam). Kept in both step lists so the id is
|
|
364
|
+
* a stable member of the sequence, but the actual test needs the assembled profile
|
|
365
|
+
* (which the caller builds after the wizard), so the run is performed separately by
|
|
366
|
+
* {@link runConnectionTest}. The step itself decides `'skip'` — a real no-op
|
|
367
|
+
* extension point that fakes nothing.
|
|
368
|
+
*/
|
|
369
|
+
function connectionTestStep() {
|
|
370
|
+
return {
|
|
371
|
+
id: 'connectionTest',
|
|
372
|
+
decide: () => 'skip',
|
|
373
|
+
async run() {
|
|
374
|
+
/* no-op: superseded by runConnectionTest, kept so the step id is stable */
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Run the connection-test hook against a finished draft, if one is wired
|
|
380
|
+
* (RUSH-2221). Returns `null` when no hook is present (no test performed) so the
|
|
381
|
+
* caller can distinguish "not tested" from "tested and passed". Separated from the
|
|
382
|
+
* step list because the test needs the assembled profile, which the caller builds.
|
|
383
|
+
*/
|
|
384
|
+
export async function runConnectionTest(draft, hooks) {
|
|
385
|
+
if (!hooks.connectionTest)
|
|
386
|
+
return null;
|
|
387
|
+
return hooks.connectionTest(draft);
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Production {@link WizardIO} over `@inquirer/prompts`, lazy-imported so the
|
|
391
|
+
* dependency loads only when a wizard actually runs. `note` prints to stderr so it
|
|
392
|
+
* never contaminates a `--json`/piped stdout.
|
|
393
|
+
*/
|
|
394
|
+
export async function defaultWizardIO() {
|
|
395
|
+
const { select, input, password, confirm } = await import('@inquirer/prompts');
|
|
396
|
+
return {
|
|
397
|
+
select: (opts) => select(opts),
|
|
398
|
+
input: (opts) => input(opts),
|
|
399
|
+
password: (opts) => password({ message: opts.message, mask: true }),
|
|
400
|
+
confirm: (opts) => confirm(opts),
|
|
401
|
+
note: (message) => console.error(message),
|
|
402
|
+
};
|
|
403
|
+
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import type { Command } from 'commander';
|
|
14
14
|
import { type AddProfileOptions } from './profiles.js';
|
|
15
15
|
import { type Profile, type ForkProfileOptions } from '../lib/profiles.js';
|
|
16
|
+
import { type HarnessDraft } from './harness-wizard.js';
|
|
16
17
|
/**
|
|
17
18
|
* Print one custom harness. Shared by `agents harness view <name>` and by
|
|
18
19
|
* `agents view <name>` — a custom harness resolves as an agent type there, so
|
|
@@ -76,4 +77,15 @@ export declare function forkNeedsWizard(source: string | undefined, name: string
|
|
|
76
77
|
* `agents harness add <preset-name>` (no flags) still resolves via the preset
|
|
77
78
|
* fallback instead of being routed into the wizard. */
|
|
78
79
|
export declare function addNeedsWizard(name: string | undefined, opts: AddProfileOptions): boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Map a finished edit-wizard draft onto {@link EditOptions}, keeping only the
|
|
82
|
+
* fields the user actually changed from the profile's current values. Unchanged
|
|
83
|
+
* accepts (the wizard pre-fills each prompt with the current value) drop out, so
|
|
84
|
+
* the resulting {@link buildEdit} touches nothing the user left alone — and the
|
|
85
|
+
* "no changes" case is detectable via {@link hasEditFlags}. Base-URL clearing is
|
|
86
|
+
* intentionally not expressed here: the flag path can't clear it either (an empty
|
|
87
|
+
* `--base-url` is a no-op in `forkProfile`), so the wizard matches that until a
|
|
88
|
+
* later subtask adds explicit clearing.
|
|
89
|
+
*/
|
|
90
|
+
export declare function draftToEditOptions(draft: HarnessDraft, original: Profile): EditOptions;
|
|
79
91
|
export declare function registerHarnessCommands(program: Command): void;
|