@lore-co/cli 0.2.27 → 0.2.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 (45) hide show
  1. package/README.md +11 -9
  2. package/dist/capture-repair.d.ts +11 -0
  3. package/dist/capture-repair.d.ts.map +1 -0
  4. package/dist/capture-repair.js +94 -0
  5. package/dist/capture-repair.js.map +1 -0
  6. package/dist/cli.d.ts +54 -4
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +121 -37
  9. package/dist/cli.js.map +1 -1
  10. package/dist/command-help.d.ts +6 -2
  11. package/dist/command-help.d.ts.map +1 -1
  12. package/dist/command-help.js +39 -6
  13. package/dist/command-help.js.map +1 -1
  14. package/dist/drafts.d.ts +8 -0
  15. package/dist/drafts.d.ts.map +1 -0
  16. package/dist/drafts.js +173 -0
  17. package/dist/drafts.js.map +1 -0
  18. package/dist/generated-assets.d.ts +8 -8
  19. package/dist/generated-assets.d.ts.map +1 -1
  20. package/dist/generated-assets.js +8 -8
  21. package/dist/generated-assets.js.map +1 -1
  22. package/dist/github.d.ts.map +1 -1
  23. package/dist/github.js +8 -7
  24. package/dist/github.js.map +1 -1
  25. package/dist/lore-agent.d.ts +1 -1
  26. package/dist/lore-agent.d.ts.map +1 -1
  27. package/dist/lore-agent.js +3 -0
  28. package/dist/lore-agent.js.map +1 -1
  29. package/dist/recovery.d.ts +5 -2
  30. package/dist/recovery.d.ts.map +1 -1
  31. package/dist/recovery.js +7 -3
  32. package/dist/recovery.js.map +1 -1
  33. package/dist/remember.d.ts.map +1 -1
  34. package/dist/remember.js +30 -1
  35. package/dist/remember.js.map +1 -1
  36. package/dist/review.d.ts.map +1 -1
  37. package/dist/review.js +8 -6
  38. package/dist/review.js.map +1 -1
  39. package/dist/runtime.d.ts.map +1 -1
  40. package/dist/runtime.js +10 -7
  41. package/dist/runtime.js.map +1 -1
  42. package/dist/self-host.d.ts +1 -1
  43. package/dist/self-host.js +2 -2
  44. package/dist/update.js +3 -3
  45. package/package.json +4 -4
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { parseDirectoryRules, checkDirectory, checkDirectoryRules, checkLoreAccess, checkWorkspaceRepository, capturePauseMessage, LORE_DISABLED_MESSAGE, loreDisabled } from "./directory-policy.js";
3
3
  import { reportCommand, runTelemetryCommand, telemetryEnabled, trackedCommand, TELEMETRY_HELP } from "./telemetry.js";
4
4
  import { secureWindowsDirectory, windowsPermissionsArePrivate } from "./windows-permissions.js";
5
- import { COMMAND_GUIDES, ROOT_HELP, helpTopic } from "./command-help.js";
5
+ import { ALL_HELP, COMMAND_GUIDES, ROOT_HELP, helpTopic } from "./command-help.js";
6
6
  import { runShareCommand } from "./share.js";
7
7
  import { resolveConnectProject, validateConnectProjectOptions } from "./connect-project.js";
8
8
  import { captureDirectories } from "./capture-scope.js";
@@ -30,7 +30,9 @@ import { runChatCommand } from "./chat.js";
30
30
  import { runLearningsCommand } from "./learnings.js";
31
31
  import { runReviewCommand } from "./review.js";
32
32
  import { runEmbeddingsCommand } from "./embeddings.js";
33
+ import { CAPTURE_REPAIR_HELP, runCaptureRepairCommand } from "./capture-repair.js";
33
34
  import { runRememberCommand } from "./remember.js";
35
+ import { runDraftsCommand } from "./drafts.js";
34
36
  import { installLoreSkill, loreSkillFiles, recapSkillFiles, removeLoreSkill } from "./skills.js";
35
37
  import { GENERIC_AGENT, genericInstructions, genericMcpServers, parseGenericAgentId, readGenericAgents, recordGenericAgent, removeGenericAgents } from "./generic-mcp.js";
36
38
  import { installLoreAgent, loreAgentFiles, removeLoreAgent } from "./lore-agent.js";
@@ -145,7 +147,7 @@ Options:
145
147
  --write With --agent generic: let the agent save through MCP (saves stay proposals)
146
148
  --timeout-ms <ms> Hook request timeout, 250-10000 (default: 2500)
147
149
  --fallback <agent> claude, codex, opencode, devin, cursor, copilot-cli, polytoken
148
- --no-recovery Disable automatic handoff setup (otherwise enabled with two installed clients)
150
+ --no-recovery Disable automatic handoff setup (otherwise set up between the agents you connect)
149
151
  --verbose Also list the files written and the connection details
150
152
  --json Print machine-readable output
151
153
  --help Show this command's help
@@ -166,14 +168,18 @@ Examples:
166
168
  lore connect --token "$LORE_WORKSPACE_TOKEN" --agent cursor
167
169
  `;
168
170
  const STATUS_HELP = `lore status
169
- Show exact installation, declared capabilities, and last invocation health.
171
+ Show whether Lore is connected, whether it records in this folder, and which agents have used it.
170
172
 
171
173
  Usage:
172
- lore status [--json]
174
+ lore status [--verbose] [--json]
175
+
176
+ Options:
177
+ --verbose Also show installation, capture queue, cache, and Guard details
178
+ --json Print every detail as JSON
173
179
 
174
180
  Examples:
175
181
  lore status
176
- lore status --json
182
+ lore status --verbose
177
183
  `;
178
184
  const DOCTOR_HELP = `lore doctor
179
185
  Check local security, exact wiring, invocation health, binaries, and Lore health.
@@ -193,6 +199,7 @@ Usage:
193
199
  lore capture off [--json]
194
200
  lore capture on [--json]
195
201
  lore capture reminder off|on [--json]
202
+ lore capture repair [--json]
196
203
 
197
204
  With capture off, Lore installs no Stop or SessionEnd hooks and never sends a
198
205
  prompt or response as a turn. Context injection, Guard, lore ask, lore remember,
@@ -202,11 +209,15 @@ decide where Lore runs at all.
202
209
  While nothing is recorded, Lore asks your agent to suggest /recap once in a
203
210
  session where you confirm a decision. lore capture reminder off stops that.
204
211
 
212
+ lore capture repair retries captures the server dead-lettered while its
213
+ extraction provider was down. See lore help capture repair.
214
+
205
215
  Examples:
206
216
  lore capture off
207
217
  lore capture status
208
218
  lore capture reminder off
209
219
  lore capture on
220
+ lore capture repair
210
221
  `;
211
222
  const DISCONNECT_HELP = `lore disconnect
212
223
  Remove Lore-owned hooks/plugin, credentials, runtime, and transient state.
@@ -2161,9 +2172,13 @@ async function connectWithCredentialLocked(options, identity) {
2161
2172
  const recoveryEnv = options.home === undefined ? process.env : { ...process.env, ZDOTDIR: paths.home };
2162
2173
  let recoveryWarning;
2163
2174
  const externalRecoveryHost = options.agents.some(agent => ["t3code", "cowork", "copilot-vscode"].includes(agent));
2175
+ // Named agents limit recovery to themselves; auto-detection already installs every detected agent.
2176
+ const namedRecoveryAgents = options.agents.length === 0 ? undefined
2177
+ : options.agents.map(agent => agent === "copilot" ? "copilot-cli" : agent);
2178
+ const namedSources = namedRecoveryAgents?.filter((agent) => isRecoverySource(agent) && agent !== options.fallback);
2164
2179
  const installRecovery = options.fallback === undefined ? options.recovery === true ?
2165
- await prepareAutomaticRecovery(paths, recoveryEnv).catch((error) => { recoveryWarning = `Automatic recovery setup: ${error.message}`; return undefined; }) : undefined :
2166
- await prepareRecoveryInstallation(paths, options.fallback, recoveryEnv).catch((error) => {
2180
+ await prepareAutomaticRecovery(paths, recoveryEnv, namedRecoveryAgents).catch((error) => { recoveryWarning = `Automatic recovery setup: ${error.message}`; return undefined; }) : undefined :
2181
+ await prepareRecoveryInstallation(paths, options.fallback, recoveryEnv, namedSources?.length ? namedSources : undefined).catch((error) => {
2167
2182
  // A desktop host can be the source even when only its destination is on PATH.
2168
2183
  if (externalRecoveryHost && error.message.startsWith("No supported source client is installed."))
2169
2184
  return undefined;
@@ -3151,20 +3166,59 @@ async function connectedServerVersion(config) {
3151
3166
  return null;
3152
3167
  }
3153
3168
  }
3169
+ /** The default `lore status`: what someone needs to know, and the one next step. */
3170
+ export function statusSummary(data, healthLines, workspaceName) {
3171
+ if (!data.connected)
3172
+ return "Lore isn't connected on this machine.\nNext: lore connect --login\n";
3173
+ const lines = [...healthLines, workspaceName ? `Connected to ${workspaceName}.` : "Connected."];
3174
+ lines.push(!data.folderAccess.allowed
3175
+ ? `This folder: Lore is off (${data.folderAccess.reason}).`
3176
+ : data.capture === "off" ? "This folder: context and Guard on; recording off."
3177
+ : data.captureSwitches.pausedBy !== null ? `This folder: context and Guard on; ${capturePauseMessage(data.captureSwitches.pausedBy).toLowerCase()}.`
3178
+ : "This folder: recording on.");
3179
+ const agents = data.agents.filter((agent) => agent.configured);
3180
+ const day = (value) => value === null ? "" : new Date(value).toISOString().slice(0, 10);
3181
+ let next = null;
3182
+ if (agents.length > 0) {
3183
+ const width = Math.max(...agents.map((agent) => agentDisplayName(agent.agent).length));
3184
+ lines.push("Agents:");
3185
+ for (const agent of agents) {
3186
+ const broken = agent.installation.state !== "complete" || agent.invocationError !== null
3187
+ || agent.invocation.state === "failed" || agent.invocation.state === "incomplete";
3188
+ if (broken)
3189
+ next ??= "lore doctor";
3190
+ const state = agent.installation.state !== "complete" ? "setup incomplete"
3191
+ : agent.invocationError !== null || agent.invocation.state === "failed" || agent.invocation.state === "incomplete" ? "last run failed"
3192
+ : agent.invocation.state === "successful" ? `last used ${day(agent.invocation.lastSuccessfulAt)}`
3193
+ : "not used yet";
3194
+ lines.push(` ${agentDisplayName(agent.agent).padEnd(width)} ${state}`);
3195
+ }
3196
+ }
3197
+ if (next !== null)
3198
+ lines.push(`Next: ${next}`);
3199
+ lines.push("Details: lore status --verbose");
3200
+ return `${lines.join("\n")}\n`;
3201
+ }
3154
3202
  async function statusCommand(args) {
3155
- const parsed = parseOutputArguments(args, STATUS_HELP, "status");
3203
+ const verbose = args.includes("--verbose");
3204
+ const parsed = parseOutputArguments(args.filter((argument) => argument !== "--verbose"), STATUS_HELP, "status");
3156
3205
  if (parsed === null) {
3157
3206
  return;
3158
3207
  }
3159
3208
  const paths = getLorePaths();
3209
+ const config = await diagnosticConfig(paths, process.cwd());
3160
3210
  const [statusResult, serverVersion] = await Promise.all([
3161
3211
  statusData(paths, process.cwd()),
3162
- diagnosticConfig(paths, process.cwd()).then((config) => config === null ? null : connectedServerVersion(config)),
3212
+ config === null ? null : connectedServerVersion(config),
3163
3213
  ]);
3164
3214
  const health = statusHealth(statusResult, serverVersion);
3165
3215
  const data = { ...statusResult, health };
3166
3216
  if (!parsed.json && loreDisabled(process.env))
3167
3217
  process.stdout.write(`${LORE_DISABLED_MESSAGE}\n`);
3218
+ if (!parsed.json && !verbose) {
3219
+ process.stdout.write(statusSummary(data, health.lines, config?.workspaceName));
3220
+ return;
3221
+ }
3168
3222
  if (!parsed.json && data.connection)
3169
3223
  process.stdout.write(`connection: ${data.connection}\n`);
3170
3224
  const agentLines = data.agents
@@ -3320,17 +3374,18 @@ export function captureModeCheck(capture, switches = CAPTURE_SWITCHES_UNCHECKED)
3320
3374
  };
3321
3375
  }
3322
3376
  /**
3323
- * Server-side queue health. Dead embedding jobs name the operator fix: once the
3324
- * provider is repaired, `lore embeddings repair` retries them without waiting
3325
- * for the reconciler's backoff.
3377
+ * Server-side queue health. Dead jobs name the operator fix: once the provider
3378
+ * is repaired, `lore capture repair` and `lore embeddings repair` retry them
3379
+ * without waiting for a new capture or the reconciler's backoff.
3326
3380
  */
3327
3381
  export function operationalMetricsCheck(metrics) {
3328
3382
  const captureBacklog = metrics.captureReplay.captureExtraction.pending +
3329
3383
  metrics.captureReplay.captureExtraction.running;
3330
3384
  const embeddingBacklog = metrics.captureReplay.memoryEmbedding.pending +
3331
3385
  metrics.captureReplay.memoryEmbedding.running;
3386
+ const captureDeadLetters = metrics.captureReplay.captureExtraction.dead;
3332
3387
  const embeddingDeadLetters = metrics.captureReplay.memoryEmbedding.dead;
3333
- const deadLetters = metrics.captureReplay.captureExtraction.dead + embeddingDeadLetters;
3388
+ const deadLetters = captureDeadLetters + embeddingDeadLetters;
3334
3389
  const detectedFailures = metrics.silentFailures.count +
3335
3390
  metrics.guard.failures +
3336
3391
  deadLetters;
@@ -3344,6 +3399,9 @@ export function operationalMetricsCheck(metrics) {
3344
3399
  ? "warning"
3345
3400
  : "ok",
3346
3401
  detail: `${detectedFailures} detected failure(s), ${captureBacklog} capture and ${embeddingBacklog} embedding job(s) in backlog; ${metrics.guard.latency.samples} Guard latency sample(s)` +
3402
+ (captureDeadLetters > 0
3403
+ ? `; ${captureDeadLetters} dead capture job(s): once the extraction provider is back, run lore capture repair`
3404
+ : "") +
3347
3405
  (embeddingDeadLetters > 0
3348
3406
  ? `; ${embeddingDeadLetters} dead embedding job(s): fix the embedding provider, then run lore embeddings repair`
3349
3407
  : ""),
@@ -3550,6 +3608,10 @@ export async function setRecapReminder(reminder, paths = getLorePaths()) {
3550
3608
  return { recapReminder: reminder, changed: true };
3551
3609
  }
3552
3610
  async function captureCommand(args) {
3611
+ if (args[0] === "repair") {
3612
+ await runCaptureRepairCommand(args.slice(1), await readConnectorConfig(getLorePaths(), process.cwd()));
3613
+ return;
3614
+ }
3553
3615
  if (args[0] === "reminder") {
3554
3616
  const json = args.includes("--json");
3555
3617
  const [reminder, ...extra] = args.slice(1).filter((argument) => argument !== "--json");
@@ -3576,7 +3638,7 @@ async function captureCommand(args) {
3576
3638
  mode = argument;
3577
3639
  continue;
3578
3640
  }
3579
- throw new Error("Usage: lore capture [status|on|off|reminder off|reminder on] [--json]");
3641
+ throw new Error("Usage: lore capture [status|on|off|reminder off|reminder on|repair] [--json]");
3580
3642
  }
3581
3643
  const paths = getLorePaths();
3582
3644
  if (mode === undefined || mode === "status") {
@@ -3655,13 +3717,18 @@ async function doctorCommand(args) {
3655
3717
  checks.push({
3656
3718
  name: "config",
3657
3719
  status: config === null ? "error" : "ok",
3658
- detail: config === null ? `missing or invalid: ${paths.config}` : paths.config,
3659
- });
3660
- checks.push({
3661
- name: "config-permissions",
3662
- status: ["600", "private-acl"].includes(status.configMode ?? "") ? "ok" : "error",
3663
- detail: status.configMode ?? "missing",
3720
+ detail: config === null ? `not connected (${paths.config} is missing or invalid). Run lore connect --login` : paths.config,
3664
3721
  });
3722
+ // Without a config there is nothing to protect yet; the check above says what to do.
3723
+ if (config !== null)
3724
+ checks.push({
3725
+ name: "config-permissions",
3726
+ status: ["600", "private-acl"].includes(status.configMode ?? "") ? "ok" : "error",
3727
+ detail: status.configMode ?? "missing",
3728
+ });
3729
+ // A fresh connection has not been used yet; that is expected for a day, not a problem.
3730
+ const connectedAt = config === null ? Number.NaN : Date.parse(config.connectedAt);
3731
+ const recentlyConnected = Number.isFinite(connectedAt) && Date.now() - connectedAt < 24 * 60 * 60 * 1000;
3665
3732
  if (status.runtimeRequired) {
3666
3733
  checks.push({
3667
3734
  name: "runtime",
@@ -3689,7 +3756,9 @@ async function doctorCommand(args) {
3689
3756
  : agent.invocation.state === "failed" ||
3690
3757
  agent.invocation.state === "incomplete"
3691
3758
  ? "error"
3692
- : "warning",
3759
+ : agent.invocation.state === "never_observed" && recentlyConnected
3760
+ ? "ok"
3761
+ : "warning",
3693
3762
  detail: agent.invocationError !== null
3694
3763
  ? agent.invocationError === "runtime_version_mismatch"
3695
3764
  ? `observed Lore ${agent.invocation.runtimeVersion}; expected ${LORE_VERSION}`
@@ -3701,7 +3770,9 @@ async function doctorCommand(args) {
3701
3770
  : agent.invocation.state === "incomplete"
3702
3771
  ? `last invocation did not complete (${agent.invocation.lastAttemptedAt})`
3703
3772
  : agent.invocation.state === "never_observed"
3704
- ? "installed, but no invocation has been observed yet"
3773
+ ? recentlyConnected
3774
+ ? "installed, not used yet"
3775
+ : `installed, but not used since connecting. Start a session in ${agentDisplayName(agent.agent)}, then run lore doctor again`
3705
3776
  : "invocation health is unavailable",
3706
3777
  });
3707
3778
  if (agent.agent === "codex" && agent.executable) {
@@ -3725,23 +3796,28 @@ async function doctorCommand(args) {
3725
3796
  detail: "surface-specific repository configuration; local installation is not claimed",
3726
3797
  });
3727
3798
  }
3728
- checks.push({
3729
- name: "reliability-store",
3730
- status: status.reliabilityError !== null
3731
- ? "error"
3732
- : status.reliability === null
3733
- ? "warning"
3734
- : "ok",
3735
- detail: status.reliabilityError ??
3736
- status.reliability?.directory ??
3737
- "not initialized",
3738
- });
3799
+ if (config !== null)
3800
+ checks.push({
3801
+ name: "reliability-store",
3802
+ status: status.reliabilityError !== null
3803
+ ? "error"
3804
+ : status.reliability === null
3805
+ ? "warning"
3806
+ : "ok",
3807
+ detail: status.reliabilityError ??
3808
+ status.reliability?.directory ??
3809
+ "not initialized",
3810
+ });
3739
3811
  if (status.reliability !== null) {
3740
3812
  checks.push(durableOutboxCheck(status.reliability.outbox, prunedDeadLetters));
3813
+ // Keys are pinned on the first delivery, so an unused connection has none yet.
3814
+ const unused = status.agents.every((agent) => !agent.configured || agent.invocation.state === "never_observed");
3741
3815
  checks.push({
3742
3816
  name: "verified-snapshot-cache",
3743
- status: status.reliability.trustKeys > 0 ? "ok" : "warning",
3744
- detail: `${status.reliability.cache.context} context, ${status.reliability.cache.policy} policy, ${status.reliability.trustKeys} pinned key(s)`,
3817
+ status: status.reliability.trustKeys > 0 || unused ? "ok" : "warning",
3818
+ detail: status.reliability.trustKeys === 0 && unused
3819
+ ? "empty until the first prompt"
3820
+ : `${status.reliability.cache.context} context, ${status.reliability.cache.policy} policy, ${status.reliability.trustKeys} pinned key(s)`,
3745
3821
  });
3746
3822
  const operational = status.reliability.operational;
3747
3823
  const detectedFailures = operational.guard.failures + operational.retrieval.failed;
@@ -3806,7 +3882,7 @@ async function printCommandHelp(path) {
3806
3882
  process.stdout.write(DOCTOR_HELP);
3807
3883
  return;
3808
3884
  case "capture":
3809
- process.stdout.write(CAPTURE_HELP);
3885
+ process.stdout.write(topic.subcommand === "repair" ? CAPTURE_REPAIR_HELP : CAPTURE_HELP);
3810
3886
  return;
3811
3887
  case "embeddings":
3812
3888
  await runEmbeddingsCommand(args, null);
@@ -3835,6 +3911,9 @@ async function printCommandHelp(path) {
3835
3911
  case "remember":
3836
3912
  await runRememberCommand(args, null);
3837
3913
  return;
3914
+ case "drafts":
3915
+ await runDraftsCommand(args, null);
3916
+ return;
3838
3917
  case "approach":
3839
3918
  await runApproachCommand(args, null);
3840
3919
  return;
@@ -3908,6 +3987,8 @@ async function executeCli(args = process.argv.slice(2)) {
3908
3987
  const path = args.slice(1);
3909
3988
  if (!path.length || (path.length === 1 && ["--help", "-h"].includes(path[0])))
3910
3989
  process.stdout.write(ROOT_HELP);
3990
+ else if (path.length === 1 && path[0] === "--all")
3991
+ process.stdout.write(ALL_HELP);
3911
3992
  else
3912
3993
  await printCommandHelp(path);
3913
3994
  return;
@@ -3929,7 +4010,7 @@ async function executeCli(args = process.argv.slice(2)) {
3929
4010
  return;
3930
4011
  }
3931
4012
  const commandArgs = args.slice(1);
3932
- if (["ask", "chat", "learnings", "review", "remember", "share", "resume", "continue", "approach", "skill"].includes(command) && !(command === "skill" && commandArgs[0] === "discover")) {
4013
+ if (["ask", "chat", "learnings", "review", "remember", "drafts", "share", "resume", "continue", "approach", "skill"].includes(command) && !(command === "skill" && commandArgs[0] === "discover")) {
3933
4014
  const decision = await checkDirectory(process.cwd(), getLorePaths(["approach", "skill"].includes(command) ? process.env.LORE_HOME : undefined).home);
3934
4015
  if (!decision.allowed)
3935
4016
  throw new Error(`Lore is off in this folder. ${decision.reason}`);
@@ -3984,6 +4065,9 @@ async function executeCli(args = process.argv.slice(2)) {
3984
4065
  case "remember":
3985
4066
  await runRememberCommand(commandArgs, await readConnectorConfig(getLorePaths(), process.cwd()));
3986
4067
  return;
4068
+ case "drafts":
4069
+ await runDraftsCommand(commandArgs, await readConnectorConfig(getLorePaths(), process.cwd()));
4070
+ return;
3987
4071
  case "approach":
3988
4072
  await runApproachCommand(commandArgs, await readConnectorConfig(getLorePaths(process.env.LORE_HOME), process.cwd()));
3989
4073
  return;