@phnx-labs/agents-cli 1.20.72 → 1.20.74
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 +395 -0
- package/README.md +40 -4
- package/dist/bin/agents +0 -0
- package/dist/commands/activity.d.ts +12 -0
- package/dist/commands/activity.js +68 -0
- package/dist/commands/apply.d.ts +13 -0
- package/dist/commands/apply.js +28 -3
- package/dist/commands/browser.js +1 -1
- package/dist/commands/cloud.js +21 -11
- package/dist/commands/doctor.js +21 -5
- package/dist/commands/events.d.ts +11 -10
- package/dist/commands/events.js +30 -21
- package/dist/commands/exec.d.ts +34 -0
- package/dist/commands/exec.js +337 -39
- package/dist/commands/export.d.ts +11 -0
- package/dist/commands/export.js +215 -0
- package/dist/commands/feed.d.ts +13 -0
- package/dist/commands/feed.js +130 -29
- package/dist/commands/hq.d.ts +2 -0
- package/dist/commands/hq.js +58 -0
- package/dist/commands/import.js +51 -3
- package/dist/commands/lease.d.ts +19 -0
- package/dist/commands/lease.js +138 -11
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +123 -0
- package/dist/commands/logs.js +1 -1
- package/dist/commands/mine.d.ts +27 -0
- package/dist/commands/mine.js +207 -0
- package/dist/commands/monitors.js +79 -49
- package/dist/commands/plugins.js +1 -0
- package/dist/commands/profiles.js +158 -0
- package/dist/commands/repo.js +2 -60
- package/dist/commands/resources.d.ts +5 -0
- package/dist/commands/resources.js +95 -0
- package/dist/commands/routines.d.ts +2 -0
- package/dist/commands/routines.js +139 -37
- package/dist/commands/secrets.js +234 -47
- package/dist/commands/send.d.ts +15 -0
- package/dist/commands/send.js +62 -0
- package/dist/commands/sessions-tail.d.ts +1 -0
- package/dist/commands/sessions-tail.js +20 -9
- package/dist/commands/sessions.js +18 -3
- package/dist/commands/setup-mine.d.ts +18 -0
- package/dist/commands/setup-mine.js +106 -0
- package/dist/commands/setup-share.js +2 -2
- package/dist/commands/setup.js +9 -2
- package/dist/commands/share.d.ts +8 -1
- package/dist/commands/share.js +122 -37
- package/dist/commands/ssh.d.ts +9 -0
- package/dist/commands/ssh.js +196 -26
- package/dist/commands/sync.js +1 -1
- package/dist/commands/teams.js +11 -2
- package/dist/commands/versions.js +36 -5
- package/dist/commands/view.js +75 -27
- package/dist/index.js +80 -10
- package/dist/lib/activity.d.ts +124 -0
- package/dist/lib/activity.js +542 -0
- package/dist/lib/agents.d.ts +35 -0
- package/dist/lib/agents.js +79 -6
- package/dist/lib/artifact-actions.d.ts +1 -1
- package/dist/lib/artifact-actions.js +1 -1
- package/dist/lib/ask-classifier.d.ts +2 -1
- package/dist/lib/ask-classifier.js +3 -3
- package/dist/lib/auth-health.d.ts +32 -4
- package/dist/lib/auth-health.js +40 -1
- package/dist/lib/auto-pull.js +8 -1
- package/dist/lib/brand.d.ts +40 -0
- package/dist/lib/brand.js +122 -0
- package/dist/lib/browser/drivers/ssh.js +4 -7
- package/dist/lib/browser/service.js +6 -8
- package/dist/lib/channels/providers/index.d.ts +2 -0
- package/dist/lib/channels/providers/index.js +20 -0
- package/dist/lib/channels/providers/mailbox.d.ts +2 -0
- package/dist/lib/channels/providers/mailbox.js +26 -0
- package/dist/lib/channels/providers/openclaw-telegram.d.ts +2 -0
- package/dist/lib/channels/providers/openclaw-telegram.js +34 -0
- package/dist/lib/channels/providers/rush.d.ts +6 -0
- package/dist/lib/channels/providers/rush.js +55 -0
- package/dist/lib/channels/registry.d.ts +42 -0
- package/dist/lib/channels/registry.js +20 -0
- package/dist/lib/channels/resolve.d.ts +11 -0
- package/dist/lib/channels/resolve.js +12 -0
- package/dist/lib/cli-resources.d.ts +18 -0
- package/dist/lib/cli-resources.js +53 -4
- package/dist/lib/cloud/codex.js +6 -3
- package/dist/lib/cloud/factory.d.ts +1 -0
- package/dist/lib/cloud/factory.js +10 -4
- package/dist/lib/cloud/rush.d.ts +3 -1
- package/dist/lib/cloud/rush.js +6 -1
- package/dist/lib/cloud/types.d.ts +2 -0
- package/dist/lib/codex-home.d.ts +35 -0
- package/dist/lib/codex-home.js +136 -0
- package/dist/lib/config-transfer.d.ts +16 -0
- package/dist/lib/config-transfer.js +56 -0
- package/dist/lib/crabbox/cli.d.ts +44 -0
- package/dist/lib/crabbox/cli.js +120 -20
- package/dist/lib/crabbox/lease.d.ts +52 -1
- package/dist/lib/crabbox/lease.js +117 -16
- package/dist/lib/crabbox/progress.d.ts +31 -0
- package/dist/lib/crabbox/progress.js +76 -0
- package/dist/lib/crabbox/runtimes.d.ts +4 -0
- package/dist/lib/crabbox/runtimes.js +33 -8
- package/dist/lib/crabbox/setup-copy.d.ts +87 -0
- package/dist/lib/crabbox/setup-copy.js +127 -0
- package/dist/lib/daemon.d.ts +5 -5
- package/dist/lib/daemon.js +9 -9
- package/dist/lib/drive-sync.js +6 -3
- package/dist/lib/event-stream.d.ts +36 -0
- package/dist/lib/event-stream.js +68 -0
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/exec.d.ts +11 -0
- package/dist/lib/exec.js +23 -2
- package/dist/lib/export.d.ts +72 -0
- package/dist/lib/export.js +269 -0
- package/dist/lib/feed-policy.d.ts +1 -1
- package/dist/lib/feed-policy.js +14 -9
- package/dist/lib/feed-ranking.d.ts +32 -0
- package/dist/lib/feed-ranking.js +224 -0
- package/dist/lib/feed.d.ts +35 -2
- package/dist/lib/feed.js +66 -3
- package/dist/lib/fleet/auth-sync.d.ts +53 -0
- package/dist/lib/fleet/auth-sync.js +92 -0
- package/dist/lib/fleet/remote-login.d.ts +187 -0
- package/dist/lib/fleet/remote-login.js +556 -0
- package/dist/lib/format.d.ts +30 -3
- package/dist/lib/format.js +34 -5
- package/dist/lib/fs-atomic.d.ts +7 -2
- package/dist/lib/fs-atomic.js +8 -12
- package/dist/lib/git.d.ts +16 -0
- package/dist/lib/git.js +79 -2
- package/dist/lib/heal.js +11 -3
- package/dist/lib/hosts/progress.d.ts +28 -10
- package/dist/lib/hosts/progress.js +79 -22
- package/dist/lib/hosts/remote-cmd.js +4 -0
- package/dist/lib/hq/floor.d.ts +87 -0
- package/dist/lib/hq/floor.js +226 -0
- package/dist/lib/import.d.ts +23 -0
- package/dist/lib/import.js +74 -2
- package/dist/lib/isolation-boundary-report.d.ts +10 -0
- package/dist/lib/isolation-boundary-report.js +35 -0
- package/dist/lib/menubar/install-menubar.js +14 -20
- package/dist/lib/notify.d.ts +1 -0
- package/dist/lib/notify.js +3 -3
- package/dist/lib/open-url.d.ts +2 -0
- package/dist/lib/open-url.js +19 -0
- package/dist/lib/openclaw-keychain.d.ts +56 -0
- package/dist/lib/openclaw-keychain.js +236 -0
- package/dist/lib/overdue.js +10 -0
- package/dist/lib/permissions.d.ts +44 -1
- package/dist/lib/permissions.js +284 -7
- package/dist/lib/project-launch.d.ts +6 -12
- package/dist/lib/project-launch.js +13 -228
- package/dist/lib/project-resources.d.ts +7 -0
- package/dist/lib/project-resources.js +306 -0
- package/dist/lib/pty-client.d.ts +27 -0
- package/dist/lib/pty-client.js +136 -10
- package/dist/lib/refresh.js +14 -10
- package/dist/lib/resource-profiles.d.ts +26 -0
- package/dist/lib/resource-profiles.js +157 -0
- package/dist/lib/resources/permissions.js +7 -1
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/resources.d.ts +1 -1
- package/dist/lib/resources.js +32 -3
- package/dist/lib/routines.d.ts +11 -0
- package/dist/lib/routines.js +56 -15
- package/dist/lib/runner.d.ts +1 -0
- package/dist/lib/runner.js +66 -5
- package/dist/lib/secrets/bundles.d.ts +16 -3
- package/dist/lib/secrets/bundles.js +206 -37
- package/dist/lib/secrets/icloud-import.d.ts +2 -2
- package/dist/lib/secrets/icloud-import.js +9 -6
- package/dist/lib/secrets/mcp.js +1 -1
- package/dist/lib/secrets/vault-age-helper.d.ts +1 -0
- package/dist/lib/secrets/vault-age-helper.js +34 -0
- package/dist/lib/secrets/vault.d.ts +49 -0
- package/dist/lib/secrets/vault.js +397 -0
- package/dist/lib/self-heal/checks/path.js +3 -1
- package/dist/lib/self-heal/checks/shadowing.js +10 -2
- package/dist/lib/self-heal/checks/shims.js +19 -11
- package/dist/lib/session/cloud.d.ts +2 -2
- package/dist/lib/session/cloud.js +2 -2
- package/dist/lib/session/db.d.ts +4 -0
- package/dist/lib/session/db.js +44 -7
- package/dist/lib/session/discover.d.ts +2 -0
- package/dist/lib/session/discover.js +114 -3
- package/dist/lib/session/stream-render.d.ts +3 -0
- package/dist/lib/session/stream-render.js +130 -0
- package/dist/lib/session/types.d.ts +6 -0
- package/dist/lib/share/analytics.d.ts +13 -0
- package/dist/lib/share/analytics.js +45 -0
- package/dist/lib/share/config.d.ts +19 -5
- package/dist/lib/share/config.js +44 -8
- package/dist/lib/share/provision.d.ts +58 -6
- package/dist/lib/share/provision.js +97 -22
- package/dist/lib/share/publish.d.ts +36 -13
- package/dist/lib/share/publish.js +55 -8
- package/dist/lib/share/worker-template.js +83 -17
- package/dist/lib/shims.d.ts +35 -0
- package/dist/lib/shims.js +159 -13
- package/dist/lib/ssh-exec.d.ts +14 -0
- package/dist/lib/ssh-exec.js +57 -0
- package/dist/lib/staleness/detectors/hooks.js +25 -1
- package/dist/lib/staleness/detectors/permissions.js +66 -0
- package/dist/lib/staleness/detectors/workflows.js +20 -0
- package/dist/lib/staleness/writers/hooks.js +58 -4
- package/dist/lib/staleness/writers/permissions.js +2 -2
- package/dist/lib/staleness/writers/skills.js +1 -1
- package/dist/lib/staleness/writers/sources.d.ts +10 -1
- package/dist/lib/staleness/writers/sources.js +68 -1
- package/dist/lib/star-nudge.d.ts +45 -0
- package/dist/lib/star-nudge.js +91 -0
- package/dist/lib/startup/command-registry.d.ts +7 -1
- package/dist/lib/startup/command-registry.js +19 -3
- package/dist/lib/state.d.ts +2 -0
- package/dist/lib/state.js +17 -4
- package/dist/lib/subagents-registry.d.ts +2 -0
- package/dist/lib/subagents-registry.js +3 -0
- package/dist/lib/teams/parsers.js +214 -6
- package/dist/lib/teams/supervisor.d.ts +7 -0
- package/dist/lib/teams/supervisor.js +2 -1
- package/dist/lib/template.d.ts +1 -1
- package/dist/lib/template.js +1 -1
- package/dist/lib/triggers/webhook.js +36 -3
- package/dist/lib/types.d.ts +84 -0
- package/dist/lib/uninstall.js +1 -45
- package/dist/lib/version.d.ts +40 -0
- package/dist/lib/version.js +94 -16
- package/dist/lib/versions.d.ts +24 -0
- package/dist/lib/versions.js +269 -78
- package/dist/lib/workflows.d.ts +2 -0
- package/dist/lib/workflows.js +88 -0
- package/package.json +2 -1
- package/dist/commands/daemon.d.ts +0 -10
- package/dist/commands/daemon.js +0 -121
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Primitives for moving an agent config directory across the agents-cli boundary.
|
|
3
|
+
*
|
|
4
|
+
* Both helpers were part of {@link ./uninstall.ts} and are unchanged — teardown was
|
|
5
|
+
* simply the first caller. `agents export` needs the same two operations (relocate a
|
|
6
|
+
* directory that may sit on another volume; copy one without dragging `~/.agents`
|
|
7
|
+
* symlinks along), so they live here rather than being duplicated or imported out of
|
|
8
|
+
* a module named for teardown.
|
|
9
|
+
*/
|
|
10
|
+
import * as fs from 'fs';
|
|
11
|
+
import * as path from 'path';
|
|
12
|
+
/**
|
|
13
|
+
* Move `source` onto `dest` across possibly-different volumes. `renameSync` is
|
|
14
|
+
* atomic but throws EXDEV when `~/.agents` lives on a different filesystem than
|
|
15
|
+
* `$HOME`; fall back to copy-then-remove so the restore still completes. The
|
|
16
|
+
* source is removed only after the copy succeeds, so a mid-copy failure never
|
|
17
|
+
* destroys the sole surviving copy.
|
|
18
|
+
*/
|
|
19
|
+
export function moveDirCrossDevice(source, dest) {
|
|
20
|
+
try {
|
|
21
|
+
fs.renameSync(source, dest);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
if (err.code !== 'EXDEV')
|
|
25
|
+
throw err;
|
|
26
|
+
fs.cpSync(source, dest, { recursive: true });
|
|
27
|
+
fs.rmSync(source, { recursive: true, force: true });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Copy `source` to `dest`, dropping any symlink whose target resolves back into
|
|
32
|
+
* `~/.agents`. Managed resources (skills/commands) are synced into a version home
|
|
33
|
+
* as symlinks into `~/.agents`; copying them verbatim would leave the result full
|
|
34
|
+
* of links that dangle the moment `~/.agents` is disposed. Stripping them yields a
|
|
35
|
+
* clean, self-contained copy — which is the entire point of an export.
|
|
36
|
+
*/
|
|
37
|
+
export function copyDirStrippingAgentsSymlinks(source, dest, agentsDir) {
|
|
38
|
+
const inside = agentsDir + path.sep;
|
|
39
|
+
fs.cpSync(source, dest, {
|
|
40
|
+
recursive: true,
|
|
41
|
+
filter: (src) => {
|
|
42
|
+
try {
|
|
43
|
+
const st = fs.lstatSync(src);
|
|
44
|
+
if (st.isSymbolicLink()) {
|
|
45
|
+
const tgt = path.resolve(path.dirname(src), fs.readlinkSync(src));
|
|
46
|
+
if (tgt === agentsDir || tgt.startsWith(inside))
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
/* unreadable entry — let cpSync surface it on the real copy */
|
|
52
|
+
}
|
|
53
|
+
return true;
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -26,6 +26,10 @@ export interface CrabboxBox {
|
|
|
26
26
|
state: string;
|
|
27
27
|
/** Public IPv4, when the provider exposes one. */
|
|
28
28
|
ip?: string;
|
|
29
|
+
/** Tailnet IPv4 (100.x), when the box was leased with `--network tailscale`. */
|
|
30
|
+
tailscaleIPv4?: string;
|
|
31
|
+
/** Tailnet MagicDNS FQDN, when the box joined the tailnet. */
|
|
32
|
+
tailscaleFQDN?: string;
|
|
29
33
|
profile?: string;
|
|
30
34
|
class?: string;
|
|
31
35
|
/** True when running + bootstrap-complete. */
|
|
@@ -49,6 +53,13 @@ export interface CrabboxOptions {
|
|
|
49
53
|
* `crabbox login` credentials.
|
|
50
54
|
*/
|
|
51
55
|
secretsBundle?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Hard cap (ms) on a single crabbox invocation that hits the provider API
|
|
58
|
+
* (`list`). Prevents a slow/unreachable provider from hanging an ambient
|
|
59
|
+
* command like `agents devices`. Defaults to 8s; callers on a fast/interactive
|
|
60
|
+
* path (devices list, `agents ssh` fall-through) pass a shorter bound.
|
|
61
|
+
*/
|
|
62
|
+
timeoutMs?: number;
|
|
52
63
|
}
|
|
53
64
|
/** Locate the crabbox binary, or throw an actionable error. */
|
|
54
65
|
export declare function findCrabbox(): string;
|
|
@@ -61,6 +72,17 @@ export declare function findCrabbox(): string;
|
|
|
61
72
|
export declare const LEASE_PROVIDER_TOKEN_KEYS: string[];
|
|
62
73
|
/** The first bundle that declares a provider token key, or undefined. Pure over `bundles`. */
|
|
63
74
|
export declare function pickLeaseBundleFromList(bundles: SecretsBundle[]): string | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* Env keys a bundle may use for a Tailscale auth key. crabbox reads
|
|
77
|
+
* `CRABBOX_TAILSCALE_AUTH_KEY` to join a leased box to the tailnet; we accept the
|
|
78
|
+
* common alternate names too and rename to that canonical key on injection.
|
|
79
|
+
*/
|
|
80
|
+
export declare const TAILSCALE_AUTH_KEY_NAMES: string[];
|
|
81
|
+
/** The first bundle + key that declares a Tailscale auth key, or undefined. Pure over `bundles`. */
|
|
82
|
+
export declare function pickTailscaleBundleFromList(bundles: SecretsBundle[]): {
|
|
83
|
+
name: string;
|
|
84
|
+
key: string;
|
|
85
|
+
} | undefined;
|
|
64
86
|
/** A resolved lease bundle: its name, plus (auto-detect only) the exact keys to inject. */
|
|
65
87
|
export interface ResolvedLeaseBundle {
|
|
66
88
|
name: string;
|
|
@@ -97,6 +119,13 @@ export interface WarmupOptions extends CrabboxOptions {
|
|
|
97
119
|
code?: boolean;
|
|
98
120
|
/** Cloud backend override (crabbox provider id, e.g. hetzner/aws/do). */
|
|
99
121
|
provider?: string;
|
|
122
|
+
/**
|
|
123
|
+
* Network mode for the leased box. `'public'` (default) leases with a public
|
|
124
|
+
* IP; `'tailscale'` joins the box to the tailnet (`--network tailscale`,
|
|
125
|
+
* tagged `tag:crabbox`) so it is reachable only over Tailscale. The caller
|
|
126
|
+
* (command layer) decides WHEN to enable this — F5 here is plumbing only.
|
|
127
|
+
*/
|
|
128
|
+
netMode?: 'public' | 'tailscale';
|
|
100
129
|
}
|
|
101
130
|
/**
|
|
102
131
|
* Lease a box and block until it is ready. Returns the leased box.
|
|
@@ -137,6 +166,21 @@ export declare function crabboxRun(slug: string, remoteCmd: string, opts?: Crabb
|
|
|
137
166
|
* Streams combined output; resolves with the remote exit code (null on dispatch failure).
|
|
138
167
|
*/
|
|
139
168
|
export declare function crabboxRunScript(slug: string, script: string, opts?: CrabboxRunOptions): Promise<number | null>;
|
|
169
|
+
/**
|
|
170
|
+
* Parse crabbox's shell-quoted `ssh` command (`'ssh' '-i' '<key>' … 'crabbox@ip'`)
|
|
171
|
+
* into an argv array. Exported for testing.
|
|
172
|
+
*/
|
|
173
|
+
export declare function parseCrabboxSshArgv(stdout: string): string[] | null;
|
|
174
|
+
/**
|
|
175
|
+
* The concrete `ssh` argv crabbox uses to reach box `slug` — including the
|
|
176
|
+
* per-lease identity key and known_hosts crabbox provisions under its own config
|
|
177
|
+
* dir. A raw `ssh crabbox@ip` fails `publickey`; this is the only auth that works.
|
|
178
|
+
* `crabbox ssh --id` PRINTS the command (it doesn't connect — that's `crabbox
|
|
179
|
+
* connect`), and `--reclaim` makes it resolve regardless of which repo holds the
|
|
180
|
+
* lease claim (matching `crabboxRunScript`). Returns null when crabbox can't
|
|
181
|
+
* resolve the box (caller treats setup-copy / `agents ssh` as best-effort).
|
|
182
|
+
*/
|
|
183
|
+
export declare function crabboxSshArgv(slug: string, opts?: CrabboxOptions): string[] | null;
|
|
140
184
|
/** Release the lease / delete the box. Best-effort; never throws. */
|
|
141
185
|
export declare function crabboxStop(slug: string, opts?: CrabboxOptions): boolean;
|
|
142
186
|
/** Never reap a box touched within this many seconds, regardless of idle-timeout. */
|
package/dist/lib/crabbox/cli.js
CHANGED
|
@@ -37,6 +37,36 @@ export function pickLeaseBundleFromList(bundles) {
|
|
|
37
37
|
}
|
|
38
38
|
return undefined;
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Env keys a bundle may use for a Tailscale auth key. crabbox reads
|
|
42
|
+
* `CRABBOX_TAILSCALE_AUTH_KEY` to join a leased box to the tailnet; we accept the
|
|
43
|
+
* common alternate names too and rename to that canonical key on injection.
|
|
44
|
+
*/
|
|
45
|
+
export const TAILSCALE_AUTH_KEY_NAMES = ['CRABBOX_TAILSCALE_AUTH_KEY', 'TAILSCALE_AUTH_KEY', 'TS_AUTHKEY'];
|
|
46
|
+
/** The first bundle + key that declares a Tailscale auth key, or undefined. Pure over `bundles`. */
|
|
47
|
+
export function pickTailscaleBundleFromList(bundles) {
|
|
48
|
+
for (const b of bundles) {
|
|
49
|
+
const key = Object.keys(b.vars ?? {}).find((k) => TAILSCALE_AUTH_KEY_NAMES.includes(k));
|
|
50
|
+
if (key)
|
|
51
|
+
return { name: b.name, key };
|
|
52
|
+
}
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
/** Process-lifetime memo so the tailscale bundle `listBundles()` scan runs at most once. */
|
|
56
|
+
let tailscaleBundleMemo;
|
|
57
|
+
function resolveTailscaleBundleMemo() {
|
|
58
|
+
if (!tailscaleBundleMemo) {
|
|
59
|
+
let value;
|
|
60
|
+
try {
|
|
61
|
+
value = pickTailscaleBundleFromList(listBundles());
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
/* secrets unreadable — no auto-detect */
|
|
65
|
+
}
|
|
66
|
+
tailscaleBundleMemo = { value };
|
|
67
|
+
}
|
|
68
|
+
return tailscaleBundleMemo.value;
|
|
69
|
+
}
|
|
40
70
|
/**
|
|
41
71
|
* The secrets bundle to feed crabbox, resolved in priority order:
|
|
42
72
|
* 1. `AGENTS_LEASE_SECRETS_BUNDLE` env var — explicit, no keychain
|
|
@@ -92,30 +122,55 @@ export function setLeaseSecretsBundle(name) {
|
|
|
92
122
|
}
|
|
93
123
|
/** Build the child env for crabbox, injecting a secrets bundle when configured. */
|
|
94
124
|
export function crabboxEnv(opts) {
|
|
125
|
+
const out = { ...process.env };
|
|
95
126
|
const resolved = opts.secretsBundle
|
|
96
127
|
? { name: opts.secretsBundle }
|
|
97
128
|
: resolveLeaseBundleMemo();
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
129
|
+
if (resolved) {
|
|
130
|
+
try {
|
|
131
|
+
// Auto-detected bundle → inject ONLY the provider token key(s) (least
|
|
132
|
+
// privilege; an unrelated bundle can't leak its other secrets into crabbox).
|
|
133
|
+
// An explicitly-named bundle (env/config or `opts.secretsBundle`) injects
|
|
134
|
+
// whole — the user chose it. Same resolver `agents secrets exec` uses.
|
|
135
|
+
const { env } = readAndResolveBundleEnv(resolved.name, {
|
|
136
|
+
caller: 'agents run --lease (crabbox)',
|
|
137
|
+
keys: resolved.keys,
|
|
138
|
+
// --lease is headless by contract and crabboxEnv is called several times
|
|
139
|
+
// per run (list/wait/spawn/stop) — resolve broker-only so a keychain bundle
|
|
140
|
+
// can't pop repeated unwatched Touch ID sheets mid-lease.
|
|
141
|
+
agentOnly: isHeadlessSecretsContext(),
|
|
142
|
+
});
|
|
143
|
+
Object.assign(out, env);
|
|
144
|
+
}
|
|
145
|
+
catch (e) {
|
|
146
|
+
throw new Error(`Could not load secrets bundle "${resolved.name}" for crabbox: ${e.message}. ` +
|
|
147
|
+
`Fix the bundle (agents secrets view ${resolved.name}) or unset lease.secretsBundle to use crabbox's own login.`);
|
|
148
|
+
}
|
|
114
149
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
150
|
+
// Tailscale plumbing (F5): inject CRABBOX_TAILSCALE_AUTH_KEY from a bundle that
|
|
151
|
+
// declares a tailscale auth key, when the ambient env doesn't already set one.
|
|
152
|
+
// Best-effort and opt-in — a missing/unreadable tailscale bundle never fails a
|
|
153
|
+
// public-network lease; `crabboxWarmup({ netMode: 'tailscale' })` is what decides
|
|
154
|
+
// whether the key is actually used.
|
|
155
|
+
if (!out.CRABBOX_TAILSCALE_AUTH_KEY) {
|
|
156
|
+
const ts = resolveTailscaleBundleMemo();
|
|
157
|
+
if (ts) {
|
|
158
|
+
try {
|
|
159
|
+
const { env } = readAndResolveBundleEnv(ts.name, {
|
|
160
|
+
caller: 'agents run --lease (crabbox tailscale)',
|
|
161
|
+
keys: [ts.key],
|
|
162
|
+
agentOnly: isHeadlessSecretsContext(),
|
|
163
|
+
});
|
|
164
|
+
const value = env[ts.key];
|
|
165
|
+
if (value)
|
|
166
|
+
out.CRABBOX_TAILSCALE_AUTH_KEY = value;
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
/* best-effort — tailscale is opt-in plumbing, never blocks a public lease */
|
|
170
|
+
}
|
|
171
|
+
}
|
|
118
172
|
}
|
|
173
|
+
return out;
|
|
119
174
|
}
|
|
120
175
|
function normalizeBox(raw) {
|
|
121
176
|
const labels = (raw.labels ?? {});
|
|
@@ -138,6 +193,8 @@ function normalizeBox(raw) {
|
|
|
138
193
|
lease: labels.lease ?? '',
|
|
139
194
|
state,
|
|
140
195
|
ip: publicNet.ipv4?.ip || undefined,
|
|
196
|
+
tailscaleIPv4: labels.tailscale_ipv4 || undefined,
|
|
197
|
+
tailscaleFQDN: labels.tailscale_fqdn || undefined,
|
|
141
198
|
profile: labels.profile,
|
|
142
199
|
class: labels.class,
|
|
143
200
|
ready: status === 'running' && state === 'ready',
|
|
@@ -151,7 +208,11 @@ function normalizeBox(raw) {
|
|
|
151
208
|
/** All crabbox machines the broker knows about. */
|
|
152
209
|
export function crabboxList(opts = {}) {
|
|
153
210
|
findCrabbox();
|
|
154
|
-
const
|
|
211
|
+
const timeoutMs = opts.timeoutMs ?? 8000;
|
|
212
|
+
const r = spawnSync('crabbox', ['list', '--json'], { encoding: 'utf-8', env: crabboxEnv(opts), timeout: timeoutMs });
|
|
213
|
+
if (r.error && r.error.code === 'ETIMEDOUT') {
|
|
214
|
+
throw new Error(`crabbox list timed out after ${Math.round(timeoutMs / 1000)}s (provider slow or unreachable)`);
|
|
215
|
+
}
|
|
155
216
|
if (r.status !== 0) {
|
|
156
217
|
throw new Error(`crabbox list failed: ${(r.stderr || r.stdout || '').trim() || 'unknown error'}`);
|
|
157
218
|
}
|
|
@@ -190,6 +251,10 @@ export async function crabboxWarmup(opts = {}) {
|
|
|
190
251
|
args.push('--provider', opts.provider);
|
|
191
252
|
if (opts.code)
|
|
192
253
|
args.push('--code');
|
|
254
|
+
// Tailscale plumbing (F5): join the box to the tailnet, tagged tag:crabbox.
|
|
255
|
+
// The auth key rides the child env as CRABBOX_TAILSCALE_AUTH_KEY (see crabboxEnv).
|
|
256
|
+
if (opts.netMode === 'tailscale')
|
|
257
|
+
args.push('--network', 'tailscale', '-tailscale-tags', 'tag:crabbox');
|
|
193
258
|
// Async spawn (not spawnSync): provisioning takes 30-90s and a blocking call
|
|
194
259
|
// would freeze any caller's progress spinner. Output is captured, not streamed.
|
|
195
260
|
const r = await new Promise((resolve) => {
|
|
@@ -324,6 +389,41 @@ export function crabboxRunScript(slug, script, opts = {}) {
|
|
|
324
389
|
proc.stdin.end();
|
|
325
390
|
});
|
|
326
391
|
}
|
|
392
|
+
/**
|
|
393
|
+
* Parse crabbox's shell-quoted `ssh` command (`'ssh' '-i' '<key>' … 'crabbox@ip'`)
|
|
394
|
+
* into an argv array. Exported for testing.
|
|
395
|
+
*/
|
|
396
|
+
export function parseCrabboxSshArgv(stdout) {
|
|
397
|
+
for (const raw of stdout.split('\n')) {
|
|
398
|
+
const line = raw.trim();
|
|
399
|
+
if (!line.startsWith("'ssh'") && !line.startsWith('ssh '))
|
|
400
|
+
continue;
|
|
401
|
+
const toks = [...line.matchAll(/'([^']*)'|(\S+)/g)].map((m) => m[1] ?? m[2]);
|
|
402
|
+
if (toks[0] === 'ssh' && toks.length >= 3)
|
|
403
|
+
return toks;
|
|
404
|
+
}
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* The concrete `ssh` argv crabbox uses to reach box `slug` — including the
|
|
409
|
+
* per-lease identity key and known_hosts crabbox provisions under its own config
|
|
410
|
+
* dir. A raw `ssh crabbox@ip` fails `publickey`; this is the only auth that works.
|
|
411
|
+
* `crabbox ssh --id` PRINTS the command (it doesn't connect — that's `crabbox
|
|
412
|
+
* connect`), and `--reclaim` makes it resolve regardless of which repo holds the
|
|
413
|
+
* lease claim (matching `crabboxRunScript`). Returns null when crabbox can't
|
|
414
|
+
* resolve the box (caller treats setup-copy / `agents ssh` as best-effort).
|
|
415
|
+
*/
|
|
416
|
+
export function crabboxSshArgv(slug, opts = {}) {
|
|
417
|
+
findCrabbox();
|
|
418
|
+
const r = spawnSync('crabbox', ['ssh', '--id', slug, '--reclaim'], {
|
|
419
|
+
encoding: 'utf-8',
|
|
420
|
+
env: crabboxEnv(opts),
|
|
421
|
+
timeout: opts.timeoutMs ?? 15000,
|
|
422
|
+
});
|
|
423
|
+
if (r.status !== 0 || !r.stdout)
|
|
424
|
+
return null;
|
|
425
|
+
return parseCrabboxSshArgv(r.stdout);
|
|
426
|
+
}
|
|
327
427
|
/** Release the lease / delete the box. Best-effort; never throws. */
|
|
328
428
|
export function crabboxStop(slug, opts = {}) {
|
|
329
429
|
try {
|
|
@@ -5,6 +5,24 @@
|
|
|
5
5
|
* credentials → run the agent on the box (via `crabbox run`, which owns the
|
|
6
6
|
* SSH) → tear the box down. The whole box-side sequence rides a single
|
|
7
7
|
* `--script-stdin` body so the token contents never touch argv.
|
|
8
|
+
*
|
|
9
|
+
* ── Command-layer contract (RUSH-1920/1921/1924) ─────────────────────────────
|
|
10
|
+
* Exports the commands layer (exec.ts / lease.ts / ssh.ts) consumes:
|
|
11
|
+
* • `buildBootstrapScript(opts)` — `opts.copySetup` (default TRUE; clear for
|
|
12
|
+
* --bare) gates the `copy-setup` progress sentinel; `opts.netMode`
|
|
13
|
+
* ('public' | 'tailscale', default 'public') adds the `joined-tailnet` step.
|
|
14
|
+
* • `LeaseRunOptions.copySetup` / `LeaseRunOptions.netMode` — forwarded by
|
|
15
|
+
* `leaseAndRun` (netMode → `crabboxWarmup`).
|
|
16
|
+
* • `crabboxWarmup(opts.netMode)` — 'tailscale' leases onto the tailnet
|
|
17
|
+
* (`--network tailscale -tailscale-tags tag:crabbox`); auth key rides the
|
|
18
|
+
* child env as `CRABBOX_TAILSCALE_AUTH_KEY` (crabboxEnv, cli.ts).
|
|
19
|
+
* • `CrabboxBox.tailscaleIPv4` / `CrabboxBox.tailscaleFQDN` — populated from
|
|
20
|
+
* the box labels by `normalizeBox` (cli.ts).
|
|
21
|
+
* • Step stream (progress.ts): `type LeaseStep`, the `onStep` router option,
|
|
22
|
+
* and the self-contained `renderStepLine(step)` helper (the lib never prints).
|
|
23
|
+
* • Setup-copy (setup-copy.ts): `copySetupToBox({ host, user?, port?,
|
|
24
|
+
* secretsBundle?, userAgentsDir?, onData? }): Promise<CopySetupResult>` — the
|
|
25
|
+
* push-from-local the command layer runs before the box run.
|
|
8
26
|
*/
|
|
9
27
|
import type { AgentId } from '../types.js';
|
|
10
28
|
import { type CrabboxBox } from './cli.js';
|
|
@@ -13,6 +31,9 @@ import { type DetectedRuntime } from './runtimes.js';
|
|
|
13
31
|
export type LeasePhase = {
|
|
14
32
|
kind: 'warmup';
|
|
15
33
|
backend?: string;
|
|
34
|
+
} | {
|
|
35
|
+
kind: 'reuse';
|
|
36
|
+
slug: string;
|
|
16
37
|
} | {
|
|
17
38
|
kind: 'ready';
|
|
18
39
|
box: CrabboxBox;
|
|
@@ -29,16 +50,36 @@ export interface LeaseRunOptions {
|
|
|
29
50
|
backend?: string;
|
|
30
51
|
boxClass?: string;
|
|
31
52
|
profile?: string;
|
|
32
|
-
/** Runtimes to install
|
|
53
|
+
/** Runtimes to install on the box. */
|
|
33
54
|
runtimes: AgentId[];
|
|
55
|
+
/** Runtime credentials to copy; defaults to `runtimes`. */
|
|
56
|
+
credentialRuntimes?: AgentId[];
|
|
34
57
|
detected: DetectedRuntime[];
|
|
58
|
+
/** Profile-dispatch config to materialize on the leased box before the run. */
|
|
59
|
+
dispatchProfile?: LeaseDispatchProfile;
|
|
35
60
|
/** Secrets bundle providing crabbox's provider token. */
|
|
36
61
|
secretsBundle?: string;
|
|
62
|
+
/**
|
|
63
|
+
* Push the git-tracked subset of the local `~/.agents` onto the box before the
|
|
64
|
+
* run (default TRUE). The command layer sets `false` for `--bare`. When true,
|
|
65
|
+
* `buildBootstrapScript` emits the `copy-setup` progress sentinel; the actual
|
|
66
|
+
* push-from-local is `copySetupToBox` (setup-copy.ts), which the command layer
|
|
67
|
+
* runs before the box run.
|
|
68
|
+
*/
|
|
69
|
+
copySetup?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Network mode threaded to `crabboxWarmup` (default `'public'`). `'tailscale'`
|
|
72
|
+
* leases the box onto the tailnet and adds a `joined-tailnet` progress step.
|
|
73
|
+
* The command layer decides WHEN to enable it — this is plumbing only.
|
|
74
|
+
*/
|
|
75
|
+
netMode?: 'public' | 'tailscale';
|
|
37
76
|
onData?: (s: string) => void;
|
|
38
77
|
/** Progress phases (warmup → ready → teardown) for a command-layer spinner. */
|
|
39
78
|
onPhase?: (phase: LeasePhase) => void;
|
|
40
79
|
/** Keep the box after the run instead of stopping it. */
|
|
41
80
|
keep?: boolean;
|
|
81
|
+
/** Existing warm crabbox slug to reuse instead of provisioning a new lease. */
|
|
82
|
+
reuseBox?: string;
|
|
42
83
|
/**
|
|
43
84
|
* Raw wrapped Claude OAuth payload (from `resolveClaudeCredentialsBlob`), written
|
|
44
85
|
* to `~/.claude/.credentials.json` on the box. The command layer resolves it
|
|
@@ -46,6 +87,16 @@ export interface LeaseRunOptions {
|
|
|
46
87
|
*/
|
|
47
88
|
claudeCredentialsJson?: string | null;
|
|
48
89
|
}
|
|
90
|
+
export interface LeaseDispatchProfile {
|
|
91
|
+
name: string;
|
|
92
|
+
agent: AgentId;
|
|
93
|
+
version?: string;
|
|
94
|
+
env: Record<string, string>;
|
|
95
|
+
description?: string;
|
|
96
|
+
preset?: string;
|
|
97
|
+
provider?: string;
|
|
98
|
+
fallbackModel?: string;
|
|
99
|
+
}
|
|
49
100
|
export interface LeaseRunResult {
|
|
50
101
|
box: CrabboxBox;
|
|
51
102
|
exitCode: number | null;
|
|
@@ -5,14 +5,58 @@
|
|
|
5
5
|
* credentials → run the agent on the box (via `crabbox run`, which owns the
|
|
6
6
|
* SSH) → tear the box down. The whole box-side sequence rides a single
|
|
7
7
|
* `--script-stdin` body so the token contents never touch argv.
|
|
8
|
+
*
|
|
9
|
+
* ── Command-layer contract (RUSH-1920/1921/1924) ─────────────────────────────
|
|
10
|
+
* Exports the commands layer (exec.ts / lease.ts / ssh.ts) consumes:
|
|
11
|
+
* • `buildBootstrapScript(opts)` — `opts.copySetup` (default TRUE; clear for
|
|
12
|
+
* --bare) gates the `copy-setup` progress sentinel; `opts.netMode`
|
|
13
|
+
* ('public' | 'tailscale', default 'public') adds the `joined-tailnet` step.
|
|
14
|
+
* • `LeaseRunOptions.copySetup` / `LeaseRunOptions.netMode` — forwarded by
|
|
15
|
+
* `leaseAndRun` (netMode → `crabboxWarmup`).
|
|
16
|
+
* • `crabboxWarmup(opts.netMode)` — 'tailscale' leases onto the tailnet
|
|
17
|
+
* (`--network tailscale -tailscale-tags tag:crabbox`); auth key rides the
|
|
18
|
+
* child env as `CRABBOX_TAILSCALE_AUTH_KEY` (crabboxEnv, cli.ts).
|
|
19
|
+
* • `CrabboxBox.tailscaleIPv4` / `CrabboxBox.tailscaleFQDN` — populated from
|
|
20
|
+
* the box labels by `normalizeBox` (cli.ts).
|
|
21
|
+
* • Step stream (progress.ts): `type LeaseStep`, the `onStep` router option,
|
|
22
|
+
* and the self-contained `renderStepLine(step)` helper (the lib never prints).
|
|
23
|
+
* • Setup-copy (setup-copy.ts): `copySetupToBox({ host, user?, port?,
|
|
24
|
+
* secretsBundle?, userAgentsDir?, onData? }): Promise<CopySetupResult>` — the
|
|
25
|
+
* push-from-local the command layer runs before the box run.
|
|
8
26
|
*/
|
|
9
|
-
import { crabboxWarmup, crabboxWaitReady, crabboxRunScript, crabboxStop } from './cli.js';
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
27
|
+
import { crabboxFind, crabboxWarmup, crabboxWaitReady, crabboxRunScript, crabboxStop } from './cli.js';
|
|
28
|
+
import * as yaml from 'yaml';
|
|
29
|
+
import { buildCredentialScript, buildHomeFileWriteScript, CLAUDE_TOKEN_REMOTE } from './runtimes.js';
|
|
30
|
+
import { LEASE_AGENT_MARKER, leasePhaseSentinel } from './progress.js';
|
|
31
|
+
import { copySetupToBox } from './setup-copy.js';
|
|
12
32
|
/** POSIX single-quote for safe embedding in the generated bootstrap script. */
|
|
13
33
|
function q(s) {
|
|
14
34
|
return "'" + s.replace(/'/g, "'\\''") + "'";
|
|
15
35
|
}
|
|
36
|
+
function profileRemotePath(name) {
|
|
37
|
+
return `.agents/profiles/${name}.yml`;
|
|
38
|
+
}
|
|
39
|
+
function buildProfileScript(profile) {
|
|
40
|
+
const body = yaml.stringify({
|
|
41
|
+
name: profile.name,
|
|
42
|
+
...(profile.description ? { description: profile.description } : {}),
|
|
43
|
+
host: {
|
|
44
|
+
agent: profile.agent,
|
|
45
|
+
...(profile.version ? { version: profile.version } : {}),
|
|
46
|
+
},
|
|
47
|
+
env: profile.env,
|
|
48
|
+
...(profile.fallbackModel ? { fallback_model: profile.fallbackModel } : {}),
|
|
49
|
+
...(profile.preset ? { preset: profile.preset } : {}),
|
|
50
|
+
...(profile.provider ? { provider: profile.provider } : {}),
|
|
51
|
+
});
|
|
52
|
+
return buildHomeFileWriteScript(profileRemotePath(profile.name), body);
|
|
53
|
+
}
|
|
54
|
+
function runtimeInstallSpec(id, dispatchProfile) {
|
|
55
|
+
if (dispatchProfile?.agent === id && dispatchProfile.version) {
|
|
56
|
+
return `${id}@${dispatchProfile.version}`;
|
|
57
|
+
}
|
|
58
|
+
return id;
|
|
59
|
+
}
|
|
16
60
|
/**
|
|
17
61
|
* Bash snippet that guarantees `agents` is runnable on the box. Fresh crabbox
|
|
18
62
|
* images ship without node, and the box user may not own the global npm prefix,
|
|
@@ -47,9 +91,11 @@ const ENSURE_AGENTS_CLI = [
|
|
|
47
91
|
* the credential files. Best-effort install steps never abort the run.
|
|
48
92
|
*/
|
|
49
93
|
export function buildBootstrapScript(opts) {
|
|
50
|
-
const
|
|
94
|
+
const credentialRuntimes = opts.credentialRuntimes ?? opts.runtimes;
|
|
95
|
+
const credScript = buildCredentialScript(credentialRuntimes, opts.detected, {
|
|
51
96
|
claudeCredentialsJson: opts.claudeCredentialsJson,
|
|
52
97
|
});
|
|
98
|
+
const profileScript = opts.dispatchProfile ? buildProfileScript(opts.dispatchProfile) : '';
|
|
53
99
|
const runParts = ['agents', 'run', q(opts.agent), q(opts.prompt), '--quiet'];
|
|
54
100
|
if (opts.mode)
|
|
55
101
|
runParts.push('--mode', q(opts.mode));
|
|
@@ -58,17 +104,40 @@ export function buildBootstrapScript(opts) {
|
|
|
58
104
|
// Credential files to shred after the run (home-level paths written above).
|
|
59
105
|
// Runs regardless of --keep-box (it's in the box body, not teardown), so a kept
|
|
60
106
|
// box still loses the token after the run — minimizing the credential window.
|
|
61
|
-
const shredPaths =
|
|
107
|
+
const shredPaths = credentialRuntimes.flatMap((id) => {
|
|
62
108
|
const paths = { claude: ['.claude.json', CLAUDE_TOKEN_REMOTE], codex: ['.codex/auth.json'], gemini: ['.gemini/google_accounts.json'], grok: ['.grok/auth.json'] }[id];
|
|
63
109
|
return paths ?? [];
|
|
64
110
|
});
|
|
111
|
+
if (opts.dispatchProfile)
|
|
112
|
+
shredPaths.push(profileRemotePath(opts.dispatchProfile.name));
|
|
65
113
|
const shred = shredPaths.map((p) => `rm -f "$HOME/${p}" 2>/dev/null || true`).join('\n');
|
|
66
|
-
const installRuntimes = opts.runtimes
|
|
114
|
+
const installRuntimes = opts.runtimes
|
|
115
|
+
.map((id) => `agents add ${q(runtimeInstallSpec(id, opts.dispatchProfile))} >/dev/null 2>&1 || true`)
|
|
116
|
+
.join('\n');
|
|
117
|
+
// `echo`-ed phase sentinels (progress.ts) let the command layer drive a
|
|
118
|
+
// step-by-step UI. `createLeaseOutputRouter` swallows these lines and surfaces
|
|
119
|
+
// them as a structured step stream — they never appear as setup noise.
|
|
120
|
+
const step = (name) => `echo ${q(leasePhaseSentinel(name))}`;
|
|
121
|
+
const copySetup = opts.copySetup !== false; // default TRUE
|
|
67
122
|
return [
|
|
68
123
|
'set -uo pipefail',
|
|
124
|
+
// crabbox has finished its workspace resync by the time this script runs;
|
|
125
|
+
// the sync sentinel marks the transition out of that (crabbox-driven) phase.
|
|
126
|
+
step('sync'),
|
|
127
|
+
// Only meaningful on a tailnet lease — the box already joined during warmup.
|
|
128
|
+
opts.netMode === 'tailscale' ? step('joined-tailnet') : '',
|
|
129
|
+
step('install'),
|
|
69
130
|
ENSURE_AGENTS_CLI,
|
|
131
|
+
step('runtime'),
|
|
70
132
|
installRuntimes,
|
|
133
|
+
step('creds'),
|
|
71
134
|
credScript,
|
|
135
|
+
profileScript,
|
|
136
|
+
// copy-setup: the host already rsync'd the git-tracked ~/.agents onto the box
|
|
137
|
+
// (leaseAndRun, before this script). Here — after ENSURE_AGENTS_CLI installed
|
|
138
|
+
// the CLI — we materialize that config into the runtime home. Gated by
|
|
139
|
+
// copySetup (cleared for --bare). Best-effort; a refresh failure never aborts.
|
|
140
|
+
copySetup ? [step('copy-setup'), 'agents repo refresh >/dev/null 2>&1 || true'].join('\n') : '',
|
|
72
141
|
// Marker on its own line: the command layer shows everything before this as
|
|
73
142
|
// setup progress and everything after (the agent's output) verbatim.
|
|
74
143
|
`echo ${q(LEASE_AGENT_MARKER)}`,
|
|
@@ -82,15 +151,47 @@ export function buildBootstrapScript(opts) {
|
|
|
82
151
|
}
|
|
83
152
|
export async function leaseAndRun(opts) {
|
|
84
153
|
const startedAt = Date.now();
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
154
|
+
let box;
|
|
155
|
+
if (opts.reuseBox) {
|
|
156
|
+
opts.onPhase?.({ kind: 'reuse', slug: opts.reuseBox });
|
|
157
|
+
const found = crabboxFind(opts.reuseBox, { secretsBundle: opts.secretsBundle });
|
|
158
|
+
if (!found)
|
|
159
|
+
throw new Error(`crabbox box "${opts.reuseBox}" was not found. Check \`crabbox list\` or pass a different --box slug.`);
|
|
160
|
+
box = found.ready
|
|
161
|
+
? found
|
|
162
|
+
: await crabboxWaitReady(opts.reuseBox, { secretsBundle: opts.secretsBundle });
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
opts.onPhase?.({ kind: 'warmup', backend: opts.backend });
|
|
166
|
+
box = await crabboxWarmup({
|
|
167
|
+
class: opts.boxClass,
|
|
168
|
+
profile: opts.profile,
|
|
169
|
+
provider: opts.backend,
|
|
170
|
+
secretsBundle: opts.secretsBundle,
|
|
171
|
+
netMode: opts.netMode,
|
|
172
|
+
});
|
|
173
|
+
await crabboxWaitReady(box.slug, { secretsBundle: opts.secretsBundle });
|
|
174
|
+
}
|
|
93
175
|
opts.onPhase?.({ kind: 'ready', box, elapsedMs: Date.now() - startedAt });
|
|
176
|
+
// Setup-copy (F1, RUSH-1920): push the git-tracked ~/.agents config onto the
|
|
177
|
+
// box from the host, over crabbox's own per-lease ssh (a raw ssh fails
|
|
178
|
+
// publickey). rsync only here — the box has no agents-cli yet, so the matching
|
|
179
|
+
// `agents repo refresh` runs inside the bootstrap script, after the install
|
|
180
|
+
// step. Best-effort: a copy failure never aborts the run (the agent just runs
|
|
181
|
+
// without the config).
|
|
182
|
+
if (opts.copySetup !== false) {
|
|
183
|
+
try {
|
|
184
|
+
await copySetupToBox({
|
|
185
|
+
slug: box.slug,
|
|
186
|
+
secretsBundle: opts.secretsBundle,
|
|
187
|
+
onData: opts.onData,
|
|
188
|
+
refresh: false,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
/* best-effort — never block the run on a config-copy failure */
|
|
193
|
+
}
|
|
194
|
+
}
|
|
94
195
|
const script = buildBootstrapScript(opts);
|
|
95
196
|
let exitCode = null;
|
|
96
197
|
let toreDown = false;
|
|
@@ -102,8 +203,8 @@ export async function leaseAndRun(opts) {
|
|
|
102
203
|
}
|
|
103
204
|
finally {
|
|
104
205
|
// Always attempt teardown (bounds credential lifetime to the run) unless the
|
|
105
|
-
// caller explicitly asked to keep the box.
|
|
106
|
-
if (!opts.keep) {
|
|
206
|
+
// caller explicitly asked to keep the box or targeted an existing warm box.
|
|
207
|
+
if (!opts.keep && !opts.reuseBox) {
|
|
107
208
|
opts.onPhase?.({ kind: 'teardown' });
|
|
108
209
|
toreDown = crabboxStop(box.slug, { secretsBundle: opts.secretsBundle });
|
|
109
210
|
}
|
|
@@ -9,6 +9,27 @@
|
|
|
9
9
|
*/
|
|
10
10
|
/** Sentinel echoed on the box right before `agents run`. Distinctive + collision-proof. */
|
|
11
11
|
export declare const LEASE_AGENT_MARKER = "___AGENTS_LEASE_AGENT_OUTPUT_b1f4c2___";
|
|
12
|
+
/**
|
|
13
|
+
* A structured setup step, parsed from a `___PHASE_<name>___` sentinel line the
|
|
14
|
+
* bootstrap script echoes before each block (sync/install/runtime/creds/…). The
|
|
15
|
+
* command layer renders these to drive a step-by-step progress UI; the lib never
|
|
16
|
+
* prints (see `renderStepLine`).
|
|
17
|
+
*/
|
|
18
|
+
export type LeaseStep = {
|
|
19
|
+
name: string;
|
|
20
|
+
detail?: string;
|
|
21
|
+
elapsedMs?: number;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* The sentinel line the bootstrap script echoes to announce phase `name`.
|
|
25
|
+
* Kept distinct from `LEASE_AGENT_MARKER` (which does NOT start with `___PHASE_`).
|
|
26
|
+
*/
|
|
27
|
+
export declare function leasePhaseSentinel(name: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Render one lease step as a single human line the command layer can print
|
|
30
|
+
* (e.g. behind a spinner). Self-contained: the lib never writes to a stream.
|
|
31
|
+
*/
|
|
32
|
+
export declare function renderStepLine(step: LeaseStep): string;
|
|
12
33
|
export interface Spinner {
|
|
13
34
|
/** Begin animating a phase (renders `frame text` on one line, TTY only). */
|
|
14
35
|
start(text: string): void;
|
|
@@ -49,14 +70,24 @@ export interface LeaseOutputRouter {
|
|
|
49
70
|
sawAgent(): boolean;
|
|
50
71
|
/** The setup lines seen so far (for a failure dump). */
|
|
51
72
|
setupLines(): string[];
|
|
73
|
+
/** The structured steps parsed from phase sentinels so far. */
|
|
74
|
+
steps(): LeaseStep[];
|
|
52
75
|
}
|
|
53
76
|
/**
|
|
54
77
|
* Split the crabbox run stream at `LEASE_AGENT_MARKER`. `onSetupLine` fires for
|
|
55
78
|
* each complete non-empty line before the marker; `onAgentChunk` fires with raw
|
|
56
79
|
* text after it (streamed promptly, not line-buffered, so agent output is live).
|
|
80
|
+
*
|
|
81
|
+
* Setup lines matching a `___PHASE_<name>___` sentinel are swallowed (never shown
|
|
82
|
+
* as setup noise) and surfaced through `onStep` instead — a structured step
|
|
83
|
+
* stream the command layer can render via `renderStepLine`. When `now` is
|
|
84
|
+
* provided, each step carries `elapsedMs` since the previous step (or router
|
|
85
|
+
* start for the first); omit `now` for deterministic, timing-free output.
|
|
57
86
|
*/
|
|
58
87
|
export declare function createLeaseOutputRouter(cb: {
|
|
59
88
|
onSetupLine: (line: string) => void;
|
|
60
89
|
onAgentChunk: (chunk: string) => void;
|
|
90
|
+
onStep?: (step: LeaseStep) => void;
|
|
61
91
|
marker?: string;
|
|
92
|
+
now?: () => number;
|
|
62
93
|
}): LeaseOutputRouter;
|