@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
|
@@ -16,7 +16,7 @@ import { execFileSync } from 'child_process';
|
|
|
16
16
|
import { getPluginsDir, getTrashPluginsDir, getExtraPluginsDir, getProjectPluginsDir, getSystemPluginsDir } from '../state.js';
|
|
17
17
|
import { IS_WINDOWS, isWindowsAbsolutePath, homeDir } from '../platform/index.js';
|
|
18
18
|
import { assertSafeGitTransport, resolveSnapshotSha } from '../git.js';
|
|
19
|
-
import { listInstalledVersions, getVersionHomePath } from '../installations/
|
|
19
|
+
import { listInstalledVersions, getVersionHomePath } from '../installations/store.js';
|
|
20
20
|
import { AGENTS, agentConfigDirName } from '../agents.js';
|
|
21
21
|
import { capableAgents, isCapable } from '../capabilities.js';
|
|
22
22
|
import { shouldInstallCommandAsSkill, installCommandSkillToVersion } from '../command-skills.js';
|
|
@@ -14,7 +14,7 @@ import { normalizeAliases } from '../resource-aliases.js';
|
|
|
14
14
|
import { AGENTS, ensureSkillsDir, agentConfigDirName } from '../agents.js';
|
|
15
15
|
import { capableAgents, isCapable } from '../capabilities.js';
|
|
16
16
|
import { getUserSkillsDir, getSkillsDir as getSystemSkillsDir, getProjectAgentsDir, getEnabledExtraRepos, getTrashSkillsDir } from '../state.js';
|
|
17
|
-
import { getEffectiveHome, getVersionHomePath, listInstalledVersions } from '../installations/
|
|
17
|
+
import { getEffectiveHome, getVersionHomePath, listInstalledVersions } from '../installations/store.js';
|
|
18
18
|
import { listCommandSkillsInVersion } from '../command-skills.js';
|
|
19
19
|
import { emit } from '../feed/events.js';
|
|
20
20
|
const HOME = os.homedir();
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client for the Rush account layer (`api.prix.dev`) — backs `agents auth` and
|
|
3
|
+
* `agents org`. `agents org` maps to `/api/v1/spaces`, not `/api/v1/orgs`: spaces
|
|
4
|
+
* already carry the free-tier caps (1 owned space, 3 members) and can exist
|
|
5
|
+
* standalone with no parent organization, matching a "just want a team" CLI flow
|
|
6
|
+
* better than the heavier enterprise-tenancy `orgs` routes (domain, SSO). See
|
|
7
|
+
* `.agents/artifacts/2026-08-20/agi-cli-client-integration-spec.md` §4.2 in the
|
|
8
|
+
* agi-cli-web repo for the full reasoning and the live-confirmed route/shape audit.
|
|
9
|
+
*
|
|
10
|
+
* Token custody: `agents auth login` writes its own session file
|
|
11
|
+
* (`getPrixSessionFile()`), separate from `~/.rush/user.yaml`, so `agents auth
|
|
12
|
+
* logout` never signs the user out of `rush` (the two CLIs share a backend
|
|
13
|
+
* account, not a credential store). Reads fall back to `~/.rush/user.yaml`
|
|
14
|
+
* (the pattern `readRushToken` in `lib/secrets/drivers/rush.ts` already uses)
|
|
15
|
+
* so a user who is only signed in via `rush login` still gets a working
|
|
16
|
+
* `agents auth whoami` / `agents org` with zero extra login step.
|
|
17
|
+
*/
|
|
18
|
+
export declare const PRIX_API_BASE = "https://api.prix.dev";
|
|
19
|
+
/** The session `agents auth login` owns. */
|
|
20
|
+
export interface PrixSession {
|
|
21
|
+
access_token: string;
|
|
22
|
+
refresh_token?: string;
|
|
23
|
+
/** Unix ms. */
|
|
24
|
+
expires_at?: number;
|
|
25
|
+
email?: string;
|
|
26
|
+
userId?: string;
|
|
27
|
+
}
|
|
28
|
+
export declare function getPrixSessionFile(): string;
|
|
29
|
+
export declare function readPrixSession(): PrixSession | null;
|
|
30
|
+
export declare function writePrixSession(session: PrixSession): void;
|
|
31
|
+
export declare function clearPrixSession(): boolean;
|
|
32
|
+
/** Where the caller's Bearer token came from — surfaced by `whoami` so the user knows which login is live. */
|
|
33
|
+
export type PrixTokenSource = 'agents' | 'rush';
|
|
34
|
+
export declare function resolvePrixToken(): {
|
|
35
|
+
token: string;
|
|
36
|
+
source: PrixTokenSource;
|
|
37
|
+
} | null;
|
|
38
|
+
export declare class PrixApiError extends Error {
|
|
39
|
+
status: number;
|
|
40
|
+
constructor(status: number, message: string);
|
|
41
|
+
}
|
|
42
|
+
export interface WhoAmI {
|
|
43
|
+
userId: string;
|
|
44
|
+
email: string;
|
|
45
|
+
valid: true;
|
|
46
|
+
}
|
|
47
|
+
/** `GET /api/v1/auth/me` — live-confirmed shape: `{email, userId, valid}`. */
|
|
48
|
+
export declare function fetchWhoAmI(token?: string): Promise<WhoAmI>;
|
|
49
|
+
export interface DeviceAuthorization {
|
|
50
|
+
device_code: string;
|
|
51
|
+
user_code: string;
|
|
52
|
+
verification_uri: string;
|
|
53
|
+
verification_uri_complete: string;
|
|
54
|
+
expires_in: number;
|
|
55
|
+
interval: number;
|
|
56
|
+
}
|
|
57
|
+
/** `POST /api/v1/auth/device/authorization` — public, no token. */
|
|
58
|
+
export declare function startDeviceAuthorization(): Promise<DeviceAuthorization>;
|
|
59
|
+
export type DeviceTokenPoll = {
|
|
60
|
+
status: 'authorized';
|
|
61
|
+
access_token: string;
|
|
62
|
+
refresh_token?: string;
|
|
63
|
+
expires_in?: number;
|
|
64
|
+
user: {
|
|
65
|
+
email: string;
|
|
66
|
+
id: string;
|
|
67
|
+
};
|
|
68
|
+
} | {
|
|
69
|
+
status: 'pending';
|
|
70
|
+
} | {
|
|
71
|
+
status: 'slow_down';
|
|
72
|
+
} | {
|
|
73
|
+
status: 'expired';
|
|
74
|
+
} | {
|
|
75
|
+
status: 'denied';
|
|
76
|
+
};
|
|
77
|
+
/** `POST /api/v1/auth/device/token` — one poll attempt. Callers own the interval loop. */
|
|
78
|
+
export declare function pollDeviceToken(deviceCode: string): Promise<DeviceTokenPoll>;
|
|
79
|
+
export interface SpaceSummary {
|
|
80
|
+
id: string;
|
|
81
|
+
slug: string;
|
|
82
|
+
name: string;
|
|
83
|
+
organization_id: string | null;
|
|
84
|
+
owner_user_id: string;
|
|
85
|
+
invite_code?: string;
|
|
86
|
+
user_role: 'owner' | 'admin' | 'member';
|
|
87
|
+
created_at: string;
|
|
88
|
+
}
|
|
89
|
+
export interface SpaceMember {
|
|
90
|
+
user_id: string;
|
|
91
|
+
email: string;
|
|
92
|
+
name?: string;
|
|
93
|
+
avatar_url?: string;
|
|
94
|
+
role: 'owner' | 'admin' | 'member';
|
|
95
|
+
joined_at: string;
|
|
96
|
+
}
|
|
97
|
+
/** `GET /api/v1/spaces` — live-confirmed: array of `SpaceSummary`. */
|
|
98
|
+
export declare function listSpaces(): Promise<SpaceSummary[]>;
|
|
99
|
+
/** `POST /api/v1/spaces` — 403 if the caller already owns a space (free tier: 1). */
|
|
100
|
+
export declare function createSpace(input: {
|
|
101
|
+
name: string;
|
|
102
|
+
slug: string;
|
|
103
|
+
description?: string;
|
|
104
|
+
}): Promise<SpaceSummary>;
|
|
105
|
+
/** `GET /api/v1/spaces/:id` — requires membership. */
|
|
106
|
+
export declare function getSpace(spaceId: string): Promise<SpaceSummary>;
|
|
107
|
+
/** `GET /api/v1/spaces/:id/members`. */
|
|
108
|
+
export declare function listSpaceMembers(spaceId: string): Promise<SpaceMember[]>;
|
|
109
|
+
export type CreateSpaceInviteResult = {
|
|
110
|
+
invited: true;
|
|
111
|
+
email: string;
|
|
112
|
+
role: string;
|
|
113
|
+
member_added: true;
|
|
114
|
+
} | {
|
|
115
|
+
invited: true;
|
|
116
|
+
email: string;
|
|
117
|
+
role: string;
|
|
118
|
+
invite_code: string;
|
|
119
|
+
member_added?: false;
|
|
120
|
+
};
|
|
121
|
+
/** `POST /api/v1/spaces/:id/invites` — sends a real email for the pending-invite path. */
|
|
122
|
+
export declare function createSpaceInvite(spaceId: string, email: string, role?: 'admin' | 'member'): Promise<CreateSpaceInviteResult>;
|
|
123
|
+
export interface SpaceInvite {
|
|
124
|
+
id: string;
|
|
125
|
+
space_id: string;
|
|
126
|
+
email: string;
|
|
127
|
+
role: 'admin' | 'member';
|
|
128
|
+
invite_code: string;
|
|
129
|
+
created_at: string;
|
|
130
|
+
}
|
|
131
|
+
/** `GET /api/v1/spaces/:id/invites`. */
|
|
132
|
+
export declare function listSpaceInvites(spaceId: string): Promise<SpaceInvite[]>;
|
|
133
|
+
/** `DELETE /api/v1/spaces/:id/invites/:inviteId`. */
|
|
134
|
+
export declare function revokeSpaceInvite(spaceId: string, inviteId: string): Promise<{
|
|
135
|
+
revoked: true;
|
|
136
|
+
}>;
|
|
137
|
+
/** `PATCH /api/v1/spaces/:id/members/:userId` — owner-only for admin changes. Route takes userId, not email. */
|
|
138
|
+
export declare function updateSpaceMemberRole(spaceId: string, userId: string, role: 'admin' | 'member'): Promise<{
|
|
139
|
+
user_id: string;
|
|
140
|
+
role: string;
|
|
141
|
+
updated: true;
|
|
142
|
+
}>;
|
|
143
|
+
/** `DELETE /api/v1/spaces/:id/members/:userId` — owner, admin, or the member themself (leave). */
|
|
144
|
+
export declare function removeSpaceMember(spaceId: string, userId: string): Promise<void>;
|
|
145
|
+
/** `DELETE /api/v1/spaces/:id` — soft delete, 30-day restore window. */
|
|
146
|
+
export declare function deleteSpace(spaceId: string): Promise<void>;
|
|
147
|
+
/** `agents-cli-space-name` -> `agi-cli-space-name`; lowercase, hyphenated, matches the backend's `^[a-z0-9-]+$` slug rule. */
|
|
148
|
+
export declare function slugify(name: string): string;
|
|
149
|
+
/**
|
|
150
|
+
* Resolve the `--space` a command should act on: an explicit id/slug match
|
|
151
|
+
* against the caller's own space list, or — with nothing passed — the
|
|
152
|
+
* caller's sole space (free tier caps ownership at one, so this is almost
|
|
153
|
+
* always unambiguous). Pure over an already-fetched list so it's cheaply
|
|
154
|
+
* unit-testable with a fixture.
|
|
155
|
+
*/
|
|
156
|
+
export declare function resolveSpaceFromList(spaces: SpaceSummary[], explicit?: string): SpaceSummary;
|
|
157
|
+
/** Resolve a member's email to their `user_id` from an already-fetched member list. */
|
|
158
|
+
export declare function resolveMemberFromList(members: SpaceMember[], email: string): SpaceMember;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client for the Rush account layer (`api.prix.dev`) — backs `agents auth` and
|
|
3
|
+
* `agents org`. `agents org` maps to `/api/v1/spaces`, not `/api/v1/orgs`: spaces
|
|
4
|
+
* already carry the free-tier caps (1 owned space, 3 members) and can exist
|
|
5
|
+
* standalone with no parent organization, matching a "just want a team" CLI flow
|
|
6
|
+
* better than the heavier enterprise-tenancy `orgs` routes (domain, SSO). See
|
|
7
|
+
* `.agents/artifacts/2026-08-20/agi-cli-client-integration-spec.md` §4.2 in the
|
|
8
|
+
* agi-cli-web repo for the full reasoning and the live-confirmed route/shape audit.
|
|
9
|
+
*
|
|
10
|
+
* Token custody: `agents auth login` writes its own session file
|
|
11
|
+
* (`getPrixSessionFile()`), separate from `~/.rush/user.yaml`, so `agents auth
|
|
12
|
+
* logout` never signs the user out of `rush` (the two CLIs share a backend
|
|
13
|
+
* account, not a credential store). Reads fall back to `~/.rush/user.yaml`
|
|
14
|
+
* (the pattern `readRushToken` in `lib/secrets/drivers/rush.ts` already uses)
|
|
15
|
+
* so a user who is only signed in via `rush login` still gets a working
|
|
16
|
+
* `agents auth whoami` / `agents org` with zero extra login step.
|
|
17
|
+
*/
|
|
18
|
+
import * as fs from 'fs';
|
|
19
|
+
import * as os from 'os';
|
|
20
|
+
import * as path from 'path';
|
|
21
|
+
import * as yaml from 'yaml';
|
|
22
|
+
import { getRuntimeStateDir } from './state.js';
|
|
23
|
+
export const PRIX_API_BASE = 'https://api.prix.dev';
|
|
24
|
+
const RUSH_USER_YAML = path.join(os.homedir(), '.rush', 'user.yaml');
|
|
25
|
+
/** Computed per-call (not cached at module load) so `AGENTS_STATE_DIR` overrides in tests take effect. */
|
|
26
|
+
function prixSessionFile() {
|
|
27
|
+
return path.join(getRuntimeStateDir(), 'prix-account.json');
|
|
28
|
+
}
|
|
29
|
+
/** Read the token `rush login` wrote, with no expiry check — the same shape `readRushToken` reads. */
|
|
30
|
+
function readRushSessionToken() {
|
|
31
|
+
if (!fs.existsSync(RUSH_USER_YAML))
|
|
32
|
+
return null;
|
|
33
|
+
try {
|
|
34
|
+
const data = yaml.parse(fs.readFileSync(RUSH_USER_YAML, 'utf-8'));
|
|
35
|
+
return data?.session?.access_token ?? null;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function getPrixSessionFile() {
|
|
42
|
+
return prixSessionFile();
|
|
43
|
+
}
|
|
44
|
+
export function readPrixSession() {
|
|
45
|
+
const file = prixSessionFile();
|
|
46
|
+
if (!fs.existsSync(file))
|
|
47
|
+
return null;
|
|
48
|
+
try {
|
|
49
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
50
|
+
if (!parsed || typeof parsed.access_token !== 'string')
|
|
51
|
+
return null;
|
|
52
|
+
return parsed;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export function writePrixSession(session) {
|
|
59
|
+
const file = prixSessionFile();
|
|
60
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
61
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
62
|
+
fs.writeFileSync(tmp, JSON.stringify(session, null, 2), { mode: 0o600 });
|
|
63
|
+
fs.renameSync(tmp, file);
|
|
64
|
+
}
|
|
65
|
+
export function clearPrixSession() {
|
|
66
|
+
const file = prixSessionFile();
|
|
67
|
+
if (!fs.existsSync(file))
|
|
68
|
+
return false;
|
|
69
|
+
fs.rmSync(file);
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
export function resolvePrixToken() {
|
|
73
|
+
const own = readPrixSession();
|
|
74
|
+
if (own?.access_token)
|
|
75
|
+
return { token: own.access_token, source: 'agents' };
|
|
76
|
+
const rushToken = readRushSessionToken();
|
|
77
|
+
if (rushToken)
|
|
78
|
+
return { token: rushToken, source: 'rush' };
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
export class PrixApiError extends Error {
|
|
82
|
+
status;
|
|
83
|
+
constructor(status, message) {
|
|
84
|
+
super(message);
|
|
85
|
+
this.status = status;
|
|
86
|
+
this.name = 'PrixApiError';
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async function prixRequest(method, endpoint, opts = {}) {
|
|
90
|
+
const auth = opts.auth !== false;
|
|
91
|
+
let token = opts.token;
|
|
92
|
+
if (auth && !token) {
|
|
93
|
+
const resolved = resolvePrixToken();
|
|
94
|
+
if (!resolved)
|
|
95
|
+
throw new Error("Not signed in. Run 'agents auth login' first.");
|
|
96
|
+
token = resolved.token;
|
|
97
|
+
}
|
|
98
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
99
|
+
if (token)
|
|
100
|
+
headers.Authorization = `Bearer ${token}`;
|
|
101
|
+
const res = await fetch(`${PRIX_API_BASE}${endpoint}`, {
|
|
102
|
+
method,
|
|
103
|
+
headers,
|
|
104
|
+
body: opts.body === undefined ? undefined : JSON.stringify(opts.body),
|
|
105
|
+
});
|
|
106
|
+
const text = await res.text();
|
|
107
|
+
const data = text ? JSON.parse(text) : undefined;
|
|
108
|
+
if (!res.ok) {
|
|
109
|
+
const message = (data && typeof data === 'object' && 'error' in data) ? String(data.error) : `${res.status} ${res.statusText}`;
|
|
110
|
+
throw new PrixApiError(res.status, message);
|
|
111
|
+
}
|
|
112
|
+
return data;
|
|
113
|
+
}
|
|
114
|
+
/** `GET /api/v1/auth/me` — live-confirmed shape: `{email, userId, valid}`. */
|
|
115
|
+
export async function fetchWhoAmI(token) {
|
|
116
|
+
return prixRequest('GET', '/api/v1/auth/me', { token });
|
|
117
|
+
}
|
|
118
|
+
/** `POST /api/v1/auth/device/authorization` — public, no token. */
|
|
119
|
+
export async function startDeviceAuthorization() {
|
|
120
|
+
return prixRequest('POST', '/api/v1/auth/device/authorization', { auth: false, body: {} });
|
|
121
|
+
}
|
|
122
|
+
/** `POST /api/v1/auth/device/token` — one poll attempt. Callers own the interval loop. */
|
|
123
|
+
export async function pollDeviceToken(deviceCode) {
|
|
124
|
+
try {
|
|
125
|
+
const data = await prixRequest('POST', '/api/v1/auth/device/token', { auth: false, body: { grant_type: 'urn:ietf:params:oauth:grant-type:device_code', device_code: deviceCode } });
|
|
126
|
+
return { status: 'authorized', ...data };
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
if (err instanceof PrixApiError) {
|
|
130
|
+
if (err.message.includes('authorization_pending'))
|
|
131
|
+
return { status: 'pending' };
|
|
132
|
+
if (err.message.includes('slow_down'))
|
|
133
|
+
return { status: 'slow_down' };
|
|
134
|
+
if (err.message.includes('expired_token'))
|
|
135
|
+
return { status: 'expired' };
|
|
136
|
+
if (err.message.includes('access_denied'))
|
|
137
|
+
return { status: 'denied' };
|
|
138
|
+
}
|
|
139
|
+
throw err;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** `GET /api/v1/spaces` — live-confirmed: array of `SpaceSummary`. */
|
|
143
|
+
export async function listSpaces() {
|
|
144
|
+
return prixRequest('GET', '/api/v1/spaces');
|
|
145
|
+
}
|
|
146
|
+
/** `POST /api/v1/spaces` — 403 if the caller already owns a space (free tier: 1). */
|
|
147
|
+
export async function createSpace(input) {
|
|
148
|
+
return prixRequest('POST', '/api/v1/spaces', { body: input });
|
|
149
|
+
}
|
|
150
|
+
/** `GET /api/v1/spaces/:id` — requires membership. */
|
|
151
|
+
export async function getSpace(spaceId) {
|
|
152
|
+
return prixRequest('GET', `/api/v1/spaces/${encodeURIComponent(spaceId)}`);
|
|
153
|
+
}
|
|
154
|
+
/** `GET /api/v1/spaces/:id/members`. */
|
|
155
|
+
export async function listSpaceMembers(spaceId) {
|
|
156
|
+
return prixRequest('GET', `/api/v1/spaces/${encodeURIComponent(spaceId)}/members`);
|
|
157
|
+
}
|
|
158
|
+
/** `POST /api/v1/spaces/:id/invites` — sends a real email for the pending-invite path. */
|
|
159
|
+
export async function createSpaceInvite(spaceId, email, role = 'member') {
|
|
160
|
+
return prixRequest('POST', `/api/v1/spaces/${encodeURIComponent(spaceId)}/invites`, { body: { email, role } });
|
|
161
|
+
}
|
|
162
|
+
/** `GET /api/v1/spaces/:id/invites`. */
|
|
163
|
+
export async function listSpaceInvites(spaceId) {
|
|
164
|
+
return prixRequest('GET', `/api/v1/spaces/${encodeURIComponent(spaceId)}/invites`);
|
|
165
|
+
}
|
|
166
|
+
/** `DELETE /api/v1/spaces/:id/invites/:inviteId`. */
|
|
167
|
+
export async function revokeSpaceInvite(spaceId, inviteId) {
|
|
168
|
+
return prixRequest('DELETE', `/api/v1/spaces/${encodeURIComponent(spaceId)}/invites/${encodeURIComponent(inviteId)}`);
|
|
169
|
+
}
|
|
170
|
+
/** `PATCH /api/v1/spaces/:id/members/:userId` — owner-only for admin changes. Route takes userId, not email. */
|
|
171
|
+
export async function updateSpaceMemberRole(spaceId, userId, role) {
|
|
172
|
+
return prixRequest('PATCH', `/api/v1/spaces/${encodeURIComponent(spaceId)}/members/${encodeURIComponent(userId)}`, { body: { role } });
|
|
173
|
+
}
|
|
174
|
+
/** `DELETE /api/v1/spaces/:id/members/:userId` — owner, admin, or the member themself (leave). */
|
|
175
|
+
export async function removeSpaceMember(spaceId, userId) {
|
|
176
|
+
await prixRequest('DELETE', `/api/v1/spaces/${encodeURIComponent(spaceId)}/members/${encodeURIComponent(userId)}`);
|
|
177
|
+
}
|
|
178
|
+
/** `DELETE /api/v1/spaces/:id` — soft delete, 30-day restore window. */
|
|
179
|
+
export async function deleteSpace(spaceId) {
|
|
180
|
+
await prixRequest('DELETE', `/api/v1/spaces/${encodeURIComponent(spaceId)}`);
|
|
181
|
+
}
|
|
182
|
+
/** `agents-cli-space-name` -> `agi-cli-space-name`; lowercase, hyphenated, matches the backend's `^[a-z0-9-]+$` slug rule. */
|
|
183
|
+
export function slugify(name) {
|
|
184
|
+
const slug = name.toLowerCase().trim().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
185
|
+
return slug || 'space';
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Resolve the `--space` a command should act on: an explicit id/slug match
|
|
189
|
+
* against the caller's own space list, or — with nothing passed — the
|
|
190
|
+
* caller's sole space (free tier caps ownership at one, so this is almost
|
|
191
|
+
* always unambiguous). Pure over an already-fetched list so it's cheaply
|
|
192
|
+
* unit-testable with a fixture.
|
|
193
|
+
*/
|
|
194
|
+
export function resolveSpaceFromList(spaces, explicit) {
|
|
195
|
+
if (explicit) {
|
|
196
|
+
const match = spaces.find(s => s.id === explicit || s.slug === explicit);
|
|
197
|
+
if (!match)
|
|
198
|
+
throw new Error(`No space matching '${explicit}'. Run 'agents org list' to see your spaces.`);
|
|
199
|
+
return match;
|
|
200
|
+
}
|
|
201
|
+
if (spaces.length === 0)
|
|
202
|
+
throw new Error("You have no spaces. Create one with 'agents org create <name>'.");
|
|
203
|
+
if (spaces.length > 1) {
|
|
204
|
+
throw new Error(`You belong to ${spaces.length} spaces — pass --space <id-or-slug>: ${spaces.map(s => s.slug).join(', ')}`);
|
|
205
|
+
}
|
|
206
|
+
return spaces[0];
|
|
207
|
+
}
|
|
208
|
+
/** Resolve a member's email to their `user_id` from an already-fetched member list. */
|
|
209
|
+
export function resolveMemberFromList(members, email) {
|
|
210
|
+
const match = members.find(m => m.email.toLowerCase() === email.toLowerCase());
|
|
211
|
+
if (!match)
|
|
212
|
+
throw new Error(`'${email}' is not a member of this space. Run 'agents org members' to see who is.`);
|
|
213
|
+
return match;
|
|
214
|
+
}
|
package/dist/lib/project-key.js
CHANGED
|
@@ -55,6 +55,13 @@ export function repoRootForCwd(dir, home = os.homedir()) {
|
|
|
55
55
|
for (;;) {
|
|
56
56
|
if (fs.existsSync(path.join(current, '.git')))
|
|
57
57
|
return current === stop ? undefined : current;
|
|
58
|
+
// Never climb past $HOME: an ancestor of home (/tmp, /, ...) is not part of
|
|
59
|
+
// any project this cwd belongs to, and treating one as the repo root would
|
|
60
|
+
// let unrelated host state (e.g. a stray /tmp/.git) swallow every loose
|
|
61
|
+
// directory under home. Mirrors the shim's own home boundary — see the codex
|
|
62
|
+
// adapter's shimExecTail (lib/harness/adapters/codex.ts).
|
|
63
|
+
if (current === stop)
|
|
64
|
+
return undefined;
|
|
58
65
|
const parent = path.dirname(current);
|
|
59
66
|
if (parent === current)
|
|
60
67
|
return undefined;
|
|
@@ -86,7 +86,7 @@ export function runLaunchSync(opts) {
|
|
|
86
86
|
}
|
|
87
87
|
/**
|
|
88
88
|
* Path of the shim's skip-fast sentinel for this (agent, version, cwd) tuple.
|
|
89
|
-
* Must match the SHIM-SIDE format in src/lib/shims.ts (PROJECT_SLUG derivation),
|
|
89
|
+
* Must match the SHIM-SIDE format in src/lib/installations/shims.ts (PROJECT_SLUG derivation),
|
|
90
90
|
* which is the canonical `toPortableKey` mapping: drop the Windows drive colon
|
|
91
91
|
* and fold `\`, `/`, and ` ` → `_`. On POSIX this is byte-identical to the old
|
|
92
92
|
* `/` and ` ` → `_` slug; on Windows it yields a legal filename (no `C:\`).
|
package/dist/lib/refresh.js
CHANGED
|
@@ -20,8 +20,8 @@ import { readManifest, MANIFEST_FILENAME } from './manifest.js';
|
|
|
20
20
|
import { getUserAgentsDir } from './state.js';
|
|
21
21
|
import { installVersion, listInstalledVersions, isVersionIsolated, getGlobalDefault, setGlobalDefault, getVersionHomePath, syncResourcesToVersion, getAvailableResources, getActuallySyncedResources, getNewResources, getProjectOnlyResources, hasNewResources, promptNewResourceSelection, promptResourceSelection, resolveConfiguredAgentTargets, } from './installations/versions.js';
|
|
22
22
|
import { listCliStatus, installCli, describeMethod, describeCheck, selectInstallMethod, } from './cli-resources.js';
|
|
23
|
-
import { ensureShimCurrent, isShimsInPath, addShimsToPath, getPathSetupInstructions, switchConfigSymlink, switchHomeFileSymlinks, } from './shims.js';
|
|
24
|
-
import { parseHookManifest, registerHooksToSettings } from './hooks.js';
|
|
23
|
+
import { ensureShimCurrent, isShimsInPath, addShimsToPath, getPathSetupInstructions, switchConfigSymlink, switchHomeFileSymlinks, } from './installations/shims.js';
|
|
24
|
+
import { parseHookManifest, registerHooksToSettings } from './hooks/install.js';
|
|
25
25
|
import { isPromptCancelled } from './format.js';
|
|
26
26
|
/**
|
|
27
27
|
* Old repo layout stored promptcuts under claude/promptcuts.yaml (agent-scoped).
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* muse), Grok hooks.json, and Kimi config.toml via checkVersionHookWiring.
|
|
19
19
|
*/
|
|
20
20
|
import type { AgentId } from './types.js';
|
|
21
|
-
import { type HookWiringReport } from './hooks.js';
|
|
21
|
+
import { type HookWiringReport } from './hooks/install.js';
|
|
22
22
|
/** Resource kinds the inventory can report. Only 'hooks' is implemented so far
|
|
23
23
|
* (RUSH-2238); the remaining kinds land with their follow-up tickets. */
|
|
24
24
|
export type InventoryKind = 'hooks' | 'skills' | 'commands' | 'plugins' | 'mcp';
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
import { AGENTS } from './agents.js';
|
|
21
21
|
import { supports } from './capabilities.js';
|
|
22
22
|
import { listResources } from './resources.js';
|
|
23
|
-
import { checkVersionHookWiring, listHooksInVersionHome, listInstalledHooksWithScope, } from './hooks.js';
|
|
23
|
+
import { checkVersionHookWiring, listHooksInVersionHome, listInstalledHooksWithScope, } from './hooks/install.js';
|
|
24
24
|
const IMPLEMENTED_KINDS = ['hooks'];
|
|
25
25
|
/**
|
|
26
26
|
* The single inventory API. Harness-scoped (claude/codex/grok/kimi/droid) —
|
package/dist/lib/rules/rules.js
CHANGED
|
@@ -10,7 +10,7 @@ import * as fs from 'fs';
|
|
|
10
10
|
import * as path from 'path';
|
|
11
11
|
import { AGENTS, ALL_AGENT_IDS, agentConfigDirName } from '../agents.js';
|
|
12
12
|
import { getResolvedRulesDir, getUserRulesDir, getProjectAgentsDir } from '../state.js';
|
|
13
|
-
import { getEffectiveHome } from '../installations/
|
|
13
|
+
import { getEffectiveHome } from '../installations/store.js';
|
|
14
14
|
/**
|
|
15
15
|
* Central rules filename constant.
|
|
16
16
|
* All agents map to this file in ~/.agents/rules/, renamed per-agent when synced.
|
|
@@ -25,7 +25,7 @@ import { enabledRoutineNames, devicesWithRoutineEnabled, replaceEnabledRoutines,
|
|
|
25
25
|
import { humanizeCron, humanizeNextRun } from '../routines-format.js';
|
|
26
26
|
import { discoverProjectRoutines } from '../routines-project.js';
|
|
27
27
|
import { listProjectDefs } from '../projects.js';
|
|
28
|
-
import { monitorRunningJobs } from '../runner.js';
|
|
28
|
+
import { monitorRunningJobs } from '../daemon/runner.js';
|
|
29
29
|
import { JobScheduler } from '../scheduler.js';
|
|
30
30
|
import { detectOverdueJobs } from '../overdue.js';
|
|
31
31
|
export function fireConditionLabel(job) {
|
|
Binary file
|
|
Binary file
|
|
@@ -305,7 +305,7 @@ export declare function resolveBundleEnv(bundle: SecretsBundle, _opts?: ResolveB
|
|
|
305
305
|
* path in index.ts can share it without a bundles↔index import cycle. See that
|
|
306
306
|
* module for the full contract.
|
|
307
307
|
*/
|
|
308
|
-
export { isHeadlessSecretsContext } from './headless.js';
|
|
308
|
+
export { isHeadlessSecretsContext, isAgentInvocationContext } from './headless.js';
|
|
309
309
|
/**
|
|
310
310
|
* Read a bundle's metadata AND resolve its env in a single Touch ID prompt.
|
|
311
311
|
*
|
|
@@ -1211,7 +1211,7 @@ export function resolveBundleEnv(bundle, _opts = {}) {
|
|
|
1211
1211
|
* path in index.ts can share it without a bundles↔index import cycle. See that
|
|
1212
1212
|
* module for the full contract.
|
|
1213
1213
|
*/
|
|
1214
|
-
export { isHeadlessSecretsContext } from './headless.js';
|
|
1214
|
+
export { isHeadlessSecretsContext, isAgentInvocationContext } from './headless.js';
|
|
1215
1215
|
/**
|
|
1216
1216
|
* Read a bundle's metadata AND resolve its env in a single Touch ID prompt.
|
|
1217
1217
|
*
|
|
@@ -25,3 +25,19 @@
|
|
|
25
25
|
* the per-caller broker-only pattern used across the headless secrets readers.
|
|
26
26
|
*/
|
|
27
27
|
export declare function isHeadlessSecretsContext(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* True when this process runs structurally inside a coding-agent session: any
|
|
30
|
+
* `agents run` launch (AGENTS_RUNTIME / AGENT_SESSION_ID ride the child env —
|
|
31
|
+
* exec.ts buildExecEnv — and are inherited by every shell the agent spawns), or
|
|
32
|
+
* a harness's own tool shell launched outside agents-cli (Claude Code stamps
|
|
33
|
+
* CLAUDECODE on its Bash tool).
|
|
34
|
+
*
|
|
35
|
+
* Distinct from isHeadlessSecretsContext above on both axes: it is
|
|
36
|
+
* platform-independent (no biometry involved), and a TTY does not clear it (an
|
|
37
|
+
* agent inside tmux has one). Its job is the materialization boundary, not the
|
|
38
|
+
* prompt boundary: a command that would PRINT a plaintext value refuses under
|
|
39
|
+
* an agent, because anything printed lands in the agent's context and session
|
|
40
|
+
* transcript. The agent path to secret values is injection — `secrets exec`,
|
|
41
|
+
* `run --secrets` — which places them only in a child process env.
|
|
42
|
+
*/
|
|
43
|
+
export declare function isAgentInvocationContext(env?: NodeJS.ProcessEnv): boolean;
|
|
@@ -33,3 +33,24 @@ export function isHeadlessSecretsContext(env = process.env, platform = process.p
|
|
|
33
33
|
return true;
|
|
34
34
|
return false;
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* True when this process runs structurally inside a coding-agent session: any
|
|
38
|
+
* `agents run` launch (AGENTS_RUNTIME / AGENT_SESSION_ID ride the child env —
|
|
39
|
+
* exec.ts buildExecEnv — and are inherited by every shell the agent spawns), or
|
|
40
|
+
* a harness's own tool shell launched outside agents-cli (Claude Code stamps
|
|
41
|
+
* CLAUDECODE on its Bash tool).
|
|
42
|
+
*
|
|
43
|
+
* Distinct from isHeadlessSecretsContext above on both axes: it is
|
|
44
|
+
* platform-independent (no biometry involved), and a TTY does not clear it (an
|
|
45
|
+
* agent inside tmux has one). Its job is the materialization boundary, not the
|
|
46
|
+
* prompt boundary: a command that would PRINT a plaintext value refuses under
|
|
47
|
+
* an agent, because anything printed lands in the agent's context and session
|
|
48
|
+
* transcript. The agent path to secret values is injection — `secrets exec`,
|
|
49
|
+
* `run --secrets` — which places them only in a child process env.
|
|
50
|
+
*/
|
|
51
|
+
export function isAgentInvocationContext(env = process.env) {
|
|
52
|
+
return Boolean(env.AGENTS_RUNTIME ||
|
|
53
|
+
env.AGENT_SESSION_ID ||
|
|
54
|
+
env.AGENTS_SESSION_ID ||
|
|
55
|
+
env.CLAUDECODE);
|
|
56
|
+
}
|
|
@@ -169,9 +169,10 @@ export type RemoteKeychainWriteVerification = {
|
|
|
169
169
|
* metadata `noAcl` last — bundles.ts writeBundleWithItems). The metadata-only bundle
|
|
170
170
|
* then fails every later read with the confusing `Bundle '<b>' key '<k>': stored
|
|
171
171
|
* item '<item>' not found` (bundles.ts resolveBundleEnv). We catch it by reading
|
|
172
|
-
* the bundle back the same way a
|
|
173
|
-
* json
|
|
174
|
-
* any keychain read — no Touch ID prompt) and confirming every pushed key
|
|
172
|
+
* the bundle back the same way a later `secrets exec`/resolve will (the marker-gated
|
|
173
|
+
* json transport, driven headlessly on the remote so its `agentOnly` guard FAILS FAST
|
|
174
|
+
* before any keychain read — no Touch ID prompt) and confirming every pushed key
|
|
175
|
+
* returned.
|
|
175
176
|
*
|
|
176
177
|
* Pure so both branches are unit-testable without a real locked keychain: inject the
|
|
177
178
|
* "read-back failed / key absent" condition through `readBack`.
|
|
@@ -193,10 +194,11 @@ export declare function keychainWriteFailureMessage(host: string, bundle: string
|
|
|
193
194
|
/**
|
|
194
195
|
* Read a bundle back from a remote over SSH (headlessly, so it fails fast rather
|
|
195
196
|
* than prompting Touch ID) and confirm the pushed keys materialized. Drives the
|
|
196
|
-
* remote's
|
|
197
|
-
*
|
|
198
|
-
* are dropped immediately and never retained or
|
|
199
|
-
* verdict; the caller renders
|
|
197
|
+
* remote's marker-gated json transport (`secrets export <bundle> --plaintext
|
|
198
|
+
* --format json` under AGENTS_SECRETS_REMOTE_TRANSPORT) but keeps only the KEY
|
|
199
|
+
* NAMES; the plaintext values are dropped immediately and never retained or
|
|
200
|
+
* logged. Returns a verification verdict; the caller renders
|
|
201
|
+
* `keychainWriteFailureMessage` on failure.
|
|
200
202
|
*/
|
|
201
203
|
export declare function verifyRemoteKeychainPush(target: string, bundle: string, pushedKeys: string[], opts?: {
|
|
202
204
|
osLookupName?: string;
|
|
@@ -212,7 +212,12 @@ export function remoteSecretsStream(target, args, opts = {}) {
|
|
|
212
212
|
*/
|
|
213
213
|
export async function remoteResolveEnv(target, bundle, opts = {}) {
|
|
214
214
|
assertValidSshTarget(target);
|
|
215
|
-
|
|
215
|
+
// AGENTS_SECRETS_REMOTE_TRANSPORT is the marker that lets the remote's
|
|
216
|
+
// `export --plaintext --format json` emit at all — the public shell-eval
|
|
217
|
+
// export mode was removed (RUSH-2774), and this machine-to-machine resolve is
|
|
218
|
+
// the only surviving caller of the JSON emitter. Riding the legacy argv keeps
|
|
219
|
+
// a new driver compatible with an old remote during a fleet rollout.
|
|
220
|
+
const remoteCmd = buildRemoteAgentsInvocation(['secrets', 'export', bundle, '--plaintext', '--format', 'json'], undefined, osForTarget(target, opts.osLookupName), { AGENTS_SECRETS_REMOTE_TRANSPORT: '1' });
|
|
216
221
|
// Resolving a remote bundle streams its plaintext values back over ssh stdout,
|
|
217
222
|
// so this read is secret-bearing: pin the managed host key and never leave a
|
|
218
223
|
// reusable control master to the source (RUSH-2527, `credentialTransportSshOpts`).
|
|
@@ -299,9 +304,10 @@ function isLockedKeychainReadBackError(stderr) {
|
|
|
299
304
|
* metadata `noAcl` last — bundles.ts writeBundleWithItems). The metadata-only bundle
|
|
300
305
|
* then fails every later read with the confusing `Bundle '<b>' key '<k>': stored
|
|
301
306
|
* item '<item>' not found` (bundles.ts resolveBundleEnv). We catch it by reading
|
|
302
|
-
* the bundle back the same way a
|
|
303
|
-
* json
|
|
304
|
-
* any keychain read — no Touch ID prompt) and confirming every pushed key
|
|
307
|
+
* the bundle back the same way a later `secrets exec`/resolve will (the marker-gated
|
|
308
|
+
* json transport, driven headlessly on the remote so its `agentOnly` guard FAILS FAST
|
|
309
|
+
* before any keychain read — no Touch ID prompt) and confirming every pushed key
|
|
310
|
+
* returned.
|
|
305
311
|
*
|
|
306
312
|
* Pure so both branches are unit-testable without a real locked keychain: inject the
|
|
307
313
|
* "read-back failed / key absent" condition through `readBack`.
|
|
@@ -359,13 +365,16 @@ export function keychainWriteFailureMessage(host, bundle, reason) {
|
|
|
359
365
|
/**
|
|
360
366
|
* Read a bundle back from a remote over SSH (headlessly, so it fails fast rather
|
|
361
367
|
* than prompting Touch ID) and confirm the pushed keys materialized. Drives the
|
|
362
|
-
* remote's
|
|
363
|
-
*
|
|
364
|
-
* are dropped immediately and never retained or
|
|
365
|
-
* verdict; the caller renders
|
|
368
|
+
* remote's marker-gated json transport (`secrets export <bundle> --plaintext
|
|
369
|
+
* --format json` under AGENTS_SECRETS_REMOTE_TRANSPORT) but keeps only the KEY
|
|
370
|
+
* NAMES; the plaintext values are dropped immediately and never retained or
|
|
371
|
+
* logged. Returns a verification verdict; the caller renders
|
|
372
|
+
* `keychainWriteFailureMessage` on failure.
|
|
366
373
|
*/
|
|
367
374
|
export function verifyRemoteKeychainPush(target, bundle, pushedKeys, opts = {}) {
|
|
368
|
-
const remoteCmd = buildRemoteAgentsInvocation(['secrets', 'export', bundle, '--plaintext', '--format', 'json'], undefined, osForTarget(target, opts.osLookupName)
|
|
375
|
+
const remoteCmd = buildRemoteAgentsInvocation(['secrets', 'export', bundle, '--plaintext', '--format', 'json'], undefined, osForTarget(target, opts.osLookupName),
|
|
376
|
+
// Same transport marker as remoteResolveEnv — see the comment there (RUSH-2774).
|
|
377
|
+
{ AGENTS_SECRETS_REMOTE_TRANSPORT: '1' });
|
|
369
378
|
// This read-back streams the just-pushed plaintext over ssh stdout, so it is
|
|
370
379
|
// as secret-bearing as the push itself — the push path passes `secret: true`
|
|
371
380
|
// so it too pins the managed host key and leaves no reusable control master to
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// or broken — silent breakage. Repair runs at most once per unique path per
|
|
5
5
|
// pass (no retry, no sync recursion); unresolved findings stay needsAttention.
|
|
6
6
|
import { resultOf } from '../types.js';
|
|
7
|
-
import { repairManagedHookRuntimeArtifacts } from '../../hooks.js';
|
|
7
|
+
import { repairManagedHookRuntimeArtifacts } from '../../hooks/install.js';
|
|
8
8
|
export const hookRuntimeCheck = {
|
|
9
9
|
id: 'hook-runtime',
|
|
10
10
|
title: 'Generated hook runtime shims',
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// Caveat: an already-open shell won't pick up the new rc/PATH entry until it reloads
|
|
7
7
|
// — but new shells will, so the recurring prompt stops.
|
|
8
8
|
import { resultOf } from '../types.js';
|
|
9
|
-
import { isShimsInPath, addShimsToPath, listAgentsWithNonIsolatedInstalledVersions } from '../../shims.js';
|
|
9
|
+
import { isShimsInPath, addShimsToPath, listAgentsWithNonIsolatedInstalledVersions } from '../../installations/shims.js';
|
|
10
10
|
export const pathCheck = {
|
|
11
11
|
id: 'path',
|
|
12
12
|
title: 'Shims directory on PATH',
|