@proletariat/cli 0.3.48 → 0.3.49

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 (38) hide show
  1. package/dist/commands/claude/index.js +21 -21
  2. package/dist/commands/claude/open.js +1 -1
  3. package/dist/commands/config/index.js +2 -2
  4. package/dist/commands/execution/config.d.ts +2 -2
  5. package/dist/commands/execution/config.js +18 -18
  6. package/dist/commands/execution/list.js +2 -2
  7. package/dist/commands/execution/view.js +2 -2
  8. package/dist/commands/orchestrator/start.d.ts +1 -1
  9. package/dist/commands/orchestrator/start.js +19 -19
  10. package/dist/commands/qa/index.js +12 -12
  11. package/dist/commands/staff/add.js +1 -1
  12. package/dist/commands/work/linear.js +28 -5
  13. package/dist/commands/work/revise.js +8 -8
  14. package/dist/commands/work/spawn.js +1 -1
  15. package/dist/commands/work/start.js +12 -12
  16. package/dist/commands/work/watch.js +3 -3
  17. package/dist/lib/agents/index.js +2 -2
  18. package/dist/lib/database/drizzle-schema.d.ts +7 -7
  19. package/dist/lib/database/drizzle-schema.js +1 -1
  20. package/dist/lib/execution/config.d.ts +6 -6
  21. package/dist/lib/execution/config.js +17 -10
  22. package/dist/lib/execution/devcontainer.d.ts +3 -3
  23. package/dist/lib/execution/devcontainer.js +3 -3
  24. package/dist/lib/execution/runners.d.ts +2 -2
  25. package/dist/lib/execution/runners.js +23 -24
  26. package/dist/lib/execution/spawner.js +3 -3
  27. package/dist/lib/execution/storage.d.ts +2 -2
  28. package/dist/lib/execution/storage.js +3 -3
  29. package/dist/lib/execution/types.d.ts +2 -2
  30. package/dist/lib/execution/types.js +1 -1
  31. package/dist/lib/external-issues/linear.d.ts +6 -0
  32. package/dist/lib/external-issues/linear.js +63 -0
  33. package/dist/lib/pmo/schema.d.ts +1 -1
  34. package/dist/lib/pmo/schema.js +1 -1
  35. package/dist/lib/pmo/storage/base.js +31 -0
  36. package/dist/lib/repos/index.js +1 -1
  37. package/oclif.manifest.json +4318 -4313
  38. package/package.json +1 -1
@@ -18,7 +18,7 @@ function rowToAgentWork(row) {
18
18
  executor: row.executor,
19
19
  environment: (row.environment || 'host'),
20
20
  displayMode: (row.display_mode || 'terminal'),
21
- sandboxed: row.sandboxed === 1,
21
+ permissionMode: (row.permission_mode || 'safe'),
22
22
  status: row.status,
23
23
  branch: row.branch || undefined,
24
24
  pid: row.pid || undefined,
@@ -51,10 +51,10 @@ export class ExecutionStorage {
51
51
  const id = `WORK-${randomUUID().substring(0, 8).toUpperCase()}`;
52
52
  this.db.prepare(`
53
53
  INSERT INTO ${T.agent_work} (
54
- id, ticket_id, agent_name, executor, environment, display_mode, sandboxed,
54
+ id, ticket_id, agent_name, executor, environment, display_mode, permission_mode,
55
55
  status, branch, pid, container_id, session_id, host, log_path, started_at
56
56
  ) VALUES (?, ?, ?, ?, ?, ?, ?, 'starting', ?, ?, ?, ?, ?, ?, ?)
57
- `).run(id, params.ticketId, params.agentName, params.executor, params.environment, params.displayMode, params.sandboxed ? 1 : 0, params.branch || null, params.pid || null, params.containerId || null, params.sessionId || null, params.host || null, params.logPath || null, now);
57
+ `).run(id, params.ticketId, params.agentName, params.executor, params.environment, params.displayMode, params.permissionMode, params.branch || null, params.pid || null, params.containerId || null, params.sessionId || null, params.host || null, params.logPath || null, now);
58
58
  return this.getExecution(id);
59
59
  }
60
60
  /**
@@ -46,7 +46,7 @@ export interface AgentWork {
46
46
  environment: ExecutionEnvironment;
47
47
  displayMode: DisplayMode;
48
48
  sessionManager?: SessionManager;
49
- sandboxed: boolean;
49
+ permissionMode: PermissionMode;
50
50
  status: ExecutionStatus;
51
51
  branch?: string;
52
52
  pid?: string;
@@ -120,7 +120,7 @@ export interface ExecutionConfig {
120
120
  autoExecute: boolean;
121
121
  shell: Shell;
122
122
  outputMode: OutputMode;
123
- sandboxed: boolean;
123
+ permissionMode: PermissionMode;
124
124
  authMethod?: AuthMethod;
125
125
  createPrDefault?: boolean;
126
126
  tmux: {
@@ -131,7 +131,7 @@ export const DEFAULT_EXECUTION_CONFIG = {
131
131
  autoExecute: false,
132
132
  shell: 'zsh', // macOS default
133
133
  outputMode: 'interactive', // Show streaming UI by default
134
- sandboxed: true, // Require approval for dangerous operations by default
134
+ permissionMode: 'safe', // Require approval for dangerous operations by default
135
135
  tmux: {
136
136
  session: 'proletariat',
137
137
  layout: 'window',
@@ -28,6 +28,12 @@ export declare function buildLinearSpawnContextMessage(envelope: NormalizedIssue
28
28
  * Build a CLI command string for selecting a specific Linear issue.
29
29
  */
30
30
  export declare function buildLinearIssueChoiceCommand(issueIdentifier: string, projectId?: string): string;
31
+ /**
32
+ * Fetch a single Linear issue by identifier (for example, ENG-123) and normalize it.
33
+ */
34
+ export declare function getLinearIssueByIdentifier(configInput: LinearAdapterConfig, identifier: string, options?: {
35
+ fetchImpl?: typeof fetch;
36
+ }): Promise<NormalizedIssueEnvelope | null>;
31
37
  /**
32
38
  * Fetch and normalize Linear issues into NormalizedIssueEnvelopes.
33
39
  */
@@ -30,6 +30,27 @@ const LINEAR_ISSUES_QUERY = `
30
30
  }
31
31
  }
32
32
  `;
33
+ const LINEAR_ISSUE_BY_IDENTIFIER_QUERY = `
34
+ query IssueForSpawn($id: String!) {
35
+ issue(id: $id) {
36
+ id
37
+ identifier
38
+ title
39
+ description
40
+ url
41
+ priority
42
+ labels {
43
+ nodes {
44
+ name
45
+ }
46
+ }
47
+ state {
48
+ name
49
+ type
50
+ }
51
+ }
52
+ }
53
+ `;
33
54
  function priorityFromLinear(value) {
34
55
  switch (value) {
35
56
  case 1:
@@ -155,6 +176,48 @@ export function buildLinearIssueChoiceCommand(issueIdentifier, projectId) {
155
176
  }
156
177
  return command;
157
178
  }
179
+ /**
180
+ * Fetch a single Linear issue by identifier (for example, ENG-123) and normalize it.
181
+ */
182
+ export async function getLinearIssueByIdentifier(configInput, identifier, options) {
183
+ const config = ensureLinearConfig(configInput);
184
+ const fetchImpl = options?.fetchImpl || fetch;
185
+ const response = await fetchImpl(config.apiUrl, {
186
+ method: 'POST',
187
+ headers: {
188
+ 'Content-Type': 'application/json',
189
+ Authorization: config.apiKey,
190
+ },
191
+ body: JSON.stringify({
192
+ query: LINEAR_ISSUE_BY_IDENTIFIER_QUERY,
193
+ variables: {
194
+ id: identifier,
195
+ },
196
+ }),
197
+ });
198
+ if (response.status === 401 || response.status === 403) {
199
+ throw new ExternalIssueAdapterError('AUTH_FAILED', 'Linear authentication failed. Verify your LINEAR_API_KEY token.');
200
+ }
201
+ if (!response.ok) {
202
+ throw new ExternalIssueAdapterError('REQUEST_FAILED', `Linear request failed with status ${response.status}.`);
203
+ }
204
+ const payload = await response.json();
205
+ if (payload.errors && payload.errors.length > 0) {
206
+ const message = payload.errors[0]?.message || 'Unknown Linear API error.';
207
+ if (/auth|token|forbidden|unauthorized/i.test(message)) {
208
+ throw new ExternalIssueAdapterError('AUTH_FAILED', `Linear authentication failed: ${message}`);
209
+ }
210
+ // "not found" should be treated as null, not hard failure.
211
+ if (/not found/i.test(message)) {
212
+ return null;
213
+ }
214
+ throw new ExternalIssueAdapterError('REQUEST_FAILED', `Linear API error: ${message}`);
215
+ }
216
+ const node = payload.data?.issue;
217
+ if (!node)
218
+ return null;
219
+ return normalizeLinearIssueToEnvelope(node);
220
+ }
158
221
  /**
159
222
  * Fetch and normalize Linear issues into NormalizedIssueEnvelopes.
160
223
  */
@@ -67,7 +67,7 @@ export declare const PMO_TABLE_SCHEMAS: {
67
67
  readonly project_specs: "\n CREATE TABLE IF NOT EXISTS pmo_project_specs (\n project_id TEXT NOT NULL REFERENCES pmo_projects(id) ON DELETE CASCADE,\n spec_id TEXT NOT NULL REFERENCES pmo_specs(id) ON DELETE CASCADE,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (project_id, spec_id)\n )";
68
68
  readonly cache_metadata: "\n CREATE TABLE IF NOT EXISTS pmo_cache_metadata (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n )";
69
69
  readonly settings: "\n CREATE TABLE IF NOT EXISTS pmo_settings (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n )";
70
- readonly agent_work: "\n CREATE TABLE IF NOT EXISTS agent_work (\n id TEXT PRIMARY KEY,\n ticket_id TEXT NOT NULL,\n agent_name TEXT NOT NULL,\n executor TEXT NOT NULL,\n environment TEXT NOT NULL DEFAULT 'host',\n display_mode TEXT NOT NULL DEFAULT 'terminal',\n sandboxed INTEGER NOT NULL DEFAULT 0,\n status TEXT NOT NULL DEFAULT 'starting',\n branch TEXT,\n pid TEXT,\n container_id TEXT,\n session_id TEXT,\n host TEXT,\n log_path TEXT,\n started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n completed_at TIMESTAMP,\n exit_code INTEGER,\n error_message TEXT,\n FOREIGN KEY (ticket_id) REFERENCES pmo_tickets(id) ON DELETE CASCADE\n )";
70
+ readonly agent_work: "\n CREATE TABLE IF NOT EXISTS agent_work (\n id TEXT PRIMARY KEY,\n ticket_id TEXT NOT NULL,\n agent_name TEXT NOT NULL,\n executor TEXT NOT NULL,\n environment TEXT NOT NULL DEFAULT 'host',\n display_mode TEXT NOT NULL DEFAULT 'terminal',\n permission_mode TEXT NOT NULL DEFAULT 'safe',\n status TEXT NOT NULL DEFAULT 'starting',\n branch TEXT,\n pid TEXT,\n container_id TEXT,\n session_id TEXT,\n host TEXT,\n log_path TEXT,\n started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n completed_at TIMESTAMP,\n exit_code INTEGER,\n error_message TEXT,\n FOREIGN KEY (ticket_id) REFERENCES pmo_tickets(id) ON DELETE CASCADE\n )";
71
71
  readonly containers: "\n CREATE TABLE IF NOT EXISTS containers (\n id TEXT PRIMARY KEY,\n agent_name TEXT NOT NULL,\n docker_id TEXT NOT NULL,\n docker_name TEXT,\n image TEXT,\n status TEXT NOT NULL DEFAULT 'unknown',\n current_execution_id TEXT,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n last_seen_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n FOREIGN KEY (agent_name) REFERENCES agents(name) ON DELETE CASCADE,\n FOREIGN KEY (current_execution_id) REFERENCES agent_work(id) ON DELETE SET NULL\n )";
72
72
  readonly id_sequences: "\n CREATE TABLE IF NOT EXISTS id_sequences (\n table_name TEXT PRIMARY KEY,\n next_id INTEGER NOT NULL DEFAULT 1\n )";
73
73
  readonly statuses: "\n CREATE TABLE IF NOT EXISTS pmo_statuses (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL,\n name TEXT NOT NULL,\n category TEXT NOT NULL,\n position INTEGER NOT NULL DEFAULT 0,\n color TEXT,\n description TEXT,\n is_default INTEGER NOT NULL DEFAULT 0,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n FOREIGN KEY (project_id) REFERENCES pmo_projects(id) ON DELETE CASCADE,\n UNIQUE(project_id, name)\n )";
@@ -328,7 +328,7 @@ export const PMO_TABLE_SCHEMAS = {
328
328
  executor TEXT NOT NULL,
329
329
  environment TEXT NOT NULL DEFAULT 'host',
330
330
  display_mode TEXT NOT NULL DEFAULT 'terminal',
331
- sandboxed INTEGER NOT NULL DEFAULT 0,
331
+ permission_mode TEXT NOT NULL DEFAULT 'safe',
332
332
  status TEXT NOT NULL DEFAULT 'starting',
333
333
  branch TEXT,
334
334
  pid TEXT,
@@ -345,6 +345,37 @@ export function runMigrations(db) {
345
345
  // Non-critical migration - don't fail initialization
346
346
  }
347
347
  }
348
+ // Migration: Rename sandboxed column to permission_mode in agent_work table
349
+ if (tableExists(T.agent_work)) {
350
+ const awColumns = db.pragma(`table_info(${T.agent_work})`);
351
+ const awColumnNames = new Set(awColumns.map(c => c.name));
352
+ if (awColumnNames.has('sandboxed') && !awColumnNames.has('permission_mode')) {
353
+ try {
354
+ db.exec(`ALTER TABLE ${T.agent_work} ADD COLUMN permission_mode TEXT NOT NULL DEFAULT 'safe'`);
355
+ // Migrate existing data: sandboxed=1 → 'safe', sandboxed=0 → 'danger'
356
+ db.exec(`UPDATE ${T.agent_work} SET permission_mode = CASE WHEN sandboxed = 1 THEN 'safe' ELSE 'danger' END`);
357
+ }
358
+ catch {
359
+ // Column may already exist
360
+ }
361
+ }
362
+ }
363
+ // Migration: Rename execution.sandboxed setting to execution.permission_mode
364
+ if (tableExists('workspace_settings')) {
365
+ try {
366
+ const oldSetting = db.prepare(`SELECT value FROM workspace_settings WHERE key = 'execution.sandboxed'`).get();
367
+ if (oldSetting) {
368
+ const permMode = oldSetting.value === 'true' ? 'safe' : 'danger';
369
+ db.prepare(`
370
+ INSERT INTO workspace_settings (key, value) VALUES ('execution.permission_mode', ?)
371
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value
372
+ `).run(permMode);
373
+ }
374
+ }
375
+ catch {
376
+ // Non-critical migration
377
+ }
378
+ }
348
379
  }
349
380
  /**
350
381
  * Seed built-in workflows from BUILTIN_TEMPLATES (single source of truth).
@@ -451,7 +451,7 @@ export async function addRepository(hqPath, repoPath, action) {
451
451
  addRepositoriesToDatabase(hqPath, dbRepoData);
452
452
  // Create worktrees for existing agents
453
453
  await createWorktreesForRepo(hqPath, repoName, targetPath);
454
- // Create devcontainer config for sandboxed execution in the central repo
454
+ // Create devcontainer config for isolated execution in the central repo
455
455
  console.log(styles.muted(`Creating devcontainer config for ${repoName}...`));
456
456
  const gitIdentity = getGitIdentity();
457
457
  createDevcontainerConfig({