@agent-deck/backend 1.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.
Files changed (166) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/cli-runtime.d.ts +4 -0
  3. package/dist/cli-runtime.d.ts.map +1 -0
  4. package/dist/cli-runtime.js +12 -0
  5. package/dist/cli-runtime.js.map +1 -0
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +30 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/lib/agent-deck-context.d.ts +8 -0
  11. package/dist/lib/agent-deck-context.d.ts.map +1 -0
  12. package/dist/lib/agent-deck-context.js +48 -0
  13. package/dist/lib/agent-deck-context.js.map +1 -0
  14. package/dist/lib/bound-deck-scope.d.ts +15 -0
  15. package/dist/lib/bound-deck-scope.d.ts.map +1 -0
  16. package/dist/lib/bound-deck-scope.js +68 -0
  17. package/dist/lib/bound-deck-scope.js.map +1 -0
  18. package/dist/lib/client-scope.d.ts +14 -0
  19. package/dist/lib/client-scope.d.ts.map +1 -0
  20. package/dist/lib/client-scope.js +46 -0
  21. package/dist/lib/client-scope.js.map +1 -0
  22. package/dist/lib/paths.d.ts +2 -0
  23. package/dist/lib/paths.d.ts.map +1 -0
  24. package/dist/lib/paths.js +24 -0
  25. package/dist/lib/paths.js.map +1 -0
  26. package/dist/lib/version.d.ts +3 -0
  27. package/dist/lib/version.d.ts.map +1 -0
  28. package/dist/lib/version.js +20 -0
  29. package/dist/lib/version.js.map +1 -0
  30. package/dist/mcp-index.d.ts +2 -0
  31. package/dist/mcp-index.d.ts.map +1 -0
  32. package/dist/mcp-index.js +32 -0
  33. package/dist/mcp-index.js.map +1 -0
  34. package/dist/mcp-server.d.ts +25 -0
  35. package/dist/mcp-server.d.ts.map +1 -0
  36. package/dist/mcp-server.js +1121 -0
  37. package/dist/mcp-server.js.map +1 -0
  38. package/dist/mcp-stdio.d.ts +2 -0
  39. package/dist/mcp-stdio.d.ts.map +1 -0
  40. package/dist/mcp-stdio.js.map +1 -0
  41. package/dist/models/database.d.ts +64 -0
  42. package/dist/models/database.d.ts.map +1 -0
  43. package/dist/models/database.js +965 -0
  44. package/dist/models/database.js.map +1 -0
  45. package/dist/playbooks/playbook-manager.d.ts +29 -0
  46. package/dist/playbooks/playbook-manager.d.ts.map +1 -0
  47. package/dist/playbooks/playbook-manager.js +198 -0
  48. package/dist/playbooks/playbook-manager.js.map +1 -0
  49. package/dist/playbooks/playbook-parser.d.ts +8 -0
  50. package/dist/playbooks/playbook-parser.d.ts.map +1 -0
  51. package/dist/playbooks/playbook-parser.js +76 -0
  52. package/dist/playbooks/playbook-parser.js.map +1 -0
  53. package/dist/playbooks/playbook-service.d.ts +9 -0
  54. package/dist/playbooks/playbook-service.d.ts.map +1 -0
  55. package/dist/playbooks/playbook-service.js +107 -0
  56. package/dist/playbooks/playbook-service.js.map +1 -0
  57. package/dist/routes/collection.d.ts +3 -0
  58. package/dist/routes/collection.d.ts.map +1 -0
  59. package/dist/routes/collection.js +34 -0
  60. package/dist/routes/collection.js.map +1 -0
  61. package/dist/routes/credentials.d.ts +3 -0
  62. package/dist/routes/credentials.d.ts.map +1 -0
  63. package/dist/routes/credentials.js +241 -0
  64. package/dist/routes/credentials.js.map +1 -0
  65. package/dist/routes/decks.d.ts +3 -0
  66. package/dist/routes/decks.d.ts.map +1 -0
  67. package/dist/routes/decks.js +430 -0
  68. package/dist/routes/decks.js.map +1 -0
  69. package/dist/routes/local-mcp.d.ts +3 -0
  70. package/dist/routes/local-mcp.d.ts.map +1 -0
  71. package/dist/routes/local-mcp.js +189 -0
  72. package/dist/routes/local-mcp.js.map +1 -0
  73. package/dist/routes/mcp.d.ts +3 -0
  74. package/dist/routes/mcp.d.ts.map +1 -0
  75. package/dist/routes/mcp.js +170 -0
  76. package/dist/routes/mcp.js.map +1 -0
  77. package/dist/routes/oauth.d.ts +3 -0
  78. package/dist/routes/oauth.d.ts.map +1 -0
  79. package/dist/routes/oauth.js +242 -0
  80. package/dist/routes/oauth.js.map +1 -0
  81. package/dist/routes/playbooks.d.ts +5 -0
  82. package/dist/routes/playbooks.d.ts.map +1 -0
  83. package/dist/routes/playbooks.js +220 -0
  84. package/dist/routes/playbooks.js.map +1 -0
  85. package/dist/routes/scope.d.ts +3 -0
  86. package/dist/routes/scope.d.ts.map +1 -0
  87. package/dist/routes/scope.js +107 -0
  88. package/dist/routes/scope.js.map +1 -0
  89. package/dist/routes/services.d.ts +3 -0
  90. package/dist/routes/services.d.ts.map +1 -0
  91. package/dist/routes/services.js +281 -0
  92. package/dist/routes/services.js.map +1 -0
  93. package/dist/routes/websocket.d.ts +11 -0
  94. package/dist/routes/websocket.d.ts.map +1 -0
  95. package/dist/routes/websocket.js +154 -0
  96. package/dist/routes/websocket.js.map +1 -0
  97. package/dist/scope/repo-deck.d.ts +10 -0
  98. package/dist/scope/repo-deck.d.ts.map +1 -0
  99. package/dist/scope/repo-deck.js +63 -0
  100. package/dist/scope/repo-deck.js.map +1 -0
  101. package/dist/server/index.d.ts +24 -0
  102. package/dist/server/index.d.ts.map +1 -0
  103. package/dist/server/index.js +111 -0
  104. package/dist/server/index.js.map +1 -0
  105. package/dist/services/collection-warning-service.d.ts +18 -0
  106. package/dist/services/collection-warning-service.d.ts.map +1 -0
  107. package/dist/services/collection-warning-service.js +129 -0
  108. package/dist/services/collection-warning-service.js.map +1 -0
  109. package/dist/services/config-manager.d.ts +32 -0
  110. package/dist/services/config-manager.d.ts.map +1 -0
  111. package/dist/services/config-manager.js +119 -0
  112. package/dist/services/config-manager.js.map +1 -0
  113. package/dist/services/icon-resolver.d.ts +20 -0
  114. package/dist/services/icon-resolver.d.ts.map +1 -0
  115. package/dist/services/icon-resolver.js +224 -0
  116. package/dist/services/icon-resolver.js.map +1 -0
  117. package/dist/services/local-mcp-server-manager.d.ts +51 -0
  118. package/dist/services/local-mcp-server-manager.d.ts.map +1 -0
  119. package/dist/services/local-mcp-server-manager.js +246 -0
  120. package/dist/services/local-mcp-server-manager.js.map +1 -0
  121. package/dist/services/mcp-client-manager.d.ts +22 -0
  122. package/dist/services/mcp-client-manager.d.ts.map +1 -0
  123. package/dist/services/mcp-client-manager.js +257 -0
  124. package/dist/services/mcp-client-manager.js.map +1 -0
  125. package/dist/services/mcp-discovery-service.d.ts +31 -0
  126. package/dist/services/mcp-discovery-service.d.ts.map +1 -0
  127. package/dist/services/mcp-discovery-service.js +164 -0
  128. package/dist/services/mcp-discovery-service.js.map +1 -0
  129. package/dist/services/oauth-manager.d.ts +25 -0
  130. package/dist/services/oauth-manager.d.ts.map +1 -0
  131. package/dist/services/oauth-manager.js +365 -0
  132. package/dist/services/oauth-manager.js.map +1 -0
  133. package/dist/services/service-manager.d.ts +61 -0
  134. package/dist/services/service-manager.d.ts.map +1 -0
  135. package/dist/services/service-manager.js +447 -0
  136. package/dist/services/service-manager.js.map +1 -0
  137. package/dist/test-local-mcp-e2e.d.ts +3 -0
  138. package/dist/test-local-mcp-e2e.d.ts.map +1 -0
  139. package/dist/test-local-mcp-e2e.js +104 -0
  140. package/dist/test-local-mcp-e2e.js.map +1 -0
  141. package/dist/test-local-mcp.d.ts +3 -0
  142. package/dist/test-local-mcp.d.ts.map +1 -0
  143. package/dist/test-local-mcp.js +54 -0
  144. package/dist/test-local-mcp.js.map +1 -0
  145. package/dist/vault/credential-manager.d.ts +45 -0
  146. package/dist/vault/credential-manager.d.ts.map +1 -0
  147. package/dist/vault/credential-manager.js +237 -0
  148. package/dist/vault/credential-manager.js.map +1 -0
  149. package/dist/vault/index.d.ts +4 -0
  150. package/dist/vault/index.d.ts.map +1 -0
  151. package/dist/vault/index.js +20 -0
  152. package/dist/vault/index.js.map +1 -0
  153. package/dist/vault/secret-store.d.ts +36 -0
  154. package/dist/vault/secret-store.d.ts.map +1 -0
  155. package/dist/vault/secret-store.js +207 -0
  156. package/dist/vault/secret-store.js.map +1 -0
  157. package/dist/vault/yaml-sync.d.ts +8 -0
  158. package/dist/vault/yaml-sync.d.ts.map +1 -0
  159. package/dist/vault/yaml-sync.js +60 -0
  160. package/dist/vault/yaml-sync.js.map +1 -0
  161. package/package.json +64 -0
  162. package/static-ui/assets/AgentDeckLogo2-z3pVqJJ3.png +0 -0
  163. package/static-ui/assets/index-BnA3AsqY.css +1 -0
  164. package/static-ui/assets/index-D1IuraRt.js +334 -0
  165. package/static-ui/favicon.png +0 -0
  166. package/static-ui/index.html +17 -0
@@ -0,0 +1,965 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DatabaseManager = void 0;
7
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
8
+ const shared_1 = require("@agent-deck/shared");
9
+ class DatabaseManager {
10
+ db;
11
+ constructor(dbPath = 'agent_deck.db') {
12
+ this.db = new better_sqlite3_1.default(dbPath);
13
+ this.initializeTables();
14
+ this.migrate();
15
+ }
16
+ migrate() {
17
+ const serviceColumns = this.db.prepare('PRAGMA table_info(services)').all();
18
+ if (!serviceColumns.some((column) => column.name === 'credential_id')) {
19
+ this.db.exec('ALTER TABLE services ADD COLUMN credential_id TEXT');
20
+ }
21
+ if (!serviceColumns.some((column) => column.name === 'icon_url')) {
22
+ this.db.exec('ALTER TABLE services ADD COLUMN icon_url TEXT');
23
+ }
24
+ if (!serviceColumns.some((column) => column.name === 'disabled_tools')) {
25
+ this.db.exec("ALTER TABLE services ADD COLUMN disabled_tools TEXT NOT NULL DEFAULT '[]'");
26
+ }
27
+ const credentialColumns = this.db.prepare('PRAGMA table_info(credentials)').all();
28
+ if (!credentialColumns.some((column) => column.name === 'docs_url')) {
29
+ this.db.exec('ALTER TABLE credentials ADD COLUMN docs_url TEXT');
30
+ }
31
+ if (!credentialColumns.some((column) => column.name === 'icon_url')) {
32
+ this.db.exec('ALTER TABLE credentials ADD COLUMN icon_url TEXT');
33
+ }
34
+ }
35
+ initializeTables() {
36
+ // Services table
37
+ this.db.exec(`
38
+ CREATE TABLE IF NOT EXISTS services (
39
+ id TEXT PRIMARY KEY,
40
+ name TEXT NOT NULL UNIQUE,
41
+ type TEXT NOT NULL CHECK (type IN ('mcp', 'a2a', 'local-mcp')),
42
+ url TEXT NOT NULL,
43
+ health TEXT NOT NULL DEFAULT 'unknown',
44
+ description TEXT,
45
+ card_color TEXT NOT NULL DEFAULT '#7ed4da',
46
+ is_connected BOOLEAN NOT NULL DEFAULT 0,
47
+ last_ping TEXT,
48
+ registered_at TEXT NOT NULL,
49
+ updated_at TEXT NOT NULL,
50
+ headers TEXT,
51
+
52
+ -- OAuth fields
53
+ oauth_client_id TEXT,
54
+ oauth_client_secret TEXT,
55
+ oauth_authorization_url TEXT,
56
+ oauth_token_url TEXT,
57
+ oauth_redirect_uri TEXT,
58
+ oauth_scope TEXT,
59
+ oauth_access_token TEXT,
60
+ oauth_refresh_token TEXT,
61
+ oauth_token_expires_at TEXT,
62
+ oauth_state TEXT,
63
+
64
+ -- Local MCP server fields
65
+ local_command TEXT,
66
+ local_args TEXT,
67
+ local_working_dir TEXT,
68
+ local_env TEXT
69
+ )
70
+ `);
71
+ // Decks table
72
+ this.db.exec(`
73
+ CREATE TABLE IF NOT EXISTS decks (
74
+ id TEXT PRIMARY KEY,
75
+ name TEXT NOT NULL,
76
+ description TEXT,
77
+ is_active BOOLEAN NOT NULL DEFAULT 0,
78
+ created_at TEXT NOT NULL,
79
+ updated_at TEXT NOT NULL
80
+ )
81
+ `);
82
+ // Deck services junction table
83
+ this.db.exec(`
84
+ CREATE TABLE IF NOT EXISTS deck_services (
85
+ deck_id TEXT NOT NULL,
86
+ service_id TEXT NOT NULL,
87
+ position INTEGER NOT NULL,
88
+ FOREIGN KEY (deck_id) REFERENCES decks (id) ON DELETE CASCADE,
89
+ FOREIGN KEY (service_id) REFERENCES services (id) ON DELETE CASCADE,
90
+ PRIMARY KEY (deck_id, service_id)
91
+ )
92
+ `);
93
+ // Credentials table (metadata only — secrets in Keychain)
94
+ this.db.exec(`
95
+ CREATE TABLE IF NOT EXISTS credentials (
96
+ id TEXT PRIMARY KEY,
97
+ label TEXT NOT NULL,
98
+ scheme TEXT NOT NULL CHECK (scheme IN ('bearer', 'header', 'http_basic_user')),
99
+ header_name TEXT,
100
+ env_name TEXT NOT NULL,
101
+ keychain_account TEXT NOT NULL,
102
+ tags TEXT NOT NULL DEFAULT '[]',
103
+ created_at TEXT NOT NULL,
104
+ updated_at TEXT NOT NULL
105
+ )
106
+ `);
107
+ // Deck credentials junction table
108
+ this.db.exec(`
109
+ CREATE TABLE IF NOT EXISTS deck_credentials (
110
+ deck_id TEXT NOT NULL,
111
+ credential_id TEXT NOT NULL,
112
+ position INTEGER NOT NULL,
113
+ FOREIGN KEY (deck_id) REFERENCES decks (id) ON DELETE CASCADE,
114
+ FOREIGN KEY (credential_id) REFERENCES credentials (id) ON DELETE CASCADE,
115
+ PRIMARY KEY (deck_id, credential_id)
116
+ )
117
+ `);
118
+ this.db.exec(`
119
+ CREATE TABLE IF NOT EXISTS playbooks (
120
+ id TEXT PRIMARY KEY,
121
+ title TEXT NOT NULL UNIQUE,
122
+ body TEXT NOT NULL DEFAULT '',
123
+ triggers TEXT NOT NULL DEFAULT '[]',
124
+ depends_on_credentials TEXT NOT NULL DEFAULT '[]',
125
+ depends_on_services TEXT NOT NULL DEFAULT '[]',
126
+ exec_command TEXT,
127
+ skill_path TEXT,
128
+ created_at TEXT NOT NULL,
129
+ updated_at TEXT NOT NULL
130
+ )
131
+ `);
132
+ this.db.exec(`
133
+ CREATE TABLE IF NOT EXISTS deck_playbooks (
134
+ deck_id TEXT NOT NULL,
135
+ playbook_id TEXT NOT NULL,
136
+ position INTEGER NOT NULL,
137
+ FOREIGN KEY (deck_id) REFERENCES decks (id) ON DELETE CASCADE,
138
+ FOREIGN KEY (playbook_id) REFERENCES playbooks (id) ON DELETE CASCADE,
139
+ PRIMARY KEY (deck_id, playbook_id)
140
+ )
141
+ `);
142
+ // Exec audit log (credential ids only, never secret values)
143
+ this.db.exec(`
144
+ CREATE TABLE IF NOT EXISTS exec_runs (
145
+ id TEXT PRIMARY KEY,
146
+ deck_id TEXT,
147
+ manifest_path TEXT,
148
+ command TEXT NOT NULL,
149
+ exit_code INTEGER,
150
+ started_at TEXT NOT NULL,
151
+ finished_at TEXT,
152
+ FOREIGN KEY (deck_id) REFERENCES decks (id) ON DELETE SET NULL
153
+ )
154
+ `);
155
+ this.db.exec(`
156
+ CREATE TABLE IF NOT EXISTS exec_run_credentials (
157
+ exec_run_id TEXT NOT NULL,
158
+ credential_id TEXT NOT NULL,
159
+ FOREIGN KEY (exec_run_id) REFERENCES exec_runs (id) ON DELETE CASCADE,
160
+ FOREIGN KEY (credential_id) REFERENCES credentials (id) ON DELETE CASCADE,
161
+ PRIMARY KEY (exec_run_id, credential_id)
162
+ )
163
+ `);
164
+ // Create indexes for better performance
165
+ this.db.exec(`
166
+ CREATE INDEX IF NOT EXISTS idx_services_type ON services(type);
167
+ CREATE INDEX IF NOT EXISTS idx_services_connected ON services(is_connected);
168
+ CREATE INDEX IF NOT EXISTS idx_decks_active ON decks(is_active);
169
+ CREATE INDEX IF NOT EXISTS idx_deck_services_position ON deck_services(position);
170
+ CREATE INDEX IF NOT EXISTS idx_credentials_env_name ON credentials(env_name);
171
+ CREATE INDEX IF NOT EXISTS idx_deck_credentials_position ON deck_credentials(position);
172
+ CREATE INDEX IF NOT EXISTS idx_deck_playbooks_position ON deck_playbooks(position);
173
+ CREATE INDEX IF NOT EXISTS idx_exec_runs_started_at ON exec_runs(started_at);
174
+ `);
175
+ }
176
+ mapCredentialRow(row) {
177
+ return {
178
+ id: row.id,
179
+ label: row.label,
180
+ scheme: row.scheme,
181
+ headerName: row.header_name ?? undefined,
182
+ envName: row.env_name,
183
+ keychainAccount: row.keychain_account,
184
+ tags: row.tags ? JSON.parse(row.tags) : [],
185
+ docsUrl: row.docs_url ?? undefined,
186
+ iconUrl: row.icon_url ?? undefined,
187
+ hasSecret: false,
188
+ createdAt: row.created_at,
189
+ updatedAt: row.updated_at,
190
+ };
191
+ }
192
+ async getDeckCredentialsForDeck(deckId) {
193
+ const stmt = this.db.prepare(`
194
+ SELECT c.*, dc.position
195
+ FROM deck_credentials dc
196
+ JOIN credentials c ON c.id = dc.credential_id
197
+ WHERE dc.deck_id = ?
198
+ ORDER BY dc.position ASC
199
+ `);
200
+ const rows = stmt.all(deckId);
201
+ return rows.map((row) => this.mapCredentialRow(row));
202
+ }
203
+ // Service operations
204
+ async createService(input) {
205
+ const now = new Date().toISOString();
206
+ const service = {
207
+ id: (0, shared_1.generateId)(),
208
+ ...input,
209
+ health: 'unknown',
210
+ cardColor: input.cardColor || '#7ed4da',
211
+ isConnected: false,
212
+ lastPing: undefined,
213
+ registeredAt: now,
214
+ updatedAt: now,
215
+ headers: input.headers,
216
+ oauthClientId: input.oauthClientId,
217
+ oauthClientSecret: input.oauthClientSecret,
218
+ oauthAuthorizationUrl: input.oauthAuthorizationUrl,
219
+ oauthTokenUrl: input.oauthTokenUrl,
220
+ oauthRedirectUri: input.oauthRedirectUri,
221
+ oauthScope: input.oauthScope,
222
+ oauthAccessToken: undefined,
223
+ oauthRefreshToken: undefined,
224
+ oauthTokenExpiresAt: undefined,
225
+ oauthState: undefined,
226
+ localCommand: input.localCommand,
227
+ localArgs: input.localArgs,
228
+ localWorkingDir: input.localWorkingDir,
229
+ localEnv: input.localEnv,
230
+ credentialId: input.credentialId,
231
+ iconUrl: input.iconUrl,
232
+ disabledToolNames: [],
233
+ };
234
+ const stmt = this.db.prepare(`
235
+ INSERT INTO services (
236
+ id, name, type, url, health, description, card_color, is_connected,
237
+ registered_at, updated_at, headers, credential_id, icon_url,
238
+ oauth_client_id, oauth_client_secret, oauth_authorization_url,
239
+ oauth_token_url, oauth_redirect_uri, oauth_scope,
240
+ local_command, local_args, local_working_dir, local_env
241
+ ) VALUES (
242
+ @id, @name, @type, @url, @health, @description, @card_color, @is_connected,
243
+ @registered_at, @updated_at, @headers, @credential_id, @icon_url,
244
+ @oauth_client_id, @oauth_client_secret, @oauth_authorization_url,
245
+ @oauth_token_url, @oauth_redirect_uri, @oauth_scope,
246
+ @local_command, @local_args, @local_working_dir, @local_env
247
+ )
248
+ `);
249
+ stmt.run({
250
+ id: service.id,
251
+ name: service.name,
252
+ type: service.type,
253
+ url: service.url,
254
+ health: service.health,
255
+ description: service.description,
256
+ card_color: service.cardColor,
257
+ is_connected: service.isConnected ? 1 : 0,
258
+ registered_at: service.registeredAt,
259
+ updated_at: service.updatedAt,
260
+ headers: service.headers ? JSON.stringify(service.headers) : null,
261
+ credential_id: service.credentialId ?? null,
262
+ icon_url: service.iconUrl ?? null,
263
+ oauth_client_id: service.oauthClientId,
264
+ oauth_client_secret: service.oauthClientSecret,
265
+ oauth_authorization_url: service.oauthAuthorizationUrl,
266
+ oauth_token_url: service.oauthTokenUrl,
267
+ oauth_redirect_uri: service.oauthRedirectUri,
268
+ oauth_scope: service.oauthScope,
269
+ local_command: service.localCommand,
270
+ local_args: service.localArgs ? JSON.stringify(service.localArgs) : null,
271
+ local_working_dir: service.localWorkingDir,
272
+ local_env: service.localEnv ? JSON.stringify(service.localEnv) : null,
273
+ });
274
+ return service;
275
+ }
276
+ mapServiceRow(row) {
277
+ return {
278
+ id: row.id,
279
+ name: row.name,
280
+ type: row.type,
281
+ url: row.url,
282
+ health: row.health,
283
+ description: row.description,
284
+ cardColor: row.card_color,
285
+ isConnected: Boolean(row.is_connected),
286
+ lastPing: row.last_ping,
287
+ registeredAt: row.registered_at,
288
+ updatedAt: row.updated_at,
289
+ headers: row.headers ? JSON.parse(row.headers) : null,
290
+ oauthClientId: row.oauth_client_id,
291
+ oauthClientSecret: row.oauth_client_secret,
292
+ oauthAuthorizationUrl: row.oauth_authorization_url,
293
+ oauthTokenUrl: row.oauth_token_url,
294
+ oauthRedirectUri: row.oauth_redirect_uri,
295
+ oauthScope: row.oauth_scope,
296
+ oauthAccessToken: row.oauth_access_token,
297
+ oauthRefreshToken: row.oauth_refresh_token,
298
+ oauthTokenExpiresAt: row.oauth_token_expires_at,
299
+ oauthState: row.oauth_state,
300
+ localCommand: row.local_command,
301
+ localArgs: row.local_args ? JSON.parse(row.local_args) : null,
302
+ localWorkingDir: row.local_working_dir,
303
+ localEnv: row.local_env ? JSON.parse(row.local_env) : null,
304
+ credentialId: row.credential_id ?? undefined,
305
+ iconUrl: row.icon_url ?? undefined,
306
+ disabledToolNames: row.disabled_tools ? JSON.parse(row.disabled_tools) : [],
307
+ };
308
+ }
309
+ async getService(id) {
310
+ const stmt = this.db.prepare('SELECT * FROM services WHERE id = ?');
311
+ const row = stmt.get(id);
312
+ if (!row)
313
+ return null;
314
+ return this.mapServiceRow(row);
315
+ }
316
+ async getAllServices() {
317
+ const stmt = this.db.prepare('SELECT * FROM services ORDER BY registered_at DESC');
318
+ const rows = stmt.all();
319
+ return rows.map((row) => this.mapServiceRow(row));
320
+ }
321
+ async updateService(id, input) {
322
+ const existing = await this.getService(id);
323
+ if (!existing)
324
+ return null;
325
+ const updated = {
326
+ ...existing,
327
+ ...input,
328
+ updatedAt: new Date().toISOString(),
329
+ };
330
+ const stmt = this.db.prepare(`
331
+ UPDATE services SET
332
+ name = @name, description = @description, card_color = @card_color,
333
+ updated_at = @updated_at, headers = @headers, credential_id = @credential_id,
334
+ icon_url = @icon_url,
335
+ oauth_client_id = @oauth_client_id, oauth_client_secret = @oauth_client_secret,
336
+ oauth_authorization_url = @oauth_authorization_url, oauth_token_url = @oauth_token_url,
337
+ oauth_redirect_uri = @oauth_redirect_uri, oauth_scope = @oauth_scope,
338
+ local_command = @local_command, local_args = @local_args,
339
+ local_working_dir = @local_working_dir, local_env = @local_env
340
+ WHERE id = @id
341
+ `);
342
+ stmt.run({
343
+ id: updated.id,
344
+ name: updated.name,
345
+ description: updated.description,
346
+ card_color: updated.cardColor,
347
+ updated_at: updated.updatedAt,
348
+ headers: updated.headers ? JSON.stringify(updated.headers) : null,
349
+ credential_id: updated.credentialId ?? null,
350
+ icon_url: updated.iconUrl ?? null,
351
+ oauth_client_id: updated.oauthClientId,
352
+ oauth_client_secret: updated.oauthClientSecret,
353
+ oauth_authorization_url: updated.oauthAuthorizationUrl,
354
+ oauth_token_url: updated.oauthTokenUrl,
355
+ oauth_redirect_uri: updated.oauthRedirectUri,
356
+ oauth_scope: updated.oauthScope,
357
+ local_command: updated.localCommand,
358
+ local_args: updated.localArgs ? JSON.stringify(updated.localArgs) : null,
359
+ local_working_dir: updated.localWorkingDir,
360
+ local_env: updated.localEnv ? JSON.stringify(updated.localEnv) : null,
361
+ });
362
+ return updated;
363
+ }
364
+ async updateServiceDisabledTools(id, disabledTools) {
365
+ const existing = await this.getService(id);
366
+ if (!existing) {
367
+ return null;
368
+ }
369
+ const stmt = this.db.prepare(`
370
+ UPDATE services SET
371
+ disabled_tools = @disabled_tools,
372
+ updated_at = @updated_at
373
+ WHERE id = @id
374
+ `);
375
+ stmt.run({
376
+ id,
377
+ disabled_tools: JSON.stringify(disabledTools),
378
+ updated_at: new Date().toISOString(),
379
+ });
380
+ return this.getService(id);
381
+ }
382
+ async deleteService(id) {
383
+ const stmt = this.db.prepare('DELETE FROM services WHERE id = ?');
384
+ const result = stmt.run(id);
385
+ return result.changes > 0;
386
+ }
387
+ async updateServiceStatus(id, isConnected, health, lastPing) {
388
+ const stmt = this.db.prepare(`
389
+ UPDATE services SET
390
+ is_connected = @is_connected,
391
+ health = @health,
392
+ last_ping = @last_ping,
393
+ updated_at = @updated_at
394
+ WHERE id = @id
395
+ `);
396
+ stmt.run({
397
+ id,
398
+ is_connected: isConnected ? 1 : 0,
399
+ health,
400
+ last_ping: lastPing,
401
+ updated_at: new Date().toISOString(),
402
+ });
403
+ }
404
+ async updateOAuthTokens(id, accessToken, refreshToken, expiresAt) {
405
+ // Create headers with Authorization Bearer token
406
+ const headers = {
407
+ Authorization: `Bearer ${accessToken}`
408
+ };
409
+ const stmt = this.db.prepare(`
410
+ UPDATE services SET
411
+ oauth_access_token = @oauth_access_token,
412
+ oauth_refresh_token = @oauth_refresh_token,
413
+ oauth_token_expires_at = @oauth_token_expires_at,
414
+ headers = @headers,
415
+ updated_at = @updated_at
416
+ WHERE id = @id
417
+ `);
418
+ stmt.run({
419
+ id,
420
+ oauth_access_token: accessToken,
421
+ oauth_refresh_token: refreshToken,
422
+ oauth_token_expires_at: expiresAt,
423
+ headers: JSON.stringify(headers),
424
+ updated_at: new Date().toISOString(),
425
+ });
426
+ }
427
+ // Deck operations
428
+ async createDeck(input) {
429
+ const now = new Date().toISOString();
430
+ const deck = {
431
+ id: (0, shared_1.generateId)(),
432
+ name: input.name,
433
+ description: input.description,
434
+ isActive: input.isActive || false,
435
+ services: [],
436
+ credentials: [],
437
+ playbooks: [],
438
+ createdAt: now,
439
+ updatedAt: now,
440
+ };
441
+ const stmt = this.db.prepare(`
442
+ INSERT INTO decks (id, name, description, is_active, created_at, updated_at)
443
+ VALUES (@id, @name, @description, @is_active, @created_at, @updated_at)
444
+ `);
445
+ stmt.run({
446
+ id: deck.id,
447
+ name: deck.name,
448
+ description: deck.description,
449
+ is_active: deck.isActive ? 1 : 0,
450
+ created_at: deck.createdAt,
451
+ updated_at: deck.updatedAt,
452
+ });
453
+ return deck;
454
+ }
455
+ async getDeck(id) {
456
+ const stmt = this.db.prepare('SELECT * FROM decks WHERE id = ?');
457
+ const row = stmt.get(id);
458
+ if (!row)
459
+ return null;
460
+ const deckServices = await this.getDeckServices(id);
461
+ const services = [];
462
+ for (const deckService of deckServices) {
463
+ const service = await this.getService(deckService.serviceId);
464
+ if (service) {
465
+ services.push(service);
466
+ }
467
+ }
468
+ const credentials = await this.getDeckCredentialsForDeck(id);
469
+ const playbooks = await this.getDeckPlaybooksForDeck(id);
470
+ return {
471
+ id: row.id,
472
+ name: row.name,
473
+ description: row.description,
474
+ isActive: Boolean(row.is_active),
475
+ services,
476
+ credentials,
477
+ playbooks,
478
+ createdAt: row.created_at,
479
+ updatedAt: row.updated_at,
480
+ };
481
+ }
482
+ async getAllDecks() {
483
+ const stmt = this.db.prepare('SELECT * FROM decks ORDER BY created_at DESC');
484
+ const rows = stmt.all();
485
+ const decks = [];
486
+ for (const row of rows) {
487
+ const deckServices = await this.getDeckServices(row.id);
488
+ const services = [];
489
+ for (const deckService of deckServices) {
490
+ const service = await this.getService(deckService.serviceId);
491
+ if (service) {
492
+ services.push(service);
493
+ }
494
+ }
495
+ const credentials = await this.getDeckCredentialsForDeck(row.id);
496
+ const playbooks = await this.getDeckPlaybooksForDeck(row.id);
497
+ decks.push({
498
+ id: row.id,
499
+ name: row.name,
500
+ description: row.description,
501
+ isActive: Boolean(row.is_active),
502
+ services,
503
+ credentials,
504
+ playbooks,
505
+ createdAt: row.created_at,
506
+ updatedAt: row.updated_at,
507
+ });
508
+ }
509
+ return decks;
510
+ }
511
+ async getActiveDeck() {
512
+ const stmt = this.db.prepare('SELECT * FROM decks WHERE is_active = 1 LIMIT 1');
513
+ const row = stmt.get();
514
+ if (!row)
515
+ return null;
516
+ const deckServices = await this.getDeckServices(row.id);
517
+ const services = [];
518
+ for (const deckService of deckServices) {
519
+ const service = await this.getService(deckService.serviceId);
520
+ if (service) {
521
+ services.push(service);
522
+ }
523
+ }
524
+ const credentials = await this.getDeckCredentialsForDeck(row.id);
525
+ const playbooks = await this.getDeckPlaybooksForDeck(row.id);
526
+ return {
527
+ id: row.id,
528
+ name: row.name,
529
+ description: row.description,
530
+ isActive: Boolean(row.is_active),
531
+ services,
532
+ credentials,
533
+ playbooks,
534
+ createdAt: row.created_at,
535
+ updatedAt: row.updated_at,
536
+ };
537
+ }
538
+ async updateDeck(id, input) {
539
+ const existing = await this.getDeck(id);
540
+ if (!existing)
541
+ return null;
542
+ const updated = {
543
+ ...existing,
544
+ ...input,
545
+ updatedAt: new Date().toISOString(),
546
+ };
547
+ const stmt = this.db.prepare(`
548
+ UPDATE decks SET
549
+ name = @name, description = @description, is_active = @is_active, updated_at = @updated_at
550
+ WHERE id = @id
551
+ `);
552
+ stmt.run({
553
+ id: updated.id,
554
+ name: updated.name,
555
+ description: updated.description,
556
+ is_active: updated.isActive ? 1 : 0,
557
+ updated_at: updated.updatedAt,
558
+ });
559
+ return updated;
560
+ }
561
+ async deleteDeck(id) {
562
+ const stmt = this.db.prepare('DELETE FROM decks WHERE id = ?');
563
+ const result = stmt.run(id);
564
+ return result.changes > 0;
565
+ }
566
+ async setActiveDeck(id) {
567
+ // First, deactivate all decks
568
+ this.db.prepare('UPDATE decks SET is_active = 0').run();
569
+ // Then activate the specified deck
570
+ this.db.prepare('UPDATE decks SET is_active = 1 WHERE id = ?').run(id);
571
+ }
572
+ // Deck services operations
573
+ async getDeckServices(deckId) {
574
+ const stmt = this.db.prepare(`
575
+ SELECT deck_id, service_id, position FROM deck_services
576
+ WHERE deck_id = ?
577
+ ORDER BY position ASC
578
+ `);
579
+ const rows = stmt.all(deckId);
580
+ return rows.map(row => ({
581
+ deckId: row.deck_id,
582
+ serviceId: row.service_id,
583
+ position: row.position,
584
+ }));
585
+ }
586
+ async addServiceToDeck(input) {
587
+ // Get the next position
588
+ const positionStmt = this.db.prepare(`
589
+ SELECT COALESCE(MAX(position), -1) + 1 as next_position
590
+ FROM deck_services WHERE deck_id = ?
591
+ `);
592
+ const positionResult = positionStmt.get(input.deckId);
593
+ const position = input.position ?? positionResult.next_position;
594
+ const stmt = this.db.prepare(`
595
+ INSERT INTO deck_services (deck_id, service_id, position)
596
+ VALUES (@deck_id, @service_id, @position)
597
+ `);
598
+ stmt.run({
599
+ deck_id: input.deckId,
600
+ service_id: input.serviceId,
601
+ position,
602
+ });
603
+ }
604
+ async removeServiceFromDeck(input) {
605
+ const stmt = this.db.prepare(`
606
+ DELETE FROM deck_services
607
+ WHERE deck_id = @deck_id AND service_id = @service_id
608
+ `);
609
+ stmt.run({
610
+ deck_id: input.deckId,
611
+ service_id: input.serviceId,
612
+ });
613
+ }
614
+ async reorderDeckServices(input) {
615
+ const transaction = this.db.transaction(() => {
616
+ // Remove all services from the deck
617
+ this.db.prepare('DELETE FROM deck_services WHERE deck_id = ?').run(input.deckId);
618
+ // Add them back in the new order
619
+ const insertStmt = this.db.prepare(`
620
+ INSERT INTO deck_services (deck_id, service_id, position)
621
+ VALUES (@deck_id, @service_id, @position)
622
+ `);
623
+ input.serviceIds.forEach((serviceId, index) => {
624
+ insertStmt.run({
625
+ deck_id: input.deckId,
626
+ service_id: serviceId,
627
+ position: index,
628
+ });
629
+ });
630
+ });
631
+ transaction();
632
+ }
633
+ async clearDeckServices(deckId) {
634
+ const stmt = this.db.prepare('DELETE FROM deck_services WHERE deck_id = ?');
635
+ stmt.run(deckId);
636
+ }
637
+ // Credential operations
638
+ async createCredential(input) {
639
+ const now = new Date().toISOString();
640
+ const credential = {
641
+ ...input,
642
+ createdAt: now,
643
+ updatedAt: now,
644
+ };
645
+ const stmt = this.db.prepare(`
646
+ INSERT INTO credentials (
647
+ id, label, scheme, header_name, env_name, keychain_account, tags, docs_url, icon_url, created_at, updated_at
648
+ ) VALUES (
649
+ @id, @label, @scheme, @header_name, @env_name, @keychain_account, @tags, @docs_url, @icon_url, @created_at, @updated_at
650
+ )
651
+ `);
652
+ stmt.run({
653
+ id: credential.id,
654
+ label: credential.label,
655
+ scheme: credential.scheme,
656
+ header_name: credential.headerName ?? null,
657
+ env_name: credential.envName,
658
+ keychain_account: credential.keychainAccount,
659
+ tags: JSON.stringify(credential.tags ?? []),
660
+ docs_url: credential.docsUrl ?? null,
661
+ icon_url: credential.iconUrl ?? null,
662
+ created_at: credential.createdAt,
663
+ updated_at: credential.updatedAt,
664
+ });
665
+ return credential;
666
+ }
667
+ async getCredential(id) {
668
+ const stmt = this.db.prepare('SELECT * FROM credentials WHERE id = ?');
669
+ const row = stmt.get(id);
670
+ if (!row) {
671
+ return null;
672
+ }
673
+ return this.mapCredentialRow(row);
674
+ }
675
+ async getAllCredentials() {
676
+ const stmt = this.db.prepare('SELECT * FROM credentials ORDER BY created_at DESC');
677
+ const rows = stmt.all();
678
+ return rows.map((row) => this.mapCredentialRow(row));
679
+ }
680
+ async updateCredential(id, input) {
681
+ const existing = await this.getCredential(id);
682
+ if (!existing) {
683
+ return null;
684
+ }
685
+ const updated = {
686
+ ...existing,
687
+ ...input,
688
+ id: existing.id,
689
+ updatedAt: new Date().toISOString(),
690
+ };
691
+ const stmt = this.db.prepare(`
692
+ UPDATE credentials SET
693
+ label = @label,
694
+ scheme = @scheme,
695
+ header_name = @header_name,
696
+ env_name = @env_name,
697
+ keychain_account = @keychain_account,
698
+ tags = @tags,
699
+ docs_url = @docs_url,
700
+ icon_url = @icon_url,
701
+ updated_at = @updated_at
702
+ WHERE id = @id
703
+ `);
704
+ stmt.run({
705
+ id: updated.id,
706
+ label: updated.label,
707
+ scheme: updated.scheme,
708
+ header_name: updated.headerName ?? null,
709
+ env_name: updated.envName,
710
+ keychain_account: updated.keychainAccount,
711
+ tags: JSON.stringify(updated.tags ?? []),
712
+ docs_url: updated.docsUrl ?? null,
713
+ icon_url: updated.iconUrl ?? null,
714
+ updated_at: updated.updatedAt,
715
+ });
716
+ return updated;
717
+ }
718
+ async touchCredential(id) {
719
+ this.db.prepare('UPDATE credentials SET updated_at = ? WHERE id = ?').run(new Date().toISOString(), id);
720
+ }
721
+ async deleteCredential(id) {
722
+ const stmt = this.db.prepare('DELETE FROM credentials WHERE id = ?');
723
+ const result = stmt.run(id);
724
+ return result.changes > 0;
725
+ }
726
+ async getDeckCredentials(deckId) {
727
+ const stmt = this.db.prepare(`
728
+ SELECT deck_id, credential_id, position FROM deck_credentials
729
+ WHERE deck_id = ?
730
+ ORDER BY position ASC
731
+ `);
732
+ const rows = stmt.all(deckId);
733
+ return rows.map((row) => ({
734
+ deckId: row.deck_id,
735
+ credentialId: row.credential_id,
736
+ position: row.position,
737
+ }));
738
+ }
739
+ async addCredentialToDeck(input) {
740
+ const positionStmt = this.db.prepare(`
741
+ SELECT COALESCE(MAX(position), -1) + 1 as next_position
742
+ FROM deck_credentials WHERE deck_id = ?
743
+ `);
744
+ const positionResult = positionStmt.get(input.deckId);
745
+ const position = input.position ?? positionResult.next_position;
746
+ const stmt = this.db.prepare(`
747
+ INSERT INTO deck_credentials (deck_id, credential_id, position)
748
+ VALUES (@deck_id, @credential_id, @position)
749
+ `);
750
+ stmt.run({
751
+ deck_id: input.deckId,
752
+ credential_id: input.credentialId,
753
+ position,
754
+ });
755
+ }
756
+ async removeCredentialFromDeck(input) {
757
+ const stmt = this.db.prepare(`
758
+ DELETE FROM deck_credentials
759
+ WHERE deck_id = @deck_id AND credential_id = @credential_id
760
+ `);
761
+ stmt.run({
762
+ deck_id: input.deckId,
763
+ credential_id: input.credentialId,
764
+ });
765
+ }
766
+ async createExecRun(input) {
767
+ const execRun = {
768
+ id: (0, shared_1.generateId)(),
769
+ deckId: input.deckId,
770
+ manifestPath: input.manifestPath,
771
+ command: input.command,
772
+ credentialIds: input.credentialIds,
773
+ exitCode: input.exitCode,
774
+ startedAt: input.startedAt,
775
+ finishedAt: input.finishedAt,
776
+ };
777
+ const insertRun = this.db.prepare(`
778
+ INSERT INTO exec_runs (
779
+ id, deck_id, manifest_path, command, exit_code, started_at, finished_at
780
+ ) VALUES (
781
+ @id, @deck_id, @manifest_path, @command, @exit_code, @started_at, @finished_at
782
+ )
783
+ `);
784
+ const insertCredential = this.db.prepare(`
785
+ INSERT INTO exec_run_credentials (exec_run_id, credential_id)
786
+ VALUES (@exec_run_id, @credential_id)
787
+ `);
788
+ const transaction = this.db.transaction(() => {
789
+ insertRun.run({
790
+ id: execRun.id,
791
+ deck_id: execRun.deckId ?? null,
792
+ manifest_path: execRun.manifestPath ?? null,
793
+ command: execRun.command,
794
+ exit_code: execRun.exitCode ?? null,
795
+ started_at: execRun.startedAt,
796
+ finished_at: execRun.finishedAt ?? null,
797
+ });
798
+ for (const credentialId of input.credentialIds) {
799
+ insertCredential.run({
800
+ exec_run_id: execRun.id,
801
+ credential_id: credentialId,
802
+ });
803
+ }
804
+ });
805
+ transaction();
806
+ return execRun;
807
+ }
808
+ mapPlaybookRow(row) {
809
+ return {
810
+ id: row.id,
811
+ title: row.title,
812
+ body: row.body ?? '',
813
+ triggers: row.triggers ? JSON.parse(row.triggers) : [],
814
+ dependsOnCredentialIds: row.depends_on_credentials
815
+ ? JSON.parse(row.depends_on_credentials)
816
+ : [],
817
+ dependsOnServiceIds: row.depends_on_services ? JSON.parse(row.depends_on_services) : [],
818
+ exec: row.exec_command ?? undefined,
819
+ skill: row.skill_path ?? undefined,
820
+ createdAt: row.created_at,
821
+ updatedAt: row.updated_at,
822
+ };
823
+ }
824
+ async createPlaybook(input) {
825
+ const now = new Date().toISOString();
826
+ const playbook = {
827
+ id: input.id,
828
+ title: input.title,
829
+ body: input.body ?? '',
830
+ triggers: input.triggers ?? [],
831
+ dependsOnCredentialIds: input.dependsOnCredentialIds ?? [],
832
+ dependsOnServiceIds: input.dependsOnServiceIds ?? [],
833
+ exec: input.exec,
834
+ skill: input.skill,
835
+ createdAt: now,
836
+ updatedAt: now,
837
+ };
838
+ const stmt = this.db.prepare(`
839
+ INSERT INTO playbooks (
840
+ id, title, body, triggers, depends_on_credentials, depends_on_services,
841
+ exec_command, skill_path, created_at, updated_at
842
+ ) VALUES (
843
+ @id, @title, @body, @triggers, @depends_on_credentials, @depends_on_services,
844
+ @exec_command, @skill_path, @created_at, @updated_at
845
+ )
846
+ `);
847
+ stmt.run({
848
+ id: playbook.id,
849
+ title: playbook.title,
850
+ body: playbook.body,
851
+ triggers: JSON.stringify(playbook.triggers),
852
+ depends_on_credentials: JSON.stringify(playbook.dependsOnCredentialIds),
853
+ depends_on_services: JSON.stringify(playbook.dependsOnServiceIds),
854
+ exec_command: playbook.exec ?? null,
855
+ skill_path: playbook.skill ?? null,
856
+ created_at: playbook.createdAt,
857
+ updated_at: playbook.updatedAt,
858
+ });
859
+ return playbook;
860
+ }
861
+ async getPlaybook(id) {
862
+ const row = this.db.prepare('SELECT * FROM playbooks WHERE id = ?').get(id);
863
+ return row ? this.mapPlaybookRow(row) : null;
864
+ }
865
+ async getAllPlaybooks() {
866
+ const rows = this.db.prepare('SELECT * FROM playbooks ORDER BY created_at DESC').all();
867
+ return rows.map((row) => this.mapPlaybookRow(row));
868
+ }
869
+ async updatePlaybook(id, input) {
870
+ const existing = await this.getPlaybook(id);
871
+ if (!existing) {
872
+ return null;
873
+ }
874
+ const updated = {
875
+ ...existing,
876
+ ...input,
877
+ updatedAt: new Date().toISOString(),
878
+ };
879
+ this.db.prepare(`
880
+ UPDATE playbooks SET
881
+ title = @title,
882
+ body = @body,
883
+ triggers = @triggers,
884
+ depends_on_credentials = @depends_on_credentials,
885
+ depends_on_services = @depends_on_services,
886
+ exec_command = @exec_command,
887
+ skill_path = @skill_path,
888
+ updated_at = @updated_at
889
+ WHERE id = @id
890
+ `).run({
891
+ id: updated.id,
892
+ title: updated.title,
893
+ body: updated.body,
894
+ triggers: JSON.stringify(updated.triggers),
895
+ depends_on_credentials: JSON.stringify(updated.dependsOnCredentialIds),
896
+ depends_on_services: JSON.stringify(updated.dependsOnServiceIds),
897
+ exec_command: updated.exec ?? null,
898
+ skill_path: updated.skill ?? null,
899
+ updated_at: updated.updatedAt,
900
+ });
901
+ return updated;
902
+ }
903
+ async deletePlaybook(id) {
904
+ const result = this.db.prepare('DELETE FROM playbooks WHERE id = ?').run(id);
905
+ return result.changes > 0;
906
+ }
907
+ async getPlaybooksDependingOnCredential(credentialId) {
908
+ return (await this.getAllPlaybooks()).filter((playbook) => playbook.dependsOnCredentialIds.includes(credentialId));
909
+ }
910
+ async getPlaybooksDependingOnService(serviceId) {
911
+ return (await this.getAllPlaybooks()).filter((playbook) => playbook.dependsOnServiceIds.includes(serviceId));
912
+ }
913
+ async getDeckPlaybooksForDeck(deckId) {
914
+ const rows = this.db.prepare(`
915
+ SELECT p.*, dp.position
916
+ FROM deck_playbooks dp
917
+ JOIN playbooks p ON p.id = dp.playbook_id
918
+ WHERE dp.deck_id = ?
919
+ ORDER BY dp.position ASC
920
+ `).all(deckId);
921
+ return rows.map((row) => this.mapPlaybookRow(row));
922
+ }
923
+ async addPlaybookToDeck(input) {
924
+ const positionResult = this.db.prepare(`
925
+ SELECT COALESCE(MAX(position), -1) + 1 as next_position
926
+ FROM deck_playbooks WHERE deck_id = ?
927
+ `).get(input.deckId);
928
+ const position = input.position ?? positionResult.next_position;
929
+ this.db.prepare(`
930
+ INSERT INTO deck_playbooks (deck_id, playbook_id, position)
931
+ VALUES (@deck_id, @playbook_id, @position)
932
+ `).run({
933
+ deck_id: input.deckId,
934
+ playbook_id: input.playbookId,
935
+ position,
936
+ });
937
+ }
938
+ async removePlaybookFromDeck(input) {
939
+ this.db.prepare(`
940
+ DELETE FROM deck_playbooks
941
+ WHERE deck_id = @deck_id AND playbook_id = @playbook_id
942
+ `).run({
943
+ deck_id: input.deckId,
944
+ playbook_id: input.playbookId,
945
+ });
946
+ }
947
+ async getDeckPlaybooks(deckId) {
948
+ const rows = this.db.prepare(`
949
+ SELECT deck_id, playbook_id, position FROM deck_playbooks
950
+ WHERE deck_id = ?
951
+ ORDER BY position ASC
952
+ `).all(deckId);
953
+ return rows.map((row) => ({
954
+ deckId: row.deck_id,
955
+ playbookId: row.playbook_id,
956
+ position: row.position,
957
+ }));
958
+ }
959
+ // Cleanup
960
+ close() {
961
+ this.db.close();
962
+ }
963
+ }
964
+ exports.DatabaseManager = DatabaseManager;
965
+ //# sourceMappingURL=database.js.map