@todoforai/tfa-memory 0.4.1 → 0.4.2
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 +134 -115
- package/package.json +1 -1
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,25 @@ 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
|
-
deviceSecret: typeof c.deviceSecret === "string" ? c.deviceSecret : undefined
|
|
46
|
-
};
|
|
47
|
-
} catch {}
|
|
48
|
-
}
|
|
49
|
-
return { apiToken: "" };
|
|
40
|
+
const c = read();
|
|
41
|
+
const apiToken = c[apiUrl] || c.apiToken || c.apiKey || "";
|
|
42
|
+
const host = c.backendHost || "";
|
|
43
|
+
const storedApiUrl = typeof c.apiUrl === "string" && c.apiUrl ? c.apiUrl : host ? `${host === "localhost" || host === "127.0.0.1" ? "http" : "https"}://${host}` : undefined;
|
|
44
|
+
return {
|
|
45
|
+
apiToken: typeof apiToken === "string" ? apiToken : "",
|
|
46
|
+
apiUrl: storedApiUrl,
|
|
47
|
+
deviceId: typeof c.deviceId === "string" ? c.deviceId : undefined,
|
|
48
|
+
deviceSecret: typeof c.deviceSecret === "string" ? c.deviceSecret : undefined
|
|
49
|
+
};
|
|
50
50
|
}
|
|
51
51
|
function persistApiToken(token) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
52
|
+
const c = read();
|
|
53
|
+
if (!Object.keys(c).length)
|
|
54
|
+
return;
|
|
55
|
+
c.apiToken = token;
|
|
56
|
+
try {
|
|
57
|
+
writeSecure(c);
|
|
58
|
+
} catch {}
|
|
60
59
|
}
|
|
61
60
|
async function refreshDeviceToken(opts = {}) {
|
|
62
61
|
const envUrl = process.env.TODOFORAI_API_URL || process.env.API_URL;
|
|
@@ -85,8 +84,12 @@ async function refreshDeviceToken(opts = {}) {
|
|
|
85
84
|
async function fetchWithRenew(url, init, opts) {
|
|
86
85
|
const header = opts.header ?? "x-api-key";
|
|
87
86
|
const renewOn = opts.renewOn ?? [401];
|
|
88
|
-
const send = (token) =>
|
|
89
|
-
|
|
87
|
+
const send = (token) => {
|
|
88
|
+
const headers = new Headers(init.headers);
|
|
89
|
+
headers.set(header, token);
|
|
90
|
+
return fetch(url, { ...init, headers });
|
|
91
|
+
};
|
|
92
|
+
const res = await send(opts.token);
|
|
90
93
|
if (renewOn.includes(res.status) && opts.token.startsWith("dst_")) {
|
|
91
94
|
const fresh = await refreshDeviceToken({ apiUrl: opts.apiUrl });
|
|
92
95
|
if (fresh && fresh !== opts.token)
|
|
@@ -99,13 +102,14 @@ function resolveToken(opts = {}) {
|
|
|
99
102
|
const initialApiUrl = normalizeApiUrl(opts.apiUrl || envUrl || DEFAULT_API_URL);
|
|
100
103
|
const stored = readStoredAuth(initialApiUrl);
|
|
101
104
|
const apiUrl = normalizeApiUrl(opts.apiUrl || envUrl || stored.apiUrl || DEFAULT_API_URL);
|
|
102
|
-
const
|
|
105
|
+
const finalStored = apiUrl === initialApiUrl ? stored : readStoredAuth(apiUrl);
|
|
106
|
+
const apiToken = opts.apiToken || process.env.TODOFORAI_API_TOKEN || finalStored.apiToken || "";
|
|
103
107
|
if (!apiToken)
|
|
104
108
|
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
109
|
return { apiUrl, apiToken };
|
|
106
110
|
}
|
|
107
111
|
|
|
108
|
-
//
|
|
112
|
+
// ../tfa-subagent/dist/login.js
|
|
109
113
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, chmodSync as chmodSync2 } from "node:fs";
|
|
110
114
|
import { join as join2, dirname as dirname2 } from "node:path";
|
|
111
115
|
import { homedir as homedir2, platform as platform2 } from "node:os";
|
|
@@ -122,10 +126,15 @@ function normalizeApiUrl2(url) {
|
|
|
122
126
|
function credentialsPath2() {
|
|
123
127
|
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
128
|
}
|
|
125
|
-
function
|
|
126
|
-
|
|
129
|
+
function read2() {
|
|
130
|
+
try {
|
|
131
|
+
return JSON.parse(readFileSync2(credentialsPath2(), "utf-8"));
|
|
132
|
+
} catch {
|
|
133
|
+
return {};
|
|
134
|
+
}
|
|
127
135
|
}
|
|
128
|
-
function writeSecure2(
|
|
136
|
+
function writeSecure2(data) {
|
|
137
|
+
const p = credentialsPath2();
|
|
129
138
|
mkdirSync2(dirname2(p), { recursive: true });
|
|
130
139
|
writeFileSync2(p, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
131
140
|
try {
|
|
@@ -133,39 +142,28 @@ function writeSecure2(p, data) {
|
|
|
133
142
|
} catch {}
|
|
134
143
|
}
|
|
135
144
|
function writeCredentials(fields) {
|
|
136
|
-
|
|
137
|
-
let existing = {};
|
|
138
|
-
try {
|
|
139
|
-
existing = JSON.parse(readFileSync2(p, "utf-8"));
|
|
140
|
-
} catch {}
|
|
141
|
-
writeSecure2(p, { ...existing, ...fields });
|
|
145
|
+
writeSecure2({ ...read2(), ...fields });
|
|
142
146
|
}
|
|
143
147
|
function readStoredAuth2(apiUrl) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
deviceSecret: typeof c.deviceSecret === "string" ? c.deviceSecret : undefined
|
|
155
|
-
};
|
|
156
|
-
} catch {}
|
|
157
|
-
}
|
|
158
|
-
return { apiToken: "" };
|
|
148
|
+
const c = read2();
|
|
149
|
+
const apiToken = c[apiUrl] || c.apiToken || c.apiKey || "";
|
|
150
|
+
const host = c.backendHost || "";
|
|
151
|
+
const storedApiUrl = typeof c.apiUrl === "string" && c.apiUrl ? c.apiUrl : host ? `${host === "localhost" || host === "127.0.0.1" ? "http" : "https"}://${host}` : undefined;
|
|
152
|
+
return {
|
|
153
|
+
apiToken: typeof apiToken === "string" ? apiToken : "",
|
|
154
|
+
apiUrl: storedApiUrl,
|
|
155
|
+
deviceId: typeof c.deviceId === "string" ? c.deviceId : undefined,
|
|
156
|
+
deviceSecret: typeof c.deviceSecret === "string" ? c.deviceSecret : undefined
|
|
157
|
+
};
|
|
159
158
|
}
|
|
160
159
|
function persistApiToken2(token) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
160
|
+
const c = read2();
|
|
161
|
+
if (!Object.keys(c).length)
|
|
162
|
+
return;
|
|
163
|
+
c.apiToken = token;
|
|
164
|
+
try {
|
|
165
|
+
writeSecure2(c);
|
|
166
|
+
} catch {}
|
|
169
167
|
}
|
|
170
168
|
async function refreshDeviceToken2(opts = {}) {
|
|
171
169
|
const envUrl = process.env.TODOFORAI_API_URL || process.env.API_URL;
|
|
@@ -287,7 +285,7 @@ async function handleLogin(argv, binName) {
|
|
|
287
285
|
process.exit(await login({ binName, deviceName: get("--device-name"), apiUrl: get("--api-url") }));
|
|
288
286
|
}
|
|
289
287
|
|
|
290
|
-
//
|
|
288
|
+
// ../tfa-subagent/dist/whoami.js
|
|
291
289
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, chmodSync as chmodSync3 } from "node:fs";
|
|
292
290
|
import { join as join3, dirname as dirname3 } from "node:path";
|
|
293
291
|
import { homedir as homedir3, platform as platform3 } from "node:os";
|
|
@@ -302,10 +300,15 @@ function normalizeApiUrl3(url) {
|
|
|
302
300
|
function credentialsPath3() {
|
|
303
301
|
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
302
|
}
|
|
305
|
-
function
|
|
306
|
-
|
|
303
|
+
function read3() {
|
|
304
|
+
try {
|
|
305
|
+
return JSON.parse(readFileSync3(credentialsPath3(), "utf-8"));
|
|
306
|
+
} catch {
|
|
307
|
+
return {};
|
|
308
|
+
}
|
|
307
309
|
}
|
|
308
|
-
function writeSecure3(
|
|
310
|
+
function writeSecure3(data) {
|
|
311
|
+
const p = credentialsPath3();
|
|
309
312
|
mkdirSync3(dirname3(p), { recursive: true });
|
|
310
313
|
writeFileSync3(p, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
311
314
|
try {
|
|
@@ -313,31 +316,25 @@ function writeSecure3(p, data) {
|
|
|
313
316
|
} catch {}
|
|
314
317
|
}
|
|
315
318
|
function readStoredAuth3(apiUrl) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
deviceSecret: typeof c.deviceSecret === "string" ? c.deviceSecret : undefined
|
|
327
|
-
};
|
|
328
|
-
} catch {}
|
|
329
|
-
}
|
|
330
|
-
return { apiToken: "" };
|
|
319
|
+
const c = read3();
|
|
320
|
+
const apiToken = c[apiUrl] || c.apiToken || c.apiKey || "";
|
|
321
|
+
const host = c.backendHost || "";
|
|
322
|
+
const storedApiUrl = typeof c.apiUrl === "string" && c.apiUrl ? c.apiUrl : host ? `${host === "localhost" || host === "127.0.0.1" ? "http" : "https"}://${host}` : undefined;
|
|
323
|
+
return {
|
|
324
|
+
apiToken: typeof apiToken === "string" ? apiToken : "",
|
|
325
|
+
apiUrl: storedApiUrl,
|
|
326
|
+
deviceId: typeof c.deviceId === "string" ? c.deviceId : undefined,
|
|
327
|
+
deviceSecret: typeof c.deviceSecret === "string" ? c.deviceSecret : undefined
|
|
328
|
+
};
|
|
331
329
|
}
|
|
332
330
|
function persistApiToken3(token) {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
331
|
+
const c = read3();
|
|
332
|
+
if (!Object.keys(c).length)
|
|
333
|
+
return;
|
|
334
|
+
c.apiToken = token;
|
|
335
|
+
try {
|
|
336
|
+
writeSecure3(c);
|
|
337
|
+
} catch {}
|
|
341
338
|
}
|
|
342
339
|
async function refreshDeviceToken3(opts = {}) {
|
|
343
340
|
const envUrl = process.env.TODOFORAI_API_URL || process.env.API_URL;
|
|
@@ -366,8 +363,12 @@ async function refreshDeviceToken3(opts = {}) {
|
|
|
366
363
|
async function fetchWithRenew2(url, init, opts) {
|
|
367
364
|
const header = opts.header ?? "x-api-key";
|
|
368
365
|
const renewOn = opts.renewOn ?? [401];
|
|
369
|
-
const send = (token) =>
|
|
370
|
-
|
|
366
|
+
const send = (token) => {
|
|
367
|
+
const headers = new Headers(init.headers);
|
|
368
|
+
headers.set(header, token);
|
|
369
|
+
return fetch(url, { ...init, headers });
|
|
370
|
+
};
|
|
371
|
+
const res = await send(opts.token);
|
|
371
372
|
if (renewOn.includes(res.status) && opts.token.startsWith("dst_")) {
|
|
372
373
|
const fresh = await refreshDeviceToken3({ apiUrl: opts.apiUrl });
|
|
373
374
|
if (fresh && fresh !== opts.token)
|
|
@@ -380,7 +381,8 @@ function resolveToken2(opts = {}) {
|
|
|
380
381
|
const initialApiUrl = normalizeApiUrl3(opts.apiUrl || envUrl || DEFAULT_API_URL3);
|
|
381
382
|
const stored = readStoredAuth3(initialApiUrl);
|
|
382
383
|
const apiUrl = normalizeApiUrl3(opts.apiUrl || envUrl || stored.apiUrl || DEFAULT_API_URL3);
|
|
383
|
-
const
|
|
384
|
+
const finalStored = apiUrl === initialApiUrl ? stored : readStoredAuth3(apiUrl);
|
|
385
|
+
const apiToken = opts.apiToken || process.env.TODOFORAI_API_TOKEN || finalStored.apiToken || "";
|
|
384
386
|
if (!apiToken)
|
|
385
387
|
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
388
|
return { apiUrl, apiToken };
|
|
@@ -409,19 +411,46 @@ async function whoami(opts) {
|
|
|
409
411
|
}
|
|
410
412
|
}
|
|
411
413
|
|
|
412
|
-
//
|
|
414
|
+
// ../tfa-subagent/dist/update-notifier.js
|
|
413
415
|
import fs from "node:fs";
|
|
414
416
|
import path from "node:path";
|
|
415
417
|
import os from "node:os";
|
|
416
|
-
function
|
|
418
|
+
function installer() {
|
|
419
|
+
let real;
|
|
417
420
|
try {
|
|
418
|
-
|
|
421
|
+
real = fs.realpathSync(process.argv[1] || "");
|
|
419
422
|
} catch {
|
|
420
|
-
return
|
|
423
|
+
return null;
|
|
421
424
|
}
|
|
425
|
+
if (!real.includes(`${path.sep}node_modules${path.sep}`))
|
|
426
|
+
return null;
|
|
427
|
+
return real.includes(path.join(".bun", "install", "global")) ? "bun" : "npm";
|
|
422
428
|
}
|
|
423
429
|
var TTL_MS = 24 * 60 * 60 * 1000;
|
|
424
430
|
var CACHE_DIR = path.join(os.homedir(), ".config", "todoforai");
|
|
431
|
+
var cacheFileFor = (name) => path.join(CACHE_DIR, `notifier-${encodeURIComponent(name)}.json`);
|
|
432
|
+
function readCache(name) {
|
|
433
|
+
try {
|
|
434
|
+
return JSON.parse(fs.readFileSync(cacheFileFor(name), "utf8"));
|
|
435
|
+
} catch {
|
|
436
|
+
return {};
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
function writeCache(name, cache) {
|
|
440
|
+
try {
|
|
441
|
+
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
442
|
+
fs.writeFileSync(cacheFileFor(name), JSON.stringify(cache));
|
|
443
|
+
} catch {}
|
|
444
|
+
}
|
|
445
|
+
function refreshCache(name, cache) {
|
|
446
|
+
if (Date.now() - (cache.ts ?? 0) <= TTL_MS)
|
|
447
|
+
return;
|
|
448
|
+
writeCache(name, { ...cache, ts: Date.now() });
|
|
449
|
+
fetch(`https://registry.npmjs.org/${name}/latest`, { signal: AbortSignal.timeout(3000) }).then((r) => r.ok ? r.json() : null).then((j) => {
|
|
450
|
+
if (j?.version)
|
|
451
|
+
writeCache(name, { ...cache, ts: Date.now(), latest: j.version });
|
|
452
|
+
}).catch(() => {});
|
|
453
|
+
}
|
|
425
454
|
function cmpVer(a, b) {
|
|
426
455
|
const pa = a.split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
|
|
427
456
|
const pb = b.split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
|
|
@@ -433,13 +462,11 @@ function cmpVer(a, b) {
|
|
|
433
462
|
return 0;
|
|
434
463
|
}
|
|
435
464
|
function checkForUpdates(pkg) {
|
|
436
|
-
if (!process.stderr.isTTY || process.env.CI || process.env.NO_UPDATE_NOTIFIER ||
|
|
465
|
+
if (!process.stderr.isTTY || process.env.CI || process.env.NO_UPDATE_NOTIFIER || !installer())
|
|
437
466
|
return;
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
cache = JSON.parse(fs.readFileSync(cacheFile, "utf8"));
|
|
442
|
-
} catch {}
|
|
467
|
+
if (process.argv.includes("--json"))
|
|
468
|
+
return;
|
|
469
|
+
const cache = readCache(pkg.name);
|
|
443
470
|
if (cache.latest && cmpVer(cache.latest, pkg.version) > 0) {
|
|
444
471
|
process.stderr.write(`
|
|
445
472
|
\x1B[33m Update available: \x1B[2m${pkg.version}\x1B[22m → \x1B[1m${cache.latest}\x1B[0m
|
|
@@ -447,22 +474,12 @@ function checkForUpdates(pkg) {
|
|
|
447
474
|
|
|
448
475
|
`);
|
|
449
476
|
}
|
|
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
|
-
}
|
|
477
|
+
refreshCache(pkg.name, cache);
|
|
461
478
|
}
|
|
462
479
|
// package.json
|
|
463
480
|
var package_default = {
|
|
464
481
|
name: "@todoforai/tfa-memory",
|
|
465
|
-
version: "0.4.
|
|
482
|
+
version: "0.4.2",
|
|
466
483
|
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.",
|
|
467
484
|
type: "module",
|
|
468
485
|
bin: {
|
|
@@ -687,6 +704,8 @@ async function main() {
|
|
|
687
704
|
die(`--meta must be a JSON object`, 2);
|
|
688
705
|
}
|
|
689
706
|
const res = await api(ctx, "POST", "/memory", { source, ref, content: text, meta });
|
|
707
|
+
for (const w of res.warnings ?? [])
|
|
708
|
+
console.error(`Warning: ${w}`);
|
|
690
709
|
console.log(json ? JSON.stringify(res) : `${res.status} ${res.address}`);
|
|
691
710
|
break;
|
|
692
711
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@todoforai/tfa-memory",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
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.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|