@phnx-labs/agents-cli 1.22.53 → 1.22.55
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 +208 -0
- package/README.md +59 -9
- package/dist/bootstrap.js +55 -154
- package/dist/cli/command-registry.d.ts +5 -0
- package/dist/cli/command-registry.js +8 -1
- package/dist/commands/accounts.js +219 -173
- package/dist/commands/apply.js +6 -3
- package/dist/commands/auth-mint.d.ts +8 -0
- package/dist/commands/auth-mint.js +96 -0
- package/dist/commands/auth.js +5 -1
- package/dist/commands/browser.js +1 -1
- package/dist/commands/cost.js +8 -2
- package/dist/commands/daemon.js +2 -2
- package/dist/commands/doctor.js +6 -1
- package/dist/commands/exec.js +10 -8
- package/dist/commands/focus.d.ts +1 -0
- package/dist/commands/focus.js +2 -2
- package/dist/commands/go.d.ts +5 -4
- package/dist/commands/go.js +7 -7
- package/dist/commands/insights.js +9 -0
- package/dist/commands/monitors.js +85 -30
- package/dist/commands/output.js +8 -2
- package/dist/commands/repo.js +18 -0
- package/dist/commands/routines.js +31 -2
- package/dist/commands/secrets.js +33 -14
- package/dist/commands/sessions.d.ts +20 -12
- package/dist/commands/sessions.js +64 -20
- package/dist/commands/setup-accounts.d.ts +8 -0
- package/dist/commands/setup-accounts.js +47 -0
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +11 -2
- package/dist/commands/share.d.ts +79 -3
- package/dist/commands/share.js +347 -18
- package/dist/commands/ssh.d.ts +7 -0
- package/dist/commands/ssh.js +18 -2
- package/dist/commands/status.js +14 -0
- package/dist/commands/view.d.ts +11 -1
- package/dist/commands/view.js +35 -7
- package/dist/lib/account-registry.js +15 -3
- package/dist/lib/accounting/rotate.d.ts +20 -6
- package/dist/lib/accounting/rotate.js +38 -7
- package/dist/lib/accounting/usage.d.ts +68 -1
- package/dist/lib/accounting/usage.js +116 -10
- package/dist/lib/agent-spec/agents.d.ts +5 -2
- package/dist/lib/agent-spec/agents.js +25 -7
- package/dist/lib/analytics/mix-commands.js +12 -6
- package/dist/lib/auth-mint.d.ts +150 -0
- package/dist/lib/auth-mint.js +434 -0
- package/dist/lib/browser/cdp.d.ts +1 -1
- package/dist/lib/browser/cdp.js +1 -1
- package/dist/lib/browser/ffmpeg.d.ts +12 -0
- package/dist/lib/browser/ffmpeg.js +184 -0
- package/dist/lib/browser/remote-control.d.ts +9 -7
- package/dist/lib/browser/remote-control.js +9 -7
- package/dist/lib/browser/service.js +119 -25
- package/dist/lib/claude-account-token.d.ts +10 -0
- package/dist/lib/claude-account-token.js +14 -4
- package/dist/lib/config-drift.d.ts +37 -0
- package/dist/lib/config-drift.js +72 -0
- package/dist/lib/daemon/auth-sync-service.d.ts +19 -0
- package/dist/lib/daemon/auth-sync-service.js +34 -0
- package/dist/lib/daemon/browser-task-reap-service.d.ts +14 -0
- package/dist/lib/daemon/browser-task-reap-service.js +26 -0
- package/dist/lib/daemon/daemon.js +87 -176
- package/dist/lib/daemon/heartbeat-service.d.ts +13 -0
- package/dist/lib/daemon/heartbeat-service.js +26 -0
- package/dist/lib/daemon/monitor-engine-service.d.ts +9 -5
- package/dist/lib/daemon/monitor-engine-service.js +15 -7
- package/dist/lib/daemon/runner.d.ts +15 -0
- package/dist/lib/daemon/runner.js +23 -0
- package/dist/lib/daemon/secrets-broker-service.d.ts +5 -4
- package/dist/lib/daemon/secrets-broker-service.js +17 -32
- package/dist/lib/daemon/service.d.ts +2 -2
- package/dist/lib/daemon/service.js +1 -1
- package/dist/lib/daemon/session-state-service.d.ts +21 -0
- package/dist/lib/daemon/session-state-service.js +34 -0
- package/dist/lib/daemon/supervisor.d.ts +17 -7
- package/dist/lib/daemon/supervisor.js +87 -14
- package/dist/lib/daemon/tmux-reap-service.d.ts +11 -0
- package/dist/lib/daemon/tmux-reap-service.js +28 -0
- package/dist/lib/daemon/webhook-receiver-service.d.ts +9 -0
- package/dist/lib/daemon/webhook-receiver-service.js +17 -0
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +25 -0
- package/dist/lib/device-config.d.ts +3 -3
- package/dist/lib/device-config.js +5 -5
- package/dist/lib/devices/connect.d.ts +26 -0
- package/dist/lib/devices/connect.js +48 -1
- package/dist/lib/devices/doctor-findings.d.ts +5 -1
- package/dist/lib/devices/doctor-findings.js +19 -1
- package/dist/lib/devices/harness-inventory.js +5 -2
- package/dist/lib/exec.d.ts +28 -0
- package/dist/lib/exec.js +73 -7
- package/dist/lib/feed/feed.d.ts +1 -1
- package/dist/lib/feed/feed.js +23 -1
- package/dist/lib/feed-broadcast.js +1 -1
- package/dist/lib/fleet/apply.d.ts +11 -0
- package/dist/lib/fleet/apply.js +23 -3
- package/dist/lib/fleet/auth-sync.js +5 -3
- package/dist/lib/help.d.ts +9 -0
- package/dist/lib/help.js +29 -1
- package/dist/lib/hosts/passthrough.d.ts +1 -10
- package/dist/lib/hosts/passthrough.js +1 -13
- package/dist/lib/installations/versions.js +9 -1
- package/dist/lib/linux-userns.d.ts +58 -0
- package/dist/lib/linux-userns.js +116 -0
- package/dist/lib/memory.d.ts +26 -0
- package/dist/lib/memory.js +80 -1
- package/dist/lib/monitors/config.d.ts +11 -0
- package/dist/lib/monitors/config.js +8 -0
- package/dist/lib/monitors/engine.d.ts +5 -1
- package/dist/lib/monitors/engine.js +13 -4
- package/dist/lib/monitors/state.d.ts +37 -1
- package/dist/lib/monitors/state.js +79 -4
- package/dist/lib/permissions-registry.d.ts +2 -0
- package/dist/lib/permissions-registry.js +116 -14
- package/dist/lib/permissions.d.ts +5 -3
- package/dist/lib/permissions.js +25 -27
- package/dist/lib/profiles.d.ts +8 -7
- package/dist/lib/profiles.js +12 -0
- package/dist/lib/project-key.d.ts +9 -0
- package/dist/lib/project-key.js +11 -0
- package/dist/lib/scheduling/routines.d.ts +47 -0
- package/dist/lib/scheduling/routines.js +70 -1
- package/dist/lib/secrets/bundles.d.ts +35 -0
- package/dist/lib/secrets/bundles.js +78 -1
- package/dist/lib/secrets/push.d.ts +3 -8
- package/dist/lib/secrets/push.js +18 -14
- package/dist/lib/secrets/remote.d.ts +9 -18
- package/dist/lib/secrets/remote.js +11 -26
- package/dist/lib/secrets/reserved-sync.d.ts +65 -0
- package/dist/lib/secrets/reserved-sync.js +129 -0
- package/dist/lib/self-heal/checks/hook-manifest.d.ts +2 -0
- package/dist/lib/self-heal/checks/hook-manifest.js +56 -0
- package/dist/lib/self-heal/registry.js +4 -0
- package/dist/lib/self-heal/types.d.ts +1 -1
- package/dist/lib/session/active.js +1 -4
- package/dist/lib/session/db.d.ts +41 -5
- package/dist/lib/session/db.js +132 -30
- package/dist/lib/session/discover.d.ts +32 -4
- package/dist/lib/session/discover.js +119 -25
- package/dist/lib/session/insights.d.ts +14 -0
- package/dist/lib/session/insights.js +25 -2
- package/dist/lib/session/linear.js +1 -1
- package/dist/lib/session/shell-programs.d.ts +17 -0
- package/dist/lib/session/shell-programs.js +21 -0
- package/dist/lib/session/state.js +2 -1
- package/dist/lib/session/stream-render.js +2 -1
- package/dist/lib/session/tool-calls.js +2 -5
- package/dist/lib/session/trajectory-html.js +2 -1
- package/dist/lib/session/trajectory.js +3 -12
- package/dist/lib/session/types.d.ts +8 -0
- package/dist/lib/share/capture.js +11 -2
- package/dist/lib/share/publish.d.ts +56 -5
- package/dist/lib/share/publish.js +126 -18
- package/dist/lib/share/worker-template.d.ts +3 -12
- package/dist/lib/share/worker-template.js +860 -59
- package/dist/lib/startup/root-command.js +2 -1
- package/dist/lib/state.d.ts +16 -0
- package/dist/lib/state.js +178 -46
- package/dist/lib/sync-status.d.ts +4 -0
- package/dist/lib/sync-status.js +3 -0
- package/dist/lib/traces/classify.js +24 -19
- package/dist/lib/usage-refresh.js +2 -1
- package/dist/lib/view-types.d.ts +7 -0
- package/package.json +9 -2
package/dist/commands/output.js
CHANGED
|
@@ -42,8 +42,14 @@ The burn is split into input / cache-read / cache-write where the harness record
|
|
|
42
42
|
(Claude/Codex/Gemini/Droid). --pricing no-cache reprices cached tokens at the input rate,
|
|
43
43
|
so you can see what caching is saving. --json always carries both actual and no-cache costs.
|
|
44
44
|
`)
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
// Read opts via optsWithGlobals(): `--json`/`--since`/`--by` collide by name
|
|
46
|
+
// with the `insights` parent's own options, so commander binds them to the
|
|
47
|
+
// parent at parse time and the leaf's plain opts() never sees them. Merging
|
|
48
|
+
// ancestor opts is what the sibling `insights mix` recipes already do
|
|
49
|
+
// (mix-commands.ts) — without it every flag on this command is silently
|
|
50
|
+
// dropped (e.g. `agents insights output --json` printed the human table).
|
|
51
|
+
.action(async (_options, command) => {
|
|
52
|
+
await outputAction(command.optsWithGlobals());
|
|
47
53
|
});
|
|
48
54
|
}
|
|
49
55
|
function resolveGroup(by) {
|
package/dist/commands/repo.js
CHANGED
|
@@ -1052,6 +1052,14 @@ export function registerRepoCommands(program) {
|
|
|
1052
1052
|
].filter(Boolean);
|
|
1053
1053
|
if (parts.length > 0)
|
|
1054
1054
|
console.log(chalk.gray(`Device policy: ${parts.join(' · ')}`));
|
|
1055
|
+
const { syncReservedAuthBundle } = await import('../lib/secrets/reserved-sync.js');
|
|
1056
|
+
const auth = syncReservedAuthBundle();
|
|
1057
|
+
if (auth.pushed.length > 0) {
|
|
1058
|
+
console.log(chalk.gray(`Auth bundle: pushed to ${auth.pushed.join(', ')}`));
|
|
1059
|
+
}
|
|
1060
|
+
for (const err of auth.errors) {
|
|
1061
|
+
console.error(chalk.yellow(`Auth bundle: ${err.device}: ${err.message}`));
|
|
1062
|
+
}
|
|
1055
1063
|
}
|
|
1056
1064
|
});
|
|
1057
1065
|
repoCmd
|
|
@@ -1091,6 +1099,16 @@ export function registerRepoCommands(program) {
|
|
|
1091
1099
|
const result = await commitAndPush(t.dir, options.message);
|
|
1092
1100
|
if (result.success) {
|
|
1093
1101
|
spinner.succeed(`${formatRepoTarget(t.alias, t.dir, result.branch)}: ${result.detail ?? 'pushed'}`);
|
|
1102
|
+
if (t.alias === 'user') {
|
|
1103
|
+
const { syncReservedAuthBundle } = await import('../lib/secrets/reserved-sync.js');
|
|
1104
|
+
const auth = syncReservedAuthBundle();
|
|
1105
|
+
if (auth.pushed.length > 0) {
|
|
1106
|
+
console.log(chalk.gray(`Auth bundle: pushed to ${auth.pushed.join(', ')}`));
|
|
1107
|
+
}
|
|
1108
|
+
for (const err of auth.errors) {
|
|
1109
|
+
console.error(chalk.yellow(`Auth bundle: ${err.device}: ${err.message}`));
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1094
1112
|
}
|
|
1095
1113
|
else {
|
|
1096
1114
|
spinner.fail(`${formatRepoTarget(t.alias, t.dir)}: ${result.error}`);
|
|
@@ -16,7 +16,7 @@ import { parseAgentVersionSpec, isAgentHardDeprecated, hardDeprecationError, ROU
|
|
|
16
16
|
import { isCustomHarnessName } from '../lib/profiles.js';
|
|
17
17
|
import { RUN_STRATEGIES, normalizeRunStrategy } from '../lib/accounting/rotate.js';
|
|
18
18
|
import { formatRepoLink, REPO_DISPLAY_MAX } from '../lib/routines-format.js';
|
|
19
|
-
import { listJobs as listAllJobs, deleteJob, readJob, validateJob, writeJob, setJobEnabled, listRuns, routineStats, getLatestRun, getRunDir, getJobPath, isCanonicalRoutineSource, parseAtTime, hasCompletedOneShotRun, isOneShotLikeSchedule, isOneShotRoutine, jobRunsOnThisDevice, checkJobDeviceEligibility, normalizeTriggerEvent, parseHostStrategy, resolveHostStrategy, HOST_STRATEGIES, computeProjectGroupKind, projectGroupKey, projectGroupTitle, projectGroupOrder, normalizeProjects, buildRoutineListJson, fireConditionLabel, nextRunForDisplay, nextRunLabel, localLatestRun, listJobsForDisplay, } from '../lib/scheduling/routines.js';
|
|
19
|
+
import { listJobs as listAllJobs, deleteJob, readJob, validateJob, writeJob, setJobEnabled, listRuns, routineStats, getLatestRun, getRunDir, getJobPath, isCanonicalRoutineSource, parseAtTime, hasCompletedOneShotRun, isOneShotLikeSchedule, isOneShotRoutine, jobRunsOnThisDevice, checkJobDeviceEligibility, normalizeTriggerEvent, parseHostStrategy, resolveHostStrategy, HOST_STRATEGIES, computeProjectGroupKind, projectGroupKey, projectGroupTitle, projectGroupOrder, normalizeProjects, buildRoutineListJson, buildRoutineStatusRows, fireConditionLabel, nextRunForDisplay, nextRunLabel, localLatestRun, listJobsForDisplay, } from '../lib/scheduling/routines.js';
|
|
20
20
|
export { buildRoutineListJson } from '../lib/scheduling/routines.js';
|
|
21
21
|
import { listProjectDefs, isSafeProjectName } from '../lib/projects.js';
|
|
22
22
|
import { evaluateActivationReadinessLive } from '../lib/routine-readiness.js';
|
|
@@ -831,6 +831,9 @@ export function registerRoutinesCommands(program) {
|
|
|
831
831
|
|
|
832
832
|
# Check whether the scheduler is running
|
|
833
833
|
agents routines status
|
|
834
|
+
|
|
835
|
+
# Machine-readable scheduler + per-routine status: owner device, last fire, last error, in-flight run
|
|
836
|
+
agents routines status --json
|
|
834
837
|
`,
|
|
835
838
|
notes: `
|
|
836
839
|
A routine is a YAML file that schedules an agent invocation. It specifies:
|
|
@@ -2148,12 +2151,38 @@ export function registerRoutinesCommands(program) {
|
|
|
2148
2151
|
routinesCmd
|
|
2149
2152
|
.command('status')
|
|
2150
2153
|
.description('Show scheduler status, enabled routines, and when each one fires next.')
|
|
2151
|
-
.
|
|
2154
|
+
.option('--json', 'Emit machine-readable scheduler + per-routine status (owner device, last fire, last error, in-flight run)')
|
|
2155
|
+
.action((options) => {
|
|
2152
2156
|
try {
|
|
2153
2157
|
monitorRunningJobs();
|
|
2154
2158
|
}
|
|
2155
2159
|
catch { /* best-effort orphan reap */ }
|
|
2156
2160
|
const status = getDaemonStatus();
|
|
2161
|
+
// The daemon-owned status surface (PHNX-3215): scheduler truth plus, per
|
|
2162
|
+
// routine, its single owner device, last fire outcome + error, and any
|
|
2163
|
+
// in-flight spawn — the fields `list --json` (definition-shaped) does not
|
|
2164
|
+
// carry. Emitted from THIS device, so per-device fields read as this box's.
|
|
2165
|
+
if (options.json) {
|
|
2166
|
+
const rows = buildRoutineStatusRows();
|
|
2167
|
+
writeJson({
|
|
2168
|
+
device: machineId(),
|
|
2169
|
+
scheduler: {
|
|
2170
|
+
state: status.state,
|
|
2171
|
+
pid: status.pid,
|
|
2172
|
+
binaryPath: status.binaryPath,
|
|
2173
|
+
heartbeat: status.heartbeat
|
|
2174
|
+
? {
|
|
2175
|
+
lastTick: status.heartbeat.lastTick,
|
|
2176
|
+
ageSec: Math.round((Date.now() - Date.parse(status.heartbeat.lastTick)) / 1000),
|
|
2177
|
+
}
|
|
2178
|
+
: null,
|
|
2179
|
+
enabled: rows.filter((r) => r.enabled).length,
|
|
2180
|
+
total: rows.length,
|
|
2181
|
+
},
|
|
2182
|
+
routines: rows,
|
|
2183
|
+
});
|
|
2184
|
+
return;
|
|
2185
|
+
}
|
|
2157
2186
|
console.log(chalk.bold('Scheduler\n'));
|
|
2158
2187
|
const stateLabel = status.state === 'running'
|
|
2159
2188
|
? chalk.green('running')
|
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, isAgentInvocationContext, 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, assertFileBundleDecryptable, isReservedBundleName, ReservedBundleWrongBackendError, 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';
|
|
@@ -846,7 +846,12 @@ export function renderExpiringCol(b, now = Date.now()) {
|
|
|
846
846
|
* exists to describe. `--force` already means "overwrite what is there".
|
|
847
847
|
*/
|
|
848
848
|
export function resolveImportBundle(name, backendOpt, synced = false, force = false) {
|
|
849
|
-
|
|
849
|
+
if (isReservedBundleName(name) && (synced || (backendOpt && backendOpt !== 'file'))) {
|
|
850
|
+
throw new ReservedBundleWrongBackendError(name, synced ? 'vault' : resolveBackendOpt(backendOpt));
|
|
851
|
+
}
|
|
852
|
+
const requestedBackend = isReservedBundleName(name)
|
|
853
|
+
? 'file'
|
|
854
|
+
: (synced ? 'vault' : resolveBackendOpt(backendOpt));
|
|
850
855
|
if (bundleExists(name)) {
|
|
851
856
|
// readBundleIfDecryptable nulls ONLY on BundleUndecryptableError; a locked
|
|
852
857
|
// keychain or logged-out vault still throws, so a recoverable state can
|
|
@@ -892,6 +897,9 @@ function applyEnvToBundle(bundle, env, opts) {
|
|
|
892
897
|
else {
|
|
893
898
|
writeBundle(bundle);
|
|
894
899
|
}
|
|
900
|
+
if (added > 0) {
|
|
901
|
+
assertFileBundleDecryptable(bundle.name, Object.keys(env).filter((k) => k in bundle.vars));
|
|
902
|
+
}
|
|
895
903
|
return { added, skipped };
|
|
896
904
|
}
|
|
897
905
|
/** Register the `agents secrets` command tree. */
|
|
@@ -1581,7 +1589,16 @@ export function registerSecretsCommands(program) {
|
|
|
1581
1589
|
// inherits the configured default (`hold`) instead of being pinned.
|
|
1582
1590
|
const policyOpt = opts.policy ?? opts.tier;
|
|
1583
1591
|
const policy = policyOpt ? parsePolicyOpt(policyOpt) : undefined;
|
|
1584
|
-
|
|
1592
|
+
let backend = opts.synced ? 'vault' : resolveBackendOpt(opts.backend);
|
|
1593
|
+
if (isReservedBundleName(resolvedName)) {
|
|
1594
|
+
// Reserved `auth` is file-backed by construction. Omitting --backend
|
|
1595
|
+
// still forces file even when agents.yaml defaults to keychain; an
|
|
1596
|
+
// explicit non-file backend fails loud (SEC-GAP-3).
|
|
1597
|
+
if (opts.synced || (opts.backend && opts.backend !== 'file')) {
|
|
1598
|
+
throw new ReservedBundleWrongBackendError(resolvedName, backend);
|
|
1599
|
+
}
|
|
1600
|
+
backend = 'file';
|
|
1601
|
+
}
|
|
1585
1602
|
if (bundleExists(resolvedName) && !opts.force) {
|
|
1586
1603
|
console.error(chalk.red(`Bundle '${resolvedName}' already exists. Use --force to overwrite.`));
|
|
1587
1604
|
process.exit(1);
|
|
@@ -2134,7 +2151,7 @@ Examples:
|
|
|
2134
2151
|
.option('--to-1password', 'Push every key in the bundle as a PASSWORD item in a 1Password vault')
|
|
2135
2152
|
.option('--vault <name>', '1Password vault name (used with --to-1password)')
|
|
2136
2153
|
.option('--device <target...>', 'Push the bundle over SSH to this device (host alias or user@host); repeatable for multiple machines')
|
|
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
|
|
2154
|
+
.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 and never forwards AGENTS_SECRETS_PASSPHRASE.', 'keychain')
|
|
2138
2155
|
.option('--force', 'Overwrite existing keys/items on the target (used with --to-1password and --device)')
|
|
2139
2156
|
.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)`)
|
|
2140
2157
|
.action(async (bundleName, opts) => {
|
|
@@ -2170,15 +2187,18 @@ Examples:
|
|
|
2170
2187
|
console.error(chalk.red("--remote-backend vault is not supported; use 'keychain' or 'file'."));
|
|
2171
2188
|
process.exit(1);
|
|
2172
2189
|
}
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
//
|
|
2181
|
-
|
|
2190
|
+
let remoteBackend = parsedBackend;
|
|
2191
|
+
if (isReservedBundleName(resolvedBundleName)) {
|
|
2192
|
+
if (opts.remoteBackend && opts.remoteBackend !== 'file') {
|
|
2193
|
+
throw new ReservedBundleWrongBackendError(resolvedBundleName, parsedBackend);
|
|
2194
|
+
}
|
|
2195
|
+
remoteBackend = 'file';
|
|
2196
|
+
}
|
|
2197
|
+
// File-backend export NEVER forwards AGENTS_SECRETS_PASSPHRASE
|
|
2198
|
+
// (PHNX-2371). Forwarding it keys the remote ciphertext to a secret
|
|
2199
|
+
// the destination daemon does not hold, so later headless reads fail
|
|
2200
|
+
// while import still prints "Imported N key(s)". The remote
|
|
2201
|
+
// auto-provisions its own machine-local key instead.
|
|
2182
2202
|
// Resolve ONCE for N hosts — reading a bundle can prompt, and doing it
|
|
2183
2203
|
// per host would prompt per host.
|
|
2184
2204
|
//
|
|
@@ -2197,7 +2217,6 @@ Examples:
|
|
|
2197
2217
|
const out = pushResolvedBundleToHost(resolvedForPush, resolvedBundleName, host, {
|
|
2198
2218
|
remoteBackend,
|
|
2199
2219
|
force: opts.force,
|
|
2200
|
-
passphrase: remotePassphrase,
|
|
2201
2220
|
operation: 'ssh export',
|
|
2202
2221
|
});
|
|
2203
2222
|
if (!out.ok) {
|
|
@@ -676,7 +676,7 @@ export declare function formatPickerLabel(s: SessionMeta, query: string, cols?:
|
|
|
676
676
|
* so it stays fixed across the picker's re-renders within a single run.
|
|
677
677
|
*/
|
|
678
678
|
export declare function formatPickerTip(sessions: SessionMeta[]): string;
|
|
679
|
-
export declare function pickSessionInteractive(sessions: SessionMeta[], message?: string, initialSearch?: string, hiddenCount?: number, enterHint?: string): Promise<PickedSession | null>;
|
|
679
|
+
export declare function pickSessionInteractive(sessions: SessionMeta[], message?: string, initialSearch?: string, hiddenCount?: number, enterHint?: string, scope?: SessionSearchScope): Promise<PickedSession | null>;
|
|
680
680
|
/**
|
|
681
681
|
* Row-id prefix for a live session whose agent has not reported a session id yet
|
|
682
682
|
* (a booting harness, a queued teammate). The browser lists these so nothing live
|
|
@@ -736,6 +736,16 @@ interface AgentFilter {
|
|
|
736
736
|
* alias and single-typo rules. Kept pure so focus and the browser cannot drift. */
|
|
737
737
|
export declare function resolveSessionAgentName(name: string): SessionAgentId | null;
|
|
738
738
|
export declare function parseAgentFilter(agentName?: string): AgentFilter;
|
|
739
|
+
/**
|
|
740
|
+
* Explicit `--agent` / `--project` / `--routine` flags. Distinct from the
|
|
741
|
+
* listing pool (cwd-scoped, default-capped) that PHNX-2767 hydrates past:
|
|
742
|
+
* these flags MUST still exclude FTS hits after that union.
|
|
743
|
+
*/
|
|
744
|
+
export type SessionSearchScope = {
|
|
745
|
+
agent?: string;
|
|
746
|
+
project?: string;
|
|
747
|
+
routine?: boolean | string;
|
|
748
|
+
};
|
|
739
749
|
/**
|
|
740
750
|
* How a `sessions <query>` argument was resolved against the pool.
|
|
741
751
|
*
|
|
@@ -759,29 +769,27 @@ export interface SessionQueryResolution {
|
|
|
759
769
|
*/
|
|
760
770
|
export declare function resolveSessionQuery(pool: SessionMeta[], query: string, options?: {
|
|
761
771
|
indexFallback?: boolean;
|
|
772
|
+
scope?: SessionSearchScope;
|
|
762
773
|
}): SessionQueryResolution;
|
|
763
774
|
/** Honest result of a fleet sweep that found nothing: it says what the sweep
|
|
764
775
|
* actually did — how many peers answered, which were unreachable — instead of
|
|
765
776
|
* dead-ending on "search with --device <host>" after the search already ran. */
|
|
766
777
|
export declare function fleetNotFoundMessage(query: string, deviceCount: number, unreachable: string[]): string[];
|
|
767
778
|
/** Filter and rank sessions by a multi-term search query across metadata and content. */
|
|
768
|
-
export declare function filterSessionsByQuery(sessions: SessionMeta[], query: string | undefined): SessionMeta[];
|
|
779
|
+
export declare function filterSessionsByQuery(sessions: SessionMeta[], query: string | undefined, scope?: SessionSearchScope): SessionMeta[];
|
|
769
780
|
/**
|
|
770
781
|
* Narrow a session list by --project and --agent before search resolution.
|
|
771
782
|
* Without this, a query like "scoped search" could match sessions in BOTH
|
|
772
783
|
* the project you specified AND elsewhere, producing an ambiguity error
|
|
773
784
|
* even though the user already pointed at the correct scope.
|
|
785
|
+
*
|
|
786
|
+
* PHNX-2767 hydrates FTS hits missing from the listing pool *after* this
|
|
787
|
+
* runs, so callers that search must also pass the same scope into
|
|
788
|
+
* `filterSessionsByQuery` / `scopedContentIndex` or the union
|
|
789
|
+
* reintroduces the rows this function just dropped.
|
|
774
790
|
*/
|
|
775
|
-
export declare function applyScopeFilters(sessions: SessionMeta[], scope:
|
|
776
|
-
|
|
777
|
-
project?: string;
|
|
778
|
-
routine?: boolean | string;
|
|
779
|
-
}): SessionMeta[];
|
|
780
|
-
export declare function artifactLookupScope(agent?: string, project?: string, routine?: boolean | string): {
|
|
781
|
-
agent?: string;
|
|
782
|
-
project?: string;
|
|
783
|
-
routine?: boolean | string;
|
|
784
|
-
};
|
|
791
|
+
export declare function applyScopeFilters(sessions: SessionMeta[], scope: SessionSearchScope): SessionMeta[];
|
|
792
|
+
export declare function artifactLookupScope(agent?: string, project?: string, routine?: boolean | string): SessionSearchScope;
|
|
785
793
|
/**
|
|
786
794
|
* Whether a missed local id lookup should widen to a cross-machine sweep.
|
|
787
795
|
*
|
|
@@ -1978,7 +1978,7 @@ export async function renderSessionPreview(query, scope) {
|
|
|
1978
1978
|
if (outcome.kind !== 'resolved'
|
|
1979
1979
|
&& (!scope.hosts?.length || shouldIncludeLocal(scope.hosts, machineId()))) {
|
|
1980
1980
|
const discovered = applyScopeFilters(await discoverSessions({ all: true, cwd: process.cwd(), limit: 5000, waitForScan: true }), scope);
|
|
1981
|
-
const localMatches = resolveSessionQuery(discovered, query, { indexFallback: false }).matches
|
|
1981
|
+
const localMatches = resolveSessionQuery(discovered, query, { indexFallback: false, scope }).matches
|
|
1982
1982
|
.map(session => ({ ...session, machine: session.machine || machineId() }));
|
|
1983
1983
|
const exact = localMatches.find(session => selectorAllowsEarlyExit(query)
|
|
1984
1984
|
&& session.id.toLowerCase() === query.trim().toLowerCase());
|
|
@@ -2674,7 +2674,11 @@ limitSource) {
|
|
|
2674
2674
|
if (toolEvidenceMode) {
|
|
2675
2675
|
const self = toolSelf;
|
|
2676
2676
|
const selectedSessions = searchQuery
|
|
2677
|
-
? filterSessionsByQuery(sessions, searchQuery
|
|
2677
|
+
? filterSessionsByQuery(sessions, searchQuery, {
|
|
2678
|
+
agent: options.agent,
|
|
2679
|
+
project: options.project,
|
|
2680
|
+
routine: options.routine,
|
|
2681
|
+
})
|
|
2678
2682
|
: sessions;
|
|
2679
2683
|
const localSessions = selectedSessions;
|
|
2680
2684
|
const mayFanOut = options.local !== true && process.env[NO_FANOUT_ENV] !== '1';
|
|
@@ -2771,7 +2775,9 @@ limitSource) {
|
|
|
2771
2775
|
// that merely MENTIONS the id, defeating exact remote resolution. A genuine
|
|
2772
2776
|
// search phrase keeps the ranked metadata+content path.
|
|
2773
2777
|
const filtered = searchQuery
|
|
2774
|
-
? resolveSessionQuery(sessions, searchQuery
|
|
2778
|
+
? resolveSessionQuery(sessions, searchQuery, {
|
|
2779
|
+
scope: { agent: options.agent, project: options.project, routine: options.routine },
|
|
2780
|
+
}).matches
|
|
2775
2781
|
: sessions;
|
|
2776
2782
|
// JSON is the canonical picker contract: enrich the durable rows once
|
|
2777
2783
|
// from the shared live cache so every consumer gets lifecycle/recovery
|
|
@@ -2880,7 +2886,7 @@ limitSource) {
|
|
|
2880
2886
|
const message = pathFilter
|
|
2881
2887
|
? `Search sessions (${path.basename(pathFilter)}):`
|
|
2882
2888
|
: formatSearchMessage(options);
|
|
2883
|
-
const picked = await pickSessionInteractive(sessions, message, searchQuery, hiddenCount);
|
|
2889
|
+
const picked = await pickSessionInteractive(sessions, message, searchQuery, hiddenCount, undefined, { agent: options.agent, project: options.project, routine: options.routine });
|
|
2884
2890
|
if (picked) {
|
|
2885
2891
|
await handlePickedSession(picked);
|
|
2886
2892
|
return;
|
|
@@ -2888,7 +2894,13 @@ limitSource) {
|
|
|
2888
2894
|
return;
|
|
2889
2895
|
}
|
|
2890
2896
|
// Non-interactive fallback (piped output) or --flat/--tree.
|
|
2891
|
-
const filtered = searchQuery
|
|
2897
|
+
const filtered = searchQuery
|
|
2898
|
+
? filterSessionsByQuery(sessions, searchQuery, {
|
|
2899
|
+
agent: options.agent,
|
|
2900
|
+
project: options.project,
|
|
2901
|
+
routine: options.routine,
|
|
2902
|
+
})
|
|
2903
|
+
: sessions;
|
|
2892
2904
|
const liveIndex = await maybeLiveIndex(options);
|
|
2893
2905
|
printSessionTable(filtered, hiddenCount, options.tree === true, liveIndex);
|
|
2894
2906
|
// Every listing path must say what it dropped — a hidden default that stays
|
|
@@ -3764,7 +3776,7 @@ const PICKER_TIPS = [
|
|
|
3764
3776
|
export function formatPickerTip(sessions) {
|
|
3765
3777
|
return chalk.gray(PICKER_TIPS[sessions.length % PICKER_TIPS.length]);
|
|
3766
3778
|
}
|
|
3767
|
-
export async function pickSessionInteractive(sessions, message = 'Search sessions:', initialSearch, hiddenCount = 0, enterHint) {
|
|
3779
|
+
export async function pickSessionInteractive(sessions, message = 'Search sessions:', initialSearch, hiddenCount = 0, enterHint, scope) {
|
|
3768
3780
|
// The hidden-session footer is console.log'd above the Inquirer prompt, so it
|
|
3769
3781
|
// scrolls the viewport the picker can't measure; tell the picker to reserve for
|
|
3770
3782
|
// it (see pickerPageSize) so the preview and the footer stay on screen together.
|
|
@@ -3784,7 +3796,7 @@ export async function pickSessionInteractive(sessions, message = 'Search session
|
|
|
3784
3796
|
// Typing: search the full pool.
|
|
3785
3797
|
if (!query.trim())
|
|
3786
3798
|
return sessions;
|
|
3787
|
-
return filterSessionsByQuery(sessions, query);
|
|
3799
|
+
return filterSessionsByQuery(sessions, query, scope);
|
|
3788
3800
|
},
|
|
3789
3801
|
labelFor: (s, query) => formatPickerLabel(s, query, cols),
|
|
3790
3802
|
pageSize: PICKER_RECENT_COUNT,
|
|
@@ -4179,7 +4191,7 @@ export function resolveSessionQuery(pool, query, options = {}) {
|
|
|
4179
4191
|
const matches = options.indexFallback === false ? [] : findSessionsById(normalized);
|
|
4180
4192
|
return { matches, byId: true, completeId };
|
|
4181
4193
|
}
|
|
4182
|
-
return { matches: filterSessionsByQuery(pool, normalized), byId: false, completeId };
|
|
4194
|
+
return { matches: filterSessionsByQuery(pool, normalized, options.scope), byId: false, completeId };
|
|
4183
4195
|
}
|
|
4184
4196
|
/** Explain an ambiguous resolution. Only a short id can be lengthened: a complete
|
|
4185
4197
|
* id is already maximal, and a search phrase was never an id to begin with. */
|
|
@@ -4218,7 +4230,7 @@ export function fleetNotFoundMessage(query, deviceCount, unreachable) {
|
|
|
4218
4230
|
return lines;
|
|
4219
4231
|
}
|
|
4220
4232
|
/** Filter and rank sessions by a multi-term search query across metadata and content. */
|
|
4221
|
-
export function filterSessionsByQuery(sessions, query) {
|
|
4233
|
+
export function filterSessionsByQuery(sessions, query, scope) {
|
|
4222
4234
|
const trimmed = query?.trim().toLowerCase() || '';
|
|
4223
4235
|
if (!trimmed)
|
|
4224
4236
|
return sessions;
|
|
@@ -4228,7 +4240,9 @@ export function filterSessionsByQuery(sessions, query) {
|
|
|
4228
4240
|
return sessions.filter((session) => session.agent === agent && session.version === version);
|
|
4229
4241
|
}
|
|
4230
4242
|
const terms = trimmed.split(/\s+/).filter(Boolean);
|
|
4231
|
-
|
|
4243
|
+
// Hydrate FTS hits missing from the pool (PHNX-2767), then drop hits that
|
|
4244
|
+
// fail --project/--agent/--routine so those flags are not undone by the union.
|
|
4245
|
+
const contentIndex = scopedContentIndex(sessions, trimmed, scope);
|
|
4232
4246
|
// If the query exactly matches a session label, short-circuit the structural
|
|
4233
4247
|
// scorer (which would otherwise surface every session whose topic happens to
|
|
4234
4248
|
// contain the same words) and return only the label hits.
|
|
@@ -4237,7 +4251,15 @@ export function filterSessionsByQuery(sessions, query) {
|
|
|
4237
4251
|
if (exactLabelHits.length > 0) {
|
|
4238
4252
|
return exactLabelHits.sort((a, b) => (b._bm25Score ?? 0) - (a._bm25Score ?? 0));
|
|
4239
4253
|
}
|
|
4240
|
-
|
|
4254
|
+
// The listing pool is a page of the index. FTS hits the whole index. Union
|
|
4255
|
+
// so a grep-visible transcript is returned even when it missed the page
|
|
4256
|
+
// (PHNX-2767).
|
|
4257
|
+
const poolById = new Map(sessions.map(s => [s.id, s]));
|
|
4258
|
+
for (const [id, hit] of contentIndex) {
|
|
4259
|
+
if (!poolById.has(id))
|
|
4260
|
+
poolById.set(id, hit);
|
|
4261
|
+
}
|
|
4262
|
+
return [...poolById.values()]
|
|
4241
4263
|
.map(session => ({ session, score: scoreSessionQuery(session, terms) }))
|
|
4242
4264
|
.filter(entry => {
|
|
4243
4265
|
// Include if scored by topic/project/etc, or matched by content search
|
|
@@ -4314,6 +4336,11 @@ function scoreSessionQuery(session, terms) {
|
|
|
4314
4336
|
* Without this, a query like "scoped search" could match sessions in BOTH
|
|
4315
4337
|
* the project you specified AND elsewhere, producing an ambiguity error
|
|
4316
4338
|
* even though the user already pointed at the correct scope.
|
|
4339
|
+
*
|
|
4340
|
+
* PHNX-2767 hydrates FTS hits missing from the listing pool *after* this
|
|
4341
|
+
* runs, so callers that search must also pass the same scope into
|
|
4342
|
+
* `filterSessionsByQuery` / `scopedContentIndex` or the union
|
|
4343
|
+
* reintroduces the rows this function just dropped.
|
|
4317
4344
|
*/
|
|
4318
4345
|
export function applyScopeFilters(sessions, scope) {
|
|
4319
4346
|
let filtered = sessions;
|
|
@@ -4350,6 +4377,23 @@ export function applyScopeFilters(sessions, scope) {
|
|
|
4350
4377
|
}
|
|
4351
4378
|
return filtered;
|
|
4352
4379
|
}
|
|
4380
|
+
/**
|
|
4381
|
+
* PHNX-2767 hydrates FTS hits missing from the listing pool. That union is
|
|
4382
|
+
* unscoped on purpose (cwd/limit are a page of the index, not a filter).
|
|
4383
|
+
* `--project` / `--agent` / `--routine` ARE filters: drop hydrated hits that
|
|
4384
|
+
* fail them so content search cannot undo a scope the user already set.
|
|
4385
|
+
*/
|
|
4386
|
+
function scopedContentIndex(sessions, query, scope) {
|
|
4387
|
+
const hits = searchContentIndex(sessions, query);
|
|
4388
|
+
if (!scope || (!scope.agent && !scope.project && !scope.routine))
|
|
4389
|
+
return hits;
|
|
4390
|
+
const kept = new Map();
|
|
4391
|
+
for (const [id, session] of hits) {
|
|
4392
|
+
if (applyScopeFilters([session], scope).length > 0)
|
|
4393
|
+
kept.set(id, session);
|
|
4394
|
+
}
|
|
4395
|
+
return kept;
|
|
4396
|
+
}
|
|
4353
4397
|
export function artifactLookupScope(agent, project, routine) {
|
|
4354
4398
|
return { agent, project, routine };
|
|
4355
4399
|
}
|
|
@@ -4365,7 +4409,7 @@ async function renderArtifactsGlobal(query, listAll, name, scope) {
|
|
|
4365
4409
|
});
|
|
4366
4410
|
tracker.stop();
|
|
4367
4411
|
const allSessions = applyScopeFilters(discovered, scope);
|
|
4368
|
-
const { matches: queryMatches, byId, completeId } = resolveSessionQuery(allSessions, query);
|
|
4412
|
+
const { matches: queryMatches, byId, completeId } = resolveSessionQuery(allSessions, query, { scope });
|
|
4369
4413
|
if (queryMatches.length === 0) {
|
|
4370
4414
|
spinner.stop();
|
|
4371
4415
|
if (byId)
|
|
@@ -4462,7 +4506,7 @@ async function renderOneSession(query, mode, scope) {
|
|
|
4462
4506
|
tracker.stop();
|
|
4463
4507
|
const allSessions = applyScopeFilters(discovered, scope);
|
|
4464
4508
|
let session;
|
|
4465
|
-
const resolution = resolveSessionQuery(allSessions, query);
|
|
4509
|
+
const resolution = resolveSessionQuery(allSessions, query, { scope });
|
|
4466
4510
|
let queryMatches = resolution.matches;
|
|
4467
4511
|
let byId = resolution.byId;
|
|
4468
4512
|
const completeId = resolution.completeId;
|
|
@@ -4474,7 +4518,7 @@ async function renderOneSession(query, mode, scope) {
|
|
|
4474
4518
|
// echoes the id in a resume prompt. Gate on looksLikeSessionId, not just
|
|
4475
4519
|
// completeId, so a short id resolves to "no match" rather than fuzzy content.
|
|
4476
4520
|
if (queryMatches.length === 0 && !looksLikeSessionId(query)) {
|
|
4477
|
-
const contentResults =
|
|
4521
|
+
const contentResults = scopedContentIndex(allSessions, query, scope);
|
|
4478
4522
|
if (contentResults.size > 0) {
|
|
4479
4523
|
const matchedSessions = Array.from(contentResults.values())
|
|
4480
4524
|
.sort((a, b) => (b._bm25Score ?? 0) - (a._bm25Score ?? 0));
|
|
@@ -4669,16 +4713,16 @@ export function fleetCandidatesByQuery(rows, query, trustResolvedRows = false) {
|
|
|
4669
4713
|
});
|
|
4670
4714
|
}
|
|
4671
4715
|
/** Resolve through the canonical metadata+content union used by keyword search. */
|
|
4672
|
-
function resolveIndexedMetadataRows(indexed, selector) {
|
|
4716
|
+
function resolveIndexedMetadataRows(indexed, selector, scope) {
|
|
4673
4717
|
const alias = resolveSessionAlias(selector);
|
|
4674
4718
|
if (alias.kind === 'resolved') {
|
|
4675
|
-
return resolveSessionQuery(indexed, alias.sessionId, { indexFallback: false }).matches;
|
|
4719
|
+
return resolveSessionQuery(indexed, alias.sessionId, { indexFallback: false, scope }).matches;
|
|
4676
4720
|
}
|
|
4677
4721
|
if (alias.kind === 'ambiguous') {
|
|
4678
4722
|
const ids = new Set(alias.sessionIds.map(id => id.toLowerCase()));
|
|
4679
4723
|
return indexed.filter(session => ids.has(session.id.toLowerCase()));
|
|
4680
4724
|
}
|
|
4681
|
-
return resolveSessionQuery(indexed, selector, { indexFallback: false }).matches;
|
|
4725
|
+
return resolveSessionQuery(indexed, selector, { indexFallback: false, scope }).matches;
|
|
4682
4726
|
}
|
|
4683
4727
|
/**
|
|
4684
4728
|
* ID-shaped selectors go straight to SQLite's primary-key/short-id lookup.
|
|
@@ -4789,7 +4833,7 @@ export async function computeLocalMetadataMatches(selector, scope, deps = {}) {
|
|
|
4789
4833
|
const includeLocal = !scope.hosts?.length || shouldIncludeLocal(scope.hosts, localMachine);
|
|
4790
4834
|
if (!includeLocal)
|
|
4791
4835
|
return [];
|
|
4792
|
-
const indexed = resolveIndexedMetadataRows(indexedRowsForSelector(selector, scope), selector);
|
|
4836
|
+
const indexed = resolveIndexedMetadataRows(indexedRowsForSelector(selector, scope), selector, scope);
|
|
4793
4837
|
if (indexed.length > 0 || !looksLikeSessionId(selector)) {
|
|
4794
4838
|
return indexed.map(session => ({ ...session, machine: session.machine || localMachine }));
|
|
4795
4839
|
}
|
|
@@ -4806,7 +4850,7 @@ export async function computeLocalMetadataMatches(selector, scope, deps = {}) {
|
|
|
4806
4850
|
*/
|
|
4807
4851
|
export async function liveMetadataMatches(selector, scope, self, deps = {}) {
|
|
4808
4852
|
const load = deps.loadActive ?? loadLocalActiveSessions;
|
|
4809
|
-
const match = (metas) => resolveIndexedMetadataRows(applyScopeFilters(metas, scope), selector);
|
|
4853
|
+
const match = (metas) => resolveIndexedMetadataRows(applyScopeFilters(metas, scope), selector, scope);
|
|
4810
4854
|
try {
|
|
4811
4855
|
const cached = liveSessionMetas((await load()).sessions, self, Date.now());
|
|
4812
4856
|
const hit = match(cached);
|
|
@@ -5023,7 +5067,7 @@ export function registerSessionsCommands(program) {
|
|
|
5023
5067
|
.option('--preview', 'With a session id/query: print a compact preview and exit (no pager)');
|
|
5024
5068
|
setHelpSections(sessionsCmd, {
|
|
5025
5069
|
examples: `
|
|
5026
|
-
# Search
|
|
5070
|
+
# Search indexed transcripts by topic, file path, or command
|
|
5027
5071
|
agents sessions "add auth middleware"
|
|
5028
5072
|
|
|
5029
5073
|
# Read a session as markdown (user + assistant + thinking + tools)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agents setup accounts` — mint a Claude setup-token so unattended usage/probe
|
|
3
|
+
* is not stuck on "usage pending". Delegates to the same mint engine as
|
|
4
|
+
* `agents accounts mint` / `agents auth mint`.
|
|
5
|
+
*/
|
|
6
|
+
import type { Command } from 'commander';
|
|
7
|
+
export declare function runAccountsSetupWizard(): Promise<void>;
|
|
8
|
+
export declare function registerSetupAccountsCommand(setupCmd: Command): void;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { confirm } from '@inquirer/prompts';
|
|
3
|
+
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
4
|
+
import { hasMintedSetupToken, mintAndSeed } from '../lib/auth-mint.js';
|
|
5
|
+
export async function runAccountsSetupWizard() {
|
|
6
|
+
const status = hasMintedSetupToken();
|
|
7
|
+
if (status.ready) {
|
|
8
|
+
console.log(chalk.green(`Setup-token already minted (${status.detail}).`));
|
|
9
|
+
console.log(chalk.gray('Re-mint with: agents accounts mint claude'));
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (!isInteractiveTerminal()) {
|
|
13
|
+
console.log(chalk.yellow('No Claude setup-token on this machine.'));
|
|
14
|
+
console.log(chalk.gray('Mint one with: agents accounts mint claude'));
|
|
15
|
+
console.log(chalk.gray('Already have a token: agents accounts mint claude --token-stdin'));
|
|
16
|
+
process.exitCode = 1;
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const proceed = await confirm({
|
|
20
|
+
message: 'Mint a Claude setup-token now? (opens a browser; seeds a named account + the reserved auth bundle)',
|
|
21
|
+
default: true,
|
|
22
|
+
});
|
|
23
|
+
if (!proceed) {
|
|
24
|
+
console.log(chalk.gray('Skipped. Run `agents accounts mint claude` when ready.'));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const result = await mintAndSeed({ harness: 'claude' });
|
|
28
|
+
console.log(chalk.green(`Minted account '${result.account}' for ${result.email}.`));
|
|
29
|
+
}
|
|
30
|
+
export function registerSetupAccountsCommand(setupCmd) {
|
|
31
|
+
setupCmd
|
|
32
|
+
.command('accounts')
|
|
33
|
+
.description('Mint a Claude setup-token into a named account (unattended usage/probe).')
|
|
34
|
+
.action(async () => {
|
|
35
|
+
try {
|
|
36
|
+
await runAccountsSetupWizard();
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
if (isPromptCancelled(err)) {
|
|
40
|
+
console.log(chalk.yellow('\nCancelled'));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
console.error(chalk.red(err instanceof Error ? err.message : String(err)));
|
|
44
|
+
process.exitCode = 1;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
package/dist/commands/setup.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export declare function ensureInitialized(program: Command): Promise<void>;
|
|
|
31
31
|
* wizard. Never throws — a cancel or an optional wizard's error just skips the
|
|
32
32
|
* rest and lets core setup complete.
|
|
33
33
|
*/
|
|
34
|
-
export type SetupPhase = 'browser' | 'computer' | 'share' | 'secrets' | 'fleet' | 'watchdog' | 'preferences';
|
|
34
|
+
export type SetupPhase = 'browser' | 'computer' | 'share' | 'secrets' | 'accounts' | 'fleet' | 'watchdog' | 'preferences';
|
|
35
35
|
export type SetupStatusState = 'ready' | 'missing' | 'n/a';
|
|
36
36
|
export interface SetupStatusRow {
|
|
37
37
|
phase: 'core' | SetupPhase;
|
package/dist/commands/setup.js
CHANGED
|
@@ -24,7 +24,9 @@ import { runShareWizard } from './artifacts-setup.js';
|
|
|
24
24
|
import { registerSetupMineCommand } from './setup-mine.js';
|
|
25
25
|
import { registerSetupSecretsCommand } from './setup-secrets.js';
|
|
26
26
|
import { registerSetupFleetCommand } from './setup-fleet.js';
|
|
27
|
+
import { registerSetupAccountsCommand, runAccountsSetupWizard } from './setup-accounts.js';
|
|
27
28
|
import { registerSetupWatchdogCommand, runWatchdogSetupWizard } from './setup-watchdog.js';
|
|
29
|
+
import { hasMintedSetupToken } from '../lib/auth-mint.js';
|
|
28
30
|
import { registerAliasCommand } from './alias.js';
|
|
29
31
|
import { registerBetaCommands } from './beta.js';
|
|
30
32
|
import { runPreferencesStep } from './setup-preferences.js';
|
|
@@ -276,6 +278,7 @@ export async function getSetupStatus() {
|
|
|
276
278
|
const devices = await loadDevices();
|
|
277
279
|
const coreReady = isGitRepo(getAgentsDir());
|
|
278
280
|
const secretsReady = fs.existsSync(setupSecretsPrefsPath());
|
|
281
|
+
const minted = hasMintedSetupToken();
|
|
279
282
|
const shareConfig = readShareConfig();
|
|
280
283
|
const watchdogEnabled = getConfigValue('watchdog.enabled').value === true;
|
|
281
284
|
const interactiveHost = getConfigValue('interactive.host').value;
|
|
@@ -285,6 +288,7 @@ export async function getSetupStatus() {
|
|
|
285
288
|
{ phase: 'browser', state: browserReady ? 'ready' : 'missing', detail: browserReady ? `profile ${browserProfile.name}` : browserProfile ? `profile ${browserProfile.name} cannot launch here` : installedBrowsers.length ? 'no default profile' : 'no supported browser found' },
|
|
286
289
|
{ phase: 'computer', state: computerState, detail: computerState === 'ready' ? 'helper trusted' : computerState === 'n/a' ? 'macOS local setup only' : 'helper not running or not trusted' },
|
|
287
290
|
{ phase: 'secrets', state: secretsReady ? 'ready' : 'missing', detail: secretsReady ? 'defaults chosen' : 'defaults not chosen' },
|
|
291
|
+
{ phase: 'accounts', state: minted.ready ? 'ready' : 'missing', detail: minted.detail },
|
|
288
292
|
{ phase: 'fleet', state: Object.keys(devices).length ? 'ready' : 'missing', detail: Object.keys(devices).length ? `${Object.keys(devices).length} device${Object.keys(devices).length === 1 ? '' : 's'} registered` : 'no devices registered' },
|
|
289
293
|
{ phase: 'share', state: shareConfig ? 'ready' : 'missing', detail: shareConfig?.baseUrl ?? 'endpoint not configured' },
|
|
290
294
|
{ phase: 'watchdog', state: watchdogEnabled ? 'ready' : 'missing', detail: watchdogEnabled ? 'enabled on this device' : 'disabled on this device' },
|
|
@@ -308,6 +312,8 @@ async function runSetupPhase(phase) {
|
|
|
308
312
|
await runShareWizard();
|
|
309
313
|
else if (phase === 'secrets')
|
|
310
314
|
await import('./setup-secrets.js').then((m) => m.runSecretsSetupWizard());
|
|
315
|
+
else if (phase === 'accounts')
|
|
316
|
+
await runAccountsSetupWizard();
|
|
311
317
|
else if (phase === 'fleet')
|
|
312
318
|
await import('./setup-fleet.js').then((m) => m.runFleetSetupWizard());
|
|
313
319
|
else if (phase === 'watchdog')
|
|
@@ -364,19 +370,20 @@ export function registerSetupCommand(program) {
|
|
|
364
370
|
.description('Set up agents-cli, or re-open the capability onboarding hub.')
|
|
365
371
|
.option('-f, --force', 'Re-run setup even if ~/.agents/.system/ already exists (use with caution)')
|
|
366
372
|
.option('--no-system-repo', 'Skip cloning the system repo (you must populate ~/.agents/.system/ yourself)');
|
|
367
|
-
// Capability subcommands: `agents setup browser|computer|mine|secrets|fleet|alias|beta`.
|
|
373
|
+
// Capability subcommands: `agents setup browser|computer|mine|secrets|accounts|fleet|alias|beta`.
|
|
368
374
|
// Share/artifact publishing is set up by `agents artifacts setup` (RUSH-2580);
|
|
369
375
|
// the hub below still offers it as a phase via runShareWizard.
|
|
370
376
|
registerSetupBrowserCommand(setupCmd);
|
|
371
377
|
registerSetupComputerCommand(setupCmd);
|
|
372
378
|
registerSetupMineCommand(setupCmd);
|
|
373
379
|
registerSetupSecretsCommand(setupCmd);
|
|
380
|
+
registerSetupAccountsCommand(setupCmd);
|
|
374
381
|
registerSetupFleetCommand(setupCmd);
|
|
375
382
|
registerSetupWatchdogCommand(setupCmd);
|
|
376
383
|
registerAliasCommand(setupCmd);
|
|
377
384
|
registerBetaCommands(setupCmd);
|
|
378
385
|
setupCmd.command('status')
|
|
379
|
-
.description('Show setup readiness for core, browser, computer, secrets, fleet, share, watchdog, and preferences.')
|
|
386
|
+
.description('Show setup readiness for core, browser, computer, secrets, accounts, fleet, share, watchdog, and preferences.')
|
|
380
387
|
.option('--json', 'print machine-readable JSON')
|
|
381
388
|
.action(async (options) => {
|
|
382
389
|
const rows = await getSetupStatus();
|
|
@@ -399,6 +406,7 @@ export function registerSetupCommand(program) {
|
|
|
399
406
|
agents setup browser
|
|
400
407
|
agents setup computer
|
|
401
408
|
agents setup secrets
|
|
409
|
+
agents setup accounts
|
|
402
410
|
agents setup fleet
|
|
403
411
|
agents setup watchdog
|
|
404
412
|
agents setup alias add teams
|
|
@@ -416,6 +424,7 @@ export function registerSetupCommand(program) {
|
|
|
416
424
|
agents setup computer # install the signed macOS helper + grant permissions
|
|
417
425
|
agents artifacts setup # provision or join a Cloudflare share endpoint
|
|
418
426
|
agents setup secrets # choose secrets backend/policy defaults + import
|
|
427
|
+
agents setup accounts # mint a Claude setup-token for unattended usage/probe
|
|
419
428
|
agents setup fleet # discover Tailscale devices + configure SSH access
|
|
420
429
|
agents setup watchdog # choose which devices run the daemon watchdog pass
|
|
421
430
|
agents setup alias add teams # PATH shorthand: teams runs agents teams
|