@tekmemo/cli 0.1.0

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.
@@ -0,0 +1,3719 @@
1
+ import { createRequire } from "node:module";
2
+ import { TekMemoCloudError, createTekMemoCloudClient, redactSecrets } from "@tekmemo/cloud-client";
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { CANONICAL_TEKMEMO_FILES, CHUNKS_INDEX_PATH, CONVERSATIONS_MEMORY_PATH, CORE_MEMORY_PATH, GRAPH_EDGES_PATH, GRAPH_NODES_PATH, MANIFEST_PATH, MEMORY_EVENTS_PATH, NOTES_MEMORY_PATH, SNAPSHOTS_INDEX_PATH, TEKMEMO_DIR, createDefaultTekMemoManifest, parseManifest, validateTekMemoManifest } from "tekmemo";
6
+ import { createHash, randomUUID } from "node:crypto";
7
+ import { Command, CommanderError } from "commander";
8
+ import { createTekMemoAgentSession, extractSessionMemory } from "@tekmemo/agentfs";
9
+ import { createNodeFsMemoryStore } from "@tekmemo/fs";
10
+ import { z } from "zod";
11
+ import readline from "node:readline/promises";
12
+
13
+ //#region src/errors/cli-errors.ts
14
+ var CliError = class extends Error {
15
+ code;
16
+ exitCode;
17
+ cause;
18
+ constructor(code, message, options) {
19
+ super(message);
20
+ this.name = this.constructor.name;
21
+ this.code = code;
22
+ this.exitCode = options?.exitCode ?? 1;
23
+ this.cause = options?.cause;
24
+ }
25
+ };
26
+ var CliUsageError = class extends CliError {
27
+ constructor(message, options) {
28
+ super("CLI_USAGE_ERROR", message, {
29
+ exitCode: 1,
30
+ cause: options?.cause
31
+ });
32
+ }
33
+ };
34
+ var CliValidationError = class extends CliError {
35
+ constructor(message, options) {
36
+ super("CLI_VALIDATION_ERROR", message, {
37
+ exitCode: 1,
38
+ cause: options?.cause
39
+ });
40
+ }
41
+ };
42
+ var CliFsError = class extends CliError {
43
+ constructor(message, options) {
44
+ super("CLI_FS_ERROR", message, {
45
+ exitCode: 1,
46
+ cause: options?.cause
47
+ });
48
+ }
49
+ };
50
+ var CliProtocolError = class extends CliError {
51
+ constructor(message, options) {
52
+ super("CLI_PROTOCOL_ERROR", message, {
53
+ exitCode: 1,
54
+ cause: options?.cause
55
+ });
56
+ }
57
+ };
58
+ var CliJsonlError = class extends CliError {
59
+ constructor(message, options) {
60
+ super("CLI_JSONL_ERROR", message, {
61
+ exitCode: 1,
62
+ cause: options?.cause
63
+ });
64
+ }
65
+ };
66
+
67
+ //#endregion
68
+ //#region src/cloud/client.ts
69
+ function createCliCloudClient(options = {}) {
70
+ return createTekMemoCloudClient(toCloudClientOptions(options));
71
+ }
72
+ function toCloudClientOptions(options = {}) {
73
+ const normalized = normalizeCloudConnectionOptions(options);
74
+ return {
75
+ baseUrl: normalized.baseUrl,
76
+ ...normalized.apiKey !== void 0 ? { apiKey: normalized.apiKey } : {},
77
+ ...normalized.projectId !== void 0 ? { defaultProjectId: normalized.projectId } : {},
78
+ ...normalized.workspaceId !== void 0 ? { defaultWorkspaceId: normalized.workspaceId } : {},
79
+ ...normalized.timeoutMs !== void 0 ? { timeoutMs: normalized.timeoutMs } : {},
80
+ userAgent: "@tekmemo/cli",
81
+ requireApiKey: !options.allowMissingApiKey,
82
+ acceptLegacyEnvelope: false
83
+ };
84
+ }
85
+ function normalizeCloudConnectionOptions(options = {}) {
86
+ const baseUrl = firstNonEmpty$1(options.cloudUrl, process.env.TEKMEMO_CLOUD_URL, process.env.TEKMEMO_API_URL);
87
+ if (!baseUrl) throw new CliUsageError("TekMemo Cloud URL is required. Pass --cloud-url or set TEKMEMO_CLOUD_URL.");
88
+ const apiKey = firstNonEmpty$1(options.apiKey, process.env.TEKMEMO_API_KEY);
89
+ if (!apiKey && !options.allowMissingApiKey) throw new CliUsageError("TekMemo Cloud API key is required. Pass --api-key or set TEKMEMO_API_KEY.");
90
+ const workspaceId = firstNonEmpty$1(options.workspaceId, process.env.TEKMEMO_WORKSPACE_ID);
91
+ const projectId = firstNonEmpty$1(options.projectId, process.env.TEKMEMO_PROJECT_ID);
92
+ if (!projectId && !options.allowMissingProjectId) throw new CliUsageError("TekMemo Cloud project ID is required. Pass --project-id or set TEKMEMO_PROJECT_ID.");
93
+ const timeoutMs = normalizeTimeoutMs$1(options.timeoutMs);
94
+ return {
95
+ baseUrl,
96
+ ...apiKey !== void 0 ? { apiKey } : {},
97
+ ...workspaceId !== void 0 ? { workspaceId } : {},
98
+ ...projectId !== void 0 ? { projectId } : {},
99
+ ...timeoutMs !== void 0 ? { timeoutMs } : {}
100
+ };
101
+ }
102
+ function formatCloudError(error) {
103
+ if (error instanceof TekMemoCloudError) {
104
+ const parts = [error.message];
105
+ if (error.status !== void 0) parts.push(`status=${error.status}`);
106
+ if (error.code) parts.push(`code=${error.code}`);
107
+ if (error.requestId) parts.push(`requestId=${error.requestId}`);
108
+ if (error.retryAfterMs !== void 0) parts.push(`retryAfterMs=${error.retryAfterMs}`);
109
+ return parts.join(" ");
110
+ }
111
+ return error instanceof Error ? error.message : String(error);
112
+ }
113
+ function cloudConnectionSummary(options) {
114
+ return {
115
+ baseUrl: options.baseUrl,
116
+ apiKey: options.apiKey ? redactSecrets(options.apiKey, [options.apiKey]) : null,
117
+ workspaceId: options.workspaceId ?? null,
118
+ projectId: options.projectId ?? null,
119
+ timeoutMs: options.timeoutMs ?? null
120
+ };
121
+ }
122
+ function firstNonEmpty$1(...values) {
123
+ for (const value of values) {
124
+ const trimmed = value?.trim();
125
+ if (trimmed) return trimmed;
126
+ }
127
+ }
128
+ function normalizeTimeoutMs$1(value) {
129
+ if (value === void 0) return void 0;
130
+ if (typeof value === "number") {
131
+ if (!Number.isInteger(value) || value < 1) throw new CliUsageError("timeout must be a positive integer in milliseconds.");
132
+ return value;
133
+ }
134
+ const parsed = Number(value);
135
+ if (!Number.isInteger(parsed) || parsed < 1) throw new CliUsageError("timeout must be a positive integer in milliseconds.");
136
+ return parsed;
137
+ }
138
+
139
+ //#endregion
140
+ //#region src/config/runtime.ts
141
+ async function resolveCliRuntimeConfig(input) {
142
+ const flags = input.flags ?? {};
143
+ const env = input.env ?? process.env;
144
+ const initialRoot = firstNonEmpty(flags.root, env.TEKMEMO_ROOT, input.cwd) ?? input.cwd;
145
+ const configPath = path.join(path.resolve(input.cwd, initialRoot), ".tekmemo", "config.json");
146
+ const config = await readOptionalConfig(configPath);
147
+ const configRoot = config.value?.root;
148
+ const root = path.resolve(input.cwd, firstNonEmpty(flags.root, env.TEKMEMO_ROOT, configRoot, initialRoot) ?? initialRoot);
149
+ const runtime = normalizeRuntime(firstNonEmpty(flags.runtime, env.TEKMEMO_RUNTIME, config.value?.runtime, "local"));
150
+ const readPolicy = normalizeReadPolicy(firstNonEmpty(flags.readPolicy, env.TEKMEMO_READ_POLICY, config.value?.hybrid?.readPolicy, "local-first"));
151
+ const writePolicy = normalizeWritePolicy(firstNonEmpty(flags.writePolicy, env.TEKMEMO_WRITE_POLICY, config.value?.hybrid?.writePolicy, "local-first"));
152
+ const timeoutMs = normalizeTimeoutMs(firstDefined(flags.timeoutMs, env.TEKMEMO_CLOUD_TIMEOUT_MS, config.value?.cloud?.timeoutMs));
153
+ return {
154
+ runtime,
155
+ root,
156
+ configPath,
157
+ configLoaded: config.loaded,
158
+ cloud: compactCloud({
159
+ cloudUrl: firstNonEmpty(flags.cloudUrl, env.TEKMEMO_CLOUD_URL, env.TEKMEMO_API_URL, config.value?.cloud?.baseUrl),
160
+ apiKey: firstNonEmpty(flags.apiKey, env.TEKMEMO_API_KEY),
161
+ workspaceId: firstNonEmpty(flags.workspaceId, env.TEKMEMO_WORKSPACE_ID, config.value?.cloud?.workspaceId),
162
+ projectId: firstNonEmpty(flags.projectId, env.TEKMEMO_PROJECT_ID, config.value?.cloud?.projectId),
163
+ ...timeoutMs !== void 0 ? { timeoutMs } : {}
164
+ }),
165
+ hybrid: {
166
+ readPolicy,
167
+ writePolicy
168
+ }
169
+ };
170
+ }
171
+ async function writeDefaultCliConfig(input) {
172
+ const root = path.resolve(input.cwd, input.root ?? ".");
173
+ const configPath = path.join(root, ".tekmemo", "config.json");
174
+ await fs.mkdir(path.dirname(configPath), { recursive: true });
175
+ const exists = await fileExists(configPath);
176
+ if (exists && !input.force) return {
177
+ path: configPath,
178
+ created: false,
179
+ overwritten: false
180
+ };
181
+ const config = input.config ?? {
182
+ version: 1,
183
+ runtime: "local",
184
+ root: "."
185
+ };
186
+ await fs.writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
187
+ return {
188
+ path: configPath,
189
+ created: !exists,
190
+ overwritten: exists
191
+ };
192
+ }
193
+ async function readOptionalConfig(configPath) {
194
+ try {
195
+ const raw = await fs.readFile(configPath, "utf8");
196
+ return {
197
+ loaded: true,
198
+ value: validateConfig(JSON.parse(raw), configPath)
199
+ };
200
+ } catch (error) {
201
+ if (isNodeError$1(error) && error.code === "ENOENT") return { loaded: false };
202
+ if (error instanceof SyntaxError) throw new CliUsageError(`Invalid TekMemo config JSON at ${configPath}: ${error.message}`);
203
+ throw error;
204
+ }
205
+ }
206
+ function validateConfig(value, configPath) {
207
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new CliUsageError(`TekMemo config must be an object: ${configPath}`);
208
+ const record = value;
209
+ if (record.runtime !== void 0) normalizeRuntime(record.runtime);
210
+ if (record.hybrid?.readPolicy !== void 0) normalizeReadPolicy(record.hybrid.readPolicy);
211
+ if (record.hybrid?.writePolicy !== void 0) normalizeWritePolicy(record.hybrid.writePolicy);
212
+ if (record.cloud?.timeoutMs !== void 0) normalizeTimeoutMs(record.cloud.timeoutMs);
213
+ return record;
214
+ }
215
+ function normalizeRuntime(value) {
216
+ if (value === "local" || value === "cloud" || value === "hybrid") return value;
217
+ throw new CliUsageError("runtime must be local, cloud, or hybrid.");
218
+ }
219
+ function normalizeReadPolicy(value) {
220
+ if (value === "local-first" || value === "cloud-first" || value === "local-only" || value === "cloud-only") return value;
221
+ throw new CliUsageError("read policy must be local-first, cloud-first, local-only, or cloud-only.");
222
+ }
223
+ function normalizeWritePolicy(value) {
224
+ if (value === "local-first" || value === "cloud-first" || value === "local-only" || value === "cloud-only") return value;
225
+ throw new CliUsageError("write policy must be local-first, cloud-first, local-only, or cloud-only.");
226
+ }
227
+ function normalizeTimeoutMs(value) {
228
+ if (value === void 0 || value === null || value === "") return void 0;
229
+ const parsed = typeof value === "number" ? value : Number(value);
230
+ if (!Number.isInteger(parsed) || parsed < 1) throw new CliUsageError("cloud timeout must be a positive integer in milliseconds.");
231
+ return parsed;
232
+ }
233
+ function firstNonEmpty(...values) {
234
+ for (const value of values) {
235
+ if (typeof value !== "string") continue;
236
+ const trimmed = value.trim();
237
+ if (trimmed) return trimmed;
238
+ }
239
+ }
240
+ function firstDefined(...values) {
241
+ for (const value of values) if (value !== void 0) return value;
242
+ }
243
+ function compactCloud(input) {
244
+ return {
245
+ ...input.cloudUrl !== void 0 ? { cloudUrl: input.cloudUrl } : {},
246
+ ...input.apiKey !== void 0 ? { apiKey: input.apiKey } : {},
247
+ ...input.workspaceId !== void 0 ? { workspaceId: input.workspaceId } : {},
248
+ ...input.projectId !== void 0 ? { projectId: input.projectId } : {},
249
+ ...input.timeoutMs !== void 0 ? { timeoutMs: input.timeoutMs } : {}
250
+ };
251
+ }
252
+ async function fileExists(filePath) {
253
+ try {
254
+ await fs.stat(filePath);
255
+ return true;
256
+ } catch (error) {
257
+ if (isNodeError$1(error) && error.code === "ENOENT") return false;
258
+ throw error;
259
+ }
260
+ }
261
+ function isNodeError$1(error) {
262
+ return error instanceof Error && "code" in error;
263
+ }
264
+
265
+ //#endregion
266
+ //#region src/fs/paths.ts
267
+ function normalizeRootDir(rootDir) {
268
+ if (typeof rootDir !== "string" || rootDir.trim().length === 0) throw new CliFsError("rootDir must be a non-empty string.");
269
+ if (rootDir.includes("\0")) throw new CliFsError("rootDir must not contain null bytes.");
270
+ return path.resolve(rootDir);
271
+ }
272
+ function resolveInsideRoot(rootDir, relativePath) {
273
+ if (typeof relativePath !== "string" || relativePath.trim().length === 0) throw new CliFsError("relativePath must be a non-empty string.");
274
+ if (relativePath.includes("\0")) throw new CliFsError("relativePath must not contain null bytes.");
275
+ if (path.isAbsolute(relativePath)) throw new CliFsError("relativePath must not be absolute.");
276
+ const normalized = relativePath.replaceAll("\\", "/");
277
+ if (normalized.split("/").includes("..")) throw new CliFsError("relativePath must not contain path traversal.");
278
+ const resolved = path.resolve(rootDir, normalized);
279
+ const relative = path.relative(rootDir, resolved);
280
+ if (relative.startsWith("..") || path.isAbsolute(relative)) throw new CliFsError("Resolved path escaped rootDir.");
281
+ return resolved;
282
+ }
283
+
284
+ //#endregion
285
+ //#region src/fs/tekmemo-fs.ts
286
+ var TekMemoFileSystem = class {
287
+ rootDir;
288
+ rejectSymlinkedTekMemoDir;
289
+ constructor(options) {
290
+ this.rootDir = normalizeRootDir(options.rootDir);
291
+ this.rejectSymlinkedTekMemoDir = options.rejectSymlinkedTekMemoDir ?? true;
292
+ }
293
+ resolve(relativePath) {
294
+ return resolveInsideRoot(this.rootDir, relativePath);
295
+ }
296
+ async ensureSafeRoot() {
297
+ await fs.mkdir(this.rootDir, { recursive: true });
298
+ await this.rejectUnsafeTekMemoSymlinkIfNeeded();
299
+ }
300
+ async exists(relativePath) {
301
+ try {
302
+ await fs.lstat(this.resolve(relativePath));
303
+ return true;
304
+ } catch (error) {
305
+ if (isNodeError(error) && error.code === "ENOENT") return false;
306
+ throw new CliFsError(`Failed checking path "${relativePath}".`, { cause: error });
307
+ }
308
+ }
309
+ async readText(relativePath) {
310
+ try {
311
+ return await fs.readFile(this.resolve(relativePath), "utf8");
312
+ } catch (error) {
313
+ throw new CliFsError(`Failed reading "${relativePath}".`, { cause: error });
314
+ }
315
+ }
316
+ async readTextIfExists(relativePath) {
317
+ try {
318
+ return await fs.readFile(this.resolve(relativePath), "utf8");
319
+ } catch (error) {
320
+ if (isNodeError(error) && error.code === "ENOENT") return void 0;
321
+ throw new CliFsError(`Failed reading "${relativePath}".`, { cause: error });
322
+ }
323
+ }
324
+ async writeText(relativePath, content) {
325
+ if (typeof content !== "string") throw new CliFsError("content must be a string.");
326
+ const target = this.resolve(relativePath);
327
+ const parent = path.dirname(target);
328
+ await fs.mkdir(parent, { recursive: true });
329
+ const tmp = path.join(parent, `.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`);
330
+ try {
331
+ await fs.writeFile(tmp, content, "utf8");
332
+ await fs.rename(tmp, target);
333
+ } catch (error) {
334
+ await fs.rm(tmp, { force: true }).catch(() => void 0);
335
+ throw new CliFsError(`Failed writing "${relativePath}".`, { cause: error });
336
+ }
337
+ }
338
+ async appendText(relativePath, content) {
339
+ if (typeof content !== "string") throw new CliFsError("content must be a string.");
340
+ const target = this.resolve(relativePath);
341
+ await fs.mkdir(path.dirname(target), { recursive: true });
342
+ try {
343
+ await fs.appendFile(target, content, "utf8");
344
+ } catch (error) {
345
+ throw new CliFsError(`Failed appending "${relativePath}".`, { cause: error });
346
+ }
347
+ }
348
+ async mkdir(relativePath) {
349
+ try {
350
+ await fs.mkdir(this.resolve(relativePath), { recursive: true });
351
+ } catch (error) {
352
+ throw new CliFsError(`Failed creating directory "${relativePath}".`, { cause: error });
353
+ }
354
+ }
355
+ async stat(relativePath) {
356
+ try {
357
+ const stats = await fs.lstat(this.resolve(relativePath));
358
+ return {
359
+ isFile: stats.isFile(),
360
+ isDirectory: stats.isDirectory(),
361
+ isSymbolicLink: stats.isSymbolicLink(),
362
+ size: stats.size
363
+ };
364
+ } catch (error) {
365
+ throw new CliFsError(`Failed stat for "${relativePath}".`, { cause: error });
366
+ }
367
+ }
368
+ async rejectUnsafeTekMemoSymlinkIfNeeded() {
369
+ if (!this.rejectSymlinkedTekMemoDir) return;
370
+ try {
371
+ if ((await fs.lstat(this.resolve(".tekmemo"))).isSymbolicLink()) throw new CliFsError("Refusing to use symlinked .tekmemo directory.");
372
+ } catch (error) {
373
+ if (error instanceof CliFsError) throw error;
374
+ if (isNodeError(error) && error.code === "ENOENT") return;
375
+ throw new CliFsError("Failed checking .tekmemo directory safety.", { cause: error });
376
+ }
377
+ }
378
+ };
379
+ function isNodeError(error) {
380
+ return error instanceof Error && "code" in error;
381
+ }
382
+
383
+ //#endregion
384
+ //#region src/output/output.ts
385
+ const colors = {
386
+ reset: "\x1B[0m",
387
+ red: "\x1B[31m",
388
+ green: "\x1B[32m",
389
+ yellow: "\x1B[33m",
390
+ dim: "\x1B[2m"
391
+ };
392
+ function shouldDisableColor(noColor) {
393
+ if (noColor) return true;
394
+ if ("NO_COLOR" in process.env) return true;
395
+ if (process.env.TERM === "dumb") return true;
396
+ return false;
397
+ }
398
+ function createBufferedOutput(options) {
399
+ const stdout = [];
400
+ const stderr = [];
401
+ const c = shouldDisableColor(options?.noColor) ? {
402
+ red: "",
403
+ green: "",
404
+ yellow: "",
405
+ reset: "",
406
+ dim: ""
407
+ } : colors;
408
+ return {
409
+ stdout,
410
+ stderr,
411
+ write(message) {
412
+ stdout.push(message);
413
+ },
414
+ error(message) {
415
+ stderr.push(`${c.red}${message}${c.reset}`);
416
+ },
417
+ success(message) {
418
+ stdout.push(`${c.green}${message}${c.reset}`);
419
+ },
420
+ warn(message) {
421
+ stdout.push(`${c.yellow}${message}${c.reset}`);
422
+ }
423
+ };
424
+ }
425
+ function printHumanOrJson(output, value, human, json = false) {
426
+ if (json) {
427
+ output.write(JSON.stringify(value, null, 2));
428
+ return;
429
+ }
430
+ output.write(human);
431
+ }
432
+ function printJsonEnvelope(output, command, data) {
433
+ const envelope = {
434
+ ok: true,
435
+ command,
436
+ data
437
+ };
438
+ output.write(JSON.stringify(envelope, null, 2));
439
+ }
440
+ function printJsonError(output, command, code, message, details) {
441
+ const error = {
442
+ ok: false,
443
+ command,
444
+ error: {
445
+ code,
446
+ message,
447
+ ...details !== void 0 ? { details } : {}
448
+ }
449
+ };
450
+ output.write(JSON.stringify(error, null, 2));
451
+ }
452
+
453
+ //#endregion
454
+ //#region src/protocol/constants.ts
455
+ /**
456
+ * Flat CLI path map kept for command ergonomics.
457
+ * The values intentionally come from `tekmemo`, so the CLI cannot drift from
458
+ * the canonical protocol owned by the core package.
459
+ */
460
+ const TEKMEMO_PATHS = {
461
+ manifest: MANIFEST_PATH,
462
+ coreMemory: CORE_MEMORY_PATH,
463
+ notesMemory: NOTES_MEMORY_PATH,
464
+ memoryEvents: MEMORY_EVENTS_PATH,
465
+ conversations: CONVERSATIONS_MEMORY_PATH,
466
+ chunks: CHUNKS_INDEX_PATH,
467
+ graphNodes: GRAPH_NODES_PATH,
468
+ graphEdges: GRAPH_EDGES_PATH,
469
+ snapshots: SNAPSHOTS_INDEX_PATH,
470
+ snapshotsDir: `${TEKMEMO_DIR}/snapshots`,
471
+ tmpDir: `${TEKMEMO_DIR}/tmp`
472
+ };
473
+ const REQUIRED_FILES = CANONICAL_TEKMEMO_FILES;
474
+ const REQUIRED_DIRS = [
475
+ TEKMEMO_DIR,
476
+ `${TEKMEMO_DIR}/memory`,
477
+ `${TEKMEMO_DIR}/events`,
478
+ `${TEKMEMO_DIR}/indexes`,
479
+ `${TEKMEMO_DIR}/graph`,
480
+ `${TEKMEMO_DIR}/snapshots`,
481
+ `${TEKMEMO_DIR}/tmp`
482
+ ];
483
+
484
+ //#endregion
485
+ //#region src/protocol/jsonl.ts
486
+ function parseJsonl(content, options) {
487
+ const strict = options?.strict ?? false;
488
+ const records = [];
489
+ content.split(/\r?\n/).forEach((line, index) => {
490
+ const lineNumber = index + 1;
491
+ const trimmed = line.trim();
492
+ if (trimmed.length === 0) return;
493
+ try {
494
+ const parsed = JSON.parse(trimmed);
495
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new CliJsonlError(`Line ${lineNumber} is not a JSON object.`);
496
+ records.push({
497
+ line: lineNumber,
498
+ value: parsed
499
+ });
500
+ } catch (error) {
501
+ if (strict) {
502
+ if (error instanceof CliJsonlError) throw error;
503
+ throw new CliJsonlError(`Line ${lineNumber} is invalid JSON.`, { cause: error });
504
+ }
505
+ }
506
+ });
507
+ return records;
508
+ }
509
+ function stringifyJsonl(records) {
510
+ return records.map((record) => JSON.stringify(record)).join("\n") + (records.length > 0 ? "\n" : "");
511
+ }
512
+
513
+ //#endregion
514
+ //#region src/protocol/manifest.ts
515
+ function createDefaultManifest(input) {
516
+ return createDefaultTekMemoManifest({
517
+ projectId: input?.projectId ?? `proj_${randomUUID()}`,
518
+ ...input?.now !== void 0 ? { now: () => input.now } : {}
519
+ });
520
+ }
521
+ function parseManifest$1(content) {
522
+ try {
523
+ return parseManifest(content);
524
+ } catch (error) {
525
+ throw new CliProtocolError(`manifest.json is invalid: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
526
+ }
527
+ }
528
+ function validateManifest(value) {
529
+ try {
530
+ return validateTekMemoManifest(value);
531
+ } catch (error) {
532
+ throw new CliProtocolError(`manifest.json is invalid: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
533
+ }
534
+ }
535
+
536
+ //#endregion
537
+ //#region src/protocol/summary.ts
538
+ async function inspectTekMemo(fs) {
539
+ const exists = await fs.exists(".tekmemo");
540
+ const manifestContent = await fs.readTextIfExists(TEKMEMO_PATHS.manifest);
541
+ const manifest = manifestContent === void 0 ? void 0 : parseManifest$1(manifestContent);
542
+ const tracked = [
543
+ TEKMEMO_PATHS.manifest,
544
+ TEKMEMO_PATHS.coreMemory,
545
+ TEKMEMO_PATHS.notesMemory,
546
+ TEKMEMO_PATHS.memoryEvents,
547
+ TEKMEMO_PATHS.conversations,
548
+ TEKMEMO_PATHS.chunks,
549
+ TEKMEMO_PATHS.graphNodes,
550
+ TEKMEMO_PATHS.graphEdges,
551
+ TEKMEMO_PATHS.snapshots
552
+ ];
553
+ const files = [];
554
+ const recordCounts = {};
555
+ for (const filePath of tracked) {
556
+ const content = await fs.readTextIfExists(filePath);
557
+ const isJsonl = filePath.endsWith(".jsonl");
558
+ let records = 0;
559
+ if (content !== void 0 && isJsonl) {
560
+ records = parseJsonl(content, { strict: false }).length;
561
+ recordCounts[filePath] = records;
562
+ }
563
+ files.push({
564
+ path: filePath,
565
+ exists: content !== void 0,
566
+ bytes: content ? Buffer.byteLength(content) : 0,
567
+ ...content !== void 0 ? { lines: content.split(/\r?\n/).filter(Boolean).length } : {},
568
+ ...content !== void 0 && isJsonl ? { records } : {}
569
+ });
570
+ }
571
+ return {
572
+ rootDir: fs.rootDir,
573
+ exists,
574
+ ...manifest ? { manifest } : {},
575
+ files,
576
+ summary: {
577
+ eventCount: recordCounts[TEKMEMO_PATHS.memoryEvents] ?? 0,
578
+ conversationCount: recordCounts[TEKMEMO_PATHS.conversations] ?? 0,
579
+ chunkCount: recordCounts[TEKMEMO_PATHS.chunks] ?? 0,
580
+ graphNodeCount: recordCounts[TEKMEMO_PATHS.graphNodes] ?? 0,
581
+ graphEdgeCount: recordCounts[TEKMEMO_PATHS.graphEdges] ?? 0,
582
+ snapshotCount: recordCounts[TEKMEMO_PATHS.snapshots] ?? 0
583
+ }
584
+ };
585
+ }
586
+
587
+ //#endregion
588
+ //#region src/commands/agent.ts
589
+ const LATEST_AGENT_SESSION_PATH = `${TEKMEMO_PATHS.tmpDir}/agent-sessions/latest.json`;
590
+ /**
591
+ * Starts a local AgentFS-style session workspace.
592
+ *
593
+ * @param options - Command options.
594
+ * @returns CLI exit code.
595
+ */
596
+ async function runAgentStartCommand(options) {
597
+ const session = createTekMemoAgentSession({
598
+ client: createLocalAgentfsClient(options.fs),
599
+ memory: createNodeFsMemoryStore({
600
+ rootDir: options.fs.rootDir,
601
+ missingFileBehavior: "empty",
602
+ createRoot: true
603
+ }),
604
+ task: options.task,
605
+ projectId: options.projectId,
606
+ actorId: options.actorId,
607
+ sessionId: options.sessionId
608
+ });
609
+ await session.prepare();
610
+ const pointer = {
611
+ sessionId: session.sessionId,
612
+ projectId: options.projectId ?? null,
613
+ root: session.paths.root,
614
+ task: options.task,
615
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
616
+ paths: session.paths
617
+ };
618
+ await options.fs.writeText(LATEST_AGENT_SESSION_PATH, `${JSON.stringify(pointer, null, 2)}\n`);
619
+ if (options.json) {
620
+ printJsonEnvelope(options.output, "agent.start", pointer);
621
+ return 0;
622
+ }
623
+ options.output.success(`Started TekMemo agent session ${session.sessionId}`);
624
+ options.output.write(formatAgentInstructions(pointer));
625
+ return 0;
626
+ }
627
+ /**
628
+ * Prints paths for a known agent session.
629
+ *
630
+ * @param options - Command options.
631
+ * @returns CLI exit code.
632
+ */
633
+ async function runAgentPathsCommand(options) {
634
+ const pointer = await readSessionPointer(options.fs, options.session);
635
+ if (options.json) {
636
+ printJsonEnvelope(options.output, "agent.paths", pointer);
637
+ return 0;
638
+ }
639
+ options.output.write(formatAgentInstructions(pointer));
640
+ return 0;
641
+ }
642
+ /**
643
+ * Extracts output files from an agent session.
644
+ *
645
+ * @param options - Command options.
646
+ * @returns CLI exit code.
647
+ */
648
+ async function runAgentExtractCommand(options) {
649
+ const pointer = await readSessionPointer(options.fs, options.session);
650
+ const extracted = await extractSessionMemory(createLocalAgentfsClient(options.fs), pointer.paths);
651
+ if (options.json) {
652
+ printJsonEnvelope(options.output, "agent.extract", {
653
+ sessionId: pointer.sessionId,
654
+ extracted
655
+ });
656
+ return 0;
657
+ }
658
+ options.output.write([
659
+ `# TekMemo Agent Session ${pointer.sessionId}`,
660
+ "",
661
+ "## Summary",
662
+ extracted.summary || "No summary written.",
663
+ "",
664
+ "## Durable Memory",
665
+ extracted.durableMemory || "No durable memory written.",
666
+ "",
667
+ "## Follow-ups",
668
+ extracted.followUps || "No follow-ups written."
669
+ ].join("\n"));
670
+ return 0;
671
+ }
672
+ /**
673
+ * Completes an agent session and optionally persists durable memory locally.
674
+ *
675
+ * @param options - Command options.
676
+ * @returns CLI exit code.
677
+ */
678
+ async function runAgentCompleteCommand(options) {
679
+ const pointer = await readSessionPointer(options.fs, options.session);
680
+ const result = await createTekMemoAgentSession({
681
+ client: createLocalAgentfsClient(options.fs),
682
+ memory: createNodeFsMemoryStore({
683
+ rootDir: options.fs.rootDir,
684
+ missingFileBehavior: "empty",
685
+ createRoot: true
686
+ }),
687
+ task: pointer.task,
688
+ projectId: pointer.projectId ?? void 0,
689
+ sessionId: pointer.sessionId
690
+ }).complete({
691
+ extractDurableMemory: options.extract ?? false,
692
+ checkpointLabel: options.checkpointLabel
693
+ });
694
+ if (options.json) {
695
+ printJsonEnvelope(options.output, "agent.complete", {
696
+ sessionId: pointer.sessionId,
697
+ ...result
698
+ });
699
+ return 0;
700
+ }
701
+ options.output.success(`Completed TekMemo agent session ${pointer.sessionId}`);
702
+ if (result.durableMemoryWritten) options.output.success("Persisted extracted durable memory to notes.");
703
+ options.output.write(`Summary: ${result.extracted.summary || "No summary written."}`);
704
+ return 0;
705
+ }
706
+ /**
707
+ * Adapts the CLI filesystem to the AgentFS-like client contract.
708
+ *
709
+ * @param fs - CLI filesystem.
710
+ * @returns AgentFS-like client.
711
+ */
712
+ function createLocalAgentfsClient(fs) {
713
+ return {
714
+ readText(path) {
715
+ return fs.readText(toRelativeAgentPath(path));
716
+ },
717
+ writeText(path, content) {
718
+ return fs.writeText(toRelativeAgentPath(path), content);
719
+ },
720
+ appendText(path, content) {
721
+ return fs.appendText(toRelativeAgentPath(path), content);
722
+ },
723
+ exists(path) {
724
+ return fs.exists(toRelativeAgentPath(path));
725
+ },
726
+ sync: {
727
+ pull: async () => {},
728
+ push: async () => {},
729
+ checkpoint: async () => {}
730
+ }
731
+ };
732
+ }
733
+ /**
734
+ * Converts AgentFS absolute-ish paths into project-relative CLI paths.
735
+ *
736
+ * @param path - AgentFS path.
737
+ * @returns Project-relative path.
738
+ */
739
+ function toRelativeAgentPath(path) {
740
+ return path.replace(/^\/+/, "");
741
+ }
742
+ /**
743
+ * Reads a session pointer from `.tekmemo/tmp`.
744
+ *
745
+ * @param fs - CLI filesystem.
746
+ * @param session - Session ID or latest.
747
+ * @returns Session pointer.
748
+ */
749
+ async function readSessionPointer(fs, session) {
750
+ const latest = await fs.readText(LATEST_AGENT_SESSION_PATH);
751
+ const pointer = JSON.parse(latest);
752
+ if (!session || session === "latest" || session === pointer.sessionId) return pointer;
753
+ throw new Error(`Unknown session "${session}". Only latest session ${pointer.sessionId} is tracked locally.`);
754
+ }
755
+ /**
756
+ * Formats agent-facing instructions for Codex, Claude Code, or any file-native agent.
757
+ *
758
+ * @param pointer - Session pointer.
759
+ * @returns Markdown instructions.
760
+ */
761
+ function formatAgentInstructions(pointer) {
762
+ return [
763
+ "",
764
+ "## Agent Instructions",
765
+ `Session: ${pointer.sessionId}`,
766
+ `Task: ${pointer.task}`,
767
+ "",
768
+ "Read before editing:",
769
+ `- ${pointer.paths.context.core}`,
770
+ `- ${pointer.paths.context.notes}`,
771
+ "",
772
+ "Update during work:",
773
+ `- ${pointer.paths.working.plan}`,
774
+ `- ${pointer.paths.working.commands}`,
775
+ `- ${pointer.paths.working.errors}`,
776
+ `- ${pointer.paths.working.changes}`,
777
+ "",
778
+ "Write before finishing:",
779
+ `- ${pointer.paths.output.summary}`,
780
+ `- ${pointer.paths.output.durableMemory}`,
781
+ `- ${pointer.paths.output.followUps}`,
782
+ ""
783
+ ].join("\n");
784
+ }
785
+
786
+ //#endregion
787
+ //#region src/commands/chunks.ts
788
+ async function runChunksCommand(options) {
789
+ const content = await options.fs.readTextIfExists(TEKMEMO_PATHS.chunks);
790
+ const records = content ? parseJsonl(content, { strict: options.strict ?? false }) : [];
791
+ const selected = options.limit && options.limit > 0 ? records.slice(-options.limit) : records;
792
+ if (options.json) {
793
+ options.output.write(JSON.stringify(selected, null, 2));
794
+ return 0;
795
+ }
796
+ if (selected.length === 0) {
797
+ options.output.write("No chunk records found.");
798
+ return 0;
799
+ }
800
+ options.output.write(selected.map((record) => {
801
+ const id = typeof record.value.id === "string" ? record.value.id : typeof record.value.chunkId === "string" ? record.value.chunkId : `line ${record.line}`;
802
+ const source = typeof record.value.sourcePath === "string" ? record.value.sourcePath : "unknown source";
803
+ return `${id} ${typeof record.value.status === "string" ? record.value.status : typeof record.value.indexStatus === "string" ? record.value.indexStatus : "unknown"} ${source}`;
804
+ }).join("\n"));
805
+ return 0;
806
+ }
807
+
808
+ //#endregion
809
+ //#region src/utils/content.ts
810
+ async function resolveCommandContent(input) {
811
+ const sources = [
812
+ input.inline !== void 0,
813
+ input.stdin === true,
814
+ input.file !== void 0
815
+ ].filter(Boolean).length;
816
+ if (sources === 0) throw new CliUsageError("Provide content as an argument, --stdin, or --file <path>.");
817
+ if (sources > 1) throw new CliUsageError("Use only one content source: argument, --stdin, or --file.");
818
+ let content;
819
+ if (input.inline !== void 0) content = input.inline;
820
+ else if (input.file !== void 0) {
821
+ const path = resolveInsideRoot(input.rootDir, input.file);
822
+ content = await fs.readFile(path, "utf8");
823
+ } else content = input.stdinContent ?? await readStdin();
824
+ if (content.includes("\0")) throw new CliUsageError("Content must not contain null bytes.");
825
+ if (content.trim().length === 0) throw new CliUsageError("Content must not be empty.");
826
+ return content.trimEnd();
827
+ }
828
+ async function readStdin() {
829
+ const chunks = [];
830
+ for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
831
+ return Buffer.concat(chunks).toString("utf8");
832
+ }
833
+
834
+ //#endregion
835
+ //#region src/utils/metadata.ts
836
+ function parseMetadataJson(value) {
837
+ if (value === void 0 || value.trim().length === 0) return void 0;
838
+ let parsed;
839
+ try {
840
+ parsed = JSON.parse(value);
841
+ } catch (error) {
842
+ throw new CliUsageError(`metadata must be valid JSON: ${error instanceof Error ? error.message : String(error)}`);
843
+ }
844
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new CliUsageError("metadata must be a JSON object.");
845
+ assertJsonSerializable(parsed, "metadata");
846
+ return parsed;
847
+ }
848
+ function assertJsonSerializable(value, name = "value") {
849
+ try {
850
+ JSON.stringify(value);
851
+ } catch (error) {
852
+ throw new CliUsageError(`${name} must be JSON serializable.`, { cause: error });
853
+ }
854
+ }
855
+
856
+ //#endregion
857
+ //#region src/utils/numbers.ts
858
+ function parseNonNegativeInteger(value, name = "value") {
859
+ const parsed = Number.parseInt(value, 10);
860
+ if (!Number.isFinite(parsed) || parsed < 0 || String(parsed) !== String(value).trim()) throw new CliUsageError(`${name} must be a non-negative integer.`);
861
+ return parsed;
862
+ }
863
+ function parsePositiveInteger(value, name = "value") {
864
+ const parsed = parseNonNegativeInteger(value, name);
865
+ if (parsed === 0) throw new CliUsageError(`${name} must be greater than 0.`);
866
+ return parsed;
867
+ }
868
+ function parseConfidence(value) {
869
+ const parsed = Number.parseFloat(value);
870
+ if (!Number.isFinite(parsed) || parsed < 0 || parsed > 1) throw new CliUsageError("confidence must be a number between 0 and 1.");
871
+ return parsed;
872
+ }
873
+
874
+ //#endregion
875
+ //#region src/utils/secrets.ts
876
+ const SECRET_PATTERNS = [
877
+ {
878
+ kind: "env_assignment_secret",
879
+ pattern: /\b[A-Z0-9_]*(?:API[_-]?KEY|SECRET|TOKEN|PASSWORD|PRIVATE[_-]?KEY)[A-Z0-9_]*\s*=\s*[^\s]+/gi
880
+ },
881
+ {
882
+ kind: "openai_key",
883
+ pattern: /\bsk-[A-Za-z0-9_-]{20,}\b/g
884
+ },
885
+ {
886
+ kind: "github_token",
887
+ pattern: /\bgh[pousr]_[A-Za-z0-9_]{20,}\b/g
888
+ },
889
+ {
890
+ kind: "jwt",
891
+ pattern: /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g
892
+ },
893
+ {
894
+ kind: "pem_private_key",
895
+ pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----/g
896
+ }
897
+ ];
898
+ function scanForSecrets(content) {
899
+ const findings = [];
900
+ for (const { kind, pattern } of SECRET_PATTERNS) {
901
+ pattern.lastIndex = 0;
902
+ let match = pattern.exec(content);
903
+ while (match !== null) {
904
+ findings.push({
905
+ kind,
906
+ index: match.index,
907
+ preview: redactSecretPreview(match[0])
908
+ });
909
+ match = pattern.exec(content);
910
+ }
911
+ }
912
+ return findings;
913
+ }
914
+ function redactSecretPreview(value) {
915
+ const trimmed = value.trim();
916
+ if (trimmed.length <= 8) return "[redacted]";
917
+ return `${trimmed.slice(0, 4)}…${trimmed.slice(-4)}`;
918
+ }
919
+
920
+ //#endregion
921
+ //#region src/commands/cloud.ts
922
+ const MEMORY_KINDS = new Set([
923
+ "decision",
924
+ "constraint",
925
+ "goal",
926
+ "preference",
927
+ "reference",
928
+ "summary",
929
+ "note"
930
+ ]);
931
+ const RECALL_STRATEGIES = new Set([
932
+ "local",
933
+ "vector",
934
+ "hybrid"
935
+ ]);
936
+ const RECALL_FALLBACKS = new Set(["none", "local"]);
937
+ const INDEX_MODES = new Set([
938
+ "all",
939
+ "changed",
940
+ "core",
941
+ "notes"
942
+ ]);
943
+ const CONFLICT_RESOLUTIONS = new Set([
944
+ "keep_cloud",
945
+ "use_client",
946
+ "ignore"
947
+ ]);
948
+ async function runCloudHealthCommand(options) {
949
+ const result = await createCloudClient(options, true, true).health();
950
+ if (options.json) {
951
+ printJsonEnvelope(options.output, "cloud.health", result);
952
+ return 0;
953
+ }
954
+ options.output.write([
955
+ "TekMemo Cloud",
956
+ `ok: ${result.ok}`,
957
+ `name: ${result.name ?? "unknown"}`,
958
+ `version: ${result.version ?? "unknown"}`,
959
+ `capabilities: ${(result.capabilities ?? []).join(", ") || "none"}`,
960
+ ...result.warnings?.length ? result.warnings.map((warning) => `warning: ${warning}`) : []
961
+ ].join("\n"));
962
+ return result.ok ? 0 : 1;
963
+ }
964
+ async function runCloudContextCommand(options) {
965
+ const client = createCloudClient(options);
966
+ const topK = normalizeOptionalPositiveInteger(options.limit, "limit");
967
+ const maxBytes = normalizeOptionalPositiveInteger(options.maxBytes, "max bytes");
968
+ const includeCore = options.includeCore !== false;
969
+ const includeNotes = options.includeNotes !== false;
970
+ const includeRecent = options.includeRecent !== false;
971
+ const sections = [];
972
+ const data = {
973
+ query: options.query,
974
+ sections: []
975
+ };
976
+ if (includeCore) {
977
+ const core = await client.memory.readCore();
978
+ sections.push(`# Core Memory\n\n${core.content.trim()}`);
979
+ data.sections.push({
980
+ type: "core",
981
+ content: core.content
982
+ });
983
+ }
984
+ if (includeNotes || includeRecent) {
985
+ const notes = await client.memory.listNotes({ limit: topK ?? 10 });
986
+ const renderedNotes = notes.items.map(renderNote).join("\n\n");
987
+ sections.push(`# Notes\n\n${renderedNotes || "No cloud notes found."}`);
988
+ data.sections.push({
989
+ type: "notes",
990
+ items: notes.items,
991
+ nextCursor: notes.nextCursor
992
+ });
993
+ }
994
+ const recall = await client.recall.query({
995
+ query: options.query,
996
+ ...topK !== void 0 ? { topK } : {},
997
+ strategy: "hybrid",
998
+ fallback: "local",
999
+ rerank: true
1000
+ });
1001
+ sections.push(`# Recall\n\n${renderRecallHits(recall.items)}`);
1002
+ data.sections.push({
1003
+ type: "recall",
1004
+ result: recall
1005
+ });
1006
+ const text = truncateText(sections.filter(Boolean).join("\n\n---\n\n"), maxBytes);
1007
+ if (options.json) {
1008
+ printJsonEnvelope(options.output, "cloud.context", {
1009
+ ...data,
1010
+ text,
1011
+ truncated: maxBytes !== void 0 && text.length >= maxBytes
1012
+ });
1013
+ return 0;
1014
+ }
1015
+ options.output.write(text.trimEnd());
1016
+ return 0;
1017
+ }
1018
+ async function runCloudRecallCommand(options) {
1019
+ const client = createCloudClient(options);
1020
+ const topK = normalizeOptionalPositiveInteger(options.limit, "limit");
1021
+ const strategy = normalizeRecallStrategy(options.strategy);
1022
+ const fallback = normalizeRecallFallback(options.fallback);
1023
+ const result = await client.recall.query({
1024
+ query: options.query,
1025
+ ...topK !== void 0 ? { topK } : {},
1026
+ ...strategy !== void 0 ? { strategy } : {},
1027
+ ...fallback !== void 0 ? { fallback } : {},
1028
+ ...options.rerank !== void 0 ? { rerank: options.rerank } : {}
1029
+ });
1030
+ if (options.json) {
1031
+ printJsonEnvelope(options.output, "cloud.recall", result);
1032
+ return 0;
1033
+ }
1034
+ options.output.write(renderRecallHits(result.items));
1035
+ return 0;
1036
+ }
1037
+ async function runCloudRecallIndexCommand(options) {
1038
+ const client = createCloudClient(options);
1039
+ const mode = normalizeIndexMode(options.mode);
1040
+ const result = await client.recall.index({
1041
+ ...mode !== void 0 ? { mode } : {},
1042
+ ...options.force !== void 0 ? { force: options.force } : {}
1043
+ });
1044
+ if (options.json) {
1045
+ printJsonEnvelope(options.output, "cloud.recall.index", result);
1046
+ return 0;
1047
+ }
1048
+ options.output.success(`Recall indexing ${result.status}${result.jobId ? ` job=${result.jobId}` : ""}`);
1049
+ if (result.indexed !== void 0) options.output.write(`indexed: ${result.indexed}`);
1050
+ for (const warning of result.warnings ?? []) options.output.warn(`warning: ${warning}`);
1051
+ return 0;
1052
+ }
1053
+ async function runCloudRememberCommand(options) {
1054
+ const client = createCloudClient(options);
1055
+ const content = await resolveCommandContent({
1056
+ rootDir: options.rootDir ?? process.cwd(),
1057
+ inline: options.content,
1058
+ stdin: options.stdin,
1059
+ file: options.file,
1060
+ stdinContent: options.stdinContent
1061
+ });
1062
+ const findings = scanForSecrets(content);
1063
+ if (findings.length > 0 && !options.allowSecrets) {
1064
+ const data = {
1065
+ stored: false,
1066
+ secretFindings: findings
1067
+ };
1068
+ if (options.json) printJsonEnvelope(options.output, "cloud.remember", data);
1069
+ else options.output.error(`Refusing to store possible secret (${findings[0]?.kind}). Use --allow-secrets only after review.`);
1070
+ return 1;
1071
+ }
1072
+ const metadata = parseMetadataJson(options.metadata);
1073
+ const note = {
1074
+ content,
1075
+ kind: normalizeMemoryKind(options.kind),
1076
+ ...options.title ? { title: options.title } : {},
1077
+ ...options.tags?.length ? { tags: options.tags.map((tag) => tag.trim()).filter(Boolean) } : {},
1078
+ ...options.source ? { source: options.source } : {},
1079
+ ...options.confidence !== void 0 ? { confidence: normalizeConfidence(options.confidence) } : {},
1080
+ ...metadata ? { metadata } : {}
1081
+ };
1082
+ const result = await client.memory.createNote(note);
1083
+ if (options.json) {
1084
+ printJsonEnvelope(options.output, "cloud.remember", {
1085
+ ...result,
1086
+ secretFindings: findings
1087
+ });
1088
+ return 0;
1089
+ }
1090
+ options.output.success(`Stored cloud memory ${result.id}`);
1091
+ return 0;
1092
+ }
1093
+ async function runCloudReadCommand(options) {
1094
+ const client = createCloudClient(options);
1095
+ if (options.target === "core") {
1096
+ const result = await client.memory.readCore();
1097
+ if (options.json) printJsonEnvelope(options.output, "cloud.read", {
1098
+ target: "core",
1099
+ ...result
1100
+ });
1101
+ else options.output.write(result.content.trimEnd());
1102
+ return 0;
1103
+ }
1104
+ const limit = normalizeOptionalPositiveInteger(options.limit, "limit");
1105
+ const result = await client.memory.listNotes({ ...limit !== void 0 ? { limit } : {} });
1106
+ if (options.json) {
1107
+ printJsonEnvelope(options.output, "cloud.read", {
1108
+ target: "notes",
1109
+ ...result
1110
+ });
1111
+ return 0;
1112
+ }
1113
+ options.output.write(result.items.map(renderNote).join("\n\n") || "No cloud notes found.");
1114
+ return 0;
1115
+ }
1116
+ async function runCloudUpdateCoreCommand(options) {
1117
+ const client = createCloudClient(options);
1118
+ const content = await resolveCommandContent({
1119
+ rootDir: options.rootDir ?? process.cwd(),
1120
+ inline: options.content,
1121
+ stdin: options.stdin,
1122
+ file: options.file,
1123
+ stdinContent: options.stdinContent
1124
+ });
1125
+ const findings = scanForSecrets(content);
1126
+ if (findings.length > 0 && !options.allowSecrets) {
1127
+ const data = {
1128
+ updated: false,
1129
+ secretFindings: findings
1130
+ };
1131
+ if (options.json) printJsonEnvelope(options.output, "cloud.update-core", data);
1132
+ else options.output.error(`Refusing to store possible secret (${findings[0]?.kind}). Use --allow-secrets only after review.`);
1133
+ return 1;
1134
+ }
1135
+ const result = await client.memory.updateCore({ content });
1136
+ if (options.json) {
1137
+ printJsonEnvelope(options.output, "cloud.update-core", {
1138
+ ...result,
1139
+ secretFindings: findings
1140
+ });
1141
+ return 0;
1142
+ }
1143
+ options.output.success("Updated cloud core memory.");
1144
+ return 0;
1145
+ }
1146
+ async function runCloudRecentCommand(options) {
1147
+ return runCloudReadCommand({
1148
+ ...options,
1149
+ target: "notes"
1150
+ });
1151
+ }
1152
+ async function runCloudValidateCommand(options) {
1153
+ const client = createCloudClient(options);
1154
+ const errors = [];
1155
+ const warnings = [];
1156
+ let healthOk = false;
1157
+ try {
1158
+ const health = await client.health();
1159
+ healthOk = health.ok;
1160
+ for (const warning of health.warnings ?? []) warnings.push(warning);
1161
+ if (!health.ok) errors.push("Cloud health check returned ok=false.");
1162
+ } catch (error) {
1163
+ errors.push(`health: ${error instanceof Error ? error.message : String(error)}`);
1164
+ }
1165
+ try {
1166
+ await client.memory.readCore();
1167
+ } catch (error) {
1168
+ errors.push(`memory/core: ${error instanceof Error ? error.message : String(error)}`);
1169
+ }
1170
+ if (options.strict) try {
1171
+ await client.memory.listNotes({ limit: 1 });
1172
+ } catch (error) {
1173
+ errors.push(`memory/notes: ${error instanceof Error ? error.message : String(error)}`);
1174
+ }
1175
+ const result = {
1176
+ ok: healthOk && errors.length === 0,
1177
+ warnings,
1178
+ errors
1179
+ };
1180
+ if (options.json) {
1181
+ printJsonEnvelope(options.output, "cloud.validate", result);
1182
+ return result.ok ? 0 : 1;
1183
+ }
1184
+ if (result.ok) options.output.success("Cloud memory is valid.");
1185
+ else options.output.error("Cloud memory validation failed.");
1186
+ for (const warning of warnings) options.output.warn(`warning: ${warning}`);
1187
+ for (const error of errors) options.output.error(`error: ${error}`);
1188
+ return result.ok ? 0 : 1;
1189
+ }
1190
+ async function runCloudSnapshotCommand(options) {
1191
+ const data = {
1192
+ created: false,
1193
+ reason: "cloud_snapshots_not_available",
1194
+ message: "Cloud snapshots/exports are planned for the R2 milestone and are not exposed by the current project-scoped Cloud API.",
1195
+ label: options.label ?? "manual",
1196
+ type: options.type ?? "manual"
1197
+ };
1198
+ if (options.json) printJsonEnvelope(options.output, "cloud.snapshot", data);
1199
+ else options.output.error(data.message);
1200
+ return 2;
1201
+ }
1202
+ async function runCloudSyncStatusCommand(options) {
1203
+ const result = await createCloudClient(options).sync.status({ ...options.clientId ? { clientId: options.clientId } : {} });
1204
+ if (options.json) {
1205
+ printJsonEnvelope(options.output, "cloud.sync.status", result);
1206
+ return 0;
1207
+ }
1208
+ options.output.write([
1209
+ `serverVersion: ${result.serverVersion}`,
1210
+ `openConflicts: ${result.openConflicts}`,
1211
+ `clients: ${result.clients.length}`,
1212
+ ...result.recentEvents !== void 0 ? [`recentEvents: ${result.recentEvents}`] : []
1213
+ ].join("\n"));
1214
+ return 0;
1215
+ }
1216
+ async function runCloudSyncPullCommand(options) {
1217
+ const client = createCloudClient(options);
1218
+ const sinceServerVersion = normalizeOptionalNonNegativeInteger(options.sinceServerVersion, "since server version");
1219
+ const limit = normalizeOptionalPositiveInteger(options.limit, "limit");
1220
+ const result = await client.sync.pull({
1221
+ clientId: options.clientId,
1222
+ ...sinceServerVersion !== void 0 ? { sinceServerVersion } : {},
1223
+ ...limit !== void 0 ? { limit } : {}
1224
+ });
1225
+ if (options.json) {
1226
+ printJsonEnvelope(options.output, "cloud.sync.pull", result);
1227
+ return 0;
1228
+ }
1229
+ options.output.write(`Pulled ${result.events.length} event(s). serverVersion=${result.serverVersion}`);
1230
+ return 0;
1231
+ }
1232
+ async function runCloudSyncPushCommand(options) {
1233
+ const client = createCloudClient(options);
1234
+ const payload = await resolveJsonPayload({
1235
+ rootDir: options.rootDir ?? process.cwd(),
1236
+ inline: options.eventsJson,
1237
+ stdin: options.stdin,
1238
+ file: options.file,
1239
+ stdinContent: options.stdinContent,
1240
+ fieldName: "events JSON"
1241
+ });
1242
+ const events = extractSyncEvents(payload);
1243
+ const checkpoint = options.checkpointJson ? parseJsonObject(options.checkpointJson, "checkpoint JSON") : extractCheckpoint(payload);
1244
+ const result = await client.sync.push({
1245
+ clientId: options.clientId,
1246
+ events,
1247
+ ...checkpoint !== void 0 ? { checkpoint } : {}
1248
+ });
1249
+ if (options.json) {
1250
+ printJsonEnvelope(options.output, "cloud.sync.push", result);
1251
+ return 0;
1252
+ }
1253
+ options.output.write([
1254
+ `accepted: ${result.accepted.length}`,
1255
+ `duplicates: ${result.duplicates.length}`,
1256
+ `rejected: ${result.rejected.length}`,
1257
+ `conflicts: ${result.conflicts.length}`,
1258
+ `serverVersion: ${result.serverVersion}`
1259
+ ].join("\n"));
1260
+ return result.rejected.length === 0 && result.conflicts.length === 0 ? 0 : 1;
1261
+ }
1262
+ async function runCloudSyncResolveCommand(options) {
1263
+ const client = createCloudClient(options);
1264
+ const resolution = normalizeConflictResolution(options.resolution);
1265
+ const content = options.contentJson ? parseJsonObject(options.contentJson, "content JSON") : void 0;
1266
+ const result = await client.sync.resolveConflict({
1267
+ conflictId: options.conflictId,
1268
+ resolution,
1269
+ ...content !== void 0 ? { content } : {}
1270
+ });
1271
+ if (options.json) {
1272
+ printJsonEnvelope(options.output, "cloud.sync.resolve", result);
1273
+ return 0;
1274
+ }
1275
+ options.output.success(`Resolved conflict ${result.conflictId}`);
1276
+ if (result.serverVersion !== void 0) options.output.write(`serverVersion: ${result.serverVersion}`);
1277
+ return result.resolved ? 0 : 1;
1278
+ }
1279
+ async function runCloudReadinessCommand(options) {
1280
+ const result = await createCloudClient(options, true, true).readiness();
1281
+ if (options.json) {
1282
+ printJsonEnvelope(options.output, "cloud.readiness", result);
1283
+ return 0;
1284
+ }
1285
+ options.output.write([
1286
+ `ok: ${result.ok}`,
1287
+ `name: ${result.name ?? "unknown"}`,
1288
+ `version: ${result.version ?? "unknown"}`,
1289
+ `capabilities: ${(result.capabilities ?? []).join(", ") || "none"}`,
1290
+ ...result.warnings?.length ? result.warnings.map((warning) => `warning: ${warning}`) : []
1291
+ ].join("\n"));
1292
+ return result.ok ? 0 : 1;
1293
+ }
1294
+ async function runCloudContextComposeCommand(options) {
1295
+ const client = createCloudClient(options);
1296
+ const topK = normalizeOptionalPositiveInteger(options.topK, "topK");
1297
+ const result = await client.context.compose({
1298
+ query: options.query,
1299
+ ...topK !== void 0 ? { topK } : {},
1300
+ ...options.strategy !== void 0 ? { strategy: options.strategy } : {},
1301
+ ...options.rerank !== void 0 ? { rerank: options.rerank } : {},
1302
+ ...options.includeCoreMemory !== void 0 ? { includeCoreMemory: options.includeCoreMemory } : {},
1303
+ ...options.includeRecallResults !== void 0 ? { includeRecallResults: options.includeRecallResults } : {},
1304
+ ...options.includeGraphContext !== void 0 ? { includeGraphContext: options.includeGraphContext } : {}
1305
+ });
1306
+ if (options.json) {
1307
+ printJsonEnvelope(options.output, "cloud.context.compose", result);
1308
+ return 0;
1309
+ }
1310
+ options.output.write(result.context);
1311
+ return 0;
1312
+ }
1313
+ async function runCloudGraphListNodesCommand(options) {
1314
+ const client = createCloudClient(options);
1315
+ const limit = normalizeOptionalPositiveInteger(options.limit, "limit");
1316
+ const result = await client.graph.listNodes({
1317
+ ...limit !== void 0 ? { limit } : {},
1318
+ ...options.cursor ? { cursor: options.cursor } : {},
1319
+ ...options.status ? { status: options.status } : {}
1320
+ });
1321
+ if (options.json) {
1322
+ printJsonEnvelope(options.output, "cloud.graph.list-nodes", result);
1323
+ return 0;
1324
+ }
1325
+ options.output.write(result.items.map((node) => `Node: ${node.nodeId} - ${node.label}`).join("\n") || "No nodes found.");
1326
+ return 0;
1327
+ }
1328
+ async function runCloudGraphCreateNodeCommand(options) {
1329
+ const client = createCloudClient(options);
1330
+ const metadata = options.metadataJson ? parseJsonObject(options.metadataJson, "metadata JSON") : void 0;
1331
+ const result = await client.graph.createNode({
1332
+ nodeId: options.nodeId,
1333
+ type: options.type,
1334
+ label: options.label,
1335
+ ...options.summary ? { summary: options.summary } : {},
1336
+ ...options.aliases ? { aliases: options.aliases } : {},
1337
+ ...metadata ? { metadata } : {}
1338
+ });
1339
+ if (options.json) {
1340
+ printJsonEnvelope(options.output, "cloud.graph.create-node", result);
1341
+ return 0;
1342
+ }
1343
+ options.output.success(`Created node ${result.nodeId}`);
1344
+ return 0;
1345
+ }
1346
+ async function runCloudGraphListEdgesCommand(options) {
1347
+ const client = createCloudClient(options);
1348
+ const limit = normalizeOptionalPositiveInteger(options.limit, "limit");
1349
+ const result = await client.graph.listEdges({
1350
+ ...limit !== void 0 ? { limit } : {},
1351
+ ...options.cursor ? { cursor: options.cursor } : {},
1352
+ ...options.status ? { status: options.status } : {}
1353
+ });
1354
+ if (options.json) {
1355
+ printJsonEnvelope(options.output, "cloud.graph.list-edges", result);
1356
+ return 0;
1357
+ }
1358
+ options.output.write(result.items.map((edge) => `Edge: ${edge.edgeId ?? "(new)"} - ${edge.fromNodeId} -> ${edge.toNodeId}`).join("\n") || "No edges found.");
1359
+ return 0;
1360
+ }
1361
+ async function runCloudGraphCreateEdgeCommand(options) {
1362
+ const client = createCloudClient(options);
1363
+ const metadata = options.metadataJson ? parseJsonObject(options.metadataJson, "metadata JSON") : void 0;
1364
+ const weight = options.weight !== void 0 ? parseFloat(String(options.weight)) : void 0;
1365
+ const result = await client.graph.createEdge({
1366
+ ...options.edgeId ? { edgeId: options.edgeId } : {},
1367
+ fromNodeId: options.fromNodeId,
1368
+ toNodeId: options.toNodeId,
1369
+ type: options.type,
1370
+ ...options.directed !== void 0 ? { directed: options.directed } : {},
1371
+ ...weight !== void 0 ? { weight } : {},
1372
+ ...metadata ? { metadata } : {}
1373
+ });
1374
+ if (options.json) {
1375
+ printJsonEnvelope(options.output, "cloud.graph.create-edge", result);
1376
+ return 0;
1377
+ }
1378
+ options.output.success(`Created edge ${result.edgeId ?? "(new)"}`);
1379
+ return 0;
1380
+ }
1381
+ async function runCloudGraphNeighborsCommand(options) {
1382
+ const client = createCloudClient(options);
1383
+ const depth = options.depth !== void 0 ? parseInt(String(options.depth), 10) : void 0;
1384
+ const limit = normalizeOptionalPositiveInteger(options.limit, "limit");
1385
+ const result = await client.graph.neighbors({
1386
+ nodeId: options.nodeId,
1387
+ ...options.direction ? { direction: options.direction } : {},
1388
+ ...depth !== void 0 ? { depth } : {},
1389
+ ...limit !== void 0 ? { limit } : {}
1390
+ });
1391
+ if (options.json) {
1392
+ printJsonEnvelope(options.output, "cloud.graph.neighbors", result);
1393
+ return 0;
1394
+ }
1395
+ options.output.write(`Nodes: ${result.nodes.length}, Edges: ${result.edges.length}`);
1396
+ return 0;
1397
+ }
1398
+ async function runCloudGraphPathCommand(options) {
1399
+ const client = createCloudClient(options);
1400
+ const maxDepth = options.maxDepth !== void 0 ? parseInt(String(options.maxDepth), 10) : void 0;
1401
+ const result = await client.graph.path({
1402
+ fromNodeId: options.fromNodeId,
1403
+ toNodeId: options.toNodeId,
1404
+ ...maxDepth !== void 0 ? { maxDepth } : {}
1405
+ });
1406
+ if (options.json) {
1407
+ printJsonEnvelope(options.output, "cloud.graph.path", result);
1408
+ return 0;
1409
+ }
1410
+ options.output.write(`Nodes: ${result.nodes.length}, Edges: ${result.edges.length}`);
1411
+ return 0;
1412
+ }
1413
+ async function runCloudExtractionRunCommand(options) {
1414
+ const result = await createCloudClient(options).extraction.run({
1415
+ ...options.mode ? { mode: options.mode } : {},
1416
+ ...options.force !== void 0 ? { force: options.force } : {}
1417
+ });
1418
+ if (options.json) {
1419
+ printJsonEnvelope(options.output, "cloud.extraction.run", result);
1420
+ return 0;
1421
+ }
1422
+ options.output.success(`Extraction ${result.status}${result.jobId ? ` job=${result.jobId}` : ""}`);
1423
+ return 0;
1424
+ }
1425
+ async function runCloudExtractionJobsCommand(options) {
1426
+ const client = createCloudClient(options);
1427
+ const limit = normalizeOptionalPositiveInteger(options.limit, "limit");
1428
+ const result = await client.extraction.jobs({ ...limit !== void 0 ? { limit } : {} });
1429
+ if (options.json) {
1430
+ printJsonEnvelope(options.output, "cloud.extraction.jobs", result);
1431
+ return 0;
1432
+ }
1433
+ options.output.write(result.items.map((job) => `Job: ${job.jobId} - ${job.status}`).join("\n") || "No jobs found.");
1434
+ return 0;
1435
+ }
1436
+ async function runCloudEvalsRunCommand(options) {
1437
+ const result = await createCloudClient(options).evals.run({
1438
+ ...options.fixtureIds ? { fixtureIds: options.fixtureIds.split(",").map((s) => s.trim()) } : {},
1439
+ ...options.iterations !== void 0 ? { iterations: parseInt(String(options.iterations), 10) } : {}
1440
+ });
1441
+ if (options.json) {
1442
+ printJsonEnvelope(options.output, "cloud.evals.run", result);
1443
+ return 0;
1444
+ }
1445
+ options.output.success(`Eval pass rate: ${result.passRate}`);
1446
+ return 0;
1447
+ }
1448
+ async function runCloudBenchmarksRunCommand(options) {
1449
+ const result = await createCloudClient(options).benchmarks.run({
1450
+ ...options.fixtureIds ? { fixtureIds: options.fixtureIds.split(",").map((s) => s.trim()) } : {},
1451
+ ...options.iterations !== void 0 ? { iterations: parseInt(String(options.iterations), 10) } : {}
1452
+ });
1453
+ if (options.json) {
1454
+ printJsonEnvelope(options.output, "cloud.benchmarks.run", result);
1455
+ return 0;
1456
+ }
1457
+ options.output.success(`Benchmark pass rate: ${result.passRate}, avg latency: ${result.avgLatencyMs ?? "N/A"}ms`);
1458
+ return 0;
1459
+ }
1460
+ async function runCloudExportsCreateCommand(options) {
1461
+ const result = await createCloudClient(options).exports.create({ ...options.label ? { label: options.label } : {} });
1462
+ if (options.json) {
1463
+ printJsonEnvelope(options.output, "cloud.exports.create", result);
1464
+ return 0;
1465
+ }
1466
+ options.output.success(`Created export ${result.exportId}`);
1467
+ return 0;
1468
+ }
1469
+ async function runCloudExportsDownloadCommand(options) {
1470
+ const result = await createCloudClient(options).exports.downloadUrl({ exportId: options.exportId });
1471
+ if (options.json) {
1472
+ printJsonEnvelope(options.output, "cloud.exports.download", result);
1473
+ return 0;
1474
+ }
1475
+ options.output.write(`Download URL: ${result.downloadUrl}`);
1476
+ return 0;
1477
+ }
1478
+ async function runCloudSnapshotsCreateCommand(options) {
1479
+ const result = await createCloudClient(options).snapshots.create({
1480
+ ...options.label ? { label: options.label } : {},
1481
+ ...options.trigger ? { trigger: options.trigger } : {}
1482
+ });
1483
+ if (options.json) {
1484
+ printJsonEnvelope(options.output, "cloud.snapshots.create", result);
1485
+ return 0;
1486
+ }
1487
+ options.output.success(`Created snapshot ${result.snapshotId}`);
1488
+ return 0;
1489
+ }
1490
+ async function runCloudSnapshotsDownloadCommand(options) {
1491
+ const result = await createCloudClient(options).snapshots.downloadUrl({ snapshotId: options.snapshotId });
1492
+ if (options.json) {
1493
+ printJsonEnvelope(options.output, "cloud.snapshots.download", result);
1494
+ return 0;
1495
+ }
1496
+ options.output.write(`Download URL: ${result.downloadUrl}`);
1497
+ return 0;
1498
+ }
1499
+ async function runCloudProvidersListCommand(options) {
1500
+ const result = await createCloudClient(options).providers.list();
1501
+ if (options.json) {
1502
+ printJsonEnvelope(options.output, "cloud.providers.list", result);
1503
+ return 0;
1504
+ }
1505
+ options.output.write(result.map((cred) => `Provider: ${cred.provider} - ${cred.keyName}`).join("\n") || "No providers found.");
1506
+ return 0;
1507
+ }
1508
+ async function runCloudProvidersCreateCommand(options) {
1509
+ const result = await createCloudClient(options).providers.create({
1510
+ provider: options.provider,
1511
+ keyName: options.keyName,
1512
+ secret: options.secret,
1513
+ ...options.restUrl ? { restUrl: options.restUrl } : {},
1514
+ ...options.embeddingModel ? { embeddingModel: options.embeddingModel } : {},
1515
+ ...options.rerankModel ? { rerankModel: options.rerankModel } : {}
1516
+ });
1517
+ if (options.json) {
1518
+ printJsonEnvelope(options.output, "cloud.providers.create", result);
1519
+ return 0;
1520
+ }
1521
+ options.output.success(`Created provider credential ${result.credentialId}`);
1522
+ return 0;
1523
+ }
1524
+ async function runCloudProvidersTestCommand(options) {
1525
+ const result = await createCloudClient(options).providers.test({ credentialId: options.credentialId });
1526
+ if (options.json) {
1527
+ printJsonEnvelope(options.output, "cloud.providers.test", result);
1528
+ return 0;
1529
+ }
1530
+ options.output.write(`Test result: ${result.ok ? "OK" : "Failed"}${result.message ? ` - ${result.message}` : ""}`);
1531
+ return result.ok ? 0 : 1;
1532
+ }
1533
+ function createCloudClient(options, allowMissingApiKey = false, allowMissingProjectId = false) {
1534
+ return createCliCloudClient({
1535
+ cloudUrl: options.cloudUrl,
1536
+ apiKey: options.apiKey,
1537
+ workspaceId: options.workspaceId,
1538
+ projectId: options.projectId,
1539
+ timeoutMs: options.timeoutMs,
1540
+ allowMissingApiKey,
1541
+ allowMissingProjectId
1542
+ });
1543
+ }
1544
+ function normalizeMemoryKind(value) {
1545
+ const candidate = value ?? "note";
1546
+ if (!MEMORY_KINDS.has(candidate)) throw new CliUsageError(`kind must be one of: ${[...MEMORY_KINDS].join(", ")}.`);
1547
+ return candidate;
1548
+ }
1549
+ function normalizeRecallStrategy(value) {
1550
+ if (value === void 0) return void 0;
1551
+ if (RECALL_STRATEGIES.has(value)) return value;
1552
+ throw new CliUsageError("recall strategy must be local, vector, or hybrid.");
1553
+ }
1554
+ function normalizeRecallFallback(value) {
1555
+ if (value === void 0) return void 0;
1556
+ if (RECALL_FALLBACKS.has(value)) return value;
1557
+ throw new CliUsageError("recall fallback must be none or local.");
1558
+ }
1559
+ function normalizeIndexMode(value) {
1560
+ if (value === void 0) return void 0;
1561
+ if (INDEX_MODES.has(value)) return value;
1562
+ throw new CliUsageError("index mode must be all, changed, core, or notes.");
1563
+ }
1564
+ function normalizeConflictResolution(value) {
1565
+ if (CONFLICT_RESOLUTIONS.has(value)) return value;
1566
+ throw new CliUsageError("conflict resolution must be keep_cloud, use_client, or ignore.");
1567
+ }
1568
+ function normalizeConfidence(value) {
1569
+ return parseConfidence(String(value));
1570
+ }
1571
+ function normalizeOptionalPositiveInteger(value, name) {
1572
+ if (value === void 0) return void 0;
1573
+ const parsed = typeof value === "number" ? value : Number(value);
1574
+ if (!Number.isInteger(parsed) || parsed < 1) throw new CliUsageError(`${name} must be a positive integer.`);
1575
+ return parsed;
1576
+ }
1577
+ function normalizeOptionalNonNegativeInteger(value, name) {
1578
+ if (value === void 0) return void 0;
1579
+ const parsed = typeof value === "number" ? value : Number(value);
1580
+ if (!Number.isInteger(parsed) || parsed < 0) throw new CliUsageError(`${name} must be a non-negative integer.`);
1581
+ return parsed;
1582
+ }
1583
+ function renderNote(note) {
1584
+ const heading = note.title ? `${note.title} (${note.id})` : note.id;
1585
+ const tags = note.tags?.length ? `\n- tags: ${note.tags.join(", ")}` : "";
1586
+ const created = note.createdAt ? `\n- createdAt: ${note.createdAt}` : "";
1587
+ return `## ${heading}\n- kind: ${note.kind}${created}${tags}\n\n${note.content.trim()}`;
1588
+ }
1589
+ function renderRecallHits(items) {
1590
+ if (items.length === 0) return "No matching cloud memories found.";
1591
+ return items.map((item, index) => {
1592
+ const score = item.score === void 0 ? "" : ` score=${item.score}`;
1593
+ return `${index + 1}. ${item.text}${score}`;
1594
+ }).join("\n\n");
1595
+ }
1596
+ function truncateText(text, maxBytes) {
1597
+ if (maxBytes === void 0 || text.length <= maxBytes) return text;
1598
+ return `${text.slice(0, Math.max(0, maxBytes - 40)).trimEnd()}\n\n[truncated by --max-bytes]`;
1599
+ }
1600
+ async function resolveJsonPayload(input) {
1601
+ const raw = await resolveCommandContent({
1602
+ rootDir: input.rootDir,
1603
+ inline: input.inline,
1604
+ stdin: input.stdin,
1605
+ file: input.file,
1606
+ stdinContent: input.stdinContent
1607
+ });
1608
+ try {
1609
+ return JSON.parse(raw);
1610
+ } catch (error) {
1611
+ throw new CliUsageError(`${input.fieldName} must be valid JSON: ${error instanceof Error ? error.message : String(error)}`);
1612
+ }
1613
+ }
1614
+ function parseJsonObject(raw, fieldName) {
1615
+ try {
1616
+ const parsed = JSON.parse(raw);
1617
+ if (!isJsonObject(parsed)) throw new Error("value must be an object");
1618
+ return parsed;
1619
+ } catch (error) {
1620
+ throw new CliUsageError(`${fieldName} must be a JSON object: ${error instanceof Error ? error.message : String(error)}`);
1621
+ }
1622
+ }
1623
+ function extractSyncEvents(payload) {
1624
+ const events = Array.isArray(payload) ? payload : isJsonObject(payload) ? payload.events : void 0;
1625
+ if (!Array.isArray(events)) throw new CliUsageError("sync push payload must be an event array or an object with an events array.");
1626
+ return events.map((event, index) => {
1627
+ if (!isJsonObject(event)) throw new CliUsageError(`sync event at index ${index} must be an object.`);
1628
+ return event;
1629
+ });
1630
+ }
1631
+ function extractCheckpoint(payload) {
1632
+ if (!isJsonObject(payload) || payload.checkpoint === void 0) return void 0;
1633
+ if (!isJsonObject(payload.checkpoint)) throw new CliUsageError("sync checkpoint must be an object.");
1634
+ return payload.checkpoint;
1635
+ }
1636
+ function isJsonObject(value) {
1637
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1638
+ }
1639
+
1640
+ //#endregion
1641
+ //#region src/commands/context.ts
1642
+ function truncate(value, maxChars) {
1643
+ if (value.length <= maxChars) return value;
1644
+ return `${value.slice(0, Math.max(0, maxChars - 20)).trimEnd()}\n\n[truncated]`;
1645
+ }
1646
+ function searchText(file, content, query) {
1647
+ const lower = query.toLowerCase();
1648
+ return content.split(/\r?\n/).map((line, index) => ({
1649
+ file,
1650
+ line: index + 1,
1651
+ content: line.trim()
1652
+ })).filter((entry) => entry.content.toLowerCase().includes(lower));
1653
+ }
1654
+ async function runContextCommand(options) {
1655
+ const maxChars = typeof options.maxChars === "number" ? options.maxChars : options.maxChars ? parsePositiveInteger(options.maxChars, "max chars") : 12e3;
1656
+ const core = await options.fs.readTextIfExists(TEKMEMO_PATHS.coreMemory) ?? "";
1657
+ const notes = await options.fs.readTextIfExists(TEKMEMO_PATHS.notesMemory) ?? "";
1658
+ const eventContent = await options.fs.readTextIfExists(TEKMEMO_PATHS.memoryEvents) ?? "";
1659
+ const chunkContent = await options.fs.readTextIfExists(TEKMEMO_PATHS.chunks) ?? "";
1660
+ const events = eventContent ? parseJsonl(eventContent).slice(-10).map((record) => record.value) : [];
1661
+ const chunks = chunkContent ? parseJsonl(chunkContent).slice(-10).map((record) => record.value) : [];
1662
+ const matches = options.query ? [...searchText(TEKMEMO_PATHS.coreMemory, core, options.query), ...searchText(TEKMEMO_PATHS.notesMemory, notes, options.query)] : [];
1663
+ const data = {
1664
+ rootDir: options.fs.rootDir,
1665
+ query: options.query ?? null,
1666
+ core: truncate(core.trim(), Math.floor(maxChars * .45)),
1667
+ notes: truncate(notes.trim(), Math.floor(maxChars * .35)),
1668
+ matches,
1669
+ ...options.includeEvents ? { recentEvents: events } : {},
1670
+ ...options.includeChunks ? { recentChunks: chunks } : {}
1671
+ };
1672
+ if (options.json) {
1673
+ printJsonEnvelope(options.output, "context", data);
1674
+ return 0;
1675
+ }
1676
+ const sections = [
1677
+ "# TekMemo Context",
1678
+ `Root: ${options.fs.rootDir}`,
1679
+ options.query ? `Query: ${options.query}` : void 0,
1680
+ "",
1681
+ "## Core Memory",
1682
+ data.core || "No core memory found.",
1683
+ "",
1684
+ "## Notes Memory",
1685
+ data.notes || "No notes memory found."
1686
+ ];
1687
+ if (matches.length > 0) sections.push("", "## Text Matches", ...matches.slice(0, 20).map((m) => `- ${m.file}:${m.line} — ${m.content}`));
1688
+ if (options.includeEvents && events.length > 0) sections.push("", "## Recent Memory Events", ...events.map((event) => `- ${String(event.timestamp ?? "unknown")} ${String(event.type ?? "unknown")} ${String(event.summary ?? "")}`.trim()));
1689
+ options.output.write(truncate(sections.filter((section) => section !== void 0).join("\n"), maxChars));
1690
+ return 0;
1691
+ }
1692
+
1693
+ //#endregion
1694
+ //#region src/commands/diff.ts
1695
+ function lineCount(content) {
1696
+ return content.split(/\r?\n/).filter(Boolean).length;
1697
+ }
1698
+ function contentHash(content) {
1699
+ return createHash("sha256").update(content).digest("hex");
1700
+ }
1701
+ async function loadBundle(fs, path) {
1702
+ const raw = await fs.readText(path);
1703
+ const parsed = JSON.parse(raw);
1704
+ if (typeof parsed !== "object" || parsed === null || typeof parsed.files !== "object") throw new Error("Snapshot bundle is malformed.");
1705
+ return parsed;
1706
+ }
1707
+ function snapshotMatches(record, key) {
1708
+ if (record.id === key) return true;
1709
+ if (record.label === key) return true;
1710
+ const metadata = record.metadata;
1711
+ if (typeof metadata === "object" && metadata !== null && !Array.isArray(metadata)) return metadata.label === key;
1712
+ return false;
1713
+ }
1714
+ async function runDiffCommand(options) {
1715
+ const snapshotContent = await options.fs.readTextIfExists(TEKMEMO_PATHS.snapshots);
1716
+ if (!snapshotContent) {
1717
+ options.output.error("No snapshots found.");
1718
+ return 1;
1719
+ }
1720
+ const snapshots = parseJsonl(snapshotContent);
1721
+ const snapA = snapshots.find((s) => snapshotMatches(s.value, options.labelA));
1722
+ const snapB = snapshots.find((s) => snapshotMatches(s.value, options.labelB));
1723
+ if (!snapA) {
1724
+ options.output.error(`Snapshot "${options.labelA}" not found.`);
1725
+ return 1;
1726
+ }
1727
+ if (!snapB) {
1728
+ options.output.error(`Snapshot "${options.labelB}" not found.`);
1729
+ return 1;
1730
+ }
1731
+ const pathA = snapA.value.path;
1732
+ const pathB = snapB.value.path;
1733
+ if (typeof pathA !== "string" || typeof pathB !== "string") {
1734
+ options.output.error("Snapshot index contains a malformed path.");
1735
+ return 1;
1736
+ }
1737
+ let bundleA;
1738
+ let bundleB;
1739
+ try {
1740
+ bundleA = await loadBundle(options.fs, pathA);
1741
+ } catch {
1742
+ options.output.error(`Failed to load snapshot bundle: ${pathA}`);
1743
+ return 1;
1744
+ }
1745
+ try {
1746
+ bundleB = await loadBundle(options.fs, pathB);
1747
+ } catch {
1748
+ options.output.error(`Failed to load snapshot bundle: ${pathB}`);
1749
+ return 1;
1750
+ }
1751
+ const allPaths = new Set([...Object.keys(bundleA.files), ...Object.keys(bundleB.files)]);
1752
+ const diffs = [];
1753
+ for (const filePath of allPaths) {
1754
+ const contentA = bundleA.files[filePath];
1755
+ const contentB = bundleB.files[filePath];
1756
+ if (contentA === void 0 && contentB !== void 0) diffs.push({
1757
+ path: filePath,
1758
+ status: "added",
1759
+ bytesB: Buffer.byteLength(contentB)
1760
+ });
1761
+ else if (contentA !== void 0 && contentB === void 0) diffs.push({
1762
+ path: filePath,
1763
+ status: "removed",
1764
+ bytesA: Buffer.byteLength(contentA)
1765
+ });
1766
+ else if (contentA !== void 0 && contentB !== void 0) if (contentHash(contentA) === contentHash(contentB)) diffs.push({
1767
+ path: filePath,
1768
+ status: "unchanged",
1769
+ bytesA: Buffer.byteLength(contentA),
1770
+ bytesB: Buffer.byteLength(contentB)
1771
+ });
1772
+ else {
1773
+ const isJsonl = filePath.endsWith(".jsonl");
1774
+ diffs.push({
1775
+ path: filePath,
1776
+ status: "changed",
1777
+ bytesA: Buffer.byteLength(contentA),
1778
+ bytesB: Buffer.byteLength(contentB),
1779
+ ...isJsonl ? {
1780
+ recordsA: lineCount(contentA),
1781
+ recordsB: lineCount(contentB)
1782
+ } : {}
1783
+ });
1784
+ }
1785
+ }
1786
+ const changed = diffs.filter((d) => d.status !== "unchanged");
1787
+ const data = {
1788
+ labelA: options.labelA,
1789
+ labelB: options.labelB,
1790
+ snapshotA: {
1791
+ id: bundleA.id,
1792
+ createdAt: bundleA.createdAt ?? bundleA.timestamp,
1793
+ path: pathA
1794
+ },
1795
+ snapshotB: {
1796
+ id: bundleB.id,
1797
+ createdAt: bundleB.createdAt ?? bundleB.timestamp,
1798
+ path: pathB
1799
+ },
1800
+ totalFiles: allPaths.size,
1801
+ changedFiles: changed.length,
1802
+ diffs: changed
1803
+ };
1804
+ if (options.json) {
1805
+ printJsonEnvelope(options.output, "diff", data);
1806
+ return 0;
1807
+ }
1808
+ options.output.write(`Comparing "${options.labelA}" vs "${options.labelB}"`);
1809
+ options.output.write(` [A] ${data.snapshotA.createdAt ?? "unknown"} [B] ${data.snapshotB.createdAt ?? "unknown"}`);
1810
+ options.output.write("");
1811
+ if (changed.length === 0) {
1812
+ options.output.success("No differences found. Snapshots are identical.");
1813
+ return 0;
1814
+ }
1815
+ const statusLabel = {
1816
+ added: "+",
1817
+ removed: "-",
1818
+ changed: "~"
1819
+ };
1820
+ for (const diff of changed) {
1821
+ let line = ` ${statusLabel[diff.status] ?? "?"} ${diff.path}`;
1822
+ if (diff.status === "changed") {
1823
+ const sizeInfo = `${diff.bytesA}B → ${diff.bytesB}B`;
1824
+ const recordInfo = diff.recordsA !== void 0 && diff.recordsB !== void 0 ? ` (${diff.recordsA} → ${diff.recordsB} records)` : "";
1825
+ line += ` ${sizeInfo}${recordInfo}`;
1826
+ } else if (diff.status === "added" && diff.bytesB !== void 0) line += ` (${diff.bytesB}B)`;
1827
+ else if (diff.status === "removed" && diff.bytesA !== void 0) line += ` (${diff.bytesA}B)`;
1828
+ options.output.write(line);
1829
+ }
1830
+ options.output.write(`\n${changed.length} file(s) changed out of ${allPaths.size} total.`);
1831
+ return 0;
1832
+ }
1833
+
1834
+ //#endregion
1835
+ //#region src/protocol/schemas.ts
1836
+ const JsonRecordSchema = z.record(z.string(), z.unknown());
1837
+ const IsoDateSchema = z.string().datetime();
1838
+ const NonEmptyStringSchema = z.string().min(1);
1839
+ const ManifestSchema = z.object({
1840
+ version: NonEmptyStringSchema,
1841
+ projectId: NonEmptyStringSchema.optional(),
1842
+ createdAt: IsoDateSchema,
1843
+ updatedAt: IsoDateSchema,
1844
+ memory: z.object({
1845
+ core: NonEmptyStringSchema,
1846
+ notes: NonEmptyStringSchema
1847
+ }),
1848
+ events: z.object({
1849
+ memoryEvents: NonEmptyStringSchema,
1850
+ conversations: NonEmptyStringSchema
1851
+ }),
1852
+ indexes: z.object({ chunks: NonEmptyStringSchema }),
1853
+ graph: z.object({
1854
+ nodes: NonEmptyStringSchema,
1855
+ edges: NonEmptyStringSchema
1856
+ }),
1857
+ snapshots: z.object({ index: NonEmptyStringSchema })
1858
+ });
1859
+ const ConversationEntrySchema = z.object({
1860
+ timestamp: IsoDateSchema,
1861
+ role: z.enum([
1862
+ "user",
1863
+ "assistant",
1864
+ "system",
1865
+ "tool"
1866
+ ]),
1867
+ content: z.string(),
1868
+ summary: z.string().optional(),
1869
+ metadata: JsonRecordSchema.optional()
1870
+ });
1871
+ const MemoryEventSchema = z.object({
1872
+ id: NonEmptyStringSchema,
1873
+ type: z.enum([
1874
+ "memory.created",
1875
+ "memory.updated",
1876
+ "memory.merged",
1877
+ "memory.conflicted",
1878
+ "memory.decayed",
1879
+ "memory.forgotten",
1880
+ "memory.restored",
1881
+ "memory.indexed",
1882
+ "memory.reindexed",
1883
+ "snapshot.created",
1884
+ "sync.started",
1885
+ "sync.completed",
1886
+ "sync.failed"
1887
+ ]),
1888
+ timestamp: IsoDateSchema,
1889
+ projectId: NonEmptyStringSchema.optional(),
1890
+ sourcePath: NonEmptyStringSchema.optional(),
1891
+ actor: z.object({
1892
+ type: z.enum([
1893
+ "user",
1894
+ "agent",
1895
+ "system",
1896
+ "api"
1897
+ ]),
1898
+ id: NonEmptyStringSchema.optional()
1899
+ }).optional(),
1900
+ summary: NonEmptyStringSchema.optional(),
1901
+ metadata: JsonRecordSchema.optional()
1902
+ });
1903
+ const ChunkRecordSchema = z.object({
1904
+ chunkId: NonEmptyStringSchema,
1905
+ sourcePath: NonEmptyStringSchema,
1906
+ sourceType: z.enum([
1907
+ "document",
1908
+ "note",
1909
+ "conversation",
1910
+ "event",
1911
+ "import",
1912
+ "graph"
1913
+ ]),
1914
+ sourceId: NonEmptyStringSchema,
1915
+ sourceHash: NonEmptyStringSchema,
1916
+ textHash: NonEmptyStringSchema,
1917
+ memoryType: z.enum([
1918
+ "core",
1919
+ "notes",
1920
+ "conversation",
1921
+ "event",
1922
+ "chunk",
1923
+ "graph"
1924
+ ]),
1925
+ index: z.number().int().nonnegative(),
1926
+ startOffset: z.number().int().nonnegative(),
1927
+ endOffset: z.number().int().nonnegative(),
1928
+ status: z.enum([
1929
+ "active",
1930
+ "stale",
1931
+ "deleted"
1932
+ ]),
1933
+ createdAt: IsoDateSchema,
1934
+ updatedAt: IsoDateSchema.optional(),
1935
+ sectionName: z.string().optional(),
1936
+ metadata: JsonRecordSchema.optional()
1937
+ });
1938
+ const SnapshotEntrySchema = z.object({
1939
+ id: NonEmptyStringSchema,
1940
+ path: NonEmptyStringSchema,
1941
+ type: z.enum([
1942
+ "manual",
1943
+ "automatic",
1944
+ "pre-sync",
1945
+ "pre-restore"
1946
+ ]),
1947
+ status: z.enum([
1948
+ "available",
1949
+ "expired",
1950
+ "deleted"
1951
+ ]),
1952
+ createdAt: IsoDateSchema,
1953
+ expiresAt: IsoDateSchema.optional(),
1954
+ checksum: NonEmptyStringSchema.optional(),
1955
+ metadata: JsonRecordSchema.optional()
1956
+ });
1957
+ const LegacySnapshotEntrySchema = z.object({
1958
+ id: NonEmptyStringSchema,
1959
+ timestamp: IsoDateSchema,
1960
+ label: NonEmptyStringSchema,
1961
+ path: NonEmptyStringSchema,
1962
+ metadata: JsonRecordSchema.optional()
1963
+ });
1964
+ const MemoryChunkSchema = ChunkRecordSchema;
1965
+
1966
+ //#endregion
1967
+ //#region src/commands/doctor.ts
1968
+ async function runDoctorCommand(options) {
1969
+ const issues = [];
1970
+ for (const dir of REQUIRED_DIRS) if (!await options.fs.exists(dir)) issues.push({
1971
+ level: "error",
1972
+ code: "missing_dir",
1973
+ message: `Missing directory: ${dir}`
1974
+ });
1975
+ for (const file of REQUIRED_FILES) if (!await options.fs.exists(file)) issues.push({
1976
+ level: "error",
1977
+ code: "missing_file",
1978
+ message: `Missing file: ${file}`
1979
+ });
1980
+ const manifestContent = await options.fs.readTextIfExists(TEKMEMO_PATHS.manifest);
1981
+ if (manifestContent) try {
1982
+ const parsed = JSON.parse(manifestContent);
1983
+ ManifestSchema.parse(parsed);
1984
+ } catch (error) {
1985
+ issues.push({
1986
+ level: "error",
1987
+ code: "invalid_manifest",
1988
+ message: `manifest.json: ${error instanceof Error ? error.message : String(error)}`
1989
+ });
1990
+ }
1991
+ const validationMap = {
1992
+ [TEKMEMO_PATHS.memoryEvents]: MemoryEventSchema,
1993
+ [TEKMEMO_PATHS.conversations]: ConversationEntrySchema,
1994
+ [TEKMEMO_PATHS.chunks]: MemoryChunkSchema,
1995
+ [TEKMEMO_PATHS.snapshots]: SnapshotEntrySchema
1996
+ };
1997
+ const conversationIds = /* @__PURE__ */ new Set();
1998
+ for (const [file, schema] of Object.entries(validationMap)) {
1999
+ const content = await options.fs.readTextIfExists(file);
2000
+ if (content === void 0) continue;
2001
+ const records = parseJsonl(content, { strict: options.strict ?? false });
2002
+ for (const record of records) try {
2003
+ const validated = schema.parse(record.value);
2004
+ if (file === TEKMEMO_PATHS.conversations && typeof validated.id === "string") conversationIds.add(validated.id);
2005
+ } catch (error) {
2006
+ issues.push({
2007
+ level: "error",
2008
+ code: "invalid_line",
2009
+ message: `${file}:${record.line}: ${error instanceof Error ? error.message : String(error)}`
2010
+ });
2011
+ }
2012
+ }
2013
+ const eventContent = await options.fs.readTextIfExists(TEKMEMO_PATHS.memoryEvents);
2014
+ if (eventContent) {
2015
+ const events = parseJsonl(eventContent);
2016
+ for (const event of events) {
2017
+ const docId = event.value.documentId;
2018
+ if (typeof docId !== "string") continue;
2019
+ if (docId === "core" || docId === "notes") continue;
2020
+ if (conversationIds.size > 0 && !conversationIds.has(docId)) issues.push({
2021
+ level: "warning",
2022
+ code: "orphaned_event",
2023
+ message: `${TEKMEMO_PATHS.memoryEvents}:${event.line}: Event references unknown document/conversation "${docId}"`
2024
+ });
2025
+ }
2026
+ }
2027
+ const result = {
2028
+ ok: issues.filter((issue) => issue.level === "error").length === 0,
2029
+ issues
2030
+ };
2031
+ if (options.json) options.output.write(JSON.stringify(result, null, 2));
2032
+ else if (result.ok) if (issues.length > 0) options.output.warn(["TekMemo doctor passed with warnings:", ...issues.map((issue) => `- [${issue.level}] ${issue.message}`)].join("\n"));
2033
+ else options.output.success("TekMemo doctor passed.");
2034
+ else options.output.error(["TekMemo doctor found errors:", ...issues.map((issue) => `- [${issue.level}] ${issue.message}`)].join("\n"));
2035
+ return result.ok ? 0 : 1;
2036
+ }
2037
+
2038
+ //#endregion
2039
+ //#region src/commands/edit.ts
2040
+ async function runEditCommand(options) {
2041
+ const findings = scanForSecrets(options.message);
2042
+ if (findings.length > 0 && !options.allowSecrets) {
2043
+ if (options.json) printJsonEnvelope(options.output, "edit", {
2044
+ updated: false,
2045
+ secretFindings: findings
2046
+ });
2047
+ else options.output.error(`Refusing to store possible secret (${findings[0]?.kind}). Use --allow-secrets only after review.`);
2048
+ return 1;
2049
+ }
2050
+ const file = options.type === "core" ? TEKMEMO_PATHS.coreMemory : TEKMEMO_PATHS.notesMemory;
2051
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2052
+ const entry = options.type === "note" ? `\n## ${timestamp}\n- kind: note\n- tags: none\n- confidence: 1\n\n${options.message.trim()}\n` : `\n${options.message.trim()}\n`;
2053
+ await options.fs.appendText(file, entry);
2054
+ const event = {
2055
+ id: `evt_${Date.now()}_${Math.random().toString(16).slice(2)}`,
2056
+ type: "memory.updated",
2057
+ timestamp,
2058
+ sourcePath: file,
2059
+ actor: { type: "user" },
2060
+ summary: `${options.type} memory updated by CLI`,
2061
+ metadata: {
2062
+ document: options.type,
2063
+ command: "edit",
2064
+ createdBy: "@tekmemo/cli"
2065
+ }
2066
+ };
2067
+ await options.fs.appendText(TEKMEMO_PATHS.memoryEvents, stringifyJsonl([event]));
2068
+ const data = {
2069
+ updated: true,
2070
+ path: file,
2071
+ eventId: event.id,
2072
+ secretFindings: findings
2073
+ };
2074
+ if (options.json) printJsonEnvelope(options.output, "edit", data);
2075
+ else options.output.success(`Updated ${file}`);
2076
+ return 0;
2077
+ }
2078
+
2079
+ //#endregion
2080
+ //#region src/commands/events.ts
2081
+ async function runEventsCommand(options) {
2082
+ const content = await options.fs.readTextIfExists(TEKMEMO_PATHS.memoryEvents);
2083
+ const records = content ? parseJsonl(content, { strict: options.strict ?? false }) : [];
2084
+ const selected = options.limit && options.limit > 0 ? records.slice(-options.limit) : records;
2085
+ if (options.json) {
2086
+ options.output.write(JSON.stringify(selected, null, 2));
2087
+ return 0;
2088
+ }
2089
+ if (selected.length === 0) {
2090
+ options.output.write("No memory events found.");
2091
+ return 0;
2092
+ }
2093
+ options.output.write(selected.map((record) => {
2094
+ const type = typeof record.value.type === "string" ? record.value.type : "unknown";
2095
+ return `${typeof record.value.timestamp === "string" ? record.value.timestamp : `line ${record.line}`} ${type}${typeof record.value.summary === "string" ? ` — ${record.value.summary}` : ""}`;
2096
+ }).join("\n"));
2097
+ return 0;
2098
+ }
2099
+
2100
+ //#endregion
2101
+ //#region src/commands/init.ts
2102
+ async function runInitCommand(options) {
2103
+ await options.fs.ensureSafeRoot();
2104
+ let projectId = options.projectId?.trim();
2105
+ if (projectId !== void 0 && projectId.length === 0) projectId = void 0;
2106
+ if (!projectId && !options.json && !options.noInput && process.stdout.isTTY) {
2107
+ options.output.write("Initializing TekMemo...");
2108
+ const rl = readline.createInterface({
2109
+ input: process.stdin,
2110
+ output: process.stdout
2111
+ });
2112
+ try {
2113
+ const answer = await rl.question("Enter project ID (leave empty for random): ");
2114
+ if (answer.trim()) projectId = answer.trim();
2115
+ } finally {
2116
+ rl.close();
2117
+ }
2118
+ }
2119
+ for (const dir of REQUIRED_DIRS) await options.fs.mkdir(dir);
2120
+ if (await options.fs.readTextIfExists(TEKMEMO_PATHS.manifest) && !options.force) {
2121
+ const data = {
2122
+ created: false,
2123
+ rootDir: options.fs.rootDir,
2124
+ message: ".tekmemo already exists. Use --force to overwrite seed files."
2125
+ };
2126
+ if (options.json) printJsonEnvelope(options.output, "init", data);
2127
+ else options.output.write(data.message);
2128
+ return 0;
2129
+ }
2130
+ const manifest = createDefaultManifest(projectId ? { projectId } : void 0);
2131
+ const seedFiles = {
2132
+ [TEKMEMO_PATHS.manifest]: `${JSON.stringify(manifest, null, 2)}\n`,
2133
+ [TEKMEMO_PATHS.coreMemory]: "# Core Memory\n\n",
2134
+ [TEKMEMO_PATHS.notesMemory]: "# Notes\n\n",
2135
+ [TEKMEMO_PATHS.memoryEvents]: "",
2136
+ [TEKMEMO_PATHS.conversations]: "",
2137
+ [TEKMEMO_PATHS.chunks]: "",
2138
+ [TEKMEMO_PATHS.graphNodes]: "",
2139
+ [TEKMEMO_PATHS.graphEdges]: "",
2140
+ [TEKMEMO_PATHS.snapshots]: ""
2141
+ };
2142
+ const created = [];
2143
+ const overwritten = [];
2144
+ const skipped = [];
2145
+ for (const file of REQUIRED_FILES) {
2146
+ const exists = await options.fs.exists(file);
2147
+ if (!exists || options.force) {
2148
+ await options.fs.writeText(file, seedFiles[file] ?? "");
2149
+ if (exists) overwritten.push(file);
2150
+ else created.push(file);
2151
+ } else skipped.push(file);
2152
+ }
2153
+ const data = {
2154
+ created: true,
2155
+ rootDir: options.fs.rootDir,
2156
+ manifest,
2157
+ files: {
2158
+ created,
2159
+ overwritten,
2160
+ skipped
2161
+ }
2162
+ };
2163
+ if (options.json) printJsonEnvelope(options.output, "init", data);
2164
+ else options.output.success(`Initialized .tekmemo at ${options.fs.rootDir} (Project ID: ${manifest.projectId ?? "none"})`);
2165
+ return 0;
2166
+ }
2167
+
2168
+ //#endregion
2169
+ //#region src/commands/inspect.ts
2170
+ async function runInspectCommand(options) {
2171
+ const inspection = await inspectTekMemo(options.fs);
2172
+ if (options.json) {
2173
+ options.output.write(JSON.stringify(inspection, null, 2));
2174
+ return 0;
2175
+ }
2176
+ const lines = [
2177
+ `TekMemo root: ${inspection.rootDir}`,
2178
+ `.tekmemo exists: ${inspection.exists ? "yes" : "no"}`,
2179
+ inspection.manifest ? `Project: ${inspection.manifest.projectId}` : "Project: missing manifest",
2180
+ "",
2181
+ "Files:"
2182
+ ];
2183
+ for (const file of inspection.files) lines.push(`- ${file.path}: ${file.exists ? `${file.bytes} bytes` : "missing"}${file.records !== void 0 ? `, ${file.records} records` : ""}`);
2184
+ lines.push("");
2185
+ lines.push("Summary:");
2186
+ lines.push(`- events: ${inspection.summary.eventCount}`);
2187
+ lines.push(`- conversations: ${inspection.summary.conversationCount}`);
2188
+ lines.push(`- chunks: ${inspection.summary.chunkCount}`);
2189
+ lines.push(`- graph nodes: ${inspection.summary.graphNodeCount}`);
2190
+ lines.push(`- graph edges: ${inspection.summary.graphEdgeCount}`);
2191
+ lines.push(`- snapshots: ${inspection.summary.snapshotCount}`);
2192
+ options.output.write(lines.join("\n"));
2193
+ return 0;
2194
+ }
2195
+
2196
+ //#endregion
2197
+ //#region src/commands/read.ts
2198
+ const TARGET_PATHS = {
2199
+ core: TEKMEMO_PATHS.coreMemory,
2200
+ notes: TEKMEMO_PATHS.notesMemory,
2201
+ manifest: TEKMEMO_PATHS.manifest
2202
+ };
2203
+ async function runReadCommand(options) {
2204
+ const path = TARGET_PATHS[options.target];
2205
+ const content = await options.fs.readTextIfExists(path);
2206
+ if (content === void 0) {
2207
+ options.output.error(`${path} does not exist. Run tekmemo init first.`);
2208
+ return 1;
2209
+ }
2210
+ if (options.json) printJsonEnvelope(options.output, "read", {
2211
+ target: options.target,
2212
+ path,
2213
+ content
2214
+ });
2215
+ else options.output.write(content.trimEnd());
2216
+ return 0;
2217
+ }
2218
+
2219
+ //#endregion
2220
+ //#region src/commands/remember.ts
2221
+ const NOTE_KINDS = new Set([
2222
+ "decision",
2223
+ "constraint",
2224
+ "goal",
2225
+ "preference",
2226
+ "reference",
2227
+ "summary",
2228
+ "note"
2229
+ ]);
2230
+ function normalizeKind(kind) {
2231
+ const normalized = (kind ?? "note").trim();
2232
+ if (!NOTE_KINDS.has(normalized)) throw new CliUsageError(`Invalid memory kind "${normalized}". Allowed: ${[...NOTE_KINDS].join(", ")}`);
2233
+ return normalized;
2234
+ }
2235
+ function parseActor(actor) {
2236
+ if (!actor) return { type: "user" };
2237
+ const [type, id] = actor.split(":", 2);
2238
+ if (type !== "user" && type !== "agent" && type !== "system" && type !== "api") throw new CliUsageError("actor must be one of user, agent, system, or api, optionally followed by :id.");
2239
+ return {
2240
+ type,
2241
+ ...id ? { id } : {}
2242
+ };
2243
+ }
2244
+ function formatTimestampedNote(input) {
2245
+ const heading = input.title ? `${input.timestamp} — ${input.title.replace(/\s+/g, " ").trim()}` : input.timestamp;
2246
+ const tags = input.tags?.length ? input.tags.join(", ") : "none";
2247
+ return [
2248
+ `## ${heading}`,
2249
+ `- kind: ${input.kind}`,
2250
+ `- tags: ${tags}`,
2251
+ `- confidence: ${input.confidence}`,
2252
+ input.source ? `- source: ${input.source}` : void 0,
2253
+ input.metadata ? `- metadata: ${JSON.stringify(input.metadata)}` : void 0,
2254
+ "",
2255
+ input.content.trim(),
2256
+ ""
2257
+ ].filter((line) => line !== void 0).join("\n");
2258
+ }
2259
+ async function runRememberCommand(options) {
2260
+ const content = await resolveCommandContent({
2261
+ rootDir: options.fs.rootDir,
2262
+ inline: options.content,
2263
+ stdin: options.stdin,
2264
+ file: options.file,
2265
+ stdinContent: options.stdinContent
2266
+ });
2267
+ const findings = scanForSecrets(content);
2268
+ if (findings.length > 0 && !options.allowSecrets) {
2269
+ const data = { secretFindings: findings };
2270
+ if (options.json) printJsonEnvelope(options.output, "remember", {
2271
+ stored: false,
2272
+ ...data
2273
+ });
2274
+ else options.output.error(`Refusing to store possible secret (${findings[0]?.kind}). Use --allow-secrets only after review.`);
2275
+ return 1;
2276
+ }
2277
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2278
+ const kind = normalizeKind(options.kind);
2279
+ const tags = (options.tags ?? []).map((tag) => tag.trim()).filter(Boolean);
2280
+ const confidence = typeof options.confidence === "number" ? options.confidence : options.confidence ? parseConfidence(options.confidence) : 1;
2281
+ const metadata = parseMetadataJson(options.metadata);
2282
+ const actor = parseActor(options.actor);
2283
+ const note = formatTimestampedNote({
2284
+ timestamp,
2285
+ kind,
2286
+ content,
2287
+ ...options.title ? { title: options.title } : {},
2288
+ ...tags.length ? { tags } : {},
2289
+ confidence,
2290
+ ...options.source ? { source: options.source } : {},
2291
+ ...metadata ? { metadata } : {}
2292
+ });
2293
+ const nextNotes = `${(await options.fs.readTextIfExists(TEKMEMO_PATHS.notesMemory) ?? "# Notes\n").trimEnd()}\n\n${note}`.trimStart();
2294
+ await options.fs.writeText(TEKMEMO_PATHS.notesMemory, `${nextNotes.trimEnd()}\n`);
2295
+ const eventId = `evt_${Date.now()}_${Math.random().toString(16).slice(2)}`;
2296
+ const event = {
2297
+ id: eventId,
2298
+ type: "memory.created",
2299
+ timestamp,
2300
+ sourcePath: TEKMEMO_PATHS.notesMemory,
2301
+ actor,
2302
+ summary: options.title ?? content.split(/\r?\n/)[0]?.slice(0, 140) ?? "Stored memory note",
2303
+ metadata: {
2304
+ kind,
2305
+ tags,
2306
+ confidence,
2307
+ ...options.source ? { source: options.source } : {},
2308
+ ...metadata ? { userMetadata: metadata } : {},
2309
+ createdBy: "@tekmemo/cli"
2310
+ }
2311
+ };
2312
+ await options.fs.appendText(TEKMEMO_PATHS.memoryEvents, stringifyJsonl([event]));
2313
+ const data = {
2314
+ stored: true,
2315
+ eventId,
2316
+ path: TEKMEMO_PATHS.notesMemory,
2317
+ kind,
2318
+ tags,
2319
+ confidence,
2320
+ secretFindings: findings
2321
+ };
2322
+ if (options.json) printJsonEnvelope(options.output, "remember", data);
2323
+ else options.output.success(`Stored ${kind} memory in ${TEKMEMO_PATHS.notesMemory}`);
2324
+ return 0;
2325
+ }
2326
+
2327
+ //#endregion
2328
+ //#region src/utils/labels.ts
2329
+ function validateSnapshotLabel(label) {
2330
+ if (typeof label !== "string" || label.trim().length === 0) throw new CliUsageError("Snapshot label must be a non-empty string.");
2331
+ const normalized = label.trim();
2332
+ if (normalized.length > 80) throw new CliUsageError("Snapshot label must be 80 characters or fewer.");
2333
+ if (normalized.includes("\0")) throw new CliUsageError("Snapshot label must not contain null bytes.");
2334
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(normalized)) throw new CliUsageError("Snapshot label may only contain letters, numbers, dots, underscores, and hyphens, and must start with a letter or number.");
2335
+ return normalized;
2336
+ }
2337
+ function createSafeIdFromLabel(label, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
2338
+ return `${validateSnapshotLabel(label)}-${timestamp.replace(/[:.]/g, "-")}`.slice(0, 120);
2339
+ }
2340
+
2341
+ //#endregion
2342
+ //#region src/commands/snapshot.ts
2343
+ function checksum(value) {
2344
+ return createHash("sha256").update(JSON.stringify(value)).digest("hex");
2345
+ }
2346
+ async function runSnapshotCommand(options) {
2347
+ const label = validateSnapshotLabel(options.label);
2348
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
2349
+ const id = createSafeIdFromLabel(label, createdAt);
2350
+ const path = `${TEKMEMO_PATHS.snapshotsDir}/${id}.json`;
2351
+ const files = {};
2352
+ for (const filePath of REQUIRED_FILES) {
2353
+ const content = await options.fs.readTextIfExists(filePath);
2354
+ if (content !== void 0) files[filePath] = content;
2355
+ }
2356
+ const bundleWithoutChecksum = {
2357
+ id,
2358
+ label,
2359
+ createdAt,
2360
+ protocolVersion: "1",
2361
+ files
2362
+ };
2363
+ const bundle = {
2364
+ ...bundleWithoutChecksum,
2365
+ checksum: checksum(bundleWithoutChecksum)
2366
+ };
2367
+ await options.fs.writeText(path, `${JSON.stringify(bundle, null, 2)}\n`);
2368
+ const record = {
2369
+ id,
2370
+ path,
2371
+ type: "manual",
2372
+ status: "available",
2373
+ createdAt,
2374
+ checksum: bundle.checksum,
2375
+ metadata: {
2376
+ label,
2377
+ fileCount: Object.keys(files).length,
2378
+ createdBy: "@tekmemo/cli"
2379
+ }
2380
+ };
2381
+ await options.fs.appendText(TEKMEMO_PATHS.snapshots, stringifyJsonl([record]));
2382
+ await options.fs.appendText(TEKMEMO_PATHS.memoryEvents, stringifyJsonl([{
2383
+ id: `evt_${id}`,
2384
+ type: "snapshot.created",
2385
+ timestamp: createdAt,
2386
+ sourcePath: path,
2387
+ actor: {
2388
+ type: "system",
2389
+ id: "@tekmemo/cli"
2390
+ },
2391
+ summary: `Created snapshot ${label}`,
2392
+ metadata: {
2393
+ snapshotId: id,
2394
+ label,
2395
+ checksum: bundle.checksum
2396
+ }
2397
+ }]));
2398
+ const data = {
2399
+ id,
2400
+ label,
2401
+ path,
2402
+ createdAt,
2403
+ checksum: bundle.checksum,
2404
+ fileCount: Object.keys(files).length
2405
+ };
2406
+ if (options.json) printJsonEnvelope(options.output, "snapshot", data);
2407
+ else options.output.success(`Created snapshot "${label}" at ${path}`);
2408
+ return 0;
2409
+ }
2410
+
2411
+ //#endregion
2412
+ //#region src/commands/validate.ts
2413
+ async function runValidateCommand(options) {
2414
+ const issues = [];
2415
+ for (const dir of REQUIRED_DIRS) if (!await options.fs.exists(dir)) issues.push({
2416
+ code: "missing_dir",
2417
+ message: `Missing required directory: ${dir}`
2418
+ });
2419
+ for (const file of REQUIRED_FILES) if (!await options.fs.exists(file)) issues.push({
2420
+ code: "missing_file",
2421
+ message: `Missing required file: ${file}`
2422
+ });
2423
+ const manifestContent = await options.fs.readTextIfExists(TEKMEMO_PATHS.manifest);
2424
+ if (manifestContent === void 0) issues.push({
2425
+ code: "missing_manifest",
2426
+ message: "manifest.json is missing"
2427
+ });
2428
+ else try {
2429
+ const manifest = parseManifest$1(manifestContent);
2430
+ for (const key of ["core", "notes"]) {
2431
+ const filePath = manifest.memory[key];
2432
+ if (!await options.fs.exists(filePath)) issues.push({
2433
+ code: "manifest_ref_missing",
2434
+ message: `Manifest references ${filePath} but file is missing`
2435
+ });
2436
+ }
2437
+ } catch (error) {
2438
+ issues.push({
2439
+ code: "invalid_manifest",
2440
+ message: `manifest.json: ${error instanceof Error ? error.message : String(error)}`
2441
+ });
2442
+ }
2443
+ const strictSchemas = {
2444
+ [TEKMEMO_PATHS.memoryEvents]: MemoryEventSchema,
2445
+ [TEKMEMO_PATHS.conversations]: ConversationEntrySchema,
2446
+ [TEKMEMO_PATHS.chunks]: MemoryChunkSchema,
2447
+ [TEKMEMO_PATHS.snapshots]: SnapshotEntrySchema
2448
+ };
2449
+ for (const [file, schema] of Object.entries(strictSchemas)) {
2450
+ const content = await options.fs.readTextIfExists(file);
2451
+ if (content === void 0) continue;
2452
+ const lines = content.split(/\r?\n/);
2453
+ for (let i = 0; i < lines.length; i++) {
2454
+ const line = lines[i]?.trim();
2455
+ if (!line) continue;
2456
+ const lineNumber = i + 1;
2457
+ let parsed;
2458
+ try {
2459
+ parsed = JSON.parse(line);
2460
+ } catch {
2461
+ issues.push({
2462
+ code: "invalid_json",
2463
+ message: `${file}:${lineNumber}: Invalid JSON`
2464
+ });
2465
+ continue;
2466
+ }
2467
+ const result = schema.safeParse(parsed);
2468
+ if (!result.success) {
2469
+ const firstIssue = result.error.issues[0];
2470
+ const path = firstIssue?.path.join(".") ?? "unknown";
2471
+ const msg = firstIssue?.message ?? "validation failed";
2472
+ issues.push({
2473
+ code: "schema_violation",
2474
+ message: `${file}:${lineNumber}: ${path} — ${msg}`
2475
+ });
2476
+ }
2477
+ }
2478
+ }
2479
+ const ok = issues.length === 0;
2480
+ if (options.json) options.output.write(JSON.stringify({
2481
+ ok,
2482
+ issues
2483
+ }, null, 2));
2484
+ else if (ok) options.output.success("Validation passed. All protocol files are valid.");
2485
+ else options.output.error(["Validation failed:", ...issues.map((issue) => `- [${issue.code}] ${issue.message}`)].join("\n"));
2486
+ return ok ? 0 : 1;
2487
+ }
2488
+
2489
+ //#endregion
2490
+ //#region src/commands/runtime.ts
2491
+ async function runRuntimeContextCommand(options) {
2492
+ if (options.config.runtime === "cloud") return runCloudContextCommand({
2493
+ output: options.output,
2494
+ json: options.json,
2495
+ ...options.config.cloud,
2496
+ query: options.query ?? "project context",
2497
+ maxBytes: options.maxChars
2498
+ });
2499
+ if (options.config.runtime === "local") return runContextCommand({
2500
+ fs: options.fs,
2501
+ output: options.output,
2502
+ json: options.json,
2503
+ query: options.query,
2504
+ maxChars: options.maxChars,
2505
+ includeEvents: options.includeEvents,
2506
+ includeChunks: options.includeChunks
2507
+ });
2508
+ return runHybridContextCommand(options);
2509
+ }
2510
+ async function runRuntimeRememberCommand(options) {
2511
+ if (options.config.runtime === "cloud") return runCloudRememberFromRuntime(options);
2512
+ if (options.config.runtime === "local") return runLocalRememberFromRuntime(options);
2513
+ return runWritePolicy(options.config.hybrid.writePolicy, () => runLocalRememberFromRuntime(options), () => runCloudRememberFromRuntime(options));
2514
+ }
2515
+ async function runRuntimeReadCommand(options) {
2516
+ if (options.target === "manifest") return runLocalReadFromRuntime(options);
2517
+ if (options.config.runtime === "cloud") return runCloudReadFromRuntime(options);
2518
+ if (options.config.runtime === "local") return runLocalReadFromRuntime(options);
2519
+ return runReadPolicy(options.config.hybrid.readPolicy, () => runLocalReadFromRuntime(options), () => runCloudReadFromRuntime(options));
2520
+ }
2521
+ async function runRuntimeValidateCommand(options) {
2522
+ if (options.config.runtime === "cloud") return runCloudValidateCommand({
2523
+ output: options.output,
2524
+ json: options.json,
2525
+ ...options.config.cloud
2526
+ });
2527
+ if (options.config.runtime === "local") return runValidateCommand({
2528
+ fs: options.fs,
2529
+ output: options.output,
2530
+ json: options.json
2531
+ });
2532
+ const local = createBufferedOutput({ noColor: true });
2533
+ const cloud = createBufferedOutput({ noColor: true });
2534
+ const localExit = await runValidateCommand({
2535
+ fs: options.fs,
2536
+ output: local,
2537
+ json: options.json
2538
+ });
2539
+ const cloudExit = await runCloudValidateCommand({
2540
+ output: cloud,
2541
+ json: options.json,
2542
+ ...options.config.cloud
2543
+ }).catch((error) => {
2544
+ cloud.error(error instanceof Error ? error.message : String(error));
2545
+ return 1;
2546
+ });
2547
+ if (options.json) {
2548
+ printJsonEnvelope(options.output, "validate", {
2549
+ runtime: "hybrid",
2550
+ local: {
2551
+ exitCode: localExit,
2552
+ stdout: local.stdout,
2553
+ stderr: local.stderr
2554
+ },
2555
+ cloud: {
2556
+ exitCode: cloudExit,
2557
+ stdout: cloud.stdout,
2558
+ stderr: cloud.stderr
2559
+ }
2560
+ });
2561
+ return localExit === 0 && cloudExit === 0 ? 0 : 1;
2562
+ }
2563
+ options.output.write([
2564
+ "# Local validation",
2565
+ ...local.stdout,
2566
+ "",
2567
+ "# Cloud validation",
2568
+ ...cloud.stdout
2569
+ ].join("\n"));
2570
+ for (const line of [...local.stderr, ...cloud.stderr]) options.output.error(line);
2571
+ return localExit === 0 && cloudExit === 0 ? 0 : 1;
2572
+ }
2573
+ async function runRuntimeSnapshotCommand(options) {
2574
+ if (options.config.runtime === "cloud") return runCloudSnapshotCommand({
2575
+ output: options.output,
2576
+ json: options.json,
2577
+ ...options.config.cloud,
2578
+ label: options.label
2579
+ });
2580
+ if (options.config.runtime === "local") return runSnapshotCommand({
2581
+ fs: options.fs,
2582
+ output: options.output,
2583
+ json: options.json,
2584
+ label: options.label ?? "manual"
2585
+ });
2586
+ return runWritePolicy(options.config.hybrid.writePolicy, () => runSnapshotCommand({
2587
+ fs: options.fs,
2588
+ output: options.output,
2589
+ json: options.json,
2590
+ label: options.label ?? "manual"
2591
+ }), () => runCloudSnapshotCommand({
2592
+ output: options.output,
2593
+ json: options.json,
2594
+ ...options.config.cloud,
2595
+ label: options.label
2596
+ }));
2597
+ }
2598
+ async function runHybridContextCommand(options) {
2599
+ const readPolicy = options.config.hybrid.readPolicy;
2600
+ if (readPolicy === "local-only") return runContextCommand({
2601
+ fs: options.fs,
2602
+ output: options.output,
2603
+ json: options.json,
2604
+ query: options.query,
2605
+ maxChars: options.maxChars,
2606
+ includeEvents: options.includeEvents,
2607
+ includeChunks: options.includeChunks
2608
+ });
2609
+ if (readPolicy === "cloud-only") return runCloudContextCommand({
2610
+ output: options.output,
2611
+ json: options.json,
2612
+ ...options.config.cloud,
2613
+ query: options.query ?? "project context",
2614
+ maxBytes: options.maxChars
2615
+ });
2616
+ const local = createBufferedOutput({ noColor: true });
2617
+ const cloud = createBufferedOutput({ noColor: true });
2618
+ const localExit = await runContextCommand({
2619
+ fs: options.fs,
2620
+ output: local,
2621
+ json: false,
2622
+ query: options.query,
2623
+ maxChars: options.maxChars,
2624
+ includeEvents: options.includeEvents,
2625
+ includeChunks: options.includeChunks
2626
+ });
2627
+ const cloudExit = await runCloudContextCommand({
2628
+ output: cloud,
2629
+ json: false,
2630
+ ...options.config.cloud,
2631
+ query: options.query ?? "project context",
2632
+ maxBytes: options.maxChars
2633
+ }).catch((error) => {
2634
+ cloud.error(error instanceof Error ? error.message : String(error));
2635
+ return 1;
2636
+ });
2637
+ const first = readPolicy === "cloud-first" ? {
2638
+ label: "Cloud",
2639
+ out: cloud,
2640
+ code: cloudExit
2641
+ } : {
2642
+ label: "Local",
2643
+ out: local,
2644
+ code: localExit
2645
+ };
2646
+ const second = readPolicy === "cloud-first" ? {
2647
+ label: "Local",
2648
+ out: local,
2649
+ code: localExit
2650
+ } : {
2651
+ label: "Cloud",
2652
+ out: cloud,
2653
+ code: cloudExit
2654
+ };
2655
+ if (options.json) {
2656
+ printJsonEnvelope(options.output, "context", {
2657
+ runtime: "hybrid",
2658
+ readPolicy,
2659
+ [first.label.toLowerCase()]: {
2660
+ exitCode: first.code,
2661
+ stdout: first.out.stdout,
2662
+ stderr: first.out.stderr
2663
+ },
2664
+ [second.label.toLowerCase()]: {
2665
+ exitCode: second.code,
2666
+ stdout: second.out.stdout,
2667
+ stderr: second.out.stderr
2668
+ }
2669
+ });
2670
+ return first.code === 0 || second.code === 0 ? 0 : 1;
2671
+ }
2672
+ options.output.write([
2673
+ `# TekMemo Hybrid Context`,
2674
+ `Read policy: ${readPolicy}`,
2675
+ "",
2676
+ `## ${first.label} Context`,
2677
+ ...first.out.stdout,
2678
+ "",
2679
+ `## ${second.label} Context`,
2680
+ ...second.out.stdout
2681
+ ].join("\n"));
2682
+ for (const line of [...first.out.stderr, ...second.out.stderr]) options.output.error(line);
2683
+ return first.code === 0 || second.code === 0 ? 0 : 1;
2684
+ }
2685
+ async function runLocalRememberFromRuntime(options) {
2686
+ return runRememberCommand({
2687
+ fs: options.fs,
2688
+ output: options.output,
2689
+ json: options.json,
2690
+ content: options.content,
2691
+ stdin: options.stdin,
2692
+ file: options.file,
2693
+ stdinContent: options.stdinContent,
2694
+ kind: options.kind,
2695
+ title: options.title,
2696
+ tags: options.tags,
2697
+ confidence: options.confidence,
2698
+ source: options.source,
2699
+ actor: options.actor,
2700
+ metadata: options.metadata,
2701
+ allowSecrets: options.allowSecrets
2702
+ });
2703
+ }
2704
+ async function runCloudRememberFromRuntime(options) {
2705
+ return runCloudRememberCommand({
2706
+ output: options.output,
2707
+ json: options.json,
2708
+ rootDir: options.config.root,
2709
+ stdinContent: options.stdinContent,
2710
+ ...options.config.cloud,
2711
+ content: options.content,
2712
+ stdin: options.stdin,
2713
+ file: options.file,
2714
+ kind: options.kind,
2715
+ title: options.title,
2716
+ tags: options.tags,
2717
+ confidence: options.confidence,
2718
+ source: options.source,
2719
+ metadata: options.metadata,
2720
+ allowSecrets: options.allowSecrets
2721
+ });
2722
+ }
2723
+ async function runLocalReadFromRuntime(options) {
2724
+ return runReadCommand({
2725
+ fs: options.fs,
2726
+ output: options.output,
2727
+ json: options.json,
2728
+ target: options.target
2729
+ });
2730
+ }
2731
+ async function runCloudReadFromRuntime(options) {
2732
+ if (options.target === "manifest") throw new Error("Cloud runtime cannot read local manifest. Use --runtime local or read core/notes.");
2733
+ return runCloudReadCommand({
2734
+ output: options.output,
2735
+ json: options.json,
2736
+ ...options.config.cloud,
2737
+ target: options.target
2738
+ });
2739
+ }
2740
+ async function runReadPolicy(policy, local, cloud) {
2741
+ if (policy === "local-only") return local();
2742
+ if (policy === "cloud-only") return cloud();
2743
+ if (policy === "cloud-first") {
2744
+ const cloudCode = await cloud();
2745
+ return cloudCode === 0 ? cloudCode : local();
2746
+ }
2747
+ const localCode = await local();
2748
+ return localCode === 0 ? localCode : cloud();
2749
+ }
2750
+ async function runWritePolicy(policy, local, cloud) {
2751
+ if (policy === "local-only") return local();
2752
+ if (policy === "cloud-only") return cloud();
2753
+ if (policy === "cloud-first") {
2754
+ const cloudCode = await cloud();
2755
+ const localCode = await local();
2756
+ return cloudCode === 0 && localCode === 0 ? 0 : 1;
2757
+ }
2758
+ const localCode = await local();
2759
+ const cloudCode = await cloud();
2760
+ return localCode === 0 && cloudCode === 0 ? 0 : 1;
2761
+ }
2762
+
2763
+ //#endregion
2764
+ //#region src/commands/search.ts
2765
+ async function runSearchCommand(options) {
2766
+ const matches = [];
2767
+ const filesToSearch = [
2768
+ TEKMEMO_PATHS.coreMemory,
2769
+ TEKMEMO_PATHS.notesMemory,
2770
+ TEKMEMO_PATHS.conversations
2771
+ ];
2772
+ let matcher;
2773
+ if (options.regex) {
2774
+ let pattern;
2775
+ try {
2776
+ pattern = new RegExp(options.query, "i");
2777
+ } catch {
2778
+ options.output.error(`Invalid regular expression: ${options.query}`);
2779
+ return 1;
2780
+ }
2781
+ matcher = (line) => pattern.test(line);
2782
+ } else {
2783
+ const query = options.query.toLowerCase();
2784
+ matcher = (line) => line.toLowerCase().includes(query);
2785
+ }
2786
+ for (const file of filesToSearch) {
2787
+ const content = await options.fs.readTextIfExists(file);
2788
+ if (!content) continue;
2789
+ const lines = content.split(/\r?\n/);
2790
+ for (let i = 0; i < lines.length; i++) {
2791
+ const line = lines[i];
2792
+ if (line !== void 0 && matcher(line)) matches.push({
2793
+ file,
2794
+ line: i + 1,
2795
+ content: line.trim()
2796
+ });
2797
+ }
2798
+ }
2799
+ if (options.json) {
2800
+ options.output.write(JSON.stringify({
2801
+ query: options.query,
2802
+ matches
2803
+ }, null, 2));
2804
+ return 0;
2805
+ }
2806
+ if (matches.length === 0) {
2807
+ options.output.write(`No matches found for "${options.query}".`);
2808
+ return 0;
2809
+ }
2810
+ for (const match of matches) options.output.write(`${match.file}:${match.line}: ${match.content}`);
2811
+ options.output.success(`Found ${matches.length} match(es).`);
2812
+ return 0;
2813
+ }
2814
+
2815
+ //#endregion
2816
+ //#region src/runner.ts
2817
+ const pkg = createRequire(import.meta.url)("../package.json");
2818
+ const parsePositiveOption = parsePositiveInteger;
2819
+ const parseNonNegativeOption = parseNonNegativeInteger;
2820
+ function createFs(root) {
2821
+ return new TekMemoFileSystem({ rootDir: root });
2822
+ }
2823
+ async function runTekMemoCli(input) {
2824
+ const output = input.output ?? createBufferedOutput(input.noColor === void 0 ? void 0 : { noColor: input.noColor });
2825
+ let exitCode = 0;
2826
+ let currentCommand = "tekmemo";
2827
+ let wantsJson = input.argv.includes("--json") || input.argv.includes("-j");
2828
+ const program = new Command();
2829
+ program.name("tekmemo").description("Production-grade CLI for TekMemo .tekmemo/ memory, context, validation, snapshots, and agent tools.").version(pkg.version).option("-r, --root <path>", "project root containing .tekmemo/", input.cwd ?? process.cwd()).option("--runtime <mode>", "runtime mode: local, cloud, or hybrid").option("--cloud-url <url>", "TekMemo Cloud API URL; defaults to config or TEKMEMO_CLOUD_URL").option("--api-key <key>", "TekMemo Cloud API key; defaults to TEKMEMO_API_KEY").option("--workspace-id <id>", "default cloud workspace ID").option("--project-id <id>", "default cloud project ID").option("--timeout-ms <n>", "cloud request timeout in milliseconds", parsePositiveOption).option("--read-policy <policy>", "hybrid read policy: local-first, cloud-first, local-only, cloud-only").option("--write-policy <policy>", "hybrid write policy: local-first, cloud-first, local-only, cloud-only").option("-j, --json", "output machine-readable JSON", false).option("-v, --verbose", "show detailed output", input.verbose ?? false).option("-q, --quiet", "suppress all output except errors", input.quiet ?? false).option("--no-color", "disable colored output", input.noColor ?? false).exitOverride().showHelpAfterError().configureOutput({
2830
+ writeOut: (str) => {
2831
+ if (!program.opts().quiet) output.write(str.trim());
2832
+ },
2833
+ writeErr: (str) => output.error(str.trim()),
2834
+ getOutHelpWidth: () => 100,
2835
+ getErrHelpWidth: () => 100
2836
+ });
2837
+ async function globals() {
2838
+ const opts = program.opts();
2839
+ wantsJson = Boolean(opts.json);
2840
+ const config = await resolveCliRuntimeConfig({
2841
+ cwd: input.cwd ?? process.cwd(),
2842
+ flags: {
2843
+ root: opts.root,
2844
+ runtime: opts.runtime,
2845
+ cloudUrl: opts.cloudUrl,
2846
+ apiKey: opts.apiKey,
2847
+ workspaceId: opts.workspaceId,
2848
+ projectId: opts.projectId,
2849
+ timeoutMs: opts.timeoutMs,
2850
+ readPolicy: opts.readPolicy,
2851
+ writePolicy: opts.writePolicy
2852
+ }
2853
+ });
2854
+ return {
2855
+ root: config.root,
2856
+ json: Boolean(opts.json),
2857
+ verbose: Boolean(opts.verbose),
2858
+ quiet: Boolean(opts.quiet),
2859
+ config
2860
+ };
2861
+ }
2862
+ program.command("init").description("initialize canonical .tekmemo/ files").option("-f, --force", "overwrite existing seed files", false).option("-p, --project-id <id>", "explicit project ID").option("--no-input", "skip interactive prompts", false).action(async (options) => {
2863
+ currentCommand = "init";
2864
+ const g = await globals();
2865
+ exitCode = await runInitCommand({
2866
+ fs: createFs(g.root),
2867
+ output,
2868
+ json: g.json,
2869
+ force: options.force,
2870
+ projectId: options.projectId,
2871
+ noInput: options.noInput ?? !process.stdout.isTTY
2872
+ });
2873
+ });
2874
+ program.command("inspect").description("summarize local TekMemo memory state").action(async () => {
2875
+ currentCommand = "inspect";
2876
+ const g = await globals();
2877
+ exitCode = await runInspectCommand({
2878
+ fs: createFs(g.root),
2879
+ output,
2880
+ json: g.json
2881
+ });
2882
+ });
2883
+ program.command("context").description("pack project memory into an agent-friendly context block").option("-q, --query <query>", "prioritize lines matching a task/query").option("--max-chars <n>", "maximum output characters", parsePositiveOption, 12e3).option("--include-events", "include recent memory events", false).option("--include-chunks", "include recent chunk records", false).action(async (options) => {
2884
+ currentCommand = "context";
2885
+ const g = await globals();
2886
+ exitCode = await runRuntimeContextCommand({
2887
+ fs: createFs(g.root),
2888
+ output,
2889
+ json: g.json,
2890
+ config: g.config,
2891
+ query: options.query,
2892
+ maxChars: options.maxChars,
2893
+ includeEvents: options.includeEvents,
2894
+ includeChunks: options.includeChunks
2895
+ });
2896
+ });
2897
+ program.command("remember").description("store a durable note for humans or coding agents").argument("[content]", "memory content").option("--stdin", "read memory content from stdin", false).option("--file <path>", "read memory content from a file inside the selected root").option("-k, --kind <kind>", "decision | constraint | goal | preference | reference | summary | note", "note").option("--title <title>", "optional note title").option("-t, --tag <tag>", "tag to attach; repeatable", collect, []).option("--confidence <n>", "confidence from 0 to 1").option("--source <source>", "source identifier, file, URL, or agent name").option("--actor <actor>", "actor type or type:id, e.g. agent:claude-code", "user").option("--metadata-json <json>", "metadata JSON object").option("--allow-secrets", "allow content that looks like a secret after manual review", false).action(async (content, options) => {
2898
+ currentCommand = "remember";
2899
+ const g = await globals();
2900
+ exitCode = await runRuntimeRememberCommand({
2901
+ fs: createFs(g.root),
2902
+ output,
2903
+ json: g.json,
2904
+ config: g.config,
2905
+ content,
2906
+ stdin: options.stdin,
2907
+ file: options.file,
2908
+ stdinContent: input.stdinContent,
2909
+ kind: options.kind,
2910
+ title: options.title,
2911
+ tags: options.tag,
2912
+ confidence: options.confidence,
2913
+ source: options.source,
2914
+ actor: options.actor,
2915
+ metadata: options.metadataJson,
2916
+ allowSecrets: options.allowSecrets
2917
+ });
2918
+ });
2919
+ program.command("read").description("read a canonical memory document").argument("<target>", "core | notes | manifest").action(async (target) => {
2920
+ currentCommand = "read";
2921
+ const g = await globals();
2922
+ if (target !== "core" && target !== "notes" && target !== "manifest") {
2923
+ output.error("read target must be core, notes, or manifest");
2924
+ exitCode = 1;
2925
+ return;
2926
+ }
2927
+ exitCode = await runRuntimeReadCommand({
2928
+ fs: createFs(g.root),
2929
+ output,
2930
+ json: g.json,
2931
+ config: g.config,
2932
+ target
2933
+ });
2934
+ });
2935
+ program.command("events").description("read memory event log").option("-l, --limit <n>", "limit number of events", parseNonNegativeOption, 0).option("-s, --strict", "strict protocol validation", false).action(async (options) => {
2936
+ currentCommand = "events";
2937
+ const g = await globals();
2938
+ exitCode = await runEventsCommand({
2939
+ fs: createFs(g.root),
2940
+ output,
2941
+ json: g.json,
2942
+ limit: options.limit,
2943
+ strict: options.strict
2944
+ });
2945
+ });
2946
+ program.command("chunks").description("read local chunk index").option("-l, --limit <n>", "limit number of chunks", parseNonNegativeOption, 0).option("-s, --strict", "strict protocol validation", false).action(async (options) => {
2947
+ currentCommand = "chunks";
2948
+ const g = await globals();
2949
+ exitCode = await runChunksCommand({
2950
+ fs: createFs(g.root),
2951
+ output,
2952
+ json: g.json,
2953
+ limit: options.limit,
2954
+ strict: options.strict
2955
+ });
2956
+ });
2957
+ program.command("snapshot").description("create local memory snapshot bundle").option("-l, --label <name>", "snapshot label", "manual").action(async (options) => {
2958
+ currentCommand = "snapshot";
2959
+ const g = await globals();
2960
+ exitCode = await runRuntimeSnapshotCommand({
2961
+ fs: createFs(g.root),
2962
+ output,
2963
+ json: g.json,
2964
+ config: g.config,
2965
+ label: options.label
2966
+ });
2967
+ });
2968
+ program.command("doctor").description("find missing or corrupt memory files").option("-s, --strict", "strict protocol validation", false).action(async (options) => {
2969
+ currentCommand = "doctor";
2970
+ const g = await globals();
2971
+ exitCode = await runDoctorCommand({
2972
+ fs: createFs(g.root),
2973
+ output,
2974
+ json: g.json,
2975
+ strict: options.strict
2976
+ });
2977
+ });
2978
+ program.command("validate").description("strict protocol validation for CI").action(async () => {
2979
+ currentCommand = "validate";
2980
+ const g = await globals();
2981
+ exitCode = await runRuntimeValidateCommand({
2982
+ fs: createFs(g.root),
2983
+ output,
2984
+ json: g.json,
2985
+ config: g.config
2986
+ });
2987
+ });
2988
+ program.command("search").description("search memory files for a query").argument("<query>", "text to search for").option("-e, --regex", "treat query as a regular expression", false).action(async (query, options) => {
2989
+ currentCommand = "search";
2990
+ const g = await globals();
2991
+ exitCode = await runSearchCommand({
2992
+ fs: createFs(g.root),
2993
+ output,
2994
+ json: g.json,
2995
+ query,
2996
+ regex: options.regex
2997
+ });
2998
+ });
2999
+ program.command("edit").description("legacy alias: append a note or core memory text").argument("<type>", "note or core").argument("<message>", "content to append").option("--allow-secrets", "allow content that looks like a secret after manual review", false).action(async (type, message, options) => {
3000
+ currentCommand = "edit";
3001
+ const g = await globals();
3002
+ if (type !== "note" && type !== "core") {
3003
+ output.error("Edit type must be 'note' or 'core'");
3004
+ exitCode = 1;
3005
+ return;
3006
+ }
3007
+ exitCode = await runEditCommand({
3008
+ fs: createFs(g.root),
3009
+ output,
3010
+ json: g.json,
3011
+ type,
3012
+ message,
3013
+ allowSecrets: options.allowSecrets
3014
+ });
3015
+ });
3016
+ program.command("diff").description("compare two memory snapshots by ID or label").argument("<labelA>", "first snapshot ID or label").argument("<labelB>", "second snapshot ID or label").action(async (labelA, labelB) => {
3017
+ currentCommand = "diff";
3018
+ const g = await globals();
3019
+ exitCode = await runDiffCommand({
3020
+ fs: createFs(g.root),
3021
+ output,
3022
+ json: g.json,
3023
+ labelA,
3024
+ labelB
3025
+ });
3026
+ });
3027
+ const agent = program.command("agent").description("manage AgentFS-backed TekMemo coding sessions");
3028
+ agent.command("start").description("start an AgentFS-style workspace for Codex, Claude Code, or another coding agent").requiredOption("--task <task>", "agent task or brief").option("--project <id>", "project ID").option("--actor <id>", "actor ID, e.g. assistant:codex").option("--session <id>", "explicit safe session ID").action(async (options) => {
3029
+ currentCommand = "agent.start";
3030
+ const g = await globals();
3031
+ exitCode = await runAgentStartCommand({
3032
+ fs: createFs(g.root),
3033
+ output,
3034
+ json: g.json,
3035
+ task: options.task,
3036
+ projectId: options.project ?? g.config.cloud.projectId,
3037
+ actorId: options.actor,
3038
+ sessionId: options.session
3039
+ });
3040
+ });
3041
+ agent.command("paths").description("print paths for the latest or selected agent session").option("--session <id>", "session ID or latest", "latest").action(async (options) => {
3042
+ currentCommand = "agent.paths";
3043
+ const g = await globals();
3044
+ exitCode = await runAgentPathsCommand({
3045
+ fs: createFs(g.root),
3046
+ output,
3047
+ json: g.json,
3048
+ session: options.session
3049
+ });
3050
+ });
3051
+ agent.command("extract").description("extract summary, durable memory, and follow-ups from an agent session").option("--session <id>", "session ID or latest", "latest").action(async (options) => {
3052
+ currentCommand = "agent.extract";
3053
+ const g = await globals();
3054
+ exitCode = await runAgentExtractCommand({
3055
+ fs: createFs(g.root),
3056
+ output,
3057
+ json: g.json,
3058
+ session: options.session
3059
+ });
3060
+ });
3061
+ agent.command("complete").description("complete an agent session and optionally persist durable memory").option("--session <id>", "session ID or latest", "latest").option("--extract", "append output/durable-memory.md to TekMemo notes", false).option("--checkpoint-label <label>", "checkpoint label").action(async (options) => {
3062
+ currentCommand = "agent.complete";
3063
+ const g = await globals();
3064
+ exitCode = await runAgentCompleteCommand({
3065
+ fs: createFs(g.root),
3066
+ output,
3067
+ json: g.json,
3068
+ session: options.session,
3069
+ extract: options.extract,
3070
+ checkpointLabel: options.checkpointLabel
3071
+ });
3072
+ });
3073
+ const cloud = program.command("cloud").description("use TekMemo Cloud through @tekmemo/cloud-client").option("--cloud-url <url>", "TekMemo Cloud API URL; defaults to TEKMEMO_CLOUD_URL or TEKMEMO_API_URL").option("--api-key <key>", "TekMemo Cloud API key; defaults to TEKMEMO_API_KEY").option("--workspace-id <id>", "default cloud workspace ID; defaults to TEKMEMO_WORKSPACE_ID").option("--project-id <id>", "default cloud project ID; defaults to TEKMEMO_PROJECT_ID").option("--timeout-ms <n>", "cloud request timeout in milliseconds", parsePositiveOption);
3074
+ async function cloudGlobals() {
3075
+ const g = await globals();
3076
+ const opts = cloud.opts();
3077
+ return {
3078
+ ...g,
3079
+ cloudUrl: opts.cloudUrl ?? g.config.cloud.cloudUrl,
3080
+ apiKey: opts.apiKey ?? g.config.cloud.apiKey,
3081
+ workspaceId: opts.workspaceId ?? g.config.cloud.workspaceId,
3082
+ projectId: opts.projectId ?? g.config.cloud.projectId,
3083
+ timeoutMs: opts.timeoutMs ?? g.config.cloud.timeoutMs
3084
+ };
3085
+ }
3086
+ cloud.command("health").description("check TekMemo Cloud health").action(async () => {
3087
+ currentCommand = "cloud.health";
3088
+ const g = await cloudGlobals();
3089
+ exitCode = await runCloudHealthCommand({
3090
+ output,
3091
+ json: g.json,
3092
+ cloudUrl: g.cloudUrl,
3093
+ apiKey: g.apiKey,
3094
+ workspaceId: g.workspaceId,
3095
+ projectId: g.projectId,
3096
+ timeoutMs: g.timeoutMs
3097
+ });
3098
+ });
3099
+ cloud.command("context").description("pack cloud memory into an agent-friendly context block").requiredOption("-q, --query <query>", "task/query used to build context").option("-l, --limit <n>", "maximum recall items", parsePositiveOption).option("--max-bytes <n>", "maximum response bytes", parsePositiveOption).option("--include-core", "include core memory", true).option("--include-notes", "include notes memory", true).option("--include-recent", "include recent memory", true).action(async (options) => {
3100
+ currentCommand = "cloud.context";
3101
+ const g = await cloudGlobals();
3102
+ exitCode = await runCloudContextCommand({
3103
+ output,
3104
+ json: g.json,
3105
+ cloudUrl: g.cloudUrl,
3106
+ apiKey: g.apiKey,
3107
+ workspaceId: g.workspaceId,
3108
+ projectId: g.projectId,
3109
+ timeoutMs: g.timeoutMs,
3110
+ query: options.query,
3111
+ limit: options.limit,
3112
+ maxBytes: options.maxBytes,
3113
+ includeCore: options.includeCore,
3114
+ includeNotes: options.includeNotes,
3115
+ includeRecent: options.includeRecent
3116
+ });
3117
+ });
3118
+ cloud.command("recall").description("search TekMemo Cloud memory").argument("<query>", "text to search for").option("-l, --limit <n>", "maximum recall items", parsePositiveOption).option("--strategy <strategy>", "local | vector | hybrid").option("--fallback <mode>", "none | local").option("--rerank", "request reranking", false).action(async (query, options) => {
3119
+ currentCommand = "cloud.recall";
3120
+ const g = await cloudGlobals();
3121
+ exitCode = await runCloudRecallCommand({
3122
+ output,
3123
+ json: g.json,
3124
+ cloudUrl: g.cloudUrl,
3125
+ apiKey: g.apiKey,
3126
+ workspaceId: g.workspaceId,
3127
+ projectId: g.projectId,
3128
+ timeoutMs: g.timeoutMs,
3129
+ query,
3130
+ limit: options.limit,
3131
+ strategy: options.strategy,
3132
+ fallback: options.fallback,
3133
+ rerank: options.rerank
3134
+ });
3135
+ });
3136
+ cloud.command("index").description("request TekMemo Cloud recall indexing for the project").option("--mode <mode>", "all | changed | core | notes", "changed").option("--force", "force re-indexing", false).action(async (options) => {
3137
+ currentCommand = "cloud.index";
3138
+ const g = await cloudGlobals();
3139
+ exitCode = await runCloudRecallIndexCommand({
3140
+ output,
3141
+ json: g.json,
3142
+ cloudUrl: g.cloudUrl,
3143
+ apiKey: g.apiKey,
3144
+ workspaceId: g.workspaceId,
3145
+ projectId: g.projectId,
3146
+ timeoutMs: g.timeoutMs,
3147
+ mode: options.mode,
3148
+ force: options.force
3149
+ });
3150
+ });
3151
+ cloud.command("remember").description("store durable memory in TekMemo Cloud").argument("[content]", "memory content").option("--stdin", "read memory content from stdin", false).option("--file <path>", "read memory content from a file inside the selected root").option("-k, --kind <kind>", "decision | constraint | goal | preference | reference | summary | note", "note").option("--title <title>", "optional note title").option("-t, --tag <tag>", "tag to attach; repeatable", collect, []).option("--confidence <n>", "confidence from 0 to 1").option("--source <source>", "source identifier, file, URL, or agent name").option("--metadata-json <json>", "metadata JSON object").option("--allow-secrets", "allow content that looks like a secret after manual review", false).action(async (content, options) => {
3152
+ currentCommand = "cloud.remember";
3153
+ const g = await cloudGlobals();
3154
+ exitCode = await runCloudRememberCommand({
3155
+ output,
3156
+ json: g.json,
3157
+ rootDir: g.root,
3158
+ stdinContent: input.stdinContent,
3159
+ cloudUrl: g.cloudUrl,
3160
+ apiKey: g.apiKey,
3161
+ workspaceId: g.workspaceId,
3162
+ projectId: g.projectId,
3163
+ timeoutMs: g.timeoutMs,
3164
+ content,
3165
+ stdin: options.stdin,
3166
+ file: options.file,
3167
+ kind: options.kind,
3168
+ title: options.title,
3169
+ tags: options.tag,
3170
+ confidence: options.confidence,
3171
+ source: options.source,
3172
+ metadata: options.metadataJson,
3173
+ allowSecrets: options.allowSecrets
3174
+ });
3175
+ });
3176
+ cloud.command("read").description("read a TekMemo Cloud memory document").argument("<target>", "core | notes").option("-l, --limit <n>", "maximum notes when target is notes", parsePositiveOption).action(async (target, options) => {
3177
+ currentCommand = "cloud.read";
3178
+ const g = await cloudGlobals();
3179
+ if (target !== "core" && target !== "notes") {
3180
+ output.error("cloud read target must be core or notes");
3181
+ exitCode = 1;
3182
+ return;
3183
+ }
3184
+ exitCode = await runCloudReadCommand({
3185
+ output,
3186
+ json: g.json,
3187
+ cloudUrl: g.cloudUrl,
3188
+ apiKey: g.apiKey,
3189
+ workspaceId: g.workspaceId,
3190
+ projectId: g.projectId,
3191
+ timeoutMs: g.timeoutMs,
3192
+ target,
3193
+ limit: options.limit
3194
+ });
3195
+ });
3196
+ cloud.command("update-core").description("replace TekMemo Cloud core memory").argument("[content]", "new core memory content").option("--stdin", "read core memory from stdin", false).option("--file <path>", "read core memory from a file inside the selected root").option("--allow-secrets", "allow content that looks like a secret after manual review", false).action(async (content, options) => {
3197
+ currentCommand = "cloud.update-core";
3198
+ const g = await cloudGlobals();
3199
+ exitCode = await runCloudUpdateCoreCommand({
3200
+ output,
3201
+ json: g.json,
3202
+ rootDir: g.root,
3203
+ stdinContent: input.stdinContent,
3204
+ cloudUrl: g.cloudUrl,
3205
+ apiKey: g.apiKey,
3206
+ workspaceId: g.workspaceId,
3207
+ projectId: g.projectId,
3208
+ timeoutMs: g.timeoutMs,
3209
+ content,
3210
+ stdin: options.stdin,
3211
+ file: options.file,
3212
+ allowSecrets: options.allowSecrets
3213
+ });
3214
+ });
3215
+ cloud.command("recent").description("list recent TekMemo Cloud memory events").option("-l, --limit <n>", "maximum recent items", parsePositiveOption).action(async (options) => {
3216
+ currentCommand = "cloud.recent";
3217
+ const g = await cloudGlobals();
3218
+ exitCode = await runCloudRecentCommand({
3219
+ output,
3220
+ json: g.json,
3221
+ cloudUrl: g.cloudUrl,
3222
+ apiKey: g.apiKey,
3223
+ workspaceId: g.workspaceId,
3224
+ projectId: g.projectId,
3225
+ timeoutMs: g.timeoutMs,
3226
+ limit: options.limit
3227
+ });
3228
+ });
3229
+ cloud.command("validate").description("validate TekMemo Cloud memory").option("-s, --strict", "strict protocol validation", false).action(async (options) => {
3230
+ currentCommand = "cloud.validate";
3231
+ const g = await cloudGlobals();
3232
+ exitCode = await runCloudValidateCommand({
3233
+ output,
3234
+ json: g.json,
3235
+ cloudUrl: g.cloudUrl,
3236
+ apiKey: g.apiKey,
3237
+ workspaceId: g.workspaceId,
3238
+ projectId: g.projectId,
3239
+ timeoutMs: g.timeoutMs,
3240
+ strict: options.strict
3241
+ });
3242
+ });
3243
+ cloud.command("snapshot").description("explain TekMemo Cloud snapshot availability").option("-l, --label <name>", "snapshot label", "manual").option("--type <type>", "manual | automatic | pre-sync | pre-restore", "manual").action(async (options) => {
3244
+ currentCommand = "cloud.snapshot";
3245
+ const g = await cloudGlobals();
3246
+ exitCode = await runCloudSnapshotCommand({
3247
+ output,
3248
+ json: g.json,
3249
+ cloudUrl: g.cloudUrl,
3250
+ apiKey: g.apiKey,
3251
+ workspaceId: g.workspaceId,
3252
+ projectId: g.projectId,
3253
+ timeoutMs: g.timeoutMs,
3254
+ label: options.label,
3255
+ type: options.type
3256
+ });
3257
+ });
3258
+ const sync = cloud.command("sync").description("use TekMemo Cloud memory sync APIs");
3259
+ sync.command("status").description("read cloud sync status").option("--client-id <id>", "optional sync client ID").action(async (options) => {
3260
+ currentCommand = "cloud.sync.status";
3261
+ const g = await cloudGlobals();
3262
+ exitCode = await runCloudSyncStatusCommand({
3263
+ output,
3264
+ json: g.json,
3265
+ cloudUrl: g.cloudUrl,
3266
+ apiKey: g.apiKey,
3267
+ workspaceId: g.workspaceId,
3268
+ projectId: g.projectId,
3269
+ timeoutMs: g.timeoutMs,
3270
+ clientId: options.clientId
3271
+ });
3272
+ });
3273
+ sync.command("pull").description("pull cloud sync events").requiredOption("--client-id <id>", "sync client ID").option("--since-server-version <n>", "pull events after this server version", parseNonNegativeOption).option("-l, --limit <n>", "maximum events to return", parsePositiveOption).action(async (options) => {
3274
+ currentCommand = "cloud.sync.pull";
3275
+ const g = await cloudGlobals();
3276
+ exitCode = await runCloudSyncPullCommand({
3277
+ output,
3278
+ json: g.json,
3279
+ cloudUrl: g.cloudUrl,
3280
+ apiKey: g.apiKey,
3281
+ workspaceId: g.workspaceId,
3282
+ projectId: g.projectId,
3283
+ timeoutMs: g.timeoutMs,
3284
+ clientId: options.clientId,
3285
+ sinceServerVersion: options.sinceServerVersion,
3286
+ limit: options.limit
3287
+ });
3288
+ });
3289
+ sync.command("push").description("push local sync events to TekMemo Cloud").requiredOption("--client-id <id>", "sync client ID").option("--events-json <json>", "event array or object with events array").option("--checkpoint-json <json>", "optional checkpoint JSON object").option("--stdin", "read events JSON from stdin", false).option("--file <path>", "read events JSON from a file inside the selected root").action(async (options) => {
3290
+ currentCommand = "cloud.sync.push";
3291
+ const g = await cloudGlobals();
3292
+ exitCode = await runCloudSyncPushCommand({
3293
+ output,
3294
+ json: g.json,
3295
+ rootDir: g.root,
3296
+ stdinContent: input.stdinContent,
3297
+ cloudUrl: g.cloudUrl,
3298
+ apiKey: g.apiKey,
3299
+ workspaceId: g.workspaceId,
3300
+ projectId: g.projectId,
3301
+ timeoutMs: g.timeoutMs,
3302
+ clientId: options.clientId,
3303
+ eventsJson: options.eventsJson,
3304
+ checkpointJson: options.checkpointJson,
3305
+ stdin: options.stdin,
3306
+ file: options.file
3307
+ });
3308
+ });
3309
+ sync.command("resolve").description("resolve a cloud sync conflict").argument("<conflictId>", "conflict ID").requiredOption("--resolution <resolution>", "keep_cloud | use_client | ignore").option("--content-json <json>", "optional resolution content JSON object").action(async (conflictId, options) => {
3310
+ currentCommand = "cloud.sync.resolve";
3311
+ const g = await cloudGlobals();
3312
+ exitCode = await runCloudSyncResolveCommand({
3313
+ output,
3314
+ json: g.json,
3315
+ cloudUrl: g.cloudUrl,
3316
+ apiKey: g.apiKey,
3317
+ workspaceId: g.workspaceId,
3318
+ projectId: g.projectId,
3319
+ timeoutMs: g.timeoutMs,
3320
+ conflictId,
3321
+ resolution: options.resolution,
3322
+ contentJson: options.contentJson
3323
+ });
3324
+ });
3325
+ cloud.command("readiness").description("check TekMemo Cloud readiness").action(async () => {
3326
+ currentCommand = "cloud.readiness";
3327
+ const g = await cloudGlobals();
3328
+ exitCode = await runCloudReadinessCommand({
3329
+ output,
3330
+ json: g.json,
3331
+ cloudUrl: g.cloudUrl,
3332
+ apiKey: g.apiKey,
3333
+ workspaceId: g.workspaceId,
3334
+ projectId: g.projectId,
3335
+ timeoutMs: g.timeoutMs
3336
+ });
3337
+ });
3338
+ cloud.command("context-compose").description("compose full context package from cloud").requiredOption("-q, --query <query>", "task/query used to build context").option("-l, --limit <n>", "maximum recall items", parsePositiveOption).option("--strategy <strategy>", "auto | vector | local").option("--rerank", "request reranking", false).option("--include-core-memory", "include core memory", true).option("--include-recall-results", "include recall results", true).option("--include-graph-context", "include graph context", true).action(async (options) => {
3339
+ currentCommand = "cloud.context-compose";
3340
+ const g = await cloudGlobals();
3341
+ exitCode = await runCloudContextComposeCommand({
3342
+ output,
3343
+ json: g.json,
3344
+ cloudUrl: g.cloudUrl,
3345
+ apiKey: g.apiKey,
3346
+ workspaceId: g.workspaceId,
3347
+ projectId: g.projectId,
3348
+ timeoutMs: g.timeoutMs,
3349
+ query: options.query,
3350
+ topK: options.limit,
3351
+ strategy: options.strategy,
3352
+ rerank: options.rerank,
3353
+ includeCoreMemory: options.includeCoreMemory,
3354
+ includeRecallResults: options.includeRecallResults,
3355
+ includeGraphContext: options.includeGraphContext
3356
+ });
3357
+ });
3358
+ const graph = cloud.command("graph").description("graph memory operations");
3359
+ graph.command("list-nodes").description("list graph nodes").option("-l, --limit <n>", "maximum nodes to return", parsePositiveOption).option("--cursor <string>", "pagination cursor").option("--status <status>", "active | deprecated | conflicted | deleted").action(async (options) => {
3360
+ currentCommand = "cloud.graph.list-nodes";
3361
+ const g = await cloudGlobals();
3362
+ exitCode = await runCloudGraphListNodesCommand({
3363
+ output,
3364
+ json: g.json,
3365
+ cloudUrl: g.cloudUrl,
3366
+ apiKey: g.apiKey,
3367
+ workspaceId: g.workspaceId,
3368
+ projectId: g.projectId,
3369
+ timeoutMs: g.timeoutMs,
3370
+ limit: options.limit,
3371
+ cursor: options.cursor,
3372
+ status: options.status
3373
+ });
3374
+ });
3375
+ graph.command("create-node").description("create a graph node").requiredOption("--node-id <id>", "node ID").requiredOption("--type <type>", "node type").requiredOption("--label <label>", "node label").option("--summary <summary>", "node summary").option("--aliases <aliases>", "comma-separated aliases").option("--metadata-json <json>", "metadata JSON object").action(async (options) => {
3376
+ currentCommand = "cloud.graph.create-node";
3377
+ const g = await cloudGlobals();
3378
+ const aliases = options.aliases ? options.aliases.split(",").map((s) => s.trim()) : void 0;
3379
+ exitCode = await runCloudGraphCreateNodeCommand({
3380
+ output,
3381
+ json: g.json,
3382
+ cloudUrl: g.cloudUrl,
3383
+ apiKey: g.apiKey,
3384
+ workspaceId: g.workspaceId,
3385
+ projectId: g.projectId,
3386
+ timeoutMs: g.timeoutMs,
3387
+ nodeId: options.nodeId,
3388
+ type: options.type,
3389
+ label: options.label,
3390
+ summary: options.summary,
3391
+ aliases,
3392
+ metadataJson: options.metadataJson
3393
+ });
3394
+ });
3395
+ graph.command("list-edges").description("list graph edges").option("-l, --limit <n>", "maximum edges to return", parsePositiveOption).option("--cursor <string>", "pagination cursor").option("--status <status>", "active | deprecated | conflicted | deleted").action(async (options) => {
3396
+ currentCommand = "cloud.graph.list-edges";
3397
+ const g = await cloudGlobals();
3398
+ exitCode = await runCloudGraphListEdgesCommand({
3399
+ output,
3400
+ json: g.json,
3401
+ cloudUrl: g.cloudUrl,
3402
+ apiKey: g.apiKey,
3403
+ workspaceId: g.workspaceId,
3404
+ projectId: g.projectId,
3405
+ timeoutMs: g.timeoutMs,
3406
+ limit: options.limit,
3407
+ cursor: options.cursor,
3408
+ status: options.status
3409
+ });
3410
+ });
3411
+ graph.command("create-edge").description("create a graph edge").option("--edge-id <id>", "edge ID").requiredOption("--from <id>", "from node ID").requiredOption("--to <id>", "to node ID").requiredOption("--type <type>", "edge type").option("--directed", "directed edge", true).option("--weight <n>", "edge weight (0-1)", parsePositiveOption).option("--metadata-json <json>", "metadata JSON object").action(async (options) => {
3412
+ currentCommand = "cloud.graph.create-edge";
3413
+ const g = await cloudGlobals();
3414
+ exitCode = await runCloudGraphCreateEdgeCommand({
3415
+ output,
3416
+ json: g.json,
3417
+ cloudUrl: g.cloudUrl,
3418
+ apiKey: g.apiKey,
3419
+ workspaceId: g.workspaceId,
3420
+ projectId: g.projectId,
3421
+ timeoutMs: g.timeoutMs,
3422
+ edgeId: options.edgeId,
3423
+ fromNodeId: options.from,
3424
+ toNodeId: options.to,
3425
+ type: options.type,
3426
+ directed: options.directed,
3427
+ weight: options.weight,
3428
+ metadataJson: options.metadataJson
3429
+ });
3430
+ });
3431
+ graph.command("neighbors").description("find graph neighbors").requiredOption("--node-id <id>", "seed node ID").option("--direction <dir>", "in | out | both").option("--depth <n>", "search depth", parsePositiveOption).option("-l, --limit <n>", "maximum results", parsePositiveOption).action(async (options) => {
3432
+ currentCommand = "cloud.graph.neighbors";
3433
+ const g = await cloudGlobals();
3434
+ exitCode = await runCloudGraphNeighborsCommand({
3435
+ output,
3436
+ json: g.json,
3437
+ cloudUrl: g.cloudUrl,
3438
+ apiKey: g.apiKey,
3439
+ workspaceId: g.workspaceId,
3440
+ projectId: g.projectId,
3441
+ timeoutMs: g.timeoutMs,
3442
+ nodeId: options.nodeId,
3443
+ direction: options.direction,
3444
+ depth: options.depth,
3445
+ limit: options.limit
3446
+ });
3447
+ });
3448
+ graph.command("path").description("find graph path between nodes").requiredOption("--from <id>", "start node ID").requiredOption("--to <id>", "target node ID").option("--max-depth <n>", "maximum search depth", parsePositiveOption).action(async (options) => {
3449
+ currentCommand = "cloud.graph.path";
3450
+ const g = await cloudGlobals();
3451
+ exitCode = await runCloudGraphPathCommand({
3452
+ output,
3453
+ json: g.json,
3454
+ cloudUrl: g.cloudUrl,
3455
+ apiKey: g.apiKey,
3456
+ workspaceId: g.workspaceId,
3457
+ projectId: g.projectId,
3458
+ timeoutMs: g.timeoutMs,
3459
+ fromNodeId: options.from,
3460
+ toNodeId: options.to,
3461
+ maxDepth: options.maxDepth
3462
+ });
3463
+ });
3464
+ const extraction = cloud.command("extraction").description("extraction operations");
3465
+ extraction.command("run").description("run graph extraction").option("--mode <mode>", "full | core | notes | sync | connectors").option("--force", "force re-extraction", false).action(async (options) => {
3466
+ currentCommand = "cloud.extraction.run";
3467
+ const g = await cloudGlobals();
3468
+ exitCode = await runCloudExtractionRunCommand({
3469
+ output,
3470
+ json: g.json,
3471
+ cloudUrl: g.cloudUrl,
3472
+ apiKey: g.apiKey,
3473
+ workspaceId: g.workspaceId,
3474
+ projectId: g.projectId,
3475
+ timeoutMs: g.timeoutMs,
3476
+ mode: options.mode,
3477
+ force: options.force
3478
+ });
3479
+ });
3480
+ extraction.command("jobs").description("list extraction jobs").option("-l, --limit <n>", "maximum jobs to return", parsePositiveOption).action(async (options) => {
3481
+ currentCommand = "cloud.extraction.jobs";
3482
+ const g = await cloudGlobals();
3483
+ exitCode = await runCloudExtractionJobsCommand({
3484
+ output,
3485
+ json: g.json,
3486
+ cloudUrl: g.cloudUrl,
3487
+ apiKey: g.apiKey,
3488
+ workspaceId: g.workspaceId,
3489
+ projectId: g.projectId,
3490
+ timeoutMs: g.timeoutMs,
3491
+ limit: options.limit
3492
+ });
3493
+ });
3494
+ cloud.command("evals").description("run context quality evals").option("--fixture-ids <ids>", "comma-separated fixture IDs").option("--iterations <n>", "number of iterations", parsePositiveOption).option("--thresholds-json <json>", "thresholds JSON object").action(async (options) => {
3495
+ currentCommand = "cloud.evals.run";
3496
+ const g = await cloudGlobals();
3497
+ exitCode = await runCloudEvalsRunCommand({
3498
+ output,
3499
+ json: g.json,
3500
+ cloudUrl: g.cloudUrl,
3501
+ apiKey: g.apiKey,
3502
+ workspaceId: g.workspaceId,
3503
+ projectId: g.projectId,
3504
+ timeoutMs: g.timeoutMs,
3505
+ fixtureIds: options.fixtureIds,
3506
+ iterations: options.iterations,
3507
+ thresholdsJson: options.thresholdsJson
3508
+ });
3509
+ });
3510
+ cloud.command("benchmarks").description("run context benchmarks").option("--fixture-ids <ids>", "comma-separated fixture IDs").option("--iterations <n>", "number of iterations", parsePositiveOption).option("--thresholds-json <json>", "thresholds JSON object").action(async (options) => {
3511
+ currentCommand = "cloud.benchmarks.run";
3512
+ const g = await cloudGlobals();
3513
+ exitCode = await runCloudBenchmarksRunCommand({
3514
+ output,
3515
+ json: g.json,
3516
+ cloudUrl: g.cloudUrl,
3517
+ apiKey: g.apiKey,
3518
+ workspaceId: g.workspaceId,
3519
+ projectId: g.projectId,
3520
+ timeoutMs: g.timeoutMs,
3521
+ fixtureIds: options.fixtureIds,
3522
+ iterations: options.iterations,
3523
+ thresholdsJson: options.thresholdsJson
3524
+ });
3525
+ });
3526
+ const exports = cloud.command("exports").description("export operations");
3527
+ exports.command("create").description("create memory export").option("--label <name>", "export label").action(async (options) => {
3528
+ currentCommand = "cloud.exports.create";
3529
+ const g = await cloudGlobals();
3530
+ exitCode = await runCloudExportsCreateCommand({
3531
+ output,
3532
+ json: g.json,
3533
+ cloudUrl: g.cloudUrl,
3534
+ apiKey: g.apiKey,
3535
+ workspaceId: g.workspaceId,
3536
+ projectId: g.projectId,
3537
+ timeoutMs: g.timeoutMs,
3538
+ label: options.label
3539
+ });
3540
+ });
3541
+ exports.command("download").description("download export archive").requiredOption("--export-id <id>", "export ID").action(async (options) => {
3542
+ currentCommand = "cloud.exports.download";
3543
+ const g = await cloudGlobals();
3544
+ exitCode = await runCloudExportsDownloadCommand({
3545
+ output,
3546
+ json: g.json,
3547
+ cloudUrl: g.cloudUrl,
3548
+ apiKey: g.apiKey,
3549
+ workspaceId: g.workspaceId,
3550
+ projectId: g.projectId,
3551
+ timeoutMs: g.timeoutMs,
3552
+ exportId: options.exportId
3553
+ });
3554
+ });
3555
+ const snapshots = cloud.command("snapshots").description("snapshot operations");
3556
+ snapshots.command("create").description("create memory snapshot").option("--label <name>", "snapshot label").option("--trigger <trigger>", "manual | sync | system").action(async (options) => {
3557
+ currentCommand = "cloud.snapshots.create";
3558
+ const g = await cloudGlobals();
3559
+ exitCode = await runCloudSnapshotsCreateCommand({
3560
+ output,
3561
+ json: g.json,
3562
+ cloudUrl: g.cloudUrl,
3563
+ apiKey: g.apiKey,
3564
+ workspaceId: g.workspaceId,
3565
+ projectId: g.projectId,
3566
+ timeoutMs: g.timeoutMs,
3567
+ label: options.label,
3568
+ trigger: options.trigger
3569
+ });
3570
+ });
3571
+ snapshots.command("download").description("download snapshot archive").requiredOption("--snapshot-id <id>", "snapshot ID").action(async (options) => {
3572
+ currentCommand = "cloud.snapshots.download";
3573
+ const g = await cloudGlobals();
3574
+ exitCode = await runCloudSnapshotsDownloadCommand({
3575
+ output,
3576
+ json: g.json,
3577
+ cloudUrl: g.cloudUrl,
3578
+ apiKey: g.apiKey,
3579
+ workspaceId: g.workspaceId,
3580
+ projectId: g.projectId,
3581
+ timeoutMs: g.timeoutMs,
3582
+ snapshotId: options.snapshotId
3583
+ });
3584
+ });
3585
+ const providers = cloud.command("providers").description("provider operations");
3586
+ providers.command("list").description("list provider credentials").action(async () => {
3587
+ currentCommand = "cloud.providers.list";
3588
+ const g = await cloudGlobals();
3589
+ exitCode = await runCloudProvidersListCommand({
3590
+ output,
3591
+ json: g.json,
3592
+ cloudUrl: g.cloudUrl,
3593
+ apiKey: g.apiKey,
3594
+ workspaceId: g.workspaceId,
3595
+ projectId: g.projectId,
3596
+ timeoutMs: g.timeoutMs
3597
+ });
3598
+ });
3599
+ providers.command("create").description("create provider credential").requiredOption("--provider <provider>", "voyageai | openai | upstash-vector").requiredOption("--key-name <name>", "key name").requiredOption("--secret <secret>", "provider secret").option("--rest-url <url>", "REST URL (required for upstash-vector)").option("--embedding-model <model>", "embedding model").option("--rerank-model <model>", "rerank model").action(async (options) => {
3600
+ currentCommand = "cloud.providers.create";
3601
+ const g = await cloudGlobals();
3602
+ exitCode = await runCloudProvidersCreateCommand({
3603
+ output,
3604
+ json: g.json,
3605
+ cloudUrl: g.cloudUrl,
3606
+ apiKey: g.apiKey,
3607
+ workspaceId: g.workspaceId,
3608
+ projectId: g.projectId,
3609
+ timeoutMs: g.timeoutMs,
3610
+ provider: options.provider,
3611
+ keyName: options.keyName,
3612
+ secret: options.secret,
3613
+ restUrl: options.restUrl,
3614
+ embeddingModel: options.embeddingModel,
3615
+ rerankModel: options.rerankModel
3616
+ });
3617
+ });
3618
+ providers.command("test").description("test provider credential").requiredOption("--credential-id <id>", "credential ID").action(async (options) => {
3619
+ currentCommand = "cloud.providers.test";
3620
+ const g = await cloudGlobals();
3621
+ exitCode = await runCloudProvidersTestCommand({
3622
+ output,
3623
+ json: g.json,
3624
+ cloudUrl: g.cloudUrl,
3625
+ apiKey: g.apiKey,
3626
+ workspaceId: g.workspaceId,
3627
+ projectId: g.projectId,
3628
+ timeoutMs: g.timeoutMs,
3629
+ credentialId: options.credentialId
3630
+ });
3631
+ });
3632
+ const config = program.command("config").description("inspect or create .tekmemo/config.json");
3633
+ config.command("get").description("print resolved CLI configuration").action(async () => {
3634
+ currentCommand = "config.get";
3635
+ const g = await globals();
3636
+ const safeConfig = {
3637
+ ...g.config,
3638
+ cloud: {
3639
+ ...g.config.cloud,
3640
+ apiKey: g.config.cloud.apiKey ? "<redacted>" : void 0
3641
+ }
3642
+ };
3643
+ if (g.json) printJsonEnvelope(output, "config.get", safeConfig);
3644
+ else output.write(JSON.stringify(safeConfig, null, 2));
3645
+ });
3646
+ config.command("init").description("create .tekmemo/config.json without storing secrets").option("-f, --force", "overwrite existing config", false).option("--runtime <mode>", "runtime mode: local, cloud, or hybrid", "local").option("--cloud-url <url>", "TekMemo Cloud API URL").option("--workspace-id <id>", "cloud workspace ID").option("--project-id <id>", "cloud project ID").option("--read-policy <policy>", "hybrid read policy", "local-first").option("--write-policy <policy>", "hybrid write policy", "local-first").action(async (options) => {
3647
+ currentCommand = "config.init";
3648
+ const g = await globals();
3649
+ const result = await writeDefaultCliConfig({
3650
+ cwd: input.cwd ?? process.cwd(),
3651
+ root: g.root,
3652
+ force: options.force,
3653
+ config: {
3654
+ version: 1,
3655
+ runtime: options.runtime,
3656
+ root: ".",
3657
+ cloud: {
3658
+ ...options.cloudUrl ? { baseUrl: options.cloudUrl } : {},
3659
+ ...options.workspaceId ? { workspaceId: options.workspaceId } : {},
3660
+ ...options.projectId ? { projectId: options.projectId } : {}
3661
+ },
3662
+ hybrid: {
3663
+ readPolicy: options.readPolicy,
3664
+ writePolicy: options.writePolicy
3665
+ }
3666
+ }
3667
+ });
3668
+ if (g.json) printJsonEnvelope(output, "config.init", result);
3669
+ else if (result.created) output.success(`Created ${result.path}`);
3670
+ else if (result.overwritten) output.success(`Overwrote ${result.path}`);
3671
+ else output.warn(`${result.path} already exists. Use --force to overwrite.`);
3672
+ });
3673
+ try {
3674
+ const args = normalizeArgv(input.argv);
3675
+ await program.parseAsync(args);
3676
+ return {
3677
+ exitCode,
3678
+ stdout: output.stdout,
3679
+ stderr: output.stderr
3680
+ };
3681
+ } catch (error) {
3682
+ if (error instanceof CliError) {
3683
+ exitCode = error.exitCode;
3684
+ if (wantsJson) printJsonError(output, currentCommand, error.code, error.message);
3685
+ else output.error(error.message);
3686
+ } else if (isCommanderError(error)) exitCode = typeof error.exitCode === "number" ? error.exitCode : 1;
3687
+ else {
3688
+ exitCode = 1;
3689
+ const message = error instanceof Error ? error.message : String(error);
3690
+ if (wantsJson) printJsonError(output, currentCommand, "CLI_UNEXPECTED_ERROR", message);
3691
+ else output.error(message);
3692
+ }
3693
+ return {
3694
+ exitCode,
3695
+ stdout: output.stdout,
3696
+ stderr: output.stderr
3697
+ };
3698
+ }
3699
+ }
3700
+ function collect(value, previous) {
3701
+ previous.push(value);
3702
+ return previous;
3703
+ }
3704
+ function normalizeArgv(argv) {
3705
+ if (argv.length > 0 && !argv[0]?.endsWith("node") && !argv[0]?.includes("/") && argv[0] !== "tekmemo") return [
3706
+ "node",
3707
+ "tekmemo",
3708
+ ...argv
3709
+ ];
3710
+ if (argv[0] === "tekmemo") return ["node", ...argv];
3711
+ return [...argv];
3712
+ }
3713
+ function isCommanderError(error) {
3714
+ return error instanceof CommanderError;
3715
+ }
3716
+
3717
+ //#endregion
3718
+ export { CliJsonlError as A, cloudConnectionSummary as C, toCloudClientOptions as D, normalizeCloudConnectionOptions as E, CliUsageError as M, CliValidationError as N, CliError as O, writeDefaultCliConfig as S, formatCloudError as T, printHumanOrJson as _, scanForSecrets as a, TekMemoFileSystem as b, parseManifest$1 as c, stringifyJsonl as d, REQUIRED_DIRS as f, createBufferedOutput as g, TEKMEMO_PATHS as h, redactSecretPreview as i, CliProtocolError as j, CliFsError as k, validateManifest as l, TEKMEMO_DIR as m, createSafeIdFromLabel as n, inspectTekMemo as o, REQUIRED_FILES as p, validateSnapshotLabel as r, createDefaultManifest as s, runTekMemoCli as t, parseJsonl as u, printJsonEnvelope as v, createCliCloudClient as w, resolveCliRuntimeConfig as x, printJsonError as y };
3719
+ //# sourceMappingURL=runner-Bz3RPzFc.mjs.map