om-memory-system 3.6.0-next.37 → 3.6.0-next.38

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 (54) hide show
  1. package/README.md +24 -16
  2. package/dist/adapters/claude-code/hook-client.d.ts +73 -0
  3. package/dist/adapters/claude-code/hook-client.js +285 -0
  4. package/dist/adapters/claude-code/hook-command.d.ts +6 -0
  5. package/dist/adapters/claude-code/hook-command.js +25 -0
  6. package/dist/cli/index.js +19 -3
  7. package/dist/cli/memory-command.d.ts +24 -0
  8. package/dist/cli/memory-command.js +180 -0
  9. package/dist/core/retrieval.d.ts +21 -0
  10. package/dist/core/retrieval.js +35 -0
  11. package/dist/importer/backfill-controls.d.ts +10 -0
  12. package/dist/importer/backfill-controls.js +40 -11
  13. package/dist/importer/backfill-model.d.ts +1 -1
  14. package/dist/importer/backfill-model.js +3 -0
  15. package/dist/importer/claude-conversation.d.ts +74 -0
  16. package/dist/importer/claude-conversation.js +216 -0
  17. package/dist/importer/claude-hook-api.d.ts +50 -0
  18. package/dist/importer/claude-hook-api.js +441 -0
  19. package/dist/importer/claude-import.d.ts +15 -0
  20. package/dist/importer/claude-import.js +62 -0
  21. package/dist/importer/claude-reader.d.ts +99 -0
  22. package/dist/importer/claude-reader.js +292 -0
  23. package/dist/importer/external-backfill-models.js +2 -2
  24. package/dist/importer/import-args.d.ts +1 -1
  25. package/dist/importer/import-args.js +11 -4
  26. package/dist/importer/import-readiness.d.ts +13 -1
  27. package/dist/importer/import-readiness.js +19 -0
  28. package/dist/importer/import-runs.js +1 -1
  29. package/dist/importer/import-sessions.d.ts +4 -1
  30. package/dist/importer/import-sessions.js +14 -2
  31. package/dist/importer/import-sources.d.ts +8 -2
  32. package/dist/importer/import-sources.js +21 -4
  33. package/dist/importer/map-suggestions.d.ts +1 -0
  34. package/dist/importer/map-suggestions.js +6 -1
  35. package/dist/importer/run-import.js +14 -10
  36. package/dist/importer/web-import-jobs.js +10 -3
  37. package/dist/index.js +9 -23
  38. package/dist/services/ai/live-model-choice.d.ts +5 -0
  39. package/dist/services/ai/live-model-choice.js +7 -0
  40. package/dist/services/backfill-state.d.ts +1 -1
  41. package/dist/services/capture-retry-queue.js +3 -2
  42. package/dist/services/claude-capture-cursor.d.ts +10 -0
  43. package/dist/services/claude-capture-cursor.js +44 -0
  44. package/dist/services/settings-snapshot.d.ts +1 -0
  45. package/dist/services/settings-snapshot.js +3 -1
  46. package/dist/services/web-server.d.ts +2 -0
  47. package/dist/services/web-server.js +35 -6
  48. package/dist/types/host-label.d.ts +3 -0
  49. package/dist/types/host-label.js +9 -0
  50. package/dist/types/index.d.ts +1 -1
  51. package/dist/web/assets/index-BFiVixOK.js +124 -0
  52. package/dist/web/index.html +1 -1
  53. package/package.json +1 -1
  54. package/dist/web/assets/index-DIy--9g_.js +0 -124
@@ -0,0 +1,180 @@
1
+ import { resolve } from "node:path";
2
+ const MODES = {
3
+ add: "Store a memory (--content, optional --type and --tags)",
4
+ search: "Search project memories (query as an argument or --query)",
5
+ profile: "Show the user profile, or save a preference with --content",
6
+ list: "List recent memories (--limit, default 20)",
7
+ forget: "Remove a memory (--id)",
8
+ help: "Print the memory tool guide as JSON",
9
+ "list-shards": "List project memory shards and orphaned path associations",
10
+ migrate: "Reassociate orphaned shards after a directory move (--from-path or --from-hash)",
11
+ export: "Export project memories to a portable JSON file (--output)",
12
+ import: "Import memories from a portable JSON file (--input)",
13
+ };
14
+ const VALUE_FLAGS = {
15
+ "--content": "content",
16
+ "--query": "query",
17
+ "--type": "type",
18
+ "--tags": "tags",
19
+ "--id": "memoryId",
20
+ "--limit": "limit",
21
+ "--scope": "scope",
22
+ "--from-path": "fromPath",
23
+ "--from-hash": "fromHash",
24
+ "--output": "outputPath",
25
+ "--input": "inputPath",
26
+ "--directory": "directory",
27
+ };
28
+ const BOOLEAN_FLAGS = {
29
+ "--dry-run": "dryRun",
30
+ "--allow-linked-source": "allowLinkedSource",
31
+ };
32
+ export const memoryCommandUsage = `Usage: om-memory-system memory <mode> [options]
33
+
34
+ Runs one memory operation for the current project and prints one JSON
35
+ document. Memories added here are stored with host claude-code.
36
+
37
+ Modes:
38
+ ${Object.entries(MODES)
39
+ .map(([mode, description]) => ` ${mode.padEnd(13)} ${description}`)
40
+ .join("\n")}
41
+
42
+ Options:
43
+ --content <text> Memory text (add) or preference text (profile)
44
+ --query <text> Search text (search)
45
+ --type <type> Memory type, for example decision or bug-fix (add)
46
+ --tags <a,b> Comma-separated tags (add)
47
+ --id <id> Memory id (forget)
48
+ --limit <n> Maximum results (search, list)
49
+ --scope <scope> project or all-projects (search, list)
50
+ --from-path <path> Old project path (migrate)
51
+ --from-hash <hash> Old project hash (migrate)
52
+ --output <file> Export file (export)
53
+ --input <file> Import file (import)
54
+ --dry-run Report without writing (migrate, import)
55
+ --allow-linked-source Allow a linked source directory (migrate)
56
+ --directory <path> Project directory (default: the working directory)
57
+ --help, -h Print this help
58
+
59
+ Text inside <private>...</private> is removed before storage.
60
+
61
+ Examples:
62
+ om-memory-system memory search "database choice"
63
+ om-memory-system memory add --content "Use libSQL for the store" --type decision`;
64
+ function isMode(value) {
65
+ return Object.hasOwn(MODES, value);
66
+ }
67
+ /** Parse `<mode> [options]` into memory tool arguments. Throws on bad input. */
68
+ export function parseMemoryCommandArgs(argv) {
69
+ const isHelpFlag = (value) => value === "--help" || value === "-h";
70
+ if (argv.length === 0 || isHelpFlag(argv[0]))
71
+ return { help: true, args: {} };
72
+ const [mode, ...rest] = argv;
73
+ if (!mode || !isMode(mode)) {
74
+ throw new Error(`Unknown memory mode "${mode}". Modes: ${Object.keys(MODES).join(", ")}`);
75
+ }
76
+ const args = { mode };
77
+ const values = {};
78
+ const positionals = [];
79
+ for (let index = 0; index < rest.length; index++) {
80
+ const token = rest[index];
81
+ if (isHelpFlag(token))
82
+ return { help: true, args: {} };
83
+ if (!token.startsWith("--")) {
84
+ positionals.push(token);
85
+ continue;
86
+ }
87
+ const equals = token.indexOf("=");
88
+ const flag = equals === -1 ? token : token.slice(0, equals);
89
+ const booleanKey = BOOLEAN_FLAGS[flag];
90
+ if (booleanKey) {
91
+ if (equals !== -1)
92
+ throw new Error(`${flag} takes no value`);
93
+ args[booleanKey] = true;
94
+ continue;
95
+ }
96
+ const key = VALUE_FLAGS[flag];
97
+ if (!key)
98
+ throw new Error(`Unknown option ${flag}`);
99
+ // A following flag is not a value; literal text that starts with "--" needs `--flag=value`.
100
+ const next = rest[index + 1];
101
+ const value = equals !== -1
102
+ ? token.slice(equals + 1)
103
+ : next !== undefined && !next.startsWith("--")
104
+ ? rest[++index]
105
+ : undefined;
106
+ if (value === undefined)
107
+ throw new Error(`${flag} needs a value`);
108
+ values[key] = value;
109
+ }
110
+ if (positionals.length > 0) {
111
+ if (mode !== "search" || positionals.length > 1 || values.query !== undefined) {
112
+ throw new Error(`Unexpected argument "${positionals[0]}"`);
113
+ }
114
+ values.query = positionals[0];
115
+ }
116
+ for (const [key, value] of Object.entries(values)) {
117
+ if (key === "limit") {
118
+ const limit = Number(value);
119
+ if (!Number.isInteger(limit) || limit <= 0) {
120
+ throw new Error("--limit needs a positive whole number");
121
+ }
122
+ args.limit = limit;
123
+ }
124
+ else if (key === "scope") {
125
+ if (value !== "project" && value !== "all-projects") {
126
+ throw new Error("--scope must be project or all-projects");
127
+ }
128
+ args.scope = value;
129
+ }
130
+ else if (key !== "directory") {
131
+ args[key] = value;
132
+ }
133
+ }
134
+ return { help: false, args, directory: values.directory };
135
+ }
136
+ /** Run `om-memory-system memory ...` and return the process exit code. */
137
+ export async function runMemoryCommand(argv, io = {}) {
138
+ const stdout = io.stdout ?? ((text) => console.log(text));
139
+ const stderr = io.stderr ?? ((text) => console.error(text));
140
+ let parsed;
141
+ try {
142
+ parsed = parseMemoryCommandArgs(argv);
143
+ }
144
+ catch (error) {
145
+ stderr(`${error instanceof Error ? error.message : String(error)}\n\n${memoryCommandUsage}`);
146
+ return 1;
147
+ }
148
+ if (parsed.help) {
149
+ stdout(memoryCommandUsage);
150
+ return 0;
151
+ }
152
+ const directory = resolve(io.cwd ?? process.cwd(), parsed.directory ?? ".");
153
+ let result;
154
+ const secrets = [];
155
+ try {
156
+ const { CONFIG, initConfigWithLegacyMigration } = await import("../config.js");
157
+ initConfigWithLegacyMigration(directory);
158
+ for (const secret of [CONFIG.memoryApiKey, CONFIG.embeddingApiKey]) {
159
+ if (secret)
160
+ secrets.push(secret);
161
+ }
162
+ const { executeMemoryOperation } = await import("../core/memory-operations.js");
163
+ const { memoryClient } = await import("../services/client.js");
164
+ try {
165
+ result = await executeMemoryOperation(parsed.args, { directory, host: "claude-code" });
166
+ }
167
+ finally {
168
+ // Release the store so the process can exit once the reply is printed.
169
+ await memoryClient.close().catch(() => { });
170
+ }
171
+ }
172
+ catch (error) {
173
+ result = { success: false, error: error instanceof Error ? error.message : String(error) };
174
+ }
175
+ let output = JSON.stringify(result, null, 2);
176
+ for (const secret of secrets)
177
+ output = output.replaceAll(secret, "[redacted]");
178
+ stdout(output);
179
+ return result.success === false ? 1 : 0;
180
+ }
@@ -6,7 +6,28 @@ export declare const RETRIEVAL_SECTION_TAG = "omms-retrieval";
6
6
  * profile). Returns null when nothing relevant is found.
7
7
  */
8
8
  export declare function buildRetrievalSection(prompt: string, directory: string, sessionId: string): Promise<string | null>;
9
+ export interface RecentMemoriesOptions {
10
+ /** Project container tag, from `getTags(directory).project.tag`. */
11
+ projectTag: string;
12
+ /** Profile owner, from `getTags(directory).user.userEmail`. */
13
+ userEmail: string | null | undefined;
14
+ sessionId: string;
15
+ /** `CONFIG.chatMessage.maxMemories` */
16
+ maxMemories?: number;
17
+ /** `CONFIG.chatMessage.excludeCurrentSession` */
18
+ excludeCurrentSession?: boolean;
19
+ /** `CONFIG.chatMessage.maxAgeDays` */
20
+ maxAgeDays?: number;
21
+ }
22
+ /**
23
+ * The project's most recent memories as a context section, shared by every host
24
+ * that injects them at session start (OpenCode's first `chat.message`, Claude
25
+ * Code `SessionStart`). Returns null when no memory qualifies.
26
+ */
27
+ export declare function buildRecentMemoriesSection(options: RecentMemoriesOptions): Promise<string | null>;
9
28
  /** Delimit a retrieval section so it is recognisable as injected memory, not user text. */
10
29
  export declare function wrapRetrievalSection(section: string): string;
30
+ /** Remove injected retrieval sections, so injected memories are never captured as user text. */
31
+ export declare function stripRetrievalSections(text: string): string;
11
32
  /** Format a session's own memories for re-injection after compaction. */
12
33
  export declare function formatMemoriesForCompaction(memories: any[]): string;
@@ -26,10 +26,45 @@ export async function buildRetrievalSection(prompt, directory, sessionId) {
26
26
  });
27
27
  return memoryContext || null;
28
28
  }
29
+ /**
30
+ * The project's most recent memories as a context section, shared by every host
31
+ * that injects them at session start (OpenCode's first `chat.message`, Claude
32
+ * Code `SessionStart`). Returns null when no memory qualifies.
33
+ */
34
+ export async function buildRecentMemoriesSection(options) {
35
+ const listResult = await memoryClient.listMemories(options.projectTag, options.maxMemories);
36
+ let memories = listResult.success ? listResult.memories : [];
37
+ if (options.excludeCurrentSession) {
38
+ memories = memories.filter((m) => m.metadata?.sessionID !== options.sessionId);
39
+ }
40
+ if (options.maxAgeDays) {
41
+ const cutoffDate = Date.now() - options.maxAgeDays * 86400000;
42
+ memories = memories.filter((m) => new Date(m.createdAt).getTime() > cutoffDate);
43
+ }
44
+ if (memories.length === 0)
45
+ return null;
46
+ const projectMemories = {
47
+ results: memories.map((m) => ({
48
+ similarity: 1.0,
49
+ memory: m.summary,
50
+ })),
51
+ total: memories.length,
52
+ timing: 0,
53
+ };
54
+ const memoryContext = await formatContextForPrompt(options.userEmail || null, projectMemories);
55
+ return memoryContext || null;
56
+ }
29
57
  /** Delimit a retrieval section so it is recognisable as injected memory, not user text. */
30
58
  export function wrapRetrievalSection(section) {
31
59
  return `<${RETRIEVAL_SECTION_TAG}>\n${section}\n</${RETRIEVAL_SECTION_TAG}>`;
32
60
  }
61
+ // Only a closed section is injected context: `wrapRetrievalSection` always closes it, and the
62
+ // Claude Code hook keeps the closing tag when it truncates. An unclosed tag is user text.
63
+ const RETRIEVAL_SECTION_RE = new RegExp(`<${RETRIEVAL_SECTION_TAG}>[\\s\\S]*?</${RETRIEVAL_SECTION_TAG}>`, "g");
64
+ /** Remove injected retrieval sections, so injected memories are never captured as user text. */
65
+ export function stripRetrievalSections(text) {
66
+ return text.replace(RETRIEVAL_SECTION_RE, "").trim();
67
+ }
33
68
  const EMBEDDED_TAGS_FOOTER_RE = /\n*Tags: ([^\n]*)\s*$/;
34
69
  function normalizeTagsKey(tags) {
35
70
  return tags
@@ -42,6 +42,16 @@ export declare class BackfillControls {
42
42
  runNow(host: BackfillHost, cwd: string): Promise<{
43
43
  started: true;
44
44
  }>;
45
+ /**
46
+ * The automatic run after a host start, under the auto-backfill rules: the
47
+ * start-up delay, `autoBackfill`, pause, and the single-run lock. The web
48
+ * app calls it on the first Claude Code session start. Returns false when
49
+ * the run cannot start here.
50
+ */
51
+ startAuto(host: BackfillHost, cwd: string): Promise<{
52
+ started: boolean;
53
+ }>;
54
+ private launch;
45
55
  /** Stop after the current exchange; a run in another process sees the flag at its next write. */
46
56
  pause(host: BackfillHost): Promise<{
47
57
  paused: true;
@@ -1,6 +1,7 @@
1
1
  import { CONFIG } from "../config.js";
2
2
  import { log } from "../services/logger.js";
3
- import { getAutoCaptureProviderStatus, resolvePiLiveModel, } from "../services/ai/live-model-choice.js";
3
+ import { hostLabel } from "../types/host-label.js";
4
+ import { externalModelIssues, getAutoCaptureProviderStatus, resolvePiLiveModel, } from "../services/ai/live-model-choice.js";
4
5
  import { parseBackfillModel } from "./backfill-model.js";
5
6
  import { readImportRun, setBackfillPaused } from "./import-runs.js";
6
7
  /** A host runtime in this process that can resolve its signed-in backfill models. */
@@ -24,7 +25,6 @@ export class BackfillControlError extends Error {
24
25
  this.name = "BackfillControlError";
25
26
  }
26
27
  }
27
- const label = (host) => (host === "pi" ? "Pi" : "OpenCode");
28
28
  /** True when the host's backfill model is the external API, directly or through the live rule. */
29
29
  export function backfillUsesExternal(host) {
30
30
  const choice = parseBackfillModel(CONFIG, host);
@@ -39,10 +39,15 @@ export function backfillUsesExternal(host) {
39
39
  }
40
40
  /** Why Run now is not available for the host in this process, or null when it is. */
41
41
  export function runNowUnavailableReason(host) {
42
+ // Claude Code has no host model: its backfill needs the external API, so name the missing setting.
43
+ if (host === "claude-code") {
44
+ const issues = externalModelIssues(CONFIG);
45
+ return issues.length > 0 ? issues.join("; ") : null;
46
+ }
42
47
  if (hostResolvers.has(host))
43
48
  return null;
44
49
  if (!backfillUsesExternal(host)) {
45
- return `Open ${label(host)}, or choose the external API for ${label(host)}'s backfill`;
50
+ return `Open ${hostLabel(host)}, or choose the external API for ${hostLabel(host)}'s backfill`;
46
51
  }
47
52
  return null;
48
53
  }
@@ -61,7 +66,11 @@ export class BackfillControls {
61
66
  run: await readImportRun(host),
62
67
  runNowUnavailable: runNowUnavailableReason(host),
63
68
  });
64
- return { pi: await entry("pi"), opencode: await entry("opencode") };
69
+ return {
70
+ pi: await entry("pi"),
71
+ opencode: await entry("opencode"),
72
+ "claude-code": await entry("claude-code"),
73
+ };
65
74
  }
66
75
  async runNow(host, cwd) {
67
76
  const reason = runNowUnavailableReason(host);
@@ -69,10 +78,10 @@ export class BackfillControls {
69
78
  throw new BackfillControlError(reason, 409);
70
79
  const current = await readImportRun(host);
71
80
  if (current?.state === "running" || this.controllers.has(host)) {
72
- throw new BackfillControlError(`${host === "pi" ? "A" : "An"} ${label(host)} import is already running`, 409);
81
+ throw new BackfillControlError(`${host === "opencode" ? "An" : "A"} ${hostLabel(host)} import is already running`, 409);
73
82
  }
74
83
  if (current?.paused) {
75
- throw new BackfillControlError(`${label(host)}'s backfill is paused; resume it first`, 409);
84
+ throw new BackfillControlError(`${hostLabel(host)}'s backfill is paused; resume it first`, 409);
76
85
  }
77
86
  // Fail fast with the missing setting instead of starting a run that cannot call a model.
78
87
  if (backfillUsesExternal(host) || !hostResolvers.has(host)) {
@@ -81,19 +90,40 @@ export class BackfillControls {
81
90
  if (issues.length > 0)
82
91
  throw new BackfillControlError(issues.join("; "), 409);
83
92
  }
93
+ await this.launch(host, cwd, true);
94
+ return { started: true };
95
+ }
96
+ /**
97
+ * The automatic run after a host start, under the auto-backfill rules: the
98
+ * start-up delay, `autoBackfill`, pause, and the single-run lock. The web
99
+ * app calls it on the first Claude Code session start. Returns false when
100
+ * the run cannot start here.
101
+ */
102
+ async startAuto(host, cwd) {
103
+ if (this.controllers.has(host))
104
+ return { started: false };
105
+ const reason = runNowUnavailableReason(host);
106
+ if (reason) {
107
+ log("Automatic backfill not started", { host, reason });
108
+ return { started: false };
109
+ }
110
+ await this.launch(host, cwd, false);
111
+ return { started: true };
112
+ }
113
+ async launch(host, cwd, userStarted) {
84
114
  const controller = new AbortController();
85
115
  this.controllers.set(host, controller);
86
116
  const { scheduleAutoBackfill } = await import("./auto-backfill.js");
117
+ const source = userStarted ? "Backfill from the Settings page" : "Automatic backfill";
87
118
  void scheduleAutoBackfill({
88
119
  host,
89
120
  cwd,
90
121
  signal: controller.signal,
91
- surface: "web",
92
- userStarted: true,
122
+ ...(userStarted ? { surface: "web", userStarted: true } : {}),
93
123
  resolveModels: () => resolveModels(host),
94
- notify: (message) => log("Backfill from the Settings page", { host, message: message.slice(0, 200) }),
124
+ notify: (message) => log(source, { host, message: message.slice(0, 200) }),
95
125
  })
96
- .catch((error) => log("Backfill from the Settings page failed", {
126
+ .catch((error) => log(`${source} failed`, {
97
127
  host,
98
128
  error: error instanceof Error ? error.name : "unknown",
99
129
  }))
@@ -101,7 +131,6 @@ export class BackfillControls {
101
131
  if (this.controllers.get(host) === controller)
102
132
  this.controllers.delete(host);
103
133
  });
104
- return { started: true };
105
134
  }
106
135
  /** Stop after the current exchange; a run in another process sees the flag at its next write. */
107
136
  async pause(host) {
@@ -1,4 +1,4 @@
1
- export type BackfillHost = "pi" | "opencode";
1
+ export type BackfillHost = "pi" | "opencode" | "claude-code";
2
2
  export type BackfillModel = "inherit" | "external" | {
3
3
  provider: string;
4
4
  model: string;
@@ -1,5 +1,8 @@
1
1
  /** Parse the global backfill model without importing the runtime config. */
2
2
  export function parseBackfillModel(config, host) {
3
+ // Claude Code hooks cannot call a session model, so its backfill has no setting.
4
+ if (host === "claude-code")
5
+ return "external";
3
6
  const value = host === "pi" ? config.piBackfillModel : config.opencodeBackfillModel;
4
7
  if (value === undefined || value === "inherit")
5
8
  return "inherit";
@@ -0,0 +1,74 @@
1
+ import type { ImportWindow } from "./importer.js";
2
+ /**
3
+ * Pure parsing of Claude Code session transcripts
4
+ * (`~/.claude/projects/<project-folder>/<session-id>.jsonl`). The format is
5
+ * internal to Claude Code and changes between versions, so the view below is
6
+ * structural and minimal. It was written against Claude Code v2.1.284 and is
7
+ * pinned by the fixtures in `tests/fixtures/claude-transcripts/`.
8
+ *
9
+ * Live capture and the history import both build their work units here.
10
+ */
11
+ export interface ClaudeContentBlock {
12
+ type: string;
13
+ text?: string;
14
+ thinking?: string;
15
+ id?: string;
16
+ name?: string;
17
+ input?: unknown;
18
+ tool_use_id?: string;
19
+ content?: unknown;
20
+ }
21
+ export interface ClaudeTranscriptEntry {
22
+ type: string;
23
+ uuid?: string;
24
+ parentUuid?: string | null;
25
+ timestamp?: string;
26
+ cwd?: string;
27
+ sessionId?: string;
28
+ isMeta?: boolean;
29
+ isSidechain?: boolean;
30
+ version?: string;
31
+ message?: {
32
+ id?: string;
33
+ role?: string;
34
+ content?: string | ClaudeContentBlock[];
35
+ };
36
+ }
37
+ export type ClaudeLineResult = {
38
+ status: "entry";
39
+ entry: ClaudeTranscriptEntry;
40
+ } | {
41
+ status: "unknown-type";
42
+ } | {
43
+ status: "unreadable";
44
+ } | {
45
+ status: "blank";
46
+ };
47
+ export interface ParsedClaudeTranscript {
48
+ entries: ClaudeTranscriptEntry[];
49
+ unreadableLines: number;
50
+ unknownTypes: number;
51
+ }
52
+ export interface ClaudeConversationWindow extends ImportWindow {
53
+ /** Working directory recorded on the prompt entry. */
54
+ cwd: string | null;
55
+ }
56
+ /** Parse one transcript line. A JSON value without a string `type` is unreadable. */
57
+ export declare function parseClaudeTranscriptLine(line: string): ClaudeLineResult;
58
+ /** Parse a whole transcript, counting the lines that were skipped. */
59
+ export declare function parseClaudeTranscript(text: string): ParsedClaudeTranscript;
60
+ /**
61
+ * All capture windows in a transcript, oldest first: one per user prompt that
62
+ * produced assistant text or tool work. Sidechain (subagent) entries, meta
63
+ * entries, and tool results are skipped; tool results and meta entries do not
64
+ * end a turn. Thinking blocks are never included. Tool inputs are bounded by
65
+ * the same truncation policy as the other hosts.
66
+ */
67
+ export declare function extractClaudeConversationWindows(entries: ClaudeTranscriptEntry[]): ClaudeConversationWindow[];
68
+ /**
69
+ * Windows for live capture: every window whose prompt comes after the cursor
70
+ * entry. Without a cursor, or when the cursor is no longer in the transcript,
71
+ * only the last window is returned, so a lost cursor never re-captures a
72
+ * whole session.
73
+ */
74
+ export declare function extractClaudeWindowsAfter(entries: ClaudeTranscriptEntry[], cursorUserUuid: string | null): ClaudeConversationWindow[];