@graph-tl/graph 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +216 -0
- package/dist/activate-DSDTR2EJ.js +45 -0
- package/dist/activate-DSDTR2EJ.js.map +1 -0
- package/dist/chunk-RVM33A4A.js +497 -0
- package/dist/chunk-RVM33A4A.js.map +1 -0
- package/dist/chunk-WKOEKYTF.js +71 -0
- package/dist/chunk-WKOEKYTF.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/init-JYP72OZQ.js +39 -0
- package/dist/init-JYP72OZQ.js.map +1 -0
- package/dist/nodes-75II37NJ.js +24 -0
- package/dist/nodes-75II37NJ.js.map +1 -0
- package/dist/server-7IB2VIMK.js +1476 -0
- package/dist/server-7IB2VIMK.js.map +1 -0
- package/package.json +43 -0
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/nodes.ts
|
|
4
|
+
import { nanoid as nanoid2 } from "nanoid";
|
|
5
|
+
|
|
6
|
+
// src/db.ts
|
|
7
|
+
import Database from "better-sqlite3";
|
|
8
|
+
import path from "path";
|
|
9
|
+
var db;
|
|
10
|
+
function getDb() {
|
|
11
|
+
if (!db) {
|
|
12
|
+
throw new Error("Database not initialized. Call initDb() first.");
|
|
13
|
+
}
|
|
14
|
+
return db;
|
|
15
|
+
}
|
|
16
|
+
function initDb(dbPath) {
|
|
17
|
+
const resolvedPath = dbPath ?? path.resolve("graph.db");
|
|
18
|
+
db = new Database(resolvedPath);
|
|
19
|
+
db.pragma("journal_mode = WAL");
|
|
20
|
+
db.pragma("foreign_keys = ON");
|
|
21
|
+
migrate(db);
|
|
22
|
+
return db;
|
|
23
|
+
}
|
|
24
|
+
function migrate(db2) {
|
|
25
|
+
db2.exec(`
|
|
26
|
+
CREATE TABLE IF NOT EXISTS nodes (
|
|
27
|
+
id TEXT PRIMARY KEY,
|
|
28
|
+
rev INTEGER NOT NULL DEFAULT 1,
|
|
29
|
+
parent TEXT REFERENCES nodes(id),
|
|
30
|
+
project TEXT NOT NULL,
|
|
31
|
+
summary TEXT NOT NULL,
|
|
32
|
+
resolved INTEGER NOT NULL DEFAULT 0,
|
|
33
|
+
depth INTEGER NOT NULL DEFAULT 0,
|
|
34
|
+
state TEXT,
|
|
35
|
+
properties TEXT NOT NULL DEFAULT '{}',
|
|
36
|
+
context_links TEXT NOT NULL DEFAULT '[]',
|
|
37
|
+
evidence TEXT NOT NULL DEFAULT '[]',
|
|
38
|
+
created_by TEXT NOT NULL,
|
|
39
|
+
created_at TEXT NOT NULL,
|
|
40
|
+
updated_at TEXT NOT NULL
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
CREATE TABLE IF NOT EXISTS edges (
|
|
44
|
+
id TEXT PRIMARY KEY,
|
|
45
|
+
from_node TEXT NOT NULL REFERENCES nodes(id),
|
|
46
|
+
to_node TEXT NOT NULL REFERENCES nodes(id),
|
|
47
|
+
type TEXT NOT NULL,
|
|
48
|
+
created_at TEXT NOT NULL,
|
|
49
|
+
UNIQUE(from_node, to_node, type)
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
53
|
+
id TEXT PRIMARY KEY,
|
|
54
|
+
node_id TEXT NOT NULL REFERENCES nodes(id),
|
|
55
|
+
agent TEXT NOT NULL,
|
|
56
|
+
action TEXT NOT NULL,
|
|
57
|
+
changes TEXT NOT NULL,
|
|
58
|
+
timestamp TEXT NOT NULL
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_project ON nodes(project);
|
|
62
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_parent ON nodes(parent);
|
|
63
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_resolved ON nodes(project, resolved);
|
|
64
|
+
CREATE INDEX IF NOT EXISTS idx_edges_from ON edges(from_node);
|
|
65
|
+
CREATE INDEX IF NOT EXISTS idx_edges_to ON edges(to_node);
|
|
66
|
+
CREATE INDEX IF NOT EXISTS idx_edges_type ON edges(from_node, type);
|
|
67
|
+
CREATE INDEX IF NOT EXISTS idx_events_node ON events(node_id);
|
|
68
|
+
`);
|
|
69
|
+
const hasDepth = db2.prepare(
|
|
70
|
+
"SELECT COUNT(*) as cnt FROM pragma_table_info('nodes') WHERE name = 'depth'"
|
|
71
|
+
).get();
|
|
72
|
+
if (hasDepth.cnt === 0) {
|
|
73
|
+
db2.exec("ALTER TABLE nodes ADD COLUMN depth INTEGER NOT NULL DEFAULT 0");
|
|
74
|
+
db2.exec(`
|
|
75
|
+
WITH RECURSIVE tree(id, depth) AS (
|
|
76
|
+
SELECT id, 0 FROM nodes WHERE parent IS NULL
|
|
77
|
+
UNION ALL
|
|
78
|
+
SELECT n.id, t.depth + 1
|
|
79
|
+
FROM nodes n JOIN tree t ON n.parent = t.id
|
|
80
|
+
)
|
|
81
|
+
UPDATE nodes SET depth = (SELECT depth FROM tree WHERE tree.id = nodes.id)
|
|
82
|
+
`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function closeDb() {
|
|
86
|
+
if (db) {
|
|
87
|
+
db.close();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/events.ts
|
|
92
|
+
import { nanoid } from "nanoid";
|
|
93
|
+
var INSERT_EVENT = `
|
|
94
|
+
INSERT INTO events (id, node_id, agent, action, changes, timestamp)
|
|
95
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
96
|
+
`;
|
|
97
|
+
function logEvent(nodeId, agent, action, changes) {
|
|
98
|
+
const db2 = getDb();
|
|
99
|
+
const event = {
|
|
100
|
+
id: nanoid(),
|
|
101
|
+
node_id: nodeId,
|
|
102
|
+
agent,
|
|
103
|
+
action,
|
|
104
|
+
changes,
|
|
105
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
106
|
+
};
|
|
107
|
+
db2.prepare(INSERT_EVENT).run(
|
|
108
|
+
event.id,
|
|
109
|
+
event.node_id,
|
|
110
|
+
event.agent,
|
|
111
|
+
event.action,
|
|
112
|
+
JSON.stringify(event.changes),
|
|
113
|
+
event.timestamp
|
|
114
|
+
);
|
|
115
|
+
return event;
|
|
116
|
+
}
|
|
117
|
+
function getEvents(nodeId, limit = 20, cursor) {
|
|
118
|
+
const db2 = getDb();
|
|
119
|
+
let query;
|
|
120
|
+
let params;
|
|
121
|
+
if (cursor) {
|
|
122
|
+
query = `
|
|
123
|
+
SELECT * FROM events
|
|
124
|
+
WHERE node_id = ? AND timestamp < ?
|
|
125
|
+
ORDER BY timestamp DESC
|
|
126
|
+
LIMIT ?
|
|
127
|
+
`;
|
|
128
|
+
params = [nodeId, cursor, limit + 1];
|
|
129
|
+
} else {
|
|
130
|
+
query = `
|
|
131
|
+
SELECT * FROM events
|
|
132
|
+
WHERE node_id = ?
|
|
133
|
+
ORDER BY timestamp DESC
|
|
134
|
+
LIMIT ?
|
|
135
|
+
`;
|
|
136
|
+
params = [nodeId, limit + 1];
|
|
137
|
+
}
|
|
138
|
+
const rows = db2.prepare(query).all(...params);
|
|
139
|
+
const hasMore = rows.length > limit;
|
|
140
|
+
const slice = hasMore ? rows.slice(0, limit) : rows;
|
|
141
|
+
const events = slice.map((row) => ({
|
|
142
|
+
id: row.id,
|
|
143
|
+
node_id: row.node_id,
|
|
144
|
+
agent: row.agent,
|
|
145
|
+
action: row.action,
|
|
146
|
+
changes: JSON.parse(row.changes),
|
|
147
|
+
timestamp: row.timestamp
|
|
148
|
+
}));
|
|
149
|
+
return {
|
|
150
|
+
events,
|
|
151
|
+
next_cursor: hasMore ? slice[slice.length - 1].timestamp : null
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/validate.ts
|
|
156
|
+
var ValidationError = class extends Error {
|
|
157
|
+
code = "validation_error";
|
|
158
|
+
constructor(message) {
|
|
159
|
+
super(message);
|
|
160
|
+
this.name = "ValidationError";
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
var EngineError = class extends Error {
|
|
164
|
+
code;
|
|
165
|
+
constructor(code, message) {
|
|
166
|
+
super(message);
|
|
167
|
+
this.name = "EngineError";
|
|
168
|
+
this.code = code;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
function requireString(value, field) {
|
|
172
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
173
|
+
throw new ValidationError(`${field} is required and must be a non-empty string`);
|
|
174
|
+
}
|
|
175
|
+
return value.trim();
|
|
176
|
+
}
|
|
177
|
+
function optionalString(value, field) {
|
|
178
|
+
if (value === void 0 || value === null) return void 0;
|
|
179
|
+
if (typeof value !== "string") {
|
|
180
|
+
throw new ValidationError(`${field} must be a string`);
|
|
181
|
+
}
|
|
182
|
+
return value;
|
|
183
|
+
}
|
|
184
|
+
function requireArray(value, field) {
|
|
185
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
186
|
+
throw new ValidationError(`${field} is required and must be a non-empty array`);
|
|
187
|
+
}
|
|
188
|
+
return value;
|
|
189
|
+
}
|
|
190
|
+
function optionalNumber(value, field, min, max) {
|
|
191
|
+
if (value === void 0 || value === null) return void 0;
|
|
192
|
+
if (typeof value !== "number" || isNaN(value)) {
|
|
193
|
+
throw new ValidationError(`${field} must be a number`);
|
|
194
|
+
}
|
|
195
|
+
if (min !== void 0 && value < min) {
|
|
196
|
+
throw new ValidationError(`${field} must be >= ${min}`);
|
|
197
|
+
}
|
|
198
|
+
if (max !== void 0 && value > max) {
|
|
199
|
+
throw new ValidationError(`${field} must be <= ${max}`);
|
|
200
|
+
}
|
|
201
|
+
return value;
|
|
202
|
+
}
|
|
203
|
+
function optionalBoolean(value, field) {
|
|
204
|
+
if (value === void 0 || value === null) return void 0;
|
|
205
|
+
if (typeof value !== "boolean") {
|
|
206
|
+
throw new ValidationError(`${field} must be a boolean`);
|
|
207
|
+
}
|
|
208
|
+
return value;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// src/nodes.ts
|
|
212
|
+
function rowToNode(row) {
|
|
213
|
+
return {
|
|
214
|
+
id: row.id,
|
|
215
|
+
rev: row.rev,
|
|
216
|
+
parent: row.parent,
|
|
217
|
+
project: row.project,
|
|
218
|
+
summary: row.summary,
|
|
219
|
+
resolved: row.resolved === 1,
|
|
220
|
+
depth: row.depth,
|
|
221
|
+
state: row.state ? JSON.parse(row.state) : null,
|
|
222
|
+
properties: JSON.parse(row.properties),
|
|
223
|
+
context_links: JSON.parse(row.context_links),
|
|
224
|
+
evidence: JSON.parse(row.evidence),
|
|
225
|
+
created_by: row.created_by,
|
|
226
|
+
created_at: row.created_at,
|
|
227
|
+
updated_at: row.updated_at
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function createNode(input) {
|
|
231
|
+
const db2 = getDb();
|
|
232
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
233
|
+
const id = nanoid2();
|
|
234
|
+
let depth = 0;
|
|
235
|
+
if (input.parent) {
|
|
236
|
+
const parentRow = db2.prepare("SELECT depth FROM nodes WHERE id = ?").get(input.parent);
|
|
237
|
+
if (parentRow) depth = parentRow.depth + 1;
|
|
238
|
+
}
|
|
239
|
+
const node = {
|
|
240
|
+
id,
|
|
241
|
+
rev: 1,
|
|
242
|
+
parent: input.parent ?? null,
|
|
243
|
+
project: input.project,
|
|
244
|
+
summary: input.summary,
|
|
245
|
+
resolved: false,
|
|
246
|
+
depth,
|
|
247
|
+
state: input.state ?? null,
|
|
248
|
+
properties: input.properties ?? {},
|
|
249
|
+
context_links: input.context_links ?? [],
|
|
250
|
+
evidence: [],
|
|
251
|
+
created_by: input.agent,
|
|
252
|
+
created_at: now,
|
|
253
|
+
updated_at: now
|
|
254
|
+
};
|
|
255
|
+
db2.prepare(`
|
|
256
|
+
INSERT INTO nodes (id, rev, parent, project, summary, resolved, depth, state, properties, context_links, evidence, created_by, created_at, updated_at)
|
|
257
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
258
|
+
`).run(
|
|
259
|
+
node.id,
|
|
260
|
+
node.rev,
|
|
261
|
+
node.parent,
|
|
262
|
+
node.project,
|
|
263
|
+
node.summary,
|
|
264
|
+
0,
|
|
265
|
+
node.depth,
|
|
266
|
+
node.state !== null ? JSON.stringify(node.state) : null,
|
|
267
|
+
JSON.stringify(node.properties),
|
|
268
|
+
JSON.stringify(node.context_links),
|
|
269
|
+
JSON.stringify(node.evidence),
|
|
270
|
+
node.created_by,
|
|
271
|
+
node.created_at,
|
|
272
|
+
node.updated_at
|
|
273
|
+
);
|
|
274
|
+
logEvent(node.id, input.agent, "created", [
|
|
275
|
+
{ field: "summary", before: null, after: node.summary }
|
|
276
|
+
]);
|
|
277
|
+
return node;
|
|
278
|
+
}
|
|
279
|
+
function getNode(id) {
|
|
280
|
+
const db2 = getDb();
|
|
281
|
+
const row = db2.prepare("SELECT * FROM nodes WHERE id = ?").get(id);
|
|
282
|
+
return row ? rowToNode(row) : null;
|
|
283
|
+
}
|
|
284
|
+
function getNodeOrThrow(id) {
|
|
285
|
+
const node = getNode(id);
|
|
286
|
+
if (!node) {
|
|
287
|
+
throw new EngineError("node_not_found", `Node not found: ${id}. Verify the ID is correct and the node hasn't been deleted.`);
|
|
288
|
+
}
|
|
289
|
+
return node;
|
|
290
|
+
}
|
|
291
|
+
function getChildren(parentId) {
|
|
292
|
+
const db2 = getDb();
|
|
293
|
+
const rows = db2.prepare("SELECT * FROM nodes WHERE parent = ?").all(parentId);
|
|
294
|
+
return rows.map(rowToNode);
|
|
295
|
+
}
|
|
296
|
+
function getAncestors(nodeId) {
|
|
297
|
+
const ancestors = [];
|
|
298
|
+
let current = getNode(nodeId);
|
|
299
|
+
while (current?.parent) {
|
|
300
|
+
const parent = getNode(current.parent);
|
|
301
|
+
if (!parent) break;
|
|
302
|
+
ancestors.unshift({ id: parent.id, summary: parent.summary, resolved: parent.resolved });
|
|
303
|
+
current = parent;
|
|
304
|
+
}
|
|
305
|
+
return ancestors;
|
|
306
|
+
}
|
|
307
|
+
function getProjectRoot(project) {
|
|
308
|
+
const db2 = getDb();
|
|
309
|
+
const row = db2.prepare("SELECT * FROM nodes WHERE project = ? AND parent IS NULL").get(project);
|
|
310
|
+
return row ? rowToNode(row) : null;
|
|
311
|
+
}
|
|
312
|
+
function listProjects() {
|
|
313
|
+
const db2 = getDb();
|
|
314
|
+
const roots = db2.prepare("SELECT * FROM nodes WHERE parent IS NULL").all();
|
|
315
|
+
return roots.map((root) => {
|
|
316
|
+
const counts = db2.prepare(
|
|
317
|
+
`SELECT
|
|
318
|
+
COUNT(*) as total,
|
|
319
|
+
SUM(CASE WHEN resolved = 1 THEN 1 ELSE 0 END) as resolved
|
|
320
|
+
FROM nodes WHERE project = ?`
|
|
321
|
+
).get(root.project);
|
|
322
|
+
return {
|
|
323
|
+
project: root.project,
|
|
324
|
+
id: root.id,
|
|
325
|
+
summary: root.summary,
|
|
326
|
+
total: counts.total,
|
|
327
|
+
resolved: counts.resolved,
|
|
328
|
+
unresolved: counts.total - counts.resolved,
|
|
329
|
+
updated_at: root.updated_at
|
|
330
|
+
};
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
function updateNode(input) {
|
|
334
|
+
const db2 = getDb();
|
|
335
|
+
const node = getNodeOrThrow(input.node_id);
|
|
336
|
+
const changes = [];
|
|
337
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
338
|
+
let newResolved = node.resolved;
|
|
339
|
+
let newState = node.state;
|
|
340
|
+
let newSummary = node.summary;
|
|
341
|
+
let newProperties = { ...node.properties };
|
|
342
|
+
let newContextLinks = [...node.context_links];
|
|
343
|
+
let newEvidence = [...node.evidence];
|
|
344
|
+
if (input.resolved === true && !node.resolved) {
|
|
345
|
+
const hasExistingEvidence = node.evidence.length > 0;
|
|
346
|
+
const hasNewEvidence = input.add_evidence && input.add_evidence.length > 0;
|
|
347
|
+
if (!hasExistingEvidence && !hasNewEvidence) {
|
|
348
|
+
throw new EngineError(
|
|
349
|
+
"evidence_required",
|
|
350
|
+
`Cannot resolve node ${input.node_id} without evidence. Add at least one add_evidence entry (type: 'git', 'note', 'test', etc.) explaining what was done.`
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (input.resolved !== void 0 && input.resolved !== node.resolved) {
|
|
355
|
+
changes.push({ field: "resolved", before: node.resolved, after: input.resolved });
|
|
356
|
+
newResolved = input.resolved;
|
|
357
|
+
}
|
|
358
|
+
if (input.state !== void 0) {
|
|
359
|
+
changes.push({ field: "state", before: node.state, after: input.state });
|
|
360
|
+
newState = input.state;
|
|
361
|
+
}
|
|
362
|
+
if (input.summary !== void 0 && input.summary !== node.summary) {
|
|
363
|
+
changes.push({ field: "summary", before: node.summary, after: input.summary });
|
|
364
|
+
newSummary = input.summary;
|
|
365
|
+
}
|
|
366
|
+
if (input.properties) {
|
|
367
|
+
for (const [key, value] of Object.entries(input.properties)) {
|
|
368
|
+
if (value === null) {
|
|
369
|
+
if (key in newProperties) {
|
|
370
|
+
changes.push({ field: `properties.${key}`, before: newProperties[key], after: null });
|
|
371
|
+
delete newProperties[key];
|
|
372
|
+
}
|
|
373
|
+
} else {
|
|
374
|
+
changes.push({ field: `properties.${key}`, before: newProperties[key] ?? null, after: value });
|
|
375
|
+
newProperties[key] = value;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (input.add_context_links) {
|
|
380
|
+
for (const link of input.add_context_links) {
|
|
381
|
+
if (!newContextLinks.includes(link)) {
|
|
382
|
+
newContextLinks.push(link);
|
|
383
|
+
changes.push({ field: "context_links", before: null, after: link });
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (input.remove_context_links) {
|
|
388
|
+
for (const link of input.remove_context_links) {
|
|
389
|
+
const idx = newContextLinks.indexOf(link);
|
|
390
|
+
if (idx !== -1) {
|
|
391
|
+
newContextLinks.splice(idx, 1);
|
|
392
|
+
changes.push({ field: "context_links", before: link, after: null });
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
if (input.add_evidence) {
|
|
397
|
+
for (const ev of input.add_evidence) {
|
|
398
|
+
const evidence = {
|
|
399
|
+
type: ev.type,
|
|
400
|
+
ref: ev.ref,
|
|
401
|
+
agent: input.agent,
|
|
402
|
+
timestamp: now
|
|
403
|
+
};
|
|
404
|
+
newEvidence.push(evidence);
|
|
405
|
+
changes.push({ field: "evidence", before: null, after: evidence });
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
if (changes.length === 0) {
|
|
409
|
+
return node;
|
|
410
|
+
}
|
|
411
|
+
const newRev = node.rev + 1;
|
|
412
|
+
db2.prepare(`
|
|
413
|
+
UPDATE nodes SET
|
|
414
|
+
rev = ?,
|
|
415
|
+
resolved = ?,
|
|
416
|
+
state = ?,
|
|
417
|
+
summary = ?,
|
|
418
|
+
properties = ?,
|
|
419
|
+
context_links = ?,
|
|
420
|
+
evidence = ?,
|
|
421
|
+
updated_at = ?
|
|
422
|
+
WHERE id = ?
|
|
423
|
+
`).run(
|
|
424
|
+
newRev,
|
|
425
|
+
newResolved ? 1 : 0,
|
|
426
|
+
newState !== null ? JSON.stringify(newState) : null,
|
|
427
|
+
newSummary,
|
|
428
|
+
JSON.stringify(newProperties),
|
|
429
|
+
JSON.stringify(newContextLinks),
|
|
430
|
+
JSON.stringify(newEvidence),
|
|
431
|
+
now,
|
|
432
|
+
input.node_id
|
|
433
|
+
);
|
|
434
|
+
const action = input.resolved === true ? "resolved" : "updated";
|
|
435
|
+
logEvent(input.node_id, input.agent, action, changes);
|
|
436
|
+
return getNodeOrThrow(input.node_id);
|
|
437
|
+
}
|
|
438
|
+
function getProjectSummary(project) {
|
|
439
|
+
const db2 = getDb();
|
|
440
|
+
const counts = db2.prepare(
|
|
441
|
+
`SELECT
|
|
442
|
+
COUNT(*) as total,
|
|
443
|
+
SUM(CASE WHEN resolved = 1 THEN 1 ELSE 0 END) as resolved
|
|
444
|
+
FROM nodes WHERE project = ?`
|
|
445
|
+
).get(project);
|
|
446
|
+
const blocked = db2.prepare(
|
|
447
|
+
`SELECT COUNT(DISTINCT n.id) as count
|
|
448
|
+
FROM nodes n
|
|
449
|
+
JOIN edges e ON e.from_node = n.id AND e.type = 'depends_on'
|
|
450
|
+
JOIN nodes dep ON dep.id = e.to_node AND dep.resolved = 0
|
|
451
|
+
WHERE n.project = ? AND n.resolved = 0`
|
|
452
|
+
).get(project);
|
|
453
|
+
const actionable = db2.prepare(
|
|
454
|
+
`SELECT COUNT(*) as count FROM nodes n
|
|
455
|
+
WHERE n.project = ? AND n.resolved = 0
|
|
456
|
+
AND NOT EXISTS (
|
|
457
|
+
SELECT 1 FROM nodes child WHERE child.parent = n.id AND child.resolved = 0
|
|
458
|
+
)
|
|
459
|
+
AND NOT EXISTS (
|
|
460
|
+
SELECT 1 FROM edges e
|
|
461
|
+
JOIN nodes dep ON dep.id = e.to_node AND dep.resolved = 0
|
|
462
|
+
WHERE e.from_node = n.id AND e.type = 'depends_on'
|
|
463
|
+
)`
|
|
464
|
+
).get(project);
|
|
465
|
+
return {
|
|
466
|
+
total: counts.total,
|
|
467
|
+
resolved: counts.resolved,
|
|
468
|
+
unresolved: counts.total - counts.resolved,
|
|
469
|
+
blocked: blocked.count,
|
|
470
|
+
actionable: actionable.count
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
export {
|
|
475
|
+
getDb,
|
|
476
|
+
initDb,
|
|
477
|
+
closeDb,
|
|
478
|
+
ValidationError,
|
|
479
|
+
EngineError,
|
|
480
|
+
requireString,
|
|
481
|
+
optionalString,
|
|
482
|
+
requireArray,
|
|
483
|
+
optionalNumber,
|
|
484
|
+
optionalBoolean,
|
|
485
|
+
logEvent,
|
|
486
|
+
getEvents,
|
|
487
|
+
createNode,
|
|
488
|
+
getNode,
|
|
489
|
+
getNodeOrThrow,
|
|
490
|
+
getChildren,
|
|
491
|
+
getAncestors,
|
|
492
|
+
getProjectRoot,
|
|
493
|
+
listProjects,
|
|
494
|
+
updateNode,
|
|
495
|
+
getProjectSummary
|
|
496
|
+
};
|
|
497
|
+
//# sourceMappingURL=chunk-RVM33A4A.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/nodes.ts","../src/db.ts","../src/events.ts","../src/validate.ts"],"sourcesContent":["import { nanoid } from \"nanoid\";\nimport { getDb } from \"./db.js\";\nimport { logEvent } from \"./events.js\";\nimport { EngineError } from \"./validate.js\";\nimport type { Node, NodeRow, Evidence, FieldChange } from \"./types.js\";\n\n// --- Row <-> Node conversion ---\n\nfunction rowToNode(row: NodeRow): Node {\n return {\n id: row.id,\n rev: row.rev,\n parent: row.parent,\n project: row.project,\n summary: row.summary,\n resolved: row.resolved === 1,\n depth: row.depth,\n state: row.state ? JSON.parse(row.state) : null,\n properties: JSON.parse(row.properties),\n context_links: JSON.parse(row.context_links),\n evidence: JSON.parse(row.evidence),\n created_by: row.created_by,\n created_at: row.created_at,\n updated_at: row.updated_at,\n };\n}\n\n// --- Create ---\n\nexport interface CreateNodeInput {\n parent?: string;\n project: string;\n summary: string;\n state?: unknown;\n properties?: Record<string, unknown>;\n context_links?: string[];\n agent: string;\n}\n\nexport function createNode(input: CreateNodeInput): Node {\n const db = getDb();\n const now = new Date().toISOString();\n const id = nanoid();\n\n // [sl:yBBVr4wcgVfWA_w8U8hQo] Compute depth from parent\n let depth = 0;\n if (input.parent) {\n const parentRow = db.prepare(\"SELECT depth FROM nodes WHERE id = ?\").get(input.parent) as { depth: number } | undefined;\n if (parentRow) depth = parentRow.depth + 1;\n }\n\n const node: Node = {\n id,\n rev: 1,\n parent: input.parent ?? null,\n project: input.project,\n summary: input.summary,\n resolved: false,\n depth,\n state: input.state ?? null,\n properties: input.properties ?? {},\n context_links: input.context_links ?? [],\n evidence: [],\n created_by: input.agent,\n created_at: now,\n updated_at: now,\n };\n\n db.prepare(`\n INSERT INTO nodes (id, rev, parent, project, summary, resolved, depth, state, properties, context_links, evidence, created_by, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n node.id,\n node.rev,\n node.parent,\n node.project,\n node.summary,\n 0,\n node.depth,\n node.state !== null ? JSON.stringify(node.state) : null,\n JSON.stringify(node.properties),\n JSON.stringify(node.context_links),\n JSON.stringify(node.evidence),\n node.created_by,\n node.created_at,\n node.updated_at\n );\n\n logEvent(node.id, input.agent, \"created\", [\n { field: \"summary\", before: null, after: node.summary },\n ]);\n\n return node;\n}\n\n// --- Read ---\n\nexport function getNode(id: string): Node | null {\n const db = getDb();\n const row = db.prepare(\"SELECT * FROM nodes WHERE id = ?\").get(id) as\n | NodeRow\n | undefined;\n return row ? rowToNode(row) : null;\n}\n\nexport function getNodeOrThrow(id: string): Node {\n const node = getNode(id);\n if (!node) {\n throw new EngineError(\"node_not_found\", `Node not found: ${id}. Verify the ID is correct and the node hasn't been deleted.`);\n }\n return node;\n}\n\nexport function getChildren(parentId: string): Node[] {\n const db = getDb();\n const rows = db\n .prepare(\"SELECT * FROM nodes WHERE parent = ?\")\n .all(parentId) as NodeRow[];\n return rows.map(rowToNode);\n}\n\nexport function getAncestors(nodeId: string): Array<{ id: string; summary: string; resolved: boolean }> {\n const ancestors: Array<{ id: string; summary: string; resolved: boolean }> = [];\n let current = getNode(nodeId);\n\n while (current?.parent) {\n const parent = getNode(current.parent);\n if (!parent) break;\n ancestors.unshift({ id: parent.id, summary: parent.summary, resolved: parent.resolved });\n current = parent;\n }\n\n return ancestors;\n}\n\nexport function getProjectRoot(project: string): Node | null {\n const db = getDb();\n const row = db\n .prepare(\"SELECT * FROM nodes WHERE project = ? AND parent IS NULL\")\n .get(project) as NodeRow | undefined;\n return row ? rowToNode(row) : null;\n}\n\nexport function listProjects(): Array<{\n project: string;\n id: string;\n summary: string;\n total: number;\n resolved: number;\n unresolved: number;\n updated_at: string;\n}> {\n const db = getDb();\n\n const roots = db\n .prepare(\"SELECT * FROM nodes WHERE parent IS NULL\")\n .all() as NodeRow[];\n\n return roots.map((root) => {\n const counts = db\n .prepare(\n `SELECT\n COUNT(*) as total,\n SUM(CASE WHEN resolved = 1 THEN 1 ELSE 0 END) as resolved\n FROM nodes WHERE project = ?`\n )\n .get(root.project) as { total: number; resolved: number };\n\n return {\n project: root.project,\n id: root.id,\n summary: root.summary,\n total: counts.total,\n resolved: counts.resolved,\n unresolved: counts.total - counts.resolved,\n updated_at: root.updated_at,\n };\n });\n}\n\n// --- Update ---\n\nexport interface UpdateNodeInput {\n node_id: string;\n agent: string;\n resolved?: boolean;\n state?: unknown;\n summary?: string;\n properties?: Record<string, unknown>;\n add_context_links?: string[];\n remove_context_links?: string[];\n add_evidence?: Array<{ type: string; ref: string }>;\n}\n\nexport function updateNode(input: UpdateNodeInput): Node {\n const db = getDb();\n const node = getNodeOrThrow(input.node_id);\n const changes: FieldChange[] = [];\n const now = new Date().toISOString();\n\n let newResolved = node.resolved;\n let newState = node.state;\n let newSummary = node.summary;\n let newProperties = { ...node.properties };\n let newContextLinks = [...node.context_links];\n let newEvidence = [...node.evidence];\n\n // [sl:OZ0or-q5TserCEfWUeMVv] Require evidence when resolving\n if (input.resolved === true && !node.resolved) {\n const hasExistingEvidence = node.evidence.length > 0;\n const hasNewEvidence = input.add_evidence && input.add_evidence.length > 0;\n if (!hasExistingEvidence && !hasNewEvidence) {\n throw new EngineError(\n \"evidence_required\",\n `Cannot resolve node ${input.node_id} without evidence. Add at least one add_evidence entry (type: 'git', 'note', 'test', etc.) explaining what was done.`\n );\n }\n }\n\n if (input.resolved !== undefined && input.resolved !== node.resolved) {\n changes.push({ field: \"resolved\", before: node.resolved, after: input.resolved });\n newResolved = input.resolved;\n }\n\n if (input.state !== undefined) {\n changes.push({ field: \"state\", before: node.state, after: input.state });\n newState = input.state;\n }\n\n if (input.summary !== undefined && input.summary !== node.summary) {\n changes.push({ field: \"summary\", before: node.summary, after: input.summary });\n newSummary = input.summary;\n }\n\n if (input.properties) {\n for (const [key, value] of Object.entries(input.properties)) {\n if (value === null) {\n if (key in newProperties) {\n changes.push({ field: `properties.${key}`, before: newProperties[key], after: null });\n delete newProperties[key];\n }\n } else {\n changes.push({ field: `properties.${key}`, before: newProperties[key] ?? null, after: value });\n newProperties[key] = value;\n }\n }\n }\n\n if (input.add_context_links) {\n for (const link of input.add_context_links) {\n if (!newContextLinks.includes(link)) {\n newContextLinks.push(link);\n changes.push({ field: \"context_links\", before: null, after: link });\n }\n }\n }\n\n if (input.remove_context_links) {\n for (const link of input.remove_context_links) {\n const idx = newContextLinks.indexOf(link);\n if (idx !== -1) {\n newContextLinks.splice(idx, 1);\n changes.push({ field: \"context_links\", before: link, after: null });\n }\n }\n }\n\n if (input.add_evidence) {\n for (const ev of input.add_evidence) {\n const evidence: Evidence = {\n type: ev.type,\n ref: ev.ref,\n agent: input.agent,\n timestamp: now,\n };\n newEvidence.push(evidence);\n changes.push({ field: \"evidence\", before: null, after: evidence });\n }\n }\n\n if (changes.length === 0) {\n return node;\n }\n\n const newRev = node.rev + 1;\n\n db.prepare(`\n UPDATE nodes SET\n rev = ?,\n resolved = ?,\n state = ?,\n summary = ?,\n properties = ?,\n context_links = ?,\n evidence = ?,\n updated_at = ?\n WHERE id = ?\n `).run(\n newRev,\n newResolved ? 1 : 0,\n newState !== null ? JSON.stringify(newState) : null,\n newSummary,\n JSON.stringify(newProperties),\n JSON.stringify(newContextLinks),\n JSON.stringify(newEvidence),\n now,\n input.node_id\n );\n\n const action = input.resolved === true ? \"resolved\" : \"updated\";\n logEvent(input.node_id, input.agent, action, changes);\n\n return getNodeOrThrow(input.node_id);\n}\n\n// --- Query helpers ---\n\nexport function getProjectSummary(project: string): {\n total: number;\n resolved: number;\n unresolved: number;\n blocked: number;\n actionable: number;\n} {\n const db = getDb();\n\n const counts = db\n .prepare(\n `SELECT\n COUNT(*) as total,\n SUM(CASE WHEN resolved = 1 THEN 1 ELSE 0 END) as resolved\n FROM nodes WHERE project = ?`\n )\n .get(project) as { total: number; resolved: number };\n\n // Blocked: unresolved nodes that have at least one unresolved dependency\n const blocked = db\n .prepare(\n `SELECT COUNT(DISTINCT n.id) as count\n FROM nodes n\n JOIN edges e ON e.from_node = n.id AND e.type = 'depends_on'\n JOIN nodes dep ON dep.id = e.to_node AND dep.resolved = 0\n WHERE n.project = ? AND n.resolved = 0`\n )\n .get(project) as { count: number };\n\n // Actionable: unresolved leaves (no unresolved children) with all deps resolved\n const actionable = db\n .prepare(\n `SELECT COUNT(*) as count FROM nodes n\n WHERE n.project = ? AND n.resolved = 0\n AND NOT EXISTS (\n SELECT 1 FROM nodes child WHERE child.parent = n.id AND child.resolved = 0\n )\n AND NOT EXISTS (\n SELECT 1 FROM edges e\n JOIN nodes dep ON dep.id = e.to_node AND dep.resolved = 0\n WHERE e.from_node = n.id AND e.type = 'depends_on'\n )`\n )\n .get(project) as { count: number };\n\n return {\n total: counts.total,\n resolved: counts.resolved,\n unresolved: counts.total - counts.resolved,\n blocked: blocked.count,\n actionable: actionable.count,\n };\n}\n","import Database from \"better-sqlite3\";\nimport path from \"path\";\n\nlet db: Database.Database;\n\nexport function getDb(): Database.Database {\n if (!db) {\n throw new Error(\"Database not initialized. Call initDb() first.\");\n }\n return db;\n}\n\nexport function initDb(dbPath?: string): Database.Database {\n const resolvedPath = dbPath ?? path.resolve(\"graph.db\");\n db = new Database(resolvedPath);\n\n db.pragma(\"journal_mode = WAL\");\n db.pragma(\"foreign_keys = ON\");\n\n migrate(db);\n return db;\n}\n\nfunction migrate(db: Database.Database): void {\n db.exec(`\n CREATE TABLE IF NOT EXISTS nodes (\n id TEXT PRIMARY KEY,\n rev INTEGER NOT NULL DEFAULT 1,\n parent TEXT REFERENCES nodes(id),\n project TEXT NOT NULL,\n summary TEXT NOT NULL,\n resolved INTEGER NOT NULL DEFAULT 0,\n depth INTEGER NOT NULL DEFAULT 0,\n state TEXT,\n properties TEXT NOT NULL DEFAULT '{}',\n context_links TEXT NOT NULL DEFAULT '[]',\n evidence TEXT NOT NULL DEFAULT '[]',\n created_by TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS edges (\n id TEXT PRIMARY KEY,\n from_node TEXT NOT NULL REFERENCES nodes(id),\n to_node TEXT NOT NULL REFERENCES nodes(id),\n type TEXT NOT NULL,\n created_at TEXT NOT NULL,\n UNIQUE(from_node, to_node, type)\n );\n\n CREATE TABLE IF NOT EXISTS events (\n id TEXT PRIMARY KEY,\n node_id TEXT NOT NULL REFERENCES nodes(id),\n agent TEXT NOT NULL,\n action TEXT NOT NULL,\n changes TEXT NOT NULL,\n timestamp TEXT NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS idx_nodes_project ON nodes(project);\n CREATE INDEX IF NOT EXISTS idx_nodes_parent ON nodes(parent);\n CREATE INDEX IF NOT EXISTS idx_nodes_resolved ON nodes(project, resolved);\n CREATE INDEX IF NOT EXISTS idx_edges_from ON edges(from_node);\n CREATE INDEX IF NOT EXISTS idx_edges_to ON edges(to_node);\n CREATE INDEX IF NOT EXISTS idx_edges_type ON edges(from_node, type);\n CREATE INDEX IF NOT EXISTS idx_events_node ON events(node_id);\n `);\n\n // [sl:yBBVr4wcgVfWA_w8U8hQo] Migration: add depth column if it doesn't exist\n const hasDepth = db.prepare(\n \"SELECT COUNT(*) as cnt FROM pragma_table_info('nodes') WHERE name = 'depth'\"\n ).get() as { cnt: number };\n\n if (hasDepth.cnt === 0) {\n db.exec(\"ALTER TABLE nodes ADD COLUMN depth INTEGER NOT NULL DEFAULT 0\");\n // Backfill depths using recursive CTE\n db.exec(`\n WITH RECURSIVE tree(id, depth) AS (\n SELECT id, 0 FROM nodes WHERE parent IS NULL\n UNION ALL\n SELECT n.id, t.depth + 1\n FROM nodes n JOIN tree t ON n.parent = t.id\n )\n UPDATE nodes SET depth = (SELECT depth FROM tree WHERE tree.id = nodes.id)\n `);\n }\n}\n\nexport function closeDb(): void {\n if (db) {\n db.close();\n }\n}\n","import { nanoid } from \"nanoid\";\nimport { getDb } from \"./db.js\";\nimport type { FieldChange, Event } from \"./types.js\";\n\nconst INSERT_EVENT = `\n INSERT INTO events (id, node_id, agent, action, changes, timestamp)\n VALUES (?, ?, ?, ?, ?, ?)\n`;\n\nexport function logEvent(\n nodeId: string,\n agent: string,\n action: string,\n changes: FieldChange[]\n): Event {\n const db = getDb();\n const event: Event = {\n id: nanoid(),\n node_id: nodeId,\n agent,\n action,\n changes,\n timestamp: new Date().toISOString(),\n };\n\n db.prepare(INSERT_EVENT).run(\n event.id,\n event.node_id,\n event.agent,\n event.action,\n JSON.stringify(event.changes),\n event.timestamp\n );\n\n return event;\n}\n\nexport function getEvents(\n nodeId: string,\n limit: number = 20,\n cursor?: string\n): { events: Event[]; next_cursor: string | null } {\n const db = getDb();\n\n let query: string;\n let params: unknown[];\n\n if (cursor) {\n query = `\n SELECT * FROM events\n WHERE node_id = ? AND timestamp < ?\n ORDER BY timestamp DESC\n LIMIT ?\n `;\n params = [nodeId, cursor, limit + 1];\n } else {\n query = `\n SELECT * FROM events\n WHERE node_id = ?\n ORDER BY timestamp DESC\n LIMIT ?\n `;\n params = [nodeId, limit + 1];\n }\n\n const rows = db.prepare(query).all(...params) as Array<{\n id: string;\n node_id: string;\n agent: string;\n action: string;\n changes: string;\n timestamp: string;\n }>;\n\n const hasMore = rows.length > limit;\n const slice = hasMore ? rows.slice(0, limit) : rows;\n\n const events: Event[] = slice.map((row) => ({\n id: row.id,\n node_id: row.node_id,\n agent: row.agent,\n action: row.action,\n changes: JSON.parse(row.changes),\n timestamp: row.timestamp,\n }));\n\n return {\n events,\n next_cursor: hasMore ? slice[slice.length - 1].timestamp : null,\n };\n}\n","export class ValidationError extends Error {\n code = \"validation_error\";\n constructor(message: string) {\n super(message);\n this.name = \"ValidationError\";\n }\n}\n\nexport class EngineError extends Error {\n code: string;\n constructor(code: string, message: string) {\n super(message);\n this.name = \"EngineError\";\n this.code = code;\n }\n}\n\nexport function requireString(value: unknown, field: string): string {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new ValidationError(`${field} is required and must be a non-empty string`);\n }\n return value.trim();\n}\n\nexport function optionalString(value: unknown, field: string): string | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value !== \"string\") {\n throw new ValidationError(`${field} must be a string`);\n }\n return value;\n}\n\nexport function requireArray<T>(value: unknown, field: string): T[] {\n if (!Array.isArray(value) || value.length === 0) {\n throw new ValidationError(`${field} is required and must be a non-empty array`);\n }\n return value as T[];\n}\n\nexport function optionalArray<T>(value: unknown, field: string): T[] | undefined {\n if (value === undefined || value === null) return undefined;\n if (!Array.isArray(value)) {\n throw new ValidationError(`${field} must be an array`);\n }\n return value as T[];\n}\n\nexport function optionalNumber(value: unknown, field: string, min?: number, max?: number): number | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value !== \"number\" || isNaN(value)) {\n throw new ValidationError(`${field} must be a number`);\n }\n if (min !== undefined && value < min) {\n throw new ValidationError(`${field} must be >= ${min}`);\n }\n if (max !== undefined && value > max) {\n throw new ValidationError(`${field} must be <= ${max}`);\n }\n return value;\n}\n\nexport function optionalBoolean(value: unknown, field: string): boolean | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value !== \"boolean\") {\n throw new ValidationError(`${field} must be a boolean`);\n }\n return value;\n}\n\nexport function requireObject(value: unknown, field: string): Record<string, unknown> {\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) {\n throw new ValidationError(`${field} is required and must be an object`);\n }\n return value as Record<string, unknown>;\n}\n"],"mappings":"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","names":["nanoid","db","db","db","nanoid"]}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/license.ts
|
|
4
|
+
import { verify } from "crypto";
|
|
5
|
+
import { readFileSync, existsSync } from "fs";
|
|
6
|
+
import { join, dirname } from "path";
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
var PUBLIC_KEY = process.env.GRAPH_LICENSE_PUBLIC_KEY ?? "";
|
|
9
|
+
var KEY_PREFIX = "graph_";
|
|
10
|
+
function verifyLicenseKey(key) {
|
|
11
|
+
if (!key || !PUBLIC_KEY) return null;
|
|
12
|
+
try {
|
|
13
|
+
const raw = key.startsWith(KEY_PREFIX) ? key.slice(KEY_PREFIX.length) : key;
|
|
14
|
+
const parts = raw.split(".");
|
|
15
|
+
if (parts.length !== 3) return null;
|
|
16
|
+
const [headerB64, payloadB64, signatureB64] = parts;
|
|
17
|
+
const signedData = Buffer.from(`${headerB64}.${payloadB64}`);
|
|
18
|
+
const signature = Buffer.from(signatureB64, "base64url");
|
|
19
|
+
const publicKeyBuffer = Buffer.from(PUBLIC_KEY, "base64");
|
|
20
|
+
const isValid = verify(
|
|
21
|
+
null,
|
|
22
|
+
// Ed25519 doesn't use a digest algorithm
|
|
23
|
+
signedData,
|
|
24
|
+
{ key: publicKeyBuffer, format: "der", type: "spki" },
|
|
25
|
+
signature
|
|
26
|
+
);
|
|
27
|
+
if (!isValid) return null;
|
|
28
|
+
const payload = JSON.parse(
|
|
29
|
+
Buffer.from(payloadB64, "base64url").toString("utf8")
|
|
30
|
+
);
|
|
31
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
32
|
+
if (payload.exp && payload.exp < now) return null;
|
|
33
|
+
const tier = payload.tier === "pro" ? "pro" : "free";
|
|
34
|
+
return {
|
|
35
|
+
tier,
|
|
36
|
+
exp: payload.exp,
|
|
37
|
+
sub: payload.sub
|
|
38
|
+
};
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function readLicenseKey(dbPath) {
|
|
44
|
+
const envKey = process.env.GRAPH_LICENSE;
|
|
45
|
+
if (envKey) return envKey.trim();
|
|
46
|
+
try {
|
|
47
|
+
const globalPath = join(homedir(), ".graph", "license");
|
|
48
|
+
if (existsSync(globalPath)) {
|
|
49
|
+
return readFileSync(globalPath, "utf8").trim();
|
|
50
|
+
}
|
|
51
|
+
if (dbPath) {
|
|
52
|
+
const localPath = join(dirname(dbPath), "license");
|
|
53
|
+
if (existsSync(localPath)) {
|
|
54
|
+
return readFileSync(localPath, "utf8").trim();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
return void 0;
|
|
60
|
+
}
|
|
61
|
+
function getLicenseTier(dbPath) {
|
|
62
|
+
const key = readLicenseKey(dbPath);
|
|
63
|
+
const info = verifyLicenseKey(key);
|
|
64
|
+
return info?.tier ?? "free";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export {
|
|
68
|
+
verifyLicenseKey,
|
|
69
|
+
getLicenseTier
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=chunk-WKOEKYTF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/license.ts"],"sourcesContent":["import { verify } from \"crypto\";\nimport { readFileSync, existsSync } from \"fs\";\nimport { join, dirname } from \"path\";\nimport { homedir } from \"os\";\n\n// [sl:VQtVq1bsZeGG4v5ngDh5z] Offline license key validation\n\nexport type Tier = \"free\" | \"pro\";\n\nexport interface LicenseInfo {\n tier: Tier;\n exp: number;\n sub?: string;\n}\n\n// Ed25519 public key (base64-encoded DER/SPKI format).\n// Set via env var. Replace with your actual public key after generating a keypair.\nconst PUBLIC_KEY = process.env.GRAPH_LICENSE_PUBLIC_KEY ?? \"\";\n\nconst KEY_PREFIX = \"graph_\";\n\n/**\n * Decode and verify a license key.\n * Returns the license info if valid, null if invalid/expired/missing.\n */\nexport function verifyLicenseKey(key: string | undefined): LicenseInfo | null {\n if (!key || !PUBLIC_KEY) return null;\n\n try {\n // Strip prefix\n const raw = key.startsWith(KEY_PREFIX) ? key.slice(KEY_PREFIX.length) : key;\n\n // JWT: header.payload.signature (all base64url)\n const parts = raw.split(\".\");\n if (parts.length !== 3) return null;\n\n const [headerB64, payloadB64, signatureB64] = parts;\n\n // Verify Ed25519 signature\n const signedData = Buffer.from(`${headerB64}.${payloadB64}`);\n const signature = Buffer.from(signatureB64, \"base64url\");\n const publicKeyBuffer = Buffer.from(PUBLIC_KEY, \"base64\");\n\n const isValid = verify(\n null, // Ed25519 doesn't use a digest algorithm\n signedData,\n { key: publicKeyBuffer, format: \"der\", type: \"spki\" },\n signature\n );\n\n if (!isValid) return null;\n\n // Decode payload\n const payload = JSON.parse(\n Buffer.from(payloadB64, \"base64url\").toString(\"utf8\")\n );\n\n // Check expiry\n const now = Math.floor(Date.now() / 1000);\n if (payload.exp && payload.exp < now) return null;\n\n // Extract tier\n const tier: Tier = payload.tier === \"pro\" ? \"pro\" : \"free\";\n\n return {\n tier,\n exp: payload.exp,\n sub: payload.sub,\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Read license key from env or file.\n * Checks: GRAPH_LICENSE env var → ~/.graph/license → <db-dir>/license\n */\nexport function readLicenseKey(dbPath?: string): string | undefined {\n const envKey = process.env.GRAPH_LICENSE;\n if (envKey) return envKey.trim();\n\n try {\n const globalPath = join(homedir(), \".graph\", \"license\");\n if (existsSync(globalPath)) {\n return readFileSync(globalPath, \"utf8\").trim();\n }\n\n if (dbPath) {\n const localPath = join(dirname(dbPath), \"license\");\n if (existsSync(localPath)) {\n return readFileSync(localPath, \"utf8\").trim();\n }\n }\n } catch {\n // File read failed — treat as no license\n }\n\n return undefined;\n}\n\n/**\n * Get the current license tier. Returns \"free\" if no valid license found.\n */\nexport function getLicenseTier(dbPath?: string): Tier {\n const key = readLicenseKey(dbPath);\n const info = verifyLicenseKey(key);\n return info?.tier ?? \"free\";\n}\n"],"mappings":";;;AAAA,SAAS,cAAc;AACvB,SAAS,cAAc,kBAAkB;AACzC,SAAS,MAAM,eAAe;AAC9B,SAAS,eAAe;AAcxB,IAAM,aAAa,QAAQ,IAAI,4BAA4B;AAE3D,IAAM,aAAa;AAMZ,SAAS,iBAAiB,KAA6C;AAC5E,MAAI,CAAC,OAAO,CAAC,WAAY,QAAO;AAEhC,MAAI;AAEF,UAAM,MAAM,IAAI,WAAW,UAAU,IAAI,IAAI,MAAM,WAAW,MAAM,IAAI;AAGxE,UAAM,QAAQ,IAAI,MAAM,GAAG;AAC3B,QAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,UAAM,CAAC,WAAW,YAAY,YAAY,IAAI;AAG9C,UAAM,aAAa,OAAO,KAAK,GAAG,SAAS,IAAI,UAAU,EAAE;AAC3D,UAAM,YAAY,OAAO,KAAK,cAAc,WAAW;AACvD,UAAM,kBAAkB,OAAO,KAAK,YAAY,QAAQ;AAExD,UAAM,UAAU;AAAA,MACd;AAAA;AAAA,MACA;AAAA,MACA,EAAE,KAAK,iBAAiB,QAAQ,OAAO,MAAM,OAAO;AAAA,MACpD;AAAA,IACF;AAEA,QAAI,CAAC,QAAS,QAAO;AAGrB,UAAM,UAAU,KAAK;AAAA,MACnB,OAAO,KAAK,YAAY,WAAW,EAAE,SAAS,MAAM;AAAA,IACtD;AAGA,UAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACxC,QAAI,QAAQ,OAAO,QAAQ,MAAM,IAAK,QAAO;AAG7C,UAAM,OAAa,QAAQ,SAAS,QAAQ,QAAQ;AAEpD,WAAO;AAAA,MACL;AAAA,MACA,KAAK,QAAQ;AAAA,MACb,KAAK,QAAQ;AAAA,IACf;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMO,SAAS,eAAe,QAAqC;AAClE,QAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,OAAQ,QAAO,OAAO,KAAK;AAE/B,MAAI;AACF,UAAM,aAAa,KAAK,QAAQ,GAAG,UAAU,SAAS;AACtD,QAAI,WAAW,UAAU,GAAG;AAC1B,aAAO,aAAa,YAAY,MAAM,EAAE,KAAK;AAAA,IAC/C;AAEA,QAAI,QAAQ;AACV,YAAM,YAAY,KAAK,QAAQ,MAAM,GAAG,SAAS;AACjD,UAAI,WAAW,SAAS,GAAG;AACzB,eAAO,aAAa,WAAW,MAAM,EAAE,KAAK;AAAA,MAC9C;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,SAAO;AACT;AAKO,SAAS,eAAe,QAAuB;AACpD,QAAM,MAAM,eAAe,MAAM;AACjC,QAAM,OAAO,iBAAiB,GAAG;AACjC,SAAO,MAAM,QAAQ;AACvB;","names":[]}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
var args = process.argv.slice(2);
|
|
5
|
+
if (args[0] === "activate") {
|
|
6
|
+
const { activate } = await import("./activate-DSDTR2EJ.js");
|
|
7
|
+
activate(args[1]);
|
|
8
|
+
} else if (args[0] === "init") {
|
|
9
|
+
const { init } = await import("./init-JYP72OZQ.js");
|
|
10
|
+
init();
|
|
11
|
+
} else {
|
|
12
|
+
const { startServer } = await import("./server-7IB2VIMK.js");
|
|
13
|
+
startServer().catch((error) => {
|
|
14
|
+
console.error("Failed to start graph:", error);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// [sl:WvU_sWubakQWRCkP993pp] CLI routing — activate subcommand or MCP server\nexport {};\n\nconst args = process.argv.slice(2);\n\nif (args[0] === \"activate\") {\n const { activate } = await import(\"./activate.js\");\n activate(args[1]);\n} else if (args[0] === \"init\") {\n const { init } = await import(\"./init.js\");\n init();\n} else {\n const { startServer } = await import(\"./server.js\");\n startServer().catch((error) => {\n console.error(\"Failed to start graph:\", error);\n process.exit(1);\n });\n}\n"],"mappings":";;;AAGA,IAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AAEjC,IAAI,KAAK,CAAC,MAAM,YAAY;AAC1B,QAAM,EAAE,SAAS,IAAI,MAAM,OAAO,wBAAe;AACjD,WAAS,KAAK,CAAC,CAAC;AAClB,WAAW,KAAK,CAAC,MAAM,QAAQ;AAC7B,QAAM,EAAE,KAAK,IAAI,MAAM,OAAO,oBAAW;AACzC,OAAK;AACP,OAAO;AACL,QAAM,EAAE,YAAY,IAAI,MAAM,OAAO,sBAAa;AAClD,cAAY,EAAE,MAAM,CAAC,UAAU;AAC7B,YAAQ,MAAM,0BAA0B,KAAK;AAC7C,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACH;","names":[]}
|