@lore-co/cli 0.2.14 → 0.2.16

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 (74) hide show
  1. package/README.md +78 -4
  2. package/dist/capture-scope.d.ts +12 -0
  3. package/dist/capture-scope.d.ts.map +1 -0
  4. package/dist/capture-scope.js +45 -0
  5. package/dist/capture-scope.js.map +1 -0
  6. package/dist/cli.d.ts +7 -1
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +126 -38
  9. package/dist/cli.js.map +1 -1
  10. package/dist/command-help.d.ts +2 -1
  11. package/dist/command-help.d.ts.map +1 -1
  12. package/dist/command-help.js +4 -1
  13. package/dist/command-help.js.map +1 -1
  14. package/dist/connect-project.d.ts +15 -0
  15. package/dist/connect-project.d.ts.map +1 -0
  16. package/dist/connect-project.js +87 -0
  17. package/dist/connect-project.js.map +1 -0
  18. package/dist/connections.d.ts +3 -1
  19. package/dist/connections.d.ts.map +1 -1
  20. package/dist/connections.js +6 -1
  21. package/dist/connections.js.map +1 -1
  22. package/dist/devin-cli.d.ts.map +1 -1
  23. package/dist/devin-cli.js +14 -6
  24. package/dist/devin-cli.js.map +1 -1
  25. package/dist/devin.d.ts.map +1 -1
  26. package/dist/devin.js +5 -2
  27. package/dist/devin.js.map +1 -1
  28. package/dist/generated-assets.d.ts +5 -4
  29. package/dist/generated-assets.d.ts.map +1 -1
  30. package/dist/generated-assets.js +5 -4
  31. package/dist/generated-assets.js.map +1 -1
  32. package/dist/host.d.ts.map +1 -1
  33. package/dist/host.js +5 -5
  34. package/dist/host.js.map +1 -1
  35. package/dist/reliability-store.d.ts +5 -0
  36. package/dist/reliability-store.d.ts.map +1 -1
  37. package/dist/reliability-store.js +9 -1
  38. package/dist/reliability-store.js.map +1 -1
  39. package/dist/remember.d.ts.map +1 -1
  40. package/dist/remember.js +8 -1
  41. package/dist/remember.js.map +1 -1
  42. package/dist/retrieval-task.d.ts +20 -0
  43. package/dist/retrieval-task.d.ts.map +1 -0
  44. package/dist/retrieval-task.js +78 -0
  45. package/dist/retrieval-task.js.map +1 -0
  46. package/dist/runtime-redaction.d.ts +2 -0
  47. package/dist/runtime-redaction.d.ts.map +1 -0
  48. package/dist/runtime-redaction.js +69 -0
  49. package/dist/runtime-redaction.js.map +1 -0
  50. package/dist/runtime.d.ts +2 -2
  51. package/dist/runtime.d.ts.map +1 -1
  52. package/dist/runtime.js +129 -89
  53. package/dist/runtime.js.map +1 -1
  54. package/dist/self-host.d.ts +2 -1
  55. package/dist/self-host.d.ts.map +1 -1
  56. package/dist/self-host.js +4 -3
  57. package/dist/self-host.js.map +1 -1
  58. package/dist/signed-snapshot.d.ts.map +1 -1
  59. package/dist/signed-snapshot.js +2 -0
  60. package/dist/signed-snapshot.js.map +1 -1
  61. package/dist/skill-command.d.ts +20 -0
  62. package/dist/skill-command.d.ts.map +1 -0
  63. package/dist/skill-command.js +215 -0
  64. package/dist/skill-command.js.map +1 -0
  65. package/dist/skill-guidance.d.ts +2 -0
  66. package/dist/skill-guidance.d.ts.map +1 -0
  67. package/dist/skill-guidance.js +7 -0
  68. package/dist/skill-guidance.js.map +1 -0
  69. package/dist/skills.d.ts +16 -0
  70. package/dist/skills.d.ts.map +1 -0
  71. package/dist/skills.js +126 -0
  72. package/dist/skills.js.map +1 -0
  73. package/dist/update.js +3 -3
  74. package/package.json +7 -3
package/dist/cli.js CHANGED
@@ -4,6 +4,8 @@ import { reportCommand, runTelemetryCommand, telemetryEnabled, trackedCommand, T
4
4
  import { secureWindowsDirectory, windowsPermissionsArePrivate } from "./windows-permissions.js";
5
5
  import { COMMAND_GUIDES, ROOT_HELP, helpTopic } from "./command-help.js";
6
6
  import { runShareCommand } from "./share.js";
7
+ import { resolveConnectProject, validateConnectProjectOptions } from "./connect-project.js";
8
+ import { captureDirectories } from "./capture-scope.js";
7
9
  import { prepareConnection, persistConnection, listConnections } from "./connections.js";
8
10
  import { connectionReferences, resolveLocalConnection } from "./directory-policy.js";
9
11
  import { withDirectoryLock } from "./reliability-store.js";
@@ -23,7 +25,9 @@ import { runHook, } from "./runtime.js";
23
25
  import { readInvocationHealthWriteInput, runInvocationHealthWrite, } from "./invocation-health-writer.js";
24
26
  import { runAskCommand } from "./ask.js";
25
27
  import { runRememberCommand } from "./remember.js";
28
+ import { installLoreSkill, removeLoreSkill } from "./skills.js";
26
29
  import { runApproachCommand } from "./approach.js";
30
+ import { runSkillCommand } from "./skill-command.js";
27
31
  import { runGuardCommand } from "./guard.js";
28
32
  import { runContinueCommand, runResumeCommand } from "./handoff.js";
29
33
  import { disableRecovery, prepareAutomaticRecovery, prepareRecoveryInstallation, runRecoveryCommand, runRecoveryHook, runWrappedAgent } from "./recovery.js";
@@ -97,6 +101,11 @@ Usage:
97
101
 
98
102
  Options:
99
103
  --name <name> Name this account/workspace connection (e.g. work)
104
+ --current-project Confirm and record the current Git project
105
+ --choose-folder Open a folder picker on this machine
106
+ --repository <repo> Verify the selected folder matches this GitHub repository
107
+ --folder <path> Record only in this folder and subfolders (repeatable)
108
+ --all-projects Record in all projects allowed by your workspace
100
109
  --url <url> Lore API base URL (default: https://api.uselore.co)
101
110
  --dashboard-url <url> Lore dashboard URL (default: https://uselore.co for hosted Lore)
102
111
  --login Approve access securely in the Lore dashboard
@@ -109,8 +118,12 @@ Options:
109
118
  --json Print machine-readable output
110
119
  --help Show this command's help
111
120
 
121
+ New connections stay paused until you choose a project, a folder, or --all-projects.
122
+ Reconnecting without a capture option keeps your saved folder rules.
123
+ Folder choices control capture; learning scope controls where knowledge applies.
124
+
112
125
  Examples:
113
- lore connect --login --agent claude
126
+ lore connect --login --agent claude --folder .
114
127
  lore connect --login --agent cowork
115
128
  lore connect --token "$LORE_WORKSPACE_TOKEN" --agent claude
116
129
  lore connect --url http://localhost:3004 --token dev-token --agent codex
@@ -1418,10 +1431,15 @@ async function installRuntime(paths) {
1418
1431
  rm(paths.runtimeSignedSnapshot, { force: true }),
1419
1432
  rm(paths.runtimeCanonicalJson, { force: true }),
1420
1433
  rm(resolve(dirname(paths.runtime), "guard-evaluation.js"), { force: true }),
1434
+ rm(resolve(dirname(paths.runtime), "capture-conflict.js"), { force: true }),
1421
1435
  rm(resolve(dirname(paths.runtime), "image-bytes.js"), { force: true }),
1422
1436
  rm(resolve(dirname(paths.runtime), "image-capture.js"), { force: true }),
1423
1437
  rm(resolve(dirname(paths.runtime), "retained-context.js"), { force: true }),
1438
+ rm(resolve(dirname(paths.runtime), "retrieval-task.js"), { force: true }),
1439
+ rm(resolve(dirname(paths.runtime), "runtime-redaction.js"), { force: true }),
1440
+ rm(resolve(dirname(paths.runtime), "user-message.js"), { force: true }),
1424
1441
  rm(resolve(dirname(paths.runtime), "approach-guidance.js"), { force: true }),
1442
+ rm(resolve(dirname(paths.runtime), "skill-guidance.js"), { force: true }),
1425
1443
  rm(paths.runtimeContextFallback, { force: true }),
1426
1444
  rm(paths.runtimeInvocationHealthWriter, { force: true }),
1427
1445
  rm(paths.runtimeVersion, { force: true }),
@@ -1449,12 +1467,17 @@ async function installRuntime(paths) {
1449
1467
  }
1450
1468
  const runtimeVersion = `export const RUNTIME_VERSION = ${JSON.stringify(LORE_VERSION)};\n`;
1451
1469
  const runtimeFiles = {
1452
- "lore-hook.mjs": runtime.replaceAll("@lore-co/core/guard-evaluation", "./guard-evaluation.js"),
1470
+ "lore-hook.mjs": runtime.replaceAll("@lore-co/core/guard-evaluation", "./guard-evaluation.js").replaceAll("@lore-co/core/capture-conflict", "./capture-conflict.js"),
1453
1471
  "guard-evaluation.js": guardEvaluation,
1472
+ "capture-conflict.js": await readFile(fileURLToPath(import.meta.resolve("@lore-co/core/capture-conflict")), "utf8"),
1454
1473
  "image-bytes.js": imageBytes,
1455
1474
  "image-capture.js": imageCapture,
1456
1475
  "retained-context.js": await readFile(resolve(sourceDirectory, "retained-context.js"), "utf8"),
1476
+ "retrieval-task.js": (await readFile(resolve(sourceDirectory, "retrieval-task.js"), "utf8")).replaceAll("@lore-co/core/user-message", "./user-message.js"),
1477
+ "runtime-redaction.js": await readFile(resolve(sourceDirectory, "runtime-redaction.js"), "utf8"),
1478
+ "user-message.js": await readFile(fileURLToPath(import.meta.resolve("@lore-co/core/user-message")), "utf8"),
1457
1479
  "approach-guidance.js": await readFile(resolve(sourceDirectory, "approach-guidance.js"), "utf8"),
1480
+ "skill-guidance.js": await readFile(resolve(sourceDirectory, "skill-guidance.js"), "utf8"),
1458
1481
  "repository.js": repository,
1459
1482
  "repository-address.js": await readFile(resolve(dirname(fileURLToPath(import.meta.resolve("@lore-co/core/repository-location"))), "repository-address.js"), "utf8"),
1460
1483
  "directory-policy.js": (await readFile(fileURLToPath(import.meta.resolve("@lore-co/core/local-scope")), "utf8")).replaceAll("./repository-location.js", "./repository.js"),
@@ -1478,10 +1501,15 @@ async function installRuntime(paths) {
1478
1501
  };
1479
1502
  await Promise.all([
1480
1503
  atomicWrite(resolve(dirname(paths.runtime), "guard-evaluation.js"), guardEvaluation, 0o600),
1504
+ atomicWrite(resolve(dirname(paths.runtime), "capture-conflict.js"), runtimeFiles["capture-conflict.js"], 0o600),
1481
1505
  atomicWrite(resolve(dirname(paths.runtime), "image-bytes.js"), imageBytes, 0o600),
1482
1506
  atomicWrite(resolve(dirname(paths.runtime), "image-capture.js"), imageCapture, 0o600),
1483
1507
  atomicWrite(resolve(dirname(paths.runtime), "retained-context.js"), runtimeFiles["retained-context.js"], 0o600),
1508
+ atomicWrite(resolve(dirname(paths.runtime), "retrieval-task.js"), runtimeFiles["retrieval-task.js"], 0o600),
1509
+ atomicWrite(resolve(dirname(paths.runtime), "runtime-redaction.js"), runtimeFiles["runtime-redaction.js"], 0o600),
1510
+ atomicWrite(resolve(dirname(paths.runtime), "user-message.js"), runtimeFiles["user-message.js"], 0o600),
1484
1511
  atomicWrite(resolve(dirname(paths.runtime), "approach-guidance.js"), runtimeFiles["approach-guidance.js"], 0o600),
1512
+ atomicWrite(resolve(dirname(paths.runtime), "skill-guidance.js"), runtimeFiles["skill-guidance.js"], 0o600),
1485
1513
  atomicWrite(paths.runtimeRepository, repository, 0o600),
1486
1514
  atomicWrite(resolve(dirname(paths.runtime), "repository-address.js"), runtimeFiles["repository-address.js"], 0o600),
1487
1515
  atomicWrite(resolve(dirname(paths.runtime), "directory-policy.js"), runtimeFiles["directory-policy.js"], 0o600),
@@ -1555,7 +1583,21 @@ function parseConnectArguments(args) {
1555
1583
  parsed.recovery = false;
1556
1584
  continue;
1557
1585
  }
1558
- if (argument !== "--name" &&
1586
+ if (argument === "--current-project") {
1587
+ parsed.currentProject = true;
1588
+ continue;
1589
+ }
1590
+ if (argument === "--choose-folder") {
1591
+ parsed.chooseFolder = true;
1592
+ continue;
1593
+ }
1594
+ if (argument === "--all-projects") {
1595
+ parsed.allProjects = true;
1596
+ continue;
1597
+ }
1598
+ if (argument !== "--repository" &&
1599
+ argument !== "--folder" &&
1600
+ argument !== "--name" &&
1559
1601
  argument !== "--url" &&
1560
1602
  argument !== "--dashboard-url" &&
1561
1603
  argument !== "--token" &&
@@ -1566,7 +1608,13 @@ function parseConnectArguments(args) {
1566
1608
  }
1567
1609
  const [value, valueIndex] = valueAfter(args, index, argument);
1568
1610
  index = valueIndex;
1569
- if (argument === "--name") {
1611
+ if (argument === "--repository") {
1612
+ parsed.repository = value;
1613
+ }
1614
+ else if (argument === "--folder") {
1615
+ (parsed.folders ??= []).push(value);
1616
+ }
1617
+ else if (argument === "--name") {
1570
1618
  parsed.connectionName = value;
1571
1619
  }
1572
1620
  else if (argument === "--fallback") {
@@ -1597,6 +1645,7 @@ function parseConnectArguments(args) {
1597
1645
  throw new Error("--agent must be claude, codex, copilot, copilot-cli, copilot-vscode, cowork, cursor, opencode, polytoken, or t3code");
1598
1646
  }
1599
1647
  }
1648
+ validateConnectProjectOptions(parsed);
1600
1649
  if (parsed.login && parsed.token !== undefined) {
1601
1650
  throw new Error("--login and --token cannot be used together");
1602
1651
  }
@@ -1661,7 +1710,8 @@ async function connectWithCredentialLocked(options, identity) {
1661
1710
  ? existing.apiUrl === apiUrl && existing.token === token ? identity
1662
1711
  : await authenticatedIdentity(existing.apiUrl, existing.token, timeoutMs).catch(() => undefined)
1663
1712
  : undefined;
1664
- const connection = await prepareConnection(paths, identity, { apiUrl, token, ...(options.connectionName ? { connectionName: options.connectionName } : {}) }, previousIdentity);
1713
+ const directories = await captureDirectories(options, paths.home, options.cwd);
1714
+ const connection = await prepareConnection(paths, identity, { apiUrl, token, ...(directories === undefined ? {} : { directories }), ...(options.connectionName ? { connectionName: options.connectionName } : {}) }, previousIdentity);
1665
1715
  await secureWindowsDirectory(paths.loreDirectory);
1666
1716
  const recoveryEnv = options.home === undefined ? process.env : { ...process.env, ZDOTDIR: paths.home };
1667
1717
  let recoveryWarning;
@@ -1708,6 +1758,31 @@ async function connectWithCredentialLocked(options, identity) {
1708
1758
  cursorMcpDocuments.set(mcpPath, { document: mcp, merged: mergeLoreCursorMcp(mcp.exists ? mcp.value : {}, paths) });
1709
1759
  }
1710
1760
  }
1761
+ const agentConfigPaths = {};
1762
+ for (const agent of agents) {
1763
+ agentConfigPaths[agent] = installations
1764
+ .filter((installation) => installation.agent === agent)
1765
+ .map((installation) => installation.configPath);
1766
+ }
1767
+ const config = {
1768
+ ...connection.fields,
1769
+ version: 2,
1770
+ apiUrl,
1771
+ ...(dashboardUrl === undefined ? {} : { dashboardUrl }),
1772
+ workspaceId: identity.workspaceId,
1773
+ token: token.trim(),
1774
+ agents,
1775
+ agentConfigPaths,
1776
+ connectedAt: connection.profiles.find(value => value.connectionId === connection.fields.connectionId)?.connectedAt ?? now.toISOString(),
1777
+ timeoutMs,
1778
+ };
1779
+ const reliabilityStore = new ReliabilityStore(identity.workspaceId, {
1780
+ home: paths.home,
1781
+ connectionId: config.connectionId,
1782
+ });
1783
+ await reliabilityStore.initialize();
1784
+ // Folder restrictions must be durable before any new hook can run.
1785
+ await persistConnection(paths, config, connection.profiles);
1711
1786
  if (installations.some((installation) => targetFor(installation.agent).runtimeRequired)) {
1712
1787
  await installRuntime(paths);
1713
1788
  }
@@ -1735,30 +1810,6 @@ async function connectWithCredentialLocked(options, identity) {
1735
1810
  backups.push(result.backup);
1736
1811
  }
1737
1812
  }
1738
- const agentConfigPaths = {};
1739
- for (const agent of agents) {
1740
- agentConfigPaths[agent] = installations
1741
- .filter((installation) => installation.agent === agent)
1742
- .map((installation) => installation.configPath);
1743
- }
1744
- const config = {
1745
- ...connection.fields,
1746
- version: 2,
1747
- apiUrl,
1748
- ...(dashboardUrl === undefined ? {} : { dashboardUrl }),
1749
- workspaceId: identity.workspaceId,
1750
- token: token.trim(),
1751
- agents,
1752
- agentConfigPaths,
1753
- connectedAt: connection.profiles.find(value => value.connectionId === connection.fields.connectionId)?.connectedAt ?? now.toISOString(),
1754
- timeoutMs,
1755
- };
1756
- const reliabilityStore = new ReliabilityStore(identity.workspaceId, {
1757
- home: paths.home,
1758
- connectionId: config.connectionId,
1759
- });
1760
- await reliabilityStore.initialize();
1761
- await persistConnection(paths, config, connection.profiles);
1762
1813
  if (existing && !existing.connectionId && existing.apiUrl === apiUrl && existing.token === token) {
1763
1814
  await new ReliabilityStore(identity.workspaceId, { home: paths.home }).transferPendingTo(reliabilityStore);
1764
1815
  await reliabilityStore.migrateLegacyQueue(paths.queue);
@@ -1778,7 +1829,10 @@ async function connectWithCredentialLocked(options, identity) {
1778
1829
  .catch((error) => [`Host recovery setup: ${error.message}`]) : [];
1779
1830
  if (options.recovery === false)
1780
1831
  await disableRecovery(paths.home);
1832
+ const skills = await installLoreSkill(paths, installations);
1781
1833
  return {
1834
+ capture: { folders: config.directories?.include ?? null, excludedFolders: config.directories?.exclude ?? [] },
1835
+ skillFiles: skills.files,
1782
1836
  connected: true,
1783
1837
  apiUrl: config.apiUrl,
1784
1838
  identity,
@@ -1786,7 +1840,7 @@ async function connectWithCredentialLocked(options, identity) {
1786
1840
  config: paths.config,
1787
1841
  changedHookFiles,
1788
1842
  backups,
1789
- warnings: [...(connection.needsFolders ? [`Lore is off for ${config.connectionName} until you add folder rules. Run lore config open --connection ${config.connectionName}.`] : []), ...requestedExpansion.warnings, ...hostRecoveryWarnings, ...(agents.includes("codex")
1843
+ warnings: [...skills.warnings, ...(connection.needsFolders ? [`Lore is off for ${config.connectionName} until you choose where to record. Reconnect with --folder <path> or --all-projects, or run lore config open --connection ${config.connectionName}.`] : []), ...requestedExpansion.warnings, ...hostRecoveryWarnings, ...(agents.includes("codex")
1790
1844
  ? ["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."]
1791
1845
  : [])],
1792
1846
  ...(recoveryNotice ? { recoveryNotice } : {}),
@@ -1940,6 +1994,7 @@ async function connectCommand(args) {
1940
1994
  if (parsed.login && hasProvidedToken) {
1941
1995
  throw new Error("--login cannot be combined with an explicit or environment workspace token. Unset it or use the token directly.");
1942
1996
  }
1997
+ const capture = await resolveConnectProject(parsed, paths.home);
1943
1998
  const authorization = parsed.login
1944
1999
  ? await requestDeviceAuthorization({
1945
2000
  apiUrl,
@@ -1953,6 +2008,7 @@ async function connectCommand(args) {
1953
2008
  throw new Error("Workspace token is required. Use --login, --token <token>, LORE_WORKSPACE_TOKEN, or LORE_TOKEN.");
1954
2009
  }
1955
2010
  const result = await connectWithCredential({
2011
+ ...capture,
1956
2012
  ...(parsed.connectionName === undefined ? {} : { connectionName: parsed.connectionName }),
1957
2013
  ...(parsed.fallback === undefined ? {} : { fallback: parsed.fallback }),
1958
2014
  recovery: parsed.recovery ?? true,
@@ -1964,7 +2020,9 @@ async function connectCommand(args) {
1964
2020
  });
1965
2021
  if (authorization)
1966
2022
  await acknowledgeDeviceAuthorization(apiUrl, authorization);
1967
- 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("")}`);
2023
+ const captureSummary = result.capture.folders === null ? "all projects allowed by the workspace"
2024
+ : result.capture.folders.length ? result.capture.folders.join(", ") : "paused — choose folders with --folder <path>";
2025
+ writeResult(result, parsed.json, `access authorized; integrations installed: ${result.agents.join(", ")}\napi_url: ${result.apiUrl}\nconfig: ${result.config}\ncapture: ${captureSummary}${result.capture.excludedFolders.length ? `; excluded: ${result.capture.excludedFolders.join(", ")}` : ""}\nRestart your agent and complete a turn, then run lore doctor to verify recording.\n${result.skillFiles.length ? 'Choose lore in your agent’s skills to find knowledge or remember this session.\n' : ''}${result.recoveryNotice ? `${result.recoveryNotice}\n` : ""}${result.warnings.map((warning) => `warning: ${warning}\n`).join("")}`);
1968
2026
  }
1969
2027
  async function queueCount(paths) {
1970
2028
  try {
@@ -2049,10 +2107,15 @@ async function inspectRuntimeInstallation(required, paths) {
2049
2107
  const runtimeFiles = {
2050
2108
  "lore-hook.mjs": paths.runtime,
2051
2109
  "guard-evaluation.js": resolve(dirname(paths.runtime), "guard-evaluation.js"),
2110
+ "capture-conflict.js": resolve(dirname(paths.runtime), "capture-conflict.js"),
2052
2111
  "image-bytes.js": resolve(dirname(paths.runtime), "image-bytes.js"),
2053
2112
  "image-capture.js": resolve(dirname(paths.runtime), "image-capture.js"),
2054
2113
  "retained-context.js": resolve(dirname(paths.runtime), "retained-context.js"),
2114
+ "retrieval-task.js": resolve(dirname(paths.runtime), "retrieval-task.js"),
2115
+ "runtime-redaction.js": resolve(dirname(paths.runtime), "runtime-redaction.js"),
2116
+ "user-message.js": resolve(dirname(paths.runtime), "user-message.js"),
2055
2117
  "approach-guidance.js": resolve(dirname(paths.runtime), "approach-guidance.js"),
2118
+ "skill-guidance.js": resolve(dirname(paths.runtime), "skill-guidance.js"),
2056
2119
  "repository.js": paths.runtimeRepository,
2057
2120
  "repository-address.js": resolve(dirname(paths.runtime), "repository-address.js"),
2058
2121
  "directory-policy.js": resolve(dirname(paths.runtime), "directory-policy.js"),
@@ -2238,8 +2301,18 @@ async function getAgentStatus(agent, config, paths, runtime) {
2238
2301
  invocationError: invocation.error,
2239
2302
  };
2240
2303
  }
2304
+ // Diagnostics remain available when the current folder has capture disabled.
2305
+ // A fallback reads only the last saved connection; it never enables capture.
2306
+ async function diagnosticConfig(paths, cwd) {
2307
+ try {
2308
+ return await readConnectorConfig(paths, cwd);
2309
+ }
2310
+ catch {
2311
+ return readConnectorConfig(paths);
2312
+ }
2313
+ }
2241
2314
  async function statusData(paths, cwd) {
2242
- const config = await readConnectorConfig(paths, cwd);
2315
+ const config = await diagnosticConfig(paths, cwd);
2243
2316
  let configMode = null;
2244
2317
  try {
2245
2318
  const metadata = await stat(paths.config);
@@ -2256,6 +2329,7 @@ async function statusData(paths, cwd) {
2256
2329
  Promise.all(CONFIGURED_AGENT_NAMES.map(async (agent) => getAgentStatus(agent, config, paths, runtime))),
2257
2330
  ]);
2258
2331
  return {
2332
+ folderAccess: await checkDirectory(cwd, paths.home, config ?? undefined),
2259
2333
  schemaVersion: "lore-cli-status-v1",
2260
2334
  integrationContractVersion: RELIABILITY_INTEGRATION_CONTRACT_VERSION,
2261
2335
  connected: config !== null,
@@ -2290,7 +2364,7 @@ async function statusCommand(args) {
2290
2364
  const metricRate = (value) => value === null || value === undefined
2291
2365
  ? "-"
2292
2366
  : `${(value * 100).toFixed(1)}%`;
2293
- writeResult(data, parsed.json, `connected: ${data.connected ? "yes" : "no"}\napi_url: ${data.apiUrl ?? "-"}\nconfig_mode: ${data.configMode ?? "-"}\nruntime: ${data.runtimeRequired ? (data.runtimeInstalled ? `installed (${data.runtimeVersion})` : data.runtimeVersion === null ? "missing" : `stale (${data.runtimeVersion})`) : "not required"}\ndurable_outbox: ${data.reliability?.outbox.total ?? 0} ready=${data.reliability?.outbox.ready ?? 0} auth_blocked=${data.reliability?.outbox.authBlocked ?? 0} dead=${data.reliability?.outbox.dead ?? 0}\noldest_capture: ${data.reliability?.outbox.oldestEnqueuedAt ?? "-"}\ncontext_snapshots: ${data.reliability?.cache.context ?? 0}\noldest_context_snapshot: ${data.reliability?.cache.oldestContextWrittenAt ?? "-"}\npolicy_snapshots: ${data.reliability?.cache.policy ?? 0}\nlocal_context_fallback_rate: ${metricRate(data.reliability?.operational.retrieval.fallbackRate)}\nlocal_guard_coverage: ${metricRate(data.reliability?.operational.guard.coverageRate)}\nlocal_guard_failures: ${data.reliability?.operational.guard.failures ?? 0}\nlocal_retrieval_failures: ${data.reliability?.operational.retrieval.failed ?? 0}\n${data.reliabilityError === null ? "" : `reliability_error: ${data.reliabilityError}\n`}${agentLines}\n${externalSurfaceLines}\n`);
2367
+ writeResult(data, parsed.json, `connected: ${data.connected ? "yes" : "no"}\ncurrent_folder: ${data.folderAccess.allowed ? "allowed" : "off"} (${data.folderAccess.reason})\napi_url: ${data.apiUrl ?? "-"}\nconfig_mode: ${data.configMode ?? "-"}\nruntime: ${data.runtimeRequired ? (data.runtimeInstalled ? `installed (${data.runtimeVersion})` : data.runtimeVersion === null ? "missing" : `stale (${data.runtimeVersion})`) : "not required"}\ndurable_outbox: ${data.reliability?.outbox.total ?? 0} ready=${data.reliability?.outbox.ready ?? 0} auth_blocked=${data.reliability?.outbox.authBlocked ?? 0} dead=${data.reliability?.outbox.dead ?? 0}\noldest_capture: ${data.reliability?.outbox.oldestEnqueuedAt ?? "-"}\ncontext_snapshots: ${data.reliability?.cache.context ?? 0}\noldest_context_snapshot: ${data.reliability?.cache.oldestContextWrittenAt ?? "-"}\npolicy_snapshots: ${data.reliability?.cache.policy ?? 0}\nlocal_context_fallback_rate: ${metricRate(data.reliability?.operational.retrieval.fallbackRate)}\nlocal_guard_coverage: ${metricRate(data.reliability?.operational.guard.coverageRate)}\nlocal_guard_failures: ${data.reliability?.operational.guard.failures ?? 0}\nlocal_retrieval_failures: ${data.reliability?.operational.retrieval.failed ?? 0}\n${data.reliabilityError === null ? "" : `reliability_error: ${data.reliabilityError}\n`}${agentLines}\n${externalSurfaceLines}\n`);
2294
2368
  }
2295
2369
  async function disconnectCommand(args) {
2296
2370
  const parsed = parseOutputArguments(args, DISCONNECT_HELP, "disconnect");
@@ -2336,6 +2410,7 @@ export async function disconnectInstallation(paths = getLorePaths(), preservePen
2336
2410
  backups.push(result.backup);
2337
2411
  }
2338
2412
  }
2413
+ await removeLoreSkill(paths, installations);
2339
2414
  await Promise.all([
2340
2415
  rm(paths.config, { force: true }),
2341
2416
  rm(resolve(paths.loreDirectory, "connections"), { recursive: true, force: true }),
@@ -2348,9 +2423,14 @@ export async function disconnectInstallation(paths = getLorePaths(), preservePen
2348
2423
  rm(paths.runtimeCanonicalJson, { force: true }),
2349
2424
  rm(paths.runtimeContextFallback, { force: true }),
2350
2425
  rm(resolve(dirname(paths.runtime), "guard-evaluation.js"), { force: true }),
2426
+ rm(resolve(dirname(paths.runtime), "capture-conflict.js"), { force: true }),
2351
2427
  rm(resolve(dirname(paths.runtime), "image-bytes.js"), { force: true }),
2352
2428
  rm(resolve(dirname(paths.runtime), "image-capture.js"), { force: true }),
2353
2429
  rm(resolve(dirname(paths.runtime), "retained-context.js"), { force: true }),
2430
+ rm(resolve(dirname(paths.runtime), "retrieval-task.js"), { force: true }),
2431
+ rm(resolve(dirname(paths.runtime), "runtime-redaction.js"), { force: true }),
2432
+ rm(resolve(dirname(paths.runtime), "user-message.js"), { force: true }),
2433
+ rm(resolve(dirname(paths.runtime), "skill-guidance.js"), { force: true }),
2354
2434
  rm(paths.runtimeInvocationHealthWriter, { force: true }),
2355
2435
  rm(paths.runtimeVersion, { force: true }),
2356
2436
  rm(paths.runtimePackage, { force: true }),
@@ -2475,9 +2555,9 @@ async function doctorCommand(args) {
2475
2555
  return;
2476
2556
  }
2477
2557
  const paths = getLorePaths();
2478
- const config = await readConnectorConfig(paths, process.cwd());
2558
+ const config = await diagnosticConfig(paths, process.cwd());
2479
2559
  const status = await statusData(paths, process.cwd());
2480
- const checks = [];
2560
+ const checks = [{ name: "capture-folder", status: status.folderAccess.allowed ? "ok" : "warning", detail: status.folderAccess.allowed ? status.folderAccess.reason : `Recording is off here: ${status.folderAccess.reason}. Run lore scope check <folder> to verify a selected folder.` }];
2481
2561
  checks.push({
2482
2562
  name: "platform",
2483
2563
  status: platform() === "darwin" || platform() === "linux" || isWindows() ? "ok" : "error",
@@ -2587,7 +2667,8 @@ async function doctorCommand(args) {
2587
2667
  status.reliability.outbox.inFlight > 0
2588
2668
  ? "warning"
2589
2669
  : "ok",
2590
- detail: `${status.reliability.outbox.total} total; ${status.reliability.outbox.ready} ready, ${status.reliability.outbox.authBlocked} authentication-blocked, ${status.reliability.outbox.dead} dead-letter`,
2670
+ detail: `${status.reliability.outbox.total} total; ${status.reliability.outbox.ready} ready, ${status.reliability.outbox.authBlocked} authentication-blocked, ${status.reliability.outbox.dead} dead-letter` +
2671
+ (status.reliability.outbox.oldestFailure ? `; oldest failure (${status.reliability.outbox.oldestFailure.attempts} attempts): ${status.reliability.outbox.oldestFailure.message}` : ""),
2591
2672
  });
2592
2673
  checks.push({
2593
2674
  name: "verified-snapshot-cache",
@@ -2664,6 +2745,9 @@ async function printCommandHelp(path) {
2664
2745
  case "approach":
2665
2746
  await runApproachCommand(args, null);
2666
2747
  return;
2748
+ case "skill":
2749
+ await runSkillCommand(args, null);
2750
+ return;
2667
2751
  case "guard":
2668
2752
  await runGuardCommand(args, null);
2669
2753
  return;
@@ -2752,8 +2836,8 @@ async function executeCli(args = process.argv.slice(2)) {
2752
2836
  return;
2753
2837
  }
2754
2838
  const commandArgs = args.slice(1);
2755
- if (["ask", "remember", "share", "resume", "continue", "approach"].includes(command)) {
2756
- const decision = await checkDirectory(process.cwd(), getLorePaths(command === "approach" ? process.env.LORE_HOME : undefined).home);
2839
+ if (["ask", "remember", "share", "resume", "continue", "approach", "skill"].includes(command) && !(command === "skill" && commandArgs[0] === "discover")) {
2840
+ const decision = await checkDirectory(process.cwd(), getLorePaths(["approach", "skill"].includes(command) ? process.env.LORE_HOME : undefined).home);
2757
2841
  if (!decision.allowed)
2758
2842
  throw new Error(`Lore is off in this folder. ${decision.reason}`);
2759
2843
  }
@@ -2798,6 +2882,9 @@ async function executeCli(args = process.argv.slice(2)) {
2798
2882
  case "approach":
2799
2883
  await runApproachCommand(commandArgs, await readConnectorConfig(getLorePaths(process.env.LORE_HOME), process.cwd()));
2800
2884
  return;
2885
+ case "skill":
2886
+ await runSkillCommand(commandArgs, commandArgs[0] === "discover" ? null : await readConnectorConfig(getLorePaths(process.env.LORE_HOME), process.cwd()));
2887
+ return;
2801
2888
  case "guard":
2802
2889
  await runGuardCommand(commandArgs, await readConnectorConfig(getLorePaths(), process.cwd()));
2803
2890
  return;
@@ -2863,6 +2950,7 @@ async function executeCli(args = process.argv.slice(2)) {
2863
2950
  });
2864
2951
  return {
2865
2952
  agents: connected.agents,
2953
+ warnings: connected.warnings,
2866
2954
  verified: await verifyConnectedInstallation(),
2867
2955
  };
2868
2956
  },