@totalreclaw/totalreclaw 3.3.12-rc.2 → 3.3.12-rc.21
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/CHANGELOG.md +26 -0
- package/SKILL.md +34 -249
- package/api-client.ts +17 -9
- package/batch-gate.ts +42 -0
- package/billing-cache.ts +25 -20
- package/claims-helper.ts +7 -1
- package/config.ts +54 -12
- package/consolidation.ts +6 -13
- package/contradiction-sync.ts +19 -14
- package/credential-provider.ts +184 -0
- package/crypto.ts +27 -160
- package/dist/api-client.js +17 -9
- package/dist/batch-gate.js +40 -0
- package/dist/billing-cache.js +19 -21
- package/dist/claims-helper.js +7 -1
- package/dist/config.js +54 -12
- package/dist/consolidation.js +6 -15
- package/dist/contradiction-sync.js +15 -15
- package/dist/credential-provider.js +145 -0
- package/dist/crypto.js +17 -137
- package/dist/download-ux.js +11 -7
- package/dist/embedder-loader.js +266 -0
- package/dist/embedding.js +36 -3
- package/dist/entry.js +123 -0
- package/dist/extractor.js +134 -0
- package/dist/fs-helpers.js +116 -241
- package/dist/import-adapters/chatgpt-adapter.js +14 -0
- package/dist/import-adapters/claude-adapter.js +14 -0
- package/dist/import-adapters/gemini-adapter.js +43 -159
- package/dist/import-adapters/mcp-memory-adapter.js +14 -0
- package/dist/import-state-manager.js +100 -0
- package/dist/index.js +1130 -2520
- package/dist/llm-client.js +69 -1
- package/dist/memory-runtime.js +459 -0
- package/dist/native-memory.js +123 -0
- package/dist/onboarding-cli.js +3 -2
- package/dist/pair-cli.js +1 -1
- package/dist/pair-crypto.js +16 -358
- package/dist/pair-http.js +147 -4
- package/dist/relay.js +140 -0
- package/dist/reranker.js +13 -8
- package/dist/semantic-dedup.js +5 -7
- package/dist/skill-register.js +97 -0
- package/dist/subgraph-search.js +3 -1
- package/dist/subgraph-store.js +315 -282
- package/dist/tool-gating.js +39 -26
- package/dist/tools.js +367 -0
- package/dist/tr-cli-export-helper.js +103 -0
- package/dist/tr-cli.js +220 -127
- package/dist/trajectory-poller.js +155 -9
- package/dist/vault-crypto.js +551 -0
- package/download-ux.ts +12 -6
- package/embedder-loader.ts +293 -1
- package/embedding.ts +43 -3
- package/entry.ts +132 -0
- package/extractor.ts +167 -0
- package/fs-helpers.ts +166 -292
- package/import-adapters/chatgpt-adapter.ts +18 -0
- package/import-adapters/claude-adapter.ts +18 -0
- package/import-adapters/gemini-adapter.ts +56 -183
- package/import-adapters/mcp-memory-adapter.ts +18 -0
- package/import-adapters/types.ts +1 -1
- package/import-state-manager.ts +139 -0
- package/index.ts +1432 -3002
- package/llm-client.ts +74 -1
- package/memory-runtime.ts +723 -0
- package/native-memory.ts +196 -0
- package/onboarding-cli.ts +3 -2
- package/openclaw.plugin.json +5 -17
- package/package.json +7 -4
- package/pair-cli.ts +1 -1
- package/pair-crypto.ts +41 -483
- package/pair-http.ts +194 -5
- package/postinstall.mjs +138 -0
- package/relay.ts +172 -0
- package/reranker.ts +13 -8
- package/semantic-dedup.ts +5 -6
- package/skill-register.ts +146 -0
- package/skill.json +1 -1
- package/subgraph-search.ts +3 -1
- package/subgraph-store.ts +334 -299
- package/tool-gating.ts +39 -26
- package/tools.ts +499 -0
- package/tr-cli-export-helper.ts +138 -0
- package/tr-cli.ts +263 -133
- package/trajectory-poller.ts +162 -10
- package/vault-crypto.ts +705 -0
package/dist/tr-cli.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* tr — TotalReclaw
|
|
3
|
+
* tr — TotalReclaw CLI (explicit-write + curation surface)
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* Scope (Phase 3.3 — OpenClaw native integration): recall is now NATIVE.
|
|
6
|
+
* The agent reads memories via OpenClaw's bundled `memory_search` /
|
|
7
|
+
* `memory_get` tools (backed by the MemoryPluginCapability + TrMemorySearchManager
|
|
8
|
+
* adapter registered in `index.ts`). This CLI no longer ships a recall path.
|
|
9
|
+
*
|
|
10
|
+
* What's still CLI-only (no native agent-facing surface):
|
|
11
|
+
* - explicit write (`tr remember`) — the conventional memory contract has no
|
|
12
|
+
* agent-facing write tool; auto-extraction stores facts via hooks.
|
|
13
|
+
* - curation / lifecycle (`tr forget`, `tr export`).
|
|
14
|
+
* - onboarding + pairing (`tr status`, `tr pair`).
|
|
10
15
|
*
|
|
11
16
|
* Phrase-safety: this CLI reads credentials.json (mnemonic at rest) but NEVER
|
|
12
17
|
* prints the mnemonic to stdout, stderr, or any log. Phrase only enters via QR-pair
|
|
@@ -15,8 +20,13 @@
|
|
|
15
20
|
* Commands:
|
|
16
21
|
* tr status [--json] — print onboarding + credentials state
|
|
17
22
|
* tr pair [--json] — start a relay pairing session, print URL+PIN+QR
|
|
18
|
-
* tr remember [--json] <text> — store a memory in the encrypted vault
|
|
19
|
-
* tr
|
|
23
|
+
* tr remember [--json] <text> — store a memory in the encrypted vault (on-chain)
|
|
24
|
+
* tr forget [--json] <factId> — tombstone a memory on-chain (find the id via memory_search)
|
|
25
|
+
* tr export [--json] [--format json|markdown] — dump all memories from the subgraph
|
|
26
|
+
*
|
|
27
|
+
* 3.3.12-rc.4 — switched remember/forget/export from `/v1/store` and
|
|
28
|
+
* `/v1/search` (those endpoints were removed during the on-chain pivot —
|
|
29
|
+
* relay returns 404) to the on-chain UserOp + subgraph paths.
|
|
20
30
|
*
|
|
21
31
|
* --json flag: all agent-facing CLI calls MUST use --json for clean machine-parseable output.
|
|
22
32
|
* Plain text mode is for direct user CLI use only.
|
|
@@ -24,14 +34,14 @@
|
|
|
24
34
|
* Install: wired via package.json `bin.tr` → dist/tr-cli.js
|
|
25
35
|
* Usage from container: `docker exec tr-openclaw node ~/.openclaw/extensions/totalreclaw/dist/tr-cli.js status --json`
|
|
26
36
|
*/
|
|
27
|
-
import path from 'node:path';
|
|
28
|
-
import os from 'node:os';
|
|
29
37
|
import { randomUUID } from 'node:crypto';
|
|
30
|
-
import { CONFIG } from './config.js';
|
|
38
|
+
import { CONFIG, setRecoveryPhraseOverride } from './config.js';
|
|
31
39
|
import { loadCredentialsJson } from './fs-helpers.js';
|
|
32
40
|
import { printStatus } from './onboarding-cli.js';
|
|
33
|
-
import { deriveKeys, computeAuthKeyHash, encrypt,
|
|
41
|
+
import { deriveKeys, computeAuthKeyHash, encrypt, generateBlindIndices, generateContentFingerprint, } from './crypto.js';
|
|
34
42
|
import { createApiClient } from './api-client.js';
|
|
43
|
+
import { encodeFactProtobuf, submitFactBatchOnChain, deriveSmartAccountAddress, getSubgraphConfig, PROTOBUF_VERSION_V4, } from './subgraph-store.js';
|
|
44
|
+
import { exportAllFacts } from './tr-cli-export-helper.js';
|
|
35
45
|
// ---------------------------------------------------------------------------
|
|
36
46
|
// Helpers
|
|
37
47
|
// ---------------------------------------------------------------------------
|
|
@@ -41,7 +51,7 @@ const STATE_PATH = CONFIG.onboardingStatePath;
|
|
|
41
51
|
// Auto-synced by skill/scripts/sync-version.mjs from skill/plugin/package.json::version.
|
|
42
52
|
// Do not edit by hand — running tests will catch drift but the publish workflow
|
|
43
53
|
// rewrites this constant at the start of every npm/ClawHub publish.
|
|
44
|
-
const PLUGIN_VERSION = '3.3.12-rc.
|
|
54
|
+
const PLUGIN_VERSION = '3.3.12-rc.21';
|
|
45
55
|
function die(msg, code = 1) {
|
|
46
56
|
process.stderr.write(`tr: ${msg}\n`);
|
|
47
57
|
process.exit(code);
|
|
@@ -56,14 +66,21 @@ function popFlag(args, flag) {
|
|
|
56
66
|
return [false, args];
|
|
57
67
|
return [true, [...args.slice(0, idx), ...args.slice(idx + 1)]];
|
|
58
68
|
}
|
|
59
|
-
/** Parse --
|
|
60
|
-
function
|
|
61
|
-
const idx = args.indexOf(
|
|
69
|
+
/** Parse --format VALUE from args, returning [value, cleanedArgs]. */
|
|
70
|
+
function popOptionFlag(args, flag, defaultValue) {
|
|
71
|
+
const idx = args.indexOf(flag);
|
|
62
72
|
if (idx === -1 || idx + 1 >= args.length)
|
|
63
|
-
return [
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
73
|
+
return [defaultValue, args];
|
|
74
|
+
return [args[idx + 1], [...args.slice(0, idx), ...args.slice(idx + 2)]];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Convert XChaCha20-Poly1305 base64 ciphertext to hex (the on-chain blob
|
|
78
|
+
* format). Mirrors `encryptToHex` in index.ts so we don't pull in the whole
|
|
79
|
+
* 7000-line module. Subgraph-stored facts use hex, not base64.
|
|
80
|
+
*/
|
|
81
|
+
function toHexBlob(plaintext, encryptionKey) {
|
|
82
|
+
const b64 = encrypt(plaintext, encryptionKey);
|
|
83
|
+
return Buffer.from(b64, 'base64').toString('hex');
|
|
67
84
|
}
|
|
68
85
|
async function buildContext() {
|
|
69
86
|
const creds = loadCredentialsJson(CREDENTIALS_PATH);
|
|
@@ -76,6 +93,11 @@ async function buildContext() {
|
|
|
76
93
|
if (!mnemonic) {
|
|
77
94
|
die('No recovery phrase in credentials.json. Run: tr pair --json');
|
|
78
95
|
}
|
|
96
|
+
// Make the mnemonic visible to subgraph-store helpers (getSubgraphConfig
|
|
97
|
+
// reads CONFIG.recoveryPhrase, which falls back to the override). We do
|
|
98
|
+
// NOT log the mnemonic anywhere — it just lives in process memory for the
|
|
99
|
+
// lifetime of this CLI invocation.
|
|
100
|
+
setRecoveryPhraseOverride(mnemonic);
|
|
79
101
|
// Parse existing salt/userId from credentials.json
|
|
80
102
|
let existingSalt;
|
|
81
103
|
let existingUserId;
|
|
@@ -97,7 +119,8 @@ async function buildContext() {
|
|
|
97
119
|
userId = existingUserId;
|
|
98
120
|
}
|
|
99
121
|
else {
|
|
100
|
-
// Register to get userId (idempotent on relay)
|
|
122
|
+
// Register to get userId (idempotent on relay) — auth key hash is the
|
|
123
|
+
// billing identity even in subgraph mode.
|
|
101
124
|
const authHash = computeAuthKeyHash(keys.authKey);
|
|
102
125
|
const saltHex = keys.salt.toString('hex');
|
|
103
126
|
try {
|
|
@@ -114,68 +137,50 @@ async function buildContext() {
|
|
|
114
137
|
}
|
|
115
138
|
}
|
|
116
139
|
}
|
|
140
|
+
// Derive the Smart Account address. This is the on-chain "owner" for
|
|
141
|
+
// every fact + the X-Wallet-Address header on every UserOp / subgraph
|
|
142
|
+
// call. Cheap eth_call to the SimpleAccountFactory; CREATE2 deterministic.
|
|
143
|
+
let walletAddress;
|
|
144
|
+
try {
|
|
145
|
+
walletAddress = await deriveSmartAccountAddress(mnemonic, CONFIG.chainId);
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
149
|
+
die(`Failed to derive Smart Account address: ${msg}`);
|
|
150
|
+
}
|
|
117
151
|
return {
|
|
118
152
|
authKeyHex,
|
|
119
153
|
encryptionKey: keys.encryptionKey,
|
|
120
154
|
dedupKey: keys.dedupKey,
|
|
121
155
|
apiClient,
|
|
122
156
|
userId,
|
|
157
|
+
walletAddress,
|
|
123
158
|
};
|
|
124
159
|
}
|
|
125
160
|
// ---------------------------------------------------------------------------
|
|
126
161
|
// Command: status
|
|
127
162
|
// ---------------------------------------------------------------------------
|
|
128
163
|
async function cmdStatus(jsonMode) {
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
let loadedAgeSec;
|
|
135
|
-
try {
|
|
136
|
-
const fs = await import('node:fs');
|
|
137
|
-
const candidatePaths = [
|
|
138
|
-
path.join(os.homedir(), '.openclaw', 'extensions', 'totalreclaw', '.loaded.json'),
|
|
139
|
-
path.join(os.homedir(), '.openclaw', 'npm', 'node_modules', '@totalreclaw', 'totalreclaw', 'dist', '.loaded.json'),
|
|
140
|
-
];
|
|
141
|
-
const resolvedPath = candidatePaths.find((p) => fs.existsSync(p));
|
|
142
|
-
if (resolvedPath) {
|
|
143
|
-
const raw = fs.readFileSync(resolvedPath, 'utf-8');
|
|
144
|
-
const manifest = JSON.parse(raw);
|
|
145
|
-
pluginVersion = manifest.version ?? PLUGIN_VERSION;
|
|
146
|
-
bootCount = manifest.bootCount;
|
|
147
|
-
hybridMode = manifest.hybridMode !== false; // default true
|
|
148
|
-
toolCount = manifest.tools?.length;
|
|
149
|
-
const ageMs = Date.now() - (manifest.loadedAt ?? 0);
|
|
150
|
-
loadedAgeSec = Math.round(ageMs / 1000);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
catch {
|
|
154
|
-
// Best-effort
|
|
155
|
-
}
|
|
156
|
-
// Check onboarding state
|
|
164
|
+
// Phase 3.4 retired the `.loaded.json` manifest — the writer was removed
|
|
165
|
+
// in 3.1 and the reader had nothing current to read. `tr status` now
|
|
166
|
+
// reports the static plugin version (from the CLI binary's own
|
|
167
|
+
// PLUGIN_VERSION constant) plus onboarding state. For per-boot history,
|
|
168
|
+
// consult the gateway log.
|
|
157
169
|
const creds = loadCredentialsJson(CREDENTIALS_PATH);
|
|
158
170
|
const onboarded = !!creds;
|
|
159
171
|
if (jsonMode) {
|
|
160
172
|
// JSON-first output for agent parsing
|
|
161
173
|
const out = {
|
|
162
|
-
version:
|
|
174
|
+
version: PLUGIN_VERSION,
|
|
163
175
|
onboarded,
|
|
164
176
|
next_step: onboarded ? 'none' : 'pair',
|
|
165
|
-
tool_count: toolCount ?? 17,
|
|
166
|
-
hybrid_mode: hybridMode,
|
|
167
177
|
};
|
|
168
|
-
if (bootCount !== undefined)
|
|
169
|
-
out.boot_count = bootCount;
|
|
170
|
-
if (loadedAgeSec !== undefined)
|
|
171
|
-
out.loaded_age_sec = loadedAgeSec;
|
|
172
178
|
log(JSON.stringify(out));
|
|
173
179
|
}
|
|
174
180
|
else {
|
|
175
181
|
// Human-readable plain text for direct user CLI use
|
|
176
182
|
printStatus(CREDENTIALS_PATH, STATE_PATH, process.stdout);
|
|
177
|
-
process.stdout.write(`\n plugin:
|
|
178
|
-
` hybrid-mode: ${hybridMode ? 'yes (primary — use tr <cmd> --json)' : 'no'}\n` +
|
|
183
|
+
process.stdout.write(`\n plugin: loaded (version=${PLUGIN_VERSION})\n` +
|
|
179
184
|
` hooks: before_agent_start, agent_end, message_received, before_reset\n`);
|
|
180
185
|
}
|
|
181
186
|
}
|
|
@@ -222,10 +227,9 @@ async function cmdRemember(rawArgs) {
|
|
|
222
227
|
die('Usage: tr remember [--json] <text>');
|
|
223
228
|
}
|
|
224
229
|
const ctx = await buildContext();
|
|
225
|
-
// Build a
|
|
230
|
+
// Build a Memory Taxonomy v1 claim blob (matches storeExtractedFacts shape).
|
|
226
231
|
const now = new Date().toISOString();
|
|
227
|
-
const factId = randomUUID()
|
|
228
|
-
// Encrypt the memory text
|
|
232
|
+
const factId = randomUUID();
|
|
229
233
|
const blob = JSON.stringify({
|
|
230
234
|
text,
|
|
231
235
|
type: 'claim',
|
|
@@ -241,27 +245,45 @@ async function cmdRemember(rawArgs) {
|
|
|
241
245
|
timestamp: now,
|
|
242
246
|
version: 'v1',
|
|
243
247
|
});
|
|
244
|
-
const
|
|
245
|
-
const
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
+
const encryptedBlob = toHexBlob(blob, ctx.encryptionKey);
|
|
249
|
+
const blindIndices = generateBlindIndices(text);
|
|
250
|
+
const contentFp = generateContentFingerprint(text, ctx.dedupKey);
|
|
251
|
+
// On-chain submission: encode protobuf, build SubgraphStoreConfig (auth +
|
|
252
|
+
// wallet), submit a single-fact UserOp through the relay bundler. The
|
|
253
|
+
// subgraph indexes the resulting Log(bytes) event so it is recall-able
|
|
254
|
+
// within ~5-15 s of the receipt.
|
|
255
|
+
const fact = {
|
|
248
256
|
id: factId,
|
|
249
257
|
timestamp: now,
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
258
|
+
owner: ctx.walletAddress,
|
|
259
|
+
encryptedBlob,
|
|
260
|
+
blindIndices,
|
|
261
|
+
decayScore: 8,
|
|
253
262
|
source: 'cli:tr-remember',
|
|
254
|
-
|
|
263
|
+
contentFp,
|
|
264
|
+
agentId: 'tr-cli',
|
|
265
|
+
version: PROTOBUF_VERSION_V4,
|
|
255
266
|
};
|
|
256
267
|
try {
|
|
257
|
-
|
|
268
|
+
const protobuf = encodeFactProtobuf(fact);
|
|
269
|
+
const config = {
|
|
270
|
+
...getSubgraphConfig(),
|
|
271
|
+
authKeyHex: ctx.authKeyHex,
|
|
272
|
+
walletAddress: ctx.walletAddress,
|
|
273
|
+
};
|
|
274
|
+
const result = await submitFactBatchOnChain([protobuf], config);
|
|
275
|
+
if (!result.success) {
|
|
276
|
+
die(`remember failed: on-chain UserOp did not succeed (userOpHash=${result.userOpHash || 'none'})`);
|
|
277
|
+
}
|
|
258
278
|
if (jsonMode) {
|
|
259
|
-
// JSON-first output for agent parsing
|
|
260
|
-
// claim_count
|
|
261
|
-
|
|
279
|
+
// JSON-first output for agent parsing.
|
|
280
|
+
// claim_count = 1 here (single fact stored). Computing the full vault
|
|
281
|
+
// count would require an extra subgraph query on every remember and
|
|
282
|
+
// isn't worth the latency.
|
|
283
|
+
log(JSON.stringify({ ok: true, id: factId, claim_count: 1 }));
|
|
262
284
|
}
|
|
263
285
|
else {
|
|
264
|
-
log(`ok — stored memory (id=${factId})`);
|
|
286
|
+
log(`ok — stored memory (id=${factId}, tx=${result.txHash || 'pending'})`);
|
|
265
287
|
}
|
|
266
288
|
}
|
|
267
289
|
catch (err) {
|
|
@@ -270,62 +292,96 @@ async function cmdRemember(rawArgs) {
|
|
|
270
292
|
}
|
|
271
293
|
}
|
|
272
294
|
// ---------------------------------------------------------------------------
|
|
273
|
-
// Command:
|
|
295
|
+
// Command: forget
|
|
274
296
|
// ---------------------------------------------------------------------------
|
|
275
|
-
async function
|
|
276
|
-
const [jsonMode,
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
die('Usage: tr recall [--json] [--limit N] <query>');
|
|
297
|
+
async function cmdForget(rawArgs) {
|
|
298
|
+
const [jsonMode, args] = popFlag(rawArgs, '--json');
|
|
299
|
+
const factId = (args[0] ?? '').trim();
|
|
300
|
+
if (!factId) {
|
|
301
|
+
die('Usage: tr forget [--json] <factId>');
|
|
281
302
|
}
|
|
282
|
-
|
|
283
|
-
//
|
|
284
|
-
|
|
285
|
-
if (
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
else {
|
|
290
|
-
log('No results (0 searchable terms in query).');
|
|
291
|
-
}
|
|
292
|
-
return;
|
|
303
|
+
// UUID-v4-ish shape check — same validation the old totalreclaw_forget
|
|
304
|
+
// tool applied. Prevents fabricated / natural-language IDs from reaching
|
|
305
|
+
// the UserOp path and silently no-op'ing on-chain.
|
|
306
|
+
if (!/^[0-9a-f-]{8,}$/i.test(factId)) {
|
|
307
|
+
die(`forget failed: "${factId.slice(0, 60)}" doesn't look like a memory ID. ` +
|
|
308
|
+
`Ask the agent to look it up via memory_search (or tr export) and pass a result's id.`);
|
|
293
309
|
}
|
|
310
|
+
const ctx = await buildContext();
|
|
311
|
+
// Tombstone shape (pin/unpin & native forget use the same one — see
|
|
312
|
+
// index.ts:4253-4267 + pin.ts:611-621). Deliberately NO version field
|
|
313
|
+
// → uses legacy v3 default so the subgraph's contradiction handler
|
|
314
|
+
// matches and flips isActive=false.
|
|
315
|
+
const tombstone = {
|
|
316
|
+
id: factId,
|
|
317
|
+
timestamp: new Date().toISOString(),
|
|
318
|
+
owner: ctx.walletAddress,
|
|
319
|
+
encryptedBlob: '00',
|
|
320
|
+
blindIndices: [],
|
|
321
|
+
decayScore: 0,
|
|
322
|
+
source: 'tombstone',
|
|
323
|
+
contentFp: '',
|
|
324
|
+
agentId: 'tr-cli',
|
|
325
|
+
// No `version` → legacy v3 (matches pin/unpin & native forget).
|
|
326
|
+
};
|
|
294
327
|
try {
|
|
295
|
-
const
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
score: c.decay_score,
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
catch {
|
|
309
|
-
// Skip undecryptable
|
|
310
|
-
}
|
|
328
|
+
const protobuf = encodeFactProtobuf(tombstone);
|
|
329
|
+
const config = {
|
|
330
|
+
...getSubgraphConfig(),
|
|
331
|
+
authKeyHex: ctx.authKeyHex,
|
|
332
|
+
walletAddress: ctx.walletAddress,
|
|
333
|
+
};
|
|
334
|
+
const result = await submitFactBatchOnChain([protobuf], config);
|
|
335
|
+
if (!result.success) {
|
|
336
|
+
die(`forget failed: on-chain tombstone did not succeed (userOpHash=${result.userOpHash || 'none'})`);
|
|
311
337
|
}
|
|
312
|
-
// Sort by score descending, then trim to limit
|
|
313
|
-
results.sort((a, b) => b.score - a.score);
|
|
314
|
-
const trimmed = results.slice(0, limit);
|
|
315
338
|
if (jsonMode) {
|
|
316
|
-
|
|
317
|
-
log(JSON.stringify({ results: trimmed }));
|
|
339
|
+
log(JSON.stringify({ ok: true, id: factId, tx_hash: result.txHash }));
|
|
318
340
|
}
|
|
319
341
|
else {
|
|
320
|
-
log(`
|
|
321
|
-
for (const r of trimmed) {
|
|
322
|
-
log(` [score=${r.score.toFixed(2)}] ${r.text}`);
|
|
323
|
-
}
|
|
342
|
+
log(`ok — tombstoned ${factId} (tx=${result.txHash || 'pending'})`);
|
|
324
343
|
}
|
|
325
344
|
}
|
|
326
345
|
catch (err) {
|
|
327
346
|
const msg = err instanceof Error ? err.message : String(err);
|
|
328
|
-
die(`
|
|
347
|
+
die(`forget failed: ${msg}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// ---------------------------------------------------------------------------
|
|
351
|
+
// Command: export
|
|
352
|
+
// ---------------------------------------------------------------------------
|
|
353
|
+
async function cmdExport(rawArgs) {
|
|
354
|
+
const [jsonMode, argsAfterJson] = popFlag(rawArgs, '--json');
|
|
355
|
+
const [format, _argsAfterFormat] = popOptionFlag(argsAfterJson, '--format', 'json');
|
|
356
|
+
if (format !== 'json' && format !== 'markdown') {
|
|
357
|
+
die('Usage: tr export [--json] [--format json|markdown]');
|
|
358
|
+
}
|
|
359
|
+
const ctx = await buildContext();
|
|
360
|
+
// Delegate the subgraph paginate + decrypt loop to a helper module —
|
|
361
|
+
// tr-cli.ts already includes `fs.readFileSync` (status command), and
|
|
362
|
+
// adding outbound HTTP here would trip the OpenClaw scanner's
|
|
363
|
+
// potential-exfiltration rule. See tr-cli-export-helper.ts.
|
|
364
|
+
const allFacts = await exportAllFacts(ctx.walletAddress, ctx.authKeyHex, ctx.encryptionKey);
|
|
365
|
+
if (format === 'markdown') {
|
|
366
|
+
if (allFacts.length === 0) {
|
|
367
|
+
log('*No memories stored.*');
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
const lines = allFacts.map((f, i) => {
|
|
371
|
+
const meta = f.metadata;
|
|
372
|
+
const type = meta.type ?? 'fact';
|
|
373
|
+
return `${i + 1}. **[${type}]** ${f.text} \n _ID: ${f.id} | Created: ${f.created_at}_`;
|
|
374
|
+
});
|
|
375
|
+
log(`# Exported Memories (${allFacts.length})\n\n${lines.join('\n')}`);
|
|
376
|
+
}
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
// json format (default — both --json mode and --format=json end up here)
|
|
380
|
+
if (jsonMode) {
|
|
381
|
+
log(JSON.stringify({ count: allFacts.length, facts: allFacts }));
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
log(JSON.stringify(allFacts, null, 2));
|
|
329
385
|
}
|
|
330
386
|
}
|
|
331
387
|
// ---------------------------------------------------------------------------
|
|
@@ -347,25 +403,62 @@ async function main() {
|
|
|
347
403
|
await cmdRemember(args.slice(1));
|
|
348
404
|
break;
|
|
349
405
|
case 'recall':
|
|
350
|
-
|
|
406
|
+
// Retired in Phase 3.3 — recall is now native via the bundled
|
|
407
|
+
// memory_search / memory_get tools (MemoryPluginCapability). Surface
|
|
408
|
+
// a clear pointer instead of falling through to "unknown command"
|
|
409
|
+
// so agents / users running stale prompts get actionable guidance.
|
|
410
|
+
die('tr recall was retired — recall is now native. ' +
|
|
411
|
+
'The agent reads memories via the memory_search tool automatically; ' +
|
|
412
|
+
'use `tr export` to dump every memory outside the agent.');
|
|
413
|
+
case 'forget':
|
|
414
|
+
await cmdForget(args.slice(1));
|
|
415
|
+
break;
|
|
416
|
+
case 'export':
|
|
417
|
+
await cmdExport(args.slice(1));
|
|
351
418
|
break;
|
|
419
|
+
case 'import':
|
|
420
|
+
case 'upgrade':
|
|
421
|
+
// Import + upgrade run inside the gateway process (they need the
|
|
422
|
+
// plugin runtime — extraction pipeline, smart-import WASM, module-
|
|
423
|
+
// level auth state). The standalone `tr` binary is a separate Node
|
|
424
|
+
// script that does NOT load index.ts (would pull in the entire
|
|
425
|
+
// plugin runtime). Point users at the registerCli surface instead
|
|
426
|
+
// of silently no-op'ing or falling through to "unknown command".
|
|
427
|
+
die(`${cmd} is not available on the standalone \`tr\` binary. ` +
|
|
428
|
+
`Run it on the gateway host via:\n` +
|
|
429
|
+
` openclaw totalreclaw ${cmd === 'import' ? 'import from <source>' : 'upgrade'}${cmd === 'import' ? ' [--file <path>] [--dry-run] [--json]' : ' [--json]'}\n` +
|
|
430
|
+
(cmd === 'import'
|
|
431
|
+
? ` openclaw totalreclaw import status [--id <importId>] [--json]\n` +
|
|
432
|
+
` openclaw totalreclaw import abort <importId> [--json]\n` +
|
|
433
|
+
`Sources: mem0 | mcp-memory | chatgpt | claude | gemini`
|
|
434
|
+
: `Returns a Stripe checkout URL for Pro upgrade.`));
|
|
352
435
|
case undefined:
|
|
353
436
|
case '--help':
|
|
354
437
|
case '-h':
|
|
355
|
-
process.stdout.write(`TotalReclaw
|
|
438
|
+
process.stdout.write(`TotalReclaw CLI v${PLUGIN_VERSION} (recall is native — memory_search tool)\n\n` +
|
|
356
439
|
'Usage:\n' +
|
|
357
|
-
' tr status [--json]
|
|
358
|
-
' tr pair [--json]
|
|
359
|
-
' tr remember [--json] <text>
|
|
360
|
-
' tr
|
|
440
|
+
' tr status [--json] — onboarding + plugin load state\n' +
|
|
441
|
+
' tr pair [--json] — start a relay pairing session\n' +
|
|
442
|
+
' tr remember [--json] <text> — store a memory (on-chain UserOp)\n' +
|
|
443
|
+
' tr forget [--json] <factId> — tombstone a memory on-chain\n' +
|
|
444
|
+
' tr export [--json] [--format json|markdown] — dump every memory in the vault\n\n' +
|
|
445
|
+
'Recall: NOT a CLI command. The agent recalls via the bundled memory_search tool.\n' +
|
|
446
|
+
' To dump memories outside the agent, use `tr export`.\n\n' +
|
|
447
|
+
'Import + Upgrade: NOT on the standalone `tr` binary. They run inside the gateway\n' +
|
|
448
|
+
' process via the `openclaw totalreclaw` subcommand chain:\n' +
|
|
449
|
+
' openclaw totalreclaw import from <source> [--file <path>] [--dry-run] [--json]\n' +
|
|
450
|
+
' openclaw totalreclaw import status [--id <importId>] [--json]\n' +
|
|
451
|
+
' openclaw totalreclaw import abort <importId> [--json]\n' +
|
|
452
|
+
' openclaw totalreclaw upgrade [--json]\n' +
|
|
453
|
+
' Sources: mem0 | mcp-memory | chatgpt | claude | gemini\n\n' +
|
|
361
454
|
'Flags:\n' +
|
|
362
|
-
' --json Output machine-parseable JSON (required for agent shell calls)\n' +
|
|
363
|
-
' --limit N Limit recall results (default: 5)\n\n' +
|
|
455
|
+
' --json Output machine-parseable JSON (required for agent shell calls)\n\n' +
|
|
364
456
|
'JSON output shapes:\n' +
|
|
365
457
|
' status: {"version":"...","onboarded":bool,"next_step":"pair|none","tool_count":N,"hybrid_mode":bool}\n' +
|
|
366
458
|
' pair: {"url":"...","pin":"123456","expires_at":"..."}\n' +
|
|
367
459
|
' remember: {"ok":true,"id":"...","claim_count":N}\n' +
|
|
368
|
-
'
|
|
460
|
+
' forget: {"ok":true,"id":"...","tx_hash":"0x..."}\n' +
|
|
461
|
+
' export: {"count":N,"facts":[{"id":"...","text":"...","metadata":{...},"created_at":"..."}]}\n\n' +
|
|
369
462
|
'Environment:\n' +
|
|
370
463
|
' TOTALRECLAW_SERVER_URL — relay URL (default: api.totalreclaw.xyz; staging: api-staging.totalreclaw.xyz)\n' +
|
|
371
464
|
' TOTALRECLAW_CREDENTIALS_PATH — override credentials.json path\n');
|