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