@phnx-labs/agents-cli 1.20.72 → 1.20.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +395 -0
- package/README.md +40 -4
- package/dist/bin/agents +0 -0
- package/dist/commands/activity.d.ts +12 -0
- package/dist/commands/activity.js +68 -0
- package/dist/commands/apply.d.ts +13 -0
- package/dist/commands/apply.js +28 -3
- package/dist/commands/browser.js +1 -1
- package/dist/commands/cloud.js +21 -11
- package/dist/commands/doctor.js +21 -5
- package/dist/commands/events.d.ts +11 -10
- package/dist/commands/events.js +30 -21
- package/dist/commands/exec.d.ts +34 -0
- package/dist/commands/exec.js +337 -39
- package/dist/commands/export.d.ts +11 -0
- package/dist/commands/export.js +215 -0
- package/dist/commands/feed.d.ts +13 -0
- package/dist/commands/feed.js +130 -29
- package/dist/commands/hq.d.ts +2 -0
- package/dist/commands/hq.js +58 -0
- package/dist/commands/import.js +51 -3
- package/dist/commands/lease.d.ts +19 -0
- package/dist/commands/lease.js +138 -11
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +123 -0
- package/dist/commands/logs.js +1 -1
- package/dist/commands/mine.d.ts +27 -0
- package/dist/commands/mine.js +207 -0
- package/dist/commands/monitors.js +79 -49
- package/dist/commands/plugins.js +1 -0
- package/dist/commands/profiles.js +158 -0
- package/dist/commands/repo.js +2 -60
- package/dist/commands/resources.d.ts +5 -0
- package/dist/commands/resources.js +95 -0
- package/dist/commands/routines.d.ts +2 -0
- package/dist/commands/routines.js +139 -37
- package/dist/commands/secrets.js +234 -47
- package/dist/commands/send.d.ts +15 -0
- package/dist/commands/send.js +62 -0
- package/dist/commands/sessions-tail.d.ts +1 -0
- package/dist/commands/sessions-tail.js +20 -9
- package/dist/commands/sessions.js +18 -3
- package/dist/commands/setup-mine.d.ts +18 -0
- package/dist/commands/setup-mine.js +106 -0
- package/dist/commands/setup-share.js +2 -2
- package/dist/commands/setup.js +9 -2
- package/dist/commands/share.d.ts +8 -1
- package/dist/commands/share.js +122 -37
- package/dist/commands/ssh.d.ts +9 -0
- package/dist/commands/ssh.js +196 -26
- package/dist/commands/sync.js +1 -1
- package/dist/commands/teams.js +11 -2
- package/dist/commands/versions.js +36 -5
- package/dist/commands/view.js +75 -27
- package/dist/index.js +80 -10
- package/dist/lib/activity.d.ts +124 -0
- package/dist/lib/activity.js +542 -0
- package/dist/lib/agents.d.ts +35 -0
- package/dist/lib/agents.js +79 -6
- package/dist/lib/artifact-actions.d.ts +1 -1
- package/dist/lib/artifact-actions.js +1 -1
- package/dist/lib/ask-classifier.d.ts +2 -1
- package/dist/lib/ask-classifier.js +3 -3
- package/dist/lib/auth-health.d.ts +32 -4
- package/dist/lib/auth-health.js +40 -1
- package/dist/lib/auto-pull.js +8 -1
- package/dist/lib/brand.d.ts +40 -0
- package/dist/lib/brand.js +122 -0
- package/dist/lib/browser/drivers/ssh.js +4 -7
- package/dist/lib/browser/service.js +6 -8
- package/dist/lib/channels/providers/index.d.ts +2 -0
- package/dist/lib/channels/providers/index.js +20 -0
- package/dist/lib/channels/providers/mailbox.d.ts +2 -0
- package/dist/lib/channels/providers/mailbox.js +26 -0
- package/dist/lib/channels/providers/openclaw-telegram.d.ts +2 -0
- package/dist/lib/channels/providers/openclaw-telegram.js +34 -0
- package/dist/lib/channels/providers/rush.d.ts +6 -0
- package/dist/lib/channels/providers/rush.js +55 -0
- package/dist/lib/channels/registry.d.ts +42 -0
- package/dist/lib/channels/registry.js +20 -0
- package/dist/lib/channels/resolve.d.ts +11 -0
- package/dist/lib/channels/resolve.js +12 -0
- package/dist/lib/cli-resources.d.ts +18 -0
- package/dist/lib/cli-resources.js +53 -4
- package/dist/lib/cloud/codex.js +6 -3
- package/dist/lib/cloud/factory.d.ts +1 -0
- package/dist/lib/cloud/factory.js +10 -4
- package/dist/lib/cloud/rush.d.ts +3 -1
- package/dist/lib/cloud/rush.js +6 -1
- package/dist/lib/cloud/types.d.ts +2 -0
- package/dist/lib/codex-home.d.ts +35 -0
- package/dist/lib/codex-home.js +136 -0
- package/dist/lib/config-transfer.d.ts +16 -0
- package/dist/lib/config-transfer.js +56 -0
- package/dist/lib/crabbox/cli.d.ts +44 -0
- package/dist/lib/crabbox/cli.js +120 -20
- package/dist/lib/crabbox/lease.d.ts +52 -1
- package/dist/lib/crabbox/lease.js +117 -16
- package/dist/lib/crabbox/progress.d.ts +31 -0
- package/dist/lib/crabbox/progress.js +76 -0
- package/dist/lib/crabbox/runtimes.d.ts +4 -0
- package/dist/lib/crabbox/runtimes.js +33 -8
- package/dist/lib/crabbox/setup-copy.d.ts +87 -0
- package/dist/lib/crabbox/setup-copy.js +127 -0
- package/dist/lib/daemon.d.ts +5 -5
- package/dist/lib/daemon.js +9 -9
- package/dist/lib/drive-sync.js +6 -3
- package/dist/lib/event-stream.d.ts +36 -0
- package/dist/lib/event-stream.js +68 -0
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/exec.d.ts +11 -0
- package/dist/lib/exec.js +23 -2
- package/dist/lib/export.d.ts +72 -0
- package/dist/lib/export.js +269 -0
- package/dist/lib/feed-policy.d.ts +1 -1
- package/dist/lib/feed-policy.js +14 -9
- package/dist/lib/feed-ranking.d.ts +32 -0
- package/dist/lib/feed-ranking.js +224 -0
- package/dist/lib/feed.d.ts +35 -2
- package/dist/lib/feed.js +66 -3
- package/dist/lib/fleet/auth-sync.d.ts +53 -0
- package/dist/lib/fleet/auth-sync.js +92 -0
- package/dist/lib/fleet/remote-login.d.ts +187 -0
- package/dist/lib/fleet/remote-login.js +556 -0
- package/dist/lib/format.d.ts +30 -3
- package/dist/lib/format.js +34 -5
- package/dist/lib/fs-atomic.d.ts +7 -2
- package/dist/lib/fs-atomic.js +8 -12
- package/dist/lib/git.d.ts +16 -0
- package/dist/lib/git.js +79 -2
- package/dist/lib/heal.js +11 -3
- package/dist/lib/hosts/progress.d.ts +28 -10
- package/dist/lib/hosts/progress.js +79 -22
- package/dist/lib/hosts/remote-cmd.js +4 -0
- package/dist/lib/hq/floor.d.ts +87 -0
- package/dist/lib/hq/floor.js +226 -0
- package/dist/lib/import.d.ts +23 -0
- package/dist/lib/import.js +74 -2
- package/dist/lib/isolation-boundary-report.d.ts +10 -0
- package/dist/lib/isolation-boundary-report.js +35 -0
- package/dist/lib/menubar/install-menubar.js +14 -20
- package/dist/lib/notify.d.ts +1 -0
- package/dist/lib/notify.js +3 -3
- package/dist/lib/open-url.d.ts +2 -0
- package/dist/lib/open-url.js +19 -0
- package/dist/lib/openclaw-keychain.d.ts +56 -0
- package/dist/lib/openclaw-keychain.js +236 -0
- package/dist/lib/overdue.js +10 -0
- package/dist/lib/permissions.d.ts +44 -1
- package/dist/lib/permissions.js +284 -7
- package/dist/lib/project-launch.d.ts +6 -12
- package/dist/lib/project-launch.js +13 -228
- package/dist/lib/project-resources.d.ts +7 -0
- package/dist/lib/project-resources.js +306 -0
- package/dist/lib/pty-client.d.ts +27 -0
- package/dist/lib/pty-client.js +136 -10
- package/dist/lib/refresh.js +14 -10
- package/dist/lib/resource-profiles.d.ts +26 -0
- package/dist/lib/resource-profiles.js +157 -0
- package/dist/lib/resources/permissions.js +7 -1
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/resources.d.ts +1 -1
- package/dist/lib/resources.js +32 -3
- package/dist/lib/routines.d.ts +11 -0
- package/dist/lib/routines.js +56 -15
- package/dist/lib/runner.d.ts +1 -0
- package/dist/lib/runner.js +66 -5
- package/dist/lib/secrets/bundles.d.ts +16 -3
- package/dist/lib/secrets/bundles.js +206 -37
- package/dist/lib/secrets/icloud-import.d.ts +2 -2
- package/dist/lib/secrets/icloud-import.js +9 -6
- package/dist/lib/secrets/mcp.js +1 -1
- package/dist/lib/secrets/vault-age-helper.d.ts +1 -0
- package/dist/lib/secrets/vault-age-helper.js +34 -0
- package/dist/lib/secrets/vault.d.ts +49 -0
- package/dist/lib/secrets/vault.js +397 -0
- package/dist/lib/self-heal/checks/path.js +3 -1
- package/dist/lib/self-heal/checks/shadowing.js +10 -2
- package/dist/lib/self-heal/checks/shims.js +19 -11
- package/dist/lib/session/cloud.d.ts +2 -2
- package/dist/lib/session/cloud.js +2 -2
- package/dist/lib/session/db.d.ts +4 -0
- package/dist/lib/session/db.js +44 -7
- package/dist/lib/session/discover.d.ts +2 -0
- package/dist/lib/session/discover.js +114 -3
- package/dist/lib/session/stream-render.d.ts +3 -0
- package/dist/lib/session/stream-render.js +130 -0
- package/dist/lib/session/types.d.ts +6 -0
- package/dist/lib/share/analytics.d.ts +13 -0
- package/dist/lib/share/analytics.js +45 -0
- package/dist/lib/share/config.d.ts +19 -5
- package/dist/lib/share/config.js +44 -8
- package/dist/lib/share/provision.d.ts +58 -6
- package/dist/lib/share/provision.js +97 -22
- package/dist/lib/share/publish.d.ts +36 -13
- package/dist/lib/share/publish.js +55 -8
- package/dist/lib/share/worker-template.js +83 -17
- package/dist/lib/shims.d.ts +35 -0
- package/dist/lib/shims.js +159 -13
- package/dist/lib/ssh-exec.d.ts +14 -0
- package/dist/lib/ssh-exec.js +57 -0
- package/dist/lib/staleness/detectors/hooks.js +25 -1
- package/dist/lib/staleness/detectors/permissions.js +66 -0
- package/dist/lib/staleness/detectors/workflows.js +20 -0
- package/dist/lib/staleness/writers/hooks.js +58 -4
- package/dist/lib/staleness/writers/permissions.js +2 -2
- package/dist/lib/staleness/writers/skills.js +1 -1
- package/dist/lib/staleness/writers/sources.d.ts +10 -1
- package/dist/lib/staleness/writers/sources.js +68 -1
- package/dist/lib/star-nudge.d.ts +45 -0
- package/dist/lib/star-nudge.js +91 -0
- package/dist/lib/startup/command-registry.d.ts +7 -1
- package/dist/lib/startup/command-registry.js +19 -3
- package/dist/lib/state.d.ts +2 -0
- package/dist/lib/state.js +17 -4
- package/dist/lib/subagents-registry.d.ts +2 -0
- package/dist/lib/subagents-registry.js +3 -0
- package/dist/lib/teams/parsers.js +214 -6
- package/dist/lib/teams/supervisor.d.ts +7 -0
- package/dist/lib/teams/supervisor.js +2 -1
- package/dist/lib/template.d.ts +1 -1
- package/dist/lib/template.js +1 -1
- package/dist/lib/triggers/webhook.js +36 -3
- package/dist/lib/types.d.ts +84 -0
- package/dist/lib/uninstall.js +1 -45
- package/dist/lib/version.d.ts +40 -0
- package/dist/lib/version.js +94 -16
- package/dist/lib/versions.d.ts +24 -0
- package/dist/lib/versions.js +269 -78
- package/dist/lib/workflows.d.ts +2 -0
- package/dist/lib/workflows.js +88 -0
- package/package.json +2 -1
- package/dist/commands/daemon.d.ts +0 -10
- package/dist/commands/daemon.js +0 -121
|
@@ -13,9 +13,12 @@
|
|
|
13
13
|
* SSH). The item-name scheme is identical, so the only difference is where
|
|
14
14
|
* bytes land. A file-backed bundle is discovered by the presence of its
|
|
15
15
|
* metadata item in the file store.
|
|
16
|
+
* - `vault`: a single age-encrypted ~/.agents/vault.age file unlocked by
|
|
17
|
+
* `agents login`; intended for user-managed cross-machine file sync.
|
|
16
18
|
*
|
|
17
|
-
*
|
|
18
|
-
* encrypted export/import flow; the bundle layer
|
|
19
|
+
* Server-backed cross-machine sync is handled by src/lib/secrets/sync.ts via
|
|
20
|
+
* an explicit encrypted export/import flow; the bundle layer also supports the
|
|
21
|
+
* local vault backend for user-managed file sync.
|
|
19
22
|
*/
|
|
20
23
|
import * as fs from 'fs';
|
|
21
24
|
import * as os from 'os';
|
|
@@ -23,8 +26,10 @@ import * as path from 'path';
|
|
|
23
26
|
import * as yaml from 'yaml';
|
|
24
27
|
import { deleteKeychainToken, getKeychainToken, getKeychainTokens, hasKeychainToken, isKeychainBackendOverridden, keychainServiceAlias, keychainUsesFileFallback, listKeychainItems, parseBundleValue, resolveRef, secretsKeychainItem, setKeychainToken, } from './index.js';
|
|
25
28
|
import { fileStore } from './filestore.js';
|
|
29
|
+
import { getVaultSession, vaultDeleteItem, vaultExists, vaultGetItems, vaultGetItem, vaultHasItem, vaultListItems, vaultSetItems, vaultSetItem, } from './vault.js';
|
|
26
30
|
import { emit } from '../events.js';
|
|
27
31
|
import { readMeta } from '../state.js';
|
|
32
|
+
import { assertNameActiveInResourceProfile, filterNamesForActiveResourceProfile } from '../resource-profiles.js';
|
|
28
33
|
import { agentGetSync, agentAutoLoadSync, agentGetMetaSync, agentAutoLoadMetaSync, agentEvictSync, secretsAgentAutoEnabled, secretsHoldMs } from './agent.js';
|
|
29
34
|
import { loadSession, deleteSession } from './session-store.js';
|
|
30
35
|
import { createHash } from 'node:crypto';
|
|
@@ -33,6 +38,10 @@ const keychainStore = {
|
|
|
33
38
|
get: getKeychainToken,
|
|
34
39
|
getBatch: getKeychainTokens,
|
|
35
40
|
set: setKeychainToken,
|
|
41
|
+
setBatch: (items, opts) => {
|
|
42
|
+
for (const [item, value] of items)
|
|
43
|
+
setKeychainToken(item, value, opts);
|
|
44
|
+
},
|
|
36
45
|
delete: deleteKeychainToken,
|
|
37
46
|
list: listKeychainItems,
|
|
38
47
|
};
|
|
@@ -59,11 +68,29 @@ const fileItemStore = {
|
|
|
59
68
|
return out;
|
|
60
69
|
},
|
|
61
70
|
set: (item, value) => fileStore.set(item, value, { allowAutoProvision: FILE_ALLOW_AUTO_PROVISION }),
|
|
71
|
+
setBatch: (items) => {
|
|
72
|
+
for (const [item, value] of items) {
|
|
73
|
+
fileStore.set(item, value, { allowAutoProvision: FILE_ALLOW_AUTO_PROVISION });
|
|
74
|
+
}
|
|
75
|
+
},
|
|
62
76
|
delete: (item) => fileStore.delete(item),
|
|
63
77
|
list: (prefix) => fileStore.list(prefix),
|
|
64
78
|
};
|
|
79
|
+
const vaultStore = {
|
|
80
|
+
has: vaultHasItem,
|
|
81
|
+
get: vaultGetItem,
|
|
82
|
+
getBatch: vaultGetItems,
|
|
83
|
+
set: (item, value) => vaultSetItem(item, value),
|
|
84
|
+
setBatch: (items) => vaultSetItems(items),
|
|
85
|
+
delete: vaultDeleteItem,
|
|
86
|
+
list: vaultListItems,
|
|
87
|
+
};
|
|
65
88
|
function itemStore(backend) {
|
|
66
|
-
|
|
89
|
+
if (backend === 'file')
|
|
90
|
+
return fileItemStore;
|
|
91
|
+
if (backend === 'vault')
|
|
92
|
+
return vaultStore;
|
|
93
|
+
return keychainStore;
|
|
67
94
|
}
|
|
68
95
|
/**
|
|
69
96
|
* Discover a bundle's backend by location: a file-backed bundle's metadata
|
|
@@ -72,7 +99,20 @@ function itemStore(backend) {
|
|
|
72
99
|
* "read metadata to learn where metadata lives." Absent ⇒ keychain.
|
|
73
100
|
*/
|
|
74
101
|
export function bundleBackend(name) {
|
|
75
|
-
|
|
102
|
+
const item = BUNDLE_META_PREFIX + name;
|
|
103
|
+
if (fileStore.has(item))
|
|
104
|
+
return 'file';
|
|
105
|
+
if (vaultExists() && getVaultSession().loggedIn) {
|
|
106
|
+
try {
|
|
107
|
+
if (vaultHasItem(item))
|
|
108
|
+
return 'vault';
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// A vault problem should not hide a keychain/file bundle that already
|
|
112
|
+
// resolved above; exact vault reads surface the decrypt/login error.
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return 'keychain';
|
|
76
116
|
}
|
|
77
117
|
/**
|
|
78
118
|
* Guard a file-backed bundle operation. On macOS the file store must be
|
|
@@ -92,6 +132,11 @@ function assertFileBackendUsable(name) {
|
|
|
92
132
|
`(no biometry prompt is available headlessly). Set it for this run, e.g.\n` +
|
|
93
133
|
` AGENTS_SECRETS_PASSPHRASE=… agents secrets exec ${name} -- <command>`);
|
|
94
134
|
}
|
|
135
|
+
function assertVaultBackendUsable(name) {
|
|
136
|
+
if (getVaultSession().loggedIn)
|
|
137
|
+
return;
|
|
138
|
+
throw new Error(`Synced bundle '${name}' needs an active login. Run: agents login`);
|
|
139
|
+
}
|
|
95
140
|
/** Allowed values for a secret's `type` metadata field. */
|
|
96
141
|
export const SECRET_TYPES = [
|
|
97
142
|
'api-key',
|
|
@@ -108,6 +153,7 @@ export const SECRET_TYPES = [
|
|
|
108
153
|
const LAST_USED_THROTTLE_MS = 60_000;
|
|
109
154
|
export const BUNDLE_NAME_PATTERN = /^[a-z0-9][a-z0-9\-_.]{0,48}$/i;
|
|
110
155
|
export const ENV_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
156
|
+
export const BUNDLE_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z0-9_-]+)?$/;
|
|
111
157
|
export const BUNDLE_META_PREFIX = 'agents-cli.bundles.';
|
|
112
158
|
const SECRETS_ITEM_PREFIX = 'agents-cli.secrets.';
|
|
113
159
|
export const RESERVED_ENV_NAMES = new Set([
|
|
@@ -121,6 +167,10 @@ export function bundleToEnvPrefix(name) {
|
|
|
121
167
|
export function isReservedEnvName(key) {
|
|
122
168
|
return RESERVED_ENV_NAMES.has(key.toUpperCase());
|
|
123
169
|
}
|
|
170
|
+
export function bundleKeyToEnvKey(key) {
|
|
171
|
+
const dot = key.indexOf('.');
|
|
172
|
+
return dot === -1 ? key : key.slice(0, dot);
|
|
173
|
+
}
|
|
124
174
|
export function isLoaderOrInterpreterEnv(name) {
|
|
125
175
|
const upper = name.toUpperCase();
|
|
126
176
|
return upper.startsWith('LD_') ||
|
|
@@ -156,10 +206,11 @@ export function validateBundleName(name) {
|
|
|
156
206
|
}
|
|
157
207
|
}
|
|
158
208
|
export function validateEnvKey(key) {
|
|
159
|
-
if (!
|
|
160
|
-
throw new Error(`Invalid
|
|
209
|
+
if (!BUNDLE_KEY_PATTERN.test(key)) {
|
|
210
|
+
throw new Error(`Invalid bundle key '${key}'. Must match [A-Za-z_][A-Za-z0-9_]* with optional .account suffix.`);
|
|
161
211
|
}
|
|
162
|
-
|
|
212
|
+
const envKey = bundleKeyToEnvKey(key);
|
|
213
|
+
if (isLoaderOrInterpreterEnv(envKey) || isReservedEnvName(envKey)) {
|
|
163
214
|
throw new Error(`Env key "${key}" is reserved — cannot be used in a secrets bundle. Reserved keys include PATH, HOME, USER, and dynamic-loader/interpreter vars (LD_*, DYLD_*, NODE_OPTIONS, etc.).`);
|
|
164
215
|
}
|
|
165
216
|
}
|
|
@@ -197,6 +248,8 @@ export function readBundle(name) {
|
|
|
197
248
|
const backend = bundleBackend(name);
|
|
198
249
|
if (backend === 'file')
|
|
199
250
|
assertFileBackendUsable(name);
|
|
251
|
+
if (backend === 'vault')
|
|
252
|
+
assertVaultBackendUsable(name);
|
|
200
253
|
let json;
|
|
201
254
|
try {
|
|
202
255
|
json = itemStore(backend).get(bundleMetaItem(name));
|
|
@@ -207,6 +260,9 @@ export function readBundle(name) {
|
|
|
207
260
|
if (backend === 'file' && fileStore.has(bundleMetaItem(name))) {
|
|
208
261
|
throw new Error(`Bundle '${name}': failed to decrypt — wrong AGENTS_SECRETS_PASSPHRASE or tampered file store. (${err.message})`);
|
|
209
262
|
}
|
|
263
|
+
if (vaultExists() && !getVaultSession().loggedIn) {
|
|
264
|
+
throw new Error(`Synced secrets are locked. Run: agents login`);
|
|
265
|
+
}
|
|
210
266
|
throw new Error(`Secrets bundle '${name}' not found.`);
|
|
211
267
|
}
|
|
212
268
|
let parsed;
|
|
@@ -226,9 +282,9 @@ export function readBundle(name) {
|
|
|
226
282
|
name,
|
|
227
283
|
description: parsed.description,
|
|
228
284
|
allow_exec: Boolean(parsed.allow_exec),
|
|
229
|
-
// Absent ⇒ keychain; only set when
|
|
285
|
+
// Absent ⇒ keychain; only set when non-keychain so a keychain bundle
|
|
230
286
|
// round-trips byte-for-byte.
|
|
231
|
-
backend: backend === '
|
|
287
|
+
backend: backend === 'keychain' ? undefined : backend,
|
|
232
288
|
// Legacy wire key: the policy is persisted under `tier` (`session` == `daily`).
|
|
233
289
|
policy: parsePolicy(parsed.tier),
|
|
234
290
|
vars: parsed.vars && typeof parsed.vars === 'object' ? parsed.vars : {},
|
|
@@ -295,11 +351,13 @@ export function shouldEvictAfterBundleWrite(skipRequested, noAgentEnv, backendOv
|
|
|
295
351
|
return false;
|
|
296
352
|
return true;
|
|
297
353
|
}
|
|
298
|
-
|
|
354
|
+
function prepareBundleWrite(bundle) {
|
|
299
355
|
validateBundleName(bundle.name);
|
|
300
356
|
const backend = bundle.backend ?? 'keychain';
|
|
301
357
|
if (backend === 'file')
|
|
302
358
|
assertFileBackendUsable(bundle.name);
|
|
359
|
+
if (backend === 'vault')
|
|
360
|
+
assertVaultBackendUsable(bundle.name);
|
|
303
361
|
for (const key of Object.keys(bundle.vars)) {
|
|
304
362
|
validateEnvKey(key);
|
|
305
363
|
}
|
|
@@ -335,7 +393,7 @@ export function writeBundle(bundle, opts = {}) {
|
|
|
335
393
|
name: bundle.name,
|
|
336
394
|
description: bundle.description,
|
|
337
395
|
allow_exec: bundle.allow_exec ? true : undefined,
|
|
338
|
-
backend: backend === '
|
|
396
|
+
backend: backend === 'keychain' ? undefined : backend,
|
|
339
397
|
// Wire format: persist the policy under the legacy `tier` token so older CLI
|
|
340
398
|
// versions on other synced machines keep reading it — `daily`⇒`session`,
|
|
341
399
|
// explicit `always`⇒`biometry`, `never`⇒`none`. An absent policy omits the
|
|
@@ -352,12 +410,13 @@ export function writeBundle(bundle, opts = {}) {
|
|
|
352
410
|
vars: bundle.vars,
|
|
353
411
|
meta,
|
|
354
412
|
};
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
413
|
+
return {
|
|
414
|
+
backend,
|
|
415
|
+
metadataItem: bundleMetaItem(bundle.name),
|
|
416
|
+
metadataJson: JSON.stringify(payload),
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
function finishBundleWrite(bundle, opts) {
|
|
361
420
|
emit('secrets.set', { module: 'secrets', bundle: bundle.name });
|
|
362
421
|
// A broker-held snapshot predates this write; evict it so the next read
|
|
363
422
|
// re-resolves from the keychain instead of serving stale values.
|
|
@@ -368,6 +427,22 @@ export function writeBundle(bundle, opts = {}) {
|
|
|
368
427
|
deleteSession(bundle.name);
|
|
369
428
|
}
|
|
370
429
|
}
|
|
430
|
+
export function writeBundle(bundle, opts = {}) {
|
|
431
|
+
const prepared = prepareBundleWrite(bundle);
|
|
432
|
+
// A `never` bundle's metadata is stored without the biometry ACL too, so
|
|
433
|
+
// `view` and the metadata half of a read resolve silently — the whole point
|
|
434
|
+
// of the tier. On an un-updated pinned helper this write fails loudly (the
|
|
435
|
+
// no-ACL command is missing) rather than silently landing an ACL'd item.
|
|
436
|
+
itemStore(prepared.backend).set(prepared.metadataItem, prepared.metadataJson, { noAcl: bundle.policy === 'never' });
|
|
437
|
+
finishBundleWrite(bundle, opts);
|
|
438
|
+
}
|
|
439
|
+
export function writeBundleWithItems(bundle, items, opts = {}) {
|
|
440
|
+
const prepared = prepareBundleWrite(bundle);
|
|
441
|
+
const batch = new Map(items);
|
|
442
|
+
batch.set(prepared.metadataItem, prepared.metadataJson);
|
|
443
|
+
itemStore(prepared.backend).setBatch(batch, { noAcl: bundle.policy === 'never' });
|
|
444
|
+
finishBundleWrite(bundle, opts);
|
|
445
|
+
}
|
|
371
446
|
export function deleteBundle(name) {
|
|
372
447
|
validateBundleName(name);
|
|
373
448
|
const deleted = itemStore(bundleBackend(name)).delete(bundleMetaItem(name));
|
|
@@ -409,7 +484,7 @@ function parseBundleMeta(nameHint, json, backend) {
|
|
|
409
484
|
name,
|
|
410
485
|
description: parsed.description,
|
|
411
486
|
allow_exec: Boolean(parsed.allow_exec),
|
|
412
|
-
backend: backend === '
|
|
487
|
+
backend: backend === 'keychain' ? undefined : backend,
|
|
413
488
|
// Legacy wire key: the policy is persisted under `tier` (`session` == `daily`).
|
|
414
489
|
policy: parsePolicy(parsed.tier),
|
|
415
490
|
vars: parsed.vars && typeof parsed.vars === 'object' ? parsed.vars : {},
|
|
@@ -423,7 +498,7 @@ function parseBundleMeta(nameHint, json, backend) {
|
|
|
423
498
|
if (parsed.meta && typeof parsed.meta === 'object')
|
|
424
499
|
bundle.meta = parsed.meta;
|
|
425
500
|
for (const key of Object.keys(bundle.vars)) {
|
|
426
|
-
if (!
|
|
501
|
+
if (!BUNDLE_KEY_PATTERN.test(key))
|
|
427
502
|
return null;
|
|
428
503
|
}
|
|
429
504
|
return bundle;
|
|
@@ -534,7 +609,33 @@ export function listBundles() {
|
|
|
534
609
|
if (bundle)
|
|
535
610
|
out.push(bundle);
|
|
536
611
|
}
|
|
537
|
-
|
|
612
|
+
if (getVaultSession().loggedIn && vaultExists()) {
|
|
613
|
+
let vaultServices = [];
|
|
614
|
+
try {
|
|
615
|
+
vaultServices = vaultListItems(BUNDLE_META_PREFIX);
|
|
616
|
+
}
|
|
617
|
+
catch {
|
|
618
|
+
vaultServices = [];
|
|
619
|
+
}
|
|
620
|
+
for (const service of vaultServices) {
|
|
621
|
+
const name = service.slice(BUNDLE_META_PREFIX.length);
|
|
622
|
+
if (!BUNDLE_NAME_PATTERN.test(name))
|
|
623
|
+
continue;
|
|
624
|
+
let json;
|
|
625
|
+
try {
|
|
626
|
+
json = vaultStore.get(bundleMetaItem(name));
|
|
627
|
+
}
|
|
628
|
+
catch {
|
|
629
|
+
out.push({ name, backend: 'vault', vars: {} });
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
const bundle = parseBundleMeta(name, json, 'vault');
|
|
633
|
+
if (bundle)
|
|
634
|
+
out.push(bundle);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
const activeNames = new Set(filterNamesForActiveResourceProfile('secrets', out.map((b) => b.name)));
|
|
638
|
+
return out.filter((bundle) => activeNames.has(bundle.name)).sort((a, b) => a.name.localeCompare(b.name));
|
|
538
639
|
}
|
|
539
640
|
export function describeBundle(bundle) {
|
|
540
641
|
const out = [];
|
|
@@ -612,6 +713,67 @@ function selectRequestedKeys(bundle, requested) {
|
|
|
612
713
|
}
|
|
613
714
|
return new Set(req ?? Object.keys(bundle.vars));
|
|
614
715
|
}
|
|
716
|
+
function assignResolvedEnvValue(env, bundle, storageKey, value, keyMode, owners) {
|
|
717
|
+
const envKey = keyMode === 'storage' ? storageKey : bundleKeyToEnvKey(storageKey);
|
|
718
|
+
const previous = owners.get(envKey);
|
|
719
|
+
if (previous && previous !== storageKey) {
|
|
720
|
+
throw new Error(`Bundle '${bundle.name}' maps multiple keys to '${envKey}': ${previous}, ${storageKey}. ` +
|
|
721
|
+
`Select one account variant with --keys.`);
|
|
722
|
+
}
|
|
723
|
+
owners.set(envKey, storageKey);
|
|
724
|
+
env[envKey] = value;
|
|
725
|
+
}
|
|
726
|
+
function projectResolvedEnv(bundle, env, selectedKeys, keyMode) {
|
|
727
|
+
if (keyMode === 'storage') {
|
|
728
|
+
const out = {};
|
|
729
|
+
for (const key of selectedKeys) {
|
|
730
|
+
if (key in env)
|
|
731
|
+
out[key] = env[key];
|
|
732
|
+
}
|
|
733
|
+
return out;
|
|
734
|
+
}
|
|
735
|
+
let needsProjection = false;
|
|
736
|
+
const owners = new Map();
|
|
737
|
+
for (const key of selectedKeys) {
|
|
738
|
+
const envKey = bundleKeyToEnvKey(key);
|
|
739
|
+
if (envKey !== key)
|
|
740
|
+
needsProjection = true;
|
|
741
|
+
const previous = owners.get(envKey);
|
|
742
|
+
if (previous && previous !== key) {
|
|
743
|
+
throw new Error(`Bundle '${bundle.name}' maps multiple keys to '${envKey}': ${previous}, ${key}. ` +
|
|
744
|
+
`Select one account variant with --keys.`);
|
|
745
|
+
}
|
|
746
|
+
owners.set(envKey, key);
|
|
747
|
+
}
|
|
748
|
+
if (!needsProjection) {
|
|
749
|
+
const envKeys = Object.keys(env);
|
|
750
|
+
if (selectedKeys.size === envKeys.length && envKeys.every((key) => selectedKeys.has(key)))
|
|
751
|
+
return env;
|
|
752
|
+
const out = {};
|
|
753
|
+
for (const key of selectedKeys) {
|
|
754
|
+
if (key in env)
|
|
755
|
+
out[key] = env[key];
|
|
756
|
+
}
|
|
757
|
+
return out;
|
|
758
|
+
}
|
|
759
|
+
const out = {};
|
|
760
|
+
for (const key of selectedKeys) {
|
|
761
|
+
if (key in env)
|
|
762
|
+
out[bundleKeyToEnvKey(key)] = env[key];
|
|
763
|
+
}
|
|
764
|
+
return out;
|
|
765
|
+
}
|
|
766
|
+
export function canCacheResolvedEnv(bundle, selectedKeys, keyMode) {
|
|
767
|
+
if (selectedKeys.size !== Object.keys(bundle.vars).length)
|
|
768
|
+
return false;
|
|
769
|
+
if (keyMode === 'storage')
|
|
770
|
+
return true;
|
|
771
|
+
for (const key of selectedKeys) {
|
|
772
|
+
if (bundleKeyToEnvKey(key) !== key)
|
|
773
|
+
return false;
|
|
774
|
+
}
|
|
775
|
+
return true;
|
|
776
|
+
}
|
|
615
777
|
/**
|
|
616
778
|
* Apply the --keys subset + expiry gate to an already-resolved snapshot from
|
|
617
779
|
* the secrets-agent fast-path. The agent stores the FULL bundle env, so a
|
|
@@ -625,15 +787,11 @@ function selectRequestedKeys(bundle, requested) {
|
|
|
625
787
|
export function filterAgentHitBySubsetAndExpiry(hit, opts) {
|
|
626
788
|
const selectedKeys = selectRequestedKeys(hit.bundle, opts.keys);
|
|
627
789
|
assertNotExpired(hit.bundle, [...selectedKeys], opts.allowExpired ?? false);
|
|
628
|
-
|
|
629
|
-
//
|
|
630
|
-
|
|
790
|
+
const env = projectResolvedEnv(hit.bundle, hit.env, selectedKeys, opts.keyMode);
|
|
791
|
+
// When no subset/projection was requested, return the cached env untouched —
|
|
792
|
+
// same reference the agent handed back, so no per-call allocation on the hot path.
|
|
793
|
+
if (env === hit.env)
|
|
631
794
|
return hit;
|
|
632
|
-
const env = {};
|
|
633
|
-
for (const key of selectedKeys) {
|
|
634
|
-
if (key in hit.env)
|
|
635
|
-
env[key] = hit.env[key];
|
|
636
|
-
}
|
|
637
795
|
return { bundle: hit.bundle, env };
|
|
638
796
|
}
|
|
639
797
|
/**
|
|
@@ -681,12 +839,13 @@ export function resolveBundleEnv(bundle, _opts = {}) {
|
|
|
681
839
|
? store.getBatch(keychainItemsToFetch)
|
|
682
840
|
: new Map();
|
|
683
841
|
const env = {};
|
|
842
|
+
const owners = new Map();
|
|
684
843
|
for (const [key, raw] of Object.entries(bundle.vars)) {
|
|
685
844
|
if (!selectedKeys.has(key))
|
|
686
845
|
continue;
|
|
687
846
|
const parsed = parsedByKey.get(key);
|
|
688
847
|
if ('literal' in parsed) {
|
|
689
|
-
env
|
|
848
|
+
assignResolvedEnvValue(env, bundle, key, parsed.literal, _opts.keyMode, owners);
|
|
690
849
|
continue;
|
|
691
850
|
}
|
|
692
851
|
if (parsed.ref.provider === 'keychain') {
|
|
@@ -696,14 +855,15 @@ export function resolveBundleEnv(bundle, _opts = {}) {
|
|
|
696
855
|
throw new Error(`Bundle '${bundle.name}' key '${key}': stored item '${item}' not found. ` +
|
|
697
856
|
`Run: agents secrets add ${bundle.name} ${key}`);
|
|
698
857
|
}
|
|
699
|
-
env
|
|
858
|
+
assignResolvedEnvValue(env, bundle, key, value, _opts.keyMode, owners);
|
|
700
859
|
continue;
|
|
701
860
|
}
|
|
702
861
|
try {
|
|
703
|
-
|
|
862
|
+
const value = resolveRef(parsed.ref, {
|
|
704
863
|
allowExec: bundle.allow_exec,
|
|
705
864
|
keychainItemFor: (shortId) => secretsKeychainItem(bundle.name, shortId),
|
|
706
865
|
});
|
|
866
|
+
assignResolvedEnvValue(env, bundle, key, value, _opts.keyMode, owners);
|
|
707
867
|
}
|
|
708
868
|
catch (err) {
|
|
709
869
|
throw new Error(`Bundle '${bundle.name}' key '${key}': ${err.message}`);
|
|
@@ -766,6 +926,7 @@ export function isHeadlessSecretsContext(env = process.env, platform = process.p
|
|
|
766
926
|
*/
|
|
767
927
|
export function readAndResolveBundleEnv(name, opts = {}) {
|
|
768
928
|
validateBundleName(name);
|
|
929
|
+
assertNameActiveInResourceProfile('secrets', name);
|
|
769
930
|
const backend = bundleBackend(name);
|
|
770
931
|
// Fast-path: if the secrets-agent holds this bundle (user ran
|
|
771
932
|
// `agents secrets unlock <name>`), return the cached snapshot with no Touch
|
|
@@ -828,6 +989,8 @@ export function readAndResolveBundleEnv(name, opts = {}) {
|
|
|
828
989
|
}
|
|
829
990
|
if (backend === 'file')
|
|
830
991
|
assertFileBackendUsable(name);
|
|
992
|
+
if (backend === 'vault')
|
|
993
|
+
assertVaultBackendUsable(name);
|
|
831
994
|
const store = itemStore(backend);
|
|
832
995
|
const metaItem = bundleMetaItem(name);
|
|
833
996
|
const bundleSecretPrefix = `${SECRETS_ITEM_PREFIX}${name}.`;
|
|
@@ -856,6 +1019,9 @@ export function readAndResolveBundleEnv(name, opts = {}) {
|
|
|
856
1019
|
if (backend === 'file' && fileStore.has(metaItem)) {
|
|
857
1020
|
throw new Error(`Bundle '${name}': failed to decrypt — wrong AGENTS_SECRETS_PASSPHRASE or tampered file store.`);
|
|
858
1021
|
}
|
|
1022
|
+
if (vaultExists() && !getVaultSession().loggedIn) {
|
|
1023
|
+
throw new Error(`Synced secrets are locked. Run: agents login`);
|
|
1024
|
+
}
|
|
859
1025
|
throw new Error(`Secrets bundle '${name}' not found.`);
|
|
860
1026
|
}
|
|
861
1027
|
let parsed;
|
|
@@ -872,7 +1038,7 @@ export function readAndResolveBundleEnv(name, opts = {}) {
|
|
|
872
1038
|
name,
|
|
873
1039
|
description: parsed.description,
|
|
874
1040
|
allow_exec: Boolean(parsed.allow_exec),
|
|
875
|
-
backend: backend === '
|
|
1041
|
+
backend: backend === 'keychain' ? undefined : backend,
|
|
876
1042
|
// Legacy wire key: the policy is persisted under `tier` (`session` == `daily`).
|
|
877
1043
|
policy: parsePolicy(parsed.tier),
|
|
878
1044
|
vars: parsed.vars && typeof parsed.vars === 'object' ? parsed.vars : {},
|
|
@@ -923,12 +1089,13 @@ export function readAndResolveBundleEnv(name, opts = {}) {
|
|
|
923
1089
|
};
|
|
924
1090
|
try {
|
|
925
1091
|
const env = {};
|
|
1092
|
+
const owners = new Map();
|
|
926
1093
|
for (const [key] of Object.entries(bundle.vars)) {
|
|
927
1094
|
if (!selectedKeys.has(key))
|
|
928
1095
|
continue;
|
|
929
1096
|
const p = parsedByKey.get(key);
|
|
930
1097
|
if ('literal' in p) {
|
|
931
|
-
env
|
|
1098
|
+
assignResolvedEnvValue(env, bundle, key, p.literal, opts.keyMode, owners);
|
|
932
1099
|
continue;
|
|
933
1100
|
}
|
|
934
1101
|
if (p.ref.provider === 'keychain') {
|
|
@@ -941,14 +1108,15 @@ export function readAndResolveBundleEnv(name, opts = {}) {
|
|
|
941
1108
|
throw new Error(`Bundle '${bundle.name}' key '${key}': stored item '${item}' not found. ` +
|
|
942
1109
|
`Run: agents secrets add ${bundle.name} ${key}`);
|
|
943
1110
|
}
|
|
944
|
-
env
|
|
1111
|
+
assignResolvedEnvValue(env, bundle, key, value, opts.keyMode, owners);
|
|
945
1112
|
continue;
|
|
946
1113
|
}
|
|
947
1114
|
try {
|
|
948
|
-
|
|
1115
|
+
const value = resolveRef(p.ref, {
|
|
949
1116
|
allowExec: bundle.allow_exec,
|
|
950
1117
|
keychainItemFor: (shortId) => secretsKeychainItem(bundle.name, shortId),
|
|
951
1118
|
});
|
|
1119
|
+
assignResolvedEnvValue(env, bundle, key, value, opts.keyMode, owners);
|
|
952
1120
|
}
|
|
953
1121
|
catch (err) {
|
|
954
1122
|
throw new Error(`Bundle '${bundle.name}' key '${key}': ${err.message}`);
|
|
@@ -967,7 +1135,8 @@ export function readAndResolveBundleEnv(name, opts = {}) {
|
|
|
967
1135
|
!opts.noAgent &&
|
|
968
1136
|
process.env.AGENTS_SECRETS_NO_AGENT !== '1' &&
|
|
969
1137
|
bundlePolicy(bundle) === 'daily' &&
|
|
970
|
-
secretsAgentAutoEnabled()
|
|
1138
|
+
secretsAgentAutoEnabled() &&
|
|
1139
|
+
canCacheResolvedEnv(bundle, selectedKeys, opts.keyMode)) {
|
|
971
1140
|
agentAutoLoadSync(name, bundle, env, secretsHoldMs());
|
|
972
1141
|
}
|
|
973
1142
|
return { bundle, env };
|
|
@@ -1127,7 +1296,7 @@ export function parseDotenv(content) {
|
|
|
1127
1296
|
(value.startsWith("'") && value.endsWith("'"))) {
|
|
1128
1297
|
value = value.slice(1, -1);
|
|
1129
1298
|
}
|
|
1130
|
-
if (
|
|
1299
|
+
if (BUNDLE_KEY_PATTERN.test(key)) {
|
|
1131
1300
|
out[key] = value;
|
|
1132
1301
|
}
|
|
1133
1302
|
}
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
*
|
|
12
12
|
* The item-name scheme is the same one the modern store uses (see bundles.ts):
|
|
13
13
|
* metadata under `agents-cli.bundles.<name>`, one value per key under
|
|
14
|
-
* `agents-cli.secrets.<bundle>.<key>`.
|
|
15
|
-
*
|
|
14
|
+
* `agents-cli.secrets.<bundle>.<key>`. Legacy env keys never contained a dot,
|
|
15
|
+
* so metadata-less synced secret services split at their LAST dot to recover
|
|
16
16
|
* the bundle/key boundary even for dotted bundle names like `hetzner.com`.
|
|
17
17
|
*/
|
|
18
18
|
import { type SecretsBackend } from './bundles.js';
|
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
*
|
|
12
12
|
* The item-name scheme is the same one the modern store uses (see bundles.ts):
|
|
13
13
|
* metadata under `agents-cli.bundles.<name>`, one value per key under
|
|
14
|
-
* `agents-cli.secrets.<bundle>.<key>`.
|
|
15
|
-
*
|
|
14
|
+
* `agents-cli.secrets.<bundle>.<key>`. Legacy env keys never contained a dot,
|
|
15
|
+
* so metadata-less synced secret services split at their LAST dot to recover
|
|
16
16
|
* the bundle/key boundary even for dotted bundle names like `hetzner.com`.
|
|
17
17
|
*/
|
|
18
18
|
import { deleteSyncedKeychainItem, getSyncedKeychainTokens, listSyncedKeychainItems, parseBundleValue, secretsKeychainItem, serializeRef, SECRETS_ITEM_PREFIX, } from './index.js';
|
|
19
|
-
import { BUNDLE_META_PREFIX, BUNDLE_NAME_PATTERN, ENV_KEY_PATTERN, bundleExists, bundleItemStore, bundlePolicy, isLoaderOrInterpreterEnv, isReservedEnvName, keychainRef, readBundle, writeBundle, } from './bundles.js';
|
|
19
|
+
import { BUNDLE_META_PREFIX, BUNDLE_NAME_PATTERN, BUNDLE_KEY_PATTERN, ENV_KEY_PATTERN, bundleKeyToEnvKey, bundleExists, bundleItemStore, bundlePolicy, isLoaderOrInterpreterEnv, isReservedEnvName, keychainRef, readBundle, writeBundle, } from './bundles.js';
|
|
20
20
|
/**
|
|
21
21
|
* Group raw synced service names into per-bundle candidates. Pure — separated
|
|
22
22
|
* from discovery so the parsing rules are unit-testable without a keychain.
|
|
@@ -88,7 +88,7 @@ export function importSyncedBundle(candidate, opts = {}) {
|
|
|
88
88
|
else {
|
|
89
89
|
bundle = {
|
|
90
90
|
name: candidate.name,
|
|
91
|
-
backend: opts.backend === '
|
|
91
|
+
backend: opts.backend === 'keychain' ? undefined : opts.backend,
|
|
92
92
|
vars: {},
|
|
93
93
|
};
|
|
94
94
|
}
|
|
@@ -121,7 +121,10 @@ export function importSyncedBundle(candidate, opts = {}) {
|
|
|
121
121
|
// these may be purged; a missing or unimportable key's iCloud item is its
|
|
122
122
|
// only copy and must survive.
|
|
123
123
|
const purgeable = new Set();
|
|
124
|
-
const rejectedByPolicy = (key) =>
|
|
124
|
+
const rejectedByPolicy = (key) => {
|
|
125
|
+
const envKey = bundleKeyToEnvKey(key);
|
|
126
|
+
return isLoaderOrInterpreterEnv(envKey) || isReservedEnvName(envKey);
|
|
127
|
+
};
|
|
125
128
|
// Keys with a synced secret item: re-store the value device-locally.
|
|
126
129
|
for (const key of candidate.keys) {
|
|
127
130
|
// The pre-cutover store accepted keys the modern one refuses (writeBundle
|
|
@@ -154,7 +157,7 @@ export function importSyncedBundle(candidate, opts = {}) {
|
|
|
154
157
|
// carry everything they need; a keychain ref without its synced item is
|
|
155
158
|
// unrecoverable.
|
|
156
159
|
for (const [key, raw] of Object.entries(metaVars)) {
|
|
157
|
-
if (!
|
|
160
|
+
if (!BUNDLE_KEY_PATTERN.test(key))
|
|
158
161
|
continue;
|
|
159
162
|
if (candidate.keys.includes(key))
|
|
160
163
|
continue; // the secret item already covered it
|
package/dist/lib/secrets/mcp.js
CHANGED
|
@@ -70,7 +70,7 @@ export function resolveSecret(bundle, key) {
|
|
|
70
70
|
}
|
|
71
71
|
// The MCP get_secret tool is typically served by a background/headless agent
|
|
72
72
|
// process; resolve broker-only there so it never raises an unwatched prompt.
|
|
73
|
-
const { env } = readAndResolveBundleEnv(bundle, { caller: 'secrets-mcp', agentOnly: isHeadlessSecretsContext() });
|
|
73
|
+
const { env } = readAndResolveBundleEnv(bundle, { caller: 'secrets-mcp', keys: [key], keyMode: 'storage', agentOnly: isHeadlessSecretsContext() });
|
|
74
74
|
const value = env[key];
|
|
75
75
|
if (value === undefined) {
|
|
76
76
|
throw new Error(`Key '${key}' in bundle '${bundle}' could not be resolved.`);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runVaultAgeHelperCli(): Promise<void>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Decrypter, Encrypter, armor } from 'age-encryption';
|
|
2
|
+
async function readStdin() {
|
|
3
|
+
const chunks = [];
|
|
4
|
+
for await (const chunk of process.stdin) {
|
|
5
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
6
|
+
}
|
|
7
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
8
|
+
}
|
|
9
|
+
async function runAge(input) {
|
|
10
|
+
if (input.action === 'encrypt') {
|
|
11
|
+
const encrypter = new Encrypter();
|
|
12
|
+
if (input.scryptWorkFactor !== undefined) {
|
|
13
|
+
encrypter.setScryptWorkFactor(input.scryptWorkFactor);
|
|
14
|
+
}
|
|
15
|
+
encrypter.setPassphrase(input.passphrase);
|
|
16
|
+
return armor.encode(await encrypter.encrypt(input.plaintext ?? ''));
|
|
17
|
+
}
|
|
18
|
+
if (input.action === 'decrypt') {
|
|
19
|
+
const decrypter = new Decrypter();
|
|
20
|
+
decrypter.addPassphrase(input.passphrase);
|
|
21
|
+
return await decrypter.decrypt(armor.decode(input.blob ?? ''), 'text');
|
|
22
|
+
}
|
|
23
|
+
throw new Error('unknown action');
|
|
24
|
+
}
|
|
25
|
+
export async function runVaultAgeHelperCli() {
|
|
26
|
+
try {
|
|
27
|
+
const input = JSON.parse(await readStdin());
|
|
28
|
+
process.stdout.write(await runAge(input));
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
32
|
+
process.exitCode = 1;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export interface VaultKey {
|
|
2
|
+
passphrase: string;
|
|
3
|
+
}
|
|
4
|
+
export interface VaultBundleRecord {
|
|
5
|
+
metadata?: string;
|
|
6
|
+
keys: Record<string, string>;
|
|
7
|
+
}
|
|
8
|
+
export interface VaultData {
|
|
9
|
+
v: 1;
|
|
10
|
+
bundles: Record<string, VaultBundleRecord>;
|
|
11
|
+
}
|
|
12
|
+
export declare const VAULT_SESSION_TTL_MS: number;
|
|
13
|
+
export declare const VAULT_SCRYPT_WORK_FACTOR = 18;
|
|
14
|
+
export declare function vaultPath(): string;
|
|
15
|
+
export declare function vaultExists(): boolean;
|
|
16
|
+
export declare function _setVaultPathForTest(filePath: string | null): void;
|
|
17
|
+
export declare function _clearVaultDataCacheForTest(): void;
|
|
18
|
+
export declare function _resetVaultAgeOperationCountForTest(): void;
|
|
19
|
+
export declare function _getVaultAgeOperationCountForTest(): number;
|
|
20
|
+
export declare function _setVaultAgeHelperLaunchForTest(launch: {
|
|
21
|
+
command: string;
|
|
22
|
+
args: string[];
|
|
23
|
+
} | null): void;
|
|
24
|
+
export declare function encrypt(key: VaultKey, json: VaultData): string;
|
|
25
|
+
export declare function decrypt(key: VaultKey, blob: string): VaultData;
|
|
26
|
+
export declare function createVault(password: string, opts?: {
|
|
27
|
+
overwrite?: boolean;
|
|
28
|
+
}): VaultKey;
|
|
29
|
+
export declare function joinVault(password: string, sourcePath: string, opts?: {
|
|
30
|
+
overwrite?: boolean;
|
|
31
|
+
}): VaultKey;
|
|
32
|
+
export declare function unlock(password: string): VaultKey;
|
|
33
|
+
export declare function cacheVaultKey(key: VaultKey, ttlMs?: number): void;
|
|
34
|
+
export declare function clearVaultKey(): void;
|
|
35
|
+
export declare function getVaultSession(): {
|
|
36
|
+
loggedIn: true;
|
|
37
|
+
key: VaultKey;
|
|
38
|
+
expiresAt: number;
|
|
39
|
+
} | {
|
|
40
|
+
loggedIn: false;
|
|
41
|
+
expiresAt?: number;
|
|
42
|
+
};
|
|
43
|
+
export declare function vaultHasItem(item: string): boolean;
|
|
44
|
+
export declare function vaultGetItem(item: string): string;
|
|
45
|
+
export declare function vaultGetItems(items: string[]): Map<string, string>;
|
|
46
|
+
export declare function vaultSetItem(item: string, value: string): void;
|
|
47
|
+
export declare function vaultSetItems(items: Map<string, string>): void;
|
|
48
|
+
export declare function vaultDeleteItem(item: string): boolean;
|
|
49
|
+
export declare function vaultListItems(prefix: string): string[];
|