@proletariat/cli 0.3.86 → 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.
- package/dist/commands/config/index.js +42 -0
- package/dist/commands/config/index.js.map +1 -1
- package/dist/commands/init.d.ts +6 -1
- package/dist/commands/init.js +44 -89
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/new.d.ts +30 -2
- package/dist/commands/new.js +141 -9
- package/dist/commands/new.js.map +1 -1
- package/dist/commands/session/poke.d.ts +3 -7
- package/dist/commands/session/poke.js +5 -7
- package/dist/commands/session/poke.js.map +1 -1
- package/dist/commands/ticket/cancel.js +2 -0
- package/dist/commands/ticket/cancel.js.map +1 -1
- package/dist/commands/ticket/complete.js +2 -0
- package/dist/commands/ticket/complete.js.map +1 -1
- package/dist/commands/ticket/delete.js +2 -0
- package/dist/commands/ticket/delete.js.map +1 -1
- package/dist/commands/ticket/edit.js +5 -0
- package/dist/commands/ticket/edit.js.map +1 -1
- package/dist/commands/ticket/move.js +2 -0
- package/dist/commands/ticket/move.js.map +1 -1
- package/dist/commands/ticket/project.js +2 -0
- package/dist/commands/ticket/project.js.map +1 -1
- package/dist/commands/ticket/reassign.js +2 -0
- package/dist/commands/ticket/reassign.js.map +1 -1
- package/dist/commands/ticket/resolve.js +2 -0
- package/dist/commands/ticket/resolve.js.map +1 -1
- package/dist/commands/ticket/update.js +5 -0
- package/dist/commands/ticket/update.js.map +1 -1
- package/dist/commands/work/complete.js +2 -0
- package/dist/commands/work/complete.js.map +1 -1
- package/dist/commands/work/peek.js +4 -0
- package/dist/commands/work/peek.js.map +1 -1
- package/dist/commands/work/poke.d.ts +3 -4
- package/dist/commands/work/poke.js +9 -4
- package/dist/commands/work/poke.js.map +1 -1
- package/dist/commands/work/ready.js +2 -0
- package/dist/commands/work/ready.js.map +1 -1
- package/dist/commands/work/start.d.ts +1 -0
- package/dist/commands/work/start.js +46 -2
- package/dist/commands/work/start.js.map +1 -1
- package/dist/commands/work/stop.js +5 -0
- package/dist/commands/work/stop.js.map +1 -1
- package/dist/hooks/init.js +6 -6
- package/dist/hooks/init.js.map +1 -1
- package/dist/hooks/postrun.js +3 -0
- package/dist/hooks/postrun.js.map +1 -1
- package/dist/lib/asana/config.d.ts +1 -1
- package/dist/lib/asana/config.js +18 -30
- package/dist/lib/asana/config.js.map +1 -1
- package/dist/lib/asana/mapper.d.ts +4 -3
- package/dist/lib/asana/mapper.js +17 -10
- package/dist/lib/asana/mapper.js.map +1 -1
- package/dist/lib/database/agents.d.ts +73 -0
- package/dist/lib/database/agents.js +341 -0
- package/dist/lib/database/agents.js.map +1 -0
- package/dist/lib/database/driver.d.ts +115 -0
- package/dist/lib/database/driver.js +109 -0
- package/dist/lib/database/driver.js.map +1 -0
- package/dist/lib/database/drizzle-schema.d.ts +1 -1
- package/dist/lib/database/index.d.ts +25 -287
- package/dist/lib/database/index.js +35 -1067
- package/dist/lib/database/index.js.map +1 -1
- package/dist/lib/database/media.d.ts +53 -0
- package/dist/lib/database/media.js +118 -0
- package/dist/lib/database/media.js.map +1 -0
- package/dist/lib/database/migrations/0011_add_review_gate.d.ts +8 -0
- package/dist/lib/database/migrations/0011_add_review_gate.js +21 -0
- package/dist/lib/database/migrations/0011_add_review_gate.js.map +1 -0
- package/dist/lib/database/migrations/index.js +2 -0
- package/dist/lib/database/migrations/index.js.map +1 -1
- package/dist/lib/database/pmo-bootstrap.d.ts +30 -0
- package/dist/lib/database/pmo-bootstrap.js +105 -0
- package/dist/lib/database/pmo-bootstrap.js.map +1 -0
- package/dist/lib/database/repositories.d.ts +26 -0
- package/dist/lib/database/repositories.js +56 -0
- package/dist/lib/database/repositories.js.map +1 -0
- package/dist/lib/database/settings-store.d.ts +60 -0
- package/dist/lib/database/settings-store.js +87 -0
- package/dist/lib/database/settings-store.js.map +1 -0
- package/dist/lib/database/themes.d.ts +59 -0
- package/dist/lib/database/themes.js +212 -0
- package/dist/lib/database/themes.js.map +1 -0
- package/dist/lib/database/workspace.d.ts +46 -0
- package/dist/lib/database/workspace.js +158 -0
- package/dist/lib/database/workspace.js.map +1 -0
- package/dist/lib/database/worktrees.d.ts +33 -0
- package/dist/lib/database/worktrees.js +60 -0
- package/dist/lib/database/worktrees.js.map +1 -0
- package/dist/lib/execution/config.d.ts +1 -1
- package/dist/lib/execution/config.js +7 -17
- package/dist/lib/execution/config.js.map +1 -1
- package/dist/lib/execution/runners/prompt-builder.js +49 -15
- package/dist/lib/execution/runners/prompt-builder.js.map +1 -1
- package/dist/lib/execution/spawner.d.ts +1 -1
- package/dist/lib/execution/storage.d.ts +6 -4
- package/dist/lib/execution/storage.js +17 -9
- package/dist/lib/execution/storage.js.map +1 -1
- package/dist/lib/execution/types.d.ts +1 -0
- package/dist/lib/execution/types.js.map +1 -1
- package/dist/lib/external-issues/mapping-store.d.ts +4 -3
- package/dist/lib/external-issues/mapping-store.js +21 -13
- package/dist/lib/external-issues/mapping-store.js.map +1 -1
- package/dist/lib/external-issues/outbound-sync.d.ts +1 -1
- package/dist/lib/jira/config.d.ts +1 -6
- package/dist/lib/jira/config.js +16 -33
- package/dist/lib/jira/config.js.map +1 -1
- package/dist/lib/linear/config.d.ts +1 -1
- package/dist/lib/linear/config.js +16 -38
- package/dist/lib/linear/config.js.map +1 -1
- package/dist/lib/linear/mapper.d.ts +4 -3
- package/dist/lib/linear/mapper.js +20 -13
- package/dist/lib/linear/mapper.js.map +1 -1
- package/dist/lib/mcp/tools/action.js +34 -0
- package/dist/lib/mcp/tools/action.js.map +1 -1
- package/dist/lib/monday/config.d.ts +1 -1
- package/dist/lib/monday/config.js +16 -32
- package/dist/lib/monday/config.js.map +1 -1
- package/dist/lib/monday/mapper.d.ts +4 -3
- package/dist/lib/monday/mapper.js +19 -12
- package/dist/lib/monday/mapper.js.map +1 -1
- package/dist/lib/onboarding/wizard.d.ts +2 -2
- package/dist/lib/onboarding/wizard.js +32 -24
- package/dist/lib/onboarding/wizard.js.map +1 -1
- package/dist/lib/pmo/diet.d.ts +1 -1
- package/dist/lib/pmo/schema.d.ts +1 -1
- package/dist/lib/pmo/schema.js +1 -0
- package/dist/lib/pmo/schema.js.map +1 -1
- package/dist/lib/pmo/storage/actions.js +9 -3
- package/dist/lib/pmo/storage/actions.js.map +1 -1
- package/dist/lib/pmo/storage/index.d.ts +9 -0
- package/dist/lib/pmo/storage/index.js +16 -1
- package/dist/lib/pmo/storage/index.js.map +1 -1
- package/dist/lib/pmo/storage/tickets.d.ts +7 -0
- package/dist/lib/pmo/storage/tickets.js +33 -2
- package/dist/lib/pmo/storage/tickets.js.map +1 -1
- package/dist/lib/pmo/storage/types.d.ts +5 -1
- package/dist/lib/pmo/types.d.ts +10 -0
- package/dist/lib/pmo/types.js.map +1 -1
- package/dist/lib/pmo/utils.d.ts +38 -0
- package/dist/lib/pmo/utils.js +61 -0
- package/dist/lib/pmo/utils.js.map +1 -1
- package/dist/lib/providers/event-emitting-provider.js +13 -2
- package/dist/lib/providers/event-emitting-provider.js.map +1 -1
- package/dist/lib/shortcut/config.d.ts +1 -7
- package/dist/lib/shortcut/config.js +13 -32
- package/dist/lib/shortcut/config.js.map +1 -1
- package/dist/lib/telemetry/analytics.d.ts +47 -0
- package/dist/lib/telemetry/analytics.js +52 -0
- package/dist/lib/telemetry/analytics.js.map +1 -1
- package/dist/lib/telemetry/telemetry-bridge.d.ts +35 -0
- package/dist/lib/telemetry/telemetry-bridge.js +139 -0
- package/dist/lib/telemetry/telemetry-bridge.js.map +1 -0
- package/dist/lib/trello/config.d.ts +1 -26
- package/dist/lib/trello/config.js +23 -64
- package/dist/lib/trello/config.js.map +1 -1
- package/dist/lib/trello/mapper.d.ts +4 -3
- package/dist/lib/trello/mapper.js +17 -10
- package/dist/lib/trello/mapper.js.map +1 -1
- package/dist/lib/work-lifecycle/post-execution.d.ts +6 -2
- package/dist/lib/work-lifecycle/post-execution.js +30 -18
- package/dist/lib/work-lifecycle/post-execution.js.map +1 -1
- package/dist/lib/work-source/config.d.ts +1 -1
- package/dist/lib/work-source/config.js +14 -24
- package/dist/lib/work-source/config.js.map +1 -1
- package/dist/lib/work-source/provider-sources.d.ts +1 -1
- package/dist/lib/work-source/provider-sources.js +8 -20
- package/dist/lib/work-source/provider-sources.js.map +1 -1
- package/oclif.manifest.json +1035 -987
- package/package.json +9 -9
|
@@ -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
|
-
*
|
|
522
|
-
*
|
|
523
|
-
*
|
|
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
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
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
|