@phnx-labs/agents-cli 1.22.78 → 1.22.80
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 +15 -0
- package/README.md +28 -1
- package/dist/bootstrap.js +40 -12
- package/dist/commands/accounts.d.ts +22 -0
- package/dist/commands/accounts.js +158 -35
- package/dist/commands/config.js +37 -0
- package/dist/commands/exec.js +21 -10
- package/dist/commands/update.js +169 -18
- package/dist/commands/versions.d.ts +11 -0
- package/dist/commands/versions.js +30 -4
- package/dist/commands/view.d.ts +69 -3
- package/dist/commands/view.js +235 -75
- package/dist/index.js +35 -2
- package/dist/lib/account-catalog.d.ts +97 -1
- package/dist/lib/account-catalog.js +134 -6
- package/dist/lib/account-registry.d.ts +43 -0
- package/dist/lib/account-registry.js +97 -2
- package/dist/lib/accounting/rotate.d.ts +2 -2
- package/dist/lib/accounting/rotate.js +5 -5
- package/dist/lib/accounts/auth-operation-lock.d.ts +9 -0
- package/dist/lib/accounts/auth-operation-lock.js +55 -0
- package/dist/lib/accounts/connect.d.ts +170 -0
- package/dist/lib/accounts/connect.js +383 -0
- package/dist/lib/capabilities.js +2 -0
- package/dist/lib/commands.js +2 -0
- package/dist/lib/config-keys.d.ts +11 -2
- package/dist/lib/config-keys.js +21 -1
- package/dist/lib/daemon/daemon.js +5 -0
- package/dist/lib/daemon/harness-update-service.d.ts +110 -0
- package/dist/lib/daemon/harness-update-service.js +216 -0
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +5 -0
- package/dist/lib/device-config.d.ts +0 -1
- package/dist/lib/device-config.js +50 -5
- package/dist/lib/exec.js +26 -2
- package/dist/lib/fs-atomic.d.ts +2 -0
- package/dist/lib/fs-atomic.js +2 -0
- package/dist/lib/hooks/install.js +7 -2
- package/dist/lib/installations/active-check.d.ts +48 -0
- package/dist/lib/installations/active-check.js +84 -0
- package/dist/lib/installations/index.d.ts +5 -1
- package/dist/lib/installations/index.js +4 -0
- package/dist/lib/installations/installation-lock.d.ts +6 -0
- package/dist/lib/installations/installation-lock.js +29 -0
- package/dist/lib/installations/launch-gate.d.ts +69 -0
- package/dist/lib/installations/launch-gate.js +133 -0
- package/dist/lib/installations/native-command.d.ts +5 -0
- package/dist/lib/installations/native-command.js +52 -0
- package/dist/lib/installations/shims.d.ts +8 -2
- package/dist/lib/installations/shims.js +105 -2
- package/dist/lib/installations/store.d.ts +5 -1
- package/dist/lib/installations/store.js +24 -3
- package/dist/lib/installations/strategies.js +55 -35
- package/dist/lib/installations/types.d.ts +17 -0
- package/dist/lib/installations/update-cancellation.d.ts +82 -0
- package/dist/lib/installations/update-cancellation.js +122 -0
- package/dist/lib/installations/update-policy.d.ts +69 -0
- package/dist/lib/installations/update-policy.js +114 -0
- package/dist/lib/installations/update-runtime.d.ts +118 -0
- package/dist/lib/installations/update-runtime.js +321 -0
- package/dist/lib/installations/update.d.ts +25 -0
- package/dist/lib/installations/update.js +141 -2
- package/dist/lib/installations/versions.d.ts +1 -0
- package/dist/lib/installations/versions.js +166 -131
- package/dist/lib/platform/process.d.ts +3 -1
- package/dist/lib/platform/process.js +2 -2
- package/dist/lib/staleness/detectors/commands.d.ts +1 -2
- package/dist/lib/staleness/detectors/hooks.d.ts +1 -2
- package/dist/lib/staleness/detectors/mcp.d.ts +1 -2
- package/dist/lib/staleness/detectors/permissions.d.ts +1 -2
- package/dist/lib/staleness/detectors/plugins.d.ts +1 -7
- package/dist/lib/staleness/detectors/rules.d.ts +1 -2
- package/dist/lib/staleness/detectors/skills.d.ts +1 -2
- package/dist/lib/staleness/detectors/subagents.d.ts +1 -7
- package/dist/lib/staleness/detectors/workflows.d.ts +1 -2
- package/dist/lib/staleness/writers/commands.d.ts +1 -2
- package/dist/lib/staleness/writers/hooks.d.ts +1 -2
- package/dist/lib/staleness/writers/mcp.d.ts +1 -2
- package/dist/lib/staleness/writers/permissions.d.ts +1 -12
- package/dist/lib/staleness/writers/plugins.d.ts +1 -6
- package/dist/lib/staleness/writers/rules.d.ts +1 -2
- package/dist/lib/staleness/writers/skills.d.ts +1 -2
- package/dist/lib/staleness/writers/subagents.d.ts +1 -2
- package/dist/lib/staleness/writers/workflows.d.ts +1 -8
- package/dist/lib/state.d.ts +3 -1
- package/dist/lib/state.js +38 -13
- package/dist/lib/types.d.ts +26 -1
- package/dist/lib/types.js +5 -0
- package/dist/lib/view-types.d.ts +6 -0
- package/package.json +1 -1
package/dist/commands/view.js
CHANGED
|
@@ -11,7 +11,7 @@ import { authCacheKey, readAuthHealthCache } from '../lib/auth-health.js';
|
|
|
11
11
|
import { agentReportsUsage, classifyUsageErrorKind, deriveUsageStatusFromSnapshot, formatUsageSection, formatUsageSummary, formatUsageStatusBadge, getUsageBenignState, getUsageInfoForIdentity, getUsageInfoByIdentity, getUsageLookupKey, isUsageHeadlessScopeError, usageErrorForDisplay, } from '../lib/accounting/usage.js';
|
|
12
12
|
import { isHeadedDeviceRole, selfConfiguredDeviceRole } from '../lib/device-config.js';
|
|
13
13
|
import { readManifest } from '../lib/manifest.js';
|
|
14
|
-
import { listInstalledVersions, listInstalledVersionDirs, getGlobalDefault, getVersionHomePath, getVersionDir, removeVersion, printTrashFooter, reconcileStaleLatestForAgent, isGlobalBinaryAgent, getLiveVersion, isVersionIsolated, getIsolatedDefault, } from '../lib/installations/versions.js';
|
|
14
|
+
import { listInstalledVersions, listInstalledVersionDirs, getGlobalDefault, setGlobalDefault, getVersionHomePath, getVersionDir, removeVersion, printTrashFooter, reconcileStaleLatestForAgent, isGlobalBinaryAgent, getLiveVersion, isVersionIsolated, getIsolatedDefault, } from '../lib/installations/versions.js';
|
|
15
15
|
import { ensureVersionedAliasCurrent, removeShim, } from '../lib/installations/shims.js';
|
|
16
16
|
import { getAgentResources } from '../lib/resources.js';
|
|
17
17
|
import { renderMergedResources } from '../lib/merged-resources.js';
|
|
@@ -21,6 +21,11 @@ import { isCapable } from '../lib/capabilities.js';
|
|
|
21
21
|
import { discoverPlugins, pluginSupportsAgent } from '../lib/plugins/plugins.js';
|
|
22
22
|
import { getAgentsDir, getUserAgentsDir, getEffectivePromptcutsPath, readMergedPromptcuts, readMeta } from '../lib/state.js';
|
|
23
23
|
import { listNativeAccounts } from '../lib/account-registry.js';
|
|
24
|
+
import { buildNativeCatalog } from '../lib/account-catalog.js';
|
|
25
|
+
import { connectSupported } from '../lib/accounts/connect.js';
|
|
26
|
+
import { readInstallation } from '../lib/installations/store.js';
|
|
27
|
+
import { isAutoUpdateEnabledForAgent } from '../lib/installations/update-policy.js';
|
|
28
|
+
import { selectUpdateStrategy } from '../lib/installations/strategies.js';
|
|
24
29
|
import { isGitRepo, getGitSyncStatus } from '../lib/git.js';
|
|
25
30
|
import { getCentralRulesFileName } from '../lib/rules/rules.js';
|
|
26
31
|
import { composeRulesFromState } from '../lib/rules/compose.js';
|
|
@@ -68,6 +73,10 @@ export function namedAccountColumnLabel(agentId, info) {
|
|
|
68
73
|
const saved = listNativeAccounts(readMeta()).find(item => item.agent === agentId && item.identityKey === identityKey);
|
|
69
74
|
return saved ? `${saved.name} · ${display || saved.identityLabel || identityKey}` : display;
|
|
70
75
|
}
|
|
76
|
+
/** Human account identity; release labels belong only in installation diagnostics. */
|
|
77
|
+
export function nativeAccountViewLabel(row) {
|
|
78
|
+
return row.name && row.name !== row.display ? `${row.name} · ${row.display}` : row.display;
|
|
79
|
+
}
|
|
71
80
|
/**
|
|
72
81
|
* Human `agents view` row order: selected default first, then email-bearing
|
|
73
82
|
* accounts alphabetically, then installs whose account has no email. Version
|
|
@@ -368,7 +377,7 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
368
377
|
const cliStates = Object.fromEntries(await Promise.all(agentsToShow
|
|
369
378
|
.filter((agentId) => !hasNonIsolatedVersion(agentId))
|
|
370
379
|
.map(async (agentId) => [agentId, await getUnmanagedCliState(agentId)])));
|
|
371
|
-
const showPaths = !!filterAgentId;
|
|
380
|
+
const showPaths = !!filterAgentId && viewOpts?.versions === true;
|
|
372
381
|
// A filtered native view is about that native harness's installed versions.
|
|
373
382
|
// Custom forks are standalone agent types and only belong in the overview.
|
|
374
383
|
const harnesses = filterAgentId ? [] : getHarnesses();
|
|
@@ -452,7 +461,7 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
452
461
|
allowInteractiveLogin: usageAllowInteractiveLogin(),
|
|
453
462
|
});
|
|
454
463
|
spinner.stop();
|
|
455
|
-
console.log(chalk.bold('Installed Agent CLIs\n'));
|
|
464
|
+
console.log(chalk.bold(viewOpts?.versions ? 'Installed Agent CLIs\n' : 'Agents and accounts\n'));
|
|
456
465
|
const mergeCanonical = (info) => {
|
|
457
466
|
const key = getUsageLookupKey(info);
|
|
458
467
|
if (!key)
|
|
@@ -504,12 +513,13 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
504
513
|
if (live)
|
|
505
514
|
liveVersionByAgent.set(agentId, live);
|
|
506
515
|
}));
|
|
507
|
-
const displayVersion = (agentId, dirVersion) => liveVersionByAgent.get(agentId) ?? dirVersion;
|
|
516
|
+
const displayVersion = (agentId, dirVersion) => liveVersionByAgent.get(agentId) ?? readInstallation(agentId, dirVersion)?.releaseVersion ?? dirVersion;
|
|
508
517
|
// Uncolored row label, shared by the width pass and the render so padding lines
|
|
509
518
|
// up. An isolated copy is never the global default (installing one deliberately
|
|
510
519
|
// records no default), so the two tags can't collide.
|
|
511
520
|
const versionRowLabel = (agentId, version, globalDefault) => {
|
|
512
|
-
const
|
|
521
|
+
const release = displayVersion(agentId, version);
|
|
522
|
+
const shown = release === version ? version : `${version} → ${release}`;
|
|
513
523
|
if (version === globalDefault)
|
|
514
524
|
return `${shown} (default)`;
|
|
515
525
|
if (isVersionIsolated(agentId, version)) {
|
|
@@ -522,7 +532,69 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
522
532
|
return shown;
|
|
523
533
|
};
|
|
524
534
|
// Show version-managed agents
|
|
525
|
-
if (versionManaged.length > 0) {
|
|
535
|
+
if (versionManaged.length > 0 && !viewOpts?.versions) {
|
|
536
|
+
const catalog = buildNativeCatalog(infoResults.map(({ agentId, version, home, info }) => ({
|
|
537
|
+
agent: agentId,
|
|
538
|
+
label: version,
|
|
539
|
+
releaseVersion: displayVersion(agentId, version),
|
|
540
|
+
accountKey: info.accountKey,
|
|
541
|
+
email: info.email,
|
|
542
|
+
signedIn: isLaunchableSignedIn(info.signedIn, credentialPresence(agentId, home)),
|
|
543
|
+
})), readMeta(), (agent) => getGlobalDefault(agent) ?? getIsolatedDefault(agent));
|
|
544
|
+
for (const agentId of versionManaged) {
|
|
545
|
+
const accounts = catalog.filter((row) => row.agent === agentId);
|
|
546
|
+
let updateLabel = 'manual updates';
|
|
547
|
+
try {
|
|
548
|
+
if (selectUpdateStrategy(agentId).transactional) {
|
|
549
|
+
updateLabel = `automatic updates ${isAutoUpdateEnabledForAgent(agentId) ? 'on' : 'off'}`;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
catch { /* unsupported updater: keep the truthful manual label */ }
|
|
553
|
+
console.log(` ${chalk.bold(agentLabel(agentId))}${chalk.gray(` · ${updateLabel}`)}`);
|
|
554
|
+
const width = Math.max(0, ...accounts.map((row) => stringWidth(nativeAccountViewLabel(row))));
|
|
555
|
+
for (const row of accounts) {
|
|
556
|
+
const source = row.installations.find((home) => home.label === row.home && home.signedIn)
|
|
557
|
+
?? row.installations.find((home) => home.signedIn)
|
|
558
|
+
?? row.installations[0];
|
|
559
|
+
const rawInfo = source ? infoMap.get(`${agentId}:${source.label}`) : undefined;
|
|
560
|
+
const info = rawInfo ? mergeCanonical(rawInfo) : undefined;
|
|
561
|
+
const usageKey = getUsageLookupKey(info);
|
|
562
|
+
const usageInfo = usageKey ? usageByKey.get(usageKey) : undefined;
|
|
563
|
+
const usage = formatUsageSummary(info?.plan ?? null, usageInfo?.snapshot ?? null, 3, viewUsageSummaryOptions(agentId, row.state === 'connected', usageInfo, usageWindowCap));
|
|
564
|
+
const connected = row.state === 'connected';
|
|
565
|
+
const model = source ? resolveConfiguredModel(agentId, source.label)?.model : null;
|
|
566
|
+
const pinned = row.installations.some((home) => readInstallation(agentId, home.label)?.updatePolicy === 'pinned');
|
|
567
|
+
const parts = [
|
|
568
|
+
` ${row.isDefault ? chalk.green('*') : ' '} ${chalk.cyan(padToWidth(nativeAccountViewLabel(row), width))}`,
|
|
569
|
+
connected ? chalk.green('connected') : chalk.yellow('not connected here'),
|
|
570
|
+
model ? chalk.yellow(model) : '',
|
|
571
|
+
usage,
|
|
572
|
+
formatUsageStatusBadge(info?.usageStatus),
|
|
573
|
+
pinned ? chalk.gray('release pinned · --versions') : '',
|
|
574
|
+
];
|
|
575
|
+
console.log(joinViewColumns(parts));
|
|
576
|
+
}
|
|
577
|
+
if (accounts.length === 0) {
|
|
578
|
+
const localIdentity = infoResults.some((row) => row.agentId === agentId && row.info.signedIn);
|
|
579
|
+
const hint = connectSupported(agentId) ? `agents accounts connect ${agentId}` : loginHint(agentId);
|
|
580
|
+
console.log(chalk.gray(localIdentity
|
|
581
|
+
? ' Native login detected · account identity unavailable'
|
|
582
|
+
: ` No native login detected · ${hint}`));
|
|
583
|
+
}
|
|
584
|
+
const isolatedCount = listInstalledVersions(agentId).filter((label) => isVersionIsolated(agentId, label)).length;
|
|
585
|
+
if (isolatedCount > 0)
|
|
586
|
+
console.log(chalk.gray(` ${isolatedCount} isolated installation${isolatedCount === 1 ? '' : 's'} · inspect with --versions`));
|
|
587
|
+
const projectVersion = getProjectVersionFromCwd(agentId);
|
|
588
|
+
if (projectVersion)
|
|
589
|
+
console.log(chalk.gray(' Project installation override configured · inspect with --versions'));
|
|
590
|
+
console.log();
|
|
591
|
+
}
|
|
592
|
+
if (filterAgentId) {
|
|
593
|
+
const connect = connectSupported(filterAgentId) ? `Connect: agents accounts connect ${filterAgentId} [name] · ` : '';
|
|
594
|
+
console.log(chalk.gray(` ${connect}Details: agents view ${filterAgentId} --versions\n`));
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
if (versionManaged.length > 0 && viewOpts?.versions) {
|
|
526
598
|
// Calculate column widths across all agents for alignment
|
|
527
599
|
let maxVerLabel = 0;
|
|
528
600
|
let maxEmail = 0;
|
|
@@ -596,7 +668,8 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
596
668
|
const isDefault = version === globalDefault;
|
|
597
669
|
const isolated = !isDefault && isVersionIsolated(agentId, version);
|
|
598
670
|
const isolatedTag = getIsolatedDefault(agentId) === version ? ' (isolated default)' : ' (isolated)';
|
|
599
|
-
const
|
|
671
|
+
const release = displayVersion(agentId, version);
|
|
672
|
+
const shown = release === version ? version : `${version} → ${release}`;
|
|
600
673
|
const base = versionRowLabel(agentId, version, globalDefault);
|
|
601
674
|
const tagPad = ' '.repeat(maxVerLabel - base.length);
|
|
602
675
|
const label = isDefault
|
|
@@ -1253,6 +1326,15 @@ export async function collectAgentsJson(filterAgentId, resourceSections, opts) {
|
|
|
1253
1326
|
}
|
|
1254
1327
|
}
|
|
1255
1328
|
const infoResults = await Promise.all(infoFetches);
|
|
1329
|
+
const globalReleases = new Map();
|
|
1330
|
+
await Promise.all(agentsToShow.filter(isGlobalBinaryAgent).map(async (agent) => {
|
|
1331
|
+
if (!infoResults.some((row) => row.agentId === agent))
|
|
1332
|
+
return;
|
|
1333
|
+
const release = await getLiveVersion(agent);
|
|
1334
|
+
if (release)
|
|
1335
|
+
globalReleases.set(agent, release);
|
|
1336
|
+
}));
|
|
1337
|
+
const actualRelease = (agent, label) => globalReleases.get(agent) ?? readInstallation(agent, label)?.releaseVersion ?? label;
|
|
1256
1338
|
const { canonicalByUsageKey, usageByKey } = await getUsageInfoByIdentity(infoResults.map(({ agentId, home, version, info }) => ({
|
|
1257
1339
|
agentId,
|
|
1258
1340
|
home,
|
|
@@ -1290,6 +1372,7 @@ export async function collectAgentsJson(filterAgentId, resourceSections, opts) {
|
|
|
1290
1372
|
const authHealth = authCache[authCacheKey(host, agentId, version)];
|
|
1291
1373
|
const entry = {
|
|
1292
1374
|
version,
|
|
1375
|
+
releaseVersion: actualRelease(agentId, version),
|
|
1293
1376
|
isDefault: version === globalDefault,
|
|
1294
1377
|
isolated: isVersionIsolated(agentId, version),
|
|
1295
1378
|
isIsolatedDefault: getIsolatedDefault(agentId) === version,
|
|
@@ -1348,7 +1431,16 @@ export async function collectAgentsJson(filterAgentId, resourceSections, opts) {
|
|
|
1348
1431
|
return a.isDefault ? -1 : 1;
|
|
1349
1432
|
return compareVersions(b.version, a.version);
|
|
1350
1433
|
});
|
|
1351
|
-
|
|
1434
|
+
const accounts = buildNativeCatalog(infoResults.filter((row) => row.agentId === agentId).map(({ version, home, info }) => ({
|
|
1435
|
+
agent: agentId,
|
|
1436
|
+
label: version,
|
|
1437
|
+
releaseVersion: actualRelease(agentId, version),
|
|
1438
|
+
accountKey: info.accountKey,
|
|
1439
|
+
email: info.email,
|
|
1440
|
+
signedIn: isLaunchableSignedIn(info.signedIn, credentialPresence(agentId, home)),
|
|
1441
|
+
})), readMeta(), (agent) => getGlobalDefault(agent) ?? getIsolatedDefault(agent))
|
|
1442
|
+
.filter((row) => row.agent === agentId);
|
|
1443
|
+
out.push({ agent: agentId, versions, accounts, harnesses: harnesses.filter((h) => h.agent === agentId) });
|
|
1352
1444
|
}
|
|
1353
1445
|
return out;
|
|
1354
1446
|
}
|
|
@@ -1363,50 +1455,116 @@ export async function collectAgentsJson(filterAgentId, resourceSections, opts) {
|
|
|
1363
1455
|
export function pruneGroupKey(info) {
|
|
1364
1456
|
return info.accountKey ?? info.email?.toLowerCase() ?? null;
|
|
1365
1457
|
}
|
|
1458
|
+
/**
|
|
1459
|
+
* PURE duplicate-home detection: given every installed home for one agent, decide
|
|
1460
|
+
* which are redundant duplicates of the SAME logical account and which single home
|
|
1461
|
+
* to keep per account. Extracted from buildAgentPrunePlan so the keeper/merge rules
|
|
1462
|
+
* are unit-tested without touching disk.
|
|
1463
|
+
*
|
|
1464
|
+
* Two rules, both conservative:
|
|
1465
|
+
* - GROUPING. A home with a captured `accountKey` groups by it (account+org, so a
|
|
1466
|
+
* personal Max and a Team seat on the same email stay separate). A home with NO
|
|
1467
|
+
* accountKey folds into a same-email account ONLY when a sibling home for that
|
|
1468
|
+
* email IS identified — that email-only home is an incompletely-captured re-login
|
|
1469
|
+
* of the known account, not a distinct seat (PHNX-3887). Otherwise it groups by
|
|
1470
|
+
* its bare email and only ever collapses against another equally-bare home.
|
|
1471
|
+
* - KEEPER. Within a group the keeper is the home that best represents the account:
|
|
1472
|
+
* captured identity first, then a signed-in credential, then the newest RUNNING
|
|
1473
|
+
* release, then the highest dir label as a stable tiebreak. This is deliberately
|
|
1474
|
+
* NOT "highest dir-name semver" — in the wild that is the freshly re-logged-in
|
|
1475
|
+
* duplicate that never captured its identity or usage, and keeping it while
|
|
1476
|
+
* trashing the identified home is the exact bug this replaces.
|
|
1477
|
+
*/
|
|
1478
|
+
export function planDuplicatePrune(candidates) {
|
|
1479
|
+
// Only installs with a working binary compete for "the live install for this account".
|
|
1480
|
+
const installed = candidates.filter((c) => c.hasBinary && c.email);
|
|
1481
|
+
// email -> every DISTINCT captured accountKey seen for it. An email-only home
|
|
1482
|
+
// folds into an identified account only when that email maps to EXACTLY ONE
|
|
1483
|
+
// account; when two distinct orgs share the email (the Personal+Team case) the
|
|
1484
|
+
// fold is ambiguous, so the identity-less home is left ungrouped rather than
|
|
1485
|
+
// guessed into one — guessing could retire the actually-working re-login of the
|
|
1486
|
+
// OTHER org (the exact bug this function exists to prevent, one level up).
|
|
1487
|
+
const emailToAccountKeys = new Map();
|
|
1488
|
+
for (const c of installed) {
|
|
1489
|
+
if (!c.accountKey)
|
|
1490
|
+
continue;
|
|
1491
|
+
const email = c.email.toLowerCase();
|
|
1492
|
+
(emailToAccountKeys.get(email) ?? emailToAccountKeys.set(email, new Set()).get(email)).add(c.accountKey);
|
|
1493
|
+
}
|
|
1494
|
+
const groupKeyFor = (c) => {
|
|
1495
|
+
if (c.accountKey)
|
|
1496
|
+
return c.accountKey;
|
|
1497
|
+
const email = c.email?.toLowerCase();
|
|
1498
|
+
const keys = email ? emailToAccountKeys.get(email) : undefined;
|
|
1499
|
+
if (keys && keys.size === 1)
|
|
1500
|
+
return [...keys][0];
|
|
1501
|
+
// ≥2 identified orgs on this email: never merge the ambiguous home. A unique
|
|
1502
|
+
// key makes it its own singleton group (length 1 → never pruned).
|
|
1503
|
+
if (keys && keys.size >= 2)
|
|
1504
|
+
return `ambiguous:${c.email.toLowerCase()}:${c.version}`;
|
|
1505
|
+
// No identified sibling at all: group equally-bare homes by email as before.
|
|
1506
|
+
return pruneGroupKey(c);
|
|
1507
|
+
};
|
|
1508
|
+
const byAccount = new Map();
|
|
1509
|
+
for (const c of installed) {
|
|
1510
|
+
const key = groupKeyFor(c);
|
|
1511
|
+
if (!key)
|
|
1512
|
+
continue;
|
|
1513
|
+
(byAccount.get(key) ?? byAccount.set(key, []).get(key)).push(c);
|
|
1514
|
+
}
|
|
1515
|
+
const rank = (c) => [c.accountKey ? 1 : 0, c.signedIn ? 1 : 0];
|
|
1516
|
+
const better = (a, b) => {
|
|
1517
|
+
const [aa, ab] = rank(a);
|
|
1518
|
+
const [ba, bb] = rank(b);
|
|
1519
|
+
if (aa !== ba)
|
|
1520
|
+
return ba - aa;
|
|
1521
|
+
if (ab !== bb)
|
|
1522
|
+
return bb - ab;
|
|
1523
|
+
if (a.release !== b.release)
|
|
1524
|
+
return compareVersions(b.release, a.release);
|
|
1525
|
+
return compareVersions(b.version, a.version);
|
|
1526
|
+
};
|
|
1527
|
+
const out = [];
|
|
1528
|
+
for (const group of byAccount.values()) {
|
|
1529
|
+
if (group.length < 2)
|
|
1530
|
+
continue;
|
|
1531
|
+
const sorted = [...group].sort(better);
|
|
1532
|
+
const keeper = sorted[0].version;
|
|
1533
|
+
for (const dup of sorted.slice(1)) {
|
|
1534
|
+
out.push({ version: dup.version, email: dup.email, keeper });
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
return out;
|
|
1538
|
+
}
|
|
1366
1539
|
async function buildAgentPrunePlan(agentId) {
|
|
1367
1540
|
const dirInfos = listInstalledVersionDirs(agentId);
|
|
1368
1541
|
const entries = await Promise.all(dirInfos.map(async ({ version, hasBinary }) => {
|
|
1369
1542
|
const home = getVersionHomePath(agentId, version);
|
|
1370
1543
|
const info = await getAccountInfo(agentId, home);
|
|
1371
|
-
|
|
1544
|
+
const release = readInstallation(agentId, version)?.releaseVersion ?? version;
|
|
1545
|
+
return { version, info, hasBinary, release };
|
|
1372
1546
|
}));
|
|
1373
1547
|
const globalDefault = getGlobalDefault(agentId);
|
|
1374
1548
|
const toPrune = [];
|
|
1375
|
-
const
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
const sorted = [...group].sort((a, b) => compareVersions(b.version, a.version));
|
|
1395
|
-
const keeper = sorted[0].version;
|
|
1396
|
-
for (const older of sorted.slice(1)) {
|
|
1397
|
-
const plan = {
|
|
1398
|
-
agentId,
|
|
1399
|
-
version: older.version,
|
|
1400
|
-
email: older.info.email,
|
|
1401
|
-
keeper,
|
|
1402
|
-
isDefault: older.version === globalDefault,
|
|
1403
|
-
reason: 'duplicate',
|
|
1404
|
-
};
|
|
1405
|
-
if (plan.isDefault)
|
|
1406
|
-
skippedDefaults.push(plan);
|
|
1407
|
-
else
|
|
1408
|
-
toPrune.push(plan);
|
|
1409
|
-
}
|
|
1549
|
+
for (const d of planDuplicatePrune(entries.map((e) => ({
|
|
1550
|
+
version: e.version,
|
|
1551
|
+
release: e.release,
|
|
1552
|
+
email: e.info.email,
|
|
1553
|
+
accountKey: e.info.accountKey,
|
|
1554
|
+
signedIn: e.info.signedIn,
|
|
1555
|
+
hasBinary: e.hasBinary,
|
|
1556
|
+
})))) {
|
|
1557
|
+
toPrune.push({
|
|
1558
|
+
agentId,
|
|
1559
|
+
version: d.version,
|
|
1560
|
+
email: d.email,
|
|
1561
|
+
keeper: d.keeper,
|
|
1562
|
+
// The default may itself be a to-be-retired duplicate (a freshly created
|
|
1563
|
+
// home often becomes the global default). We do NOT skip it: executePrune
|
|
1564
|
+
// repoints the default onto the keeper first, then retires the duplicate.
|
|
1565
|
+
isDefault: d.version === globalDefault,
|
|
1566
|
+
reason: 'duplicate',
|
|
1567
|
+
});
|
|
1410
1568
|
}
|
|
1411
1569
|
// Home-only leftovers: dirs without a binary. These are residue from a
|
|
1412
1570
|
// prior removeVersion before the soft-delete migration, plus any hand-edited
|
|
@@ -1425,10 +1583,20 @@ async function buildAgentPrunePlan(agentId) {
|
|
|
1425
1583
|
reason: 'home-leftover',
|
|
1426
1584
|
});
|
|
1427
1585
|
}
|
|
1428
|
-
return { agentId, toPrune
|
|
1586
|
+
return { agentId, toPrune };
|
|
1429
1587
|
}
|
|
1430
|
-
async function executePrunePlan(plan) {
|
|
1588
|
+
export async function executePrunePlan(plan) {
|
|
1431
1589
|
const moved = [];
|
|
1590
|
+
// Repoint the global default onto the keeper BEFORE retiring a duplicate that
|
|
1591
|
+
// currently holds it, so consolidation collapses the duplicate instead of
|
|
1592
|
+
// leaving it pinned as the default (and so removeVersion's own fallback never
|
|
1593
|
+
// has to guess a replacement).
|
|
1594
|
+
for (const p of plan.toPrune) {
|
|
1595
|
+
if (p.reason === 'duplicate' && p.isDefault && p.keeper && p.keeper !== p.version) {
|
|
1596
|
+
setGlobalDefault(p.agentId, p.keeper);
|
|
1597
|
+
console.log(chalk.gray(`Default ${agentLabel(p.agentId)} moved ${p.version} → ${p.keeper} (keeper) before pruning the duplicate.`));
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1432
1600
|
for (const p of plan.toPrune) {
|
|
1433
1601
|
const ok = removeVersion(p.agentId, p.version);
|
|
1434
1602
|
if (ok) {
|
|
@@ -1445,14 +1613,6 @@ async function executePrunePlan(plan) {
|
|
|
1445
1613
|
return moved;
|
|
1446
1614
|
}
|
|
1447
1615
|
function printPrunePlan(plan, isFirst) {
|
|
1448
|
-
if (plan.skippedDefaults.length > 0) {
|
|
1449
|
-
console.log(chalk.yellow(`Skipping default versions for ${agentLabel(plan.agentId)} (switch default first):`));
|
|
1450
|
-
for (const s of plan.skippedDefaults) {
|
|
1451
|
-
console.log(` ${agentLabel(s.agentId)}@${s.version} ${chalk.cyan(s.email)} ` +
|
|
1452
|
-
chalk.gray(`— duplicate of ${s.agentId}@${s.keeper}. Run: agents use ${s.agentId}@${s.keeper}`));
|
|
1453
|
-
}
|
|
1454
|
-
console.log();
|
|
1455
|
-
}
|
|
1456
1616
|
if (plan.toPrune.length === 0)
|
|
1457
1617
|
return;
|
|
1458
1618
|
const heading = isFirst ? `Will move to trash for ${agentLabel(plan.agentId)}:` : `Also found candidates for ${agentLabel(plan.agentId)}:`;
|
|
@@ -1470,9 +1630,11 @@ function printPrunePlan(plan, isFirst) {
|
|
|
1470
1630
|
console.log();
|
|
1471
1631
|
}
|
|
1472
1632
|
/**
|
|
1473
|
-
*
|
|
1474
|
-
*
|
|
1475
|
-
*
|
|
1633
|
+
* Consolidate to one home per logical account: retire the redundant duplicate
|
|
1634
|
+
* homes an account accumulated, keeping the single home that best represents it
|
|
1635
|
+
* (see {@link planDuplicatePrune} for the keeper/merge rules). When the retired
|
|
1636
|
+
* duplicate holds the global default, the default is first repointed onto the
|
|
1637
|
+
* keeper, so consolidation collapses it instead of leaving it pinned.
|
|
1476
1638
|
*
|
|
1477
1639
|
* When filterAgentId is set, prunes that agent first, then cascades: after
|
|
1478
1640
|
* each agent, offers the next agent with duplicates. User answering "no"
|
|
@@ -1485,7 +1647,7 @@ export async function pruneDuplicates(filterAgentId, yes, dryRun) {
|
|
|
1485
1647
|
const spinner = ora({ text: 'Scanning installed versions...', isSilent: !process.stdout.isTTY }).start();
|
|
1486
1648
|
const plans = await Promise.all(ordered.map((a) => buildAgentPrunePlan(a)));
|
|
1487
1649
|
spinner.stop();
|
|
1488
|
-
const actionable = plans.filter((p) => p.toPrune.length > 0
|
|
1650
|
+
const actionable = plans.filter((p) => p.toPrune.length > 0);
|
|
1489
1651
|
if (actionable.length === 0) {
|
|
1490
1652
|
console.log(chalk.gray('Nothing to prune — no duplicate-account installs and no home-only leftovers.'));
|
|
1491
1653
|
return;
|
|
@@ -1496,11 +1658,6 @@ export async function pruneDuplicates(filterAgentId, yes, dryRun) {
|
|
|
1496
1658
|
let processedAny = false;
|
|
1497
1659
|
for (const plan of actionable) {
|
|
1498
1660
|
printPrunePlan(plan, isFirst);
|
|
1499
|
-
if (plan.toPrune.length === 0) {
|
|
1500
|
-
// Only skippedDefaults for this agent; move on.
|
|
1501
|
-
isFirst = false;
|
|
1502
|
-
continue;
|
|
1503
|
-
}
|
|
1504
1661
|
if (dryRun) {
|
|
1505
1662
|
processedAny = true;
|
|
1506
1663
|
isFirst = false;
|
|
@@ -1603,8 +1760,7 @@ export async function viewAction(agentArg, options) {
|
|
|
1603
1760
|
console.log(JSON.stringify(data, null, 2));
|
|
1604
1761
|
return;
|
|
1605
1762
|
}
|
|
1606
|
-
|
|
1607
|
-
await showInstalledVersions(undefined, { forceRefresh });
|
|
1763
|
+
await showInstalledVersions(undefined, { forceRefresh, versions: options?.versions });
|
|
1608
1764
|
return;
|
|
1609
1765
|
}
|
|
1610
1766
|
// Parse agent@version syntax
|
|
@@ -1675,15 +1831,15 @@ export async function viewAction(agentArg, options) {
|
|
|
1675
1831
|
await showAgentResources(agentId, 'default', filter);
|
|
1676
1832
|
}
|
|
1677
1833
|
else {
|
|
1678
|
-
|
|
1679
|
-
await showInstalledVersions(agentId, { forceRefresh });
|
|
1834
|
+
await showInstalledVersions(agentId, { forceRefresh, versions: options?.versions });
|
|
1680
1835
|
}
|
|
1681
1836
|
}
|
|
1682
1837
|
/** Register the `agents view` command. */
|
|
1683
1838
|
export function registerViewCommand(program) {
|
|
1684
1839
|
addHostOption(program.command('view [agent]'))
|
|
1685
|
-
.description('Show
|
|
1686
|
-
.option('--
|
|
1840
|
+
.description('Show your agents, connected accounts, and usage. Use --versions for installation diagnostics.')
|
|
1841
|
+
.option('--versions', 'Show every installation, actual release, account, model, and home path.')
|
|
1842
|
+
.option('--json', 'Emit machine-readable JSON (accounts plus backward-compatible installation list).')
|
|
1687
1843
|
.option('--resources [sections]', 'In --json mode, include each version\'s resources: "all" (default) or a comma list (skills,plugins,mcp,commands,workflows,memory,hooks). Implies --json.')
|
|
1688
1844
|
.option('--detailed', 'Include all resources in --json output (alias for --resources all). Implies --json.')
|
|
1689
1845
|
.option('-r, --refresh', 'Force a live usage refresh, bypassing the cache (slower). Repopulates the S:/W: limit bars for every account whose token is reachable.')
|
|
@@ -1703,12 +1859,15 @@ export function registerViewCommand(program) {
|
|
|
1703
1859
|
.option('--merged', 'Show the merged, first-wins resource surface across all layers (project, user, extras, system) in one table with the winning layer per row.')
|
|
1704
1860
|
.addHelpText('after', `
|
|
1705
1861
|
Examples:
|
|
1706
|
-
# Show
|
|
1862
|
+
# Show installed agents with connected accounts and usage
|
|
1707
1863
|
agents view
|
|
1708
1864
|
|
|
1709
|
-
# Show
|
|
1865
|
+
# Show accounts for one agent
|
|
1710
1866
|
agents view claude
|
|
1711
1867
|
|
|
1868
|
+
# Inspect installation labels, current releases, models, and paths
|
|
1869
|
+
agents view claude --versions
|
|
1870
|
+
|
|
1712
1871
|
# Describe one custom harness (host, model, provider, auth, path)
|
|
1713
1872
|
agents view deepseek-flash
|
|
1714
1873
|
|
|
@@ -1738,16 +1897,17 @@ Examples:
|
|
|
1738
1897
|
agents view --merged
|
|
1739
1898
|
|
|
1740
1899
|
When to use:
|
|
1741
|
-
- Checking which agents
|
|
1742
|
-
- Seeing
|
|
1900
|
+
- Checking which agents and accounts are connected on this device
|
|
1901
|
+
- Seeing the selected default account and each account's usage
|
|
1743
1902
|
- Inspecting commands, skills, hooks, and MCP servers synced to a version
|
|
1744
1903
|
- Verifying a version is installed before running it
|
|
1745
1904
|
- Cleaning up stale versions left behind after upgrading (--prune)
|
|
1746
1905
|
|
|
1747
1906
|
Output:
|
|
1748
|
-
- Without arguments: table of all agents with
|
|
1907
|
+
- Without arguments: table of all agents with accounts, connection state, usage,
|
|
1749
1908
|
then one block per custom harness (see 'agents harness')
|
|
1750
|
-
- With agent name:
|
|
1909
|
+
- With agent name: one row per native identity, showing the default account
|
|
1910
|
+
- With --versions: every retained installation, actual release, model, and home
|
|
1751
1911
|
- With a custom harness name: that harness's host, model, provider, and auth
|
|
1752
1912
|
- With agent@version: detailed breakdown of resources synced to that version
|
|
1753
1913
|
- With --json: structured JSON with version, isDefault, signedIn, authVerdict,
|
package/dist/index.js
CHANGED
|
@@ -24,8 +24,21 @@
|
|
|
24
24
|
* agent.js would pull the whole secrets graph into every invocation.
|
|
25
25
|
*/
|
|
26
26
|
import { SYNC_GET_CMD, SYNC_PING_CMD, SYNC_LOCK_CMD } from './lib/secrets/sync-commands.js';
|
|
27
|
-
// Force exit on Ctrl+C when no interactive prompt is handling it
|
|
28
|
-
|
|
27
|
+
// Force exit on Ctrl+C when no interactive prompt is handling it — UNLESS a
|
|
28
|
+
// guarded harness auto-update pass is mutating this process transactionally
|
|
29
|
+
// (PHNX-3940). A hard exit mid-swap can leave an installation whose directory
|
|
30
|
+
// and metadata disagree, so defer ONLY while that guard is held; the pass sees
|
|
31
|
+
// the same SIGINT cooperatively (lib/installations/update-cancellation.ts) and
|
|
32
|
+
// stops at its next safe boundary. The depth is read through the shared
|
|
33
|
+
// `Symbol.for` registry key so this slim entry shell needs no static import of
|
|
34
|
+
// that module (agent.test.ts pins the single allowed import). Every other SIGINT
|
|
35
|
+
// still force-exits 130, and no other listener is touched.
|
|
36
|
+
process.on('SIGINT', () => {
|
|
37
|
+
const depth = globalThis[Symbol.for('agents.guardedAutoUpdateDepth')] ?? 0;
|
|
38
|
+
if (depth > 0)
|
|
39
|
+
return;
|
|
40
|
+
process.exit(130);
|
|
41
|
+
});
|
|
29
42
|
// Ignore SIGPIPE — prevents exit code 13 crashes in piped environments
|
|
30
43
|
// (e.g. `agents sessions | head`, or when stdout is captured by another process).
|
|
31
44
|
process.on('SIGPIPE', () => { });
|
|
@@ -58,6 +71,15 @@ if (process.argv[2] === SYNC_GET_CMD ||
|
|
|
58
71
|
: await runAgentLockSync(name);
|
|
59
72
|
process.exit(code);
|
|
60
73
|
}
|
|
74
|
+
// Launch-lease delegate: every generated native shim/alias calls
|
|
75
|
+
// `agents __launch-lease <agent> <label> <pid>` right before its final `exec`
|
|
76
|
+
// (PHNX-3940) — on the hot launch path of every managed agent, so it must skip
|
|
77
|
+
// the same update-check/bootstrap machinery `__shim` skips below. See
|
|
78
|
+
// `lib/installations/launch-gate.ts` for what this call actually does.
|
|
79
|
+
if (process.argv[2] === '__launch-lease') {
|
|
80
|
+
const { runLaunchLeaseCli } = await import('./lib/installations/launch-gate.js');
|
|
81
|
+
process.exit(await runLaunchLeaseCli(process.argv.slice(3)));
|
|
82
|
+
}
|
|
61
83
|
// Transparent shim delegate: the generated Windows `.cmd` shims invoke
|
|
62
84
|
// `agents __shim <agent>[@version] <raw args>`. Intercept here, before bootstrap
|
|
63
85
|
// parses anything, so the agent's own flags (`--help`, `--version`, etc.) pass
|
|
@@ -98,6 +120,17 @@ if (process.argv[2] === '__usage-export') {
|
|
|
98
120
|
process.stdout.write(JSON.stringify({ v: 1, rows: exportClaudeUsageCacheRows() }));
|
|
99
121
|
process.exit(0);
|
|
100
122
|
}
|
|
123
|
+
// Harness auto-update child (PHNX-3940): the daemon's harness-update tick spawns
|
|
124
|
+
// `agents __harness-update-run` over a Node IPC channel and cancels it
|
|
125
|
+
// cooperatively by MESSAGE, never a kill (see
|
|
126
|
+
// lib/daemon/harness-update-service.ts + lib/installations/update-cancellation.ts).
|
|
127
|
+
// Above bootstrap for the same reason as __daemon-run: this internal periodic
|
|
128
|
+
// child must not itself fire a self-update check or fork a detached sync, and its
|
|
129
|
+
// stdout carries a clean JSON summary the daemon logs.
|
|
130
|
+
if (process.argv[2] === '__harness-update-run') {
|
|
131
|
+
const { runHarnessUpdateChild } = await import('./lib/installations/update-runtime.js');
|
|
132
|
+
process.exit(await runHarnessUpdateChild());
|
|
133
|
+
}
|
|
101
134
|
if (process.argv[2] === '__daemon-run') {
|
|
102
135
|
const { runDaemon, log: daemonLog } = await import('./lib/daemon/daemon.js');
|
|
103
136
|
// RUSH-2418: the daemon is the one always-on process here, and it ran with no
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type AccountInfo } from './agents.js';
|
|
2
|
+
import type { AgentId, Meta } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Strict "is this home actually connected?" — a live CREDENTIAL in that exact
|
|
5
|
+
* version home, not just a metadata identity claim (PHNX-3940). A `.claude.json`
|
|
6
|
+
* carrying an `oauthAccount` block with no `.credentials.json`/`.oauth_token`
|
|
7
|
+
* beside it is stale/expired, and must read as `reconnect-needed`, never
|
|
8
|
+
* `connected`. Where agents-cli does not know the credential location
|
|
9
|
+
* (`knownLocation` false), it falls back to the metadata `signedIn` since there
|
|
10
|
+
* is nothing stricter to check.
|
|
11
|
+
*/
|
|
12
|
+
export declare function isLaunchableSignedIn(agent: AgentId, versionHome: string, info: Pick<AccountInfo, 'signedIn'>): boolean;
|
|
2
13
|
export interface NativeAccountCatalogEntry {
|
|
3
14
|
kind: 'native';
|
|
4
15
|
id: string;
|
|
@@ -16,3 +27,88 @@ export declare function groupNativeAccountRows(rows: Array<{
|
|
|
16
27
|
}>): NativeAccountCatalogEntry[];
|
|
17
28
|
/** Discover signed-in harness-native identities without copying their auth files. */
|
|
18
29
|
export declare function discoverNativeAccounts(): Promise<NativeAccountCatalogEntry[]>;
|
|
30
|
+
/** One installed home carrying an identity — the secondary "release/home" facts. */
|
|
31
|
+
export interface AccountHome {
|
|
32
|
+
/** The installation label (version-dir basename). */
|
|
33
|
+
label: string;
|
|
34
|
+
/** The vendor release the home currently carries, when the record is present. */
|
|
35
|
+
releaseVersion: string | null;
|
|
36
|
+
/** Whether this exact home currently has a live signed-in credential. */
|
|
37
|
+
signedIn: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A live credential state, derived from the homes rather than the registry:
|
|
41
|
+
* - `connected` — at least one home for this identity is signed in.
|
|
42
|
+
* - `reconnect-needed` — the account is registered (or was created by connect)
|
|
43
|
+
* but no home currently holds a live credential; the label alone is not a
|
|
44
|
+
* connection. `agents accounts connect <name>` re-authenticates it.
|
|
45
|
+
*/
|
|
46
|
+
export type AccountConnectionState = 'connected' | 'reconnect-needed';
|
|
47
|
+
/** Account-first row for a native (harness-owned OAuth) identity. */
|
|
48
|
+
export interface NativeAccountCatalogRow {
|
|
49
|
+
kind: 'native';
|
|
50
|
+
agent: AgentId;
|
|
51
|
+
identityKey: string;
|
|
52
|
+
/** Registered account name/label, or null for an unnamed discovered login. */
|
|
53
|
+
name: string | null;
|
|
54
|
+
/** Registered stable account id, or null when unnamed. */
|
|
55
|
+
id: string | null;
|
|
56
|
+
email: string | null;
|
|
57
|
+
display: string;
|
|
58
|
+
/**
|
|
59
|
+
* The LOCAL installation home for this identity on THIS box: the recorded
|
|
60
|
+
* connect home when installed here, else the local home carrying the login.
|
|
61
|
+
* Null when no local home exists (the identity may be connected on another
|
|
62
|
+
* box). Per-host by construction — never a label another box minted.
|
|
63
|
+
*/
|
|
64
|
+
home: string | null;
|
|
65
|
+
/** Every installed home carrying this identity (secondary diagnostics). */
|
|
66
|
+
installations: AccountHome[];
|
|
67
|
+
/** Whether the harness's configured default account points at this identity. */
|
|
68
|
+
isDefault: boolean;
|
|
69
|
+
state: AccountConnectionState;
|
|
70
|
+
}
|
|
71
|
+
/** Account-first row for a durable provider (API-key / token) account bundle. */
|
|
72
|
+
export interface ProviderAccountCatalogRow {
|
|
73
|
+
kind: 'provider';
|
|
74
|
+
name: string;
|
|
75
|
+
id: string;
|
|
76
|
+
provider: string;
|
|
77
|
+
auth: string;
|
|
78
|
+
baseUrl?: string;
|
|
79
|
+
}
|
|
80
|
+
export type AccountCatalogRow = NativeAccountCatalogRow | ProviderAccountCatalogRow;
|
|
81
|
+
export interface AccountCatalog {
|
|
82
|
+
native: NativeAccountCatalogRow[];
|
|
83
|
+
provider: ProviderAccountCatalogRow[];
|
|
84
|
+
}
|
|
85
|
+
/** One installed home probed for its native identity — the raw input to the fold. */
|
|
86
|
+
export interface NativeHomeRow {
|
|
87
|
+
agent: AgentId;
|
|
88
|
+
label: string;
|
|
89
|
+
releaseVersion: string | null;
|
|
90
|
+
accountKey: string | null;
|
|
91
|
+
email: string | null;
|
|
92
|
+
signedIn: boolean;
|
|
93
|
+
}
|
|
94
|
+
/** Probe every installed home of every inspectable harness for its native identity. */
|
|
95
|
+
export declare function collectNativeHomeRows(): Promise<NativeHomeRow[]>;
|
|
96
|
+
/** The subset of Meta the pure builder reads — the registered native accounts + defaults. */
|
|
97
|
+
type CatalogMeta = Pick<Meta, 'accounts' | 'deviceAccounts'>;
|
|
98
|
+
/**
|
|
99
|
+
* Fold installed homes + the registry into account-first native rows (pure).
|
|
100
|
+
*
|
|
101
|
+
* Every home carrying an identity contributes an `AccountHome`; the identity is
|
|
102
|
+
* the group key. A registered account (name/id/connect home) is merged onto its
|
|
103
|
+
* identity, and — crucially — a registered account whose identity has NO live
|
|
104
|
+
* home still appears, as `reconnect-needed`, so a stale/expired login is never
|
|
105
|
+
* silently dropped from the list nor shown as connected.
|
|
106
|
+
*/
|
|
107
|
+
export declare function buildNativeCatalog(rows: NativeHomeRow[], meta: CatalogMeta, globalDefault?: (agent: AgentId) => string | null): NativeAccountCatalogRow[];
|
|
108
|
+
/**
|
|
109
|
+
* Wire the real collectors to the pure builder. This is the canonical
|
|
110
|
+
* account-first read model `view.ts` renders: native identities (account +
|
|
111
|
+
* connection first, release/home secondary) plus the durable provider bundles.
|
|
112
|
+
*/
|
|
113
|
+
export declare function loadAccountCatalog(): Promise<AccountCatalog>;
|
|
114
|
+
export {};
|