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