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
@@ -1,162 +1,2 @@
1
- /**
2
- * v7 deprecation notices Wyrm 6.18 bridge release (BROOD Phase F1, T007).
3
- *
4
- * 100% ADDITIVE on the 6.x surface: nothing here changes tool behavior,
5
- * names, schemas, or profiles. This module is the single registry for the
6
- * 22 CLI-exile candidates (spec FR-4: 18 operator tools + the device-egress
7
- * quartet) and emits three kinds of forward-compat warnings:
8
- *
9
- * 1. A description SUFFIX merged into tools/list at build time
10
- * (`applyDeprecationNotices`, wired in the ListToolsRequestSchema
11
- * handler next to `annotateTools`).
12
- * 2. A ONE-TIME-per-tool stderr note when a deprecated tool is invoked via
13
- * MCP (`noteDeprecatedToolInvocation`, wired at the top of the CallTool
14
- * handler). Failure-isolated: it can never block or break a call.
15
- * (The 6.18 bridge also carried a ONE-TIME `WYRM_PROFILE=full` notice —
16
- * removed at T022 when `full` became the permanent synonym of `legacy` it
17
- * announced; see tool-profiles.ts.)
18
- *
19
- * (wyrm-http — the legacy HTTP server the 6.18 bridge warned about — was
20
- * DELETED at 7.0.0/T033; its T005 access log recorded zero traffic. `wyrm
21
- * serve` (http-fast) is the maintained HTTP surface.)
22
- *
23
- * CLI commands follow the spec FR-4 / T023 disposition: `wyrm
24
- * setup|embed|sync|cloud` already exist; `wyrm
25
- * license|activate|index|update|prompt|hours|invoice|agent` ship with T023.
26
- * In 7.0 the MCP aliases for these names return a structured redirect to the
27
- * exact command below — EXCEPT `wyrm_cloud_backup` and `wyrm_sync_export`,
28
- * which stay FUNCTIONAL through 7.0.x with a published sunset so scheduled
29
- * backups never silently stop.
30
- *
31
- * All output goes to STDERR only (stdout is the MCP wire on stdio).
32
- */
33
- // T023: the structured redirect rides the single T019 error dual-emit, so
34
- // text/structured can never drift (render.ts imports nothing from here — no cycle).
35
- import { renderErrorResponse } from './render.js';
36
- /** The 22 CLI-exile candidates (spec FR-4): 18 operator + 4 device-egress. */
37
- export const CLI_EXILE_TOOLS = {
38
- // ── 18 operator tools ──────────────────────────────────────────────────
39
- wyrm_setup: { cli: "wyrm setup" },
40
- wyrm_embed: { cli: "wyrm embed" },
41
- wyrm_inject_prompt: { cli: "wyrm prompt inject" },
42
- wyrm_migrate_prompt: { cli: "wyrm prompt migrate" },
43
- wyrm_check_update: { cli: "wyrm update --check" },
44
- wyrm_self_update: { cli: "wyrm update" },
45
- wyrm_vector_setup: { cli: "wyrm index setup" },
46
- // reindex's long-running form also survives inside wyrm_maintenance (FR-3).
47
- wyrm_reindex: { cli: "wyrm index rebuild" },
48
- wyrm_encrypt_setup: { cli: "wyrm setup --encrypt" },
49
- wyrm_license: { cli: "wyrm license" },
50
- wyrm_activate: { cli: "wyrm activate <key>" },
51
- wyrm_hours_report: { cli: "wyrm hours report" },
52
- wyrm_invoice_generate: { cli: "wyrm invoice generate" },
53
- wyrm_agent_init: { cli: "wyrm agent init" },
54
- wyrm_agent_status: { cli: "wyrm agent status" },
55
- wyrm_agent_stop: { cli: "wyrm agent stop" },
56
- wyrm_agent_restart: { cli: "wyrm agent restart" },
57
- wyrm_intro: { cli: "wyrm intro" },
58
- // ── device-egress quartet ──────────────────────────────────────────────
59
- wyrm_sync_export: { cli: "wyrm sync export --out <path>", functionalThrough: "7.0.x" },
60
- wyrm_sync_import: { cli: "wyrm sync import --from <path>" },
61
- wyrm_cloud_backup: { cli: "wyrm cloud backup", functionalThrough: "7.0.x" },
62
- wyrm_cloud_sync: { cli: "wyrm cloud sync" },
63
- };
64
- /**
65
- * The description suffix advertised on a deprecated tool, or '' for
66
- * non-deprecated tools. Exact format pinned by tests.
67
- */
68
- export function deprecationSuffix(name) {
69
- const entry = CLI_EXILE_TOOLS[name];
70
- if (!entry)
71
- return "";
72
- const grace = entry.functionalThrough
73
- ? ` — remains functional through ${entry.functionalThrough}`
74
- : "";
75
- return ` [Deprecated in v7 → use the wyrm CLI: ${entry.cli}${grace}]`;
76
- }
77
- /**
78
- * Append the deprecation suffix to each CLI-exile tool's description.
79
- * Non-mutating; unknown tools pass through untouched. Never throws.
80
- */
81
- export function applyDeprecationNotices(tools) {
82
- return tools.map((tool) => {
83
- const suffix = deprecationSuffix(tool.name);
84
- if (!suffix)
85
- return tool;
86
- return { ...tool, description: `${tool.description ?? ""}${suffix}` };
87
- });
88
- }
89
- // One-time-per-process warning state (per tool name).
90
- const warnedTools = new Set();
91
- /** Test hook: reset the one-time warning state. */
92
- export function _resetDeprecationWarningsForTests() {
93
- warnedTools.clear();
94
- }
95
- /**
96
- * One-time stderr note when a CLI-exile tool is invoked via MCP.
97
- * Failure-isolated: never throws, never blocks the call.
98
- */
99
- export function noteDeprecatedToolInvocation(name) {
100
- try {
101
- const entry = CLI_EXILE_TOOLS[name];
102
- if (!entry || warnedTools.has(name))
103
- return;
104
- warnedTools.add(name);
105
- const grace = entry.functionalThrough
106
- ? ` This name stays functional through ${entry.functionalThrough} (published sunset) so scheduled jobs never silently stop.`
107
- : "";
108
- process.stderr.write(`[wyrm] DEPRECATION: '${name}' moves off MCP to the wyrm CLI in 7.0 — use: ${entry.cli}.` +
109
- ` Behavior is unchanged in 6.18.${grace}\n`);
110
- }
111
- catch {
112
- /* warnings must never break a tool call */
113
- }
114
- }
115
- // v7 F3 (T022): noteFullProfileDeprecation() (the 6.18 bridge notice) is
116
- // GONE — WYRM_PROFILE=full is now the permanent synonym of 'legacy' it
117
- // announced (tool-profiles.ts resolveProfile), and full has always meant
118
- // "every tool", so there is nothing left to warn about.
119
- // ── v7 F3 (T023): the structured CLI-exile redirect ─────────────────────────
120
- //
121
- // In 7.0 the MCP aliases for the names above stop executing and return this
122
- // structured redirect instead (spec FR-4 / §6: "~22 operator/egress names
123
- // return a structured redirect to their exact `wyrm` CLI replacement").
124
- // EXCEPTION: the functionalThrough pair (cloud_backup / sync_export) keeps
125
- // EXECUTING through 7.0.x with the published sunset — scheduled backups never
126
- // silently stop. isHardCliExile() is the dispatcher's gate.
127
- /** Machine-readable code for a CLI-exile redirect. Deliberately distinct from
128
- * WYRM_BUSY (retry as-is) and WYRM_VALIDATION (correct an argument): a CLI
129
- * exile is NEVER satisfiable over MCP — the contract is "run this command". */
130
- export const WYRM_CLI_EXILE_CODE = 'WYRM_CLI_EXILE';
131
- /** True iff `name` is CLI-exiled AND past its grace window — i.e. the MCP
132
- * alias returns the structured redirect instead of executing. The
133
- * functionalThrough pair (cloud_backup/sync_export) returns false: they stay
134
- * functional through 7.0.x (published sunset). */
135
- export function isHardCliExile(name) {
136
- const entry = CLI_EXILE_TOOLS[name];
137
- return entry !== undefined && entry.functionalThrough === undefined;
138
- }
139
- /** Build the redirect body. Deterministic (Article VIII: same name, same bytes). */
140
- export function cliExileRedirectBody(name) {
141
- const entry = CLI_EXILE_TOOLS[name];
142
- if (!entry)
143
- throw new Error(`cliExileRedirectBody: '${name}' is not a CLI-exile tool`);
144
- return {
145
- error: {
146
- code: WYRM_CLI_EXILE_CODE,
147
- message: `${name} moved off MCP in 7.0 — run: ${entry.cli}`,
148
- retryable: false,
149
- },
150
- cli: entry.cli,
151
- tool: name,
152
- expected: `This operator/egress tool is CLI-exiled (spec FR-4): its MCP alias never executes in 7.0. ` +
153
- `Run \`${entry.cli}\` from a shell instead — do not retry this MCP call (the redirect is ` +
154
- `deterministic). The wyrm CLI ships the full replacement; \`wyrm --help\` lists it.`,
155
- };
156
- }
157
- /** The full dual-emit MCP response for a CLI-exile redirect — `isError:true`,
158
- * text = JSON of the SAME body riding structuredContent (the T019 renderer). */
159
- export function cliExileRedirectResponse(name) {
160
- return renderErrorResponse(cliExileRedirectBody(name));
161
- }
162
- //# sourceMappingURL=deprecations.js.map
1
+ import{renderErrorResponse as c}from"./render.js";const i={wyrm_setup:{cli:"wyrm setup"},wyrm_embed:{cli:"wyrm embed"},wyrm_inject_prompt:{cli:"wyrm prompt inject"},wyrm_migrate_prompt:{cli:"wyrm prompt migrate"},wyrm_check_update:{cli:"wyrm update --check"},wyrm_self_update:{cli:"wyrm update"},wyrm_vector_setup:{cli:"wyrm index setup"},wyrm_reindex:{cli:"wyrm index rebuild"},wyrm_encrypt_setup:{cli:"wyrm setup --encrypt"},wyrm_license:{cli:"wyrm license"},wyrm_activate:{cli:"wyrm activate <key>"},wyrm_hours_report:{cli:"wyrm hours report"},wyrm_invoice_generate:{cli:"wyrm invoice generate"},wyrm_agent_init:{cli:"wyrm agent init"},wyrm_agent_status:{cli:"wyrm agent status"},wyrm_agent_stop:{cli:"wyrm agent stop"},wyrm_agent_restart:{cli:"wyrm agent restart"},wyrm_intro:{cli:"wyrm intro"},wyrm_sync_export:{cli:"wyrm sync export --out <path>",functionalThrough:"7.0.x"},wyrm_sync_import:{cli:"wyrm sync import --from <path>"},wyrm_cloud_backup:{cli:"wyrm cloud backup",functionalThrough:"7.0.x"},wyrm_cloud_sync:{cli:"wyrm cloud sync"}};function o(r){const e=i[r];if(!e)return"";const t=e.functionalThrough?` \u2014 remains functional through ${e.functionalThrough}`:"";return` [Deprecated in v7 \u2192 use the wyrm CLI: ${e.cli}${t}]`}function y(r){return r.map(e=>{const t=o(e.name);return t?{...e,description:`${e.description??""}${t}`}:e})}const n=new Set;function m(){n.clear()}function a(r){try{const e=i[r];if(!e||n.has(r))return;n.add(r);const t=e.functionalThrough?` This name stays functional through ${e.functionalThrough} (published sunset) so scheduled jobs never silently stop.`:"";process.stderr.write(`[wyrm] DEPRECATION: '${r}' moves off MCP to the wyrm CLI in 7.0 \u2014 use: ${e.cli}. Behavior is unchanged in 6.18.${t}
2
+ `)}catch{}}const s="WYRM_CLI_EXILE";function p(r){const e=i[r];return e!==void 0&&e.functionalThrough===void 0}function l(r){const e=i[r];if(!e)throw new Error(`cliExileRedirectBody: '${r}' is not a CLI-exile tool`);return{error:{code:s,message:`${r} moved off MCP in 7.0 \u2014 run: ${e.cli}`,retryable:!1},cli:e.cli,tool:r,expected:`This operator/egress tool is CLI-exiled (spec FR-4): its MCP alias never executes in 7.0. Run \`${e.cli}\` from a shell instead \u2014 do not retry this MCP call (the redirect is deterministic). The wyrm CLI ships the full replacement; \`wyrm --help\` lists it.`}}function w(r){return c(l(r))}export{i as CLI_EXILE_TOOLS,s as WYRM_CLI_EXILE_CODE,m as _resetDeprecationWarningsForTests,y as applyDeprecationNotices,l as cliExileRedirectBody,w as cliExileRedirectResponse,o as deprecationSuffix,p as isHardCliExile,a as noteDeprecatedToolInvocation};
package/dist/design.js CHANGED
@@ -1,38 +1,4 @@
1
- /**
2
- * Design tokens + references (5.9.0 — creative workflow).
3
- *
4
- * Design tokens store a project's design system primitives (colour /
5
- * type / spacing / motion / etc.) as first-class data so AI helpers
6
- * working on creative tasks don't have to re-derive the system from
7
- * scattered CSS or memory artifacts on every call.
8
- *
9
- * Design references store inspiration / mood-board URLs and image paths
10
- * with tags, so a designer accreting visual references over time has a
11
- * searchable library.
12
- *
13
- * @copyright 2026 Ghost Protocol (Pvt) Ltd.
14
- * @license AGPL-3.0-or-later — dual-licensed; commercial terms: ghosts.lk@proton.me. See LICENSE.
15
- */
16
- const VALID_CATEGORIES = new Set([
17
- 'color', 'type', 'spacing', 'motion', 'shadow', 'radius', 'breakpoint', 'custom',
18
- ]);
19
- const VALID_REF_KINDS = new Set([
20
- 'url', 'image', 'palette', 'snippet',
21
- ]);
22
- export class DesignSystem {
23
- db;
24
- constructor(db) {
25
- this.db = db;
26
- }
27
- // ---------- Tokens ----------
28
- setToken(input) {
29
- if (!VALID_CATEGORIES.has(input.category)) {
30
- throw new Error(`invalid category: ${input.category} (must be one of ${[...VALID_CATEGORIES].join(', ')})`);
31
- }
32
- if (!input.key || !input.value) {
33
- throw new Error('key and value are required');
34
- }
35
- this.db.prepare(`
1
+ const a=new Set(["color","type","spacing","motion","shadow","radius","breakpoint","custom"]),d=new Set(["url","image","palette","snippet"]);class E{db;constructor(e){this.db=e}setToken(e){if(!a.has(e.category))throw new Error(`invalid category: ${e.category} (must be one of ${[...a].join(", ")})`);if(!e.key||!e.value)throw new Error("key and value are required");return this.db.prepare(`
36
2
  INSERT INTO design_tokens (project_id, category, key, value, notes, source)
37
3
  VALUES (?, ?, ?, ?, ?, ?)
38
4
  ON CONFLICT(project_id, category, key) DO UPDATE SET
@@ -40,135 +6,41 @@ export class DesignSystem {
40
6
  notes = excluded.notes,
41
7
  source = excluded.source,
42
8
  updated_at = datetime('now')
43
- `).run(input.projectId, input.category, input.key, input.value, input.notes ?? null, input.source ?? 'user');
44
- return this.getToken(input.projectId, input.category, input.key);
45
- }
46
- getToken(projectId, category, key) {
47
- const row = this.db.prepare(`
9
+ `).run(e.projectId,e.category,e.key,e.value,e.notes??null,e.source??"user"),this.getToken(e.projectId,e.category,e.key)}getToken(e,r,t){return this.db.prepare(`
48
10
  SELECT * FROM design_tokens
49
11
  WHERE project_id = ? AND category = ? AND key = ?
50
- `).get(projectId, category, key);
51
- return row ?? null;
52
- }
53
- listTokens(projectId, category) {
54
- if (category) {
55
- return this.db.prepare(`
12
+ `).get(e,r,t)??null}listTokens(e,r){return r?this.db.prepare(`
56
13
  SELECT * FROM design_tokens
57
14
  WHERE project_id = ? AND category = ?
58
15
  ORDER BY key
59
- `).all(projectId, category);
60
- }
61
- return this.db.prepare(`
16
+ `).all(e,r):this.db.prepare(`
62
17
  SELECT * FROM design_tokens
63
18
  WHERE project_id = ?
64
19
  ORDER BY category, key
65
- `).all(projectId);
66
- }
67
- deleteToken(projectId, category, key) {
68
- const result = this.db.prepare(`
20
+ `).all(e)}deleteToken(e,r,t){return(this.db.prepare(`
69
21
  DELETE FROM design_tokens
70
22
  WHERE project_id = ? AND category = ? AND key = ?
71
- `).run(projectId, category, key);
72
- return (result.changes ?? 0) > 0;
73
- }
74
- /**
75
- * Render the token set as a markdown table grouped by category — handy
76
- * for folding into context briefs.
77
- */
78
- formatForContext(projectId) {
79
- const tokens = this.listTokens(projectId);
80
- if (tokens.length === 0)
81
- return '';
82
- const byCategory = new Map();
83
- for (const t of tokens) {
84
- const arr = byCategory.get(t.category) ?? [];
85
- arr.push(t);
86
- byCategory.set(t.category, arr);
87
- }
88
- const lines = [];
89
- lines.push('### 🎨 Design tokens');
90
- for (const [cat, items] of byCategory) {
91
- lines.push('');
92
- lines.push(`**${cat}:**`);
93
- for (const t of items) {
94
- lines.push(`- \`${t.key}\` = \`${t.value}\`${t.notes ? ` — ${t.notes}` : ''}`);
95
- }
96
- }
97
- return lines.join('\n');
98
- }
99
- // ---------- References ----------
100
- addReference(input) {
101
- if (!VALID_REF_KINDS.has(input.kind)) {
102
- throw new Error(`invalid kind: ${input.kind} (must be one of ${[...VALID_REF_KINDS].join(', ')})`);
103
- }
104
- if (!input.location) {
105
- throw new Error('location is required');
106
- }
107
- const result = this.db.prepare(`
23
+ `).run(e,r,t).changes??0)>0}formatForContext(e){const r=this.listTokens(e);if(r.length===0)return"";const t=new Map;for(const s of r){const o=t.get(s.category)??[];o.push(s),t.set(s.category,o)}const n=[];n.push("### \u{1F3A8} Design tokens");for(const[s,o]of t){n.push(""),n.push(`**${s}:**`);for(const c of o)n.push(`- \`${c.key}\` = \`${c.value}\`${c.notes?` \u2014 ${c.notes}`:""}`)}return n.join(`
24
+ `)}addReference(e){if(!d.has(e.kind))throw new Error(`invalid kind: ${e.kind} (must be one of ${[...d].join(", ")})`);if(!e.location)throw new Error("location is required");const r=this.db.prepare(`
108
25
  INSERT INTO design_references (project_id, kind, location, title, notes, tags)
109
26
  VALUES (?, ?, ?, ?, ?, ?)
110
- `).run(input.projectId ?? null, input.kind, input.location, input.title ?? null, input.notes ?? null, input.tags ?? null);
111
- return this.getReference(Number(result.lastInsertRowid));
112
- }
113
- getReference(id) {
114
- const row = this.db.prepare(`SELECT * FROM design_references WHERE id = ?`).get(id);
115
- return row ?? null;
116
- }
117
- listReferences(opts = {}) {
118
- const limit = Math.min(opts.limit ?? 50, 200);
119
- if (opts.tag) {
120
- const tagPattern = `%${opts.tag}%`;
121
- if (opts.projectId != null) {
122
- return this.db.prepare(`
27
+ `).run(e.projectId??null,e.kind,e.location,e.title??null,e.notes??null,e.tags??null);return this.getReference(Number(r.lastInsertRowid))}getReference(e){return this.db.prepare("SELECT * FROM design_references WHERE id = ?").get(e)??null}listReferences(e={}){const r=Math.min(e.limit??50,200);if(e.tag){const t=`%${e.tag}%`;return e.projectId!=null?this.db.prepare(`
123
28
  SELECT * FROM design_references
124
29
  WHERE project_id = ? AND tags LIKE ?
125
30
  ORDER BY captured_at DESC LIMIT ?
126
- `).all(opts.projectId, tagPattern, limit);
127
- }
128
- return this.db.prepare(`
31
+ `).all(e.projectId,t,r):this.db.prepare(`
129
32
  SELECT * FROM design_references
130
33
  WHERE tags LIKE ?
131
34
  ORDER BY captured_at DESC LIMIT ?
132
- `).all(tagPattern, limit);
133
- }
134
- if (opts.projectId != null) {
135
- return this.db.prepare(`
35
+ `).all(t,r)}return e.projectId!=null?this.db.prepare(`
136
36
  SELECT * FROM design_references
137
37
  WHERE project_id = ?
138
38
  ORDER BY captured_at DESC LIMIT ?
139
- `).all(opts.projectId, limit);
140
- }
141
- return this.db.prepare(`
39
+ `).all(e.projectId,r):this.db.prepare(`
142
40
  SELECT * FROM design_references
143
41
  ORDER BY captured_at DESC LIMIT ?
144
- `).all(limit);
145
- }
146
- searchReferences(query, opts = {}) {
147
- const limit = Math.min(opts.limit ?? 20, 100);
148
- // FTS5 lookup, joined back to design_references for the full rows.
149
- const baseQuery = `
42
+ `).all(r)}searchReferences(e,r={}){const t=Math.min(r.limit??20,100),n=`
150
43
  SELECT dr.* FROM design_references_fts ft
151
44
  JOIN design_references dr ON dr.id = ft.rowid
152
45
  WHERE design_references_fts MATCH ?
153
- `;
154
- if (opts.projectId != null) {
155
- return this.db.prepare(`${baseQuery} AND dr.project_id = ? ORDER BY dr.captured_at DESC LIMIT ?`)
156
- .all(query, opts.projectId, limit);
157
- }
158
- return this.db.prepare(`${baseQuery} ORDER BY dr.captured_at DESC LIMIT ?`)
159
- .all(query, limit);
160
- }
161
- deleteReference(id) {
162
- const result = this.db.prepare(`DELETE FROM design_references WHERE id = ?`).run(id);
163
- return (result.changes ?? 0) > 0;
164
- }
165
- countReferences(projectId) {
166
- if (projectId != null) {
167
- const row = this.db.prepare(`SELECT COUNT(*) AS n FROM design_references WHERE project_id = ?`).get(projectId);
168
- return row.n;
169
- }
170
- const row = this.db.prepare(`SELECT COUNT(*) AS n FROM design_references`).get();
171
- return row.n;
172
- }
173
- }
174
- //# sourceMappingURL=design.js.map
46
+ `;return r.projectId!=null?this.db.prepare(`${n} AND dr.project_id = ? ORDER BY dr.captured_at DESC LIMIT ?`).all(e,r.projectId,t):this.db.prepare(`${n} ORDER BY dr.captured_at DESC LIMIT ?`).all(e,t)}deleteReference(e){return(this.db.prepare("DELETE FROM design_references WHERE id = ?").run(e).changes??0)>0}countReferences(e){return e!=null?this.db.prepare("SELECT COUNT(*) AS n FROM design_references WHERE project_id = ?").get(e).n:this.db.prepare("SELECT COUNT(*) AS n FROM design_references").get().n}}export{E as DesignSystem};
@@ -1,112 +1 @@
1
- /**
2
- * Live Memory v6.4 (Phase 3) — cross-device event replication.
3
- *
4
- * A Wyrm node syncs with a PEER node over the Phase-2 HTTP surface — no separate
5
- * cloud relay required, the peer is just another Wyrm HTTP server (`wyrm serve`):
6
- * - PULL: GET peer `/events?project=…&since=<pull-watermark>` → ingest each.
7
- * - PUSH: POST peer `/events` with our shared events newer than <push-watermark>.
8
- *
9
- * Privacy: only `is_shared = 1` events are ever collected for push, and
10
- * reference-only rows keep `payload_json = NULL` — protected data never leaves
11
- * the box. Dedup + echo-suppression happen on the receiving side
12
- * (`ingestRemoteEvent`), so re-delivery and round-trips are no-ops. Events are
13
- * append-only, so there are no merge conflicts.
14
- *
15
- * The DB dependency is a structural interface (`ReplDb`) and `fetch` is injected,
16
- * so this whole module is unit-testable with a fake peer and no real sockets.
17
- *
18
- * @copyright 2026 Ghost Protocol (Pvt) Ltd.
19
- * @license AGPL-3.0-or-later — dual-licensed; commercial terms: ghosts.lk@proton.me. See LICENSE.
20
- */
21
- import { logger } from './logger.js';
22
- import { guardedFetchJson, checkPeerUrl } from './repl-guard.js';
23
- const PUSH_LIMIT = 500;
24
- const pushKey = (p) => `repl:push:${p.baseUrl}:${p.localProjectId}`;
25
- const pullKey = (p) => `repl:pull:${p.baseUrl}:${p.localProjectId}`;
26
- const toInt = (v) => Number.parseInt(v || '0', 10) || 0;
27
- function authHeaders(token) {
28
- const h = { 'Content-Type': 'application/json' };
29
- if (token)
30
- h['Authorization'] = `Bearer ${token}`;
31
- return h;
32
- }
33
- /** Map a stored event to the wire shape; the peer records OUR cursor as its `cloud_cursor`. */
34
- function toRemote(e) {
35
- return {
36
- origin_device: e.origin_device, origin_seq: e.origin_seq, cloud_cursor: e.cursor,
37
- kind: e.kind, ref_table: e.ref_table, ref_id: e.ref_id, actor: e.actor,
38
- is_shared: e.is_shared, payload_json: e.payload_json, created_at: e.created_at,
39
- };
40
- }
41
- /** PUSH local shared events newer than our push-watermark to the peer. */
42
- export async function pushSharedEvents(db, peer, fetchImpl) {
43
- const pre = checkPeerUrl(peer.baseUrl);
44
- if (!pre.ok)
45
- return { pushed: 0, error: `push: ${pre.reason}` };
46
- const since = toInt(db.getMeta(pushKey(peer)));
47
- const batch = db.eventsForPush(peer.localProjectId, since, PUSH_LIMIT);
48
- if (batch.length === 0)
49
- return { pushed: 0 };
50
- try {
51
- // guardedFetchJson enforces scheme/host allowlist, no-redirect, timeout, bounded body.
52
- await guardedFetchJson(fetchImpl, peer.baseUrl, '/events', {
53
- method: 'POST', headers: authHeaders(peer.token),
54
- body: JSON.stringify({ project: peer.remoteProject, events: batch.map(toRemote) }),
55
- });
56
- // Advance the watermark only on a confirmed 2xx (ingest is idempotent, so a
57
- // retried push that double-sends is harmless on the peer).
58
- db.setMeta(pushKey(peer), String(batch[batch.length - 1].cursor));
59
- return { pushed: batch.length };
60
- }
61
- catch (err) {
62
- const msg = err instanceof Error ? err.message : String(err);
63
- logger.warn('live-memory: push failed', { peer: peer.baseUrl, err: msg });
64
- return { pushed: 0, error: `push: ${msg}` };
65
- }
66
- }
67
- /** PULL peer events newer than our pull-watermark and ingest them. */
68
- export async function pullPeerEvents(db, peer, fetchImpl) {
69
- const since = toInt(db.getMeta(pullKey(peer)));
70
- try {
71
- // `shared=1`: only the peer's shareable events — its private events stay on its box.
72
- const path = `/events?project=${encodeURIComponent(peer.remoteProject)}&since=${since}&limit=${PUSH_LIMIT}&shared=1`;
73
- const data = (await guardedFetchJson(fetchImpl, peer.baseUrl, path, {
74
- method: 'GET', headers: authHeaders(peer.token),
75
- }));
76
- // Defend against a hostile/buggy peer: keep only well-shaped events with a
77
- // finite numeric cursor, and never ingest more than we asked for.
78
- const events = (Array.isArray(data.events) ? data.events : [])
79
- .filter((e) => !!e && typeof e === 'object' && Number.isFinite(e.cursor))
80
- .slice(0, PUSH_LIMIT);
81
- let pulled = 0, duplicates = 0, echoes = 0;
82
- let maxCursor = since;
83
- for (const e of events) {
84
- const r = db.ingestRemoteEvent(peer.localProjectId, { ...toRemote(e), cloud_cursor: e.cursor });
85
- if (r === 'inserted')
86
- pulled++;
87
- else if (r === 'echo')
88
- echoes++;
89
- else
90
- duplicates++;
91
- if (e.cursor > maxCursor)
92
- maxCursor = e.cursor;
93
- }
94
- // Advance the watermark ONLY from cursors we actually received — NEVER from a
95
- // peer-claimed `cursor` scalar. An empty or forged response can't skip us forward.
96
- db.setMeta(pullKey(peer), String(maxCursor));
97
- return { pulled, duplicates, echoes };
98
- }
99
- catch (err) {
100
- const msg = err instanceof Error ? err.message : String(err);
101
- logger.warn('live-memory: pull failed', { peer: peer.baseUrl, err: msg });
102
- return { pulled: 0, duplicates: 0, echoes: 0, error: `pull: ${msg}` };
103
- }
104
- }
105
- /** One-shot replication with a peer: pull, then push. Never throws. */
106
- export async function replicateOnce(db, peer, fetchImpl) {
107
- const pull = await pullPeerEvents(db, peer, fetchImpl);
108
- const push = await pushSharedEvents(db, peer, fetchImpl);
109
- const errors = [pull.error, push.error].filter((e) => Boolean(e));
110
- return { pulled: pull.pulled, pushed: push.pushed, duplicates: pull.duplicates, echoes: pull.echoes, errors };
111
- }
112
- //# sourceMappingURL=event-replication.js.map
1
+ import{logger as f}from"./logger.js";import{guardedFetchJson as m,checkPeerUrl as P}from"./repl-guard.js";const u=500,g=e=>`repl:push:${e.baseUrl}:${e.localProjectId}`,y=e=>`repl:pull:${e.baseUrl}:${e.localProjectId}`,_=e=>Number.parseInt(e||"0",10)||0;function v(e){const r={"Content-Type":"application/json"};return e&&(r.Authorization=`Bearer ${e}`),r}function j(e){return{origin_device:e.origin_device,origin_seq:e.origin_seq,cloud_cursor:e.cursor,kind:e.kind,ref_table:e.ref_table,ref_id:e.ref_id,actor:e.actor,is_shared:e.is_shared,payload_json:e.payload_json,created_at:e.created_at}}async function $(e,r,a){const o=P(r.baseUrl);if(!o.ok)return{pushed:0,error:`push: ${o.reason}`};const s=_(e.getMeta(g(r))),t=e.eventsForPush(r.localProjectId,s,u);if(t.length===0)return{pushed:0};try{return await m(a,r.baseUrl,"/events",{method:"POST",headers:v(r.token),body:JSON.stringify({project:r.remoteProject,events:t.map(j)})}),e.setMeta(g(r),String(t[t.length-1].cursor)),{pushed:t.length}}catch(c){const i=c instanceof Error?c.message:String(c);return f.warn("live-memory: push failed",{peer:r.baseUrl,err:i}),{pushed:0,error:`push: ${i}`}}}async function U(e,r,a){const o=_(e.getMeta(y(r)));try{const s=`/events?project=${encodeURIComponent(r.remoteProject)}&since=${o}&limit=${u}&shared=1`,t=await m(a,r.baseUrl,s,{method:"GET",headers:v(r.token)}),c=(Array.isArray(t.events)?t.events:[]).filter(n=>!!n&&typeof n=="object"&&Number.isFinite(n.cursor)).slice(0,u);let i=0,d=0,h=0,l=o;for(const n of c){const p=e.ingestRemoteEvent(r.localProjectId,{...j(n),cloud_cursor:n.cursor});p==="inserted"?i++:p==="echo"?h++:d++,n.cursor>l&&(l=n.cursor)}return e.setMeta(y(r),String(l)),{pulled:i,duplicates:d,echoes:h}}catch(s){const t=s instanceof Error?s.message:String(s);return f.warn("live-memory: pull failed",{peer:r.baseUrl,err:t}),{pulled:0,duplicates:0,echoes:0,error:`pull: ${t}`}}}async function S(e,r,a){const o=await U(e,r,a),s=await $(e,r,a),t=[o.error,s.error].filter(c=>!!c);return{pulled:o.pulled,pushed:s.pushed,duplicates:o.duplicates,echoes:o.echoes,errors:t}}export{U as pullPeerEvents,$ as pushSharedEvents,S as replicateOnce};
@@ -1,43 +1,7 @@
1
- /**
2
- * Live Memory v6.4 (Phase 2) — SSE framing + cursor-resume helpers.
3
- *
4
- * Pure functions, no DB/HTTP imports. Kept in their own module so they can be
5
- * unit-tested in isolation. (Historical: importing `http-fast.ts` used to run
6
- * a top-level `new WyrmDB()` and open the real ~/.wyrm/wyrm.db at module
7
- * load; v7 F2 T012 made that open lazy, but small pure modules remain the
8
- * right unit-test surface.)
9
- *
10
- * @copyright 2026 Ghost Protocol (Pvt) Ltd.
11
- * @license AGPL-3.0-or-later — dual-licensed; commercial terms: ghosts.lk@proton.me. See LICENSE.
12
- */
13
- /**
14
- * Serialize one event as an SSE frame. The `id:` line is the event cursor, so a
15
- * dropped client reconnects with `Last-Event-ID: <cursor>` and resumes exactly
16
- * where it left off. `kind` becomes the `event:` type; newlines are stripped so
17
- * a malformed kind can't inject extra SSE fields.
18
- */
19
- export function sseFrame(ev) {
20
- const kind = String(ev.kind).replace(/[\r\n]/g, ' ');
21
- return `id: ${ev.cursor}\nevent: ${kind}\ndata: ${JSON.stringify(ev)}\n\n`;
22
- }
23
- /** An SSE keep-alive comment line (ignored by clients, keeps the socket warm). */
24
- export const SSE_KEEPALIVE = ': ka\n\n';
25
- /**
26
- * Resolve the cursor an SSE / pull client should start from.
27
- * Precedence: SSE reconnect `Last-Event-ID` header → `?since=` query param → 0.
28
- * Always returns a finite integer ≥ 0 (defends against negative, NaN, and the
29
- * `string[]` shape Node uses for repeated headers). A present-but-garbage
30
- * Last-Event-ID falls through to `since` rather than silently resetting to 0.
31
- */
32
- export function resolveStartCursor(lastEventId, sinceParam) {
33
- const fromHeader = Array.isArray(lastEventId) ? lastEventId[0] : lastEventId;
34
- for (const raw of [fromHeader, sinceParam]) {
35
- if (raw === undefined || raw === null || raw === '')
36
- continue;
37
- const n = Number.parseInt(String(raw), 10);
38
- if (Number.isFinite(n) && n >= 0)
39
- return n;
40
- }
41
- return 0;
42
- }
43
- //# sourceMappingURL=events-sse.js.map
1
+ function o(r){const e=String(r.kind).replace(/[\r\n]/g," ");return`id: ${r.cursor}
2
+ event: ${e}
3
+ data: ${JSON.stringify(r)}
4
+
5
+ `}const s=`: ka
6
+
7
+ `;function u(r,e){const i=Array.isArray(r)?r[0]:r;for(const n of[i,e]){if(n==null||n==="")continue;const t=Number.parseInt(String(n),10);if(Number.isFinite(t)&&t>=0)return t}return 0}export{s as SSE_KEEPALIVE,u as resolveStartCursor,o as sseFrame};