@phnx-labs/agents-cli 1.20.74 → 1.20.76
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 +893 -0
- package/README.md +36 -10
- package/dist/bin/agents +0 -0
- package/dist/commands/apply.js +21 -3
- package/dist/commands/attach.d.ts +10 -0
- package/dist/commands/attach.js +41 -0
- package/dist/commands/computer.js +3 -3
- package/dist/commands/detach-core.d.ts +46 -0
- package/dist/commands/detach-core.js +61 -0
- package/dist/commands/detach.d.ts +5 -0
- package/dist/commands/detach.js +171 -0
- package/dist/commands/doctor.js +18 -0
- package/dist/commands/exec.js +31 -3
- package/dist/commands/feed.d.ts +7 -1
- package/dist/commands/feed.js +47 -4
- package/dist/commands/go.d.ts +9 -2
- package/dist/commands/go.js +16 -6
- package/dist/commands/harness.d.ts +14 -0
- package/dist/commands/harness.js +145 -0
- package/dist/commands/import.js +38 -8
- package/dist/commands/inspect.js +5 -2
- package/dist/commands/profiles.d.ts +20 -0
- package/dist/commands/profiles.js +68 -21
- package/dist/commands/repo.js +14 -0
- package/dist/commands/routines.js +15 -2
- package/dist/commands/secrets.js +44 -21
- package/dist/commands/sessions-browser.js +1 -1
- package/dist/commands/sessions-export.js +19 -5
- package/dist/commands/sessions-migrate.d.ts +29 -0
- package/dist/commands/sessions-migrate.js +596 -0
- package/dist/commands/sessions-picker.js +16 -1
- package/dist/commands/sessions.d.ts +36 -13
- package/dist/commands/sessions.js +210 -40
- package/dist/commands/setup-computer.js +2 -2
- package/dist/commands/ssh.js +71 -35
- package/dist/commands/status.js +10 -2
- package/dist/commands/versions.js +15 -6
- package/dist/commands/view.d.ts +5 -0
- package/dist/commands/view.js +24 -4
- package/dist/commands/watchdog.d.ts +7 -2
- package/dist/commands/watchdog.js +73 -57
- package/dist/index.js +58 -7
- package/dist/lib/activity.d.ts +14 -4
- package/dist/lib/activity.js +17 -5
- package/dist/lib/actor.d.ts +50 -0
- package/dist/lib/actor.js +166 -0
- package/dist/lib/agent-spec/provider.js +2 -1
- package/dist/lib/agent-spec/resolve.js +19 -5
- package/dist/lib/agent-spec/types.d.ts +9 -1
- package/dist/lib/agents.d.ts +21 -0
- package/dist/lib/agents.js +70 -6
- package/dist/lib/cloud/codex.d.ts +2 -0
- package/dist/lib/cloud/codex.js +14 -3
- package/dist/lib/cloud/session-index.d.ts +32 -0
- package/dist/lib/cloud/session-index.js +58 -0
- package/dist/lib/cloud/store.d.ts +7 -0
- package/dist/lib/cloud/store.js +25 -0
- package/dist/lib/config-transfer.js +4 -0
- package/dist/lib/daemon.js +18 -2
- package/dist/lib/devices/connect.d.ts +15 -1
- package/dist/lib/devices/connect.js +15 -1
- package/dist/lib/devices/fleet.d.ts +21 -0
- package/dist/lib/devices/fleet.js +26 -0
- package/dist/lib/devices/health-report.d.ts +38 -0
- package/dist/lib/devices/health-report.js +214 -0
- package/dist/lib/devices/health.js +4 -1
- package/dist/lib/devices/reachability.d.ts +31 -0
- package/dist/lib/devices/reachability.js +40 -0
- package/dist/lib/devices/registry.d.ts +33 -0
- package/dist/lib/devices/registry.js +37 -0
- package/dist/lib/devices/resolve-target.d.ts +15 -27
- package/dist/lib/devices/resolve-target.js +63 -102
- package/dist/lib/devices/sync.d.ts +18 -0
- package/dist/lib/devices/sync.js +23 -1
- package/dist/lib/devices/tailscale.d.ts +3 -0
- package/dist/lib/devices/tailscale.js +1 -0
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/exec.d.ts +66 -0
- package/dist/lib/exec.js +195 -6
- package/dist/lib/feed-post.d.ts +63 -0
- package/dist/lib/feed-post.js +204 -0
- package/dist/lib/fleet/apply.d.ts +32 -2
- package/dist/lib/fleet/apply.js +97 -10
- package/dist/lib/fleet/types.d.ts +11 -0
- package/dist/lib/fs-walk.d.ts +13 -0
- package/dist/lib/fs-walk.js +16 -7
- package/dist/lib/hooks.d.ts +1 -0
- package/dist/lib/hooks.js +156 -0
- package/dist/lib/hosts/dispatch.d.ts +7 -0
- package/dist/lib/hosts/dispatch.js +2 -0
- package/dist/lib/hosts/passthrough.js +2 -0
- package/dist/lib/hosts/reconnect.d.ts +78 -0
- package/dist/lib/hosts/reconnect.js +127 -0
- package/dist/lib/hosts/registry.d.ts +75 -14
- package/dist/lib/hosts/registry.js +205 -30
- package/dist/lib/hosts/remote-cmd.js +1 -0
- package/dist/lib/hosts/run-target.d.ts +13 -5
- package/dist/lib/hosts/run-target.js +26 -10
- package/dist/lib/hosts/session-index.d.ts +18 -1
- package/dist/lib/hosts/session-index.js +37 -2
- package/dist/lib/hosts/session-marker.d.ts +33 -0
- package/dist/lib/hosts/session-marker.js +51 -0
- package/dist/lib/hosts/tasks.d.ts +8 -4
- package/dist/lib/import.d.ts +2 -0
- package/dist/lib/import.js +35 -2
- package/dist/lib/menubar/install-menubar.d.ts +20 -0
- package/dist/lib/menubar/install-menubar.js +45 -1
- package/dist/lib/migrate.d.ts +16 -0
- package/dist/lib/migrate.js +36 -0
- package/dist/lib/models.d.ts +27 -0
- package/dist/lib/models.js +54 -1
- package/dist/lib/overdue.d.ts +7 -2
- package/dist/lib/overdue.js +18 -11
- package/dist/lib/picker.js +4 -1
- package/dist/lib/platform/process.d.ts +17 -0
- package/dist/lib/platform/process.js +70 -0
- package/dist/lib/profiles-presets.js +9 -7
- package/dist/lib/profiles.d.ts +31 -0
- package/dist/lib/profiles.js +70 -0
- package/dist/lib/pty-server.d.ts +2 -10
- package/dist/lib/pty-server.js +4 -38
- package/dist/lib/registry.d.ts +1 -1
- package/dist/lib/registry.js +48 -8
- package/dist/lib/rotate.d.ts +18 -0
- package/dist/lib/rotate.js +28 -0
- package/dist/lib/routines.d.ts +26 -0
- package/dist/lib/routines.js +19 -2
- package/dist/lib/runner.js +104 -7
- package/dist/lib/secrets/agent.d.ts +66 -1
- package/dist/lib/secrets/agent.js +154 -90
- package/dist/lib/secrets/bundles.d.ts +34 -0
- package/dist/lib/secrets/bundles.js +179 -19
- package/dist/lib/secrets/index.d.ts +15 -1
- package/dist/lib/secrets/index.js +21 -3
- package/dist/lib/secrets/rc-hygiene.d.ts +55 -0
- package/dist/lib/secrets/rc-hygiene.js +154 -0
- package/dist/lib/secrets/remote.d.ts +7 -4
- package/dist/lib/secrets/remote.js +7 -4
- package/dist/lib/secrets/sync-commands.d.ts +21 -0
- package/dist/lib/secrets/sync-commands.js +21 -0
- package/dist/lib/self-update.d.ts +23 -2
- package/dist/lib/self-update.js +86 -5
- package/dist/lib/session/active.d.ts +114 -7
- package/dist/lib/session/active.js +323 -52
- package/dist/lib/session/cloud.js +2 -1
- package/dist/lib/session/db.d.ts +48 -0
- package/dist/lib/session/db.js +238 -14
- package/dist/lib/session/detached.d.ts +30 -0
- package/dist/lib/session/detached.js +92 -0
- package/dist/lib/session/discover.d.ts +465 -2
- package/dist/lib/session/discover.js +1648 -468
- package/dist/lib/session/fork.js +2 -1
- package/dist/lib/session/hook-sessions.d.ts +43 -0
- package/dist/lib/session/hook-sessions.js +135 -0
- package/dist/lib/session/linear.d.ts +6 -0
- package/dist/lib/session/linear.js +55 -0
- package/dist/lib/session/migrate-targets.d.ts +65 -0
- package/dist/lib/session/migrate-targets.js +94 -0
- package/dist/lib/session/migrations.d.ts +37 -0
- package/dist/lib/session/migrations.js +60 -0
- package/dist/lib/session/parse.d.ts +18 -0
- package/dist/lib/session/parse.js +130 -30
- package/dist/lib/session/pid-registry.d.ts +34 -2
- package/dist/lib/session/pid-registry.js +49 -2
- package/dist/lib/session/render.d.ts +7 -2
- package/dist/lib/session/render.js +22 -10
- package/dist/lib/session/short-id.d.ts +17 -0
- package/dist/lib/session/short-id.js +20 -0
- package/dist/lib/session/state.js +6 -2
- package/dist/lib/shims.d.ts +1 -1
- package/dist/lib/shims.js +20 -6
- package/dist/lib/sqlite.d.ts +3 -2
- package/dist/lib/sqlite.js +27 -4
- package/dist/lib/staleness/detectors/commands.js +1 -1
- package/dist/lib/staleness/detectors/workflows.js +13 -2
- package/dist/lib/staleness/writers/commands.js +7 -7
- package/dist/lib/staleness/writers/workflows.d.ts +4 -2
- package/dist/lib/startup/command-registry.d.ts +1 -0
- package/dist/lib/startup/command-registry.js +3 -0
- package/dist/lib/state.d.ts +1 -1
- package/dist/lib/state.js +1 -32
- package/dist/lib/teams/agents.d.ts +19 -10
- package/dist/lib/teams/agents.js +40 -39
- package/dist/lib/types.d.ts +37 -2
- package/dist/lib/usage.d.ts +33 -1
- package/dist/lib/usage.js +172 -12
- package/dist/lib/versions.d.ts +9 -2
- package/dist/lib/versions.js +37 -8
- package/dist/lib/watchdog/log.d.ts +43 -0
- package/dist/lib/watchdog/log.js +69 -0
- package/dist/lib/watchdog/routine.d.ts +44 -0
- package/dist/lib/watchdog/routine.js +69 -0
- package/dist/lib/watchdog/runner.d.ts +51 -7
- package/dist/lib/watchdog/runner.js +239 -64
- package/dist/lib/watchdog/watchdog.d.ts +1 -1
- package/dist/lib/watchdog/watchdog.js +31 -16
- package/dist/lib/workflows.d.ts +16 -0
- package/dist/lib/workflows.js +110 -1
- package/package.json +1 -1
package/dist/commands/feed.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `agents feed` --
|
|
2
|
+
* `agents feed` -- operator inbox + agent status posts.
|
|
3
3
|
*
|
|
4
|
+
* Default (`agents feed`): list open blocks (decisions agents are waiting on).
|
|
4
5
|
* Aggregates block records from the local feed store and, with --host, from
|
|
5
6
|
* reachable remote hosts via SSH passthrough. Each block carries enough
|
|
6
7
|
* identity for `agents message` to route a reply back to the right agent.
|
|
@@ -8,6 +9,11 @@
|
|
|
8
9
|
* Default view groups by **outcome** (ticket / PR / worktree / Unassigned) so
|
|
9
10
|
* an operator sees dozens of deliverables, not ~1,100 agents. Pass `--flat`
|
|
10
11
|
* for the legacy per-agent list.
|
|
12
|
+
*
|
|
13
|
+
* `agents feed post`: agent-callable free-text progress into the activity
|
|
14
|
+
* stream (milestone `status.posted`). Session/agent/host/runtime/pid identity
|
|
15
|
+
* is auto-stamped from env + the pid registry — no domain-specific flags.
|
|
16
|
+
* Humans watch via the feed activity lane / `agents activity`.
|
|
11
17
|
*/
|
|
12
18
|
import type { Command } from 'commander';
|
|
13
19
|
import { type OpenBlock } from '../lib/feed.js';
|
package/dist/commands/feed.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { ensureFeedPublishHook, listAskStats, listBlocks, recordNotified } from '../lib/feed.js';
|
|
3
3
|
import { ensureActivityLogHook, readRecentActivity, formatActivityLine } from '../lib/activity.js';
|
|
4
|
+
import { postFeedStatus } from '../lib/feed-post.js';
|
|
4
5
|
import { enrichBlocksFromSessions, groupBlocksByOutcome, isUnambiguousOutcomeAnswer, openBlocksForOutcome, stampBlockOutcomes, } from '../lib/feed-outcome.js';
|
|
5
6
|
import { classifyBlock, filterBlocksForFeed, suppressionDigest, } from '../lib/ask-classifier.js';
|
|
6
7
|
import { machineId, normalizeHost } from '../lib/machine-id.js';
|
|
@@ -214,9 +215,9 @@ export function sessionHintsFromActive(sessions) {
|
|
|
214
215
|
}));
|
|
215
216
|
}
|
|
216
217
|
export function registerFeedCommand(program) {
|
|
217
|
-
program
|
|
218
|
+
const feed = program
|
|
218
219
|
.command('feed')
|
|
219
|
-
.description('
|
|
220
|
+
.description('Open blocks (needs you) + agent status posts (feed post)')
|
|
220
221
|
.option('--json', 'Output as JSON (each block stamped with its outcome + ask class)')
|
|
221
222
|
.option('--flat', 'List one block per agent instead of grouping by outcome')
|
|
222
223
|
.option('--all', 'Include stalls/FYIs that policy would suppress (default: hide them)')
|
|
@@ -225,8 +226,50 @@ export function registerFeedCommand(program) {
|
|
|
225
226
|
.option('--device <target...>', 'Alias for --host; repeatable')
|
|
226
227
|
.option('--dispatch', 'Run stall suppression + default-on-no-answer policy and urgent notifications')
|
|
227
228
|
.option('--pause <id>', 'Pause a runaway/needy local process (SIGSTOP) or cancel a cloud task')
|
|
228
|
-
.option('--kill <id>', 'Kill a runaway/needy local process (SIGTERM) or cancel a cloud task')
|
|
229
|
-
|
|
229
|
+
.option('--kill <id>', 'Kill a runaway/needy local process (SIGTERM) or cancel a cloud task');
|
|
230
|
+
feed
|
|
231
|
+
.command('post')
|
|
232
|
+
.description('Post a status update to the fleet activity stream (for agents)')
|
|
233
|
+
.argument('<text...>', 'What just happened — one short human line')
|
|
234
|
+
.option('--session <id>', 'Session id escape hatch (default: auto from env / pid registry)')
|
|
235
|
+
.option('--json', 'Emit the written event as JSON')
|
|
236
|
+
.addHelpText('after', `
|
|
237
|
+
Examples:
|
|
238
|
+
# Inside an agents-cli run (session identity is already in the env):
|
|
239
|
+
agents feed post "CHANGELOG pushed; watching CI and mac-mini E2E"
|
|
240
|
+
agents feed post "ready for review" --json
|
|
241
|
+
|
|
242
|
+
# Outside a run, pass the session explicitly:
|
|
243
|
+
agents feed post "manual note" --session 00998b0e-2d15-4d2f-a58b-974a886c9b47
|
|
244
|
+
|
|
245
|
+
Identity (session, agent, host, runtime, pid, launchId) is stamped automatically.
|
|
246
|
+
Domain facts (tickets, PRs) are not CLI flags — join them on the session at read time.
|
|
247
|
+
`)
|
|
248
|
+
.action((textParts, opts, cmd) => {
|
|
249
|
+
// Parent `feed` also declares `--json` (for the list view). Commander
|
|
250
|
+
// binds the flag on the parent, so a `feed post … --json` lands on
|
|
251
|
+
// parent.opts().json — not the child. Read both.
|
|
252
|
+
const flags = {
|
|
253
|
+
session: opts?.session ?? cmd?.opts?.()?.session,
|
|
254
|
+
json: Boolean(opts?.json ?? cmd?.opts?.()?.json ?? cmd?.parent?.opts?.()?.json),
|
|
255
|
+
};
|
|
256
|
+
try {
|
|
257
|
+
const { event } = postFeedStatus({
|
|
258
|
+
text: Array.isArray(textParts) ? textParts.join(' ') : String(textParts ?? ''),
|
|
259
|
+
sessionId: flags.session,
|
|
260
|
+
});
|
|
261
|
+
if (flags.json) {
|
|
262
|
+
console.log(JSON.stringify(event, null, 2));
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
console.log(formatActivityLine(event, { showHost: true }).trimStart());
|
|
266
|
+
}
|
|
267
|
+
catch (err) {
|
|
268
|
+
console.error(chalk.red(err.message));
|
|
269
|
+
process.exitCode = 1;
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
feed.action(async (opts) => {
|
|
230
273
|
if (opts.device?.length)
|
|
231
274
|
opts.host = [...(opts.host ?? []), ...opts.device];
|
|
232
275
|
const self = machineId();
|
package/dist/commands/go.d.ts
CHANGED
|
@@ -17,8 +17,15 @@ import type { Command } from 'commander';
|
|
|
17
17
|
import { type ActiveSession } from '../lib/session/active.js';
|
|
18
18
|
import type { SessionMeta } from '../lib/session/types.js';
|
|
19
19
|
export declare function registerGoCommand(program: Command): void;
|
|
20
|
-
/**
|
|
21
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Live jump targets (local + remote), keyed by session id. Cloud is excluded by
|
|
22
|
+
* default (it has no local pid to attach), but `detach` opts in with
|
|
23
|
+
* `includeCloud` so it can resolve a cloud id and refuse it with a clear message
|
|
24
|
+
* instead of a bare "no live session".
|
|
25
|
+
*/
|
|
26
|
+
export declare function gatherLiveTargets(local: boolean, opts?: {
|
|
27
|
+
includeCloud?: boolean;
|
|
28
|
+
}): Promise<{
|
|
22
29
|
self: string;
|
|
23
30
|
activeById: Map<string, ActiveSession>;
|
|
24
31
|
}>;
|
package/dist/commands/go.js
CHANGED
|
@@ -20,6 +20,7 @@ import { promisify } from 'util';
|
|
|
20
20
|
import { getActiveSessions, findSessionFileForKind } from '../lib/session/active.js';
|
|
21
21
|
import { gatherRemoteActive } from '../lib/session/remote-active.js';
|
|
22
22
|
import { discoverSessions } from '../lib/session/discover.js';
|
|
23
|
+
import { deriveShortId } from '../lib/session/short-id.js';
|
|
23
24
|
import { dedupeByMachineSession, mergeLocalFirst, pickSessionInteractive } from './sessions.js';
|
|
24
25
|
import { focusAction } from './focus.js';
|
|
25
26
|
import { machineId } from '../lib/session/sync/config.js';
|
|
@@ -39,8 +40,13 @@ export function registerGoCommand(program) {
|
|
|
39
40
|
await focusAction(id, { local: opts.local, attachOnly: true });
|
|
40
41
|
});
|
|
41
42
|
}
|
|
42
|
-
/**
|
|
43
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Live jump targets (local + remote), keyed by session id. Cloud is excluded by
|
|
45
|
+
* default (it has no local pid to attach), but `detach` opts in with
|
|
46
|
+
* `includeCloud` so it can resolve a cloud id and refuse it with a clear message
|
|
47
|
+
* instead of a bare "no live session".
|
|
48
|
+
*/
|
|
49
|
+
export async function gatherLiveTargets(local, opts = {}) {
|
|
44
50
|
const self = machineId();
|
|
45
51
|
const localActive = await getActiveSessions();
|
|
46
52
|
for (const s of localActive)
|
|
@@ -55,9 +61,13 @@ export async function gatherLiveTargets(local) {
|
|
|
55
61
|
catch { /* remote sweep is best-effort */ }
|
|
56
62
|
}
|
|
57
63
|
const activeById = new Map();
|
|
58
|
-
for (const s of active)
|
|
59
|
-
if (s.
|
|
60
|
-
|
|
64
|
+
for (const s of active) {
|
|
65
|
+
if (!s.sessionId)
|
|
66
|
+
continue;
|
|
67
|
+
if (s.context === 'cloud' && !opts.includeCloud)
|
|
68
|
+
continue;
|
|
69
|
+
activeById.set(s.sessionId, s);
|
|
70
|
+
}
|
|
61
71
|
return { self, activeById };
|
|
62
72
|
}
|
|
63
73
|
/** Interactive pick over the live sessions' rich SessionMeta; returns the chosen live session. */
|
|
@@ -99,7 +109,7 @@ function synthMeta(s, self) {
|
|
|
99
109
|
const filePath = remote ? '' : (findSessionFileForKind(s.kind, s.cwd, s.sessionId) ?? '');
|
|
100
110
|
return {
|
|
101
111
|
id: s.sessionId,
|
|
102
|
-
shortId: s.sessionId
|
|
112
|
+
shortId: deriveShortId(s.sessionId),
|
|
103
113
|
agent: s.kind,
|
|
104
114
|
timestamp: new Date(s.startedAtMs ?? Date.now()).toISOString(),
|
|
105
115
|
filePath,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom harness commands (`agents harness`).
|
|
3
|
+
*
|
|
4
|
+
* A "custom harness" is a named (host CLI + model) combo — e.g. OpenCode pinned
|
|
5
|
+
* to meta/muse-spark-1.1, called `spark`. It runs like a native agent type
|
|
6
|
+
* (`agents run spark`) and syncs across devices via `agents repo push user`.
|
|
7
|
+
*
|
|
8
|
+
* Mechanism: harnesses ARE profiles (same ~/.agents/profiles/*.yml, same run
|
|
9
|
+
* resolution). This command group is the harness-flavored surface over that
|
|
10
|
+
* layer + a discovery `list` spanning custom harnesses, addable presets, and the
|
|
11
|
+
* native harness registry. The `agents profiles` tree stays unchanged.
|
|
12
|
+
*/
|
|
13
|
+
import type { Command } from 'commander';
|
|
14
|
+
export declare function registerHarnessCommands(program: Command): void;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom harness commands (`agents harness`).
|
|
3
|
+
*
|
|
4
|
+
* A "custom harness" is a named (host CLI + model) combo — e.g. OpenCode pinned
|
|
5
|
+
* to meta/muse-spark-1.1, called `spark`. It runs like a native agent type
|
|
6
|
+
* (`agents run spark`) and syncs across devices via `agents repo push user`.
|
|
7
|
+
*
|
|
8
|
+
* Mechanism: harnesses ARE profiles (same ~/.agents/profiles/*.yml, same run
|
|
9
|
+
* resolution). This command group is the harness-flavored surface over that
|
|
10
|
+
* layer + a discovery `list` spanning custom harnesses, addable presets, and the
|
|
11
|
+
* native harness registry. The `agents profiles` tree stays unchanged.
|
|
12
|
+
*/
|
|
13
|
+
import chalk from 'chalk';
|
|
14
|
+
import { addProfile } from './profiles.js';
|
|
15
|
+
import { listProfiles, readProfile, deleteProfile, profileHostLabel, profileProviderLabel, profileModelLabel, profileAuthLabel, getProfilePath, } from '../lib/profiles.js';
|
|
16
|
+
import { listPresets } from '../lib/profiles-presets.js';
|
|
17
|
+
import { AGENTS, ALL_AGENT_IDS } from '../lib/agents.js';
|
|
18
|
+
/** Short capability summary for a native harness — its supported run modes. */
|
|
19
|
+
function nativeModes(id) {
|
|
20
|
+
const modes = AGENTS[id]?.capabilities?.modes ?? [];
|
|
21
|
+
return modes.length ? modes.join('/') : '-';
|
|
22
|
+
}
|
|
23
|
+
export function registerHarnessCommands(program) {
|
|
24
|
+
const cmd = program
|
|
25
|
+
.command('harness')
|
|
26
|
+
.alias('harnesses')
|
|
27
|
+
.description('Custom harnesses — name a (host CLI + model) combo and run it like a native agent type.')
|
|
28
|
+
.addHelpText('after', `
|
|
29
|
+
A custom harness pins a host CLI (opencode, claude, codex, grok, gemini, ...) to a
|
|
30
|
+
model and gives it a name. 'agents run <name>' then behaves like a native agent
|
|
31
|
+
type, and 'agents repo push user' syncs it to every device.
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
# Meta Muse Spark 1.1 through OpenCode, called 'spark'
|
|
35
|
+
agents harness add spark --host opencode --model meta/muse-spark-1.1
|
|
36
|
+
agents run spark "refactor api/handlers/checkout.py"
|
|
37
|
+
|
|
38
|
+
# Per-run model override still wins
|
|
39
|
+
agents run spark --model opencode/big-pickle "quick pass"
|
|
40
|
+
|
|
41
|
+
# See custom harnesses, addable presets, and native harnesses
|
|
42
|
+
agents harness list
|
|
43
|
+
|
|
44
|
+
# Private OpenAI/Anthropic-compatible endpoint (stores a keychain key)
|
|
45
|
+
agents harness add corp --host claude --model gpt-x --base-url https://gw.corp/v1 --auth-provider corp
|
|
46
|
+
|
|
47
|
+
# Custom harnesses are just profiles — this also works via 'agents profiles'
|
|
48
|
+
`);
|
|
49
|
+
cmd
|
|
50
|
+
.command('add <name>')
|
|
51
|
+
.description('Create a custom harness from a host + model (or apply a built-in preset).')
|
|
52
|
+
.option('--host <agent>', 'Host CLI to run under (opencode, claude, codex, grok, gemini, ...) — pair with --model')
|
|
53
|
+
.option('--model <id>', 'Model id to pin on the host (e.g., meta/muse-spark-1.1) — pair with --host')
|
|
54
|
+
.option('--base-url <url>', 'Custom endpoint base URL (claude/codex hosts)')
|
|
55
|
+
.option('--auth-provider <provider>', 'Attach a keychain-backed API key under this provider (for private endpoints)')
|
|
56
|
+
.option('--preset <preset>', 'Apply a built-in preset instead of --host/--model')
|
|
57
|
+
.option('--version <version>', 'Pin the host CLI version (e.g., 1.16.0)')
|
|
58
|
+
.option('--key-stdin', 'Read API key from stdin instead of prompting (for scripts/CI)')
|
|
59
|
+
.option('--force', 'Overwrite an existing harness with the same name')
|
|
60
|
+
.action(async (name, opts) => {
|
|
61
|
+
try {
|
|
62
|
+
await addProfile(name, opts, 'Harness');
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
console.error(chalk.red(err.message));
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
cmd
|
|
70
|
+
.command('list')
|
|
71
|
+
.alias('ls')
|
|
72
|
+
.description('List custom harnesses, addable presets, and native harnesses.')
|
|
73
|
+
.option('--json', 'Emit JSON instead of a table')
|
|
74
|
+
.action((opts) => {
|
|
75
|
+
const custom = listProfiles();
|
|
76
|
+
const presets = listPresets();
|
|
77
|
+
const native = ALL_AGENT_IDS.map((id) => ({ id, name: AGENTS[id].name, modes: nativeModes(id) }));
|
|
78
|
+
if (opts.json) {
|
|
79
|
+
console.log(JSON.stringify({
|
|
80
|
+
custom: custom.map((p) => ({
|
|
81
|
+
name: p.name,
|
|
82
|
+
host: p.host.agent,
|
|
83
|
+
model: profileModelLabel(p),
|
|
84
|
+
provider: profileProviderLabel(p),
|
|
85
|
+
})),
|
|
86
|
+
presets: presets.map((p) => ({ name: p.name, provider: p.provider, description: p.description })),
|
|
87
|
+
native,
|
|
88
|
+
}, null, 2));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
console.log(chalk.bold('Custom harnesses') + chalk.gray(' (yours — agents run <name>)'));
|
|
92
|
+
if (custom.length === 0) {
|
|
93
|
+
console.log(chalk.gray(' none yet — try: agents harness add spark --host opencode --model meta/muse-spark-1.1'));
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
for (const p of custom) {
|
|
97
|
+
console.log(` ${chalk.cyan(p.name.padEnd(16))} ${profileHostLabel(p).padEnd(14)} ${chalk.gray(profileModelLabel(p))}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
console.log('');
|
|
101
|
+
console.log(chalk.bold('Presets') + chalk.gray(' (addable — agents harness add <name>)'));
|
|
102
|
+
for (const p of presets) {
|
|
103
|
+
console.log(` ${chalk.cyan(p.name.padEnd(16))} ${p.provider.padEnd(12)} ${chalk.gray(p.description.slice(0, 70))}`);
|
|
104
|
+
}
|
|
105
|
+
console.log('');
|
|
106
|
+
console.log(chalk.bold('Native harnesses') + chalk.gray(' (built-in — agents run <id>)'));
|
|
107
|
+
for (const n of native) {
|
|
108
|
+
console.log(` ${chalk.cyan(n.id.padEnd(16))} ${n.name.padEnd(14)} ${chalk.gray('modes: ' + n.modes)}`);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
cmd
|
|
112
|
+
.command('view <name>')
|
|
113
|
+
.alias('show')
|
|
114
|
+
.description('Show one custom harness (host, model, provider, auth, path).')
|
|
115
|
+
.action((name) => {
|
|
116
|
+
try {
|
|
117
|
+
const p = readProfile(name);
|
|
118
|
+
console.log(chalk.bold(p.name));
|
|
119
|
+
if (p.description)
|
|
120
|
+
console.log(chalk.gray(p.description));
|
|
121
|
+
console.log('');
|
|
122
|
+
console.log(`Host: ${profileHostLabel(p)}`);
|
|
123
|
+
console.log(`Model: ${profileModelLabel(p)}`);
|
|
124
|
+
console.log(`Provider: ${profileProviderLabel(p)}`);
|
|
125
|
+
console.log(`Auth: ${profileAuthLabel(p)}`);
|
|
126
|
+
console.log(chalk.gray(getProfilePath(p.name)));
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
console.error(chalk.red(err.message));
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
cmd
|
|
134
|
+
.command('remove <name>')
|
|
135
|
+
.alias('rm')
|
|
136
|
+
.description('Delete a custom harness (keychain token is kept).')
|
|
137
|
+
.action((name) => {
|
|
138
|
+
const existed = deleteProfile(name);
|
|
139
|
+
if (!existed) {
|
|
140
|
+
console.error(chalk.red(`Harness '${name}' not found.`));
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
console.log(chalk.green(`Harness '${name}' removed.`));
|
|
144
|
+
});
|
|
145
|
+
}
|
package/dist/commands/import.js
CHANGED
|
@@ -134,7 +134,7 @@ async function runImport(agentArg, opts) {
|
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
|
-
let version = opts.
|
|
137
|
+
let version = opts.as;
|
|
138
138
|
if (!version) {
|
|
139
139
|
if (!useDirectBinaryImport && globalPath) {
|
|
140
140
|
try {
|
|
@@ -157,7 +157,7 @@ async function runImport(agentArg, opts) {
|
|
|
157
157
|
}
|
|
158
158
|
if (!version) {
|
|
159
159
|
console.error(chalk.red(`Could not determine version for ${agentLabel(agentId)}.`));
|
|
160
|
-
console.error(chalk.gray('Pass --
|
|
160
|
+
console.error(chalk.gray('Pass --as <version> explicitly.'));
|
|
161
161
|
process.exit(1);
|
|
162
162
|
}
|
|
163
163
|
if (!isValidImportVersion(version)) {
|
|
@@ -167,7 +167,7 @@ async function runImport(agentArg, opts) {
|
|
|
167
167
|
}
|
|
168
168
|
const versionDir = getVersionDir(agentId, version);
|
|
169
169
|
const fromLabel = useDirectBinaryImport ? installScriptBinary : globalPath;
|
|
170
|
-
console.log(chalk.bold(`\nImport ${agentLabel(agentId)} v${version}`));
|
|
170
|
+
console.log(chalk.bold(`\nImport ${agentLabel(agentId)} v${version}${opts.isolated ? ' (isolated copy)' : ''}`));
|
|
171
171
|
console.log(` from: ${chalk.gray(fromLabel)}`);
|
|
172
172
|
console.log(` into: ${chalk.gray(versionDir)}`);
|
|
173
173
|
const configDirExists = fs.existsSync(agent.configDir);
|
|
@@ -178,6 +178,16 @@ async function runImport(agentArg, opts) {
|
|
|
178
178
|
configAlreadyManaged = true;
|
|
179
179
|
console.log(` config: ${chalk.gray(`${agent.configDir} (already managed — will skip)`)}`);
|
|
180
180
|
}
|
|
181
|
+
else if (opts.isolated) {
|
|
182
|
+
// This summary is printed BEFORE the branch that does the work, so it has to
|
|
183
|
+
// describe the mode it is actually about to run. Saying "will be moved" under
|
|
184
|
+
// --isolated announced the exact adoption the flag exists to prevent — a
|
|
185
|
+
// confirmation prompt that misdescribes the operation is worse than none.
|
|
186
|
+
console.log(` config: ${chalk.gray(`${agent.configDir} (will be COPIED — your original stays put)`)}`);
|
|
187
|
+
if (!opts.withAuth) {
|
|
188
|
+
console.log(` ${chalk.gray('credentials are skipped; pass --with-auth to include them')}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
181
191
|
else {
|
|
182
192
|
console.log(` config: ${chalk.gray(`${agent.configDir} (will be moved into version home)`)}`);
|
|
183
193
|
}
|
|
@@ -188,7 +198,9 @@ async function runImport(agentArg, opts) {
|
|
|
188
198
|
if (!opts.yes && isInteractiveTerminal()) {
|
|
189
199
|
console.log();
|
|
190
200
|
const proceed = await confirm({
|
|
191
|
-
message:
|
|
201
|
+
message: opts.isolated
|
|
202
|
+
? `Import ${agentLabel(agentId)} v${version} as an isolated copy?`
|
|
203
|
+
: `Import ${agentLabel(agentId)} v${version} into agents-cli?`,
|
|
192
204
|
default: true,
|
|
193
205
|
}).catch((err) => {
|
|
194
206
|
if (isPromptCancelled(err))
|
|
@@ -205,17 +217,23 @@ async function runImport(agentArg, opts) {
|
|
|
205
217
|
// want a stranded symlink farm. Binary registration is cheap and reversible
|
|
206
218
|
// — if it fails after config, the next `agents import` call retries cleanly.
|
|
207
219
|
const willImportConfig = configDirExists && !configAlreadyManaged && !opts.isolated;
|
|
208
|
-
if (opts.isolated && configDirExists) {
|
|
220
|
+
if (opts.isolated && configDirExists && configAlreadyManaged) {
|
|
221
|
+
console.log(chalk.gray(` Skipping config copy: ${agent.configDir} is a managed symlink, not your real settings.`));
|
|
222
|
+
}
|
|
223
|
+
else if (opts.isolated && configDirExists) {
|
|
209
224
|
// COPY the user's settings in; never move, never symlink. The original stays
|
|
210
225
|
// exactly where it is — that is what separates this from adoption.
|
|
211
226
|
const seedSpinner = ora(`Copying ${agent.configDir} into the isolated copy...`).start();
|
|
212
|
-
const seed = seedIsolatedConfigFromLocal(agentId, version, { withAuth: opts.withAuth });
|
|
227
|
+
const seed = seedIsolatedConfigFromLocal(agentId, version, { withAuth: opts.withAuth, all: opts.all });
|
|
213
228
|
if (seed.error) {
|
|
214
229
|
seedSpinner.fail(`Config: ${seed.error}`);
|
|
215
230
|
process.exit(1);
|
|
216
231
|
}
|
|
217
232
|
else if (seed.seeded) {
|
|
218
233
|
seedSpinner.succeed(`Settings copied (${seed.from} -> ${seed.to}); your original is untouched`);
|
|
234
|
+
if (seed.skippedRuntime.length > 0) {
|
|
235
|
+
console.log(chalk.gray(` Runtime state NOT copied: ${seed.skippedRuntime.join(', ')} (regenerated as you use it; --all to include)`));
|
|
236
|
+
}
|
|
219
237
|
if (seed.skippedAuth.length > 0) {
|
|
220
238
|
console.log(chalk.gray(` Credentials NOT copied: ${seed.skippedAuth.join(', ')}`));
|
|
221
239
|
console.log(chalk.gray(' The copy signs in separately. Use --with-auth to copy them too.'));
|
|
@@ -288,15 +306,22 @@ export function registerImportCommand(program) {
|
|
|
288
306
|
.command('import')
|
|
289
307
|
.argument('<agent>', 'Agent id (e.g. openclaw, claude, codex)')
|
|
290
308
|
.description('Import an existing unmanaged agent install into agents-cli')
|
|
291
|
-
|
|
309
|
+
// NOT `--version`. The program declares `.version(VERSION)` (src/index.ts), which
|
|
310
|
+
// claims `-V, --version` globally and wins over a subcommand option of the same
|
|
311
|
+
// name — so `agents import codex --version 1.2.3` printed the CLI's own version
|
|
312
|
+
// and exited without importing anything. The flag had been unreachable since it
|
|
313
|
+
// was introduced, and the "could not determine version" error even advised using
|
|
314
|
+
// it. Renamed so it actually reaches the command.
|
|
315
|
+
.option('--as <version>', 'Version label to import as (otherwise read from package.json)')
|
|
292
316
|
.option('--from-path <path>', 'Path to the npm package dir (otherwise auto-detected from PATH)')
|
|
293
317
|
.option('--isolated', 'Copy the install into a self-contained isolated version instead of adopting it')
|
|
294
318
|
.option('--with-auth', 'With --isolated, also copy credentials into the sandbox (skipped by default)')
|
|
319
|
+
.option('--all', 'With --isolated, also copy session history, logs and caches (skipped by default)')
|
|
295
320
|
.option('-y, --yes', 'Skip the confirmation prompt')
|
|
296
321
|
.addHelpText('after', `
|
|
297
322
|
Examples:
|
|
298
323
|
$ agents import openclaw Auto-detect via PATH
|
|
299
|
-
$ agents import openclaw --
|
|
324
|
+
$ agents import openclaw --as 2026.3.8 Pin a version label
|
|
300
325
|
$ agents import openclaw --from-path /opt/homebrew/lib/node_modules/openclaw
|
|
301
326
|
|
|
302
327
|
# installScript-based agents (curl/brew installers, no npm package):
|
|
@@ -305,6 +330,11 @@ Examples:
|
|
|
305
330
|
$ agents import cursor Adopt ~/.local/bin/cursor-agent
|
|
306
331
|
$ agents import antigravity --from-path ~/.local/bin/agy
|
|
307
332
|
|
|
333
|
+
# Copy your setup into a sandbox instead of adopting it:
|
|
334
|
+
$ agents import codex --isolated New isolated copy at the local version
|
|
335
|
+
$ agents import codex --isolated --as 0.146.0 Re-seed an EXISTING isolated copy
|
|
336
|
+
$ agents import codex --isolated --with-auth ...and share credentials with it
|
|
337
|
+
|
|
308
338
|
When to use:
|
|
309
339
|
When an agent CLI is already installed globally and you want to bring it
|
|
310
340
|
under agents-cli management without reinstalling. Creates a symlink farm
|
package/dist/commands/inspect.js
CHANGED
|
@@ -21,6 +21,7 @@ import { truncate, termLink } from '../lib/format.js';
|
|
|
21
21
|
import * as yaml from 'yaml';
|
|
22
22
|
import { AGENTS, getCliState, resolveAgentName } from '../lib/agents.js';
|
|
23
23
|
import { supports } from '../lib/capabilities.js';
|
|
24
|
+
import { resolveConfiguredModel } from '../lib/models.js';
|
|
24
25
|
import { resolveSingleAgentTarget, AgentSpecError } from '../lib/agent-spec/index.js';
|
|
25
26
|
import { readMeta, getUserAgentsDir, getSystemAgentsDir, getProjectAgentsDir, getEnabledExtraRepos, } from '../lib/state.js';
|
|
26
27
|
import { getVersionHomePath } from '../lib/versions.js';
|
|
@@ -585,8 +586,10 @@ async function renderSummary(agent, version, versionHome, options) {
|
|
|
585
586
|
console.log(JSON.stringify(json, null, 2));
|
|
586
587
|
return;
|
|
587
588
|
}
|
|
588
|
-
// Plain text
|
|
589
|
-
const
|
|
589
|
+
// Plain text — model sits right beside the version, same priority (no label).
|
|
590
|
+
const configuredModel = resolveConfiguredModel(agent, version)?.model;
|
|
591
|
+
const modelPart = configuredModel ? ' ' + chalk.yellow(configuredModel) : '';
|
|
592
|
+
const head = `${chalk.bold(agent)} ${chalk.gray('@')} ${chalk.cyan(version)}${modelPart}${isDefault ? ' ' + chalk.green('[default]') : ''}`;
|
|
590
593
|
console.log('\n' + head + '\n');
|
|
591
594
|
const rows = [
|
|
592
595
|
['install', versionHome],
|
|
@@ -15,5 +15,25 @@ import { type Preset } from '../lib/profiles-presets.js';
|
|
|
15
15
|
* without mocking @inquirer/prompts.
|
|
16
16
|
*/
|
|
17
17
|
export declare function buildProfileFromCollection(name: string, preset: Preset, collected: Record<string, string>, version?: string): Profile;
|
|
18
|
+
/** Options accepted by {@link addProfile} — shared by `agents profiles add` and `agents harness add`. */
|
|
19
|
+
export interface AddProfileOptions {
|
|
20
|
+
preset?: string;
|
|
21
|
+
host?: string;
|
|
22
|
+
model?: string;
|
|
23
|
+
baseUrl?: string;
|
|
24
|
+
authProvider?: string;
|
|
25
|
+
version?: string;
|
|
26
|
+
keyStdin?: boolean;
|
|
27
|
+
force?: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Create a profile ("custom harness"). Two paths:
|
|
31
|
+
* - `--host <agent> --model <id>`: one-shot custom harness from a host + model
|
|
32
|
+
* (no preset needed). This is what makes a model like Muse Spark a named,
|
|
33
|
+
* runnable harness.
|
|
34
|
+
* - otherwise: apply a built-in preset (existing behavior).
|
|
35
|
+
* `label` only tunes the success wording (Profile vs Harness). Throws on error.
|
|
36
|
+
*/
|
|
37
|
+
export declare function addProfile(name: string, opts: AddProfileOptions, label?: 'Profile' | 'Harness'): Promise<void>;
|
|
18
38
|
/** Register the `agents profiles` command tree. */
|
|
19
39
|
export declare function registerProfilesCommands(program: Command): void;
|
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
import chalk from 'chalk';
|
|
10
10
|
import { readStdinSync } from '../lib/format.js';
|
|
11
11
|
import { spawn } from 'child_process';
|
|
12
|
-
import { listProfiles, readProfile, writeProfile, deleteProfile, profileExists, profileFromPreset, validateProfileName, getPresetForProfile, } from '../lib/profiles.js';
|
|
12
|
+
import { listProfiles, readProfile, writeProfile, deleteProfile, profileExists, profileFromPreset, profileFromHostModel, baseUrlEnvKeyForHost, authEnvKeyForHost, validateProfileName, getPresetForProfile, } from '../lib/profiles.js';
|
|
13
13
|
import { getPreset, listPresets, expandPreset } from '../lib/profiles-presets.js';
|
|
14
14
|
import { hasKeychainToken, keychainItemName, setKeychainToken, deleteKeychainToken, } from '../lib/secrets/profiles.js';
|
|
15
15
|
import { isInteractiveTerminal } from './utils.js';
|
|
16
16
|
import { getAgentsInvocation } from '../lib/daemon.js';
|
|
17
17
|
import { getActiveResourceProfileName, getResourceProfilePreset, listResourceProfileNames, setActiveResourceProfile, upsertResourceProfilePreset, validateResourceProfileName, } from '../lib/resource-profiles.js';
|
|
18
|
-
import { AGENTS } from '../lib/agents.js';
|
|
18
|
+
import { AGENTS, ALL_AGENT_IDS } from '../lib/agents.js';
|
|
19
19
|
import { listInstalledVersions, syncResourcesToVersion } from '../lib/versions.js';
|
|
20
20
|
/**
|
|
21
21
|
* Pure helper: builds a Profile from collected wizard inputs. Extracted so the
|
|
@@ -31,6 +31,7 @@ export function buildProfileFromCollection(name, preset, collected, version) {
|
|
|
31
31
|
envVar: preset.authEnvVar,
|
|
32
32
|
keychainItem: keychainItemName(preset.provider),
|
|
33
33
|
},
|
|
34
|
+
authOptional: preset.authOptional,
|
|
34
35
|
description: preset.description,
|
|
35
36
|
preset: preset.name,
|
|
36
37
|
provider: preset.provider,
|
|
@@ -65,6 +66,66 @@ async function ensureProviderToken(provider, signupUrl, fromStdin) {
|
|
|
65
66
|
setKeychainToken(item, token);
|
|
66
67
|
console.log(chalk.green(`Stored in keychain: ${item}`));
|
|
67
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Create a profile ("custom harness"). Two paths:
|
|
71
|
+
* - `--host <agent> --model <id>`: one-shot custom harness from a host + model
|
|
72
|
+
* (no preset needed). This is what makes a model like Muse Spark a named,
|
|
73
|
+
* runnable harness.
|
|
74
|
+
* - otherwise: apply a built-in preset (existing behavior).
|
|
75
|
+
* `label` only tunes the success wording (Profile vs Harness). Throws on error.
|
|
76
|
+
*/
|
|
77
|
+
export async function addProfile(name, opts, label = 'Profile') {
|
|
78
|
+
validateProfileName(name);
|
|
79
|
+
if (profileExists(name) && !opts.force) {
|
|
80
|
+
throw new Error(`${label} '${name}' already exists. Use --force to overwrite.`);
|
|
81
|
+
}
|
|
82
|
+
// One-shot host + model → custom harness, no preset required.
|
|
83
|
+
if (opts.host || opts.model) {
|
|
84
|
+
if (!opts.host || !opts.model) {
|
|
85
|
+
throw new Error('Both --host <agent> and --model <id> are required to build a harness from a host + model.');
|
|
86
|
+
}
|
|
87
|
+
if (!ALL_AGENT_IDS.includes(opts.host)) {
|
|
88
|
+
throw new Error(`Unknown host '${opts.host}'. Valid hosts: ${ALL_AGENT_IDS.join(', ')}`);
|
|
89
|
+
}
|
|
90
|
+
const host = opts.host;
|
|
91
|
+
if (opts.baseUrl && !baseUrlEnvKeyForHost(host)) {
|
|
92
|
+
console.error(chalk.yellow(`Note: --base-url has no known env var for host '${host}'; ignoring it.`));
|
|
93
|
+
}
|
|
94
|
+
let authEnvVar;
|
|
95
|
+
if (opts.authProvider) {
|
|
96
|
+
const key = authEnvKeyForHost(host);
|
|
97
|
+
if (!key) {
|
|
98
|
+
throw new Error(`--auth-provider is set but host '${host}' has no known auth env var. Use a preset or a hand-written profile YAML.`);
|
|
99
|
+
}
|
|
100
|
+
authEnvVar = key;
|
|
101
|
+
await ensureProviderToken(opts.authProvider, undefined, opts.keyStdin);
|
|
102
|
+
}
|
|
103
|
+
const profile = profileFromHostModel(name, host, opts.model, {
|
|
104
|
+
version: opts.version,
|
|
105
|
+
baseUrl: opts.baseUrl,
|
|
106
|
+
provider: opts.authProvider,
|
|
107
|
+
authEnvVar,
|
|
108
|
+
});
|
|
109
|
+
writeProfile(profile);
|
|
110
|
+
console.log(chalk.green(`${label} '${name}' added — ${host} + ${opts.model}.`));
|
|
111
|
+
console.log(chalk.gray(`Try: agents run ${name} "hello"`));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
// Preset path.
|
|
115
|
+
const presetName = opts.preset || name;
|
|
116
|
+
const preset = getPreset(presetName);
|
|
117
|
+
if (!preset) {
|
|
118
|
+
throw new Error(`No preset '${presetName}'.\nAvailable presets: ${listPresets().map((p) => p.name).join(', ')}\n` +
|
|
119
|
+
'Or build a custom harness: --host <agent> --model <id>.');
|
|
120
|
+
}
|
|
121
|
+
if (!preset.authOptional) {
|
|
122
|
+
await ensureProviderToken(preset.provider, preset.signupUrl, opts.keyStdin);
|
|
123
|
+
}
|
|
124
|
+
const profile = profileFromPreset(name, preset, opts.version);
|
|
125
|
+
writeProfile(profile);
|
|
126
|
+
console.log(chalk.green(`${label} '${name}' added.`));
|
|
127
|
+
console.log(chalk.gray(`Try: agents run ${name} "hello"`));
|
|
128
|
+
}
|
|
68
129
|
/** Format a single profile as a table row for the `profiles list` output. */
|
|
69
130
|
function renderProfileRow(p) {
|
|
70
131
|
const host = p.host.version ? `${p.host.agent}@${p.host.version}` : p.host.agent;
|
|
@@ -462,27 +523,13 @@ Examples:
|
|
|
462
523
|
.option('--version <version>', 'Pin the host CLI version (e.g., 2.1.113)')
|
|
463
524
|
.option('--key-stdin', 'Read API key from stdin instead of prompting (for scripts/CI)')
|
|
464
525
|
.option('--force', 'Overwrite an existing profile with the same name')
|
|
526
|
+
.addHelpText('after', '\nTo build a custom harness from a host CLI + model in one shot, use `agents harness add`.\n')
|
|
465
527
|
.action(async (name, opts) => {
|
|
528
|
+
// Preset-only surface here — `--host` on `profiles` is reserved for remote
|
|
529
|
+
// device routing (see lib/hosts/passthrough.ts). The host+model one-shot
|
|
530
|
+
// lives on `agents harness add`, which owns its own `--host`.
|
|
466
531
|
try {
|
|
467
|
-
|
|
468
|
-
console.error(chalk.red(`Profile '${name}' already exists. Use --force to overwrite.`));
|
|
469
|
-
process.exit(1);
|
|
470
|
-
}
|
|
471
|
-
const presetName = opts.preset || name;
|
|
472
|
-
const preset = getPreset(presetName);
|
|
473
|
-
if (!preset) {
|
|
474
|
-
console.error(chalk.red(`No preset '${presetName}'.`));
|
|
475
|
-
console.error(chalk.gray('Available presets: ' + listPresets().map((p) => p.name).join(', ')));
|
|
476
|
-
console.error(chalk.gray('Or pass --preset <name> to pick explicitly.'));
|
|
477
|
-
process.exit(1);
|
|
478
|
-
}
|
|
479
|
-
if (!preset.authOptional) {
|
|
480
|
-
await ensureProviderToken(preset.provider, preset.signupUrl, opts.keyStdin);
|
|
481
|
-
}
|
|
482
|
-
const profile = profileFromPreset(name, preset, opts.version);
|
|
483
|
-
writeProfile(profile);
|
|
484
|
-
console.log(chalk.green(`Profile '${name}' added.`));
|
|
485
|
-
console.log(chalk.gray(`Try: agents run ${name} "hello"`));
|
|
532
|
+
await addProfile(name, { preset: opts.preset, version: opts.version, keyStdin: opts.keyStdin, force: opts.force }, 'Profile');
|
|
486
533
|
}
|
|
487
534
|
catch (err) {
|
|
488
535
|
console.error(chalk.red(err.message));
|
package/dist/commands/repo.js
CHANGED
|
@@ -812,6 +812,7 @@ export function registerRepoCommands(program) {
|
|
|
812
812
|
console.log(chalk.gray('No repos to pull.'));
|
|
813
813
|
return;
|
|
814
814
|
}
|
|
815
|
+
let anyPulled = false;
|
|
815
816
|
for (const t of targets) {
|
|
816
817
|
if (!fs.existsSync(t.dir) || !isGitRepo(t.dir)) {
|
|
817
818
|
// A plain (never-cloned) user repo — setup makes ~/.agents a bare dir and
|
|
@@ -851,11 +852,24 @@ export function registerRepoCommands(program) {
|
|
|
851
852
|
const result = await pullRepo(t.dir);
|
|
852
853
|
if (result.success) {
|
|
853
854
|
spinner.succeed(`${formatRepoTarget(t.alias, t.dir, result.branch)}: ${result.commit}`);
|
|
855
|
+
anyPulled = true;
|
|
854
856
|
}
|
|
855
857
|
else {
|
|
856
858
|
spinner.fail(`${formatRepoTarget(t.alias, t.dir)}: ${result.error}`);
|
|
857
859
|
}
|
|
858
860
|
}
|
|
861
|
+
// RUSH-1980: a pull rewrites the routine YAML on disk, but the daemon's
|
|
862
|
+
// scheduler froze its JobConfigs (device pins included) at load. Without a
|
|
863
|
+
// reload it keeps firing the pre-pull pins — a routine re-pinned to another
|
|
864
|
+
// host still fires here, double-firing across the fleet. SIGHUP the daemon
|
|
865
|
+
// so scheduler.reloadAll() re-reads the synced YAML and device pins refresh.
|
|
866
|
+
// No-op when the daemon isn't running (or on Windows, which has no SIGHUP).
|
|
867
|
+
if (anyPulled) {
|
|
868
|
+
const { isDaemonRunning, signalDaemonReload } = await import('../lib/daemon.js');
|
|
869
|
+
if (isDaemonRunning() && signalDaemonReload()) {
|
|
870
|
+
console.log(chalk.gray('Reloaded the routines daemon (device pins refreshed).'));
|
|
871
|
+
}
|
|
872
|
+
}
|
|
859
873
|
});
|
|
860
874
|
repoCmd
|
|
861
875
|
.command('push [alias]')
|