wyrm-mcp 7.2.1 → 7.2.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 (150) hide show
  1. package/LICENSE +26 -667
  2. package/NOTICE +14 -33
  3. package/dist/activation.js +1 -60
  4. package/dist/agent-daemon.js +4 -281
  5. package/dist/agent-loop.js +7 -332
  6. package/dist/analytics.js +13 -236
  7. package/dist/attribution.js +1 -49
  8. package/dist/audit.js +2 -457
  9. package/dist/auto-capture.js +3 -138
  10. package/dist/auto-orchestrator.js +1 -325
  11. package/dist/autoconfig.js +39 -840
  12. package/dist/buddy-runner.js +1 -109
  13. package/dist/buddy.js +14 -564
  14. package/dist/build-flags.js +1 -17
  15. package/dist/capabilities.js +3 -183
  16. package/dist/capture.js +1 -56
  17. package/dist/causality.js +6 -107
  18. package/dist/cli.js +20 -281
  19. package/dist/cloud/cli.js +5 -541
  20. package/dist/cloud/client.js +1 -221
  21. package/dist/cloud/crypto.js +1 -85
  22. package/dist/cloud/machine-id.js +2 -113
  23. package/dist/cloud/recovery.js +1 -60
  24. package/dist/cloud/sync-engine.js +7 -543
  25. package/dist/cloud-backup.js +5 -579
  26. package/dist/cloud-profile.js +1 -138
  27. package/dist/cloud-sync-entrypoint.js +1 -47
  28. package/dist/cloud-sync.js +2 -309
  29. package/dist/constellation.js +12 -168
  30. package/dist/context-build-budgeted.js +4 -144
  31. package/dist/context-ranking.js +1 -69
  32. package/dist/crypto.js +1 -179
  33. package/dist/daemon-write-endpoint.js +1 -290
  34. package/dist/daemon-writer.js +2 -406
  35. package/dist/database.js +43 -1110
  36. package/dist/deprecations.js +2 -162
  37. package/dist/design.js +13 -141
  38. package/dist/event-replication.js +1 -112
  39. package/dist/events-sse.js +7 -43
  40. package/dist/events.js +6 -238
  41. package/dist/failure-patterns.js +42 -659
  42. package/dist/federation.js +12 -236
  43. package/dist/goals.js +13 -101
  44. package/dist/golden.js +3 -355
  45. package/dist/handlers/agent.js +4 -165
  46. package/dist/handlers/alias-adapters.js +1 -129
  47. package/dist/handlers/aliases.js +1 -171
  48. package/dist/handlers/audit.js +1 -87
  49. package/dist/handlers/boundary.js +1 -221
  50. package/dist/handlers/capture.js +73 -1109
  51. package/dist/handlers/causality.js +7 -114
  52. package/dist/handlers/cloud.js +85 -382
  53. package/dist/handlers/companion.js +28 -459
  54. package/dist/handlers/datalake.js +7 -187
  55. package/dist/handlers/dispatch-context.js +0 -22
  56. package/dist/handlers/entity.js +25 -256
  57. package/dist/handlers/events.js +16 -335
  58. package/dist/handlers/failure.js +13 -340
  59. package/dist/handlers/goals.js +4 -296
  60. package/dist/handlers/intelligence.js +126 -674
  61. package/dist/handlers/invoicing.js +1 -70
  62. package/dist/handlers/mcpclient.js +6 -137
  63. package/dist/handlers/orchestration.js +40 -125
  64. package/dist/handlers/output-schemas.js +1 -24
  65. package/dist/handlers/presence.js +3 -99
  66. package/dist/handlers/project.js +28 -182
  67. package/dist/handlers/prompts.js +6 -157
  68. package/dist/handlers/quest.js +4 -224
  69. package/dist/handlers/recall.js +11 -218
  70. package/dist/handlers/registry.js +1 -167
  71. package/dist/handlers/resources.js +1 -288
  72. package/dist/handlers/review.js +11 -74
  73. package/dist/handlers/run.js +17 -487
  74. package/dist/handlers/search.js +15 -326
  75. package/dist/handlers/session.js +28 -615
  76. package/dist/handlers/share.js +8 -184
  77. package/dist/handlers/shims.js +1 -464
  78. package/dist/handlers/skill.js +67 -449
  79. package/dist/handlers/survivors.js +1 -120
  80. package/dist/handlers/symbols.js +8 -109
  81. package/dist/handlers/syncops.js +4 -302
  82. package/dist/handlers/types.js +1 -27
  83. package/dist/harvest.js +5 -191
  84. package/dist/hours.js +7 -156
  85. package/dist/http-auth.js +3 -321
  86. package/dist/http-fast.js +21 -1137
  87. package/dist/icons.js +1 -47
  88. package/dist/index.js +2 -924
  89. package/dist/indexer.js +4 -145
  90. package/dist/intelligence.js +31 -261
  91. package/dist/internal-dispatch.js +3 -212
  92. package/dist/keyset.js +1 -110
  93. package/dist/knowledge-graph.js +12 -176
  94. package/dist/license.js +2 -441
  95. package/dist/logger.js +2 -199
  96. package/dist/maintenance.js +2 -148
  97. package/dist/mcp-client.js +6 -262
  98. package/dist/memory-artifacts.js +30 -449
  99. package/dist/migrate-prompt.js +2 -124
  100. package/dist/migrations.js +40 -655
  101. package/dist/performance.js +1 -228
  102. package/dist/presence.js +11 -140
  103. package/dist/priority-embed.js +5 -164
  104. package/dist/providers/embedding-provider.js +1 -196
  105. package/dist/readonly-gate.js +1 -29
  106. package/dist/rehydration.js +9 -157
  107. package/dist/reindex.js +1 -88
  108. package/dist/render-target.js +21 -514
  109. package/dist/render.js +4 -280
  110. package/dist/repl-guard.js +1 -173
  111. package/dist/replication-daemon-entrypoint.js +1 -31
  112. package/dist/replication-daemon.js +2 -262
  113. package/dist/resilience.js +1 -591
  114. package/dist/reverse-bridge.js +5 -360
  115. package/dist/security.js +1 -244
  116. package/dist/session-seen.js +3 -51
  117. package/dist/setup.js +1 -260
  118. package/dist/skill-author.js +5 -168
  119. package/dist/spec-kit.js +1 -191
  120. package/dist/sqlite-busy.js +1 -154
  121. package/dist/statusline.js +11 -315
  122. package/dist/sub-agent.js +13 -262
  123. package/dist/summarizer.js +13 -139
  124. package/dist/symbols.js +7 -283
  125. package/dist/sync.js +5 -359
  126. package/dist/tasks-dispatch.js +1 -84
  127. package/dist/tasks.js +1 -282
  128. package/dist/token-budget.js +1 -143
  129. package/dist/tool-analytics.js +7 -129
  130. package/dist/tool-annotations.js +1 -365
  131. package/dist/tool-manifest-v2.json +1 -1
  132. package/dist/tool-manifest.json +1 -1
  133. package/dist/tool-profiles.js +1 -75
  134. package/dist/trace-harvest.js +6 -244
  135. package/dist/types.js +1 -30
  136. package/dist/ui-dashboard.js +41 -50
  137. package/dist/ulid.js +1 -81
  138. package/dist/validate.js +1 -129
  139. package/dist/vault.js +1 -534
  140. package/dist/vectors.js +3 -184
  141. package/dist/version-check.js +4 -136
  142. package/dist/visibility.js +19 -155
  143. package/dist/wyrm-cli.js +98 -2464
  144. package/dist/wyrm-guard.js +14 -424
  145. package/dist/wyrm-loop.js +3 -150
  146. package/dist/wyrm-manifest.json +1 -1
  147. package/dist/wyrm-statusline-daemon.js +1 -11
  148. package/dist/wyrm-statusline.js +4 -56
  149. package/dist/wyrm-ui.js +9 -77
  150. package/package.json +4 -2
package/dist/cloud/cli.js CHANGED
@@ -1,135 +1,4 @@
1
- /**
2
- * `wyrm cloud …` subcommand dispatcher.
3
- *
4
- * wyrm cloud login authenticate via Google/GitHub OAuth
5
- * wyrm cloud logout revoke this session
6
- * wyrm cloud status account + tier + storage + devices
7
- * wyrm cloud devices list registered devices
8
- * wyrm cloud devices revoke <id> revoke a device
9
- * wyrm cloud sync [--dry-run] push local changes + pull peer deltas
10
- * wyrm cloud export <path> download full account dump (encrypted blobs included)
11
- * wyrm cloud delete --confirm permanently delete the account on Wyrm Cloud
12
- *
13
- * @copyright 2026 Ghost Protocol (Pvt) Ltd.
14
- */
15
- import { randomBytes } from 'node:crypto';
16
- import { writeFileSync } from 'node:fs';
17
- import { hostname } from 'node:os';
18
- import { resolve } from 'node:path';
19
- import { createInterface } from 'node:readline';
20
- import { CloudClient, CloudError, DEFAULT_BASE, SESSION_FILE, loadSession, saveSession, clearSession } from './client.js';
21
- import { getMasterKey, keyExists, keyFilePath } from './crypto.js';
22
- import { computeMachineFp, classifySession } from './machine-id.js';
23
- import { runSync, CopiedSessionError } from './sync-engine.js';
24
- import { initializeLicense, hasFeature, getTier } from '../license.js';
25
- /**
26
- * Gate the data-movement cloud commands behind a Pro+ license.
27
- *
28
- * Cloud sync is a paid feature (the `cloud_backup` flag, granted at pro
29
- * tier and above). The MCP tools already enforce this; without the same
30
- * check here, the CLI was a free bypass of the paid tier. Setup/visibility
31
- * commands (login, logout, status, devices, recovery, delete) stay free so
32
- * users can authenticate, see the upsell, and always retain the right to
33
- * delete their own data.
34
- */
35
- function requireCloudLicense() {
36
- initializeLicense(); // loads ~/.wyrm/license.json (no-op → free tier)
37
- if (!hasFeature('cloud_backup')) {
38
- console.error('󱅝 Wyrm Cloud sync requires a Pro license or higher.');
39
- console.error(` Current tier: ${getTier()}`);
40
- console.error(' Upgrade: https://ghosts.lk/wyrm');
41
- console.error(' Already paid? Activate your license (wyrm_license tool, or place');
42
- console.error(' the signed JSON at ~/.wyrm/license.json) and retry.');
43
- process.exit(1);
44
- }
45
- }
46
- function fmtBytes(n) {
47
- if (n < 1024)
48
- return `${n} B`;
49
- if (n < 1024 * 1024)
50
- return `${(n / 1024).toFixed(1)} KiB`;
51
- if (n < 1024 * 1024 * 1024)
52
- return `${(n / (1024 * 1024)).toFixed(1)} MiB`;
53
- return `${(n / (1024 * 1024 * 1024)).toFixed(2)} GiB`;
54
- }
55
- function fmtTime(unixMs) {
56
- if (!unixMs)
57
- return '—';
58
- return new Date(unixMs).toISOString().replace('T', ' ').slice(0, 16) + ' UTC';
59
- }
60
- export async function cmdCloud(args) {
61
- const sub = args[0];
62
- const rest = args.slice(1);
63
- try {
64
- switch (sub) {
65
- case undefined:
66
- case 'help':
67
- case '--help':
68
- case '-h':
69
- return cmdHelp();
70
- case 'login': return await cmdLogin(rest);
71
- case 'logout': return await cmdLogout();
72
- case 'status': return await cmdStatus();
73
- case 'doctor': return cmdDoctor();
74
- case 'devices': return await cmdDevices(rest);
75
- case 'sync': return await cmdSync(rest);
76
- case 'export': return await cmdExport(rest);
77
- case 'delete': return await cmdDelete(rest);
78
- case 'recovery': return await cmdRecovery(rest);
79
- default:
80
- console.error(`Unknown subcommand: wyrm cloud ${sub}`);
81
- console.error(`Try \`wyrm cloud help\``);
82
- process.exit(1);
83
- }
84
- }
85
- catch (err) {
86
- // Translate common errors to friendly CLI output. Falls through
87
- // to a generic message for unexpected exceptions.
88
- if (err instanceof CopiedSessionError) {
89
- // The detailed loud guidance was already written to stderr by the
90
- // sync engine. Just exit non-zero without burying it under a
91
- // generic message.
92
- process.exit(1);
93
- }
94
- if (err instanceof CloudError) {
95
- if (err.status === 401) {
96
- console.error('✘ Not authenticated. Run `wyrm cloud login`.');
97
- }
98
- else if (err.status === 403) {
99
- console.error(`✘ Forbidden: ${err.message}`);
100
- }
101
- else if (err.status === 0) {
102
- // Network / timeout error from fetch.
103
- console.error(`✘ ${err.message}`);
104
- console.error(' Check your internet connection. If wyrm.ghosts.lk is unreachable, see status at https://www.cloudflarestatus.com');
105
- }
106
- else if (err.status === 501) {
107
- console.error(`✘ Service not configured: ${err.message}`);
108
- }
109
- else {
110
- console.error(`✘ ${err.message} (HTTP ${err.status})`);
111
- }
112
- }
113
- else if (err instanceof Error) {
114
- const msg = err.message;
115
- if (msg.includes('Not logged in')) {
116
- console.error('✘ Not logged in. Run `wyrm cloud login`.');
117
- }
118
- else if (msg.includes('Wyrm Cloud key')) {
119
- console.error(`✘ Encryption-key error: ${msg}`);
120
- }
121
- else {
122
- console.error(`✘ ${msg}`);
123
- }
124
- }
125
- else {
126
- console.error('✘ Unexpected error:', err);
127
- }
128
- process.exit(1);
129
- }
130
- }
131
- function cmdHelp() {
132
- console.log(`󱅝 wyrm cloud — sync your Wyrm memory across devices
1
+ import{randomBytes as D}from"node:crypto";import{writeFileSync as I}from"node:fs";import{hostname as N}from"node:os";import{resolve as P}from"node:path";import{createInterface as A}from"node:readline";import{CloudClient as a,CloudError as k,DEFAULT_BASE as p,SESSION_FILE as f,loadSession as d,saveSession as S,clearSession as b}from"./client.js";import{getMasterKey as R,keyExists as h,keyFilePath as g}from"./crypto.js";import{computeMachineFp as E,classifySession as O}from"./machine-id.js";import{runSync as L,CopiedSessionError as W}from"./sync-engine.js";import{initializeLicense as F,hasFeature as M,getTier as B}from"../license.js";function T(){F(),M("cloud_backup")||(console.error("\u{F115D} Wyrm Cloud sync requires a Pro license or higher."),console.error(` Current tier: ${B()}`),console.error(" Upgrade: https://ghosts.lk/wyrm"),console.error(" Already paid? Activate your license (wyrm_license tool, or place"),console.error(" the signed JSON at ~/.wyrm/license.json) and retry."),process.exit(1))}function w(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KiB`:e<1024*1024*1024?`${(e/(1024*1024)).toFixed(1)} MiB`:`${(e/(1024*1024*1024)).toFixed(2)} GiB`}function G(e){return e?new Date(e).toISOString().replace("T"," ").slice(0,16)+" UTC":"\u2014"}async function ie(e){const n=e[0],s=e.slice(1);try{switch(n){case void 0:case"help":case"--help":case"-h":return U();case"login":return await j(s);case"logout":return await K();case"status":return await H();case"doctor":return Y();case"devices":return await q(s);case"sync":return await z(s);case"export":return await J(s);case"delete":return await Q(s);case"recovery":return await V(s);default:console.error(`Unknown subcommand: wyrm cloud ${n}`),console.error("Try `wyrm cloud help`"),process.exit(1)}}catch(o){if(o instanceof W&&process.exit(1),o instanceof k)o.status===401?console.error("\u2718 Not authenticated. Run `wyrm cloud login`."):o.status===403?console.error(`\u2718 Forbidden: ${o.message}`):o.status===0?(console.error(`\u2718 ${o.message}`),console.error(" Check your internet connection. If wyrm.ghosts.lk is unreachable, see status at https://www.cloudflarestatus.com")):o.status===501?console.error(`\u2718 Service not configured: ${o.message}`):console.error(`\u2718 ${o.message} (HTTP ${o.status})`);else if(o instanceof Error){const r=o.message;r.includes("Not logged in")?console.error("\u2718 Not logged in. Run `wyrm cloud login`."):r.includes("Wyrm Cloud key")?console.error(`\u2718 Encryption-key error: ${r}`):console.error(`\u2718 ${r}`)}else console.error("\u2718 Unexpected error:",o);process.exit(1)}}function U(){console.log(`\u{F115D} wyrm cloud \u2014 sync your Wyrm memory across devices
133
2
 
134
3
  USAGE:
135
4
  wyrm cloud <subcommand> [args]
@@ -155,7 +24,7 @@ SUBCOMMANDS:
155
24
 
156
25
  CONFIG:
157
26
  Session file: ~/.wyrm/cloud.json (0600)
158
- Master key: ~/.wyrm/cloud.key (0600) never sent to server
27
+ Master key: ~/.wyrm/cloud.key (0600) \u2190 never sent to server
159
28
 
160
29
  CONSTITUTION:
161
30
  All sync payloads AES-256-GCM encrypted client-side before upload.
@@ -163,411 +32,6 @@ CONSTITUTION:
163
32
 
164
33
  DOMAIN:
165
34
  https://wyrm.ghosts.lk
166
- `);
167
- }
168
- // ── login ───────────────────────────────────────────────────────────────────
169
- async function cmdLogin(_args) {
170
- if (loadSession()) {
171
- console.error('Already logged in. Run `wyrm cloud logout` first to switch accounts.');
172
- process.exit(1);
173
- }
174
- const client = new CloudClient(DEFAULT_BASE);
175
- const pollToken = randomBytes(32).toString('hex'); // 64 chars
176
- let init;
177
- try {
178
- init = await client.cliInit(pollToken);
179
- }
180
- catch (err) {
181
- console.error(`✘ Could not start login: ${err instanceof Error ? err.message : err}`);
182
- process.exit(1);
183
- }
184
- console.log('');
185
- console.log('󱅝 Sign in to Wyrm Cloud');
186
- console.log('');
187
- console.log(` 1. Open this URL on any device:`);
188
- console.log(` ${init.browser_url}`);
189
- console.log('');
190
- console.log(` 2. Type this code when prompted:`);
191
- console.log(` ${init.code}`);
192
- console.log('');
193
- console.log(` 3. Sign in with Google or GitHub.`);
194
- console.log('');
195
- console.log(` Waiting (expires in ${Math.round(init.expires_in_sec / 60)} min)…`);
196
- // Poll up to expires_in_sec, every 3s.
197
- const start = Date.now();
198
- const deadline = start + init.expires_in_sec * 1000;
199
- let session;
200
- let accountId;
201
- while (Date.now() < deadline) {
202
- await new Promise((r) => { setTimeout(r, 3000); });
203
- try {
204
- const poll = await client.cliPoll(pollToken);
205
- if (poll.ready && poll.session) {
206
- session = poll.session;
207
- accountId = poll.account_id;
208
- break;
209
- }
210
- }
211
- catch (err) {
212
- // The pending state is a 200 with ready:false (handled above). A
213
- // 404 here means the poll_token isn't in the DB — link expired or
214
- // server lost state. Fail loud; don't spin until deadline.
215
- console.error(`\n✘ Login failed: ${err instanceof Error ? err.message : err}`);
216
- process.exit(1);
217
- }
218
- }
219
- if (!session || !accountId) {
220
- console.error('\n✘ Login timed out. Run `wyrm cloud login` again.');
221
- process.exit(1);
222
- }
223
- // Save session, fetch account info for the welcome message.
224
- saveSession({
225
- base: DEFAULT_BASE,
226
- session,
227
- account_id: accountId,
228
- created_at: Date.now(),
229
- });
230
- const authed = new CloudClient(DEFAULT_BASE, session);
231
- let email = '';
232
- try {
233
- const me = await authed.me();
234
- email = me.account.email;
235
- }
236
- catch { /* whatever, login still succeeded */ }
237
- // Register this machine as a device (idempotent: every fresh login creates one).
238
- const deviceName = `${hostname()} (${process.platform})`;
239
- let deviceId;
240
- try {
241
- const reg = await authed.registerDevice(deviceName);
242
- deviceId = reg.device.id;
243
- // Update session file with device info + this machine's fingerprint.
244
- // The fp binds the session to THIS machine so a later copy of
245
- // cloud.json to a second box is detected at sync (failure #40).
246
- const s = loadSession();
247
- if (s)
248
- saveSession({ ...s, email, device_id: deviceId, device_name: deviceName, machine_fp: computeMachineFp() });
249
- }
250
- catch (err) {
251
- console.error(`\n⚠ Logged in but could not register device: ${err instanceof Error ? err.message : err}`);
252
- console.error(' Re-run `wyrm cloud login` to retry.');
253
- return;
254
- }
255
- console.log('');
256
- console.log(`✓ Signed in as ${email}`);
257
- console.log(`✓ Device registered: ${deviceName}`);
258
- console.log(`✓ Session saved to ${SESSION_FILE}`);
259
- // Eagerly materialise the master key so `wyrm cloud recovery show`
260
- // works immediately after login. Without this, the key only gets
261
- // created lazily on the first encrypt(), and the post-login UX of
262
- // "save your recovery phrase right now" doesn't actually work.
263
- const keyWasFresh = !keyExists();
264
- getMasterKey(); // creates the file if missing
265
- if (keyWasFresh) {
266
- console.log(`✓ Encryption key generated at ${keyFilePath()} (0600)`);
267
- console.log('');
268
- console.log(' ⚠ Back up the key file. Losing it means losing access to your synced data.');
269
- console.log(' The Wyrm Cloud server CANNOT recover it — that\'s the operator-owns-data');
270
- console.log(' guarantee. Run `wyrm cloud recovery show` now to get a 24-word phrase,');
271
- console.log(' or copy the key file to a password manager / encrypted backup.');
272
- }
273
- else {
274
- console.log(`✓ Encryption key reused from ${keyFilePath()}`);
275
- }
276
- console.log('');
277
- console.log(`Next: \`wyrm cloud sync\` to push your memory.`);
278
- }
279
- // ── logout ──────────────────────────────────────────────────────────────────
280
- async function cmdLogout() {
281
- const s = loadSession();
282
- if (!s) {
283
- console.log('Not logged in.');
284
- return;
285
- }
286
- try {
287
- const client = new CloudClient(s.base, s.session);
288
- await client.logout();
289
- console.log('✓ Session revoked server-side.');
290
- }
291
- catch (err) {
292
- console.error(`⚠ Could not revoke server-side: ${err instanceof Error ? err.message : err}`);
293
- console.error(' Local session cleared anyway.');
294
- }
295
- clearSession();
296
- console.log(`✓ Local session removed (${SESSION_FILE}).`);
297
- console.log(' Note: ~/.wyrm/cloud.key kept. Delete manually if you want a clean slate.');
298
- }
299
- // ── status ──────────────────────────────────────────────────────────────────
300
- async function cmdStatus() {
301
- const s = loadSession();
302
- if (!s) {
303
- console.log('Not logged in. Run `wyrm cloud login`.');
304
- return;
305
- }
306
- const client = new CloudClient(s.base, s.session);
307
- try {
308
- const me = await client.me();
309
- const st = await client.syncStatus();
310
- console.log('');
311
- console.log(`󱅝 Wyrm Cloud — ${me.account.email}`);
312
- console.log('');
313
- console.log(` Tier: ${st.tier}`);
314
- console.log(` Storage: ${fmtBytes(st.storage.used_bytes)} / ${fmtBytes(st.storage.limit_bytes)} (${st.storage.percent}%)`);
315
- console.log(` Deltas: ${st.deltas}`);
316
- console.log(` Devices: ${st.devices}`);
317
- console.log(` Providers:${me.identities.map((i) => ' ' + i.provider).join(',')}`);
318
- console.log('');
319
- console.log(` This machine: ${s.device_name ?? '(not registered)'}`);
320
- console.log(` Device ID: ${s.device_id ? s.device_id.slice(0, 8) + '…' : '(none)'}`);
321
- console.log(` Session age: ${Math.round((Date.now() - s.created_at) / (24 * 60 * 60 * 1000))} day(s)`);
322
- printIdentityHealth(s);
323
- console.log('');
324
- // Encryption-key health. The whole operator-owns-data guarantee rests
325
- // on this file existing — if it's gone, no pulled blob can be decrypted.
326
- if (!keyExists()) {
327
- console.log(' ⚠ ENCRYPTION KEY MISSING');
328
- console.log(` Expected at: ${keyFilePath()}`);
329
- console.log(` Without this file, deltas already pushed cannot be decrypted.`);
330
- console.log(` If you have a backup, restore it before running sync.`);
331
- console.log('');
332
- }
333
- else if (st.deltas > 0) {
334
- console.log(` Key: ${keyFilePath()} (0600)`);
335
- console.log(` ⚠ Back this file up. Losing it = losing access to your ${st.deltas} deltas.`);
336
- console.log('');
337
- }
338
- }
339
- catch (err) {
340
- if (err instanceof CloudError && err.status === 401) {
341
- console.error('Session expired or invalid. Run `wyrm cloud login` again.');
342
- process.exit(1);
343
- }
344
- throw err;
345
- }
346
- }
347
- /**
348
- * Print the device-identity health line(s) for a session: whether the
349
- * stored machine fingerprint matches THIS machine. A mismatch is the
350
- * copied-session / device_id-collision symptom (failure #40). Shared by
351
- * `status` and `doctor`.
352
- */
353
- function printIdentityHealth(s) {
354
- const verdict = classifySession(s.machine_fp);
355
- if (verdict.state === 'match') {
356
- console.log(' Identity: ✓ machine fingerprint matches (not a copied session)');
357
- }
358
- else if (verdict.state === 'adopt') {
359
- console.log(' Identity: — no fingerprint yet (pre-7.0.3 session; adopted on next sync)');
360
- }
361
- else {
362
- console.log(' Identity: ⚠ FINGERPRINT MISMATCH — this session looks COPIED');
363
- console.log(` stored ${verdict.stored.slice(0, 12)}… vs this machine ${verdict.current.slice(0, 12)}…`);
364
- console.log(' device_id collision → cross-device pull returns 0 silently.');
365
- console.log(' Fix: rm ~/.wyrm/cloud.json ~/.wyrm/cloud-cursor.json (KEEP cloud.key),');
366
- console.log(' then `wyrm cloud login`. (Or `wyrm cloud sync --force` if intended.)');
367
- }
368
- }
369
- // ── doctor ────────────────────────────────────────────────────────────────────
370
- function cmdDoctor() {
371
- const s = loadSession();
372
- console.log('');
373
- console.log('󱅝 Wyrm Cloud — device identity doctor');
374
- console.log('');
375
- if (!s) {
376
- console.log(' Not logged in. Run `wyrm cloud login`.');
377
- console.log('');
378
- return;
379
- }
380
- console.log(` Account: ${s.email ?? s.account_id.slice(0, 8) + '…'}`);
381
- console.log(` Device ID: ${s.device_id ? s.device_id.slice(0, 8) + '…' : '(none — re-run `wyrm cloud login`)'}`);
382
- console.log(` Server: ${s.base}`);
383
- console.log(` Stored fp: ${s.machine_fp ? s.machine_fp.slice(0, 12) + '…' : '(none — pre-7.0.3 session)'}`);
384
- console.log(` This box fp: ${computeMachineFp().slice(0, 12)}…`);
385
- printIdentityHealth(s);
386
- console.log('');
387
- console.log(' Only ~/.wyrm/cloud.key is meant to be shared across your machines.');
388
- console.log(' cloud.json + cloud-cursor.json + machine-id are per-device.');
389
- console.log('');
390
- }
391
- // ── devices ─────────────────────────────────────────────────────────────────
392
- async function cmdDevices(args) {
393
- const action = args[0];
394
- const client = CloudClient.fromSession();
395
- if (action === 'revoke') {
396
- const id = args[1];
397
- if (!id) {
398
- console.error('Usage: wyrm cloud devices revoke <device-id>');
399
- process.exit(1);
400
- }
401
- await client.revokeDevice(id);
402
- console.log(`✓ Device ${id.slice(0, 8)}… revoked.`);
403
- return;
404
- }
405
- const { devices } = await client.listDevices();
406
- const session = loadSession();
407
- const thisId = session?.device_id;
408
- console.log('');
409
- console.log(`󱅝 Devices (${devices.length})`);
410
- console.log('');
411
- for (const d of devices) {
412
- const marker = d.id === thisId ? '★' : ' ';
413
- const status = d.revoked ? '(revoked)' : '';
414
- console.log(` ${marker} ${d.id.slice(0, 8)}… ${d.name.padEnd(30)} last_seen=${fmtTime(d.last_seen_at)} ${status}`);
415
- }
416
- console.log('');
417
- console.log(' ★ = this machine');
418
- console.log(' Revoke: wyrm cloud devices revoke <device-id>');
419
- console.log('');
420
- }
421
- // ── sync ────────────────────────────────────────────────────────────────────
422
- async function cmdSync(args) {
423
- requireCloudLicense();
424
- const dryRun = args.includes('--dry-run');
425
- const all = args.includes('--all');
426
- const force = args.includes('--force');
427
- if (!keyExists()) {
428
- console.error('⚠ Encryption key not found at', keyFilePath());
429
- console.error(' A new key will be generated, but it will NOT decrypt deltas');
430
- console.error(' pushed by other devices using a different key. If you have a backup,');
431
- console.error(' restore it to ~/.wyrm/cloud.key (0600) before running sync.');
432
- console.error(' Continuing in 5 seconds — press Ctrl+C to abort.');
433
- await new Promise((r) => { setTimeout(r, 5000); });
434
- }
435
- if (all) {
436
- console.log('🌐 --all mode: syncing every semantic table (ignoring visibility flags).');
437
- console.log(' Skipped: per-device logs, FTS shadows, sync internals.');
438
- console.log('');
439
- }
440
- const result = await runSync({ dryRun, all, force });
441
- console.log('');
442
- console.log('󱅝 Sync complete');
443
- console.log('');
444
- console.log(` Pushed: ${result.pushed} row(s)${dryRun ? ' (dry run)' : ''}`);
445
- console.log(` Pulled: ${result.pulled} row(s)`);
446
- console.log(` Deleted: ${result.deleted_local} row(s) (peer tombstones)`);
447
- if (result.errors.length > 0) {
448
- console.log('');
449
- console.log(' Errors:');
450
- for (const e of result.errors)
451
- console.log(` - ${e}`);
452
- }
453
- console.log('');
454
- }
455
- // ── export ──────────────────────────────────────────────────────────────────
456
- async function cmdExport(args) {
457
- requireCloudLicense();
458
- const client = CloudClient.fromSession();
459
- const targetArg = args.find((a) => !a.startsWith('-'));
460
- const target = resolve(targetArg ?? `wyrm-cloud-export-${new Date().toISOString().slice(0, 10)}.json`);
461
- console.log(' Fetching account export…');
462
- const dump = await client.accountExport();
463
- const body = JSON.stringify(dump, null, 2);
464
- writeFileSync(target, body, { mode: 0o600 });
465
- console.log('');
466
- console.log(`✓ Exported to ${target}`);
467
- console.log('');
468
- console.log(` Account: ${dump.account.email} (${dump.account.id.slice(0, 8)}…)`);
469
- console.log(` Devices: ${dump.devices.length}`);
470
- console.log(` Identities: ${dump.identities.length}`);
471
- console.log(` Orgs: ${dump.orgs.length}`);
472
- console.log(` Deltas: ${dump.deltas.length}`);
473
- console.log(` Size: ${fmtBytes(body.length)}`);
474
- console.log('');
475
- console.log(' Deltas are still AES-256-GCM ciphertext. To decrypt them you');
476
- console.log(` need the key at ${keyFilePath()}. Back that up alongside this file.`);
477
- console.log('');
478
- }
479
- // ── delete ──────────────────────────────────────────────────────────────────
480
- async function cmdDelete(args) {
481
- const confirmed = args.includes('--confirm');
482
- if (!confirmed) {
483
- console.error('✘ This permanently deletes your Wyrm Cloud account.');
484
- console.error(' Re-run with --confirm to proceed:');
485
- console.error(' wyrm cloud delete --confirm');
486
- process.exit(1);
487
- }
488
- const s = loadSession();
489
- if (!s) {
490
- console.error('✘ Not logged in. Nothing to delete.');
491
- process.exit(1);
492
- }
493
- console.log('');
494
- console.log('⚠ You are about to PERMANENTLY DELETE your Wyrm Cloud account.');
495
- console.log('');
496
- console.log(` Account: ${s.email ?? s.account_id}`);
497
- console.log(` Server: ${s.base}`);
498
- console.log('');
499
- console.log(' This wipes all encrypted blobs, devices, identities, and org');
500
- console.log(' memberships you own. The local ~/.wyrm/cloud.key is kept so');
501
- console.log(' any backups remain decryptable. This action CANNOT be undone.');
502
- console.log('');
503
- const phrase = await prompt(' Type "DELETE my account" to confirm: ');
504
- if (phrase.trim() !== 'DELETE my account') {
505
- console.error('✘ Confirmation phrase did not match. Aborting.');
506
- process.exit(1);
507
- }
508
- const client = new CloudClient(s.base, s.session);
509
- await client.accountDelete();
510
- clearSession();
511
- console.log('');
512
- console.log('✓ Account deleted on server.');
513
- console.log(`✓ Local session removed (${SESSION_FILE}).`);
514
- console.log(` Kept: ${keyFilePath()} (in case you need to decrypt an old export).`);
515
- console.log('');
516
- }
517
- function prompt(question) {
518
- return new Promise((resolveP) => {
519
- const rl = createInterface({ input: process.stdin, output: process.stdout });
520
- rl.question(question, (answer) => { rl.close(); resolveP(answer); });
521
- });
522
- }
523
- // ── recovery ────────────────────────────────────────────────────────────────
524
- async function cmdRecovery(args) {
525
- const action = args[0];
526
- if (action === 'show') {
527
- const { keyToMnemonic } = await import('./recovery.js');
528
- const phrase = keyToMnemonic();
529
- const words = phrase.split(' ');
530
- console.log('');
531
- console.log('🔑 Wyrm Cloud recovery phrase (BIP39, 24 words)');
532
- console.log('');
533
- // 4 rows × 6 columns — easier to write down without losing place.
534
- for (let row = 0; row < 4; row++) {
535
- const line = words
536
- .slice(row * 6, row * 6 + 6)
537
- .map((w, i) => `${String(row * 6 + i + 1).padStart(2)}. ${w.padEnd(10)}`)
538
- .join(' ');
539
- console.log(` ${line}`);
540
- }
541
- console.log('');
542
- console.log(' Write these down on PAPER and keep them somewhere safe.');
543
- console.log(' Anyone with this phrase can decrypt your synced Wyrm memory.');
544
- console.log(' Wyrm Cloud has NO COPY of this. Lose it = lose your data.');
545
- console.log('');
546
- return;
547
- }
548
- if (action === 'restore') {
549
- const overwrite = args.includes('--overwrite');
550
- console.log('');
551
- console.log('🔑 Restore master key from BIP39 recovery phrase');
552
- console.log('');
553
- console.log(' Enter your 24 words separated by spaces (case-insensitive).');
554
- console.log(' Press Enter when done.');
555
- console.log('');
556
- const phrase = await prompt(' > ');
557
- const { mnemonicToKey } = await import('./recovery.js');
558
- const { path, overwrote } = mnemonicToKey(phrase, { overwrite });
559
- console.log('');
560
- console.log(`✓ Master key restored to ${path} (0600)`);
561
- if (overwrote) {
562
- console.log(' Replaced existing key (you passed --overwrite).');
563
- }
564
- console.log(' Run `wyrm cloud sync` to pull deltas with the restored key.');
565
- console.log('');
566
- return;
567
- }
568
- console.error('Usage:');
569
- console.error(' wyrm cloud recovery show');
570
- console.error(' wyrm cloud recovery restore [--overwrite]');
571
- process.exit(1);
572
- }
573
- //# sourceMappingURL=cli.js.map
35
+ `)}async function j(e){d()&&(console.error("Already logged in. Run `wyrm cloud logout` first to switch accounts."),process.exit(1));const n=new a(p),s=D(32).toString("hex");let o;try{o=await n.cliInit(s)}catch(t){console.error(`\u2718 Could not start login: ${t instanceof Error?t.message:t}`),process.exit(1)}console.log(""),console.log("\u{F115D} Sign in to Wyrm Cloud"),console.log(""),console.log(" 1. Open this URL on any device:"),console.log(` ${o.browser_url}`),console.log(""),console.log(" 2. Type this code when prompted:"),console.log(` ${o.code}`),console.log(""),console.log(" 3. Sign in with Google or GitHub."),console.log(""),console.log(` Waiting (expires in ${Math.round(o.expires_in_sec/60)} min)\u2026`);const l=Date.now()+o.expires_in_sec*1e3;let c,i;for(;Date.now()<l;){await new Promise(t=>{setTimeout(t,3e3)});try{const t=await n.cliPoll(s);if(t.ready&&t.session){c=t.session,i=t.account_id;break}}catch(t){console.error(`
36
+ \u2718 Login failed: ${t instanceof Error?t.message:t}`),process.exit(1)}}(!c||!i)&&(console.error("\n\u2718 Login timed out. Run `wyrm cloud login` again."),process.exit(1)),S({base:p,session:c,account_id:i,created_at:Date.now()});const u=new a(p,c);let y="";try{y=(await u.me()).account.email}catch{}const m=`${N()} (${process.platform})`;let v;try{v=(await u.registerDevice(m)).device.id;const $=d();$&&S({...$,email:y,device_id:v,device_name:m,machine_fp:E()})}catch(t){console.error(`
37
+ \u26A0 Logged in but could not register device: ${t instanceof Error?t.message:t}`),console.error(" Re-run `wyrm cloud login` to retry.");return}console.log(""),console.log(`\u2713 Signed in as ${y}`),console.log(`\u2713 Device registered: ${m}`),console.log(`\u2713 Session saved to ${f}`);const C=!h();R(),C?(console.log(`\u2713 Encryption key generated at ${g()} (0600)`),console.log(""),console.log(" \u26A0 Back up the key file. Losing it means losing access to your synced data."),console.log(" The Wyrm Cloud server CANNOT recover it \u2014 that's the operator-owns-data"),console.log(" guarantee. Run `wyrm cloud recovery show` now to get a 24-word phrase,"),console.log(" or copy the key file to a password manager / encrypted backup.")):console.log(`\u2713 Encryption key reused from ${g()}`),console.log(""),console.log("Next: `wyrm cloud sync` to push your memory.")}async function K(){const e=d();if(!e){console.log("Not logged in.");return}try{await new a(e.base,e.session).logout(),console.log("\u2713 Session revoked server-side.")}catch(n){console.error(`\u26A0 Could not revoke server-side: ${n instanceof Error?n.message:n}`),console.error(" Local session cleared anyway.")}b(),console.log(`\u2713 Local session removed (${f}).`),console.log(" Note: ~/.wyrm/cloud.key kept. Delete manually if you want a clean slate.")}async function H(){const e=d();if(!e){console.log("Not logged in. Run `wyrm cloud login`.");return}const n=new a(e.base,e.session);try{const s=await n.me(),o=await n.syncStatus();console.log(""),console.log(`\u{F115D} Wyrm Cloud \u2014 ${s.account.email}`),console.log(""),console.log(` Tier: ${o.tier}`),console.log(` Storage: ${w(o.storage.used_bytes)} / ${w(o.storage.limit_bytes)} (${o.storage.percent}%)`),console.log(` Deltas: ${o.deltas}`),console.log(` Devices: ${o.devices}`),console.log(` Providers:${s.identities.map(r=>" "+r.provider).join(",")}`),console.log(""),console.log(` This machine: ${e.device_name??"(not registered)"}`),console.log(` Device ID: ${e.device_id?e.device_id.slice(0,8)+"\u2026":"(none)"}`),console.log(` Session age: ${Math.round((Date.now()-e.created_at)/(1440*60*1e3))} day(s)`),x(e),console.log(""),h()?o.deltas>0&&(console.log(` Key: ${g()} (0600)`),console.log(` \u26A0 Back this file up. Losing it = losing access to your ${o.deltas} deltas.`),console.log("")):(console.log(" \u26A0 ENCRYPTION KEY MISSING"),console.log(` Expected at: ${g()}`),console.log(" Without this file, deltas already pushed cannot be decrypted."),console.log(" If you have a backup, restore it before running sync."),console.log(""))}catch(s){throw s instanceof k&&s.status===401&&(console.error("Session expired or invalid. Run `wyrm cloud login` again."),process.exit(1)),s}}function x(e){const n=O(e.machine_fp);n.state==="match"?console.log(" Identity: \u2713 machine fingerprint matches (not a copied session)"):n.state==="adopt"?console.log(" Identity: \u2014 no fingerprint yet (pre-7.0.3 session; adopted on next sync)"):(console.log(" Identity: \u26A0 FINGERPRINT MISMATCH \u2014 this session looks COPIED"),console.log(` stored ${n.stored.slice(0,12)}\u2026 vs this machine ${n.current.slice(0,12)}\u2026`),console.log(" device_id collision \u2192 cross-device pull returns 0 silently."),console.log(" Fix: rm ~/.wyrm/cloud.json ~/.wyrm/cloud-cursor.json (KEEP cloud.key),"),console.log(" then `wyrm cloud login`. (Or `wyrm cloud sync --force` if intended.)"))}function Y(){const e=d();if(console.log(""),console.log("\u{F115D} Wyrm Cloud \u2014 device identity doctor"),console.log(""),!e){console.log(" Not logged in. Run `wyrm cloud login`."),console.log("");return}console.log(` Account: ${e.email??e.account_id.slice(0,8)+"\u2026"}`),console.log(` Device ID: ${e.device_id?e.device_id.slice(0,8)+"\u2026":"(none \u2014 re-run `wyrm cloud login`)"}`),console.log(` Server: ${e.base}`),console.log(` Stored fp: ${e.machine_fp?e.machine_fp.slice(0,12)+"\u2026":"(none \u2014 pre-7.0.3 session)"}`),console.log(` This box fp: ${E().slice(0,12)}\u2026`),x(e),console.log(""),console.log(" Only ~/.wyrm/cloud.key is meant to be shared across your machines."),console.log(" cloud.json + cloud-cursor.json + machine-id are per-device."),console.log("")}async function q(e){const n=e[0],s=a.fromSession();if(n==="revoke"){const c=e[1];c||(console.error("Usage: wyrm cloud devices revoke <device-id>"),process.exit(1)),await s.revokeDevice(c),console.log(`\u2713 Device ${c.slice(0,8)}\u2026 revoked.`);return}const{devices:o}=await s.listDevices(),l=d()?.device_id;console.log(""),console.log(`\u{F115D} Devices (${o.length})`),console.log("");for(const c of o){const i=c.id===l?"\u2605":" ",u=c.revoked?"(revoked)":"";console.log(` ${i} ${c.id.slice(0,8)}\u2026 ${c.name.padEnd(30)} last_seen=${G(c.last_seen_at)} ${u}`)}console.log(""),console.log(" \u2605 = this machine"),console.log(" Revoke: wyrm cloud devices revoke <device-id>"),console.log("")}async function z(e){T();const n=e.includes("--dry-run"),s=e.includes("--all"),o=e.includes("--force");h()||(console.error("\u26A0 Encryption key not found at",g()),console.error(" A new key will be generated, but it will NOT decrypt deltas"),console.error(" pushed by other devices using a different key. If you have a backup,"),console.error(" restore it to ~/.wyrm/cloud.key (0600) before running sync."),console.error(" Continuing in 5 seconds \u2014 press Ctrl+C to abort."),await new Promise(l=>{setTimeout(l,5e3)})),s&&(console.log("\u{1F310} --all mode: syncing every semantic table (ignoring visibility flags)."),console.log(" Skipped: per-device logs, FTS shadows, sync internals."),console.log(""));const r=await L({dryRun:n,all:s,force:o});if(console.log(""),console.log("\u{F115D} Sync complete"),console.log(""),console.log(` Pushed: ${r.pushed} row(s)${n?" (dry run)":""}`),console.log(` Pulled: ${r.pulled} row(s)`),console.log(` Deleted: ${r.deleted_local} row(s) (peer tombstones)`),r.errors.length>0){console.log(""),console.log(" Errors:");for(const l of r.errors)console.log(` - ${l}`)}console.log("")}async function J(e){T();const n=a.fromSession(),s=e.find(c=>!c.startsWith("-")),o=P(s??`wyrm-cloud-export-${new Date().toISOString().slice(0,10)}.json`);console.log(" Fetching account export\u2026");const r=await n.accountExport(),l=JSON.stringify(r,null,2);I(o,l,{mode:384}),console.log(""),console.log(`\u2713 Exported to ${o}`),console.log(""),console.log(` Account: ${r.account.email} (${r.account.id.slice(0,8)}\u2026)`),console.log(` Devices: ${r.devices.length}`),console.log(` Identities: ${r.identities.length}`),console.log(` Orgs: ${r.orgs.length}`),console.log(` Deltas: ${r.deltas.length}`),console.log(` Size: ${w(l.length)}`),console.log(""),console.log(" Deltas are still AES-256-GCM ciphertext. To decrypt them you"),console.log(` need the key at ${g()}. Back that up alongside this file.`),console.log("")}async function Q(e){e.includes("--confirm")||(console.error("\u2718 This permanently deletes your Wyrm Cloud account."),console.error(" Re-run with --confirm to proceed:"),console.error(" wyrm cloud delete --confirm"),process.exit(1));const s=d();s||(console.error("\u2718 Not logged in. Nothing to delete."),process.exit(1)),console.log(""),console.log("\u26A0 You are about to PERMANENTLY DELETE your Wyrm Cloud account."),console.log(""),console.log(` Account: ${s.email??s.account_id}`),console.log(` Server: ${s.base}`),console.log(""),console.log(" This wipes all encrypted blobs, devices, identities, and org"),console.log(" memberships you own. The local ~/.wyrm/cloud.key is kept so"),console.log(" any backups remain decryptable. This action CANNOT be undone."),console.log(""),(await _(' Type "DELETE my account" to confirm: ')).trim()!=="DELETE my account"&&(console.error("\u2718 Confirmation phrase did not match. Aborting."),process.exit(1)),await new a(s.base,s.session).accountDelete(),b(),console.log(""),console.log("\u2713 Account deleted on server."),console.log(`\u2713 Local session removed (${f}).`),console.log(` Kept: ${g()} (in case you need to decrypt an old export).`),console.log("")}function _(e){return new Promise(n=>{const s=A({input:process.stdin,output:process.stdout});s.question(e,o=>{s.close(),n(o)})})}async function V(e){const n=e[0];if(n==="show"){const{keyToMnemonic:s}=await import("./recovery.js"),r=s().split(" ");console.log(""),console.log("\u{1F511} Wyrm Cloud recovery phrase (BIP39, 24 words)"),console.log("");for(let l=0;l<4;l++){const c=r.slice(l*6,l*6+6).map((i,u)=>`${String(l*6+u+1).padStart(2)}. ${i.padEnd(10)}`).join(" ");console.log(` ${c}`)}console.log(""),console.log(" Write these down on PAPER and keep them somewhere safe."),console.log(" Anyone with this phrase can decrypt your synced Wyrm memory."),console.log(" Wyrm Cloud has NO COPY of this. Lose it = lose your data."),console.log("");return}if(n==="restore"){const s=e.includes("--overwrite");console.log(""),console.log("\u{1F511} Restore master key from BIP39 recovery phrase"),console.log(""),console.log(" Enter your 24 words separated by spaces (case-insensitive)."),console.log(" Press Enter when done."),console.log("");const o=await _(" > "),{mnemonicToKey:r}=await import("./recovery.js"),{path:l,overwrote:c}=r(o,{overwrite:s});console.log(""),console.log(`\u2713 Master key restored to ${l} (0600)`),c&&console.log(" Replaced existing key (you passed --overwrite)."),console.log(" Run `wyrm cloud sync` to pull deltas with the restored key."),console.log("");return}console.error("Usage:"),console.error(" wyrm cloud recovery show"),console.error(" wyrm cloud recovery restore [--overwrite]"),process.exit(1)}export{ie as cmdCloud};