@todoforai/tfa-memory 0.4.2 → 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 +73 -12
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -38,9 +38,13 @@ function writeSecure(data) {
|
|
|
38
38
|
}
|
|
39
39
|
function readStoredAuth(apiUrl) {
|
|
40
40
|
const c = read();
|
|
41
|
-
const apiToken = c[apiUrl] || c.apiToken || c.apiKey || "";
|
|
42
41
|
const host = c.backendHost || "";
|
|
43
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 || "";
|
|
44
48
|
return {
|
|
45
49
|
apiToken: typeof apiToken === "string" ? apiToken : "",
|
|
46
50
|
apiUrl: storedApiUrl,
|
|
@@ -64,6 +68,8 @@ async function refreshDeviceToken(opts = {}) {
|
|
|
64
68
|
const apiUrl = normalizeApiUrl(opts.apiUrl || envUrl || stored.apiUrl || DEFAULT_API_URL);
|
|
65
69
|
if (!stored.deviceId || !stored.deviceSecret)
|
|
66
70
|
return "";
|
|
71
|
+
if (stored.apiUrl && normalizeApiUrl(stored.apiUrl) !== apiUrl)
|
|
72
|
+
return "";
|
|
67
73
|
try {
|
|
68
74
|
const res = await fetch(`${apiUrl}/api/v1/cli/device/token`, {
|
|
69
75
|
method: "POST",
|
|
@@ -90,7 +96,7 @@ async function fetchWithRenew(url, init, opts) {
|
|
|
90
96
|
return fetch(url, { ...init, headers });
|
|
91
97
|
};
|
|
92
98
|
const res = await send(opts.token);
|
|
93
|
-
if (renewOn.includes(res.status)
|
|
99
|
+
if (renewOn.includes(res.status)) {
|
|
94
100
|
const fresh = await refreshDeviceToken({ apiUrl: opts.apiUrl });
|
|
95
101
|
if (fresh && fresh !== opts.token)
|
|
96
102
|
return { res: await send(fresh), token: fresh };
|
|
@@ -146,9 +152,13 @@ function writeCredentials(fields) {
|
|
|
146
152
|
}
|
|
147
153
|
function readStoredAuth2(apiUrl) {
|
|
148
154
|
const c = read2();
|
|
149
|
-
const apiToken = c[apiUrl] || c.apiToken || c.apiKey || "";
|
|
150
155
|
const host = c.backendHost || "";
|
|
151
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 || "";
|
|
152
162
|
return {
|
|
153
163
|
apiToken: typeof apiToken === "string" ? apiToken : "",
|
|
154
164
|
apiUrl: storedApiUrl,
|
|
@@ -172,6 +182,8 @@ async function refreshDeviceToken2(opts = {}) {
|
|
|
172
182
|
const apiUrl = normalizeApiUrl2(opts.apiUrl || envUrl || stored.apiUrl || DEFAULT_API_URL2);
|
|
173
183
|
if (!stored.deviceId || !stored.deviceSecret)
|
|
174
184
|
return "";
|
|
185
|
+
if (stored.apiUrl && normalizeApiUrl2(stored.apiUrl) !== apiUrl)
|
|
186
|
+
return "";
|
|
175
187
|
try {
|
|
176
188
|
const res = await fetch(`${apiUrl}/api/v1/cli/device/token`, {
|
|
177
189
|
method: "POST",
|
|
@@ -317,9 +329,13 @@ function writeSecure3(data) {
|
|
|
317
329
|
}
|
|
318
330
|
function readStoredAuth3(apiUrl) {
|
|
319
331
|
const c = read3();
|
|
320
|
-
const apiToken = c[apiUrl] || c.apiToken || c.apiKey || "";
|
|
321
332
|
const host = c.backendHost || "";
|
|
322
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 || "";
|
|
323
339
|
return {
|
|
324
340
|
apiToken: typeof apiToken === "string" ? apiToken : "",
|
|
325
341
|
apiUrl: storedApiUrl,
|
|
@@ -343,6 +359,8 @@ async function refreshDeviceToken3(opts = {}) {
|
|
|
343
359
|
const apiUrl = normalizeApiUrl3(opts.apiUrl || envUrl || stored.apiUrl || DEFAULT_API_URL3);
|
|
344
360
|
if (!stored.deviceId || !stored.deviceSecret)
|
|
345
361
|
return "";
|
|
362
|
+
if (stored.apiUrl && normalizeApiUrl3(stored.apiUrl) !== apiUrl)
|
|
363
|
+
return "";
|
|
346
364
|
try {
|
|
347
365
|
const res = await fetch(`${apiUrl}/api/v1/cli/device/token`, {
|
|
348
366
|
method: "POST",
|
|
@@ -369,7 +387,7 @@ async function fetchWithRenew2(url, init, opts) {
|
|
|
369
387
|
return fetch(url, { ...init, headers });
|
|
370
388
|
};
|
|
371
389
|
const res = await send(opts.token);
|
|
372
|
-
if (renewOn.includes(res.status)
|
|
390
|
+
if (renewOn.includes(res.status)) {
|
|
373
391
|
const fresh = await refreshDeviceToken3({ apiUrl: opts.apiUrl });
|
|
374
392
|
if (fresh && fresh !== opts.token)
|
|
375
393
|
return { res: await send(fresh), token: fresh };
|
|
@@ -479,8 +497,8 @@ function checkForUpdates(pkg) {
|
|
|
479
497
|
// package.json
|
|
480
498
|
var package_default = {
|
|
481
499
|
name: "@todoforai/tfa-memory",
|
|
482
|
-
version: "0.4.
|
|
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.",
|
|
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.",
|
|
484
502
|
type: "module",
|
|
485
503
|
bin: {
|
|
486
504
|
"tfa-memory": "dist/cli.js"
|
|
@@ -521,6 +539,10 @@ every TODO under the read-only \`todo\` source (\`todo/<todoId>:<messageId>\`,
|
|
|
521
539
|
live from the backend index, nothing separate to sync). \`--source todo\` searches
|
|
522
540
|
past TODO messages only; \`get todo/<todoId>:<messageId>\` prints the whole message.
|
|
523
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
|
+
|
|
524
546
|
Usage:
|
|
525
547
|
tfa-memory <command> [options] [args...]
|
|
526
548
|
|
|
@@ -528,8 +550,10 @@ Commands:
|
|
|
528
550
|
add [source/ref] <text|-> Save a row. Without an address → manual/<timestamp>.
|
|
529
551
|
"-" reads stdin.
|
|
530
552
|
search <query> Full-text search over saved rows + past TODO messages
|
|
553
|
+
live Re-render L2 working memory (the always-on prompt block)
|
|
531
554
|
ls List saved rows, newest first (todo messages excluded)
|
|
532
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
|
|
533
557
|
rm <source/ref> Delete one row
|
|
534
558
|
rm --source <name> Delete every row in a source
|
|
535
559
|
sources List sources with counts and last write
|
|
@@ -539,7 +563,11 @@ Commands:
|
|
|
539
563
|
Options:
|
|
540
564
|
-s, --source <a,b> Restrict search/ls to sources (comma-separated; "todo" = TODO messages)
|
|
541
565
|
--todo <id> Restrict a todo-message search to one TODO
|
|
542
|
-
|
|
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
|
|
543
571
|
--meta <json> Attach JSON metadata to the row (e.g. '{"todoId":"..."}')
|
|
544
572
|
-n, --limit <n> Max rows (default 5, max 50)
|
|
545
573
|
-j, --json Machine-readable output
|
|
@@ -554,8 +582,10 @@ Examples:
|
|
|
554
582
|
tfa-memory add deploy-notes/rollback "run scripts/rollback.sh, then verify /health"
|
|
555
583
|
cat docs/auth.md | tfa-memory add acme/docs/auth.md -
|
|
556
584
|
tfa-memory search "rollback procedure"
|
|
557
|
-
tfa-memory search "how do we deploy" --semantic
|
|
585
|
+
tfa-memory search "how do we deploy" --mode semantic
|
|
558
586
|
tfa-memory search "android build" --source todo
|
|
587
|
+
tfa-memory live
|
|
588
|
+
tfa-memory live -B 800
|
|
559
589
|
tfa-memory get todo/<todoId>:<messageId>
|
|
560
590
|
tfa-memory ls --source deploy-notes
|
|
561
591
|
tfa-memory get deploy-notes/rollback
|
|
@@ -591,7 +621,10 @@ function resolveMemoryUrl(flag, apiUrl) {
|
|
|
591
621
|
async function api(ctx, method, path2, body) {
|
|
592
622
|
const init = {
|
|
593
623
|
method,
|
|
594
|
-
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
|
+
},
|
|
595
628
|
body: body !== undefined ? JSON.stringify(body) : undefined
|
|
596
629
|
};
|
|
597
630
|
const { res, token } = await fetchWithRenew(`${ctx.memoryUrl}/v1${path2}`, init, { apiUrl: ctx.apiUrl, token: ctx.token, renewOn: [401, 403] });
|
|
@@ -631,8 +664,12 @@ async function main() {
|
|
|
631
664
|
options: {
|
|
632
665
|
source: { type: "string", short: "s" },
|
|
633
666
|
todo: { type: "string" },
|
|
667
|
+
budget: { type: "string", short: "B" },
|
|
634
668
|
meta: { type: "string" },
|
|
635
|
-
|
|
669
|
+
mode: { type: "string" },
|
|
670
|
+
exclude: { type: "string", multiple: true },
|
|
671
|
+
before: { type: "string" },
|
|
672
|
+
after: { type: "string" },
|
|
636
673
|
limit: { type: "string", short: "n" },
|
|
637
674
|
json: { type: "boolean", short: "j", default: false },
|
|
638
675
|
"api-token": { type: "string" },
|
|
@@ -711,12 +748,28 @@ async function main() {
|
|
|
711
748
|
}
|
|
712
749
|
case "search": {
|
|
713
750
|
const query = rest[0] ?? die(`usage: tfa-memory search "query"`, 2);
|
|
714
|
-
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 })}`);
|
|
715
753
|
for (const w of res.warnings ?? [])
|
|
716
754
|
console.error(`Warning: ${w}`);
|
|
755
|
+
if (res.lexicalCount !== undefined)
|
|
756
|
+
console.error(`${res.lexicalCount} row(s) contain a query word`);
|
|
717
757
|
printRows(res.rows, json);
|
|
718
758
|
break;
|
|
719
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
|
+
}
|
|
720
773
|
case "ls": {
|
|
721
774
|
const res = await api(ctx, "GET", `/memory${qs({ sources, limit })}`);
|
|
722
775
|
printRows(res.rows, json);
|
|
@@ -732,6 +785,14 @@ async function main() {
|
|
|
732
785
|
`);
|
|
733
786
|
break;
|
|
734
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
|
+
}
|
|
735
796
|
case "rm":
|
|
736
797
|
case "delete":
|
|
737
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"
|