@proletariat/cli 0.3.87 → 0.3.88

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 (96) hide show
  1. package/dist/commands/init.d.ts +6 -1
  2. package/dist/commands/init.js +44 -89
  3. package/dist/commands/init.js.map +1 -1
  4. package/dist/commands/new.d.ts +30 -2
  5. package/dist/commands/new.js +141 -9
  6. package/dist/commands/new.js.map +1 -1
  7. package/dist/hooks/init.js +3 -6
  8. package/dist/hooks/init.js.map +1 -1
  9. package/dist/lib/asana/config.d.ts +1 -1
  10. package/dist/lib/asana/config.js +18 -30
  11. package/dist/lib/asana/config.js.map +1 -1
  12. package/dist/lib/asana/mapper.d.ts +4 -3
  13. package/dist/lib/asana/mapper.js +17 -10
  14. package/dist/lib/asana/mapper.js.map +1 -1
  15. package/dist/lib/database/agents.d.ts +73 -0
  16. package/dist/lib/database/agents.js +341 -0
  17. package/dist/lib/database/agents.js.map +1 -0
  18. package/dist/lib/database/driver.d.ts +115 -0
  19. package/dist/lib/database/driver.js +109 -0
  20. package/dist/lib/database/driver.js.map +1 -0
  21. package/dist/lib/database/index.d.ts +25 -287
  22. package/dist/lib/database/index.js +35 -1067
  23. package/dist/lib/database/index.js.map +1 -1
  24. package/dist/lib/database/media.d.ts +53 -0
  25. package/dist/lib/database/media.js +118 -0
  26. package/dist/lib/database/media.js.map +1 -0
  27. package/dist/lib/database/pmo-bootstrap.d.ts +30 -0
  28. package/dist/lib/database/pmo-bootstrap.js +105 -0
  29. package/dist/lib/database/pmo-bootstrap.js.map +1 -0
  30. package/dist/lib/database/repositories.d.ts +26 -0
  31. package/dist/lib/database/repositories.js +56 -0
  32. package/dist/lib/database/repositories.js.map +1 -0
  33. package/dist/lib/database/settings-store.d.ts +60 -0
  34. package/dist/lib/database/settings-store.js +87 -0
  35. package/dist/lib/database/settings-store.js.map +1 -0
  36. package/dist/lib/database/themes.d.ts +59 -0
  37. package/dist/lib/database/themes.js +212 -0
  38. package/dist/lib/database/themes.js.map +1 -0
  39. package/dist/lib/database/workspace.d.ts +46 -0
  40. package/dist/lib/database/workspace.js +158 -0
  41. package/dist/lib/database/workspace.js.map +1 -0
  42. package/dist/lib/database/worktrees.d.ts +33 -0
  43. package/dist/lib/database/worktrees.js +60 -0
  44. package/dist/lib/database/worktrees.js.map +1 -0
  45. package/dist/lib/execution/config.d.ts +1 -1
  46. package/dist/lib/execution/config.js +7 -17
  47. package/dist/lib/execution/config.js.map +1 -1
  48. package/dist/lib/execution/spawner.d.ts +1 -1
  49. package/dist/lib/execution/storage.d.ts +4 -3
  50. package/dist/lib/execution/storage.js +11 -4
  51. package/dist/lib/execution/storage.js.map +1 -1
  52. package/dist/lib/external-issues/mapping-store.d.ts +4 -3
  53. package/dist/lib/external-issues/mapping-store.js +21 -13
  54. package/dist/lib/external-issues/mapping-store.js.map +1 -1
  55. package/dist/lib/external-issues/outbound-sync.d.ts +1 -1
  56. package/dist/lib/jira/config.d.ts +1 -6
  57. package/dist/lib/jira/config.js +16 -33
  58. package/dist/lib/jira/config.js.map +1 -1
  59. package/dist/lib/linear/config.d.ts +1 -1
  60. package/dist/lib/linear/config.js +16 -38
  61. package/dist/lib/linear/config.js.map +1 -1
  62. package/dist/lib/linear/mapper.d.ts +4 -3
  63. package/dist/lib/linear/mapper.js +20 -13
  64. package/dist/lib/linear/mapper.js.map +1 -1
  65. package/dist/lib/monday/config.d.ts +1 -1
  66. package/dist/lib/monday/config.js +16 -32
  67. package/dist/lib/monday/config.js.map +1 -1
  68. package/dist/lib/monday/mapper.d.ts +4 -3
  69. package/dist/lib/monday/mapper.js +19 -12
  70. package/dist/lib/monday/mapper.js.map +1 -1
  71. package/dist/lib/onboarding/wizard.d.ts +2 -2
  72. package/dist/lib/onboarding/wizard.js +32 -24
  73. package/dist/lib/onboarding/wizard.js.map +1 -1
  74. package/dist/lib/pmo/diet.d.ts +1 -1
  75. package/dist/lib/pmo/storage/index.d.ts +8 -0
  76. package/dist/lib/pmo/storage/index.js +13 -1
  77. package/dist/lib/pmo/storage/index.js.map +1 -1
  78. package/dist/lib/pmo/storage/types.d.ts +4 -1
  79. package/dist/lib/shortcut/config.d.ts +1 -7
  80. package/dist/lib/shortcut/config.js +13 -32
  81. package/dist/lib/shortcut/config.js.map +1 -1
  82. package/dist/lib/trello/config.d.ts +1 -26
  83. package/dist/lib/trello/config.js +23 -64
  84. package/dist/lib/trello/config.js.map +1 -1
  85. package/dist/lib/trello/mapper.d.ts +4 -3
  86. package/dist/lib/trello/mapper.js +17 -10
  87. package/dist/lib/trello/mapper.js.map +1 -1
  88. package/dist/lib/work-lifecycle/post-execution.d.ts +1 -1
  89. package/dist/lib/work-source/config.d.ts +1 -1
  90. package/dist/lib/work-source/config.js +14 -24
  91. package/dist/lib/work-source/config.js.map +1 -1
  92. package/dist/lib/work-source/provider-sources.d.ts +1 -1
  93. package/dist/lib/work-source/provider-sources.js +8 -20
  94. package/dist/lib/work-source/provider-sources.js.map +1 -1
  95. package/oclif.manifest.json +1461 -1412
  96. package/package.json +1 -1
@@ -1,1070 +1,38 @@
1
- import Database from 'better-sqlite3';
2
- import { eq, and, or, isNull, sql, asc, desc, like } from 'drizzle-orm';
3
- import * as fs from 'node:fs';
4
- import * as path from 'node:path';
5
- import { getThemePersistentDir, isEphemeralAgentName } from '../themes.js';
6
- import { throwIfNativeBindingError } from './native-validation.js';
7
- import { runDrizzleMigrations } from './migrator.js';
8
- import { ALL_MIGRATIONS } from './migrations/index.js';
9
- import { createDrizzleConnection } from './drizzle.js';
10
- import { workspace as workspaceTable, repositories as repositoriesTable, agents as agentsTable, agentThemes as agentThemesTable, agentThemeNames as agentThemeNamesTable, agentWorktrees as agentWorktreesTable, workspaceSettings as workspaceSettingsTable, mediaItems as mediaItemsTable, } from './drizzle-schema.js';
11
- // Re-export CREATE_TABLES_SQL from its canonical location
12
- export { CREATE_TABLES_SQL } from './workspace-schema.js';
13
- // =============================================================================
14
- // Internal helpers
15
- // =============================================================================
16
- /**
17
- * Open the workspace database, wrap it with Drizzle, run a function,
18
- * and close the connection. Handles the open/close lifecycle.
19
- */
20
- function withDrizzle(workspacePath, fn) {
21
- const sqliteDb = openWorkspaceDatabase(workspacePath);
22
- const ddb = createDrizzleConnection(sqliteDb);
23
- try {
24
- return fn(ddb, sqliteDb);
25
- }
26
- finally {
27
- sqliteDb.close();
28
- }
29
- }
30
- /**
31
- * Map a Drizzle agent row to the Agent interface.
32
- * Handles default values for backwards compatibility with old databases.
33
- */
34
- function toAgent(row) {
35
- return {
36
- name: row.name,
37
- type: (row.type || 'persistent'),
38
- status: (row.status || 'active'),
39
- base_name: row.baseName,
40
- theme_id: row.themeId,
41
- worktree_path: row.worktreePath,
42
- mount_mode: (row.mountMode || 'worktree'),
43
- created_at: row.createdAt,
44
- cleaned_at: row.cleanedAt,
45
- };
46
- }
47
- /**
48
- * Map a Drizzle theme row to the AgentTheme interface.
49
- */
50
- function toAgentTheme(row) {
51
- return {
52
- id: row.id,
53
- name: row.name,
54
- display_name: row.displayName,
55
- description: row.description,
56
- builtin: Boolean(row.builtin),
57
- created_at: row.createdAt,
58
- };
59
- }
60
- /**
61
- * Ensure ephemeral agents are correctly typed based on their worktree path or naming pattern.
62
- * Uses raw SQL because it relies on SQLite-specific GLOB operator and sqlite_master introspection.
63
- */
64
- function ensureEphemeralAgentTypes(db) {
65
- // Check if agents table exists (sqlite_master introspection — no Drizzle equivalent)
66
- const tableExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='agents'").get();
67
- if (!tableExists) {
68
- return;
69
- }
70
- // Agents in temp directory should be ephemeral
71
- db.exec("UPDATE agents SET type = 'ephemeral' WHERE worktree_path LIKE 'agents/temp/%' AND type != 'ephemeral'");
72
- // Detect ephemeral agents by naming pattern using SQLite GLOB (no Drizzle equivalent)
73
- db.exec(`
74
- UPDATE agents SET type = 'ephemeral'
75
- WHERE type != 'ephemeral'
76
- AND name GLOB '*-*-[0-9]*'
77
- `);
78
- // Also detect numberless ephemeral names (e.g., bold-bezos) using isEphemeralAgentName()
79
- const potentialEphemeral = db.prepare(`
80
- SELECT name FROM agents
81
- WHERE type != 'ephemeral'
82
- AND name LIKE '%-%'
83
- AND name NOT GLOB '*-*-[0-9]*'
84
- `).all();
85
- const updateStmt = db.prepare("UPDATE agents SET type = 'ephemeral' WHERE name = ?");
86
- for (const agent of potentialEphemeral) {
87
- if (isEphemeralAgentName(agent.name)) {
88
- updateStmt.run(agent.name);
89
- }
90
- }
91
- }
92
- /**
93
- * Get the database path for a workspace
94
- */
95
- export function getDatabasePath(workspacePath) {
96
- return path.join(workspacePath, '.proletariat', 'workspace.db');
97
- }
98
- /**
99
- * Get the config path for a workspace
100
- */
101
- export function getConfigPath(workspacePath) {
102
- return path.join(workspacePath, '.proletariat', 'config.json');
103
- }
104
- /**
105
- * Open workspace database connection
106
- */
107
- export function openWorkspaceDatabase(workspacePath) {
108
- const dbPath = getDatabasePath(workspacePath);
109
- if (!fs.existsSync(dbPath)) {
110
- throw new Error(`Database not found: ${dbPath}. Run 'prlt new' first.`);
111
- }
112
- let db;
113
- try {
114
- db = new Database(dbPath);
115
- }
116
- catch (error) {
117
- throwIfNativeBindingError(error, 'openWorkspaceDatabase');
118
- throw error;
119
- }
120
- db.pragma('foreign_keys = ON');
121
- db.pragma('busy_timeout = 5000'); // Wait up to 5 seconds if database is locked
122
- // Run Drizzle migrations (creates tracking table, applies pending migrations)
123
- runDrizzleMigrations(db, ALL_MIGRATIONS);
124
- // Ensure ephemeral agents are correctly typed (raw SQL — uses SQLite GLOB)
125
- ensureEphemeralAgentTypes(db);
126
- return db;
127
- }
128
- /**
129
- * Create and initialize workspace database
130
- */
131
- export function createWorkspaceDatabase(workspacePath, type, workspaceName, hasPMO = false) {
132
- const dbPath = getDatabasePath(workspacePath);
133
- const configPath = getConfigPath(workspacePath);
134
- // Ensure .proletariat directory exists
135
- const proletariatDir = path.dirname(dbPath);
136
- if (!fs.existsSync(proletariatDir)) {
137
- fs.mkdirSync(proletariatDir, { recursive: true });
138
- }
139
- // Create minimal config.json (bootstrap only)
140
- const bootstrapConfig = {
141
- version: "1.0.0",
142
- schemaVersion: 1
143
- };
144
- fs.writeFileSync(configPath, JSON.stringify(bootstrapConfig, null, 2));
145
- // Create and setup SQLite database
146
- let db;
147
- try {
148
- db = new Database(dbPath);
149
- }
150
- catch (error) {
151
- throwIfNativeBindingError(error, 'createWorkspaceDatabase');
152
- throw error;
153
- }
154
- // Enable foreign keys
155
- db.pragma('foreign_keys = ON');
156
- // Run all migrations (baseline creates core workspace + PMO tables)
157
- runDrizzleMigrations(db, ALL_MIGRATIONS);
158
- // Insert workspace data using Drizzle
159
- const ddb = createDrizzleConnection(db);
160
- ddb.insert(workspaceTable).values({
161
- id: 1,
162
- type,
163
- workspaceName,
164
- hasPmo: hasPMO,
165
- createdAt: new Date().toISOString(),
166
- }).run();
167
- return db;
168
- }
169
- /**
170
- * Get workspace configuration
171
- */
172
- export function getWorkspaceConfig(workspacePath) {
173
- try {
174
- return withDrizzle(workspacePath, (ddb) => {
175
- const row = ddb.select().from(workspaceTable).limit(1).get();
176
- if (!row)
177
- return null;
178
- return {
179
- id: row.id ?? 1,
180
- type: row.type,
181
- workspace_name: row.workspaceName,
182
- has_pmo: Boolean(row.hasPmo),
183
- active_theme_id: row.activeThemeId,
184
- created_at: row.createdAt,
185
- };
186
- });
187
- }
188
- catch {
189
- return null;
190
- }
191
- }
192
- /**
193
- * Get the active theme for a workspace
194
- * Auto-detects theme from existing agents if not explicitly set
195
- */
196
- export function getActiveTheme(workspacePath) {
197
- const config = getWorkspaceConfig(workspacePath);
198
- // If explicitly set, use that
199
- if (config?.active_theme_id) {
200
- return getTheme(workspacePath, config.active_theme_id);
201
- }
202
- // Auto-detect from existing agents
203
- const agentList = getWorkspaceAgents(workspacePath);
204
- if (agentList.length === 0) {
205
- return null;
206
- }
207
- // Check if any agent has a theme_id set
208
- const themedAgent = agentList.find(a => a.theme_id);
209
- if (themedAgent?.theme_id) {
210
- const theme = getTheme(workspacePath, themedAgent.theme_id);
211
- if (theme) {
212
- // Auto-set it for future use
213
- setActiveTheme(workspacePath, themedAgent.theme_id);
214
- return theme;
215
- }
216
- }
217
- // Check if agent names match any builtin theme
218
- const themes = getThemes(workspacePath);
219
- for (const theme of themes) {
220
- const themeNames = getThemeNames(workspacePath, theme.id);
221
- const themeNameSet = new Set(themeNames.map(n => n.name.toLowerCase()));
222
- // If any existing agent matches this theme's names
223
- const matchingAgent = agentList.find(a => themeNameSet.has(a.name.toLowerCase()));
224
- if (matchingAgent) {
225
- // Auto-set it for future use
226
- setActiveTheme(workspacePath, theme.id);
227
- return theme;
228
- }
229
- }
230
- return null;
231
- }
232
- /**
233
- * Set the active theme for a workspace
234
- */
235
- export function setActiveTheme(workspacePath, themeId) {
236
- withDrizzle(workspacePath, (ddb) => {
237
- if (themeId) {
238
- // Validate theme exists
239
- const theme = ddb.select({ id: agentThemesTable.id })
240
- .from(agentThemesTable)
241
- .where(eq(agentThemesTable.id, themeId))
242
- .get();
243
- if (!theme) {
244
- throw new Error(`Theme "${themeId}" not found`);
245
- }
246
- }
247
- ddb.update(workspaceTable)
248
- .set({ activeThemeId: themeId })
249
- .where(eq(workspaceTable.id, 1))
250
- .run();
251
- });
252
- }
253
- /**
254
- * Add repositories to database
255
- */
256
- export function addRepositoriesToDatabase(workspacePath, repos) {
257
- withDrizzle(workspacePath, (ddb) => {
258
- for (const repo of repos) {
259
- ddb.insert(repositoriesTable)
260
- .values({
261
- name: repo.name,
262
- path: repo.path,
263
- type: 'main',
264
- sourceUrl: repo.source_url || null,
265
- action: repo.action || null,
266
- addedAt: new Date().toISOString(),
267
- })
268
- .onConflictDoUpdate({
269
- target: repositoriesTable.name,
270
- set: {
271
- path: repo.path,
272
- type: 'main',
273
- sourceUrl: repo.source_url || null,
274
- action: repo.action || null,
275
- addedAt: new Date().toISOString(),
276
- },
277
- })
278
- .run();
279
- }
280
- });
281
- }
282
- /**
283
- * Add agents to database (case-insensitive uniqueness)
284
- */
285
- export function addAgentsToDatabase(workspacePath, agentNames, themeId, mountMode = 'worktree') {
286
- withDrizzle(workspacePath, (ddb, sqliteDb) => {
287
- // Get workspace config to determine paths
288
- const wsRow = ddb.select().from(workspaceTable).get();
289
- if (!wsRow)
290
- throw new Error('No workspace config found');
291
- // Get all repos for this workspace
292
- const repos = ddb.select({ name: repositoriesTable.name }).from(repositoriesTable).all();
293
- // Determine the effective theme ID (provided or active theme)
294
- const effectiveThemeId = themeId || wsRow.activeThemeId || undefined;
295
- const persistentDir = getThemePersistentDir(effectiveThemeId);
296
- const transaction = sqliteDb.transaction(() => {
297
- for (const agentName of agentNames) {
298
- // Check for existing agents (case-insensitive) via Drizzle sql
299
- const existing = ddb.select({ name: agentsTable.name })
300
- .from(agentsTable)
301
- .where(sql `LOWER(${agentsTable.name}) = LOWER(${agentName})`)
302
- .get();
303
- if (existing) {
304
- continue; // Agent already exists with same name (different case)
305
- }
306
- const now = new Date().toISOString();
307
- // Determine worktree path for the agent
308
- const agentWorktreePath = wsRow.type === 'hq'
309
- ? `agents/${persistentDir}/${agentName}`
310
- : agentName;
311
- // Add agent (persistent type for manually added agents)
312
- ddb.insert(agentsTable).values({
313
- name: agentName,
314
- type: 'persistent',
315
- baseName: null,
316
- themeId: effectiveThemeId || null,
317
- worktreePath: agentWorktreePath,
318
- mountMode,
319
- createdAt: now,
320
- }).onConflictDoUpdate({
321
- target: agentsTable.name,
322
- set: {
323
- type: 'persistent',
324
- baseName: null,
325
- themeId: effectiveThemeId || null,
326
- worktreePath: agentWorktreePath,
327
- mountMode,
328
- createdAt: now,
329
- },
330
- }).run();
331
- // Add worktrees for all repos
332
- for (const repo of repos) {
333
- const worktreePath = wsRow.type === 'hq'
334
- ? `agents/${persistentDir}/${agentName}/${repo.name}`
335
- : `${agentName}/${repo.name}`;
336
- ddb.insert(agentWorktreesTable).values({
337
- agentName,
338
- repoName: repo.name,
339
- worktreePath,
340
- branch: `agent-${agentName}`,
341
- createdAt: now,
342
- }).onConflictDoUpdate({
343
- target: [agentWorktreesTable.agentName, agentWorktreesTable.repoName],
344
- set: {
345
- worktreePath,
346
- branch: `agent-${agentName}`,
347
- createdAt: now,
348
- },
349
- }).run();
350
- }
351
- }
352
- });
353
- transaction();
354
- });
355
- }
356
- /**
357
- * Add an ephemeral agent to the database.
358
- * Throws on name collision — use tryAddEphemeralAgentToDatabase for
359
- * concurrency-safe insertion with conflict detection.
360
- */
361
- export function addEphemeralAgentToDatabase(workspacePath, agentName, baseName, themeId, mountMode = 'worktree') {
362
- const result = tryAddEphemeralAgentToDatabase(workspacePath, agentName, baseName, themeId, mountMode);
363
- if (!result) {
364
- throw new Error(`Agent name "${agentName}" already exists (UNIQUE constraint failed: agents.name)`);
365
- }
366
- return result;
367
- }
368
- /**
369
- * Try to add an ephemeral agent to the database.
370
- * Returns the Agent on success, or null if the name already exists
371
- * (SQLITE_CONSTRAINT_PRIMARYKEY). This is concurrency-safe: parallel
372
- * processes that generate the same name will not crash — the loser
373
- * simply gets null and can retry with a different name.
374
- */
375
- export function tryAddEphemeralAgentToDatabase(workspacePath, agentName, baseName, themeId, mountMode = 'worktree') {
376
- const sqliteDb = openWorkspaceDatabase(workspacePath);
377
- const ddb = createDrizzleConnection(sqliteDb);
378
- try {
379
- const now = new Date().toISOString();
380
- const worktreePath = `agents/temp/${agentName}`;
381
- ddb.insert(agentsTable).values({
382
- name: agentName,
383
- type: 'ephemeral',
384
- status: 'active',
385
- baseName,
386
- themeId: themeId || null,
387
- worktreePath,
388
- mountMode,
389
- createdAt: now,
390
- }).run();
391
- const agent = ddb.select().from(agentsTable)
392
- .where(eq(agentsTable.name, agentName))
393
- .get();
394
- if (!agent)
395
- return null;
396
- return toAgent(agent);
397
- }
398
- catch (err) {
399
- const sqliteErr = err;
400
- if (sqliteErr.code === 'SQLITE_CONSTRAINT_PRIMARYKEY' || sqliteErr.code === 'SQLITE_CONSTRAINT_UNIQUE') {
401
- return null;
402
- }
403
- throw err;
404
- }
405
- finally {
406
- sqliteDb.close();
407
- }
408
- }
409
- /**
410
- * Get all ephemeral agent names from the database
411
- */
412
- export function getEphemeralAgentNames(workspacePath) {
413
- return withDrizzle(workspacePath, (ddb) => {
414
- const rows = ddb.select({ name: agentsTable.name })
415
- .from(agentsTable)
416
- .where(eq(agentsTable.type, 'ephemeral'))
417
- .all();
418
- return new Set(rows.map(a => a.name.toLowerCase()));
419
- });
420
- }
421
- /**
422
- * Remove an ephemeral agent from the database
423
- */
424
- export function removeEphemeralAgent(workspacePath, agentName) {
425
- withDrizzle(workspacePath, (ddb) => {
426
- ddb.delete(agentsTable)
427
- .where(and(eq(agentsTable.name, agentName), eq(agentsTable.type, 'ephemeral')))
428
- .run();
429
- });
430
- }
431
- /**
432
- * Get all agents in workspace
433
- */
434
- export function getWorkspaceAgents(workspacePath, includeCleanedUp = false) {
435
- return withDrizzle(workspacePath, (ddb) => {
436
- let rows;
437
- if (includeCleanedUp) {
438
- rows = ddb.select().from(agentsTable)
439
- .orderBy(asc(agentsTable.createdAt))
440
- .all();
441
- }
442
- else {
443
- rows = ddb.select().from(agentsTable)
444
- .where(or(eq(agentsTable.status, 'active'), isNull(agentsTable.status)))
445
- .orderBy(asc(agentsTable.createdAt))
446
- .all();
447
- }
448
- return rows.map(toAgent);
449
- });
450
- }
451
- /**
452
- * Get an agent by directory path.
453
- * Looks up agent where the given absolute path is inside the agent's worktree.
454
- * Returns null if no matching agent found.
455
- */
456
- export function getAgentByPath(workspacePath, absolutePath) {
457
- // Normalize paths
458
- const normalizedWorkspace = path.resolve(workspacePath);
459
- const normalizedPath = path.resolve(absolutePath);
460
- // Path must be inside workspace
461
- if (!normalizedPath.startsWith(normalizedWorkspace)) {
462
- return null;
463
- }
464
- // Get relative path from workspace root
465
- const relativePath = path.relative(normalizedWorkspace, normalizedPath);
466
- return withDrizzle(workspacePath, (ddb) => {
467
- const rows = ddb.select().from(agentsTable)
468
- .where(or(eq(agentsTable.status, 'active'), isNull(agentsTable.status)))
469
- .all();
470
- // Find agent whose worktree_path matches or contains the relative path
471
- for (const row of rows) {
472
- if (row.worktreePath) {
473
- if (relativePath === row.worktreePath || relativePath.startsWith(row.worktreePath + '/')) {
474
- return toAgent(row);
475
- }
476
- }
477
- }
478
- return null;
479
- });
480
- }
481
- /**
482
- * Mark an agent as cleaned up (keeps the record for history)
483
- */
484
- export function markAgentCleaned(workspacePath, agentName) {
485
- withDrizzle(workspacePath, (ddb) => {
486
- ddb.update(agentsTable)
487
- .set({ status: 'cleaned', cleanedAt: new Date().toISOString() })
488
- .where(eq(agentsTable.name, agentName))
489
- .run();
490
- });
491
- }
492
- /**
493
- * Sync agents in database with what exists on disk.
494
- * Marks agents as 'cleaned' if their directory no longer exists.
495
- * Returns list of agents that were cleaned up.
496
- */
497
- export function syncAgentsWithDisk(workspacePath) {
498
- const agentList = getWorkspaceAgents(workspacePath, false); // Only active agents
499
- const cleanedAgents = [];
500
- for (const agent of agentList) {
501
- // Determine expected directory path
502
- let agentDir;
503
- if (agent.worktree_path) {
504
- agentDir = path.join(workspacePath, agent.worktree_path);
505
- }
506
- else if (agent.type === 'ephemeral') {
507
- agentDir = path.join(workspacePath, 'agents', 'temp', agent.name);
508
- }
509
- else {
510
- agentDir = path.join(workspacePath, 'agents', 'staff', agent.name);
511
- }
512
- // If directory doesn't exist, mark agent as cleaned
513
- if (!fs.existsSync(agentDir)) {
514
- markAgentCleaned(workspacePath, agent.name);
515
- cleanedAgents.push(agent.name);
516
- }
517
- }
518
- return cleanedAgents;
519
- }
520
1
  /**
521
- * Discover agents on disk that aren't in the database and register them.
522
- * Also cleans up agents in DB whose directories no longer exist.
523
- * Returns both discovered and cleaned agents.
2
+ * Database Module - Re-export Facade
3
+ *
4
+ * This module re-exports all database operations from domain-specific modules.
5
+ * Consumers should import from this index for backward compatibility.
6
+ *
7
+ * Domain modules:
8
+ * - workspace.ts - Database lifecycle (open, create, config)
9
+ * - agents.ts - Agent CRUD operations
10
+ * - repositories.ts - Repository operations
11
+ * - themes.ts - Agent naming theme operations
12
+ * - worktrees.ts - Agent worktree queries
13
+ * - media.ts - Media item operations
14
+ * - pmo-bootstrap.ts - PMO initialization/teardown
15
+ * - driver.ts - DatabaseDriver abstraction layer
16
+ * - settings-store.ts - Workspace settings key-value store
524
17
  */
525
- export function discoverAgentsOnDisk(workspacePath) {
526
- const result = { discovered: [], cleaned: [] };
527
- // First, clean up missing agents
528
- result.cleaned = syncAgentsWithDisk(workspacePath);
529
- // Get existing ACTIVE agents from DB (case-insensitive lookup)
530
- const activeAgents = getWorkspaceAgents(workspacePath, false); // Only active agents
531
- const activeNames = new Set(activeAgents.map(a => a.name.toLowerCase()));
532
- // Get ALL agents including cleaned (for reactivation)
533
- const allAgents = getWorkspaceAgents(workspacePath, true);
534
- const cleanedAgentsMap = new Map(allAgents.filter(a => a.status === 'cleaned').map(a => [a.name.toLowerCase(), a]));
535
- withDrizzle(workspacePath, (ddb) => {
536
- // Scan staff directory
537
- const staffDir = path.join(workspacePath, 'agents', 'staff');
538
- if (fs.existsSync(staffDir)) {
539
- const staffEntries = fs.readdirSync(staffDir, { withFileTypes: true });
540
- for (const entry of staffEntries) {
541
- if (entry.isDirectory() && !entry.name.startsWith('.')) {
542
- const nameLower = entry.name.toLowerCase();
543
- if (!activeNames.has(nameLower)) {
544
- const worktreePath = `agents/staff/${entry.name}`;
545
- const now = new Date().toISOString();
546
- const cleanedAgent = cleanedAgentsMap.get(nameLower);
547
- if (cleanedAgent) {
548
- // Reactivate the cleaned agent
549
- ddb.update(agentsTable)
550
- .set({ status: 'active', cleanedAt: null, worktreePath })
551
- .where(sql `LOWER(${agentsTable.name}) = LOWER(${entry.name})`)
552
- .run();
553
- }
554
- else {
555
- // Register new agent
556
- ddb.insert(agentsTable).values({
557
- name: entry.name,
558
- type: 'persistent',
559
- status: 'active',
560
- worktreePath,
561
- mountMode: 'worktree',
562
- createdAt: now,
563
- }).run();
564
- }
565
- result.discovered.push({ name: entry.name, type: 'persistent', path: worktreePath });
566
- activeNames.add(nameLower);
567
- }
568
- }
569
- }
570
- }
571
- // Scan temp directory
572
- const tempDir = path.join(workspacePath, 'agents', 'temp');
573
- if (fs.existsSync(tempDir)) {
574
- const tempEntries = fs.readdirSync(tempDir, { withFileTypes: true });
575
- for (const entry of tempEntries) {
576
- if (entry.isDirectory() && !entry.name.startsWith('.')) {
577
- const nameLower = entry.name.toLowerCase();
578
- if (!activeNames.has(nameLower)) {
579
- const worktreePath = `agents/temp/${entry.name}`;
580
- const now = new Date().toISOString();
581
- const cleanedAgent = cleanedAgentsMap.get(nameLower);
582
- if (cleanedAgent) {
583
- // Reactivate the cleaned agent
584
- ddb.update(agentsTable)
585
- .set({ status: 'active', cleanedAt: null, worktreePath })
586
- .where(sql `LOWER(${agentsTable.name}) = LOWER(${entry.name})`)
587
- .run();
588
- }
589
- else {
590
- // Register new agent
591
- ddb.insert(agentsTable).values({
592
- name: entry.name,
593
- type: 'ephemeral',
594
- status: 'active',
595
- worktreePath,
596
- mountMode: 'worktree',
597
- createdAt: now,
598
- }).run();
599
- }
600
- result.discovered.push({ name: entry.name, type: 'ephemeral', path: worktreePath });
601
- activeNames.add(nameLower);
602
- }
603
- }
604
- }
605
- }
606
- });
607
- return result;
608
- }
609
- /**
610
- * Get all repositories in workspace
611
- */
612
- export function getWorkspaceRepositories(workspacePath) {
613
- return withDrizzle(workspacePath, (ddb) => {
614
- const rows = ddb.select().from(repositoriesTable)
615
- .orderBy(asc(repositoriesTable.addedAt))
616
- .all();
617
- return rows.map(row => ({
618
- name: row.name,
619
- path: row.path,
620
- type: (row.type || 'main'),
621
- source_url: row.sourceUrl ?? undefined,
622
- action: (row.action ?? undefined),
623
- added_at: row.addedAt,
624
- }));
625
- });
626
- }
627
- /**
628
- * Get worktrees for a specific agent
629
- */
630
- export function getAgentWorktrees(workspacePath, agentName) {
631
- return withDrizzle(workspacePath, (ddb) => {
632
- const rows = ddb.select().from(agentWorktreesTable)
633
- .where(eq(agentWorktreesTable.agentName, agentName))
634
- .all();
635
- return rows.map(row => ({
636
- agent_name: row.agentName,
637
- repo_name: row.repoName,
638
- worktree_path: row.worktreePath,
639
- branch: row.branch,
640
- created_at: row.createdAt,
641
- last_commit_hash: row.lastCommitHash ?? undefined,
642
- commits_ahead: row.commitsAhead,
643
- is_clean: Boolean(row.isClean),
644
- last_checked: row.lastChecked ?? undefined,
645
- }));
646
- });
647
- }
648
- /**
649
- * Find agent worktrees matching a branch pattern (case-insensitive LIKE).
650
- */
651
- export function findWorktreesByBranch(workspacePath, branchPattern) {
652
- return withDrizzle(workspacePath, (ddb) => {
653
- const rows = ddb.select().from(agentWorktreesTable)
654
- .where(like(sql `LOWER(${agentWorktreesTable.branch})`, branchPattern))
655
- .all();
656
- return rows.map(row => ({
657
- agent_name: row.agentName,
658
- repo_name: row.repoName,
659
- worktree_path: row.worktreePath,
660
- branch: row.branch,
661
- created_at: row.createdAt,
662
- last_commit_hash: row.lastCommitHash ?? undefined,
663
- commits_ahead: row.commitsAhead,
664
- is_clean: Boolean(row.isClean),
665
- last_checked: row.lastChecked ?? undefined,
666
- }));
667
- });
668
- }
669
- /**
670
- * Get agent worktrees for a specific repository.
671
- */
672
- export function getWorktreesForRepo(workspacePath, repoName) {
673
- return withDrizzle(workspacePath, (ddb) => {
674
- const rows = ddb.select({
675
- agent_name: agentWorktreesTable.agentName,
676
- is_clean: sql `${agentWorktreesTable.isClean}`,
677
- commits_ahead: agentWorktreesTable.commitsAhead,
678
- branch: agentWorktreesTable.branch,
679
- }).from(agentWorktreesTable)
680
- .where(eq(agentWorktreesTable.repoName, repoName))
681
- .all();
682
- return rows;
683
- });
684
- }
685
- /**
686
- * Upsert a workspace setting (key-value pair).
687
- */
688
- export function upsertWorkspaceSetting(db, key, value) {
689
- const ddb = createDrizzleConnection(db);
690
- ddb.insert(workspaceSettingsTable)
691
- .values({ key, value })
692
- .onConflictDoUpdate({
693
- target: workspaceSettingsTable.key,
694
- set: { value },
695
- })
696
- .run();
697
- }
698
- /**
699
- * Remove agents from database
700
- */
701
- export function removeAgentsFromDatabase(workspacePath, agentNames) {
702
- withDrizzle(workspacePath, (ddb, sqliteDb) => {
703
- // Note: agent_worktrees will be deleted automatically due to CASCADE
704
- const transaction = sqliteDb.transaction(() => {
705
- for (const agentName of agentNames) {
706
- ddb.delete(agentsTable)
707
- .where(eq(agentsTable.name, agentName))
708
- .run();
709
- }
710
- });
711
- transaction();
712
- });
713
- }
714
- // =============================================================================
715
- // PMO Bootstrapping Operations
716
- // Raw SQL is required here because these operate before migrations run
717
- // or perform DDL operations that Drizzle doesn't support.
718
- // =============================================================================
719
- /**
720
- * Check if PMO tables exist and get basic stats.
721
- * Used by pmo init to detect existing PMO before storage layer is available.
722
- * Raw SQL: uses sqlite_master introspection (pre-migration bootstrap).
723
- */
724
- export function checkPMOExists(dbPath) {
725
- let db;
726
- try {
727
- db = new Database(dbPath);
728
- }
729
- catch (error) {
730
- throwIfNativeBindingError(error, 'checkPMOExists');
731
- throw error;
732
- }
733
- try {
734
- const result = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='pmo_projects'").get();
735
- if (result === undefined) {
736
- return { exists: false, projectCount: 0, ticketCount: 0 };
737
- }
738
- const projectCountResult = db.prepare('SELECT COUNT(*) as count FROM pmo_projects').get();
739
- const ticketCountResult = db.prepare('SELECT COUNT(*) as count FROM pmo_tickets').get();
740
- return {
741
- exists: true,
742
- projectCount: projectCountResult.count,
743
- ticketCount: ticketCountResult.count,
744
- };
745
- }
746
- finally {
747
- db.close();
748
- }
749
- }
750
- /**
751
- * Get a PMO setting from the pmo_settings table.
752
- * Used for bootstrapping queries before storage layer is available.
753
- * Raw SQL: pre-migration bootstrap query.
754
- */
755
- export function getPMOSetting(dbPath, key) {
756
- let db;
757
- try {
758
- db = new Database(dbPath);
759
- }
760
- catch (error) {
761
- throwIfNativeBindingError(error, 'getPMOSetting');
762
- throw error;
763
- }
764
- try {
765
- const result = db.prepare('SELECT value FROM pmo_settings WHERE key = ?').get(key);
766
- return result?.value ?? null;
767
- }
768
- catch {
769
- return null;
770
- }
771
- finally {
772
- db.close();
773
- }
774
- }
775
- /**
776
- * Drop PMO tables from the database.
777
- * Used during PMO reinitialization.
778
- * Raw SQL: DDL operations (DROP TABLE) are not supported by Drizzle.
779
- */
780
- export function dropPMOTables(dbPath, tables) {
781
- let db;
782
- try {
783
- db = new Database(dbPath);
784
- }
785
- catch (error) {
786
- throwIfNativeBindingError(error, 'dropPMOTables');
787
- throw error;
788
- }
789
- try {
790
- for (const table of tables) {
791
- try {
792
- db.prepare(`DROP TABLE IF EXISTS ${table}`).run();
793
- }
794
- catch {
795
- // Ignore errors - table might not exist
796
- }
797
- }
798
- }
799
- finally {
800
- db.close();
801
- }
802
- }
803
- // =============================================================================
804
- // Theme CRUD Operations
805
- // =============================================================================
806
- /**
807
- * Get all themes
808
- */
809
- export function getThemes(workspacePath) {
810
- return withDrizzle(workspacePath, (ddb) => {
811
- const rows = ddb.select().from(agentThemesTable)
812
- .orderBy(desc(agentThemesTable.builtin), asc(agentThemesTable.name))
813
- .all();
814
- return rows.map(toAgentTheme);
815
- });
816
- }
817
- /**
818
- * Get a theme by ID
819
- */
820
- export function getTheme(workspacePath, themeId) {
821
- return withDrizzle(workspacePath, (ddb) => {
822
- const row = ddb.select().from(agentThemesTable)
823
- .where(eq(agentThemesTable.id, themeId))
824
- .get();
825
- return row ? toAgentTheme(row) : null;
826
- });
827
- }
828
- /**
829
- * Create a new theme
830
- */
831
- export function createTheme(workspacePath, theme) {
832
- return withDrizzle(workspacePath, (ddb) => {
833
- const now = new Date().toISOString();
834
- ddb.insert(agentThemesTable).values({
835
- id: theme.id,
836
- name: theme.name,
837
- displayName: theme.displayName,
838
- description: theme.description || null,
839
- builtin: theme.builtin || false,
840
- createdAt: now,
841
- }).run();
842
- const created = ddb.select().from(agentThemesTable)
843
- .where(eq(agentThemesTable.id, theme.id))
844
- .get();
845
- return toAgentTheme(created);
846
- });
847
- }
848
- /**
849
- * Delete a theme (cannot delete builtin themes)
850
- */
851
- export function deleteTheme(workspacePath, themeId) {
852
- return withDrizzle(workspacePath, (ddb) => {
853
- const theme = ddb.select({ builtin: agentThemesTable.builtin })
854
- .from(agentThemesTable)
855
- .where(eq(agentThemesTable.id, themeId))
856
- .get();
857
- if (!theme) {
858
- return false;
859
- }
860
- if (theme.builtin) {
861
- throw new Error('Cannot delete built-in themes');
862
- }
863
- ddb.delete(agentThemesTable)
864
- .where(eq(agentThemesTable.id, themeId))
865
- .run();
866
- return true;
867
- });
868
- }
869
- /**
870
- * Get names for a theme
871
- */
872
- export function getThemeNames(workspacePath, themeId) {
873
- return withDrizzle(workspacePath, (ddb) => {
874
- const rows = ddb.select().from(agentThemeNamesTable)
875
- .where(eq(agentThemeNamesTable.themeId, themeId))
876
- .orderBy(asc(agentThemeNamesTable.name))
877
- .all();
878
- return rows.map(row => ({
879
- theme_id: row.themeId,
880
- name: row.name,
881
- }));
882
- });
883
- }
884
- /**
885
- * Get available names for a theme.
886
- * A name is available if:
887
- * 1. No staff agent exists in the database with that name (case-insensitive), OR
888
- * 2. The agent exists but its worktree directory is missing (manually deleted)
889
- */
890
- export function getAvailableThemeNames(workspacePath, themeId) {
891
- return withDrizzle(workspacePath, (ddb) => {
892
- // Get all theme names
893
- const names = ddb.select({ name: agentThemeNamesTable.name })
894
- .from(agentThemeNamesTable)
895
- .where(eq(agentThemeNamesTable.themeId, themeId))
896
- .orderBy(asc(agentThemeNamesTable.name))
897
- .all();
898
- // Get existing staff agents with their worktree paths (persistent type only)
899
- const existingAgents = ddb.select({
900
- name: sql `LOWER(${agentsTable.name})`,
901
- worktreePath: agentsTable.worktreePath,
902
- })
903
- .from(agentsTable)
904
- .where(and(eq(agentsTable.type, 'persistent'), or(eq(agentsTable.status, 'active'), isNull(agentsTable.status))))
905
- .all();
906
- // Build a set of names that are truly in use (agent exists AND worktree exists)
907
- const inUseNames = new Set();
908
- for (const agent of existingAgents) {
909
- if (agent.worktreePath) {
910
- const fullPath = path.join(workspacePath, agent.worktreePath);
911
- if (fs.existsSync(fullPath)) {
912
- inUseNames.add(agent.name);
913
- }
914
- }
915
- else {
916
- // No worktree path means we can't verify - treat as in use to be safe
917
- inUseNames.add(agent.name);
918
- }
919
- }
920
- // Filter out names that are truly in use
921
- return names
922
- .map(n => n.name)
923
- .filter(name => !inUseNames.has(name.toLowerCase()));
924
- });
925
- }
926
- /**
927
- * Add names to a theme (case-insensitive uniqueness)
928
- */
929
- export function addThemeNames(workspacePath, themeId, names) {
930
- withDrizzle(workspacePath, (ddb, sqliteDb) => {
931
- const transaction = sqliteDb.transaction(() => {
932
- for (const name of names) {
933
- // Check for existing name (case-insensitive)
934
- const existing = ddb.select({ name: agentThemeNamesTable.name })
935
- .from(agentThemeNamesTable)
936
- .where(and(eq(agentThemeNamesTable.themeId, themeId), sql `LOWER(${agentThemeNamesTable.name}) = LOWER(${name})`))
937
- .get();
938
- if (existing) {
939
- continue;
940
- }
941
- ddb.insert(agentThemeNamesTable).values({
942
- themeId,
943
- name,
944
- }).run();
945
- }
946
- });
947
- transaction();
948
- });
949
- }
950
- /**
951
- * Add a media item to the database
952
- */
953
- export function addMediaItemToDatabase(workspacePath, item) {
954
- withDrizzle(workspacePath, (ddb) => {
955
- const now = new Date().toISOString();
956
- ddb.insert(mediaItemsTable)
957
- .values({
958
- name: item.name,
959
- path: item.path,
960
- sourcePath: item.source_path || null,
961
- mediaType: item.media_type,
962
- frameInterval: item.frame_interval || 30,
963
- addedAt: now,
964
- })
965
- .onConflictDoUpdate({
966
- target: mediaItemsTable.name,
967
- set: {
968
- path: item.path,
969
- sourcePath: item.source_path || null,
970
- mediaType: item.media_type,
971
- frameInterval: item.frame_interval || 30,
972
- addedAt: now,
973
- },
974
- })
975
- .run();
976
- });
977
- }
978
- /**
979
- * Update media item after preprocessing
980
- */
981
- export function updateMediaItemStatus(workspacePath, name, updates) {
982
- withDrizzle(workspacePath, (ddb) => {
983
- const setValues = { status: updates.status };
984
- if (updates.duration_seconds !== undefined) {
985
- setValues.durationSeconds = updates.duration_seconds;
986
- }
987
- if (updates.resolution !== undefined) {
988
- setValues.resolution = updates.resolution;
989
- }
990
- if (updates.frame_count !== undefined) {
991
- setValues.frameCount = updates.frame_count;
992
- }
993
- if (updates.has_transcript !== undefined) {
994
- setValues.hasTranscript = updates.has_transcript;
995
- }
996
- if (updates.error_message !== undefined) {
997
- setValues.errorMessage = updates.error_message;
998
- }
999
- if (updates.status === 'ready' || updates.status === 'error') {
1000
- setValues.processedAt = new Date().toISOString();
1001
- }
1002
- ddb.update(mediaItemsTable)
1003
- .set(setValues)
1004
- .where(eq(mediaItemsTable.name, name))
1005
- .run();
1006
- });
1007
- }
1008
- /**
1009
- * Get all media items in workspace
1010
- */
1011
- export function getWorkspaceMediaItems(workspacePath) {
1012
- return withDrizzle(workspacePath, (ddb) => {
1013
- const rows = ddb.select().from(mediaItemsTable)
1014
- .orderBy(asc(mediaItemsTable.addedAt))
1015
- .all();
1016
- return rows.map(row => ({
1017
- name: row.name,
1018
- path: row.path,
1019
- source_path: row.sourcePath,
1020
- media_type: row.mediaType,
1021
- duration_seconds: row.durationSeconds,
1022
- resolution: row.resolution,
1023
- frame_count: row.frameCount,
1024
- has_transcript: Boolean(row.hasTranscript),
1025
- frame_interval: row.frameInterval,
1026
- status: row.status,
1027
- error_message: row.errorMessage,
1028
- added_at: row.addedAt,
1029
- processed_at: row.processedAt,
1030
- }));
1031
- });
1032
- }
1033
- /**
1034
- * Get a single media item by name
1035
- */
1036
- export function getMediaItem(workspacePath, name) {
1037
- return withDrizzle(workspacePath, (ddb) => {
1038
- const row = ddb.select().from(mediaItemsTable)
1039
- .where(eq(mediaItemsTable.name, name))
1040
- .get();
1041
- if (!row)
1042
- return null;
1043
- return {
1044
- name: row.name,
1045
- path: row.path,
1046
- source_path: row.sourcePath,
1047
- media_type: row.mediaType,
1048
- duration_seconds: row.durationSeconds,
1049
- resolution: row.resolution,
1050
- frame_count: row.frameCount,
1051
- has_transcript: Boolean(row.hasTranscript),
1052
- frame_interval: row.frameInterval,
1053
- status: row.status,
1054
- error_message: row.errorMessage,
1055
- added_at: row.addedAt,
1056
- processed_at: row.processedAt,
1057
- };
1058
- });
1059
- }
1060
- /**
1061
- * Remove a media item from the database
1062
- */
1063
- export function removeMediaItemFromDatabase(workspacePath, name) {
1064
- withDrizzle(workspacePath, (ddb) => {
1065
- ddb.delete(mediaItemsTable)
1066
- .where(eq(mediaItemsTable.name, name))
1067
- .run();
1068
- });
1069
- }
18
+ // Re-export CREATE_TABLES_SQL from its canonical location
19
+ export { CREATE_TABLES_SQL } from './workspace-schema.js';
20
+ // Workspace lifecycle
21
+ export { withDrizzle, getDatabasePath, getConfigPath, openWorkspaceDatabase, openWorkspaceDriver, createWorkspaceDatabase, getWorkspaceConfig, } from './workspace.js';
22
+ // Agent operations
23
+ export { addAgentsToDatabase, addEphemeralAgentToDatabase, tryAddEphemeralAgentToDatabase, getEphemeralAgentNames, removeEphemeralAgent, getWorkspaceAgents, getAgentByPath, markAgentCleaned, syncAgentsWithDisk, discoverAgentsOnDisk, removeAgentsFromDatabase, } from './agents.js';
24
+ // Repository operations
25
+ export { addRepositoriesToDatabase, getWorkspaceRepositories, } from './repositories.js';
26
+ // Theme operations
27
+ export { getActiveTheme, setActiveTheme, getThemes, getTheme, createTheme, deleteTheme, getThemeNames, getAvailableThemeNames, addThemeNames, } from './themes.js';
28
+ // Worktree operations
29
+ export { getAgentWorktrees, findWorktreesByBranch, getWorktreesForRepo, } from './worktrees.js';
30
+ // Media item operations
31
+ export { addMediaItemToDatabase, updateMediaItemStatus, getWorkspaceMediaItems, getMediaItem, removeMediaItemFromDatabase, } from './media.js';
32
+ // PMO bootstrap operations
33
+ export { checkPMOExists, getPMOSetting, dropPMOTables, upsertWorkspaceSetting, } from './pmo-bootstrap.js';
34
+ // Database driver abstraction
35
+ export { BetterSqlite3Driver, wrapDatabase, openDriver, getRawDatabase, } from './driver.js';
36
+ // Settings store
37
+ export { SettingsStore, createSettingsStore, } from './settings-store.js';
1070
38
  //# sourceMappingURL=index.js.map