@todoforai/tfa-memory 0.4.1 → 0.4.3
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/dist/cli.js +203 -123
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
6
6
|
import { parseArgs } from "node:util";
|
|
7
7
|
import { readFileSync as readFileSync4 } from "node:fs";
|
|
8
8
|
|
|
9
|
-
//
|
|
9
|
+
// ../tfa-subagent/dist/auth.js
|
|
10
10
|
import { readFileSync, writeFileSync, mkdirSync, chmodSync } from "node:fs";
|
|
11
11
|
import { join, dirname } from "node:path";
|
|
12
12
|
import { homedir, platform } from "node:os";
|
|
@@ -21,10 +21,15 @@ function normalizeApiUrl(url) {
|
|
|
21
21
|
function credentialsPath() {
|
|
22
22
|
return platform() === "win32" ? join(homedir(), "AppData", "Roaming", "todoforai", "credentials.json") : platform() === "darwin" ? join(homedir(), "Library", "Application Support", "todoforai", "credentials.json") : join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "todoforai", "credentials.json");
|
|
23
23
|
}
|
|
24
|
-
function
|
|
25
|
-
|
|
24
|
+
function read() {
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(readFileSync(credentialsPath(), "utf-8"));
|
|
27
|
+
} catch {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
26
30
|
}
|
|
27
|
-
function writeSecure(
|
|
31
|
+
function writeSecure(data) {
|
|
32
|
+
const p = credentialsPath();
|
|
28
33
|
mkdirSync(dirname(p), { recursive: true });
|
|
29
34
|
writeFileSync(p, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
30
35
|
try {
|
|
@@ -32,31 +37,29 @@ function writeSecure(p, data) {
|
|
|
32
37
|
} catch {}
|
|
33
38
|
}
|
|
34
39
|
function readStoredAuth(apiUrl) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
return { apiToken: "" };
|
|
40
|
+
const c = read();
|
|
41
|
+
const host = c.backendHost || "";
|
|
42
|
+
const storedApiUrl = typeof c.apiUrl === "string" && c.apiUrl ? c.apiUrl : host ? `${host === "localhost" || host === "127.0.0.1" ? "http" : "https"}://${host}` : undefined;
|
|
43
|
+
const dstIsForThisUrl = !storedApiUrl || normalizeApiUrl(storedApiUrl) === normalizeApiUrl(apiUrl);
|
|
44
|
+
const isDst = typeof c.apiToken === "string" && c.apiToken.startsWith("dst_");
|
|
45
|
+
const dst = dstIsForThisUrl && isDst ? c.apiToken : "";
|
|
46
|
+
const fallbackApiToken = isDst && !dstIsForThisUrl ? "" : c.apiToken;
|
|
47
|
+
const apiToken = dst || c[apiUrl] || fallbackApiToken || c.apiKey || "";
|
|
48
|
+
return {
|
|
49
|
+
apiToken: typeof apiToken === "string" ? apiToken : "",
|
|
50
|
+
apiUrl: storedApiUrl,
|
|
51
|
+
deviceId: typeof c.deviceId === "string" ? c.deviceId : undefined,
|
|
52
|
+
deviceSecret: typeof c.deviceSecret === "string" ? c.deviceSecret : undefined
|
|
53
|
+
};
|
|
50
54
|
}
|
|
51
55
|
function persistApiToken(token) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
56
|
+
const c = read();
|
|
57
|
+
if (!Object.keys(c).length)
|
|
58
|
+
return;
|
|
59
|
+
c.apiToken = token;
|
|
60
|
+
try {
|
|
61
|
+
writeSecure(c);
|
|
62
|
+
} catch {}
|
|
60
63
|
}
|
|
61
64
|
async function refreshDeviceToken(opts = {}) {
|
|
62
65
|
const envUrl = process.env.TODOFORAI_API_URL || process.env.API_URL;
|
|
@@ -65,6 +68,8 @@ async function refreshDeviceToken(opts = {}) {
|
|
|
65
68
|
const apiUrl = normalizeApiUrl(opts.apiUrl || envUrl || stored.apiUrl || DEFAULT_API_URL);
|
|
66
69
|
if (!stored.deviceId || !stored.deviceSecret)
|
|
67
70
|
return "";
|
|
71
|
+
if (stored.apiUrl && normalizeApiUrl(stored.apiUrl) !== apiUrl)
|
|
72
|
+
return "";
|
|
68
73
|
try {
|
|
69
74
|
const res = await fetch(`${apiUrl}/api/v1/cli/device/token`, {
|
|
70
75
|
method: "POST",
|
|
@@ -85,9 +90,13 @@ async function refreshDeviceToken(opts = {}) {
|
|
|
85
90
|
async function fetchWithRenew(url, init, opts) {
|
|
86
91
|
const header = opts.header ?? "x-api-key";
|
|
87
92
|
const renewOn = opts.renewOn ?? [401];
|
|
88
|
-
const send = (token) =>
|
|
89
|
-
|
|
90
|
-
|
|
93
|
+
const send = (token) => {
|
|
94
|
+
const headers = new Headers(init.headers);
|
|
95
|
+
headers.set(header, token);
|
|
96
|
+
return fetch(url, { ...init, headers });
|
|
97
|
+
};
|
|
98
|
+
const res = await send(opts.token);
|
|
99
|
+
if (renewOn.includes(res.status)) {
|
|
91
100
|
const fresh = await refreshDeviceToken({ apiUrl: opts.apiUrl });
|
|
92
101
|
if (fresh && fresh !== opts.token)
|
|
93
102
|
return { res: await send(fresh), token: fresh };
|
|
@@ -99,13 +108,14 @@ function resolveToken(opts = {}) {
|
|
|
99
108
|
const initialApiUrl = normalizeApiUrl(opts.apiUrl || envUrl || DEFAULT_API_URL);
|
|
100
109
|
const stored = readStoredAuth(initialApiUrl);
|
|
101
110
|
const apiUrl = normalizeApiUrl(opts.apiUrl || envUrl || stored.apiUrl || DEFAULT_API_URL);
|
|
102
|
-
const
|
|
111
|
+
const finalStored = apiUrl === initialApiUrl ? stored : readStoredAuth(apiUrl);
|
|
112
|
+
const apiToken = opts.apiToken || process.env.TODOFORAI_API_TOKEN || finalStored.apiToken || "";
|
|
103
113
|
if (!apiToken)
|
|
104
114
|
throw new Error("TODOFORAI_API_TOKEN not set and no apiToken found in ~/.config/todoforai/credentials.json (run `<cli> login` or `todoforai-bridge login`, or pass --api-token)");
|
|
105
115
|
return { apiUrl, apiToken };
|
|
106
116
|
}
|
|
107
117
|
|
|
108
|
-
//
|
|
118
|
+
// ../tfa-subagent/dist/login.js
|
|
109
119
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, chmodSync as chmodSync2 } from "node:fs";
|
|
110
120
|
import { join as join2, dirname as dirname2 } from "node:path";
|
|
111
121
|
import { homedir as homedir2, platform as platform2 } from "node:os";
|
|
@@ -122,10 +132,15 @@ function normalizeApiUrl2(url) {
|
|
|
122
132
|
function credentialsPath2() {
|
|
123
133
|
return platform2() === "win32" ? join2(homedir2(), "AppData", "Roaming", "todoforai", "credentials.json") : platform2() === "darwin" ? join2(homedir2(), "Library", "Application Support", "todoforai", "credentials.json") : join2(process.env.XDG_CONFIG_HOME || join2(homedir2(), ".config"), "todoforai", "credentials.json");
|
|
124
134
|
}
|
|
125
|
-
function
|
|
126
|
-
|
|
135
|
+
function read2() {
|
|
136
|
+
try {
|
|
137
|
+
return JSON.parse(readFileSync2(credentialsPath2(), "utf-8"));
|
|
138
|
+
} catch {
|
|
139
|
+
return {};
|
|
140
|
+
}
|
|
127
141
|
}
|
|
128
|
-
function writeSecure2(
|
|
142
|
+
function writeSecure2(data) {
|
|
143
|
+
const p = credentialsPath2();
|
|
129
144
|
mkdirSync2(dirname2(p), { recursive: true });
|
|
130
145
|
writeFileSync2(p, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
131
146
|
try {
|
|
@@ -133,39 +148,32 @@ function writeSecure2(p, data) {
|
|
|
133
148
|
} catch {}
|
|
134
149
|
}
|
|
135
150
|
function writeCredentials(fields) {
|
|
136
|
-
|
|
137
|
-
let existing = {};
|
|
138
|
-
try {
|
|
139
|
-
existing = JSON.parse(readFileSync2(p, "utf-8"));
|
|
140
|
-
} catch {}
|
|
141
|
-
writeSecure2(p, { ...existing, ...fields });
|
|
151
|
+
writeSecure2({ ...read2(), ...fields });
|
|
142
152
|
}
|
|
143
153
|
function readStoredAuth2(apiUrl) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
return { apiToken: "" };
|
|
154
|
+
const c = read2();
|
|
155
|
+
const host = c.backendHost || "";
|
|
156
|
+
const storedApiUrl = typeof c.apiUrl === "string" && c.apiUrl ? c.apiUrl : host ? `${host === "localhost" || host === "127.0.0.1" ? "http" : "https"}://${host}` : undefined;
|
|
157
|
+
const dstIsForThisUrl = !storedApiUrl || normalizeApiUrl2(storedApiUrl) === normalizeApiUrl2(apiUrl);
|
|
158
|
+
const isDst = typeof c.apiToken === "string" && c.apiToken.startsWith("dst_");
|
|
159
|
+
const dst = dstIsForThisUrl && isDst ? c.apiToken : "";
|
|
160
|
+
const fallbackApiToken = isDst && !dstIsForThisUrl ? "" : c.apiToken;
|
|
161
|
+
const apiToken = dst || c[apiUrl] || fallbackApiToken || c.apiKey || "";
|
|
162
|
+
return {
|
|
163
|
+
apiToken: typeof apiToken === "string" ? apiToken : "",
|
|
164
|
+
apiUrl: storedApiUrl,
|
|
165
|
+
deviceId: typeof c.deviceId === "string" ? c.deviceId : undefined,
|
|
166
|
+
deviceSecret: typeof c.deviceSecret === "string" ? c.deviceSecret : undefined
|
|
167
|
+
};
|
|
159
168
|
}
|
|
160
169
|
function persistApiToken2(token) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
170
|
+
const c = read2();
|
|
171
|
+
if (!Object.keys(c).length)
|
|
172
|
+
return;
|
|
173
|
+
c.apiToken = token;
|
|
174
|
+
try {
|
|
175
|
+
writeSecure2(c);
|
|
176
|
+
} catch {}
|
|
169
177
|
}
|
|
170
178
|
async function refreshDeviceToken2(opts = {}) {
|
|
171
179
|
const envUrl = process.env.TODOFORAI_API_URL || process.env.API_URL;
|
|
@@ -174,6 +182,8 @@ async function refreshDeviceToken2(opts = {}) {
|
|
|
174
182
|
const apiUrl = normalizeApiUrl2(opts.apiUrl || envUrl || stored.apiUrl || DEFAULT_API_URL2);
|
|
175
183
|
if (!stored.deviceId || !stored.deviceSecret)
|
|
176
184
|
return "";
|
|
185
|
+
if (stored.apiUrl && normalizeApiUrl2(stored.apiUrl) !== apiUrl)
|
|
186
|
+
return "";
|
|
177
187
|
try {
|
|
178
188
|
const res = await fetch(`${apiUrl}/api/v1/cli/device/token`, {
|
|
179
189
|
method: "POST",
|
|
@@ -287,7 +297,7 @@ async function handleLogin(argv, binName) {
|
|
|
287
297
|
process.exit(await login({ binName, deviceName: get("--device-name"), apiUrl: get("--api-url") }));
|
|
288
298
|
}
|
|
289
299
|
|
|
290
|
-
//
|
|
300
|
+
// ../tfa-subagent/dist/whoami.js
|
|
291
301
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, chmodSync as chmodSync3 } from "node:fs";
|
|
292
302
|
import { join as join3, dirname as dirname3 } from "node:path";
|
|
293
303
|
import { homedir as homedir3, platform as platform3 } from "node:os";
|
|
@@ -302,10 +312,15 @@ function normalizeApiUrl3(url) {
|
|
|
302
312
|
function credentialsPath3() {
|
|
303
313
|
return platform3() === "win32" ? join3(homedir3(), "AppData", "Roaming", "todoforai", "credentials.json") : platform3() === "darwin" ? join3(homedir3(), "Library", "Application Support", "todoforai", "credentials.json") : join3(process.env.XDG_CONFIG_HOME || join3(homedir3(), ".config"), "todoforai", "credentials.json");
|
|
304
314
|
}
|
|
305
|
-
function
|
|
306
|
-
|
|
315
|
+
function read3() {
|
|
316
|
+
try {
|
|
317
|
+
return JSON.parse(readFileSync3(credentialsPath3(), "utf-8"));
|
|
318
|
+
} catch {
|
|
319
|
+
return {};
|
|
320
|
+
}
|
|
307
321
|
}
|
|
308
|
-
function writeSecure3(
|
|
322
|
+
function writeSecure3(data) {
|
|
323
|
+
const p = credentialsPath3();
|
|
309
324
|
mkdirSync3(dirname3(p), { recursive: true });
|
|
310
325
|
writeFileSync3(p, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
311
326
|
try {
|
|
@@ -313,31 +328,29 @@ function writeSecure3(p, data) {
|
|
|
313
328
|
} catch {}
|
|
314
329
|
}
|
|
315
330
|
function readStoredAuth3(apiUrl) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
}
|
|
330
|
-
return { apiToken: "" };
|
|
331
|
+
const c = read3();
|
|
332
|
+
const host = c.backendHost || "";
|
|
333
|
+
const storedApiUrl = typeof c.apiUrl === "string" && c.apiUrl ? c.apiUrl : host ? `${host === "localhost" || host === "127.0.0.1" ? "http" : "https"}://${host}` : undefined;
|
|
334
|
+
const dstIsForThisUrl = !storedApiUrl || normalizeApiUrl3(storedApiUrl) === normalizeApiUrl3(apiUrl);
|
|
335
|
+
const isDst = typeof c.apiToken === "string" && c.apiToken.startsWith("dst_");
|
|
336
|
+
const dst = dstIsForThisUrl && isDst ? c.apiToken : "";
|
|
337
|
+
const fallbackApiToken = isDst && !dstIsForThisUrl ? "" : c.apiToken;
|
|
338
|
+
const apiToken = dst || c[apiUrl] || fallbackApiToken || c.apiKey || "";
|
|
339
|
+
return {
|
|
340
|
+
apiToken: typeof apiToken === "string" ? apiToken : "",
|
|
341
|
+
apiUrl: storedApiUrl,
|
|
342
|
+
deviceId: typeof c.deviceId === "string" ? c.deviceId : undefined,
|
|
343
|
+
deviceSecret: typeof c.deviceSecret === "string" ? c.deviceSecret : undefined
|
|
344
|
+
};
|
|
331
345
|
}
|
|
332
346
|
function persistApiToken3(token) {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
347
|
+
const c = read3();
|
|
348
|
+
if (!Object.keys(c).length)
|
|
349
|
+
return;
|
|
350
|
+
c.apiToken = token;
|
|
351
|
+
try {
|
|
352
|
+
writeSecure3(c);
|
|
353
|
+
} catch {}
|
|
341
354
|
}
|
|
342
355
|
async function refreshDeviceToken3(opts = {}) {
|
|
343
356
|
const envUrl = process.env.TODOFORAI_API_URL || process.env.API_URL;
|
|
@@ -346,6 +359,8 @@ async function refreshDeviceToken3(opts = {}) {
|
|
|
346
359
|
const apiUrl = normalizeApiUrl3(opts.apiUrl || envUrl || stored.apiUrl || DEFAULT_API_URL3);
|
|
347
360
|
if (!stored.deviceId || !stored.deviceSecret)
|
|
348
361
|
return "";
|
|
362
|
+
if (stored.apiUrl && normalizeApiUrl3(stored.apiUrl) !== apiUrl)
|
|
363
|
+
return "";
|
|
349
364
|
try {
|
|
350
365
|
const res = await fetch(`${apiUrl}/api/v1/cli/device/token`, {
|
|
351
366
|
method: "POST",
|
|
@@ -366,9 +381,13 @@ async function refreshDeviceToken3(opts = {}) {
|
|
|
366
381
|
async function fetchWithRenew2(url, init, opts) {
|
|
367
382
|
const header = opts.header ?? "x-api-key";
|
|
368
383
|
const renewOn = opts.renewOn ?? [401];
|
|
369
|
-
const send = (token) =>
|
|
370
|
-
|
|
371
|
-
|
|
384
|
+
const send = (token) => {
|
|
385
|
+
const headers = new Headers(init.headers);
|
|
386
|
+
headers.set(header, token);
|
|
387
|
+
return fetch(url, { ...init, headers });
|
|
388
|
+
};
|
|
389
|
+
const res = await send(opts.token);
|
|
390
|
+
if (renewOn.includes(res.status)) {
|
|
372
391
|
const fresh = await refreshDeviceToken3({ apiUrl: opts.apiUrl });
|
|
373
392
|
if (fresh && fresh !== opts.token)
|
|
374
393
|
return { res: await send(fresh), token: fresh };
|
|
@@ -380,7 +399,8 @@ function resolveToken2(opts = {}) {
|
|
|
380
399
|
const initialApiUrl = normalizeApiUrl3(opts.apiUrl || envUrl || DEFAULT_API_URL3);
|
|
381
400
|
const stored = readStoredAuth3(initialApiUrl);
|
|
382
401
|
const apiUrl = normalizeApiUrl3(opts.apiUrl || envUrl || stored.apiUrl || DEFAULT_API_URL3);
|
|
383
|
-
const
|
|
402
|
+
const finalStored = apiUrl === initialApiUrl ? stored : readStoredAuth3(apiUrl);
|
|
403
|
+
const apiToken = opts.apiToken || process.env.TODOFORAI_API_TOKEN || finalStored.apiToken || "";
|
|
384
404
|
if (!apiToken)
|
|
385
405
|
throw new Error("TODOFORAI_API_TOKEN not set and no apiToken found in ~/.config/todoforai/credentials.json (run `<cli> login` or `todoforai-bridge login`, or pass --api-token)");
|
|
386
406
|
return { apiUrl, apiToken };
|
|
@@ -409,19 +429,46 @@ async function whoami(opts) {
|
|
|
409
429
|
}
|
|
410
430
|
}
|
|
411
431
|
|
|
412
|
-
//
|
|
432
|
+
// ../tfa-subagent/dist/update-notifier.js
|
|
413
433
|
import fs from "node:fs";
|
|
414
434
|
import path from "node:path";
|
|
415
435
|
import os from "node:os";
|
|
416
|
-
function
|
|
436
|
+
function installer() {
|
|
437
|
+
let real;
|
|
417
438
|
try {
|
|
418
|
-
|
|
439
|
+
real = fs.realpathSync(process.argv[1] || "");
|
|
419
440
|
} catch {
|
|
420
|
-
return
|
|
441
|
+
return null;
|
|
421
442
|
}
|
|
443
|
+
if (!real.includes(`${path.sep}node_modules${path.sep}`))
|
|
444
|
+
return null;
|
|
445
|
+
return real.includes(path.join(".bun", "install", "global")) ? "bun" : "npm";
|
|
422
446
|
}
|
|
423
447
|
var TTL_MS = 24 * 60 * 60 * 1000;
|
|
424
448
|
var CACHE_DIR = path.join(os.homedir(), ".config", "todoforai");
|
|
449
|
+
var cacheFileFor = (name) => path.join(CACHE_DIR, `notifier-${encodeURIComponent(name)}.json`);
|
|
450
|
+
function readCache(name) {
|
|
451
|
+
try {
|
|
452
|
+
return JSON.parse(fs.readFileSync(cacheFileFor(name), "utf8"));
|
|
453
|
+
} catch {
|
|
454
|
+
return {};
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
function writeCache(name, cache) {
|
|
458
|
+
try {
|
|
459
|
+
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
460
|
+
fs.writeFileSync(cacheFileFor(name), JSON.stringify(cache));
|
|
461
|
+
} catch {}
|
|
462
|
+
}
|
|
463
|
+
function refreshCache(name, cache) {
|
|
464
|
+
if (Date.now() - (cache.ts ?? 0) <= TTL_MS)
|
|
465
|
+
return;
|
|
466
|
+
writeCache(name, { ...cache, ts: Date.now() });
|
|
467
|
+
fetch(`https://registry.npmjs.org/${name}/latest`, { signal: AbortSignal.timeout(3000) }).then((r) => r.ok ? r.json() : null).then((j) => {
|
|
468
|
+
if (j?.version)
|
|
469
|
+
writeCache(name, { ...cache, ts: Date.now(), latest: j.version });
|
|
470
|
+
}).catch(() => {});
|
|
471
|
+
}
|
|
425
472
|
function cmpVer(a, b) {
|
|
426
473
|
const pa = a.split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
|
|
427
474
|
const pb = b.split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
|
|
@@ -433,13 +480,11 @@ function cmpVer(a, b) {
|
|
|
433
480
|
return 0;
|
|
434
481
|
}
|
|
435
482
|
function checkForUpdates(pkg) {
|
|
436
|
-
if (!process.stderr.isTTY || process.env.CI || process.env.NO_UPDATE_NOTIFIER ||
|
|
483
|
+
if (!process.stderr.isTTY || process.env.CI || process.env.NO_UPDATE_NOTIFIER || !installer())
|
|
437
484
|
return;
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
cache = JSON.parse(fs.readFileSync(cacheFile, "utf8"));
|
|
442
|
-
} catch {}
|
|
485
|
+
if (process.argv.includes("--json"))
|
|
486
|
+
return;
|
|
487
|
+
const cache = readCache(pkg.name);
|
|
443
488
|
if (cache.latest && cmpVer(cache.latest, pkg.version) > 0) {
|
|
444
489
|
process.stderr.write(`
|
|
445
490
|
\x1B[33m Update available: \x1B[2m${pkg.version}\x1B[22m → \x1B[1m${cache.latest}\x1B[0m
|
|
@@ -447,23 +492,13 @@ function checkForUpdates(pkg) {
|
|
|
447
492
|
|
|
448
493
|
`);
|
|
449
494
|
}
|
|
450
|
-
|
|
451
|
-
try {
|
|
452
|
-
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
453
|
-
fs.writeFileSync(cacheFile, JSON.stringify({ ...cache, ts: Date.now() }));
|
|
454
|
-
} catch {}
|
|
455
|
-
fetch(`https://registry.npmjs.org/${pkg.name}/latest`, { signal: AbortSignal.timeout(3000) }).then((r) => r.ok ? r.json() : null).then((j) => {
|
|
456
|
-
if (!j?.version)
|
|
457
|
-
return;
|
|
458
|
-
fs.writeFileSync(cacheFile, JSON.stringify({ ts: Date.now(), latest: j.version }));
|
|
459
|
-
}).catch(() => {});
|
|
460
|
-
}
|
|
495
|
+
refreshCache(pkg.name, cache);
|
|
461
496
|
}
|
|
462
497
|
// package.json
|
|
463
498
|
var package_default = {
|
|
464
499
|
name: "@todoforai/tfa-memory",
|
|
465
|
-
version: "0.4.
|
|
466
|
-
description: "Zero-config TODOforAI memory CLI — add/search/ls/get/rm text memory rows addressed as source/ref, plus federated search over every message of every past TODO (read-only `todo` source), with full-text and semantic search. Reuses bridge credentials.",
|
|
500
|
+
version: "0.4.3",
|
|
501
|
+
description: "Zero-config TODOforAI memory CLI — add/search/ls/get/rm/live text memory rows addressed as source/ref, plus federated search over every message of every past TODO (read-only `todo` source), with full-text and semantic search, plus L2 working-memory re-render. Reuses bridge credentials.",
|
|
467
502
|
type: "module",
|
|
468
503
|
bin: {
|
|
469
504
|
"tfa-memory": "dist/cli.js"
|
|
@@ -504,6 +539,10 @@ every TODO under the read-only \`todo\` source (\`todo/<todoId>:<messageId>\`,
|
|
|
504
539
|
live from the backend index, nothing separate to sync). \`--source todo\` searches
|
|
505
540
|
past TODO messages only; \`get todo/<todoId>:<messageId>\` prints the whole message.
|
|
506
541
|
|
|
542
|
+
Owner-private TODOs are searchable in personal contexts. In agent shells,
|
|
543
|
+
TODOFORAI_TODO_ID supplies the destination; shared conversations cannot read
|
|
544
|
+
private TODOs or account-private saved notes. --todo selects a source, not an audience.
|
|
545
|
+
|
|
507
546
|
Usage:
|
|
508
547
|
tfa-memory <command> [options] [args...]
|
|
509
548
|
|
|
@@ -511,8 +550,10 @@ Commands:
|
|
|
511
550
|
add [source/ref] <text|-> Save a row. Without an address → manual/<timestamp>.
|
|
512
551
|
"-" reads stdin.
|
|
513
552
|
search <query> Full-text search over saved rows + past TODO messages
|
|
553
|
+
live Re-render L2 working memory (the always-on prompt block)
|
|
514
554
|
ls List saved rows, newest first (todo messages excluded)
|
|
515
555
|
get <source/ref> Print full content of one row
|
|
556
|
+
around <source/ref> Neighbours of one row: todo messages in chat order, saved rows by time within the source
|
|
516
557
|
rm <source/ref> Delete one row
|
|
517
558
|
rm --source <name> Delete every row in a source
|
|
518
559
|
sources List sources with counts and last write
|
|
@@ -522,7 +563,11 @@ Commands:
|
|
|
522
563
|
Options:
|
|
523
564
|
-s, --source <a,b> Restrict search/ls to sources (comma-separated; "todo" = TODO messages)
|
|
524
565
|
--todo <id> Restrict a todo-message search to one TODO
|
|
525
|
-
|
|
566
|
+
-B, --budget <n> Token budget for \`live\` (default 1500, 100–20000)
|
|
567
|
+
--mode lexical|semantic|fused lexical: all words, newest first (default) · semantic: meaning · fused: best recall
|
|
568
|
+
--exclude <address> Leave out an already-read row (repeatable); fused also prints the lexical match count
|
|
569
|
+
--before <n> around: rows before the pivot (default 2, max 200)
|
|
570
|
+
--after <n> around: rows after the pivot (default 2, max 200); large = read the rest
|
|
526
571
|
--meta <json> Attach JSON metadata to the row (e.g. '{"todoId":"..."}')
|
|
527
572
|
-n, --limit <n> Max rows (default 5, max 50)
|
|
528
573
|
-j, --json Machine-readable output
|
|
@@ -537,8 +582,10 @@ Examples:
|
|
|
537
582
|
tfa-memory add deploy-notes/rollback "run scripts/rollback.sh, then verify /health"
|
|
538
583
|
cat docs/auth.md | tfa-memory add acme/docs/auth.md -
|
|
539
584
|
tfa-memory search "rollback procedure"
|
|
540
|
-
tfa-memory search "how do we deploy" --semantic
|
|
585
|
+
tfa-memory search "how do we deploy" --mode semantic
|
|
541
586
|
tfa-memory search "android build" --source todo
|
|
587
|
+
tfa-memory live
|
|
588
|
+
tfa-memory live -B 800
|
|
542
589
|
tfa-memory get todo/<todoId>:<messageId>
|
|
543
590
|
tfa-memory ls --source deploy-notes
|
|
544
591
|
tfa-memory get deploy-notes/rollback
|
|
@@ -574,7 +621,10 @@ function resolveMemoryUrl(flag, apiUrl) {
|
|
|
574
621
|
async function api(ctx, method, path2, body) {
|
|
575
622
|
const init = {
|
|
576
623
|
method,
|
|
577
|
-
headers:
|
|
624
|
+
headers: {
|
|
625
|
+
...body !== undefined && { "content-type": "application/json" },
|
|
626
|
+
...process.env.TODOFORAI_TODO_ID && { "x-tfa-todo-id": process.env.TODOFORAI_TODO_ID }
|
|
627
|
+
},
|
|
578
628
|
body: body !== undefined ? JSON.stringify(body) : undefined
|
|
579
629
|
};
|
|
580
630
|
const { res, token } = await fetchWithRenew(`${ctx.memoryUrl}/v1${path2}`, init, { apiUrl: ctx.apiUrl, token: ctx.token, renewOn: [401, 403] });
|
|
@@ -614,8 +664,12 @@ async function main() {
|
|
|
614
664
|
options: {
|
|
615
665
|
source: { type: "string", short: "s" },
|
|
616
666
|
todo: { type: "string" },
|
|
667
|
+
budget: { type: "string", short: "B" },
|
|
617
668
|
meta: { type: "string" },
|
|
618
|
-
|
|
669
|
+
mode: { type: "string" },
|
|
670
|
+
exclude: { type: "string", multiple: true },
|
|
671
|
+
before: { type: "string" },
|
|
672
|
+
after: { type: "string" },
|
|
619
673
|
limit: { type: "string", short: "n" },
|
|
620
674
|
json: { type: "boolean", short: "j", default: false },
|
|
621
675
|
"api-token": { type: "string" },
|
|
@@ -687,17 +741,35 @@ async function main() {
|
|
|
687
741
|
die(`--meta must be a JSON object`, 2);
|
|
688
742
|
}
|
|
689
743
|
const res = await api(ctx, "POST", "/memory", { source, ref, content: text, meta });
|
|
744
|
+
for (const w of res.warnings ?? [])
|
|
745
|
+
console.error(`Warning: ${w}`);
|
|
690
746
|
console.log(json ? JSON.stringify(res) : `${res.status} ${res.address}`);
|
|
691
747
|
break;
|
|
692
748
|
}
|
|
693
749
|
case "search": {
|
|
694
750
|
const query = rest[0] ?? die(`usage: tfa-memory search "query"`, 2);
|
|
695
|
-
const
|
|
751
|
+
const exclude = values.exclude?.join(",");
|
|
752
|
+
const res = await api(ctx, "GET", `/memory/search${qs({ query, mode: values.mode, sources, limit, todoId: values.todo, exclude })}`);
|
|
696
753
|
for (const w of res.warnings ?? [])
|
|
697
754
|
console.error(`Warning: ${w}`);
|
|
755
|
+
if (res.lexicalCount !== undefined)
|
|
756
|
+
console.error(`${res.lexicalCount} row(s) contain a query word`);
|
|
698
757
|
printRows(res.rows, json);
|
|
699
758
|
break;
|
|
700
759
|
}
|
|
760
|
+
case "live": {
|
|
761
|
+
const budget = values.budget !== undefined ? Number(values.budget) : 1500;
|
|
762
|
+
if (!Number.isInteger(budget) || budget < 100 || budget > 20000)
|
|
763
|
+
die(`--budget must be an integer from 100 to 20000`, 2);
|
|
764
|
+
const res = await api(ctx, "GET", `/live/render${qs({ budget })}`);
|
|
765
|
+
const block = res.block ?? "";
|
|
766
|
+
if (json)
|
|
767
|
+
console.log(JSON.stringify({ block }));
|
|
768
|
+
else
|
|
769
|
+
process.stdout.write((block || "(empty live memory)") + `
|
|
770
|
+
`);
|
|
771
|
+
break;
|
|
772
|
+
}
|
|
701
773
|
case "ls": {
|
|
702
774
|
const res = await api(ctx, "GET", `/memory${qs({ sources, limit })}`);
|
|
703
775
|
printRows(res.rows, json);
|
|
@@ -713,6 +785,14 @@ async function main() {
|
|
|
713
785
|
`);
|
|
714
786
|
break;
|
|
715
787
|
}
|
|
788
|
+
case "around": {
|
|
789
|
+
const [source, ref] = splitAddr(rest[0] ?? die("address required (source/ref)", 2));
|
|
790
|
+
const res = await api(ctx, "GET", `/memory/around${qs({ source, ref, before: values.before, after: values.after })}`);
|
|
791
|
+
if (res.total !== undefined)
|
|
792
|
+
console.error(`message ${res.index + 1} of ${res.total}`);
|
|
793
|
+
printRows(res.rows, json, 2000);
|
|
794
|
+
break;
|
|
795
|
+
}
|
|
716
796
|
case "rm":
|
|
717
797
|
case "delete":
|
|
718
798
|
case "del": {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@todoforai/tfa-memory",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "Zero-config TODOforAI memory CLI — add/search/ls/get/rm text memory rows addressed as source/ref, plus federated search over every message of every past TODO (read-only `todo` source), with full-text and semantic search. Reuses bridge credentials.",
|
|
3
|
+
"version": "0.4.3",
|
|
4
|
+
"description": "Zero-config TODOforAI memory CLI — add/search/ls/get/rm/live text memory rows addressed as source/ref, plus federated search over every message of every past TODO (read-only `todo` source), with full-text and semantic search, plus L2 working-memory re-render. Reuses bridge credentials.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"tfa-memory": "dist/cli.js"
|