@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.
Files changed (2) hide show
  1. package/dist/cli.js +134 -115
  2. 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
- // node_modules/@todoforai/tfa-subagent/dist/auth.js
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 credentialsPaths() {
25
- return [credentialsPath()];
24
+ function read() {
25
+ try {
26
+ return JSON.parse(readFileSync(credentialsPath(), "utf-8"));
27
+ } catch {
28
+ return {};
29
+ }
26
30
  }
27
- function writeSecure(p, data) {
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
- for (const p of credentialsPaths()) {
36
- try {
37
- const c = JSON.parse(readFileSync(p, "utf-8"));
38
- const apiToken = c[apiUrl] || c.apiToken || c.apiKey || "";
39
- const host = c.backendHost || "";
40
- const storedApiUrl = typeof c.apiUrl === "string" && c.apiUrl ? c.apiUrl : host ? `${host === "localhost" || host === "127.0.0.1" ? "http" : "https"}://${host}` : undefined;
41
- return {
42
- apiToken,
43
- apiUrl: storedApiUrl,
44
- deviceId: typeof c.deviceId === "string" ? c.deviceId : undefined,
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
- for (const p of credentialsPaths()) {
53
- try {
54
- const c = JSON.parse(readFileSync(p, "utf-8"));
55
- c.apiToken = token;
56
- writeSecure(p, c);
57
- return;
58
- } catch {}
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) => fetch(url, { ...init, headers: { ...init.headers, [header]: token } });
89
- let res = await send(opts.token);
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 apiToken = opts.apiToken || process.env.TODOFORAI_API_TOKEN || stored.apiToken || "";
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
- // node_modules/@todoforai/tfa-subagent/dist/login.js
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 credentialsPaths2() {
126
- return [credentialsPath2()];
129
+ function read2() {
130
+ try {
131
+ return JSON.parse(readFileSync2(credentialsPath2(), "utf-8"));
132
+ } catch {
133
+ return {};
134
+ }
127
135
  }
128
- function writeSecure2(p, data) {
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
- const p = credentialsPath2();
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
- for (const p of credentialsPaths2()) {
145
- try {
146
- const c = JSON.parse(readFileSync2(p, "utf-8"));
147
- const apiToken = c[apiUrl] || c.apiToken || c.apiKey || "";
148
- const host = c.backendHost || "";
149
- const storedApiUrl = typeof c.apiUrl === "string" && c.apiUrl ? c.apiUrl : host ? `${host === "localhost" || host === "127.0.0.1" ? "http" : "https"}://${host}` : undefined;
150
- return {
151
- apiToken,
152
- apiUrl: storedApiUrl,
153
- deviceId: typeof c.deviceId === "string" ? c.deviceId : undefined,
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
- for (const p of credentialsPaths2()) {
162
- try {
163
- const c = JSON.parse(readFileSync2(p, "utf-8"));
164
- c.apiToken = token;
165
- writeSecure2(p, c);
166
- return;
167
- } catch {}
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
- // node_modules/@todoforai/tfa-subagent/dist/whoami.js
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 credentialsPaths3() {
306
- return [credentialsPath3()];
303
+ function read3() {
304
+ try {
305
+ return JSON.parse(readFileSync3(credentialsPath3(), "utf-8"));
306
+ } catch {
307
+ return {};
308
+ }
307
309
  }
308
- function writeSecure3(p, data) {
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
- for (const p of credentialsPaths3()) {
317
- try {
318
- const c = JSON.parse(readFileSync3(p, "utf-8"));
319
- const apiToken = c[apiUrl] || c.apiToken || c.apiKey || "";
320
- const host = c.backendHost || "";
321
- const storedApiUrl = typeof c.apiUrl === "string" && c.apiUrl ? c.apiUrl : host ? `${host === "localhost" || host === "127.0.0.1" ? "http" : "https"}://${host}` : undefined;
322
- return {
323
- apiToken,
324
- apiUrl: storedApiUrl,
325
- deviceId: typeof c.deviceId === "string" ? c.deviceId : undefined,
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
- for (const p of credentialsPaths3()) {
334
- try {
335
- const c = JSON.parse(readFileSync3(p, "utf-8"));
336
- c.apiToken = token;
337
- writeSecure3(p, c);
338
- return;
339
- } catch {}
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) => fetch(url, { ...init, headers: { ...init.headers, [header]: token } });
370
- let res = await send(opts.token);
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 apiToken = opts.apiToken || process.env.TODOFORAI_API_TOKEN || stored.apiToken || "";
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
- // node_modules/@todoforai/tfa-subagent/dist/update-notifier.js
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 isLinkedInstall() {
418
+ function installer() {
419
+ let real;
417
420
  try {
418
- return !fs.realpathSync(process.argv[1] || "").includes(`${path.sep}node_modules${path.sep}`);
421
+ real = fs.realpathSync(process.argv[1] || "");
419
422
  } catch {
420
- return false;
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 || isLinkedInstall())
465
+ if (!process.stderr.isTTY || process.env.CI || process.env.NO_UPDATE_NOTIFIER || !installer())
437
466
  return;
438
- const cacheFile = path.join(CACHE_DIR, `notifier-${encodeURIComponent(pkg.name)}.json`);
439
- let cache = {};
440
- try {
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
- if (Date.now() - (cache.ts ?? 0) > TTL_MS) {
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.1",
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.1",
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": {