@lore-co/cli 0.1.27 → 0.1.29

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 (91) hide show
  1. package/README.md +59 -9
  2. package/dist/agents.d.ts +3 -1
  3. package/dist/agents.d.ts.map +1 -1
  4. package/dist/agents.js +31 -7
  5. package/dist/agents.js.map +1 -1
  6. package/dist/cli.d.ts +8 -0
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +182 -71
  9. package/dist/cli.js.map +1 -1
  10. package/dist/command-help.d.ts +34 -0
  11. package/dist/command-help.d.ts.map +1 -0
  12. package/dist/command-help.js +227 -0
  13. package/dist/command-help.js.map +1 -0
  14. package/dist/demo.d.ts.map +1 -1
  15. package/dist/demo.js +15 -2
  16. package/dist/demo.js.map +1 -1
  17. package/dist/generated-assets.d.ts +9 -4
  18. package/dist/generated-assets.d.ts.map +1 -1
  19. package/dist/generated-assets.js +5 -4
  20. package/dist/generated-assets.js.map +1 -1
  21. package/dist/guard.d.ts.map +1 -1
  22. package/dist/guard.js +4 -3
  23. package/dist/guard.js.map +1 -1
  24. package/dist/recovery-cloud-workspace.d.ts +11 -0
  25. package/dist/recovery-cloud-workspace.d.ts.map +1 -0
  26. package/dist/recovery-cloud-workspace.js +113 -0
  27. package/dist/recovery-cloud-workspace.js.map +1 -0
  28. package/dist/recovery-cloud.d.ts +31 -0
  29. package/dist/recovery-cloud.d.ts.map +1 -0
  30. package/dist/recovery-cloud.js +242 -0
  31. package/dist/recovery-cloud.js.map +1 -0
  32. package/dist/recovery-codex.d.ts +5 -0
  33. package/dist/recovery-codex.d.ts.map +1 -0
  34. package/dist/recovery-codex.js +111 -0
  35. package/dist/recovery-codex.js.map +1 -0
  36. package/dist/recovery-copilot.d.ts +14 -0
  37. package/dist/recovery-copilot.d.ts.map +1 -0
  38. package/dist/recovery-copilot.js +120 -0
  39. package/dist/recovery-copilot.js.map +1 -0
  40. package/dist/recovery-desktop.d.ts +7 -0
  41. package/dist/recovery-desktop.d.ts.map +1 -0
  42. package/dist/recovery-desktop.js +179 -0
  43. package/dist/recovery-desktop.js.map +1 -0
  44. package/dist/recovery-external.d.ts +61 -0
  45. package/dist/recovery-external.d.ts.map +1 -0
  46. package/dist/recovery-external.js +98 -0
  47. package/dist/recovery-external.js.map +1 -0
  48. package/dist/recovery-hooks.d.ts +5 -0
  49. package/dist/recovery-hooks.d.ts.map +1 -0
  50. package/dist/recovery-hooks.js +115 -0
  51. package/dist/recovery-hooks.js.map +1 -0
  52. package/dist/recovery-hosts.d.ts +13 -0
  53. package/dist/recovery-hosts.d.ts.map +1 -0
  54. package/dist/recovery-hosts.js +263 -0
  55. package/dist/recovery-hosts.js.map +1 -0
  56. package/dist/recovery-polytoken.d.ts +14 -0
  57. package/dist/recovery-polytoken.d.ts.map +1 -0
  58. package/dist/recovery-polytoken.js +194 -0
  59. package/dist/recovery-polytoken.js.map +1 -0
  60. package/dist/recovery-remote.d.ts +8 -0
  61. package/dist/recovery-remote.d.ts.map +1 -0
  62. package/dist/recovery-remote.js +135 -0
  63. package/dist/recovery-remote.js.map +1 -0
  64. package/dist/recovery-state.d.ts +25 -1
  65. package/dist/recovery-state.d.ts.map +1 -1
  66. package/dist/recovery-state.js +69 -15
  67. package/dist/recovery-state.js.map +1 -1
  68. package/dist/recovery-t3.d.ts +17 -0
  69. package/dist/recovery-t3.d.ts.map +1 -0
  70. package/dist/recovery-t3.js +58 -0
  71. package/dist/recovery-t3.js.map +1 -0
  72. package/dist/recovery-transfer.d.ts +22 -0
  73. package/dist/recovery-transfer.d.ts.map +1 -0
  74. package/dist/recovery-transfer.js +228 -0
  75. package/dist/recovery-transfer.js.map +1 -0
  76. package/dist/recovery.d.ts +9 -1
  77. package/dist/recovery.d.ts.map +1 -1
  78. package/dist/recovery.js +187 -47
  79. package/dist/recovery.js.map +1 -1
  80. package/dist/runtime.d.ts +1 -0
  81. package/dist/runtime.d.ts.map +1 -1
  82. package/dist/runtime.js +1 -0
  83. package/dist/runtime.js.map +1 -1
  84. package/dist/self-host.d.ts +1 -1
  85. package/dist/self-host.js +2 -2
  86. package/dist/uninstall.d.ts +19 -0
  87. package/dist/uninstall.d.ts.map +1 -0
  88. package/dist/uninstall.js +128 -0
  89. package/dist/uninstall.js.map +1 -0
  90. package/dist/update.js +3 -3
  91. package/package.json +3 -3
package/dist/cli.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { COMMAND_GUIDES, ROOT_HELP, helpTopic } from "./command-help.js";
2
3
  import { runShareCommand } from "./share.js";
3
4
  import { codexApprovalDetail, inspectCodexHookApproval } from "./codex-hook-approval.js";
4
5
  import { claudeOnlyCommand, disableDevinCli, runDevinCliHook } from "./devin-cli.js";
@@ -17,7 +18,7 @@ import { runAskCommand } from "./ask.js";
17
18
  import { runRememberCommand } from "./remember.js";
18
19
  import { runGuardCommand } from "./guard.js";
19
20
  import { runContinueCommand, runResumeCommand } from "./handoff.js";
20
- import { disableRecovery, prepareRecoveryInstallation, runRecoveryCommand, runRecoveryHook, runWrappedAgent } from "./recovery.js";
21
+ import { disableRecovery, prepareAutomaticRecovery, prepareRecoveryInstallation, runRecoveryCommand, runRecoveryHook, runWrappedAgent } from "./recovery.js";
21
22
  import { LAUNCHABLE_AGENTS, isLaunchable, isRecoverySource } from "./agents.js";
22
23
  import { runAttachCommand, runLiveHostCommand, runLiveRunCommand, } from "./live.js";
23
24
  import { runMachinesCommand } from "./machine.js";
@@ -26,6 +27,7 @@ import { runDevinCommand } from "./devin.js";
26
27
  import { runHostCommand } from "./host.js";
27
28
  import { runDemoCommand } from "./demo.js";
28
29
  import { updateCommand } from "./update.js";
30
+ import { UNINSTALL_HELP, uninstallCommand } from "./uninstall.js";
29
31
  import { runSelfHostCommand } from "./self-host.js";
30
32
  import { ReliabilityStore, ReliabilityStoreError, integrationInvocationStateKey, } from "./reliability-store.js";
31
33
  import { IS_STANDALONE_BINARY, LORE_VERSION } from "./version.js";
@@ -78,62 +80,6 @@ const AGENT_MANIFESTS = {
78
80
  opencode: OPENCODE_RELIABILITY_INTEGRATION,
79
81
  polytoken: POLYTOKEN_RELIABILITY_INTEGRATION,
80
82
  };
81
- const ROOT_HELP = `lore
82
- Connect local coding agents to Lore shared engineering memory.
83
-
84
- Usage:
85
- lore <command> [options]
86
-
87
- Commands:
88
- connect Configure Lore and install native agent integrations
89
- share Get a private workspace link to a learning, session, or delivery
90
- ask Query shared knowledge and prior session context
91
- remember Save a durable fact, convention, correction, or decision
92
- guard Control Context Guard (off, assist, enforce)
93
- resume Continue previous work with saved Lore context
94
- continue Hand the current work to a different agent (--on <agent>)
95
- run Run a coding agent with configured recovery
96
- recovery Inspect recovery metrics, record feedback, or disable recovery
97
- attach Reconnect to a live Lore-managed session (--from <machine>)
98
- machines List registered machines and their live sessions
99
- github Prepare/post GitHub reviews and observe corrections
100
- devin Start and manage Lore-enabled Devin sessions
101
- host Call auditable APIs from external agent hosts
102
- status Show connector and agent integration state
103
- doctor Diagnose configuration, integrations, and API reachability
104
- self-host Run a version-pinned local Docker Compose deployment
105
- demo Prove a file-scoped Claude-to-Codex handoff
106
- update Install the latest Lore CLI binary
107
- disconnect Remove Lore-owned integrations and local credentials
108
- hook Internal native hook handler
109
-
110
- Discover:
111
- lore connect --help
112
- lore ask --help
113
- lore remember --help
114
- lore guard --help
115
- lore resume --help
116
- lore continue --help
117
- lore attach --help
118
- lore status --help
119
- lore doctor --help
120
- lore self-host --help
121
- lore disconnect --help
122
- lore host --help
123
-
124
- Examples:
125
- lore connect --token "$LORE_WORKSPACE_TOKEN"
126
- lore ask "How should payment retries work?"
127
- lore remember "Use pnpm for this repository." --repo acme/app
128
- lore resume
129
- lore continue --on codex
130
- lore connect github --repo owner/repository
131
- lore status --json
132
- lore doctor
133
- lore demo
134
- lore update
135
- lore devin --help
136
- `;
137
83
  const CONNECT_HELP = `lore connect
138
84
  Authorize this device and idempotently install agent integrations.
139
85
 
@@ -150,6 +96,7 @@ Options:
150
96
  --agent <name> claude, codex, copilot, copilot-cli, copilot-vscode, cowork, cursor, opencode, polytoken, or t3code
151
97
  --timeout-ms <ms> Hook request timeout, 250-10000 (default: 2500)
152
98
  --fallback <agent> claude, codex, opencode, devin, cursor, copilot-cli, polytoken
99
+ --no-recovery Disable automatic handoff setup (otherwise enabled with two installed clients)
153
100
  --json Print machine-readable output
154
101
  --help Show this command's help
155
102
 
@@ -990,7 +937,7 @@ function parseConnectorConfig(value) {
990
937
  timeoutMs,
991
938
  };
992
939
  }
993
- async function readConnectorConfig(paths) {
940
+ export async function readConnectorConfig(paths) {
994
941
  try {
995
942
  return parseConnectorConfig(JSON.parse(await readFile(paths.config, "utf8")));
996
943
  }
@@ -1567,6 +1514,10 @@ function parseConnectArguments(args) {
1567
1514
  parsed.openBrowser = false;
1568
1515
  continue;
1569
1516
  }
1517
+ if (argument === "--no-recovery") {
1518
+ parsed.recovery = false;
1519
+ continue;
1520
+ }
1570
1521
  if (argument !== "--url" &&
1571
1522
  argument !== "--dashboard-url" &&
1572
1523
  argument !== "--token" &&
@@ -1638,8 +1589,8 @@ export async function connectWithCredential(options) {
1638
1589
  throw new Error("Lore agent integrations currently support macOS and Linux");
1639
1590
  }
1640
1591
  const paths = getLorePaths(options.home, options.home === undefined ? process.env : {}, options.cwd);
1641
- const installRecovery = options.fallback === undefined ? undefined :
1642
- await prepareRecoveryInstallation(paths, options.fallback, options.home === undefined ? process.env : { ...process.env, ZDOTDIR: paths.home });
1592
+ if (options.recovery === false && options.fallback)
1593
+ throw new Error("--no-recovery cannot be combined with --fallback.");
1643
1594
  const existing = await readConnectorConfig(paths);
1644
1595
  const apiUrl = normalizeApiUrl(options.apiUrl);
1645
1596
  const dashboardUrl = options.dashboardUrl === undefined
@@ -1651,6 +1602,17 @@ export async function connectWithCredential(options) {
1651
1602
  }
1652
1603
  const timeoutMs = options.timeoutMs ?? existing?.timeoutMs ?? 2_500;
1653
1604
  const identity = await authenticatedIdentity(apiUrl, token.trim(), timeoutMs);
1605
+ const recoveryEnv = options.home === undefined ? process.env : { ...process.env, ZDOTDIR: paths.home };
1606
+ let recoveryWarning;
1607
+ const externalRecoveryHost = options.agents.some(agent => ["t3code", "cowork", "copilot-vscode"].includes(agent));
1608
+ const installRecovery = options.fallback === undefined ? options.recovery === true ?
1609
+ await prepareAutomaticRecovery(paths, recoveryEnv).catch((error) => { recoveryWarning = `Automatic recovery setup: ${error.message}`; return undefined; }) : undefined :
1610
+ await prepareRecoveryInstallation(paths, options.fallback, recoveryEnv).catch((error) => {
1611
+ // A desktop host can be the source even when only its destination is on PATH.
1612
+ if (externalRecoveryHost && error.message.startsWith("No supported source client is installed."))
1613
+ return undefined;
1614
+ throw error;
1615
+ });
1654
1616
  const recoveryAgents = installRecovery ? [...installRecovery.sources, installRecovery.fallback]
1655
1617
  .filter((agent) => agent !== "devin") : [];
1656
1618
  const requestedAgents = [...new Set([...options.agents, ...recoveryAgents])];
@@ -1743,7 +1705,12 @@ export async function connectWithCredential(options) {
1743
1705
  await credentialStore.transferPendingTo(reliabilityStore);
1744
1706
  }
1745
1707
  await reliabilityStore.releaseAuthBlocked(now);
1746
- const recoveryNotice = installRecovery ? await installRecovery() : undefined;
1708
+ const recoveryNotice = installRecovery ? await installRecovery() : recoveryWarning;
1709
+ const hostRecoveryWarnings = options.recovery === true || options.fallback
1710
+ ? await (await import("./recovery-hosts.js")).installExternalRecovery(paths, options.agents, options.cwd ?? process.cwd(), options.fallback)
1711
+ .catch((error) => [`Host recovery setup: ${error.message}`]) : [];
1712
+ if (options.recovery === false)
1713
+ await disableRecovery(paths.home);
1747
1714
  return {
1748
1715
  connected: true,
1749
1716
  apiUrl: config.apiUrl,
@@ -1752,7 +1719,7 @@ export async function connectWithCredential(options) {
1752
1719
  config: paths.config,
1753
1720
  changedHookFiles,
1754
1721
  backups,
1755
- warnings: [...requestedExpansion.warnings, ...(agents.includes("codex")
1722
+ warnings: [...requestedExpansion.warnings, ...hostRecoveryWarnings, ...(agents.includes("codex")
1756
1723
  ? ["Codex requires hook approval. Open /hooks in Codex and review any new or changed Lore hooks, then start a fresh session. Run lore doctor to verify approval and the first invocation."]
1757
1724
  : [])],
1758
1725
  ...(recoveryNotice ? { recoveryNotice } : {}),
@@ -1807,6 +1774,8 @@ async function requestDeviceAuthorization(input) {
1807
1774
  const intervalMs = authorization.data.intervalSeconds * 1_000;
1808
1775
  while (Date.now() < expiresAt) {
1809
1776
  await delay(intervalMs);
1777
+ if (Date.now() >= expiresAt)
1778
+ break;
1810
1779
  let pollResponse;
1811
1780
  try {
1812
1781
  pollResponse = await fetch(`${input.apiUrl}/v1/device-authorizations/poll`, {
@@ -1831,10 +1800,23 @@ async function requestDeviceAuthorization(input) {
1831
1800
  }
1832
1801
  continue;
1833
1802
  }
1803
+ if (pollResponse.status >= 500) {
1804
+ await pollResponse.body?.cancel().catch(() => { });
1805
+ continue;
1806
+ }
1834
1807
  if (!pollResponse.ok) {
1835
1808
  throw new Error("Lore browser approval failed. Run connect --login again.");
1836
1809
  }
1837
- const result = PollDeviceAuthorizationResponseSchema.safeParse(await pollResponse.json().catch(() => null));
1810
+ let body;
1811
+ try {
1812
+ body = await pollResponse.json();
1813
+ }
1814
+ catch (error) {
1815
+ if (!(error instanceof SyntaxError))
1816
+ continue;
1817
+ throw new Error("Lore returned an incompatible device authorization response.");
1818
+ }
1819
+ const result = PollDeviceAuthorizationResponseSchema.safeParse(body);
1838
1820
  if (!result.success) {
1839
1821
  throw new Error("Lore returned an incompatible device authorization response.");
1840
1822
  }
@@ -1842,15 +1824,38 @@ async function requestDeviceAuthorization(input) {
1842
1824
  continue;
1843
1825
  }
1844
1826
  if (result.data.status === "approved") {
1845
- return result.data.token;
1827
+ return { token: result.data.token, deviceCode: authorization.data.deviceCode };
1846
1828
  }
1847
1829
  if (result.data.status === "denied") {
1848
1830
  throw new Error("Lore access was denied in the dashboard.");
1849
1831
  }
1832
+ if (result.data.status === "consumed") {
1833
+ throw new Error("Lore approval was already completed, but this login did not receive its credential. Run connect --login again.");
1834
+ }
1850
1835
  throw new Error("Lore browser approval expired. Run connect --login again.");
1851
1836
  }
1852
1837
  throw new Error("Lore browser approval expired. Run connect --login again.");
1853
1838
  }
1839
+ async function acknowledgeDeviceAuthorization(apiUrl, authorization) {
1840
+ // The credential is already saved. A lost acknowledgement must not undo
1841
+ // login; old servers may lack this endpoint, and unacknowledged grants expire.
1842
+ for (let attempt = 0; attempt < 3; attempt += 1) {
1843
+ try {
1844
+ const response = await fetch(`${apiUrl}/v1/device-authorizations/acknowledge`, {
1845
+ method: "POST",
1846
+ headers: { accept: "application/json", "content-type": "application/json" },
1847
+ body: JSON.stringify(authorization),
1848
+ signal: AbortSignal.timeout(2_000),
1849
+ });
1850
+ await response.body?.cancel();
1851
+ if (response.ok || (response.status >= 400 && response.status < 500))
1852
+ return;
1853
+ }
1854
+ catch {
1855
+ // Retry a lost response with the same secrets.
1856
+ }
1857
+ }
1858
+ }
1854
1859
  async function connectCommand(args) {
1855
1860
  const parsed = parseConnectArguments(args);
1856
1861
  if (parsed === null) {
@@ -1876,25 +1881,29 @@ async function connectCommand(args) {
1876
1881
  if (parsed.login && hasProvidedToken) {
1877
1882
  throw new Error("--login cannot be combined with an explicit or environment workspace token. Unset it or use the token directly.");
1878
1883
  }
1879
- const token = parsed.login
1884
+ const authorization = parsed.login
1880
1885
  ? await requestDeviceAuthorization({
1881
1886
  apiUrl,
1882
1887
  ...(dashboardUrl === undefined ? {} : { dashboardUrl }),
1883
1888
  agents: parsed.agents,
1884
1889
  openBrowser: parsed.openBrowser,
1885
1890
  })
1886
- : configured;
1891
+ : undefined;
1892
+ const token = authorization?.token ?? configured;
1887
1893
  if (token === undefined || token.trim() === "") {
1888
1894
  throw new Error("Workspace token is required. Use --login, --token <token>, LORE_WORKSPACE_TOKEN, or LORE_TOKEN.");
1889
1895
  }
1890
1896
  const result = await connectWithCredential({
1891
1897
  ...(parsed.fallback === undefined ? {} : { fallback: parsed.fallback }),
1898
+ recovery: parsed.recovery ?? true,
1892
1899
  apiUrl,
1893
1900
  ...(dashboardUrl === undefined ? {} : { dashboardUrl }),
1894
1901
  token,
1895
1902
  agents: parsed.agents,
1896
1903
  ...(parsed.timeoutMs === undefined ? {} : { timeoutMs: parsed.timeoutMs }),
1897
1904
  });
1905
+ if (authorization)
1906
+ await acknowledgeDeviceAuthorization(apiUrl, authorization);
1898
1907
  writeResult(result, parsed.json, `access authorized; integrations installed: ${result.agents.join(", ")}\napi_url: ${result.apiUrl}\nconfig: ${result.config}\nRestart your agent and complete a turn, then run lore doctor to verify recording.\n${result.recoveryNotice ? `${result.recoveryNotice}\n` : ""}${result.warnings.map((warning) => `warning: ${warning}\n`).join("")}`);
1899
1908
  }
1900
1909
  async function queueCount(paths) {
@@ -2217,7 +2226,10 @@ async function disconnectCommand(args) {
2217
2226
  if (parsed === null) {
2218
2227
  return;
2219
2228
  }
2220
- const paths = getLorePaths();
2229
+ const result = await disconnectInstallation();
2230
+ writeResult(result, parsed.json, `disconnected: yes\nchanged_hook_files: ${result.changedHookFiles.length}\n`);
2231
+ }
2232
+ export async function disconnectInstallation(paths = getLorePaths(), preservePendingState = false) {
2221
2233
  await disableRecovery(paths.home);
2222
2234
  await disableDevinCli(paths.home);
2223
2235
  const config = await readConnectorConfig(paths);
@@ -2267,11 +2279,12 @@ async function disconnectCommand(args) {
2267
2279
  rm(paths.runtimeInvocationHealthWriter, { force: true }),
2268
2280
  rm(paths.runtimeVersion, { force: true }),
2269
2281
  rm(paths.runtimePackage, { force: true }),
2270
- rm(paths.state, { recursive: true, force: true }),
2271
- rm(paths.queue, { recursive: true, force: true }),
2282
+ ...(preservePendingState ? [] : [
2283
+ rm(paths.state, { recursive: true, force: true }),
2284
+ rm(paths.queue, { recursive: true, force: true }),
2285
+ ]),
2272
2286
  ]);
2273
- const result = { connected: false, changedHookFiles, backups };
2274
- writeResult(result, parsed.json, `disconnected: yes\nchanged_hook_files: ${changedHookFiles.length}\n`);
2287
+ return { connected: false, changedHookFiles, backups };
2275
2288
  }
2276
2289
  async function apiChecks(config) {
2277
2290
  const readinessUrl = `${config.apiUrl}/health/ready`;
@@ -2536,8 +2549,95 @@ async function doctorCommand(args) {
2536
2549
  process.exitCode = 1;
2537
2550
  }
2538
2551
  }
2552
+ async function printCommandHelp(path) {
2553
+ const topic = helpTopic(path);
2554
+ process.stdout.write(`When to use: ${COMMAND_GUIDES[topic.command]}\n\n`);
2555
+ if (topic.text) {
2556
+ process.stdout.write(topic.text);
2557
+ return;
2558
+ }
2559
+ const args = [...(topic.subcommand ? [topic.subcommand] : []), "--help"];
2560
+ // Help never loads workspace credentials, makes requests, or runs an agent.
2561
+ switch (topic.command) {
2562
+ case "connect":
2563
+ process.stdout.write(CONNECT_HELP);
2564
+ return;
2565
+ case "status":
2566
+ process.stdout.write(STATUS_HELP);
2567
+ return;
2568
+ case "doctor":
2569
+ process.stdout.write(DOCTOR_HELP);
2570
+ return;
2571
+ case "disconnect":
2572
+ process.stdout.write(DISCONNECT_HELP);
2573
+ return;
2574
+ case "uninstall":
2575
+ process.stdout.write(UNINSTALL_HELP);
2576
+ return;
2577
+ case "share":
2578
+ await runShareCommand(args, null);
2579
+ return;
2580
+ case "ask":
2581
+ await runAskCommand(args, null);
2582
+ return;
2583
+ case "remember":
2584
+ await runRememberCommand(args, null);
2585
+ return;
2586
+ case "guard":
2587
+ await runGuardCommand(args, null);
2588
+ return;
2589
+ case "resume":
2590
+ await runResumeCommand(args, null, getLorePaths());
2591
+ return;
2592
+ case "continue":
2593
+ await runContinueCommand(args, null, getLorePaths());
2594
+ return;
2595
+ case "attach":
2596
+ await runAttachCommand(args, null, getLorePaths());
2597
+ return;
2598
+ case "machines":
2599
+ await runMachinesCommand(args, null, getLorePaths());
2600
+ return;
2601
+ case "host":
2602
+ await runHostCommand(args);
2603
+ return;
2604
+ case "devin":
2605
+ if (topic.subcommand === "cli") {
2606
+ process.stdout.write((await import("./devin-cli.js")).DEVIN_CLI_HELP);
2607
+ return;
2608
+ }
2609
+ await runDevinCommand(args);
2610
+ return;
2611
+ case "self-host":
2612
+ await runSelfHostCommand(args);
2613
+ return;
2614
+ case "demo":
2615
+ await runDemoCommand(args, null);
2616
+ return;
2617
+ case "update":
2618
+ await updateCommand(args);
2619
+ return;
2620
+ default: throw new Error(`Help is unavailable for ${topic.command}`);
2621
+ }
2622
+ }
2539
2623
  export async function runCli(args = process.argv.slice(2)) {
2540
2624
  const command = args[0];
2625
+ if (command === "help") {
2626
+ const path = args.slice(1);
2627
+ if (!path.length || (path.length === 1 && ["--help", "-h"].includes(path[0])))
2628
+ process.stdout.write(ROOT_HELP);
2629
+ else
2630
+ await printCommandHelp(path);
2631
+ return;
2632
+ }
2633
+ const separator = args.indexOf("--");
2634
+ const helpIndex = args.findIndex((value, index) => index > 0 && (separator < 0 || index < separator) && (value === "--help" || value === "-h"));
2635
+ if (helpIndex > 0) {
2636
+ const subcommand = args[1];
2637
+ const grouped = ["connect", "guard", "recovery", "github", "devin", "host", "self-host"].includes(command ?? "");
2638
+ await printCommandHelp([command, ...(grouped && subcommand && !subcommand.startsWith("-") ? [subcommand] : [])]);
2639
+ return;
2640
+ }
2541
2641
  if (command === undefined || command === "--help" || command === "-h") {
2542
2642
  process.stdout.write(ROOT_HELP);
2543
2643
  return;
@@ -2577,6 +2677,11 @@ export async function runCli(args = process.argv.slice(2)) {
2577
2677
  await runWrappedAgent(commandArgs, getLorePaths(process.env.LORE_HOME));
2578
2678
  return;
2579
2679
  case "recovery":
2680
+ if (commandArgs[0] === "cloud") {
2681
+ const paths = getLorePaths(process.env.LORE_HOME);
2682
+ await (await import("./recovery-cloud.js")).runCloudRecoveryCommand(commandArgs.slice(1), paths, await readConnectorConfig(paths));
2683
+ return;
2684
+ }
2580
2685
  await runRecoveryCommand(commandArgs, getLorePaths(process.env.LORE_HOME));
2581
2686
  return;
2582
2687
  case "recovery-hook":
@@ -2638,6 +2743,12 @@ export async function runCli(args = process.argv.slice(2)) {
2638
2743
  case "disconnect":
2639
2744
  await disconnectCommand(commandArgs);
2640
2745
  return;
2746
+ case "uninstall":
2747
+ await uninstallCommand(commandArgs, {
2748
+ disconnect: () => disconnectInstallation(getLorePaths(), true),
2749
+ retainedDirectory: getLorePaths().loreDirectory,
2750
+ });
2751
+ return;
2641
2752
  case "devin-cli-hook":
2642
2753
  try {
2643
2754
  const result = await runDevinCliHook(getLorePaths(process.env.LORE_HOME).home);