@phnx-labs/agents-cli 1.20.70 → 1.20.72

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.
Files changed (80) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/dist/bin/agents +0 -0
  3. package/dist/commands/apply.js +36 -3
  4. package/dist/commands/check.js +3 -1
  5. package/dist/commands/commands.js +2 -0
  6. package/dist/commands/exec.js +12 -0
  7. package/dist/commands/fleet-capture.d.ts +14 -0
  8. package/dist/commands/fleet-capture.js +107 -0
  9. package/dist/commands/fork.js +13 -7
  10. package/dist/commands/hosts.js +11 -0
  11. package/dist/commands/logs.js +38 -9
  12. package/dist/commands/mcp.js +2 -0
  13. package/dist/commands/resource-view.d.ts +2 -0
  14. package/dist/commands/resource-view.js +8 -0
  15. package/dist/commands/secrets.d.ts +1 -0
  16. package/dist/commands/secrets.js +24 -4
  17. package/dist/commands/sessions-tail.js +1 -2
  18. package/dist/commands/sessions.d.ts +6 -0
  19. package/dist/commands/sessions.js +14 -6
  20. package/dist/commands/skills.js +2 -0
  21. package/dist/commands/ssh.js +24 -6
  22. package/dist/commands/status.js +8 -2
  23. package/dist/commands/subagents.js +3 -1
  24. package/dist/commands/uninstall.d.ts +11 -0
  25. package/dist/commands/uninstall.js +158 -0
  26. package/dist/commands/usage.d.ts +13 -0
  27. package/dist/commands/usage.js +50 -28
  28. package/dist/commands/utils.d.ts +29 -0
  29. package/dist/commands/utils.js +24 -0
  30. package/dist/commands/versions.js +120 -11
  31. package/dist/index.js +5 -3
  32. package/dist/lib/commands.d.ts +10 -0
  33. package/dist/lib/commands.js +31 -7
  34. package/dist/lib/daemon.d.ts +9 -0
  35. package/dist/lib/daemon.js +43 -0
  36. package/dist/lib/devices/connect.d.ts +13 -0
  37. package/dist/lib/devices/connect.js +20 -0
  38. package/dist/lib/devices/fleet.d.ts +36 -3
  39. package/dist/lib/devices/fleet.js +42 -4
  40. package/dist/lib/devices/sync.d.ts +30 -0
  41. package/dist/lib/devices/sync.js +50 -0
  42. package/dist/lib/doctor-diff.js +38 -16
  43. package/dist/lib/fleet/apply.d.ts +4 -0
  44. package/dist/lib/fleet/apply.js +28 -5
  45. package/dist/lib/fleet/capture.d.ts +35 -0
  46. package/dist/lib/fleet/capture.js +51 -0
  47. package/dist/lib/fleet/manifest.d.ts +6 -0
  48. package/dist/lib/fleet/manifest.js +24 -1
  49. package/dist/lib/fleet/types.d.ts +24 -1
  50. package/dist/lib/git.d.ts +14 -0
  51. package/dist/lib/git.js +39 -1
  52. package/dist/lib/hosts/logs.d.ts +12 -0
  53. package/dist/lib/hosts/logs.js +18 -0
  54. package/dist/lib/menubar/install-menubar.d.ts +12 -0
  55. package/dist/lib/menubar/install-menubar.js +26 -13
  56. package/dist/lib/secrets/agent.d.ts +5 -0
  57. package/dist/lib/secrets/agent.js +35 -3
  58. package/dist/lib/secrets/bundles.js +28 -0
  59. package/dist/lib/secrets/index.d.ts +14 -0
  60. package/dist/lib/secrets/index.js +36 -5
  61. package/dist/lib/secrets/session-store.d.ts +90 -0
  62. package/dist/lib/secrets/session-store.js +221 -0
  63. package/dist/lib/session/render.d.ts +9 -1
  64. package/dist/lib/session/render.js +35 -4
  65. package/dist/lib/share/capture.d.ts +2 -0
  66. package/dist/lib/share/capture.js +12 -5
  67. package/dist/lib/shims.d.ts +27 -0
  68. package/dist/lib/shims.js +29 -3
  69. package/dist/lib/skills.d.ts +8 -1
  70. package/dist/lib/skills.js +11 -1
  71. package/dist/lib/startup/command-registry.d.ts +1 -0
  72. package/dist/lib/startup/command-registry.js +2 -0
  73. package/dist/lib/types.d.ts +5 -1
  74. package/dist/lib/uninstall.d.ts +84 -0
  75. package/dist/lib/uninstall.js +347 -0
  76. package/dist/lib/usage.d.ts +13 -1
  77. package/dist/lib/usage.js +32 -14
  78. package/dist/lib/versions.d.ts +16 -0
  79. package/dist/lib/versions.js +40 -3
  80. package/package.json +1 -1
@@ -108,6 +108,14 @@ export declare function renderConversationMarkdown(events: SessionEvent[], opts?
108
108
  * `{ session, events }` wrapper so top-level fields (`plan`, `prUrl`, …) that
109
109
  * live on `SessionMeta` are visible without re-parsing events. Legacy
110
110
  * callers that pass no meta still get a bare `SessionEvent[]` array.
111
+ *
112
+ * Secrets are redacted by default (`opts.redact !== false`), matching
113
+ * `renderConversationMarkdown` — the JSON path must not be the one output
114
+ * format that leaks credentials. Redaction covers both events AND the session
115
+ * meta (e.g. `topic`, a verbatim first-message excerpt). Pass `{ redact: false }`
116
+ * for `--no-redact`.
111
117
  */
112
- export declare function renderJson(events: SessionEvent[], meta?: SessionMeta): string;
118
+ export declare function renderJson(events: SessionEvent[], meta?: SessionMeta, opts?: {
119
+ redact?: boolean;
120
+ }): string;
113
121
  export {};
@@ -913,19 +913,50 @@ export function renderConversationMarkdown(events, opts = {}) {
913
913
  }
914
914
  return parts.join('\n\n');
915
915
  }
916
+ /**
917
+ * Deep-redact every string reachable from a JSON value, preserving structure.
918
+ * `redactSecrets` only rewrites secret-shaped substrings, so non-secret strings
919
+ * (paths, URLs, ids) and non-strings (numbers, booleans) pass through unchanged.
920
+ * Applied to the whole `--json` payload so every free-text field — event
921
+ * `content`/`command`/`output`/`args`, and meta `topic`/`label`/`plan`/`todos`
922
+ * — is covered, including fields added later.
923
+ */
924
+ function redactDeep(value, sanitize) {
925
+ if (typeof value === 'string')
926
+ return sanitize(value);
927
+ if (Array.isArray(value))
928
+ return value.map((item) => redactDeep(item, sanitize));
929
+ if (value && typeof value === 'object') {
930
+ const out = {};
931
+ for (const [key, val] of Object.entries(value))
932
+ out[key] = redactDeep(val, sanitize);
933
+ return out;
934
+ }
935
+ return value;
936
+ }
916
937
  /**
917
938
  * Render one session's JSON output — the shape `agents sessions <id> --json`
918
939
  * emits. When `meta` is provided (the standard path), returns a
919
940
  * `{ session, events }` wrapper so top-level fields (`plan`, `prUrl`, …) that
920
941
  * live on `SessionMeta` are visible without re-parsing events. Legacy
921
942
  * callers that pass no meta still get a bare `SessionEvent[]` array.
943
+ *
944
+ * Secrets are redacted by default (`opts.redact !== false`), matching
945
+ * `renderConversationMarkdown` — the JSON path must not be the one output
946
+ * format that leaks credentials. Redaction covers both events AND the session
947
+ * meta (e.g. `topic`, a verbatim first-message excerpt). Pass `{ redact: false }`
948
+ * for `--no-redact`.
922
949
  */
923
- export function renderJson(events, meta) {
950
+ export function renderJson(events, meta, opts = {}) {
951
+ const redact = opts.redact !== false;
952
+ const sanitize = (text) => redactSecrets(text);
953
+ const safeEvents = redact ? events.map((event) => redactDeep(event, sanitize)) : events;
924
954
  if (!meta)
925
- return JSON.stringify(events, null, 2);
955
+ return JSON.stringify(safeEvents, null, 2);
926
956
  // Strip internal-only bookkeeping fields the listing --json path also strips.
927
- const { _matchedTerms, _bm25Score, _remote, ...session } = meta;
928
- return JSON.stringify({ session, events }, null, 2);
957
+ const { _matchedTerms, _bm25Score, _remote, ...rest } = meta;
958
+ const session = redact ? redactDeep(rest, sanitize) : rest;
959
+ return JSON.stringify({ session, events: safeEvents }, null, 2);
929
960
  }
930
961
  /** Replace the home directory prefix with ~ for trace display. */
931
962
  function shortenPathTrace(p) {
@@ -21,6 +21,8 @@ export declare const OG_HEIGHT = 630;
21
21
  export declare const OG_SCALE = 2;
22
22
  /** Ordered list of candidate Chromium-family binaries to try for headless capture. */
23
23
  export declare function candidateBrowsers(): string[];
24
+ /** Newest-first Chromium binaries under the Playwright / Puppeteer download caches. */
25
+ export declare function scanCaches(home?: string, platform?: NodeJS.Platform): string[];
24
26
  /**
25
27
  * Screenshot `htmlPath`'s top 1200×630 (its hero) to a PNG buffer, or null if no
26
28
  * headless-capable browser is available or every candidate fails. Never throws —
@@ -55,22 +55,29 @@ export function candidateBrowsers() {
55
55
  return out;
56
56
  }
57
57
  /** Newest-first Chromium binaries under the Playwright / Puppeteer download caches. */
58
- function scanCaches() {
59
- const home = os.homedir();
60
- const roots = os.platform() === 'darwin'
58
+ export function scanCaches(home = os.homedir(), platform = os.platform()) {
59
+ const roots = platform === 'darwin'
61
60
  ? [
62
61
  path.join(home, 'Library/Caches/ms-playwright'),
63
62
  path.join(home, '.cache/ms-playwright'),
64
63
  path.join(home, '.cache/puppeteer/chrome'),
65
64
  ]
66
65
  : [path.join(home, '.cache/ms-playwright'), path.join(home, '.cache/puppeteer/chrome')];
67
- const rel = os.platform() === 'darwin'
66
+ const rel = platform === 'darwin'
68
67
  ? [
69
68
  'chrome-mac/Chromium.app/Contents/MacOS/Chromium',
70
69
  'chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing',
71
70
  'chrome-mac-x64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing',
71
+ // Playwright's newer headless-shell packages (chromium_headless_shell-*) —
72
+ // a raw binary, not an .app bundle.
73
+ 'chrome-headless-shell-mac-arm64/chrome-headless-shell',
74
+ 'chrome-headless-shell-mac-x64/chrome-headless-shell',
72
75
  ]
73
- : ['chrome-linux/chrome', 'chrome-linux64/chrome'];
76
+ : [
77
+ 'chrome-linux/chrome',
78
+ 'chrome-linux64/chrome',
79
+ 'chrome-headless-shell-linux64/chrome-headless-shell',
80
+ ];
74
81
  const found = [];
75
82
  for (const root of roots) {
76
83
  let entries;
@@ -153,6 +153,27 @@ export declare function removeShim(agent: AgentId): boolean;
153
153
  * self-update under the shim).
154
154
  */
155
155
  export declare const VERSIONED_ALIAS_SCHEMA_VERSION = 13;
156
+ /**
157
+ * Agents whose config directory can be relocated by an environment variable
158
+ * (the per-agent `managedEnv` block in `generateVersionedAliasScript` below).
159
+ *
160
+ * Only these can be installed with `agents add --isolated`: the versioned alias
161
+ * points that env var at the copy's private home, so the copy never reads or
162
+ * writes the user's real `~/.<agent>`. Every other agent isolates ONLY by
163
+ * adopting `~/.<agent>` via a symlink — which an isolated install deliberately
164
+ * skips — so an isolated copy would silently fall back to (and mutate) the real
165
+ * config dir. For those agents `--isolated` is refused up front.
166
+ *
167
+ * KEEP IN SYNC with the `managedEnv` switch in `generateVersionedAliasScript`.
168
+ * The colocated test `shims.isolation-capability.test.ts` enforces this.
169
+ */
170
+ export declare const CONFIG_ENV_ISOLATED_AGENTS: readonly AgentId[];
171
+ /**
172
+ * Whether an agent supports a clean `--isolated` install — i.e. its config
173
+ * location can be redirected by an env var so the isolated copy stays fully
174
+ * separate from the user's real `~/.<agent>`. See {@link CONFIG_ENV_ISOLATED_AGENTS}.
175
+ */
176
+ export declare function supportsIsolatedInstall(agent: AgentId): boolean;
156
177
  /**
157
178
  * Generate a versioned alias script that directly execs a specific version.
158
179
  * e.g., claude@2.0.65 -> directly runs that version's binary
@@ -209,6 +230,11 @@ export declare function removeVersionedAlias(agent: AgentId, version: string): b
209
230
  * Check if a versioned alias exists (the on-disk artifact for this platform).
210
231
  */
211
232
  export declare function versionedAliasExists(agent: AgentId, version: string): boolean;
233
+ /**
234
+ * Get the path to the agent's config directory in HOME.
235
+ * e.g., ~/.claude for claude, ~/.codex for codex
236
+ */
237
+ export declare function getAgentConfigPath(agent: AgentId): string;
212
238
  /**
213
239
  * Read the user's configured Codex model from their active `~/.codex/config.toml`.
214
240
  *
@@ -449,6 +475,7 @@ export declare function hasAliasShadowingShim(agent: AgentId, overrides?: {
449
475
  * Check if shims directory is in PATH.
450
476
  */
451
477
  export declare function isShimsInPath(): boolean;
478
+ export declare function stripShimPathLines(content: string, shimsDir: string): string;
452
479
  /**
453
480
  * Get shell configuration instructions for adding shims to PATH.
454
481
  */
package/dist/lib/shims.js CHANGED
@@ -785,6 +785,29 @@ function assertSafeVersion(version) {
785
785
  throw new Error(`Refusing to generate shim for unsafe version: ${JSON.stringify(version)}`);
786
786
  }
787
787
  }
788
+ /**
789
+ * Agents whose config directory can be relocated by an environment variable
790
+ * (the per-agent `managedEnv` block in `generateVersionedAliasScript` below).
791
+ *
792
+ * Only these can be installed with `agents add --isolated`: the versioned alias
793
+ * points that env var at the copy's private home, so the copy never reads or
794
+ * writes the user's real `~/.<agent>`. Every other agent isolates ONLY by
795
+ * adopting `~/.<agent>` via a symlink — which an isolated install deliberately
796
+ * skips — so an isolated copy would silently fall back to (and mutate) the real
797
+ * config dir. For those agents `--isolated` is refused up front.
798
+ *
799
+ * KEEP IN SYNC with the `managedEnv` switch in `generateVersionedAliasScript`.
800
+ * The colocated test `shims.isolation-capability.test.ts` enforces this.
801
+ */
802
+ export const CONFIG_ENV_ISOLATED_AGENTS = ['claude', 'codex', 'copilot', 'grok', 'kimi'];
803
+ /**
804
+ * Whether an agent supports a clean `--isolated` install — i.e. its config
805
+ * location can be redirected by an env var so the isolated copy stays fully
806
+ * separate from the user's real `~/.<agent>`. See {@link CONFIG_ENV_ISOLATED_AGENTS}.
807
+ */
808
+ export function supportsIsolatedInstall(agent) {
809
+ return CONFIG_ENV_ISOLATED_AGENTS.includes(agent);
810
+ }
788
811
  /**
789
812
  * Generate a versioned alias script that directly execs a specific version.
790
813
  * e.g., claude@2.0.65 -> directly runs that version's binary
@@ -1039,7 +1062,7 @@ export function versionedAliasExists(agent, version) {
1039
1062
  * Get the path to the agent's config directory in HOME.
1040
1063
  * e.g., ~/.claude for claude, ~/.codex for codex
1041
1064
  */
1042
- function getAgentConfigPath(agent) {
1065
+ export function getAgentConfigPath(agent) {
1043
1066
  const agentConfig = AGENTS[agent];
1044
1067
  const home = process.env.AGENTS_REAL_HOME || os.homedir();
1045
1068
  return agentConfig.configDir.replace(os.homedir(), home);
@@ -1567,7 +1590,10 @@ export function getConfigSymlinkVersion(agent) {
1567
1590
  if (!stat.isSymbolicLink()) {
1568
1591
  return null;
1569
1592
  }
1570
- const target = fs.readlinkSync(configPath);
1593
+ // Normalize separators so this matches on Windows too — readlinkSync there
1594
+ // returns backslash paths, which the forward-slash-only regex never matched
1595
+ // (misclassifying an owned symlink as foreign, e.g. in `agents uninstall`).
1596
+ const target = fs.readlinkSync(configPath).replace(/\\/g, '/');
1571
1597
  // Extract version from path like ~/.agents/versions/claude/2.0.65/home/.claude
1572
1598
  const match = target.match(/versions\/[^/]+\/([^/]+)\/home/);
1573
1599
  return match ? match[1] : null;
@@ -2194,7 +2220,7 @@ function isShimPathCommandLine(line, shimsDir) {
2194
2220
  }
2195
2221
  return trimmed.startsWith('export PATH=') || trimmed.startsWith('fish_add_path ');
2196
2222
  }
2197
- function stripShimPathLines(content, shimsDir) {
2223
+ export function stripShimPathLines(content, shimsDir) {
2198
2224
  const lines = content.split('\n');
2199
2225
  const kept = [];
2200
2226
  for (let i = 0; i < lines.length; i++) {
@@ -92,7 +92,14 @@ export declare function listAllSkills(): string[];
92
92
  */
93
93
  export declare function getVersionSkillsDir(agent: AgentId, version: string): string;
94
94
  /**
95
- * List skill names installed in a specific version home.
95
+ * List real skill names installed in a specific version home.
96
+ *
97
+ * Command-as-skill WRAPPERS (a `skills/<name>/SKILL.md` carrying the
98
+ * `agents_command` marker — how command-runtime agents like Codex/Kimi install
99
+ * commands) are excluded: they are commands, reconciled by the commands diff
100
+ * against the command source, not skills. Counting them here made the skill
101
+ * orphan/extra detectors flag every command wrapper (tickets, prune, swarm-plan,
102
+ * …) as an unmanaged skill — a false positive that `prune cleanup` would delete.
96
103
  */
97
104
  export declare function listSkillsInVersionHome(agent: AgentId, version: string): string[];
98
105
  export interface VersionSkillDiff {
@@ -14,6 +14,7 @@ import { AGENTS, ensureSkillsDir, agentConfigDirName } from './agents.js';
14
14
  import { capableAgents, isCapable } from './capabilities.js';
15
15
  import { getUserSkillsDir, getSkillsDir as getSystemSkillsDir, getProjectAgentsDir, getEnabledExtraRepos, getTrashSkillsDir } from './state.js';
16
16
  import { getEffectiveHome, getVersionHomePath, listInstalledVersions } from './versions.js';
17
+ import { listCommandSkillsInVersion } from './command-skills.js';
17
18
  import { emit } from './events.js';
18
19
  const HOME = os.homedir();
19
20
  /** User-scoped skills dir (~/.agents/skills/). Used for installs. */
@@ -392,16 +393,25 @@ export function getVersionSkillsDir(agent, version) {
392
393
  return path.join(home, agentConfigDirName(agent), 'skills');
393
394
  }
394
395
  /**
395
- * List skill names installed in a specific version home.
396
+ * List real skill names installed in a specific version home.
397
+ *
398
+ * Command-as-skill WRAPPERS (a `skills/<name>/SKILL.md` carrying the
399
+ * `agents_command` marker — how command-runtime agents like Codex/Kimi install
400
+ * commands) are excluded: they are commands, reconciled by the commands diff
401
+ * against the command source, not skills. Counting them here made the skill
402
+ * orphan/extra detectors flag every command wrapper (tickets, prune, swarm-plan,
403
+ * …) as an unmanaged skill — a false positive that `prune cleanup` would delete.
396
404
  */
397
405
  export function listSkillsInVersionHome(agent, version) {
398
406
  const dir = getVersionSkillsDir(agent, version);
399
407
  if (!fs.existsSync(dir))
400
408
  return [];
409
+ const commandWrappers = new Set(listCommandSkillsInVersion(path.join(getVersionHomePath(agent, version), agentConfigDirName(agent))));
401
410
  return fs.readdirSync(dir, { withFileTypes: true })
402
411
  .filter((e) => e.isDirectory() && !e.name.startsWith('.'))
403
412
  .filter((e) => fs.existsSync(path.join(dir, e.name, 'SKILL.md')))
404
413
  .map((e) => e.name)
414
+ .filter((name) => !commandWrappers.has(name))
405
415
  .sort();
406
416
  }
407
417
  /**
@@ -85,6 +85,7 @@ export declare const loadPull: ModuleLoader;
85
85
  export declare const loadPush: ModuleLoader;
86
86
  export declare const loadRepo: ModuleLoader;
87
87
  export declare const loadSetup: ModuleLoader;
88
+ export declare const loadUninstall: ModuleLoader;
88
89
  export declare const loadSessions: ModuleLoader;
89
90
  export declare const loadTeams: ModuleLoader;
90
91
  export declare const loadCloud: ModuleLoader;
@@ -63,6 +63,7 @@ export const loadPull = async () => (await import('../../commands/pull.js')).reg
63
63
  export const loadPush = async () => (await import('../../commands/push.js')).registerPushCommand;
64
64
  export const loadRepo = async () => (await import('../../commands/repo.js')).registerRepoCommands;
65
65
  export const loadSetup = async () => (await import('../../commands/setup.js')).registerSetupCommand;
66
+ export const loadUninstall = async () => (await import('../../commands/uninstall.js')).registerUninstallCommands;
66
67
  export const loadSessions = async () => (await import('../../commands/sessions.js')).registerSessionsCommands;
67
68
  export const loadTeams = async () => (await import('../../commands/teams.js')).registerTeamsCommands;
68
69
  export const loadCloud = async () => (await import('../../commands/cloud.js')).registerCloudCommands;
@@ -174,6 +175,7 @@ export const COMMAND_LOADERS = {
174
175
  repos: [loadRepo],
175
176
  repo: [loadRepo],
176
177
  setup: [loadSetup],
178
+ uninstall: [loadUninstall],
177
179
  sessions: [loadSessions],
178
180
  teams: [loadTeams],
179
181
  cloud: [loadCloud],
@@ -664,12 +664,16 @@ export interface Meta {
664
664
  * concurrent runs read silently — set it `false` to force a prompt on every read.
665
665
  * `holdMs` caps how long an unlocked/auto-cached bundle is held before the next
666
666
  * read re-prompts (default 7 days; e.g. 86400000 for a 24h cap). Clamped to
667
- * [1 minute, 30 days]. */
667
+ * [1 minute, 30 days]. `durable` (default off) makes every `agents secrets
668
+ * unlock` survive sleep + reboot as well as upgrade/restart — the same effect as
669
+ * passing `--durable` per unlock; off means the secure split default (survive
670
+ * upgrade/restart, re-lock on sleep). */
668
671
  secrets?: {
669
672
  policy?: 'always' | 'daily';
670
673
  agent?: {
671
674
  auto?: boolean;
672
675
  holdMs?: number;
676
+ durable?: boolean;
673
677
  };
674
678
  };
675
679
  /** Spend guardrails (issue #346). User-global caps; project agents.yaml overrides. */
@@ -0,0 +1,84 @@
1
+ import type { AgentId } from './types.js';
2
+ /** What the uninstall intends to do with one agent's config directory. */
3
+ export type ConfigAction = {
4
+ agent: AgentId;
5
+ realPath: string;
6
+ kind: 'restore-backup';
7
+ source: string;
8
+ } | {
9
+ agent: AgentId;
10
+ realPath: string;
11
+ kind: 'restore-version-home';
12
+ source: string;
13
+ } | {
14
+ agent: AgentId;
15
+ realPath: string;
16
+ kind: 'remove-dangling';
17
+ } | {
18
+ agent: AgentId;
19
+ realPath: string;
20
+ kind: 'leave-real';
21
+ } | {
22
+ agent: AgentId;
23
+ realPath: string;
24
+ kind: 'leave-foreign';
25
+ } | {
26
+ agent: AgentId;
27
+ realPath: string;
28
+ kind: 'absent';
29
+ };
30
+ /** A home-level file symlink (e.g. `~/.claude.json`) agents-cli owns. */
31
+ export interface HomeFileAction {
32
+ realPath: string;
33
+ /** Resolved symlink target whose contents are copied back to `realPath`. */
34
+ source: string;
35
+ }
36
+ /** The full, read-only plan describing what an uninstall would change. */
37
+ export interface UninstallPlan {
38
+ isInstalled: boolean;
39
+ agentsDir: string;
40
+ legacySymlink: string | null;
41
+ configs: ConfigAction[];
42
+ homeFiles: HomeFileAction[];
43
+ launchers: string[];
44
+ rcFiles: string[];
45
+ }
46
+ /** Structured result of an executed uninstall (for reporting). */
47
+ export interface UninstallResult {
48
+ restoredConfigs: Array<{
49
+ agent: AgentId;
50
+ realPath: string;
51
+ }>;
52
+ removedDanglingConfigs: Array<{
53
+ agent: AgentId;
54
+ realPath: string;
55
+ }>;
56
+ restoredHomeFiles: string[];
57
+ releasedLaunchers: string[];
58
+ cleanedRcFiles: string[];
59
+ agentsDir: {
60
+ path: string;
61
+ disposition: 'moved' | 'purged' | 'absent';
62
+ movedTo?: string;
63
+ };
64
+ legacySymlinkRemoved: boolean;
65
+ /** True when `--purge` was requested but downgraded to move-aside after errors. */
66
+ purgeDowngraded: boolean;
67
+ errors: string[];
68
+ }
69
+ /**
70
+ * Build a read-only plan of everything a complete uninstall would change.
71
+ * Performs no mutations; safe to run for `--dry-run` and to print for confirm.
72
+ */
73
+ export declare function planUninstall(): UninstallPlan;
74
+ /**
75
+ * Execute a plan built by {@link planUninstall}. Restores adopted config dirs
76
+ * and home files, releases adopted launchers, strips shim PATH lines, then
77
+ * disposes of `~/.agents` — moved aside to `~/.agents.removed-<ts>` (recoverable)
78
+ * by default, or hard-deleted when `purge` is set. Config restore always runs
79
+ * before disposal because the backups live inside `~/.agents`.
80
+ */
81
+ export declare function executeUninstall(plan: UninstallPlan, opts: {
82
+ purge?: boolean;
83
+ timestamp: number;
84
+ }): UninstallResult;