wyrm-mcp 7.2.0 → 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 (156) hide show
  1. package/LICENSE +26 -667
  2. package/NOTICE +14 -33
  3. package/dist/activation.d.ts.map +1 -1
  4. package/dist/activation.js +1 -44
  5. package/dist/activation.js.map +1 -1
  6. package/dist/agent-daemon.js +4 -281
  7. package/dist/agent-loop.js +7 -332
  8. package/dist/analytics.js +13 -236
  9. package/dist/attribution.js +1 -49
  10. package/dist/audit.js +2 -457
  11. package/dist/auto-capture.js +3 -138
  12. package/dist/auto-orchestrator.js +1 -325
  13. package/dist/autoconfig.js +39 -840
  14. package/dist/buddy-runner.js +1 -109
  15. package/dist/buddy.js +14 -564
  16. package/dist/build-flags.js +1 -17
  17. package/dist/capabilities.js +3 -183
  18. package/dist/capture.js +1 -56
  19. package/dist/causality.js +6 -107
  20. package/dist/cli.js +20 -281
  21. package/dist/cloud/cli.js +5 -541
  22. package/dist/cloud/client.js +1 -221
  23. package/dist/cloud/crypto.js +1 -85
  24. package/dist/cloud/machine-id.js +2 -113
  25. package/dist/cloud/recovery.js +1 -60
  26. package/dist/cloud/sync-engine.js +7 -543
  27. package/dist/cloud-backup.js +5 -579
  28. package/dist/cloud-profile.js +1 -138
  29. package/dist/cloud-sync-entrypoint.js +1 -47
  30. package/dist/cloud-sync.js +2 -309
  31. package/dist/constellation.js +12 -168
  32. package/dist/context-build-budgeted.js +4 -144
  33. package/dist/context-ranking.js +1 -69
  34. package/dist/crypto.js +1 -179
  35. package/dist/daemon-write-endpoint.js +1 -290
  36. package/dist/daemon-writer.js +2 -406
  37. package/dist/database.js +43 -1110
  38. package/dist/deprecations.js +2 -162
  39. package/dist/design.js +13 -141
  40. package/dist/event-replication.js +1 -112
  41. package/dist/events-sse.js +7 -43
  42. package/dist/events.js +6 -238
  43. package/dist/failure-patterns.js +42 -659
  44. package/dist/federation.js +12 -236
  45. package/dist/goals.js +13 -101
  46. package/dist/golden.js +3 -355
  47. package/dist/handlers/agent.js +4 -165
  48. package/dist/handlers/alias-adapters.js +1 -129
  49. package/dist/handlers/aliases.js +1 -171
  50. package/dist/handlers/audit.js +1 -87
  51. package/dist/handlers/boundary.js +1 -221
  52. package/dist/handlers/capture.js +73 -1109
  53. package/dist/handlers/causality.js +7 -114
  54. package/dist/handlers/cloud.js +85 -382
  55. package/dist/handlers/companion.js +28 -459
  56. package/dist/handlers/datalake.js +7 -187
  57. package/dist/handlers/dispatch-context.js +0 -22
  58. package/dist/handlers/entity.js +25 -256
  59. package/dist/handlers/events.js +16 -335
  60. package/dist/handlers/failure.js +13 -340
  61. package/dist/handlers/goals.js +4 -296
  62. package/dist/handlers/intelligence.js +126 -674
  63. package/dist/handlers/invoicing.js +1 -70
  64. package/dist/handlers/mcpclient.js +6 -137
  65. package/dist/handlers/orchestration.js +40 -125
  66. package/dist/handlers/output-schemas.js +1 -24
  67. package/dist/handlers/presence.js +3 -99
  68. package/dist/handlers/project.js +28 -182
  69. package/dist/handlers/prompts.js +6 -157
  70. package/dist/handlers/quest.js +4 -224
  71. package/dist/handlers/recall.js +11 -218
  72. package/dist/handlers/registry.js +1 -167
  73. package/dist/handlers/resources.js +1 -288
  74. package/dist/handlers/review.js +11 -74
  75. package/dist/handlers/run.js +17 -487
  76. package/dist/handlers/search.js +15 -326
  77. package/dist/handlers/session.js +28 -615
  78. package/dist/handlers/share.js +8 -184
  79. package/dist/handlers/shims.js +1 -464
  80. package/dist/handlers/skill.js +67 -449
  81. package/dist/handlers/survivors.js +1 -120
  82. package/dist/handlers/symbols.js +8 -109
  83. package/dist/handlers/syncops.js +4 -302
  84. package/dist/handlers/types.js +1 -27
  85. package/dist/harvest.js +5 -191
  86. package/dist/hours.js +7 -156
  87. package/dist/http-auth.js +3 -321
  88. package/dist/http-fast.js +21 -1137
  89. package/dist/icons.js +1 -47
  90. package/dist/index.js +2 -924
  91. package/dist/indexer.js +4 -145
  92. package/dist/intelligence.js +31 -261
  93. package/dist/internal-dispatch.js +3 -212
  94. package/dist/keyset.js +1 -110
  95. package/dist/knowledge-graph.js +12 -176
  96. package/dist/license.d.ts +11 -0
  97. package/dist/license.d.ts.map +1 -1
  98. package/dist/license.js +2 -414
  99. package/dist/license.js.map +1 -1
  100. package/dist/logger.js +2 -199
  101. package/dist/maintenance.js +2 -148
  102. package/dist/mcp-client.js +6 -262
  103. package/dist/memory-artifacts.js +30 -449
  104. package/dist/migrate-prompt.js +2 -124
  105. package/dist/migrations.js +40 -655
  106. package/dist/performance.js +1 -228
  107. package/dist/presence.js +11 -140
  108. package/dist/priority-embed.js +5 -164
  109. package/dist/providers/embedding-provider.js +1 -196
  110. package/dist/readonly-gate.js +1 -29
  111. package/dist/rehydration.js +9 -157
  112. package/dist/reindex.js +1 -88
  113. package/dist/render-target.js +21 -514
  114. package/dist/render.js +4 -280
  115. package/dist/repl-guard.js +1 -173
  116. package/dist/replication-daemon-entrypoint.js +1 -31
  117. package/dist/replication-daemon.js +2 -262
  118. package/dist/resilience.js +1 -591
  119. package/dist/reverse-bridge.js +5 -360
  120. package/dist/security.js +1 -244
  121. package/dist/session-seen.js +3 -51
  122. package/dist/setup.js +1 -260
  123. package/dist/skill-author.js +5 -168
  124. package/dist/spec-kit.js +1 -191
  125. package/dist/sqlite-busy.js +1 -154
  126. package/dist/statusline.js +11 -315
  127. package/dist/sub-agent.js +13 -262
  128. package/dist/summarizer.js +13 -139
  129. package/dist/symbols.js +7 -283
  130. package/dist/sync.js +5 -359
  131. package/dist/tasks-dispatch.js +1 -84
  132. package/dist/tasks.js +1 -282
  133. package/dist/token-budget.js +1 -143
  134. package/dist/tool-analytics.js +7 -129
  135. package/dist/tool-annotations.js +1 -365
  136. package/dist/tool-manifest-v2.json +1 -1
  137. package/dist/tool-manifest.json +1 -1
  138. package/dist/tool-profiles.js +1 -75
  139. package/dist/trace-harvest.js +6 -244
  140. package/dist/types.js +1 -30
  141. package/dist/ui-dashboard.js +41 -50
  142. package/dist/ulid.js +1 -81
  143. package/dist/validate.js +1 -129
  144. package/dist/vault.js +1 -534
  145. package/dist/vectors.js +3 -184
  146. package/dist/version-check.js +4 -136
  147. package/dist/visibility.js +19 -155
  148. package/dist/wyrm-cli.js +98 -2451
  149. package/dist/wyrm-cli.js.map +1 -1
  150. package/dist/wyrm-guard.js +14 -424
  151. package/dist/wyrm-loop.js +3 -150
  152. package/dist/wyrm-manifest.json +1 -1
  153. package/dist/wyrm-statusline-daemon.js +1 -11
  154. package/dist/wyrm-statusline.js +4 -56
  155. package/dist/wyrm-ui.js +9 -77
  156. 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};