@phnx-labs/agents-cli 1.22.40 → 1.22.42
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 +36 -0
- package/README.md +14 -3
- package/dist/bin/agents +0 -0
- package/dist/bootstrap.js +0 -1
- package/dist/cli/command-registry.d.ts +2 -0
- package/dist/cli/command-registry.js +5 -2
- package/dist/commands/accounts.d.ts +29 -0
- package/dist/commands/accounts.js +220 -29
- package/dist/commands/alias.js +1 -1
- package/dist/commands/auth.d.ts +9 -0
- package/dist/commands/auth.js +104 -0
- package/dist/commands/browser-sessions-picker.js +42 -62
- package/dist/commands/browser.d.ts +15 -0
- package/dist/commands/browser.js +145 -78
- package/dist/commands/computer-actions.d.ts +1 -1
- package/dist/commands/computer-actions.js +1 -1
- package/dist/commands/computer-sessions-picker.js +32 -55
- package/dist/commands/computer.d.ts +1 -1
- package/dist/commands/computer.js +2 -2
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/doctor.js +3 -3
- package/dist/commands/feed.js +4 -12
- package/dist/commands/hooks.js +1 -1
- package/dist/commands/import.js +1 -1
- package/dist/commands/insights.js +163 -85
- package/dist/commands/inspect.js +2 -2
- package/dist/commands/mine.js +1 -1
- package/dist/commands/org.d.ts +6 -0
- package/dist/commands/org.js +174 -0
- package/dist/commands/packages.js +1 -1
- package/dist/commands/prune.js +1 -1
- package/dist/commands/routines.js +1 -1
- package/dist/commands/run-account-picker.d.ts +19 -0
- package/dist/commands/run-account-picker.js +81 -0
- package/dist/commands/secrets.js +87 -97
- package/dist/commands/sessions-picker-factory.d.ts +40 -0
- package/dist/commands/sessions-picker-factory.js +67 -0
- package/dist/commands/sessions-picker.d.ts +1 -0
- package/dist/commands/sessions-picker.js +27 -5
- package/dist/commands/sessions-share.d.ts +25 -0
- package/dist/commands/sessions-share.js +166 -0
- package/dist/commands/sessions.js +2 -0
- package/dist/commands/setup-browser.js +5 -5
- package/dist/commands/setup-mine.js +1 -1
- package/dist/commands/setup-preferences.js +4 -4
- package/dist/commands/setup.js +5 -3
- package/dist/commands/sync.js +1 -1
- package/dist/commands/versions.js +1 -1
- package/dist/commands/view.js +1 -1
- package/dist/commands/webhook.js +14 -9
- package/dist/lib/account-capabilities.d.ts +10 -0
- package/dist/lib/account-capabilities.js +34 -0
- package/dist/lib/agent-spec/agents.d.ts +584 -0
- package/dist/lib/agent-spec/agents.js +3127 -0
- package/dist/lib/agent-spec/provider.js +1 -1
- package/dist/lib/agents.d.ts +2 -584
- package/dist/lib/agents.js +2 -3127
- package/dist/lib/browser/cdp.js +4 -0
- package/dist/lib/browser/chrome.js +12 -1
- package/dist/lib/browser/drivers/local.d.ts +8 -2
- package/dist/lib/browser/drivers/local.js +8 -2
- package/dist/lib/browser/drivers/ssh.d.ts +8 -2
- package/dist/lib/browser/drivers/ssh.js +11 -4
- package/dist/lib/browser/hygiene.d.ts +1 -1
- package/dist/lib/browser/hygiene.js +5 -1
- package/dist/lib/browser/profiles.d.ts +77 -4
- package/dist/lib/browser/profiles.js +121 -12
- package/dist/lib/browser/runtime-state.d.ts +11 -5
- package/dist/lib/browser/runtime-state.js +13 -9
- package/dist/lib/browser/service.d.ts +91 -30
- package/dist/lib/browser/service.js +342 -207
- package/dist/lib/browser/types.d.ts +96 -4
- package/dist/lib/browser/types.js +51 -0
- package/dist/lib/catchup.js +1 -1
- package/dist/lib/computer/actions.d.ts +1 -1
- package/dist/lib/computer/actions.js +1 -1
- package/dist/lib/{computer-rpc.js → computer/computer-rpc.js} +5 -5
- package/dist/lib/computer/dispatch.d.ts +1 -1
- package/dist/lib/computer/download.d.ts +1 -1
- package/dist/lib/computer/download.js +3 -3
- package/dist/lib/{ssh-tunnel.d.ts → computer/ssh-tunnel.d.ts} +1 -1
- package/dist/lib/{ssh-tunnel.js → computer/ssh-tunnel.js} +12 -12
- package/dist/lib/daemon/daemon.js +1 -1
- package/dist/lib/{runner.d.ts → daemon/runner.d.ts} +10 -10
- package/dist/lib/{runner.js → daemon/runner.js} +59 -118
- package/dist/lib/daemon-webhooks.d.ts +3 -1
- package/dist/lib/daemon-webhooks.js +12 -7
- package/dist/lib/device-config.js +1 -1
- package/dist/lib/devices/doctor-findings.d.ts +1 -1
- package/dist/lib/devices/doctor-findings.js +3 -3
- package/dist/lib/devices/fleet-inventory.js +1 -1
- package/dist/lib/devices/harness-inventory.js +1 -1
- package/dist/lib/devices/registry.js +4 -22
- package/dist/lib/doctor-diff.d.ts +1 -1
- package/dist/lib/doctor-diff.js +1 -1
- package/dist/lib/drift.js +1 -1
- package/dist/lib/entitlement.d.ts +31 -0
- package/dist/lib/entitlement.js +137 -0
- package/dist/lib/exec.js +39 -202
- package/dist/lib/feed/feed.js +2 -8
- package/dist/lib/fs-atomic.d.ts +7 -0
- package/dist/lib/fs-atomic.js +9 -0
- package/dist/lib/harness/adapter.d.ts +160 -0
- package/dist/lib/harness/adapter.js +47 -0
- package/dist/lib/harness/adapters/claude.d.ts +2 -0
- package/dist/lib/harness/adapters/claude.js +110 -0
- package/dist/lib/harness/adapters/codex.d.ts +2 -0
- package/dist/lib/harness/adapters/codex.js +83 -0
- package/dist/lib/harness/adapters/copilot.d.ts +2 -0
- package/dist/lib/harness/adapters/copilot.js +23 -0
- package/dist/lib/harness/adapters/cursor.d.ts +2 -0
- package/dist/lib/harness/adapters/cursor.js +38 -0
- package/dist/lib/harness/adapters/droid.d.ts +2 -0
- package/dist/lib/harness/adapters/droid.js +17 -0
- package/dist/lib/harness/adapters/grok.d.ts +2 -0
- package/dist/lib/harness/adapters/grok.js +14 -0
- package/dist/lib/harness/adapters/kimi.d.ts +2 -0
- package/dist/lib/harness/adapters/kimi.js +49 -0
- package/dist/lib/harness/adapters/muse.d.ts +2 -0
- package/dist/lib/harness/adapters/muse.js +44 -0
- package/dist/lib/harness/adapters/opencode.d.ts +2 -0
- package/dist/lib/harness/adapters/opencode.js +14 -0
- package/dist/lib/harness/exec-config-version.d.ts +8 -0
- package/dist/lib/harness/exec-config-version.js +22 -0
- package/dist/lib/harness/index.d.ts +1 -0
- package/dist/lib/harness/index.js +34 -0
- package/dist/lib/{hooks.d.ts → hooks/install.d.ts} +1 -1
- package/dist/lib/{hooks.js → hooks/install.js} +11 -11
- package/dist/lib/import.js +1 -1
- package/dist/lib/installations/index.d.ts +4 -4
- package/dist/lib/installations/index.js +3 -4
- package/dist/lib/installations/migrate.js +6 -6
- package/dist/lib/{shims.d.ts → installations/shims.d.ts} +2 -2
- package/dist/lib/{shims.js → installations/shims.js} +12 -114
- package/dist/lib/installations/store.d.ts +141 -3
- package/dist/lib/installations/store.js +475 -5
- package/dist/lib/installations/versions.d.ts +1 -126
- package/dist/lib/installations/versions.js +5 -465
- package/dist/lib/isolation-boundary-report.d.ts +1 -1
- package/dist/lib/isolation-boundary-report.js +1 -1
- package/dist/lib/mailbox-gc.js +2 -3
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/monitors/dispatch.d.ts +1 -1
- package/dist/lib/monitors/dispatch.js +2 -2
- package/dist/lib/monitors/state.d.ts +1 -1
- package/dist/lib/observe-aliases.d.ts +2 -2
- package/dist/lib/observe-aliases.js +2 -11
- package/dist/lib/plugins/plugins.js +1 -1
- package/dist/lib/plugins/skills.js +1 -1
- package/dist/lib/prix-account.d.ts +158 -0
- package/dist/lib/prix-account.js +214 -0
- package/dist/lib/project-key.js +7 -0
- package/dist/lib/project-launch.js +1 -1
- package/dist/lib/refresh.js +2 -2
- package/dist/lib/resource-inventory.d.ts +1 -1
- package/dist/lib/resource-inventory.js +1 -1
- package/dist/lib/rules/rules.js +1 -1
- package/dist/lib/scheduling/routines.js +1 -1
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/bundles.d.ts +1 -1
- package/dist/lib/secrets/bundles.js +1 -1
- package/dist/lib/secrets/headless.d.ts +16 -0
- package/dist/lib/secrets/headless.js +21 -0
- package/dist/lib/secrets/remote.d.ts +9 -7
- package/dist/lib/secrets/remote.js +18 -9
- package/dist/lib/self-heal/checks/hook-runtime.js +1 -1
- package/dist/lib/self-heal/checks/path.js +1 -1
- package/dist/lib/self-heal/checks/shadowing.js +1 -1
- package/dist/lib/self-heal/checks/shims.js +1 -1
- package/dist/lib/session/discover.d.ts +39 -0
- package/dist/lib/session/discover.js +3 -3
- package/dist/lib/session/parse.js +24 -42
- package/dist/lib/session/recovery.js +1 -1
- package/dist/lib/session/share-html.d.ts +55 -0
- package/dist/lib/session/share-html.js +319 -0
- package/dist/lib/settings-manifest.d.ts +2 -0
- package/dist/lib/settings-manifest.js +81 -3
- package/dist/lib/share/publish.d.ts +38 -0
- package/dist/lib/share/publish.js +81 -8
- package/dist/lib/staleness/prune.d.ts +1 -1
- package/dist/lib/staleness/prune.js +1 -1
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/startup/command-registry.d.ts +2 -1
- package/dist/lib/startup/command-registry.js +5 -3
- package/dist/lib/teams/agents.js +2 -19
- package/dist/lib/teams/parsers.js +20 -26
- package/dist/lib/teams/registry.js +2 -21
- package/dist/lib/triggers/handlers.d.ts +37 -2
- package/dist/lib/triggers/handlers.js +57 -6
- package/dist/lib/triggers/webhook.d.ts +80 -6
- package/dist/lib/triggers/webhook.js +128 -4
- package/dist/lib/types.d.ts +1 -1
- package/dist/lib/uninstall.js +1 -1
- package/dist/lib/version.d.ts +9 -2
- package/dist/lib/version.js +11 -9
- package/dist/lib/wrap.d.ts +33 -0
- package/dist/lib/wrap.js +70 -0
- package/package.json +1 -1
- /package/dist/lib/{computer-rpc.d.ts → computer/computer-rpc.d.ts} +0 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { setHelpSections } from '../lib/help.js';
|
|
3
|
+
import { createSpace, createSpaceInvite, fetchWhoAmI, listSpaceMembers, listSpaces, removeSpaceMember, resolveMemberFromList, resolvePrixToken, resolveSpaceFromList, slugify, updateSpaceMemberRole, } from '../lib/prix-account.js';
|
|
4
|
+
/** Every `agents org` subcommand needs a token up front — one place to say so. */
|
|
5
|
+
function requireToken() {
|
|
6
|
+
if (!resolvePrixToken())
|
|
7
|
+
throw new Error("Not signed in. Run 'agents auth login' first.");
|
|
8
|
+
}
|
|
9
|
+
async function resolveSpace(explicit) {
|
|
10
|
+
const spaces = await listSpaces();
|
|
11
|
+
return resolveSpaceFromList(spaces, explicit);
|
|
12
|
+
}
|
|
13
|
+
function printSpace(space) {
|
|
14
|
+
console.log(`${chalk.bold(space.name)} ${chalk.gray(space.slug)} ${chalk.gray(space.id)}`);
|
|
15
|
+
console.log(` role: ${space.user_role}${space.organization_id ? ` org: ${space.organization_id}` : ''}`);
|
|
16
|
+
}
|
|
17
|
+
function printMembers(members) {
|
|
18
|
+
if (!members.length) {
|
|
19
|
+
console.log(chalk.gray(' No members.'));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
for (const m of members)
|
|
23
|
+
console.log(` ${chalk.cyan(m.email)} ${m.role} ${chalk.gray(m.user_id)}`);
|
|
24
|
+
}
|
|
25
|
+
async function runCreate(name, o) {
|
|
26
|
+
requireToken();
|
|
27
|
+
const space = await createSpace({ name, slug: o.slug ?? slugify(name), description: o.description });
|
|
28
|
+
if (o.json) {
|
|
29
|
+
console.log(JSON.stringify(space, null, 2));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
console.log(chalk.green(`Created space '${space.name}' (${space.slug}).`));
|
|
33
|
+
printSpace(space);
|
|
34
|
+
}
|
|
35
|
+
async function runList(o) {
|
|
36
|
+
requireToken();
|
|
37
|
+
const spaces = await listSpaces();
|
|
38
|
+
if (o.json) {
|
|
39
|
+
console.log(JSON.stringify(spaces, null, 2));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (!spaces.length) {
|
|
43
|
+
console.log(chalk.gray("No spaces. Create one with 'agents org create <name>'."));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
for (const space of spaces)
|
|
47
|
+
printSpace(space);
|
|
48
|
+
}
|
|
49
|
+
async function runView(spaceArg, o) {
|
|
50
|
+
requireToken();
|
|
51
|
+
const space = await resolveSpace(spaceArg);
|
|
52
|
+
if (o.json) {
|
|
53
|
+
console.log(JSON.stringify(space, null, 2));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
printSpace(space);
|
|
57
|
+
}
|
|
58
|
+
/** `--role` defaults to member when omitted; anything present must be a real role — never silently coerced. */
|
|
59
|
+
export function resolveInviteRole(raw) {
|
|
60
|
+
return raw === undefined ? 'member' : parseRole(raw);
|
|
61
|
+
}
|
|
62
|
+
async function runInvite(email, o) {
|
|
63
|
+
requireToken();
|
|
64
|
+
const role = resolveInviteRole(o.role);
|
|
65
|
+
const space = await resolveSpace(o.space);
|
|
66
|
+
const result = await createSpaceInvite(space.id, email, role);
|
|
67
|
+
if (o.json) {
|
|
68
|
+
console.log(JSON.stringify(result, null, 2));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (result.member_added)
|
|
72
|
+
console.log(chalk.green(`Added ${email} to '${space.name}' as ${role}.`));
|
|
73
|
+
else
|
|
74
|
+
console.log(chalk.green(`Invited ${email} to '${space.name}' as ${role}. They'll get an email.`));
|
|
75
|
+
}
|
|
76
|
+
async function runMembers(spaceArg, o) {
|
|
77
|
+
requireToken();
|
|
78
|
+
const space = await resolveSpace(spaceArg);
|
|
79
|
+
const members = await listSpaceMembers(space.id);
|
|
80
|
+
if (o.json) {
|
|
81
|
+
console.log(JSON.stringify(members, null, 2));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
console.log(chalk.bold(`${space.name} (${members.length} member${members.length === 1 ? '' : 's'})`));
|
|
85
|
+
printMembers(members);
|
|
86
|
+
}
|
|
87
|
+
/** Validate a role argument before any network call — same rule the backend enforces server-side. */
|
|
88
|
+
export function parseRole(raw) {
|
|
89
|
+
if (raw === 'admin' || raw === 'member')
|
|
90
|
+
return raw;
|
|
91
|
+
throw new Error(`role must be 'admin' or 'member', got '${raw}'.`);
|
|
92
|
+
}
|
|
93
|
+
async function runRole(email, roleRaw, o) {
|
|
94
|
+
requireToken();
|
|
95
|
+
const role = parseRole(roleRaw);
|
|
96
|
+
const space = await resolveSpace(o.space);
|
|
97
|
+
const members = await listSpaceMembers(space.id);
|
|
98
|
+
const member = resolveMemberFromList(members, email);
|
|
99
|
+
const result = await updateSpaceMemberRole(space.id, member.user_id, role);
|
|
100
|
+
if (o.json) {
|
|
101
|
+
console.log(JSON.stringify(result, null, 2));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
console.log(chalk.green(`${email} is now ${role} in '${space.name}'.`));
|
|
105
|
+
}
|
|
106
|
+
async function runRemove(email, o) {
|
|
107
|
+
requireToken();
|
|
108
|
+
const space = await resolveSpace(o.space);
|
|
109
|
+
const members = await listSpaceMembers(space.id);
|
|
110
|
+
const member = resolveMemberFromList(members, email);
|
|
111
|
+
await removeSpaceMember(space.id, member.user_id);
|
|
112
|
+
if (o.json) {
|
|
113
|
+
console.log(JSON.stringify({ removed: email, space: space.slug }, null, 2));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
console.log(chalk.green(`Removed ${email} from '${space.name}'.`));
|
|
117
|
+
}
|
|
118
|
+
async function runLeave(spaceArg, o) {
|
|
119
|
+
requireToken();
|
|
120
|
+
const resolved = resolvePrixToken();
|
|
121
|
+
if (!resolved)
|
|
122
|
+
throw new Error("Not signed in. Run 'agents auth login' first.");
|
|
123
|
+
const who = await fetchWhoAmI(resolved.token);
|
|
124
|
+
const space = await resolveSpace(spaceArg);
|
|
125
|
+
if (space.owner_user_id === who.userId) {
|
|
126
|
+
throw new Error(`You own '${space.name}'. Transfer ownership or delete the space instead of leaving it.`);
|
|
127
|
+
}
|
|
128
|
+
await removeSpaceMember(space.id, who.userId);
|
|
129
|
+
if (o.json) {
|
|
130
|
+
console.log(JSON.stringify({ left: space.slug }, null, 2));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
console.log(chalk.green(`Left '${space.name}'.`));
|
|
134
|
+
}
|
|
135
|
+
export function registerOrgCommand(program) {
|
|
136
|
+
const org = program.command('org').description('Create and manage a team (a Rush "space") you can share with `agents auth login` collaborators');
|
|
137
|
+
org.command('create <name>').description('Create a space (free tier: 1 owned space)')
|
|
138
|
+
.option('--slug <slug>', 'Override the derived slug')
|
|
139
|
+
.option('--description <text>', 'Optional description')
|
|
140
|
+
.option('--json', 'Machine-readable output')
|
|
141
|
+
.action((name, o, command) => runCreate(name, { ...o, json: !!(o.json || command.optsWithGlobals().json) }));
|
|
142
|
+
org.command('list').description('List spaces you own or belong to').option('--json', 'Machine-readable output')
|
|
143
|
+
.action((o, command) => runList({ json: !!(o.json || command.optsWithGlobals().json) }));
|
|
144
|
+
org.command('view [space]').description('Show one space (defaults to your only space)').option('--json', 'Machine-readable output')
|
|
145
|
+
.action((space, o, command) => runView(space, { json: !!(o.json || command.optsWithGlobals().json) }));
|
|
146
|
+
org.command('invite <email>').description('Invite (or directly add) a member')
|
|
147
|
+
.option('--role <role>', 'admin | member', 'member')
|
|
148
|
+
.option('--space <id-or-slug>', 'Space to invite into (defaults to your only space)')
|
|
149
|
+
.option('--json', 'Machine-readable output')
|
|
150
|
+
.action((email, o, command) => runInvite(email, { ...o, json: !!(o.json || command.optsWithGlobals().json) }));
|
|
151
|
+
org.command('members [space]').description('List a space\'s members').option('--json', 'Machine-readable output')
|
|
152
|
+
.action((space, o, command) => runMembers(space, { json: !!(o.json || command.optsWithGlobals().json) }));
|
|
153
|
+
org.command('role <email> <role>').description('Change a member\'s role (owner-only for admin)')
|
|
154
|
+
.option('--space <id-or-slug>', 'Space to change (defaults to your only space)')
|
|
155
|
+
.option('--json', 'Machine-readable output')
|
|
156
|
+
.action((email, role, o, command) => runRole(email, role, { ...o, json: !!(o.json || command.optsWithGlobals().json) }));
|
|
157
|
+
org.command('remove <email>').description('Remove a member from a space')
|
|
158
|
+
.option('--space <id-or-slug>', 'Space to remove from (defaults to your only space)')
|
|
159
|
+
.option('--json', 'Machine-readable output')
|
|
160
|
+
.action((email, o, command) => runRemove(email, { ...o, json: !!(o.json || command.optsWithGlobals().json) }));
|
|
161
|
+
org.command('leave [space]').description('Leave a space you do not own').option('--json', 'Machine-readable output')
|
|
162
|
+
.action((space, o, command) => runLeave(space, { json: !!(o.json || command.optsWithGlobals().json) }));
|
|
163
|
+
setHelpSections(org, {
|
|
164
|
+
examples: `agents org create acme-team
|
|
165
|
+
agents org list
|
|
166
|
+
agents org view acme-team
|
|
167
|
+
agents org invite dev@example.com --role admin
|
|
168
|
+
agents org members
|
|
169
|
+
agents org role dev@example.com admin
|
|
170
|
+
agents org remove dev@example.com
|
|
171
|
+
agents org leave acme-team`,
|
|
172
|
+
notes: 'Maps to the Rush backend\'s /api/v1/spaces (the free-tier team primitive), not /api/v1/orgs. Free tier: 1 owned space, 3 members per space. Every command needs `agents auth login` (or a `rush login` session) first. `--space` is only needed once you belong to more than one space.',
|
|
173
|
+
});
|
|
174
|
+
}
|
|
@@ -15,7 +15,7 @@ import { getRegistries, setRegistry, removeRegistry, search as searchRegistries,
|
|
|
15
15
|
import { cloneRepo } from '../lib/git.js';
|
|
16
16
|
import { discoverCommands, resolveCommandSource, installCommand, installCommandCentrally, } from '../lib/commands.js';
|
|
17
17
|
import { discoverSkillsFromRepo, installSkill, installSkillCentrally, } from '../lib/plugins/skills.js';
|
|
18
|
-
import { discoverHooksFromRepo, installHooks, installHooksCentrally, } from '../lib/hooks.js';
|
|
18
|
+
import { discoverHooksFromRepo, installHooks, installHooksCentrally, } from '../lib/hooks/install.js';
|
|
19
19
|
import { discoverWorkflowsFromRepo, installWorkflowCentrally, } from '../lib/workflows.js';
|
|
20
20
|
import { discoverSubagentsFromRepo, installSubagentCentrally, } from '../lib/subagents.js';
|
|
21
21
|
import { discoverPermissionsFromRepo, installPermissionSet, } from '../lib/permissions.js';
|
package/dist/commands/prune.js
CHANGED
|
@@ -27,7 +27,7 @@ import { formatBytes } from '../lib/format.js';
|
|
|
27
27
|
import { confirm } from '@inquirer/prompts';
|
|
28
28
|
import { diffVersionCommands, iterCommandsCapableVersions, removeCommandFromVersion, } from '../lib/commands.js';
|
|
29
29
|
import { diffVersionSkills, iterSkillsCapableVersions, removeSkillFromVersion, } from '../lib/plugins/skills.js';
|
|
30
|
-
import { listUnmanagedHooksInVersionHome, iterHooksCapableVersions, removeHookFromVersion, } from '../lib/hooks.js';
|
|
30
|
+
import { listUnmanagedHooksInVersionHome, iterHooksCapableVersions, removeHookFromVersion, } from '../lib/hooks/install.js';
|
|
31
31
|
import { diffVersionPlugins, iterPluginsCapableVersions, removePluginSkillFromVersion, } from '../lib/plugins/plugins.js';
|
|
32
32
|
import { diffVersionSubagents, iterSubagentsCapableVersions, removeSubagentFromVersion, } from '../lib/subagents.js';
|
|
33
33
|
import { getGlobalDefault } from '../lib/installations/versions.js';
|
|
@@ -24,7 +24,7 @@ import { fireWebhookJobs, matchJobsToWebhook } from '../lib/triggers/webhook.js'
|
|
|
24
24
|
import { getRoutinesDir } from '../lib/state.js';
|
|
25
25
|
import { IS_WINDOWS } from '../lib/platform/index.js';
|
|
26
26
|
import { safeJoin } from '../lib/paths.js';
|
|
27
|
-
import { executeJob, monitorRunningJobs } from '../lib/runner.js';
|
|
27
|
+
import { executeJob, monitorRunningJobs } from '../lib/daemon/runner.js';
|
|
28
28
|
import { JobScheduler } from '../lib/scheduler.js';
|
|
29
29
|
import { detectOverdueJobs } from '../lib/overdue.js';
|
|
30
30
|
import { runCatchup } from '../lib/catchup.js';
|
|
@@ -12,10 +12,29 @@ export interface RunAccountChoice {
|
|
|
12
12
|
*/
|
|
13
13
|
signInRequired: boolean;
|
|
14
14
|
}
|
|
15
|
+
/** One named account row for `agents accounts switch` (reuses this picker's layout). */
|
|
16
|
+
export interface SwitchAccountRow {
|
|
17
|
+
accountName: string;
|
|
18
|
+
kind: 'provider' | 'native';
|
|
19
|
+
detail: string;
|
|
20
|
+
current: boolean;
|
|
21
|
+
candidate: RotateCandidate | null;
|
|
22
|
+
}
|
|
15
23
|
/** Human-readable remaining capacity for every window the provider exposes. */
|
|
16
24
|
export declare function formatAccountLimits(candidate: RotateCandidate): string;
|
|
17
25
|
/** Build aligned picker rows with usable accounts first and unsafe rows disabled. */
|
|
18
26
|
export declare function buildRunAccountChoices(candidates: RotateCandidate[], globalDefault: string | null): RunAccountChoice[];
|
|
27
|
+
/**
|
|
28
|
+
* Aligned picker rows for `accounts switch`. Same columns as the run picker
|
|
29
|
+
* (identity, status, limits) but the value is the named account to set-default.
|
|
30
|
+
* Rows stay selectable: setting a default is not a launch.
|
|
31
|
+
*/
|
|
32
|
+
export declare function buildSwitchAccountChoices(rows: SwitchAccountRow[]): RunAccountChoice[];
|
|
33
|
+
/**
|
|
34
|
+
* Prompt for the named account that becomes this harness's default.
|
|
35
|
+
* A cancelled picker writes nothing.
|
|
36
|
+
*/
|
|
37
|
+
export declare function pickSwitchAccount(agent: AgentId, rows: SwitchAccountRow[]): Promise<string | null>;
|
|
19
38
|
/**
|
|
20
39
|
* Whether a zero-healthy run may recover by launching for a login, or must keep
|
|
21
40
|
* failing loud. Three inputs, all of which have to hold:
|
|
@@ -124,6 +124,87 @@ export function buildRunAccountChoices(candidates, globalDefault) {
|
|
|
124
124
|
signInRequired: row.signInRequired,
|
|
125
125
|
}));
|
|
126
126
|
}
|
|
127
|
+
function switchRowStatus(row) {
|
|
128
|
+
if (!row.candidate) {
|
|
129
|
+
return {
|
|
130
|
+
status: row.kind === 'provider' ? 'credential' : 'named',
|
|
131
|
+
limits: 'limits unavailable',
|
|
132
|
+
ready: true,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
const readiness = readinessFromCandidate(row.candidate);
|
|
136
|
+
const authReason = readiness.ready ? null : readiness.reason;
|
|
137
|
+
const status = authReason === 'revoked'
|
|
138
|
+
? 'needs re-login'
|
|
139
|
+
: authReason === 'signed_out'
|
|
140
|
+
? 'logged out'
|
|
141
|
+
: authReason === 'rate_limited'
|
|
142
|
+
? 'rate limited'
|
|
143
|
+
: authReason === 'out_of_credits'
|
|
144
|
+
? 'out of credits'
|
|
145
|
+
: 'logged in';
|
|
146
|
+
const limits = authReason === 'revoked'
|
|
147
|
+
? 'needs re-authentication'
|
|
148
|
+
: authReason === 'signed_out'
|
|
149
|
+
? 'signed out'
|
|
150
|
+
: formatAccountLimits(row.candidate);
|
|
151
|
+
return { status, limits, ready: readiness.ready };
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Aligned picker rows for `accounts switch`. Same columns as the run picker
|
|
155
|
+
* (identity, status, limits) but the value is the named account to set-default.
|
|
156
|
+
* Rows stay selectable: setting a default is not a launch.
|
|
157
|
+
*/
|
|
158
|
+
export function buildSwitchAccountChoices(rows) {
|
|
159
|
+
const rendered = rows.map((row) => {
|
|
160
|
+
const { status, limits, ready } = switchRowStatus(row);
|
|
161
|
+
const account = row.current ? `${row.accountName} (default)` : row.accountName;
|
|
162
|
+
const kind = row.kind === 'provider' ? `provider · ${row.detail}` : `native · ${row.detail}`;
|
|
163
|
+
return { account, kind, status, limits, ready, value: row.accountName };
|
|
164
|
+
});
|
|
165
|
+
const accountWidth = Math.max(0, ...rendered.map((row) => row.account.length));
|
|
166
|
+
const kindWidth = Math.max(0, ...rendered.map((row) => row.kind.length));
|
|
167
|
+
const statusWidth = Math.max(0, ...rendered.map((row) => row.status.length));
|
|
168
|
+
return rendered.map((row) => ({
|
|
169
|
+
name: [
|
|
170
|
+
row.account.padEnd(accountWidth),
|
|
171
|
+
row.kind.padEnd(kindWidth),
|
|
172
|
+
row.status.padEnd(statusWidth),
|
|
173
|
+
row.limits,
|
|
174
|
+
].join(' '),
|
|
175
|
+
value: row.value,
|
|
176
|
+
ready: row.ready,
|
|
177
|
+
signInRequired: false,
|
|
178
|
+
}));
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Prompt for the named account that becomes this harness's default.
|
|
182
|
+
* A cancelled picker writes nothing.
|
|
183
|
+
*/
|
|
184
|
+
export async function pickSwitchAccount(agent, rows) {
|
|
185
|
+
if (!isInteractiveTerminal()) {
|
|
186
|
+
requireInteractiveSelection(`Selecting a ${agentLabel(agent)} account`, [
|
|
187
|
+
`agents accounts switch ${agent} <account>`,
|
|
188
|
+
`agents accounts set-default ${agent} <account>`,
|
|
189
|
+
]);
|
|
190
|
+
}
|
|
191
|
+
if (rows.length === 0) {
|
|
192
|
+
throw new Error(`No named accounts for ${agent}. Add one with 'agents accounts add <name> --provider <p>' or 'agents accounts name ${agent}@<version> <name>'.`);
|
|
193
|
+
}
|
|
194
|
+
const choices = buildSwitchAccountChoices(rows).map(({ ready: _ready, signInRequired: _signInRequired, ...choice }) => choice);
|
|
195
|
+
try {
|
|
196
|
+
return await select({
|
|
197
|
+
message: `Select the default ${agentLabel(agent)} account:`,
|
|
198
|
+
choices,
|
|
199
|
+
loop: false,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
if (isPromptCancelled(err))
|
|
204
|
+
return null;
|
|
205
|
+
throw err;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
127
208
|
/**
|
|
128
209
|
* Whether a zero-healthy run may recover by launching for a login, or must keep
|
|
129
210
|
* failing loud. Three inputs, all of which have to hold:
|
package/dist/commands/secrets.js
CHANGED
|
@@ -18,7 +18,7 @@ import { ensureDaemonStarted, isDaemonRunning } from '../lib/daemon/daemon.js';
|
|
|
18
18
|
import { parseHostsOption, remoteResolveEnv, remoteSecretsRaw, remoteSecretsStream, resolveHostSshTarget, } from '../lib/secrets/remote.js';
|
|
19
19
|
import { resolveBundleForPush, pushResolvedBundleToHost, bundleEnvToDotenv } from '../lib/secrets/push.js';
|
|
20
20
|
export { bundleEnvToDotenv };
|
|
21
|
-
import { bundleBackend, bundleExists, bundleItemStore, bundlePolicy, deleteBundle, describeBundle, keychainItemsForBundle, keychainRef, listBundles, healKeychainBundleMetadataAclOnce, isHeadlessSecretsContext, migrateLegacyBundles, parseDotenv, readAndResolveBundleEnv, readBundle, readBundleIfDecryptable, reAclBundleItems, renameBundle, rotateBundleSecret, sanitizeProcessEnv, validateBundleName, validateEnvKey, validateExpiresFutureDated, validateSecretType, writeBundle, writeBundleWithItems, SECRET_TYPES, } from '../lib/secrets/bundles.js';
|
|
21
|
+
import { bundleBackend, bundleExists, bundleItemStore, bundlePolicy, deleteBundle, describeBundle, keychainItemsForBundle, keychainRef, listBundles, healKeychainBundleMetadataAclOnce, isHeadlessSecretsContext, isAgentInvocationContext, migrateLegacyBundles, parseDotenv, readAndResolveBundleEnv, readBundle, readBundleIfDecryptable, reAclBundleItems, renameBundle, rotateBundleSecret, sanitizeProcessEnv, validateBundleName, validateEnvKey, validateExpiresFutureDated, validateSecretType, writeBundle, writeBundleWithItems, SECRET_TYPES, } from '../lib/secrets/bundles.js';
|
|
22
22
|
import { parseListFilters, bundleMatchesFilter, bundleExpiry, filterIsActive, describeFilter, parseSortField, sortBundles, SORT_FIELDS, REF_KINDS, DEFAULT_EXPIRING_DAYS, } from '../lib/secrets/list-filter.js';
|
|
23
23
|
import { encryptForFallback, decryptForFallback } from '../lib/secrets/filestore.js';
|
|
24
24
|
import { getKeychainToken, hasKeychainToken, secretsKeychainItem, setKeychainToken, maybeAutoRekey, } from '../lib/secrets/index.js';
|
|
@@ -38,7 +38,6 @@ import { frequentlyPromptedBundles } from '../lib/secrets/unlock-hints.js';
|
|
|
38
38
|
import { registerCommandGroups, setHelpSections } from '../lib/help.js';
|
|
39
39
|
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
40
40
|
import { discoverSyncedBundles, importSyncedBundle, } from '../lib/secrets/icloud-import.js';
|
|
41
|
-
import { getVaultSession, vaultExists } from '../lib/secrets/vault.js';
|
|
42
41
|
import { registerSecretsSyncCommands } from './secrets-sync.js';
|
|
43
42
|
import { registerSecretsMigrateAclCommand } from './secrets-migrate.js';
|
|
44
43
|
import { registerSecretsImportKeyringCommand } from './secrets-import.js';
|
|
@@ -924,14 +923,17 @@ export function registerSecretsCommands(program) {
|
|
|
924
923
|
# Stop a noisy automation bundle from prompting every run: ask once a week
|
|
925
924
|
agents secrets policy prod hold
|
|
926
925
|
|
|
927
|
-
#
|
|
928
|
-
|
|
926
|
+
# Run a one-off command with secrets injected (values ride the child env, never stdout)
|
|
927
|
+
agents secrets exec prod -- ./deploy.sh
|
|
928
|
+
|
|
929
|
+
# Need one value inside a script? Read it under injection, not with a printer
|
|
930
|
+
agents secrets exec prod -- printenv STRIPE_API_KEY
|
|
931
|
+
|
|
932
|
+
# Deliberately reveal values at your terminal (interactive only)
|
|
933
|
+
agents secrets view prod --reveal
|
|
929
934
|
|
|
930
935
|
# Push the bundle to remote machine(s) over SSH (lands as a native bundle there)
|
|
931
936
|
agents secrets export prod --device yosemite-s0 --device yosemite-s1 --force
|
|
932
|
-
|
|
933
|
-
# Run a one-off command with secrets injected
|
|
934
|
-
agents secrets exec prod -- ./deploy.sh
|
|
935
937
|
`,
|
|
936
938
|
notes: `
|
|
937
939
|
Bundles are containers; secrets are the variables inside them. Keychain values
|
|
@@ -1222,13 +1224,22 @@ export function registerSecretsCommands(program) {
|
|
|
1222
1224
|
.command('view [name]')
|
|
1223
1225
|
.alias('show')
|
|
1224
1226
|
.description('Show a bundle. Keychain values are masked by default — pass --reveal to see them.')
|
|
1225
|
-
.option('--reveal', 'Print keychain-backed values in the clear (
|
|
1226
|
-
.option('--plaintext', 'Allow --reveal in non-interactive shells (use with care)')
|
|
1227
|
+
.option('--reveal', 'Print keychain-backed values in the clear (interactive terminal only)')
|
|
1227
1228
|
.option('--json', 'Emit machine-readable JSON (values masked unless --reveal) instead of the human view')
|
|
1228
1229
|
.option('--device <target>', 'Show a bundle on a remote device over SSH (enrolled `agents hosts` name, ssh-config alias, or user@host)')
|
|
1229
1230
|
.option('--devices <list>', 'Comma-separated devices to show in one shot, e.g. yosemite-s0,yosemite-s1')
|
|
1230
1231
|
.action(async (name, opts) => {
|
|
1231
1232
|
try {
|
|
1233
|
+
// `--reveal` is a deliberate HUMAN reveal: interactive terminal only, and
|
|
1234
|
+
// never inside an agent session (an agent under tmux has a TTY, so the
|
|
1235
|
+
// env markers are the load-bearing check). The old `--plaintext` escape
|
|
1236
|
+
// that allowed a piped/non-TTY reveal is removed (RUSH-2774) — printed
|
|
1237
|
+
// values land in an agent's context and session transcript.
|
|
1238
|
+
if (opts.reveal && (isAgentInvocationContext() || !isInteractiveTerminal())) {
|
|
1239
|
+
console.error(chalk.red('--reveal prints values in the clear and needs a real interactive terminal outside an agent session.'));
|
|
1240
|
+
console.error(chalk.dim('Run the consuming command under injection instead: agents secrets exec <bundle> -- <cmd>'));
|
|
1241
|
+
process.exit(1);
|
|
1242
|
+
}
|
|
1232
1243
|
const targets = parseHostsOption({ device: opts.device, devices: opts.devices });
|
|
1233
1244
|
if (targets.length > 0) {
|
|
1234
1245
|
if (!name) {
|
|
@@ -1238,14 +1249,13 @@ export function registerSecretsCommands(program) {
|
|
|
1238
1249
|
const args = ['view', name];
|
|
1239
1250
|
if (opts.reveal)
|
|
1240
1251
|
args.push('--reveal');
|
|
1241
|
-
if (opts.plaintext)
|
|
1242
|
-
args.push('--plaintext');
|
|
1243
1252
|
if (opts.json)
|
|
1244
1253
|
args.push('--json');
|
|
1245
1254
|
// With --reveal, force a TTY so the remote keychain prompt can surface
|
|
1246
|
-
//
|
|
1247
|
-
//
|
|
1248
|
-
|
|
1255
|
+
// and the remote's own interactive-terminal gate is satisfied. The
|
|
1256
|
+
// early gate above already proved this side is an interactive,
|
|
1257
|
+
// non-agent terminal.
|
|
1258
|
+
const tty = Boolean(opts.reveal);
|
|
1249
1259
|
// `--reveal` streams the plaintext value back over ssh stdout — a
|
|
1250
1260
|
// secret-bearing read that must pin the host key and not multiplex,
|
|
1251
1261
|
// whether it runs on the interactive (tty) or `--plaintext` non-tty path.
|
|
@@ -1288,14 +1298,10 @@ export function registerSecretsCommands(program) {
|
|
|
1288
1298
|
emitSecretAudit({ event: 'secrets.view', bundle: bundle.name, operation: opts.json ? 'view --json' : 'view', source: 'view', status: 'success' });
|
|
1289
1299
|
if (opts.json) {
|
|
1290
1300
|
// Machine-readable discovery for agents. Values are null unless --reveal
|
|
1291
|
-
// (
|
|
1292
|
-
//
|
|
1293
|
-
//
|
|
1301
|
+
// (already gated above: interactive terminal, outside an agent session).
|
|
1302
|
+
// Gated on the explicit flag, not stdout.isTTY, so a piped human view
|
|
1303
|
+
// never silently swaps formats.
|
|
1294
1304
|
const reveal = Boolean(opts.reveal);
|
|
1295
|
-
if (reveal && !isInteractiveTerminal() && !opts.plaintext) {
|
|
1296
|
-
console.error(chalk.red('--reveal in a non-TTY requires --plaintext.'));
|
|
1297
|
-
process.exit(1);
|
|
1298
|
-
}
|
|
1299
1305
|
const revealed = new Map();
|
|
1300
1306
|
if (reveal) {
|
|
1301
1307
|
const { env } = readAndResolveBundleEnv(bundle.name, {
|
|
@@ -1415,10 +1421,6 @@ export function registerSecretsCommands(program) {
|
|
|
1415
1421
|
return;
|
|
1416
1422
|
}
|
|
1417
1423
|
const reveal = Boolean(opts.reveal);
|
|
1418
|
-
if (reveal && !isInteractiveTerminal() && !opts.plaintext) {
|
|
1419
|
-
console.error(chalk.red('--reveal in a non-TTY requires --plaintext.'));
|
|
1420
|
-
process.exit(1);
|
|
1421
|
-
}
|
|
1422
1424
|
// An explicit human `view --reveal` at a terminal is a deliberate value
|
|
1423
1425
|
// access: resolve interactively (one Touch ID) so a locked bundle shows
|
|
1424
1426
|
// its values. Under an agent (AGENTS_RUNTIME) or headless (no TTY) it
|
|
@@ -1491,50 +1493,38 @@ export function registerSecretsCommands(program) {
|
|
|
1491
1493
|
});
|
|
1492
1494
|
cmd
|
|
1493
1495
|
.command('get <item> [key]')
|
|
1494
|
-
.description('Print one
|
|
1496
|
+
.description('Print one raw keychain item by name, for shell hooks/automation. Cross-platform. Bundle values are never printed — run commands under `agents secrets exec` instead.')
|
|
1495
1497
|
.action((item, key) => {
|
|
1496
|
-
if (key
|
|
1497
|
-
//
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
const value = getKeychainToken(item);
|
|
1505
|
-
// Raw item reads bypass readAndResolveBundleEnv, so audit here too.
|
|
1506
|
-
// `item` is the keychain service name, never the value.
|
|
1507
|
-
emitSecretAudit({ event: 'secrets.get', item, source: 'raw-item', status: 'success' });
|
|
1508
|
-
process.stdout.write(value.endsWith('\n') ? value : `${value}\n`);
|
|
1509
|
-
}
|
|
1510
|
-
catch {
|
|
1511
|
-
// Missing item is a normal, quiet outcome for a hook probe: exit 1,
|
|
1512
|
-
// print nothing to stdout. Callers test the exit code / empty capture.
|
|
1513
|
-
process.exit(1);
|
|
1514
|
-
}
|
|
1515
|
-
return;
|
|
1498
|
+
if (key !== undefined) {
|
|
1499
|
+
// The bundle-key form (`get <bundle> <KEY>`) is REMOVED (RUSH-2774): a
|
|
1500
|
+
// one-liner that prints a bundle credential is the exfiltration path
|
|
1501
|
+
// this change closes, for every caller. The injection replacement is
|
|
1502
|
+
// just as short for a human script and never touches this stdout.
|
|
1503
|
+
console.error(chalk.red(`'secrets get <bundle> <KEY>' has been removed — printing a bundle value to stdout is the exfiltration path this blocks.`));
|
|
1504
|
+
console.error(chalk.dim(`Use: agents secrets exec ${item} -- printenv ${key} (or run the consuming command directly under exec)`));
|
|
1505
|
+
process.exit(1);
|
|
1516
1506
|
}
|
|
1517
|
-
//
|
|
1518
|
-
//
|
|
1519
|
-
//
|
|
1507
|
+
// Raw keychain item path — a single ad-hoc token, not a credential bundle.
|
|
1508
|
+
// Deliberately NOT agent-gated (unlike the removed bundle-key form): shell
|
|
1509
|
+
// hooks that run inside agent sessions inherit the session env markers and
|
|
1510
|
+
// legitimately capture a raw item into their own variables (e.g. the
|
|
1511
|
+
// posthog analytics hook), where the value never reaches the transcript.
|
|
1512
|
+
// A single ad-hoc token is the accepted narrower residual (RUSH-2774).
|
|
1520
1513
|
try {
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
//
|
|
1526
|
-
|
|
1527
|
-
//
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
console.error(chalk.red(`Key '${key}' not in bundle '${item}'.`));
|
|
1531
|
-
process.exit(1);
|
|
1532
|
-
}
|
|
1533
|
-
const value = env[key];
|
|
1514
|
+
// Routes through the platform keychain layer: macOS reads bare items
|
|
1515
|
+
// via /usr/bin/security (no Touch ID), Linux via secret-tool with the
|
|
1516
|
+
// encrypted-file fallback. The value goes to stdout (newline-terminated
|
|
1517
|
+
// so `$(agents secrets get NAME)` captures it cleanly); diagnostics go
|
|
1518
|
+
// to stderr so they never pollute the captured value.
|
|
1519
|
+
const value = getKeychainToken(item);
|
|
1520
|
+
// Raw item reads bypass readAndResolveBundleEnv, so audit here too.
|
|
1521
|
+
// `item` is the keychain service name, never the value.
|
|
1522
|
+
emitSecretAudit({ event: 'secrets.get', item, source: 'raw-item', status: 'success' });
|
|
1534
1523
|
process.stdout.write(value.endsWith('\n') ? value : `${value}\n`);
|
|
1535
1524
|
}
|
|
1536
|
-
catch
|
|
1537
|
-
|
|
1525
|
+
catch {
|
|
1526
|
+
// Missing item is a normal, quiet outcome for a hook probe: exit 1,
|
|
1527
|
+
// print nothing to stdout. Callers test the exit code / empty capture.
|
|
1538
1528
|
process.exit(1);
|
|
1539
1529
|
}
|
|
1540
1530
|
});
|
|
@@ -2133,18 +2123,23 @@ Examples:
|
|
|
2133
2123
|
});
|
|
2134
2124
|
cmd
|
|
2135
2125
|
.command('export [bundle]')
|
|
2136
|
-
.description('
|
|
2137
|
-
|
|
2126
|
+
.description('Move a bundle without exposing it: push to remote machine(s) over SSH with --device, to a 1Password vault with --to-1password, or to an encrypted file with --to-file.')
|
|
2127
|
+
// Both transport flags are hidden, not documented: the ONLY caller is the
|
|
2128
|
+
// machine-to-machine SSH resolve (remoteResolveEnv / verifyRemoteKeychainPush),
|
|
2129
|
+
// which sets AGENTS_SECRETS_REMOTE_TRANSPORT=1 on the remote invocation. The
|
|
2130
|
+
// old public shell-eval mode (`eval "$(agents secrets export … --plaintext)"`)
|
|
2131
|
+
// was the top agent-exfiltration path and is removed (RUSH-2774).
|
|
2132
|
+
.addOption(new Option('--plaintext', 'internal remote-resolve transport').hideHelp())
|
|
2133
|
+
.addOption(new Option('--format <json>', 'internal remote-resolve transport').hideHelp())
|
|
2138
2134
|
.option('--to-1password', 'Push every key in the bundle as a PASSWORD item in a 1Password vault')
|
|
2139
2135
|
.option('--vault <name>', '1Password vault name (used with --to-1password)')
|
|
2140
2136
|
.option('--device <target...>', 'Push the bundle over SSH to this device (host alias or user@host); repeatable for multiple machines')
|
|
2141
2137
|
.option('--remote-backend <backend>', 'Backend for the bundle on the remote device (with --device): keychain (default) or file. file is headless-readable via the remote\'s machine-local key; it forwards AGENTS_SECRETS_PASSPHRASE over stdin only if set (opt-in).', 'keychain')
|
|
2142
2138
|
.option('--force', 'Overwrite existing keys/items on the target (used with --to-1password and --device)')
|
|
2143
|
-
.option('--format <shell|json>', 'Output for --plaintext export: shell (default) or json (lossless, machine-readable; used by remote resolve)', 'shell')
|
|
2144
2139
|
.option('--to-file <path>', `Write the bundle as an AES-256-GCM encrypted offline file (needs ${SYNC_PASSPHRASE_ENV}; symmetric counterpart of import --from-file)`)
|
|
2145
2140
|
.action(async (bundleName, opts) => {
|
|
2146
2141
|
try {
|
|
2147
|
-
const { readAndResolveBundleEnv
|
|
2142
|
+
const { readAndResolveBundleEnv } = await import('../lib/secrets/bundles.js');
|
|
2148
2143
|
const resolvedBundleName = bundleName ?? (await pickBundleName('export'));
|
|
2149
2144
|
if (opts.toFile) {
|
|
2150
2145
|
// Transport, not the local store's master key — see lib/secrets/sync-passphrase.ts.
|
|
@@ -2251,41 +2246,36 @@ Examples:
|
|
|
2251
2246
|
console.log(chalk.green(`Exported to 1Password vault '${vault}': ${parts.join(', ')}.`));
|
|
2252
2247
|
return;
|
|
2253
2248
|
}
|
|
2254
|
-
|
|
2255
|
-
|
|
2249
|
+
// The shell-eval print mode (`eval "$(agents secrets export … --plaintext)"`)
|
|
2250
|
+
// is REMOVED (RUSH-2774): a whole bundle printed to stdout lands in a
|
|
2251
|
+
// coding agent's context and session transcript, and it was the single
|
|
2252
|
+
// most-copied exfiltration one-liner on the fleet. The sole surviving
|
|
2253
|
+
// stdout emitter is the machine-to-machine SSH resolve transport, which
|
|
2254
|
+
// requires the remote-invocation marker AND a non-agent context — both
|
|
2255
|
+
// set only by remoteResolveEnv / verifyRemoteKeychainPush building the
|
|
2256
|
+
// remote command. Everything else gets the refusal with the paved paths.
|
|
2257
|
+
const isTransportCall = process.env.AGENTS_SECRETS_REMOTE_TRANSPORT === '1' &&
|
|
2258
|
+
Boolean(opts.plaintext) &&
|
|
2259
|
+
opts.format === 'json' &&
|
|
2260
|
+
!isAgentInvocationContext();
|
|
2261
|
+
if (!isTransportCall) {
|
|
2262
|
+
console.error(chalk.red('export no longer prints values. Pick a destination:'));
|
|
2263
|
+
console.error(chalk.red(' --device <host> | --to-1password | --to-file <path>'));
|
|
2264
|
+
console.error(chalk.dim(`Run a command with the values injected instead: agents secrets exec ${resolvedBundleName} -- <cmd>`));
|
|
2265
|
+
console.error(chalk.dim(`Reveal one value at your terminal: agents secrets view ${resolvedBundleName} --reveal`));
|
|
2256
2266
|
process.exit(1);
|
|
2257
2267
|
}
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
//
|
|
2263
|
-
// Every caller is broker-only, including a plain shell, so export never
|
|
2264
|
-
// raises Touch ID on the interactive user's screen.
|
|
2268
|
+
// Machine-readable form consumed by `remoteResolveEnv` over SSH: single
|
|
2269
|
+
// object of injected env KEY -> value; values verbatim. Broker-only, so a
|
|
2270
|
+
// headless remote read fails fast rather than raising Touch ID. The
|
|
2271
|
+
// resolve emits its own `secrets.get` audit on the remote; no export
|
|
2272
|
+
// event here — that would tally every peer pull as an export.
|
|
2265
2273
|
const { env } = readAndResolveBundleEnv(resolvedBundleName, {
|
|
2266
|
-
caller:
|
|
2274
|
+
caller: 'remote resolve transport',
|
|
2267
2275
|
keyMode: 'process',
|
|
2268
2276
|
agentOnly: true,
|
|
2269
2277
|
});
|
|
2270
|
-
|
|
2271
|
-
// Machine-readable form consumed by `remoteResolveEnv` over SSH.
|
|
2272
|
-
// Single object of injected env KEY -> value; values verbatim.
|
|
2273
|
-
process.stdout.write(JSON.stringify(env));
|
|
2274
|
-
return;
|
|
2275
|
-
}
|
|
2276
|
-
const prefix = bundleToEnvPrefix(resolvedBundleName);
|
|
2277
|
-
for (const [k, v] of Object.entries(env)) {
|
|
2278
|
-
const exportKey = isReservedEnvName(k) ? `${prefix}_${k}` : k;
|
|
2279
|
-
const needsQuotes = /[\s$`"'\\|&;<>(){}[\]!#~]/.test(v);
|
|
2280
|
-
const output = needsQuotes ? `'${v.replace(/'/g, `'\\''`)}'` : v;
|
|
2281
|
-
process.stdout.write(`export ${exportKey}=${output}\n`);
|
|
2282
|
-
}
|
|
2283
|
-
// Record the shell export (the `eval "$(...)"` path). The `--format json`
|
|
2284
|
-
// branch above is the machine-to-machine remote-resolve transport (already
|
|
2285
|
-
// counted as an access on this machine by the resolve above), so it is
|
|
2286
|
-
// deliberately not counted here as an export — that would tally every peer
|
|
2287
|
-
// pull as an export.
|
|
2288
|
-
emitSecretAudit({ event: 'secrets.export', bundle: resolvedBundleName, operation: 'export --plaintext', source: 'shell', status: 'success', keyCount: Object.keys(env).length });
|
|
2278
|
+
process.stdout.write(JSON.stringify(env));
|
|
2289
2279
|
}
|
|
2290
2280
|
catch (err) {
|
|
2291
2281
|
if (isPromptCancelled(err))
|