chainlesschain 0.37.8 → 0.37.10

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 (59) hide show
  1. package/README.md +403 -8
  2. package/bin/chainlesschain.js +4 -0
  3. package/package.json +7 -2
  4. package/src/commands/agent.js +30 -0
  5. package/src/commands/ask.js +114 -0
  6. package/src/commands/audit.js +286 -0
  7. package/src/commands/auth.js +387 -0
  8. package/src/commands/browse.js +184 -0
  9. package/src/commands/chat.js +35 -0
  10. package/src/commands/db.js +152 -0
  11. package/src/commands/did.js +376 -0
  12. package/src/commands/encrypt.js +233 -0
  13. package/src/commands/export.js +125 -0
  14. package/src/commands/git.js +215 -0
  15. package/src/commands/import.js +259 -0
  16. package/src/commands/instinct.js +202 -0
  17. package/src/commands/llm.js +288 -0
  18. package/src/commands/mcp.js +302 -0
  19. package/src/commands/memory.js +282 -0
  20. package/src/commands/note.js +489 -0
  21. package/src/commands/org.js +505 -0
  22. package/src/commands/p2p.js +274 -0
  23. package/src/commands/plugin.js +398 -0
  24. package/src/commands/search.js +237 -0
  25. package/src/commands/session.js +238 -0
  26. package/src/commands/skill.js +479 -0
  27. package/src/commands/sync.js +249 -0
  28. package/src/commands/tokens.js +214 -0
  29. package/src/commands/wallet.js +416 -0
  30. package/src/index.js +65 -0
  31. package/src/lib/audit-logger.js +364 -0
  32. package/src/lib/bm25-search.js +322 -0
  33. package/src/lib/browser-automation.js +216 -0
  34. package/src/lib/crypto-manager.js +246 -0
  35. package/src/lib/did-manager.js +270 -0
  36. package/src/lib/ensure-utf8.js +59 -0
  37. package/src/lib/git-integration.js +220 -0
  38. package/src/lib/instinct-manager.js +190 -0
  39. package/src/lib/knowledge-exporter.js +302 -0
  40. package/src/lib/knowledge-importer.js +293 -0
  41. package/src/lib/llm-providers.js +325 -0
  42. package/src/lib/mcp-client.js +413 -0
  43. package/src/lib/memory-manager.js +211 -0
  44. package/src/lib/note-versioning.js +244 -0
  45. package/src/lib/org-manager.js +424 -0
  46. package/src/lib/p2p-manager.js +317 -0
  47. package/src/lib/pdf-parser.js +96 -0
  48. package/src/lib/permission-engine.js +374 -0
  49. package/src/lib/plan-mode.js +333 -0
  50. package/src/lib/platform.js +15 -0
  51. package/src/lib/plugin-manager.js +312 -0
  52. package/src/lib/response-cache.js +156 -0
  53. package/src/lib/session-manager.js +189 -0
  54. package/src/lib/sync-manager.js +347 -0
  55. package/src/lib/token-tracker.js +200 -0
  56. package/src/lib/wallet-manager.js +348 -0
  57. package/src/repl/agent-repl.js +912 -0
  58. package/src/repl/chat-repl.js +262 -0
  59. package/src/runtime/bootstrap.js +159 -0
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Note version control — track changes to notes with full history.
3
+ */
4
+
5
+ /**
6
+ * Ensure the note_versions table exists.
7
+ */
8
+ export function ensureVersionsTable(db) {
9
+ db.exec(`
10
+ CREATE TABLE IF NOT EXISTS note_versions (
11
+ id TEXT PRIMARY KEY,
12
+ note_id TEXT NOT NULL,
13
+ version INTEGER NOT NULL,
14
+ title TEXT NOT NULL,
15
+ content TEXT DEFAULT '',
16
+ tags TEXT DEFAULT '[]',
17
+ category TEXT DEFAULT 'general',
18
+ change_type TEXT DEFAULT 'edit',
19
+ created_at TEXT DEFAULT (datetime('now'))
20
+ )
21
+ `);
22
+ }
23
+
24
+ /**
25
+ * Generate a simple ID.
26
+ */
27
+ function generateId() {
28
+ const hex = () =>
29
+ Math.floor(Math.random() * 0x10000)
30
+ .toString(16)
31
+ .padStart(4, "0");
32
+ return `${hex()}${hex()}-${hex()}-${hex()}-${hex()}-${hex()}${hex()}${hex()}`;
33
+ }
34
+
35
+ /**
36
+ * Get the next version number for a note.
37
+ */
38
+ export function getNextVersion(db, noteId) {
39
+ const row = db
40
+ .prepare(
41
+ "SELECT MAX(version) as max_ver FROM note_versions WHERE note_id = ?",
42
+ )
43
+ .get(noteId);
44
+ return (row?.max_ver || 0) + 1;
45
+ }
46
+
47
+ /**
48
+ * Save a version snapshot of a note.
49
+ * @param {object} db - Database instance
50
+ * @param {string} noteId - Note ID
51
+ * @param {object} noteData - { title, content, tags, category }
52
+ * @param {string} changeType - 'create' | 'edit' | 'revert'
53
+ * @returns {object} The saved version record
54
+ */
55
+ export function saveVersion(db, noteId, noteData, changeType = "edit") {
56
+ ensureVersionsTable(db);
57
+ const version = getNextVersion(db, noteId);
58
+ const id = generateId();
59
+ const tagsJson =
60
+ typeof noteData.tags === "string"
61
+ ? noteData.tags
62
+ : JSON.stringify(noteData.tags || []);
63
+
64
+ db.prepare(
65
+ "INSERT INTO note_versions (id, note_id, version, title, content, tags, category, change_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
66
+ ).run(
67
+ id,
68
+ noteId,
69
+ version,
70
+ noteData.title || "",
71
+ noteData.content || "",
72
+ tagsJson,
73
+ noteData.category || "general",
74
+ changeType,
75
+ );
76
+
77
+ return {
78
+ id,
79
+ note_id: noteId,
80
+ version,
81
+ title: noteData.title || "",
82
+ content: noteData.content || "",
83
+ tags: tagsJson,
84
+ category: noteData.category || "general",
85
+ change_type: changeType,
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Get the version history for a note.
91
+ */
92
+ export function getHistory(db, noteId) {
93
+ ensureVersionsTable(db);
94
+ return db
95
+ .prepare(
96
+ "SELECT id, note_id, version, title, change_type, created_at FROM note_versions WHERE note_id = ? ORDER BY version DESC",
97
+ )
98
+ .all(noteId);
99
+ }
100
+
101
+ /**
102
+ * Get a specific version of a note.
103
+ */
104
+ export function getVersion(db, noteId, version) {
105
+ ensureVersionsTable(db);
106
+ return db
107
+ .prepare("SELECT * FROM note_versions WHERE note_id = ? AND version = ?")
108
+ .get(noteId, version);
109
+ }
110
+
111
+ /**
112
+ * Compute a simple text diff between two strings.
113
+ * Returns an array of { type: 'add'|'remove'|'same', line } objects.
114
+ */
115
+ export function simpleDiff(oldText, newText) {
116
+ const oldLines = (oldText || "").split("\n");
117
+ const newLines = (newText || "").split("\n");
118
+ const result = [];
119
+
120
+ // Simple line-by-line diff using LCS approach
121
+ const lcs = computeLcs(oldLines, newLines);
122
+ let oi = 0;
123
+ let ni = 0;
124
+ let li = 0;
125
+
126
+ while (oi < oldLines.length || ni < newLines.length) {
127
+ if (li < lcs.length && oi < oldLines.length && oldLines[oi] === lcs[li]) {
128
+ if (ni < newLines.length && newLines[ni] === lcs[li]) {
129
+ result.push({ type: "same", line: lcs[li] });
130
+ oi++;
131
+ ni++;
132
+ li++;
133
+ } else if (ni < newLines.length) {
134
+ result.push({ type: "add", line: newLines[ni] });
135
+ ni++;
136
+ }
137
+ } else if (oi < oldLines.length) {
138
+ if (li < lcs.length && oldLines[oi] !== lcs[li]) {
139
+ result.push({ type: "remove", line: oldLines[oi] });
140
+ oi++;
141
+ } else if (li >= lcs.length) {
142
+ result.push({ type: "remove", line: oldLines[oi] });
143
+ oi++;
144
+ }
145
+ } else if (ni < newLines.length) {
146
+ result.push({ type: "add", line: newLines[ni] });
147
+ ni++;
148
+ }
149
+ }
150
+
151
+ return result;
152
+ }
153
+
154
+ /**
155
+ * Compute the Longest Common Subsequence of two string arrays.
156
+ */
157
+ function computeLcs(a, b) {
158
+ const m = a.length;
159
+ const n = b.length;
160
+ const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
161
+
162
+ for (let i = 1; i <= m; i++) {
163
+ for (let j = 1; j <= n; j++) {
164
+ if (a[i - 1] === b[j - 1]) {
165
+ dp[i][j] = dp[i - 1][j - 1] + 1;
166
+ } else {
167
+ dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
168
+ }
169
+ }
170
+ }
171
+
172
+ // Backtrack to get the LCS
173
+ const result = [];
174
+ let i = m;
175
+ let j = n;
176
+ while (i > 0 && j > 0) {
177
+ if (a[i - 1] === b[j - 1]) {
178
+ result.unshift(a[i - 1]);
179
+ i--;
180
+ j--;
181
+ } else if (dp[i - 1][j] > dp[i][j - 1]) {
182
+ i--;
183
+ } else {
184
+ j--;
185
+ }
186
+ }
187
+
188
+ return result;
189
+ }
190
+
191
+ /**
192
+ * Format a diff result as a readable string.
193
+ */
194
+ export function formatDiff(diffResult) {
195
+ return diffResult
196
+ .map((d) => {
197
+ if (d.type === "add") return `+ ${d.line}`;
198
+ if (d.type === "remove") return `- ${d.line}`;
199
+ return ` ${d.line}`;
200
+ })
201
+ .join("\n");
202
+ }
203
+
204
+ /**
205
+ * Revert a note to a specific version.
206
+ * Saves the current state as a new version first, then applies the old version.
207
+ */
208
+ export function revertToVersion(db, noteId, version) {
209
+ ensureVersionsTable(db);
210
+
211
+ // Get the target version
212
+ const targetVersion = getVersion(db, noteId, version);
213
+ if (!targetVersion) return null;
214
+
215
+ // Get current note state
216
+ const current = db
217
+ .prepare("SELECT * FROM notes WHERE id = ? AND deleted_at IS NULL")
218
+ .get(noteId);
219
+ if (!current) return null;
220
+
221
+ // Save current state as a version before reverting
222
+ saveVersion(db, noteId, current, "edit");
223
+
224
+ // Apply the target version to the note
225
+ db.prepare(
226
+ "UPDATE notes SET title = ?, content = ?, tags = ?, category = ?, updated_at = datetime('now') WHERE id = ?",
227
+ ).run(
228
+ targetVersion.title,
229
+ targetVersion.content,
230
+ targetVersion.tags,
231
+ targetVersion.category,
232
+ noteId,
233
+ );
234
+
235
+ // Save the revert action as a new version
236
+ const revertRecord = saveVersion(db, noteId, targetVersion, "revert");
237
+
238
+ return {
239
+ note_id: noteId,
240
+ reverted_to: version,
241
+ new_version: revertRecord.version,
242
+ title: targetVersion.title,
243
+ };
244
+ }
@@ -0,0 +1,424 @@
1
+ /**
2
+ * Org Manager — Organization, team, and approval workflow management for CLI.
3
+ * Supports org creation, member management, role assignment, and approval workflows.
4
+ */
5
+
6
+ import crypto from "crypto";
7
+
8
+ /**
9
+ * Ensure organization tables exist.
10
+ */
11
+ export function ensureOrgTables(db) {
12
+ db.exec(`
13
+ CREATE TABLE IF NOT EXISTS organizations (
14
+ id TEXT PRIMARY KEY,
15
+ name TEXT NOT NULL,
16
+ description TEXT,
17
+ owner_id TEXT NOT NULL,
18
+ status TEXT DEFAULT 'active',
19
+ settings TEXT,
20
+ created_at TEXT DEFAULT (datetime('now')),
21
+ updated_at TEXT DEFAULT (datetime('now'))
22
+ )
23
+ `);
24
+ db.exec(`
25
+ CREATE TABLE IF NOT EXISTS org_members (
26
+ id TEXT PRIMARY KEY,
27
+ org_id TEXT NOT NULL,
28
+ user_id TEXT NOT NULL,
29
+ display_name TEXT,
30
+ role TEXT DEFAULT 'member',
31
+ status TEXT DEFAULT 'active',
32
+ invited_by TEXT,
33
+ joined_at TEXT DEFAULT (datetime('now'))
34
+ )
35
+ `);
36
+ db.exec(`
37
+ CREATE TABLE IF NOT EXISTS org_teams (
38
+ id TEXT PRIMARY KEY,
39
+ org_id TEXT NOT NULL,
40
+ name TEXT NOT NULL,
41
+ description TEXT,
42
+ lead_id TEXT,
43
+ created_at TEXT DEFAULT (datetime('now'))
44
+ )
45
+ `);
46
+ db.exec(`
47
+ CREATE TABLE IF NOT EXISTS org_team_members (
48
+ team_id TEXT NOT NULL,
49
+ user_id TEXT NOT NULL,
50
+ role TEXT DEFAULT 'member',
51
+ added_at TEXT DEFAULT (datetime('now'))
52
+ )
53
+ `);
54
+ db.exec(`
55
+ CREATE TABLE IF NOT EXISTS approval_requests (
56
+ id TEXT PRIMARY KEY,
57
+ org_id TEXT NOT NULL,
58
+ requester_id TEXT NOT NULL,
59
+ approver_id TEXT,
60
+ request_type TEXT NOT NULL,
61
+ title TEXT NOT NULL,
62
+ description TEXT,
63
+ data TEXT,
64
+ status TEXT DEFAULT 'pending',
65
+ decision_note TEXT,
66
+ created_at TEXT DEFAULT (datetime('now')),
67
+ decided_at TEXT
68
+ )
69
+ `);
70
+ }
71
+
72
+ // ─── Organization CRUD ──────────────────────────────────
73
+
74
+ /**
75
+ * Create an organization.
76
+ */
77
+ export function createOrg(db, name, ownerId, description) {
78
+ ensureOrgTables(db);
79
+ const id = `org-${crypto.randomBytes(8).toString("hex")}`;
80
+
81
+ db.prepare(
82
+ `INSERT INTO organizations (id, name, description, owner_id, status)
83
+ VALUES (?, ?, ?, ?, ?)`,
84
+ ).run(id, name, description || null, ownerId, "active");
85
+
86
+ // Add owner as admin member
87
+ const memberId = `member-${crypto.randomBytes(8).toString("hex")}`;
88
+ db.prepare(
89
+ `INSERT INTO org_members (id, org_id, user_id, role, status)
90
+ VALUES (?, ?, ?, ?, ?)`,
91
+ ).run(memberId, id, ownerId, "admin", "active");
92
+
93
+ return { id, name, description, ownerId, status: "active" };
94
+ }
95
+
96
+ /**
97
+ * Get an organization by ID.
98
+ */
99
+ export function getOrg(db, orgId) {
100
+ ensureOrgTables(db);
101
+ return db.prepare("SELECT * FROM organizations WHERE id = ?").get(orgId);
102
+ }
103
+
104
+ /**
105
+ * List all organizations.
106
+ */
107
+ export function listOrgs(db) {
108
+ ensureOrgTables(db);
109
+ return db
110
+ .prepare("SELECT * FROM organizations ORDER BY created_at DESC")
111
+ .all();
112
+ }
113
+
114
+ /**
115
+ * Update an organization.
116
+ */
117
+ export function updateOrg(db, orgId, updates) {
118
+ ensureOrgTables(db);
119
+ const { name, description, status } = updates;
120
+ if (name)
121
+ db.prepare("UPDATE organizations SET name = ? WHERE id = ?").run(
122
+ name,
123
+ orgId,
124
+ );
125
+ if (description !== undefined)
126
+ db.prepare("UPDATE organizations SET description = ? WHERE id = ?").run(
127
+ description,
128
+ orgId,
129
+ );
130
+ if (status)
131
+ db.prepare("UPDATE organizations SET status = ? WHERE id = ?").run(
132
+ status,
133
+ orgId,
134
+ );
135
+ return getOrg(db, orgId);
136
+ }
137
+
138
+ /**
139
+ * Delete an organization.
140
+ */
141
+ export function deleteOrg(db, orgId) {
142
+ ensureOrgTables(db);
143
+ db.prepare(
144
+ "DELETE FROM org_team_members WHERE team_id IN (SELECT id FROM org_teams WHERE org_id = ?)",
145
+ ).run(orgId);
146
+ db.prepare("DELETE FROM org_teams WHERE org_id = ?").run(orgId);
147
+ db.prepare("DELETE FROM org_members WHERE org_id = ?").run(orgId);
148
+ db.prepare("DELETE FROM approval_requests WHERE org_id = ?").run(orgId);
149
+ const result = db
150
+ .prepare("DELETE FROM organizations WHERE id = ?")
151
+ .run(orgId);
152
+ return result.changes > 0;
153
+ }
154
+
155
+ // ─── Members ────────────────────────────────────────────
156
+
157
+ /**
158
+ * Invite a member to an organization.
159
+ */
160
+ export function inviteMember(db, orgId, userId, displayName, role, invitedBy) {
161
+ ensureOrgTables(db);
162
+ const org = getOrg(db, orgId);
163
+ if (!org) throw new Error(`Organization not found: ${orgId}`);
164
+
165
+ const id = `member-${crypto.randomBytes(8).toString("hex")}`;
166
+ db.prepare(
167
+ `INSERT INTO org_members (id, org_id, user_id, display_name, role, status, invited_by)
168
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
169
+ ).run(
170
+ id,
171
+ orgId,
172
+ userId,
173
+ displayName || null,
174
+ role || "member",
175
+ "invited",
176
+ invitedBy || null,
177
+ );
178
+
179
+ return {
180
+ id,
181
+ orgId,
182
+ userId,
183
+ displayName,
184
+ role: role || "member",
185
+ status: "invited",
186
+ };
187
+ }
188
+
189
+ /**
190
+ * Accept an invitation.
191
+ */
192
+ export function acceptInvite(db, memberId) {
193
+ ensureOrgTables(db);
194
+ const result = db
195
+ .prepare("UPDATE org_members SET status = ? WHERE id = ?")
196
+ .run("active", memberId);
197
+ return result.changes > 0;
198
+ }
199
+
200
+ /**
201
+ * Get members of an organization.
202
+ */
203
+ export function getMembers(db, orgId) {
204
+ ensureOrgTables(db);
205
+ return db
206
+ .prepare(
207
+ "SELECT * FROM org_members WHERE org_id = ? ORDER BY joined_at ASC",
208
+ )
209
+ .all(orgId);
210
+ }
211
+
212
+ /**
213
+ * Update a member's role.
214
+ */
215
+ export function updateMemberRole(db, memberId, newRole) {
216
+ ensureOrgTables(db);
217
+ const result = db
218
+ .prepare("UPDATE org_members SET role = ? WHERE id = ?")
219
+ .run(newRole, memberId);
220
+ return result.changes > 0;
221
+ }
222
+
223
+ /**
224
+ * Remove a member from an organization.
225
+ */
226
+ export function removeMember(db, memberId) {
227
+ ensureOrgTables(db);
228
+ const result = db
229
+ .prepare("DELETE FROM org_members WHERE id = ?")
230
+ .run(memberId);
231
+ return result.changes > 0;
232
+ }
233
+
234
+ // ─── Teams ──────────────────────────────────────────────
235
+
236
+ /**
237
+ * Create a team within an organization.
238
+ */
239
+ export function createTeam(db, orgId, name, description, leadId) {
240
+ ensureOrgTables(db);
241
+ const org = getOrg(db, orgId);
242
+ if (!org) throw new Error(`Organization not found: ${orgId}`);
243
+
244
+ const id = `team-${crypto.randomBytes(8).toString("hex")}`;
245
+ db.prepare(
246
+ `INSERT INTO org_teams (id, org_id, name, description, lead_id)
247
+ VALUES (?, ?, ?, ?, ?)`,
248
+ ).run(id, orgId, name, description || null, leadId || null);
249
+
250
+ return { id, orgId, name, description, leadId };
251
+ }
252
+
253
+ /**
254
+ * List teams in an organization.
255
+ */
256
+ export function listTeams(db, orgId) {
257
+ ensureOrgTables(db);
258
+ return db
259
+ .prepare("SELECT * FROM org_teams WHERE org_id = ? ORDER BY created_at ASC")
260
+ .all(orgId);
261
+ }
262
+
263
+ /**
264
+ * Add a member to a team.
265
+ */
266
+ export function addTeamMember(db, teamId, userId, role) {
267
+ ensureOrgTables(db);
268
+ db.prepare(
269
+ `INSERT INTO org_team_members (team_id, user_id, role) VALUES (?, ?, ?)`,
270
+ ).run(teamId, userId, role || "member");
271
+ return { teamId, userId, role: role || "member" };
272
+ }
273
+
274
+ /**
275
+ * Get team members.
276
+ */
277
+ export function getTeamMembers(db, teamId) {
278
+ ensureOrgTables(db);
279
+ return db
280
+ .prepare("SELECT * FROM org_team_members WHERE team_id = ?")
281
+ .all(teamId);
282
+ }
283
+
284
+ /**
285
+ * Remove a team member.
286
+ */
287
+ export function removeTeamMember(db, teamId, userId) {
288
+ ensureOrgTables(db);
289
+ const result = db
290
+ .prepare("DELETE FROM org_team_members WHERE team_id = ? AND user_id = ?")
291
+ .run(teamId, userId);
292
+ return result.changes > 0;
293
+ }
294
+
295
+ /**
296
+ * Delete a team.
297
+ */
298
+ export function deleteTeam(db, teamId) {
299
+ ensureOrgTables(db);
300
+ db.prepare("DELETE FROM org_team_members WHERE team_id = ?").run(teamId);
301
+ const result = db.prepare("DELETE FROM org_teams WHERE id = ?").run(teamId);
302
+ return result.changes > 0;
303
+ }
304
+
305
+ // ─── Approvals ──────────────────────────────────────────
306
+
307
+ /**
308
+ * Submit an approval request.
309
+ */
310
+ export function submitApproval(
311
+ db,
312
+ orgId,
313
+ requesterId,
314
+ requestType,
315
+ title,
316
+ description,
317
+ data,
318
+ ) {
319
+ ensureOrgTables(db);
320
+ const id = `approval-${crypto.randomBytes(8).toString("hex")}`;
321
+
322
+ db.prepare(
323
+ `INSERT INTO approval_requests (id, org_id, requester_id, request_type, title, description, data, status)
324
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
325
+ ).run(
326
+ id,
327
+ orgId,
328
+ requesterId,
329
+ requestType,
330
+ title,
331
+ description || null,
332
+ data ? JSON.stringify(data) : null,
333
+ "pending",
334
+ );
335
+
336
+ return { id, orgId, requesterId, requestType, title, status: "pending" };
337
+ }
338
+
339
+ /**
340
+ * Approve a request.
341
+ */
342
+ export function approveRequest(db, approvalId, approverId, note) {
343
+ ensureOrgTables(db);
344
+ const result = db
345
+ .prepare(
346
+ "UPDATE approval_requests SET status = ?, approver_id = ?, decision_note = ?, decided_at = datetime('now') WHERE id = ?",
347
+ )
348
+ .run("approved", approverId, note || null, approvalId);
349
+ return result.changes > 0;
350
+ }
351
+
352
+ /**
353
+ * Reject a request.
354
+ */
355
+ export function rejectRequest(db, approvalId, approverId, note) {
356
+ ensureOrgTables(db);
357
+ const result = db
358
+ .prepare(
359
+ "UPDATE approval_requests SET status = ?, approver_id = ?, decision_note = ?, decided_at = datetime('now') WHERE id = ?",
360
+ )
361
+ .run("rejected", approverId, note || null, approvalId);
362
+ return result.changes > 0;
363
+ }
364
+
365
+ /**
366
+ * Get approval requests.
367
+ */
368
+ export function getApprovals(db, options = {}) {
369
+ ensureOrgTables(db);
370
+ const { orgId, status, requesterId } = options;
371
+
372
+ let sql = "SELECT * FROM approval_requests WHERE 1=1";
373
+ const params = [];
374
+
375
+ if (orgId) {
376
+ sql += " AND org_id = ?";
377
+ params.push(orgId);
378
+ }
379
+ if (status) {
380
+ sql += " AND status = ?";
381
+ params.push(status);
382
+ }
383
+ if (requesterId) {
384
+ sql += " AND requester_id = ?";
385
+ params.push(requesterId);
386
+ }
387
+
388
+ sql += " ORDER BY created_at DESC";
389
+ return db.prepare(sql).all(...params);
390
+ }
391
+
392
+ /**
393
+ * Get a single approval request.
394
+ */
395
+ export function getApproval(db, approvalId) {
396
+ ensureOrgTables(db);
397
+ return db
398
+ .prepare("SELECT * FROM approval_requests WHERE id = ?")
399
+ .get(approvalId);
400
+ }
401
+
402
+ /**
403
+ * Get org summary statistics.
404
+ */
405
+ export function getOrgSummary(db, orgId) {
406
+ ensureOrgTables(db);
407
+ const members = db
408
+ .prepare("SELECT COUNT(*) as c FROM org_members WHERE org_id = ?")
409
+ .get(orgId);
410
+ const teams = db
411
+ .prepare("SELECT COUNT(*) as c FROM org_teams WHERE org_id = ?")
412
+ .get(orgId);
413
+ const pending = db
414
+ .prepare(
415
+ "SELECT COUNT(*) as c FROM approval_requests WHERE org_id = ? AND status = ?",
416
+ )
417
+ .get(orgId, "pending");
418
+
419
+ return {
420
+ memberCount: members?.c || 0,
421
+ teamCount: teams?.c || 0,
422
+ pendingApprovals: pending?.c || 0,
423
+ };
424
+ }