@phnx-labs/agents-cli 1.22.107 → 1.22.110
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 +96 -0
- package/dist/bootstrap.js +3 -2
- package/dist/commands/browser-sessions-picker.js +2 -1
- package/dist/commands/computer-sessions-picker.js +2 -1
- package/dist/commands/cost.js +2 -1
- package/dist/commands/daemon.js +45 -40
- package/dist/commands/doctor.js +7 -0
- package/dist/commands/exec.js +4 -2
- package/dist/commands/fork.js +6 -4
- package/dist/commands/logs.js +2 -1
- package/dist/commands/monitors.js +15 -2
- package/dist/commands/routines.js +46 -6
- package/dist/commands/sessions-backfill.d.ts +30 -0
- package/dist/commands/sessions-backfill.js +72 -0
- package/dist/commands/sessions-backup-setup.d.ts +12 -0
- package/dist/commands/sessions-backup-setup.js +65 -0
- package/dist/commands/sessions-inject.d.ts +3 -3
- package/dist/commands/sessions-inject.js +5 -11
- package/dist/commands/sessions-picker.js +10 -7
- package/dist/commands/sessions-resume.d.ts +17 -2
- package/dist/commands/sessions-resume.js +88 -12
- package/dist/commands/sessions.js +46 -24
- package/dist/commands/share.js +15 -2
- package/dist/commands/sync.js +30 -1
- package/dist/commands/utils.d.ts +9 -0
- package/dist/commands/utils.js +18 -0
- package/dist/commands/watchdog.js +4 -1
- package/dist/lib/accounting/rotate.d.ts +9 -0
- package/dist/lib/accounting/rotate.js +17 -1
- package/dist/lib/accounting/usage-sync.d.ts +22 -0
- package/dist/lib/accounting/usage-sync.js +70 -6
- package/dist/lib/accounting/usage.d.ts +32 -0
- package/dist/lib/accounting/usage.js +37 -2
- package/dist/lib/accounts/slots.js +7 -0
- package/dist/lib/agent-spec/agents.d.ts +60 -8
- package/dist/lib/agent-spec/agents.js +118 -45
- package/dist/lib/auth-health.js +19 -0
- package/dist/lib/claude-statusline.js +5 -0
- package/dist/lib/computer/sessions-list.js +2 -1
- package/dist/lib/daemon/auth-sync-service.d.ts +2 -0
- package/dist/lib/daemon/auth-sync-service.js +2 -2
- package/dist/lib/daemon/daemon.d.ts +34 -1
- package/dist/lib/daemon/daemon.js +70 -9
- package/dist/lib/daemon/leaked-daemons.d.ts +60 -0
- package/dist/lib/daemon/leaked-daemons.js +180 -0
- package/dist/lib/daemon/session-title-service.d.ts +41 -0
- package/dist/lib/daemon/session-title-service.js +76 -0
- package/dist/lib/daemon/usage-sync-service.d.ts +9 -1
- package/dist/lib/daemon/usage-sync-service.js +10 -2
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +5 -0
- package/dist/lib/daemon-ticks.js +6 -0
- package/dist/lib/devices/doctor-findings.d.ts +7 -1
- package/dist/lib/devices/doctor-findings.js +32 -1
- package/dist/lib/fleet-shared-state.d.ts +2 -0
- package/dist/lib/hooks/install.js +78 -64
- package/dist/lib/mailbox-target.js +2 -1
- package/dist/lib/models.js +76 -5
- package/dist/lib/session/active.d.ts +93 -16
- package/dist/lib/session/active.js +80 -14
- package/dist/lib/session/cloud.js +3 -1
- package/dist/lib/session/db.d.ts +44 -1
- package/dist/lib/session/db.js +122 -17
- package/dist/lib/session/fork.d.ts +10 -2
- package/dist/lib/session/fork.js +11 -2
- package/dist/lib/session/live-metadata.js +6 -0
- package/dist/lib/session/mirror.d.ts +3 -2
- package/dist/lib/session/mirror.js +5 -2
- package/dist/lib/session/parse.d.ts +1 -0
- package/dist/lib/session/parse.js +136 -2
- package/dist/lib/session/recovery.d.ts +9 -1
- package/dist/lib/session/recovery.js +14 -4
- package/dist/lib/session/remote/watch.js +18 -8
- package/dist/lib/session/title.d.ts +256 -0
- package/dist/lib/session/title.js +312 -0
- package/dist/lib/session/tool-calls.d.ts +1 -1
- package/dist/lib/session/tool-calls.js +40 -5
- package/dist/lib/session/tool-index.d.ts +5 -0
- package/dist/lib/session/tool-index.js +1 -0
- package/dist/lib/session/types.d.ts +11 -0
- package/dist/lib/share/backend.d.ts +23 -0
- package/dist/lib/share/backend.js +24 -0
- package/dist/lib/share/provision.d.ts +12 -0
- package/dist/lib/share/provision.js +30 -0
- package/dist/lib/share/worker-template.js +273 -0
- package/dist/lib/startup/root-command.d.ts +2 -0
- package/dist/lib/startup/root-command.js +22 -0
- package/dist/lib/terminal/engine.js +13 -1
- package/dist/lib/traces/sync.js +1 -0
- package/dist/lib/usage-refresh.d.ts +58 -7
- package/dist/lib/usage-refresh.js +145 -23
- package/dist/lib/watchdog/runner.d.ts +3 -0
- package/dist/lib/watchdog/runner.js +1 -0
- package/dist/session-tracker/dist/install-hook.js +4 -4
- package/package.json +1 -1
|
@@ -37,6 +37,7 @@ import { inferSessionState } from '../lib/session/state.js';
|
|
|
37
37
|
import { discoverSessions, queryIndexedSessions, countSessionsInScope, resolveSessionById, isCompleteSessionId, looksLikeSessionId, searchContentIndex, getSessionRoots, scopeToManaged } from '../lib/session/discover.js';
|
|
38
38
|
import { findSessionsById, querySessions, readSessionContent, readArchivedSessionPreview } from '../lib/session/db.js';
|
|
39
39
|
import { liveSessionMetas, fleetExecutionMachineById, reconcileLiveMetaMachine } from '../lib/session/live-metadata.js';
|
|
40
|
+
import { sessionHeadline } from '../lib/session/title.js';
|
|
40
41
|
import { filterTeamSessions, shouldShowTeamSessions, safeTeamText, groupSessionsByTeam, NO_TEAM_GROUP_KEY, } from '../lib/session/team-filter.js';
|
|
41
42
|
import { parseSession } from '../lib/session/parse.js';
|
|
42
43
|
import { runRemoteSessions, buildForwardedArgs, ensureWholeIndex } from '../lib/session/remote.js';
|
|
@@ -75,6 +76,7 @@ import { registerSessionsRenderCommand } from './sessions-render.js';
|
|
|
75
76
|
import { registerSessionsTraceCommand } from './sessions-trace.js';
|
|
76
77
|
import { registerSessionsShareCommand } from './sessions-share.js';
|
|
77
78
|
import { registerSessionsImportCommand } from './sessions-import.js';
|
|
79
|
+
import { registerSessionsBackupSetupCommand } from './sessions-backup-setup.js';
|
|
78
80
|
import { registerSessionsMigrateCommand, registerSessionsMigrationsCommand } from './sessions-migrate.js';
|
|
79
81
|
import { registerSessionsBackfillCommand } from './sessions-backfill.js';
|
|
80
82
|
import { registerSessionsStatsCommand } from './sessions-stats.js';
|
|
@@ -350,6 +352,7 @@ export function buildSessionDescription(s) {
|
|
|
350
352
|
return cleanPreview(parts.filter(Boolean).join(' · '));
|
|
351
353
|
}
|
|
352
354
|
// Terminal, headless, or sub-agent: todos + live preview, then label, then topic.
|
|
355
|
+
// ladder-exempt: the compact --active preview BASE (a live snippet), not the row's headline title.
|
|
353
356
|
const base = s.preview || s.label || s.topic || '';
|
|
354
357
|
return cleanPreview([todo, base].filter(Boolean).join(' · '));
|
|
355
358
|
}
|
|
@@ -758,22 +761,39 @@ export function renderActiveRowLines(s, indent, termW) {
|
|
|
758
761
|
// Line 2: label/topic + checklist (the identity, no longer buried) then the
|
|
759
762
|
// jump locator. Skipped entirely when there is nothing to say. desc may carry a
|
|
760
763
|
// clickable project link, so it also goes through fitCell.
|
|
764
|
+
const contIndent = indent + ' '.repeat(ROW_ID_W);
|
|
761
765
|
const desc = formatActiveRowDescription(s);
|
|
762
766
|
const loc = locatorBadge(s);
|
|
763
|
-
if (
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
767
|
+
if (desc || loc) {
|
|
768
|
+
const room2 = Math.max(0, termW - stringWidth(contIndent) - 2);
|
|
769
|
+
const locCell = fitCell(loc, room2);
|
|
770
|
+
const locW = stringWidth(locCell);
|
|
771
|
+
const descRoom = Math.max(0, room2 - (locW ? locW + 2 : 0));
|
|
772
|
+
const descCell = chalk.white(fitCell(desc || '-', descRoom));
|
|
773
|
+
let line2 = contIndent + chalk.dim('└ ') + descCell;
|
|
774
|
+
if (locCell)
|
|
775
|
+
line2 += ' ' + locCell;
|
|
776
|
+
if (stringWidth(line2) > termW)
|
|
777
|
+
line2 = truncateToWidth(line2, termW);
|
|
778
|
+
lines.push(line2);
|
|
779
|
+
}
|
|
780
|
+
// Secondary line (PHNX-3797 owner feedback): surface the most important recent
|
|
781
|
+
// agent message when it is a pending QUESTION or a NEEDS-YOU block — the urgency
|
|
782
|
+
// a rolling activity preview buries. Plain `activity` is already the preview on
|
|
783
|
+
// line 2, so it earns no extra line here; the full ranked message still rides
|
|
784
|
+
// the JSON/mirror feed for AGI EXT via `s.importantMessage`.
|
|
785
|
+
const important = s.importantMessage;
|
|
786
|
+
if (important && (important.kind === 'question' || important.kind === 'needs_you')) {
|
|
787
|
+
const glyph = important.kind === 'question' ? '? ' : '! ';
|
|
788
|
+
const room3 = Math.max(0, termW - stringWidth(contIndent) - 2 - glyph.length);
|
|
789
|
+
const msgCell = fitCell(cleanPreview(important.text), room3);
|
|
790
|
+
if (msgCell) {
|
|
791
|
+
let line3 = contIndent + chalk.dim(glyph + msgCell);
|
|
792
|
+
if (stringWidth(line3) > termW)
|
|
793
|
+
line3 = truncateToWidth(line3, termW);
|
|
794
|
+
lines.push(line3);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
777
797
|
return lines;
|
|
778
798
|
}
|
|
779
799
|
/** Render a single agent-session row inside an already-printed group header. */
|
|
@@ -2199,7 +2219,7 @@ export function printToolSearch(envelope) {
|
|
|
2199
2219
|
? truncate(sanitizeForTerminal(session.machine).replace(/\s+/g, ' '), 80)
|
|
2200
2220
|
: '';
|
|
2201
2221
|
const machine = machineName ? ` @ ${machineName}` : '';
|
|
2202
|
-
const rawHeading = session
|
|
2222
|
+
const rawHeading = sessionHeadline(session) || session.project || session.shortId;
|
|
2203
2223
|
const heading = truncate(sanitizeForTerminal(rawHeading).replace(/\s+/g, ' '), Math.max(30, terminalWidth() - 20));
|
|
2204
2224
|
console.log(`${chalk.cyan(session.shortId)}${chalk.gray(machine)} ${heading}`);
|
|
2205
2225
|
for (const call of session.calls) {
|
|
@@ -3373,7 +3393,7 @@ function printTeamsView(pool, liveIndex, hiddenUnmanaged = 0) {
|
|
|
3373
3393
|
// degradation the --active teams rows use (active.ts resolveOrchestratorLabels).
|
|
3374
3394
|
const labelById = new Map();
|
|
3375
3395
|
for (const s of pool) {
|
|
3376
|
-
const label = s
|
|
3396
|
+
const label = sessionHeadline(s);
|
|
3377
3397
|
if (label)
|
|
3378
3398
|
labelById.set(s.id, cleanPreview(label));
|
|
3379
3399
|
}
|
|
@@ -3503,7 +3523,7 @@ function renderArchivedSession(session, mode, options = {}) {
|
|
|
3503
3523
|
const shown = sessionDisplayAgent(session);
|
|
3504
3524
|
const agentColor = colorAgent(shown);
|
|
3505
3525
|
const absTime = formatAbsoluteTime(session.timestamp);
|
|
3506
|
-
const title = session
|
|
3526
|
+
const title = sessionHeadline(session);
|
|
3507
3527
|
console.log('');
|
|
3508
3528
|
if (title)
|
|
3509
3529
|
console.log(chalk.bold.white(title));
|
|
@@ -3561,9 +3581,10 @@ async function renderSession(session, mode, filters, options = {}) {
|
|
|
3561
3581
|
const modelStr = stats.models.length > 0 ? chalk.yellow(` ${stats.models.join(', ')}`) : '';
|
|
3562
3582
|
const branchStr = session.gitBranch ? chalk.gray(` (${session.gitBranch})`) : '';
|
|
3563
3583
|
const absTime = formatAbsoluteTime(session.timestamp);
|
|
3564
|
-
// Auto-inferred title headline (user /rename > Claude ai-title >
|
|
3565
|
-
// topic) — the fastest way to
|
|
3566
|
-
|
|
3584
|
+
// Auto-inferred title headline (user /rename > Claude ai-title >
|
|
3585
|
+
// daemon-generated title > first-prompt topic) — the fastest way to
|
|
3586
|
+
// recognize which task this session is.
|
|
3587
|
+
const title = sessionHeadline(session);
|
|
3567
3588
|
if (title) {
|
|
3568
3589
|
const badges = signalBadges(metaSignals(session));
|
|
3569
3590
|
console.log(chalk.bold.white(title) + (badges ? ' ' + badges : ''));
|
|
@@ -4455,7 +4476,7 @@ async function renderArtifactsGlobal(query, listAll, name, scope) {
|
|
|
4455
4476
|
spinner.stop();
|
|
4456
4477
|
console.error(chalk.red(`Multiple sessions match "${query}":`));
|
|
4457
4478
|
for (const m of queryMatches.slice(0, 10)) {
|
|
4458
|
-
console.error(chalk.cyan(` ${m.shortId} ${m.id} ${m
|
|
4479
|
+
console.error(chalk.cyan(` ${m.shortId} ${m.id} ${sessionHeadline(m) ?? ''}`));
|
|
4459
4480
|
}
|
|
4460
4481
|
console.error(chalk.gray(ambiguityHint(byId, completeId)));
|
|
4461
4482
|
process.exit(1);
|
|
@@ -4612,7 +4633,7 @@ async function renderOneSession(query, mode, scope) {
|
|
|
4612
4633
|
spinner.stop();
|
|
4613
4634
|
console.error(chalk.red(`Multiple sessions match "${query}":`));
|
|
4614
4635
|
for (const match of queryMatches.slice(0, 10)) {
|
|
4615
|
-
console.error(chalk.cyan(` ${match.shortId} ${match.id} ${match
|
|
4636
|
+
console.error(chalk.cyan(` ${match.shortId} ${match.id} ${sessionHeadline(match) ?? ''}`));
|
|
4616
4637
|
}
|
|
4617
4638
|
console.error(chalk.gray(ambiguityHint(byId, completeId)));
|
|
4618
4639
|
process.exit(1);
|
|
@@ -5066,7 +5087,7 @@ export async function resolveSessionMetadata(selector, scope, deps = { gatherRem
|
|
|
5066
5087
|
for (const candidate of outcome.candidates) {
|
|
5067
5088
|
const session = candidate.hits[0].session;
|
|
5068
5089
|
const machines = candidate.hits.map(hit => hit.machine).join(', ');
|
|
5069
|
-
console.error(chalk.cyan(` ${session.shortId} ${session.id}`) + chalk.gray(` ${machines} ${session
|
|
5090
|
+
console.error(chalk.cyan(` ${session.shortId} ${session.id}`) + chalk.gray(` ${machines} ${sessionHeadline(session) ?? ''}`));
|
|
5070
5091
|
}
|
|
5071
5092
|
console.error(chalk.gray(looksLikeSessionId(selector) ? 'Pass a longer ID to narrow it down.' : 'Narrow the keywords to one session.'));
|
|
5072
5093
|
process.exit(1);
|
|
@@ -5106,7 +5127,7 @@ export async function resolveSessionAcrossFleet(query, mode, hosts, deps = { gat
|
|
|
5106
5127
|
console.error(chalk.red(`Multiple sessions match "${query}" across the fleet:`));
|
|
5107
5128
|
for (const candidate of candidates) {
|
|
5108
5129
|
const s = candidate.hits[0].session;
|
|
5109
|
-
const label = s
|
|
5130
|
+
const label = sessionHeadline(s) ?? '';
|
|
5110
5131
|
const machines = candidate.hits.map(hit => hit.machine).join(', ');
|
|
5111
5132
|
console.error(chalk.cyan(` ${s.shortId} ${s.id}`) + chalk.gray(` ${machines} ${s.agent}${s.version ? ` ${s.version}` : ''} ${label}`));
|
|
5112
5133
|
}
|
|
@@ -5387,6 +5408,7 @@ export function registerSessionsCommands(program) {
|
|
|
5387
5408
|
registerSessionsTraceCommand(sessionsCmd);
|
|
5388
5409
|
registerSessionsShareCommand(sessionsCmd);
|
|
5389
5410
|
registerSessionsImportCommand(sessionsCmd);
|
|
5411
|
+
registerSessionsBackupSetupCommand(sessionsCmd);
|
|
5390
5412
|
registerSessionsMigrateCommand(sessionsCmd);
|
|
5391
5413
|
registerSessionsMigrationsCommand(sessionsCmd);
|
|
5392
5414
|
registerSessionsBackfillCommand(sessionsCmd);
|
package/dist/commands/share.js
CHANGED
|
@@ -10,13 +10,13 @@ import { formatBytes } from '../lib/format.js';
|
|
|
10
10
|
import { Argument, Option } from 'commander';
|
|
11
11
|
import chalk from 'chalk';
|
|
12
12
|
import { DEFAULT_BUCKET_NAME, DEFAULT_CF_BUNDLE, DEFAULT_SHARE_DOMAIN, DEFAULT_WORKER_NAME, generateWriteToken, readCloudflareCreds, readShareConfig, readWriteToken, readWriteTokenEnv, readWriteTokenFromBundle, storeWriteToken, writeShareConfig, } from '../lib/share/config.js';
|
|
13
|
-
import { addCustomDomain, configureBucketLifecycle, createBucket, deployWorker, enableWorkersDev, findZoneId, hashWorkerScript, putWorkerSecret, updateWorker, WORKER_PHOENIX_ID_BASE_SECRET, setWorkerSecret, } from '../lib/share/provision.js';
|
|
13
|
+
import { addCustomDomain, configureBucketLifecycle, createBucket, deployWorker, enableWorkersDev, findZoneId, hashWorkerScript, putWorkerSecret, updateWorker, WORKER_PHOENIX_ID_BASE_SECRET, WORKER_COLLAB_BASE_SECRET, WORKER_COLLAB_SERVICE_TOKEN_SECRET, setWorkerSecret, } from '../lib/share/provision.js';
|
|
14
14
|
import { publishFile, resolveShareUsername, parseMetaEntries, sanitizeLabel, scanShareContent, formatSensitiveContentError, unlistedNotPrivateWarning, SHARE_VISIBILITY_LEVELS, PUBLISH_VISIBILITY_LEVELS, } from '../lib/share/publish.js';
|
|
15
15
|
import { deleteShare, resolveDeleteTarget } from '../lib/share/delete.js';
|
|
16
16
|
import { renderWorkerBundle } from '../lib/share/worker-template.js';
|
|
17
17
|
import { analyticsEnabled } from '../lib/share/analytics.js';
|
|
18
18
|
import { extractShareHttpError, formatShareHttpErrorDetail } from '../lib/share/http-error.js';
|
|
19
|
-
import { phoenixIdBaseForDeploy, resolveShareBackend, shouldUseManaged, } from '../lib/share/backend.js';
|
|
19
|
+
import { collabConfigForDeploy, phoenixIdBaseForDeploy, resolveShareBackend, shouldUseManaged, } from '../lib/share/backend.js';
|
|
20
20
|
import { publishVisibility } from '../lib/storage/visibility.js';
|
|
21
21
|
import { resolveGitHubUsername } from '../lib/git.js';
|
|
22
22
|
import { refreshSessionAvatar } from '../lib/identity/index.js';
|
|
@@ -1271,6 +1271,15 @@ export async function runShareProvision(opts) {
|
|
|
1271
1271
|
await putWorkerSecret(apiToken, accountId, workerName, WORKER_PHOENIX_ID_BASE_SECRET, phoenixIdBase, provisionOpts);
|
|
1272
1272
|
spin.text = `Worker '${workerName}' Phoenix ID base set`;
|
|
1273
1273
|
}
|
|
1274
|
+
// Managed collaboration backend (PHNX-3835). Dormant unless BOTH
|
|
1275
|
+
// PRIX_ARTIFACT_COLLAB_BASE and ARTIFACT_COLLAB_SERVICE_TOKEN are set in the
|
|
1276
|
+
// deploy env; the service token rides the Secrets API, never rendered HTML.
|
|
1277
|
+
const collab = collabConfigForDeploy({ managed: opts.managed }, { baseUrl, domain });
|
|
1278
|
+
if (collab.collabBase && collab.collabServiceToken) {
|
|
1279
|
+
await putWorkerSecret(apiToken, accountId, workerName, WORKER_COLLAB_BASE_SECRET, collab.collabBase, provisionOpts);
|
|
1280
|
+
await putWorkerSecret(apiToken, accountId, workerName, WORKER_COLLAB_SERVICE_TOKEN_SECRET, collab.collabServiceToken, provisionOpts);
|
|
1281
|
+
spin.text = `Worker '${workerName}' collaboration backend set`;
|
|
1282
|
+
}
|
|
1274
1283
|
spin.succeed('Provisioned');
|
|
1275
1284
|
const cfg = {
|
|
1276
1285
|
baseUrl,
|
|
@@ -1336,10 +1345,14 @@ export async function runShareUpdate(opts = {}) {
|
|
|
1336
1345
|
}
|
|
1337
1346
|
const writeToken = readWriteToken();
|
|
1338
1347
|
const phoenixIdBase = phoenixIdBaseForDeploy({ managed: opts.managed }, cfg);
|
|
1348
|
+
const collab = collabConfigForDeploy({ managed: opts.managed }, cfg);
|
|
1339
1349
|
const provisionOpts = {
|
|
1340
1350
|
...(opts.request ? { request: opts.request } : {}),
|
|
1341
1351
|
force: opts.force,
|
|
1342
1352
|
...(phoenixIdBase !== undefined ? { phoenixIdBase } : {}),
|
|
1353
|
+
...(collab.collabBase !== undefined
|
|
1354
|
+
? { collabBase: collab.collabBase, collabServiceToken: collab.collabServiceToken }
|
|
1355
|
+
: {}),
|
|
1343
1356
|
};
|
|
1344
1357
|
const result = await updateWorker(apiToken, accountId, cfg.workerName, cfg.bucketName, worker, writeToken, cfg.templateHash, provisionOpts);
|
|
1345
1358
|
if (!result.skipped) {
|
package/dist/commands/sync.js
CHANGED
|
@@ -190,7 +190,7 @@ export function registerSyncCommand(program) {
|
|
|
190
190
|
.option('-y, --yes', 'Skip the interactive preview and auto-sync all detected resources', false)
|
|
191
191
|
.option('--force', 'Re-sync even if no changes are detected since the last sync', false)
|
|
192
192
|
.option('--quiet', 'Suppress all output (exit code indicates success)', false)
|
|
193
|
-
.option('--dry-run', 'Show what would be synced without making any changes', false)
|
|
193
|
+
.option('--dry-run', 'Show what would be synced without making any changes — requires an agent scope (e.g. agents sync claude --dry-run); the umbrella verb refuses it', false)
|
|
194
194
|
.option('--allow-exec-surfaces', 'Allow syncing plugin exec surfaces (scripts, binaries) — off by default for safety', false)
|
|
195
195
|
.option('--json', 'Emit machine-readable JSON (also accepted so fleet fan-out via --device all can parse each peer)', false)
|
|
196
196
|
// Umbrella verb (no agent given): make this machine current.
|
|
@@ -480,6 +480,35 @@ function evictCentralBrowserProfilesForSync(quiet, json, outLog, errLog) {
|
|
|
480
480
|
* a one-line summary. Stage failures are non-fatal and surfaced as warnings.
|
|
481
481
|
*/
|
|
482
482
|
async function runUmbrella(opts, quiet, outLog, errLog, json = false) {
|
|
483
|
+
// `--dry-run` on the umbrella verb is NOT supported and must fail LOUD before
|
|
484
|
+
// touching anything (PHNX-3923). The umbrella composes stages that only exist
|
|
485
|
+
// as mutating operations — repo `git pull`, a full `refresh()` reconcile into
|
|
486
|
+
// every installed version home, central browser-profile eviction, device sync,
|
|
487
|
+
// and `repairAfterSync` — none of which carry a non-mutating preview mode. The
|
|
488
|
+
// old code ignored `opts.dryRun` entirely, ran `runUmbrellaSync` + evict +
|
|
489
|
+
// repair, and so MUTATED every native home despite `--dry-run`. Rather than
|
|
490
|
+
// ship a partial preview that silently skips the stages it cannot model (a
|
|
491
|
+
// lying "would sync" that contradicts the flag's promise), refuse here and
|
|
492
|
+
// point at the scoped path, which DOES honor `--dry-run` non-destructively.
|
|
493
|
+
if (opts.dryRun) {
|
|
494
|
+
const installed = MANAGED_AGENT_IDS.filter((id) => listInstalledVersions(id).length > 0);
|
|
495
|
+
const example = installed[0] ?? 'claude';
|
|
496
|
+
const error = '`agents sync --dry-run` has no umbrella preview: the machine-wide sync pulls repos, ' +
|
|
497
|
+
'reconciles every installed version, syncs devices, and repairs homes — stages that ' +
|
|
498
|
+
'cannot be previewed without making changes.';
|
|
499
|
+
const hint = `Preview one agent instead (this is non-destructive): agents sync ${example} --dry-run [--repo <repo>]`;
|
|
500
|
+
if (json) {
|
|
501
|
+
emitJson({ ok: false, mode: 'umbrella', dryRun: true, error, hint, installedAgents: installed });
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
errLog(chalk.red(error));
|
|
505
|
+
errLog(chalk.gray(hint));
|
|
506
|
+
if (installed.length > 0)
|
|
507
|
+
errLog(chalk.gray(`Installed agents: ${installed.join(', ')}`));
|
|
508
|
+
}
|
|
509
|
+
process.exitCode = 1;
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
483
512
|
// Interactive bare `agents sync` (a TTY, no --yes, no scope flag) drops into
|
|
484
513
|
// the two-checklist picker: which repos to sync from, which agents to sync
|
|
485
514
|
// into. Any explicit flag, --yes, or --json keeps the non-interactive path.
|
package/dist/commands/utils.d.ts
CHANGED
|
@@ -45,6 +45,15 @@ export interface Surface {
|
|
|
45
45
|
* to a single `const s = resolveSurface(cmd)` without changing its flag surface.
|
|
46
46
|
*/
|
|
47
47
|
export declare function resolveSurface(cmd: Command): Surface;
|
|
48
|
+
/**
|
|
49
|
+
* Coerce a `--device` value that may arrive as a scalar or an array to a single
|
|
50
|
+
* host string. A subcommand whose own `--device` collides in name with an
|
|
51
|
+
* ancestor's variadic `-D, --device <target...>` (e.g. `sessions inject`,
|
|
52
|
+
* `sessions resume` under the parent `sessions` command) can receive an array
|
|
53
|
+
* even when the command only ever targets one device — fail loud on more than
|
|
54
|
+
* one rather than guessing the first (PHNX-3688, PHNX-3940).
|
|
55
|
+
*/
|
|
56
|
+
export declare function normalizeSingleDeviceOption(value: string | string[] | undefined, commandLabel: string): string | undefined;
|
|
48
57
|
/**
|
|
49
58
|
* Exit with a clean message when a picker would be required in a non-interactive shell.
|
|
50
59
|
*/
|
package/dist/commands/utils.js
CHANGED
|
@@ -59,6 +59,24 @@ export function resolveSurface(cmd) {
|
|
|
59
59
|
interactive: tty && !json,
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Coerce a `--device` value that may arrive as a scalar or an array to a single
|
|
64
|
+
* host string. A subcommand whose own `--device` collides in name with an
|
|
65
|
+
* ancestor's variadic `-D, --device <target...>` (e.g. `sessions inject`,
|
|
66
|
+
* `sessions resume` under the parent `sessions` command) can receive an array
|
|
67
|
+
* even when the command only ever targets one device — fail loud on more than
|
|
68
|
+
* one rather than guessing the first (PHNX-3688, PHNX-3940).
|
|
69
|
+
*/
|
|
70
|
+
export function normalizeSingleDeviceOption(value, commandLabel) {
|
|
71
|
+
const list = value == null ? [] : Array.isArray(value) ? value : [value];
|
|
72
|
+
const hosts = list.map((v) => String(v).trim()).filter((v) => v.length > 0);
|
|
73
|
+
if (hosts.length === 0)
|
|
74
|
+
return undefined;
|
|
75
|
+
if (hosts.length > 1) {
|
|
76
|
+
throw new Error(`${commandLabel} targets a single device, but --device named ${hosts.length}: ${hosts.join(', ')}.`);
|
|
77
|
+
}
|
|
78
|
+
return hosts[0];
|
|
79
|
+
}
|
|
62
80
|
/**
|
|
63
81
|
* Exit with a clean message when a picker would be required in a non-interactive shell.
|
|
64
82
|
*/
|
|
@@ -27,6 +27,7 @@ import { isWatchdogRotateEnabled, listRotateStates, setWatchdogRotateEnabled } f
|
|
|
27
27
|
import { loadWatchdogSessions, runWatchdogPass } from '../lib/watchdog/service.js';
|
|
28
28
|
import { readWatchdogEvents, WATCHDOG_LOG_PATH } from '../lib/watchdog/log.js';
|
|
29
29
|
import { selectWatchdogHistory } from '../lib/watchdog/history.js';
|
|
30
|
+
import { sessionHeadline } from '../lib/session/title.js';
|
|
30
31
|
/** Default state dir the runner and these subcommands share. */
|
|
31
32
|
function stateDir() {
|
|
32
33
|
return path.join(getRuntimeStateDir(), 'watchdog');
|
|
@@ -99,7 +100,9 @@ export function formatWatchdogTickLines(result, willInject, verbose = false) {
|
|
|
99
100
|
: o.decision === 'nudge' ? 'WOULD-NUDGE'
|
|
100
101
|
: 'skip';
|
|
101
102
|
const id = o.sessionId?.slice(0, 8) ?? 'no-session-id';
|
|
102
|
-
|
|
103
|
+
// `name` is the `agents run --name` launch handle — a user-given name, so it
|
|
104
|
+
// ranks with the label; everything below it is the shared headline ladder.
|
|
105
|
+
const title = o.label || o.name || sessionHeadline(o);
|
|
103
106
|
lines.push(` ${tag.padEnd(11)} ${id}${title ? ` · ${title}` : ''}`);
|
|
104
107
|
const metadata = [
|
|
105
108
|
o.kind,
|
|
@@ -178,6 +178,14 @@ export declare function isVersionLaunchableHere(agent: AgentId, version: string)
|
|
|
178
178
|
* launched into it while the account was actually at its weekly cap.
|
|
179
179
|
*/
|
|
180
180
|
export declare const USAGE_DECISION_MAX_AGE_MS: number;
|
|
181
|
+
/**
|
|
182
|
+
* How old a *synced* snapshot (arrived from the account's poller through the
|
|
183
|
+
* fleet store) may be and still settle a routing decision. Matches the
|
|
184
|
+
* usage-sync tick so a worker that only ever sees 15-minute-old rows does not
|
|
185
|
+
* drop to the account picker. Local captures (`poll` / `statusline`) keep
|
|
186
|
+
* {@link USAGE_DECISION_MAX_AGE_MS}.
|
|
187
|
+
*/
|
|
188
|
+
export declare const USAGE_SYNC_TRUST_MS: number;
|
|
181
189
|
/**
|
|
182
190
|
* How old a usage snapshot may be before routing REFUSES to run at all
|
|
183
191
|
* (NO_VERIFIED_USAGE), as opposed to merely declining to *weight* by its number.
|
|
@@ -212,6 +220,7 @@ export declare const USAGE_STALE_REFUSAL_MAX_AGE_MS: number;
|
|
|
212
220
|
* running it refreshes that log. That self-reinforcing pin is exactly what the
|
|
213
221
|
* narrowing rule below exists to prevent.
|
|
214
222
|
*/
|
|
223
|
+
export declare function usageVerifiedMaxAgeMs(snapshot: UsageSnapshot | null | undefined): number;
|
|
215
224
|
export declare function isUsageVerified(candidate: RotateCandidate, nowMs?: number): boolean;
|
|
216
225
|
/**
|
|
217
226
|
* Whether this candidate carries a GENUINELY-STALE usage number: a snapshot with
|
|
@@ -140,6 +140,14 @@ export async function isVersionLaunchableHere(agent, version) {
|
|
|
140
140
|
* launched into it while the account was actually at its weekly cap.
|
|
141
141
|
*/
|
|
142
142
|
export const USAGE_DECISION_MAX_AGE_MS = 5 * 60 * 1000;
|
|
143
|
+
/**
|
|
144
|
+
* How old a *synced* snapshot (arrived from the account's poller through the
|
|
145
|
+
* fleet store) may be and still settle a routing decision. Matches the
|
|
146
|
+
* usage-sync tick so a worker that only ever sees 15-minute-old rows does not
|
|
147
|
+
* drop to the account picker. Local captures (`poll` / `statusline`) keep
|
|
148
|
+
* {@link USAGE_DECISION_MAX_AGE_MS}.
|
|
149
|
+
*/
|
|
150
|
+
export const USAGE_SYNC_TRUST_MS = 15 * 60 * 1000;
|
|
143
151
|
/**
|
|
144
152
|
* How old a usage snapshot may be before routing REFUSES to run at all
|
|
145
153
|
* (NO_VERIFIED_USAGE), as opposed to merely declining to *weight* by its number.
|
|
@@ -174,12 +182,18 @@ export const USAGE_STALE_REFUSAL_MAX_AGE_MS = 40 * 60 * 1000;
|
|
|
174
182
|
* running it refreshes that log. That self-reinforcing pin is exactly what the
|
|
175
183
|
* narrowing rule below exists to prevent.
|
|
176
184
|
*/
|
|
185
|
+
export function usageVerifiedMaxAgeMs(snapshot) {
|
|
186
|
+
// D8: a row that arrived via sync from the account's own poller is trusted
|
|
187
|
+
// for the sync cadence. A locally captured row (poll / statusline / unset)
|
|
188
|
+
// keeps the 5-minute bar — the poller is on this box and can refresh it.
|
|
189
|
+
return snapshot?.freshness?.source === 'sync' ? USAGE_SYNC_TRUST_MS : USAGE_DECISION_MAX_AGE_MS;
|
|
190
|
+
}
|
|
177
191
|
export function isUsageVerified(candidate, nowMs = Date.now()) {
|
|
178
192
|
const snapshot = candidate.usageSnapshot;
|
|
179
193
|
const capturedAt = snapshot?.capturedAt;
|
|
180
194
|
if (!capturedAt || !snapshot?.windows.length)
|
|
181
195
|
return false;
|
|
182
|
-
return nowMs - capturedAt.getTime() <=
|
|
196
|
+
return nowMs - capturedAt.getTime() <= usageVerifiedMaxAgeMs(snapshot);
|
|
183
197
|
}
|
|
184
198
|
/**
|
|
185
199
|
* Whether this candidate carries a GENUINELY-STALE usage number: a snapshot with
|
|
@@ -996,6 +1010,8 @@ function describeRotationCandidate(c, nowMs) {
|
|
|
996
1010
|
tier,
|
|
997
1011
|
source: snap?.source ?? null,
|
|
998
1012
|
sourceLabel: snap?.sourceLabel ?? null,
|
|
1013
|
+
captureSource: snap?.freshness?.source ?? null,
|
|
1014
|
+
pollerDevice: snap?.freshness?.poller ?? null,
|
|
999
1015
|
capturedAt: snap?.capturedAt ? snap.capturedAt.toISOString() : null,
|
|
1000
1016
|
ageMs: capturedAtMs === null ? null : nowMs - capturedAtMs,
|
|
1001
1017
|
windows: (snap?.windows ?? []).map((w) => ({ key: w.key, usedPercent: Math.round(w.usedPercent) })),
|
|
@@ -9,6 +9,16 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { type ConfiguredDeviceRole } from '../device-config.js';
|
|
11
11
|
import { type CachedUsageSnapshot } from './usage.js';
|
|
12
|
+
/** Cadence of the usage-sync git tick — the trust window for a `sync` snapshot. */
|
|
13
|
+
export declare const USAGE_SYNC_INTERVAL_MS: number;
|
|
14
|
+
/** Re-publish an unchanged windowed row at least this often so workers' 15-min trust does not lapse. */
|
|
15
|
+
export declare const USAGE_PUBLISH_HEARTBEAT_MS: number;
|
|
16
|
+
/**
|
|
17
|
+
* Keep the previously published `capturedAt` when meters have not moved and
|
|
18
|
+
* the last publish is still inside the heartbeat. Stops statusline re-renders
|
|
19
|
+
* from dirtying the shared store (and taking the git lock) every few seconds.
|
|
20
|
+
*/
|
|
21
|
+
export declare function mergeUsageRowsForPublish(previous: Record<string, CachedUsageSnapshot> | undefined, next: Record<string, CachedUsageSnapshot>, nowMs?: number): Record<string, CachedUsageSnapshot>;
|
|
12
22
|
/** Legacy hidden ingest/export envelope kept for older fleet CLI compatibility. */
|
|
13
23
|
export interface UsageSyncPayload {
|
|
14
24
|
v: 1;
|
|
@@ -29,6 +39,18 @@ export interface PublishUsageSnapshotResult {
|
|
|
29
39
|
}
|
|
30
40
|
/** Publish this headed device's stable usage snapshot into its owned store file. */
|
|
31
41
|
export declare function publishUsageSnapshotToSharedStore(options?: PublishUsageSnapshotOptions): Promise<PublishUsageSnapshotResult>;
|
|
42
|
+
/**
|
|
43
|
+
* Publish a changed snapshot immediately and exchange the shared repo so
|
|
44
|
+
* workers see it without waiting for the 15-minute tick. No-ops when the
|
|
45
|
+
* serialized store is unchanged (statusline re-renders with the same windows).
|
|
46
|
+
*/
|
|
47
|
+
export declare function pushUsageSnapshotNow(options?: PublishUsageSnapshotOptions & {
|
|
48
|
+
lockPath?: string;
|
|
49
|
+
timeoutMs?: number;
|
|
50
|
+
}): Promise<{
|
|
51
|
+
published: PublishUsageSnapshotResult;
|
|
52
|
+
transport?: import('../fleet-shared-repo-sync.js').FleetSharedRepoSyncResult;
|
|
53
|
+
}>;
|
|
32
54
|
export interface ConsumeUsageSnapshotsOptions {
|
|
33
55
|
userAgentsDir?: string;
|
|
34
56
|
cachePath?: string;
|
|
@@ -12,6 +12,41 @@ import { readFleetSharedDeviceStates, updateFleetSharedDeviceStateAsync, } from
|
|
|
12
12
|
import { getUserAgentsDir } from '../state.js';
|
|
13
13
|
import { machineId, normalizeHost } from '../session/sync/config.js';
|
|
14
14
|
import { exportClaudeUsageCacheRows, ingestPeerClaudeUsageRows, } from './usage.js';
|
|
15
|
+
/** Cadence of the usage-sync git tick — the trust window for a `sync` snapshot. */
|
|
16
|
+
export const USAGE_SYNC_INTERVAL_MS = 15 * 60_000;
|
|
17
|
+
/** Re-publish an unchanged windowed row at least this often so workers' 15-min trust does not lapse. */
|
|
18
|
+
export const USAGE_PUBLISH_HEARTBEAT_MS = 10 * 60_000;
|
|
19
|
+
function usageMeterSignature(row) {
|
|
20
|
+
return JSON.stringify({
|
|
21
|
+
windows: row.windows,
|
|
22
|
+
plan: row.plan ?? null,
|
|
23
|
+
unavailable: row.unavailable ?? null,
|
|
24
|
+
freshnessSource: row.freshnessSource ?? null,
|
|
25
|
+
pollerDevice: row.pollerDevice ?? null,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Keep the previously published `capturedAt` when meters have not moved and
|
|
30
|
+
* the last publish is still inside the heartbeat. Stops statusline re-renders
|
|
31
|
+
* from dirtying the shared store (and taking the git lock) every few seconds.
|
|
32
|
+
*/
|
|
33
|
+
export function mergeUsageRowsForPublish(previous, next, nowMs = Date.now()) {
|
|
34
|
+
if (!previous)
|
|
35
|
+
return next;
|
|
36
|
+
const out = {};
|
|
37
|
+
for (const [key, row] of Object.entries(next)) {
|
|
38
|
+
const prior = previous[key];
|
|
39
|
+
if (prior && usageMeterSignature(prior) === usageMeterSignature(row)) {
|
|
40
|
+
const priorMs = prior.capturedAt ? Date.parse(prior.capturedAt) : NaN;
|
|
41
|
+
if (Number.isFinite(priorMs) && nowMs - priorMs < USAGE_PUBLISH_HEARTBEAT_MS) {
|
|
42
|
+
out[key] = { ...row, capturedAt: prior.capturedAt };
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
out[key] = row;
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
15
50
|
/** Publish this headed device's stable usage snapshot into its owned store file. */
|
|
16
51
|
export async function publishUsageSnapshotToSharedStore(options = {}) {
|
|
17
52
|
const result = {
|
|
@@ -26,13 +61,19 @@ export async function publishUsageSnapshotToSharedStore(options = {}) {
|
|
|
26
61
|
result.skipped = 'this device is not a usage publisher (mark it personal or desktop)';
|
|
27
62
|
return result;
|
|
28
63
|
}
|
|
29
|
-
const
|
|
30
|
-
if (Object.keys(
|
|
64
|
+
const rawRows = exportClaudeUsageCacheRows(options.cachePath);
|
|
65
|
+
if (Object.keys(rawRows).length === 0) {
|
|
31
66
|
result.skipped = 'no local usage snapshot to publish';
|
|
32
67
|
return result;
|
|
33
68
|
}
|
|
34
69
|
try {
|
|
35
|
-
const
|
|
70
|
+
const device = options.device ?? machineId();
|
|
71
|
+
const userAgentsDir = options.userAgentsDir ?? getUserAgentsDir();
|
|
72
|
+
const prior = readFleetSharedDeviceStates(userAgentsDir).states
|
|
73
|
+
.find((state) => normalizeHost(state.device) === normalizeHost(device))
|
|
74
|
+
?.usage?.rows;
|
|
75
|
+
const rows = mergeUsageRowsForPublish(prior, rawRows);
|
|
76
|
+
const write = await updateFleetSharedDeviceStateAsync(device, { usage: { rows } }, userAgentsDir);
|
|
36
77
|
result.published = true;
|
|
37
78
|
result.changed = write.changed;
|
|
38
79
|
result.path = write.path;
|
|
@@ -42,6 +83,24 @@ export async function publishUsageSnapshotToSharedStore(options = {}) {
|
|
|
42
83
|
}
|
|
43
84
|
return result;
|
|
44
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Publish a changed snapshot immediately and exchange the shared repo so
|
|
88
|
+
* workers see it without waiting for the 15-minute tick. No-ops when the
|
|
89
|
+
* serialized store is unchanged (statusline re-renders with the same windows).
|
|
90
|
+
*/
|
|
91
|
+
export async function pushUsageSnapshotNow(options = {}) {
|
|
92
|
+
const published = await publishUsageSnapshotToSharedStore(options);
|
|
93
|
+
if (!published.changed)
|
|
94
|
+
return { published };
|
|
95
|
+
const { syncFleetSharedStateRepo } = await import('../fleet-shared-repo-sync.js');
|
|
96
|
+
const transport = await syncFleetSharedStateRepo({
|
|
97
|
+
userAgentsDir: options.userAgentsDir,
|
|
98
|
+
device: options.device,
|
|
99
|
+
timeoutMs: options.timeoutMs,
|
|
100
|
+
lockPath: options.lockPath,
|
|
101
|
+
});
|
|
102
|
+
return { published, transport };
|
|
103
|
+
}
|
|
45
104
|
function capturedAtMs(row) {
|
|
46
105
|
if (!row.capturedAt)
|
|
47
106
|
return null;
|
|
@@ -70,15 +129,20 @@ export function consumeUsageSnapshotsFromSharedStore(options = {}) {
|
|
|
70
129
|
for (const [identity, incoming] of Object.entries(state.usage.rows)) {
|
|
71
130
|
if (!incoming || !Array.isArray(incoming.windows) || incoming.windows.length === 0)
|
|
72
131
|
continue;
|
|
132
|
+
const stamped = {
|
|
133
|
+
...incoming,
|
|
134
|
+
freshnessSource: 'sync',
|
|
135
|
+
pollerDevice: incoming.pollerDevice ?? state.device,
|
|
136
|
+
};
|
|
73
137
|
const current = rows[identity];
|
|
74
138
|
if (!current) {
|
|
75
|
-
rows[identity] =
|
|
139
|
+
rows[identity] = stamped;
|
|
76
140
|
continue;
|
|
77
141
|
}
|
|
78
|
-
const incomingMs = capturedAtMs(
|
|
142
|
+
const incomingMs = capturedAtMs(stamped);
|
|
79
143
|
const currentMs = capturedAtMs(current);
|
|
80
144
|
if (incomingMs !== null && (currentMs === null || incomingMs > currentMs))
|
|
81
|
-
rows[identity] =
|
|
145
|
+
rows[identity] = stamped;
|
|
82
146
|
}
|
|
83
147
|
}
|
|
84
148
|
result.sources.sort();
|
|
@@ -157,6 +157,12 @@ export declare function claudeAccessTokenNeedsRefresh(expiresAt: number | null |
|
|
|
157
157
|
export declare function setClaudeUsageCachePathForTest(cachePath: string | null): string | null;
|
|
158
158
|
/** Discriminator for usage window types. */
|
|
159
159
|
export type UsageWindowKey = 'session' | 'week' | 'sonnet_week' | 'month';
|
|
160
|
+
/**
|
|
161
|
+
* How this box obtained a usage row (delta-spec D8). Distinct from
|
|
162
|
+
* {@link UsageSnapshot.source} (`live` | `last_seen`), which is how the
|
|
163
|
+
* number was collected from the harness/API.
|
|
164
|
+
*/
|
|
165
|
+
export type UsageCaptureSource = 'poll' | 'statusline' | 'sync';
|
|
160
166
|
/** A single rate-limit window with utilization percentage and reset time. */
|
|
161
167
|
export interface UsageWindow {
|
|
162
168
|
key: UsageWindowKey;
|
|
@@ -203,6 +209,16 @@ export interface UsageSnapshot {
|
|
|
203
209
|
reason: 'session_limit' | 'out_of_credits';
|
|
204
210
|
resetsAt?: Date;
|
|
205
211
|
};
|
|
212
|
+
/**
|
|
213
|
+
* D8 freshness provenance. A `sync` row arrived from the account's poller
|
|
214
|
+
* through the fleet store and is trusted for the sync cadence; `poll` and
|
|
215
|
+
* `statusline` are local captures and keep the 5-minute decision bar.
|
|
216
|
+
*/
|
|
217
|
+
freshness?: {
|
|
218
|
+
source: UsageCaptureSource;
|
|
219
|
+
/** Device that polled or ingested the authoritative reading. */
|
|
220
|
+
poller?: string;
|
|
221
|
+
};
|
|
206
222
|
}
|
|
207
223
|
/** Usage data plus any error encountered while fetching. */
|
|
208
224
|
export interface UsageInfo {
|
|
@@ -257,6 +273,13 @@ interface UsageOptions {
|
|
|
257
273
|
* an unattended loop never transmits the interactive login to Anthropic.
|
|
258
274
|
*/
|
|
259
275
|
allowInteractiveLogin?: boolean;
|
|
276
|
+
/**
|
|
277
|
+
* Headed usage poller: skip the setup-token and the ACL keychain, and read
|
|
278
|
+
* only `<home>/.claude/.credentials.json` (the native rotating blob). A
|
|
279
|
+
* setup-token 403s on `/api/oauth/usage`; the keychain pops Touch ID. The
|
|
280
|
+
* file blob is the Linux headed native login, and a no-op when absent.
|
|
281
|
+
*/
|
|
282
|
+
nativeFileLogin?: boolean;
|
|
260
283
|
}
|
|
261
284
|
/** Canonical input for a single usage fetch operation. */
|
|
262
285
|
export interface UsageFetchInput {
|
|
@@ -317,6 +340,10 @@ export interface CachedUsageSnapshot {
|
|
|
317
340
|
* usage bucket.
|
|
318
341
|
*/
|
|
319
342
|
modelRefusals?: Record<string, CachedModelRefusal>;
|
|
343
|
+
/** D8: `poll` | `statusline` | `sync`. Survives export → ingest. */
|
|
344
|
+
freshnessSource?: UsageCaptureSource;
|
|
345
|
+
/** D8: device that polled or ingested the authoritative reading. */
|
|
346
|
+
pollerDevice?: string;
|
|
320
347
|
}
|
|
321
348
|
/** The single registry of agent usage sources and their transport. */
|
|
322
349
|
declare const USAGE_SOURCES: {
|
|
@@ -406,6 +433,8 @@ export interface UsageLookupOptions {
|
|
|
406
433
|
* `loadClaudeOauth`. Unset for every other lookup.
|
|
407
434
|
*/
|
|
408
435
|
allowInteractiveLogin?: boolean;
|
|
436
|
+
/** Headed poller: native `.credentials.json` only, never the setup-token. */
|
|
437
|
+
nativeFileLogin?: boolean;
|
|
409
438
|
}
|
|
410
439
|
export declare function getUsageInfoByIdentity(inputs: UsageIdentityInput[], opts?: UsageLookupOptions): Promise<{
|
|
411
440
|
canonicalByUsageKey: Map<string, AccountInfo>;
|
|
@@ -712,10 +741,13 @@ export declare function normalizeDroidWindows(data: DroidBillingLimitsResponse):
|
|
|
712
741
|
* `.credentials.json` only. Used by the daemon usage refresher so a background
|
|
713
742
|
* tick can never pop Touch ID.
|
|
714
743
|
*/
|
|
744
|
+
/** True when `<home>/.claude/.credentials.json` is a native rotating OAuth blob. */
|
|
745
|
+
export declare function claudeHomeHasNativeOauthFile(home?: string): boolean;
|
|
715
746
|
export declare function loadClaudeOauth(home?: string, opts?: {
|
|
716
747
|
accessTokenCache?: boolean;
|
|
717
748
|
fileOnly?: boolean;
|
|
718
749
|
allowInteractiveLogin?: boolean;
|
|
750
|
+
nativeFileLogin?: boolean;
|
|
719
751
|
}): Promise<ClaudeOauthCredentials | null>;
|
|
720
752
|
/**
|
|
721
753
|
* Save Claude OAuth credentials to the system keychain/keyring.
|