@totalreclaw/totalreclaw 1.3.0 → 1.4.0

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 (3) hide show
  1. package/README.md +2 -2
  2. package/index.ts +135 -6
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -79,9 +79,9 @@ Most of the time you won't use these directly -- the automatic hooks handle memo
79
79
  | Tier | Memories | Reads | Storage | Price |
80
80
  |------|----------|-------|---------|-------|
81
81
  | **Free** | 500/month | Unlimited | Testnet (trial) | $0 |
82
- | **Pro** | Unlimited | Unlimited | Permanent on-chain (Gnosis) | $5/month |
82
+ | **Pro** | Unlimited | Unlimited | Permanent on-chain (Gnosis) | See `totalreclaw_status` |
83
83
 
84
- Pay with card via Stripe. Counter resets monthly.
84
+ Pay with card via Stripe. Use `totalreclaw_status` to check current pricing. Counter resets monthly.
85
85
 
86
86
  ## Using with Other Agents
87
87
 
package/index.ts CHANGED
@@ -9,6 +9,7 @@
9
9
  * - totalreclaw_status -- check billing/subscription status
10
10
  * - totalreclaw_consolidate -- scan and merge near-duplicate memories
11
11
  * - totalreclaw_import_from -- import memories from other tools (Mem0, MCP Memory, etc.)
12
+ * - totalreclaw_upgrade -- create Stripe checkout for Pro upgrade
12
13
  *
13
14
  * Also registers a `before_agent_start` hook that automatically injects
14
15
  * relevant memories into the agent's context.
@@ -134,6 +135,9 @@ const MAX_FACTS_PER_EXTRACTION = 15;
134
135
  // Store-time near-duplicate detection (consolidation module)
135
136
  const STORE_DEDUP_ENABLED = process.env.TOTALRECLAW_STORE_DEDUP !== 'false';
136
137
 
138
+ // One-time welcome-back message for returning Pro users (set during init, consumed by first before_agent_start)
139
+ let welcomeBackMessage: string | null = null;
140
+
137
141
  // B2: Minimum relevance threshold — cosine below this means no memory injection
138
142
  const RELEVANCE_THRESHOLD = parseFloat(process.env.TOTALRECLAW_RELEVANCE_THRESHOLD ?? '0.3');
139
143
 
@@ -421,6 +425,45 @@ async function initialize(logger: OpenClawPluginApi['logger']): Promise<void> {
421
425
  subgraphOwner = userId;
422
426
  }
423
427
  }
428
+
429
+ // One-time billing check for returning users (imported recovery phrase).
430
+ // If they already have an active Pro subscription, inform them on next conversation start.
431
+ if (existingUserId && authKeyHex) {
432
+ try {
433
+ const walletAddr = subgraphOwner || userId || '';
434
+ if (walletAddr) {
435
+ const billingUrl = (process.env.TOTALRECLAW_SERVER_URL || 'https://api.totalreclaw.xyz').replace(/\/+$/, '');
436
+ const resp = await fetch(`${billingUrl}/v1/billing/status?wallet_address=${encodeURIComponent(walletAddr)}`, {
437
+ method: 'GET',
438
+ headers: {
439
+ 'Authorization': `Bearer ${authKeyHex}`,
440
+ 'Accept': 'application/json',
441
+ 'X-TotalReclaw-Client': 'openclaw-plugin',
442
+ },
443
+ });
444
+ if (resp.ok) {
445
+ const billingData = await resp.json() as Record<string, unknown>;
446
+ const tier = billingData.tier as string;
447
+ const expiresAt = billingData.expires_at as string | undefined;
448
+ // Populate billing cache for future use.
449
+ writeBillingCache({
450
+ tier: tier || 'free',
451
+ free_writes_used: (billingData.free_writes_used as number) ?? 0,
452
+ free_writes_limit: (billingData.free_writes_limit as number) ?? 0,
453
+ features: billingData.features as BillingCache['features'] | undefined,
454
+ checked_at: Date.now(),
455
+ });
456
+ if (tier === 'pro' && expiresAt) {
457
+ const expiryDate = new Date(expiresAt).toLocaleDateString();
458
+ welcomeBackMessage = `Welcome back! Your Pro subscription is active (expires: ${expiryDate}).`;
459
+ logger.info(`Returning Pro user detected — expires ${expiryDate}`);
460
+ }
461
+ }
462
+ }
463
+ } catch {
464
+ // Best-effort — don't block initialization on billing check failure.
465
+ }
466
+ }
424
467
  }
425
468
 
426
469
  function isDocker(): boolean {
@@ -2183,6 +2226,85 @@ const plugin = {
2183
2226
  { name: 'totalreclaw_import_from' },
2184
2227
  );
2185
2228
 
2229
+ // ---------------------------------------------------------------
2230
+ // Tool: totalreclaw_upgrade
2231
+ // ---------------------------------------------------------------
2232
+
2233
+ api.registerTool(
2234
+ {
2235
+ name: 'totalreclaw_upgrade',
2236
+ label: 'Upgrade to Pro',
2237
+ description:
2238
+ 'Upgrade to TotalReclaw Pro for unlimited encrypted memories. ' +
2239
+ 'Returns a Stripe checkout URL for the user to complete payment via credit/debit card.',
2240
+ parameters: {
2241
+ type: 'object',
2242
+ properties: {},
2243
+ additionalProperties: false,
2244
+ },
2245
+ async execute() {
2246
+ try {
2247
+ await requireFullSetup(api.logger);
2248
+
2249
+ if (!authKeyHex) {
2250
+ return {
2251
+ content: [{ type: 'text', text: 'Auth credentials are not available. Please initialize first.' }],
2252
+ };
2253
+ }
2254
+
2255
+ const serverUrl = (process.env.TOTALRECLAW_SERVER_URL || 'https://api.totalreclaw.xyz').replace(/\/+$/, '');
2256
+ const walletAddr = subgraphOwner || userId || '';
2257
+
2258
+ if (!walletAddr) {
2259
+ return {
2260
+ content: [{ type: 'text', text: 'Wallet address not available. Please ensure the plugin is fully initialized.' }],
2261
+ };
2262
+ }
2263
+
2264
+ const response = await fetch(`${serverUrl}/v1/billing/checkout`, {
2265
+ method: 'POST',
2266
+ headers: {
2267
+ 'Authorization': `Bearer ${authKeyHex}`,
2268
+ 'Content-Type': 'application/json',
2269
+ 'X-TotalReclaw-Client': 'openclaw-plugin',
2270
+ },
2271
+ body: JSON.stringify({
2272
+ wallet_address: walletAddr,
2273
+ tier: 'pro',
2274
+ }),
2275
+ });
2276
+
2277
+ if (!response.ok) {
2278
+ const body = await response.text().catch(() => '');
2279
+ return {
2280
+ content: [{ type: 'text', text: `Failed to create checkout session (HTTP ${response.status}): ${body || response.statusText}` }],
2281
+ };
2282
+ }
2283
+
2284
+ const data = await response.json() as { checkout_url?: string };
2285
+
2286
+ if (!data.checkout_url) {
2287
+ return {
2288
+ content: [{ type: 'text', text: 'Failed to create checkout session: no checkout URL returned.' }],
2289
+ };
2290
+ }
2291
+
2292
+ return {
2293
+ content: [{ type: 'text', text: `Open this URL to upgrade to Pro: ${data.checkout_url}` }],
2294
+ details: { checkout_url: data.checkout_url },
2295
+ };
2296
+ } catch (err: unknown) {
2297
+ const message = err instanceof Error ? err.message : String(err);
2298
+ api.logger.error(`totalreclaw_upgrade failed: ${message}`);
2299
+ return {
2300
+ content: [{ type: 'text', text: `Failed to create checkout session: ${message}` }],
2301
+ };
2302
+ }
2303
+ },
2304
+ },
2305
+ { name: 'totalreclaw_upgrade' },
2306
+ );
2307
+
2186
2308
  // ---------------------------------------------------------------
2187
2309
  // Hook: before_agent_start
2188
2310
  // ---------------------------------------------------------------
@@ -2213,6 +2335,13 @@ const plugin = {
2213
2335
  };
2214
2336
  }
2215
2337
 
2338
+ // One-time welcome-back message for returning Pro users.
2339
+ let welcomeBack = '';
2340
+ if (welcomeBackMessage) {
2341
+ welcomeBack = `\n\n${welcomeBackMessage}`;
2342
+ welcomeBackMessage = null; // Consume — only show once
2343
+ }
2344
+
2216
2345
  // Billing cache check — warn if quota is approaching limit.
2217
2346
  let billingWarning = '';
2218
2347
  try {
@@ -2286,7 +2415,7 @@ const plugin = {
2286
2415
  const lines = cachedFacts.slice(0, 8).map((f, i) =>
2287
2416
  `${i + 1}. ${f.text} (importance: ${f.importance}/10, cached)`,
2288
2417
  );
2289
- return { prependContext: `## Relevant Memories\n\n${lines.join('\n')}` + billingWarning };
2418
+ return { prependContext: `## Relevant Memories\n\n${lines.join('\n')}` + welcomeBack + billingWarning };
2290
2419
  }
2291
2420
  }
2292
2421
 
@@ -2299,7 +2428,7 @@ const plugin = {
2299
2428
  const lines = cachedFacts.slice(0, 8).map((f, i) =>
2300
2429
  `${i + 1}. ${f.text} (importance: ${f.importance}/10, cached)`,
2301
2430
  );
2302
- return { prependContext: `## Relevant Memories\n\n${lines.join('\n')}` + billingWarning };
2431
+ return { prependContext: `## Relevant Memories\n\n${lines.join('\n')}` + welcomeBack + billingWarning };
2303
2432
  }
2304
2433
 
2305
2434
  if (allTrapdoors.length === 0) return undefined;
@@ -2316,7 +2445,7 @@ const plugin = {
2316
2445
  const lines = cachedFacts.slice(0, 8).map((f, i) =>
2317
2446
  `${i + 1}. ${f.text} (importance: ${f.importance}/10, cached)`,
2318
2447
  );
2319
- return { prependContext: `## Relevant Memories\n\n${lines.join('\n')}` + billingWarning };
2448
+ return { prependContext: `## Relevant Memories\n\n${lines.join('\n')}` + welcomeBack + billingWarning };
2320
2449
  }
2321
2450
  return undefined;
2322
2451
  }
@@ -2328,7 +2457,7 @@ const plugin = {
2328
2457
  const lines = cachedFacts.slice(0, 8).map((f, i) =>
2329
2458
  `${i + 1}. ${f.text} (importance: ${f.importance}/10, cached)`,
2330
2459
  );
2331
- return { prependContext: `## Relevant Memories\n\n${lines.join('\n')}` + billingWarning };
2460
+ return { prependContext: `## Relevant Memories\n\n${lines.join('\n')}` + welcomeBack + billingWarning };
2332
2461
  }
2333
2462
 
2334
2463
  // 5. Decrypt subgraph results and build reranker input.
@@ -2434,7 +2563,7 @@ const plugin = {
2434
2563
  });
2435
2564
  const contextString = `## Relevant Memories\n\n${lines.join('\n')}`;
2436
2565
 
2437
- return { prependContext: contextString + billingWarning };
2566
+ return { prependContext: contextString + welcomeBack + billingWarning };
2438
2567
  }
2439
2568
 
2440
2569
  // --- Server mode (existing behavior) ---
@@ -2546,7 +2675,7 @@ const plugin = {
2546
2675
  });
2547
2676
  const contextString = `## Relevant Memories\n\n${lines.join('\n')}`;
2548
2677
 
2549
- return { prependContext: contextString + billingWarning };
2678
+ return { prependContext: contextString + welcomeBack + billingWarning };
2550
2679
  } catch (err: unknown) {
2551
2680
  // The hook must NEVER throw -- log and return undefined.
2552
2681
  const message = err instanceof Error ? err.message : String(err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@totalreclaw/totalreclaw",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "End-to-end encrypted memory for AI agents — portable, yours forever. Automatic extraction, semantic search, and on-chain storage",
5
5
  "type": "module",
6
6
  "keywords": [