@proletariat/cli 0.3.34 → 0.3.36
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/agent/auth.d.ts +15 -3
- package/dist/commands/agent/auth.js +136 -15
- package/dist/commands/agent/index.js +11 -2
- package/dist/commands/agent/list.js +16 -7
- package/dist/commands/agent/staff/add.d.ts +1 -0
- package/dist/commands/agent/staff/add.js +1 -0
- package/dist/commands/agent/staff/index.d.ts +15 -0
- package/dist/commands/agent/staff/index.js +83 -0
- package/dist/commands/agent/staff/list.d.ts +1 -0
- package/dist/commands/agent/staff/list.js +1 -0
- package/dist/commands/agent/staff/remove.d.ts +1 -0
- package/dist/commands/agent/staff/remove.js +1 -0
- package/dist/commands/agent/status.js +32 -4
- package/dist/commands/agent/themes/add-names.d.ts +1 -0
- package/dist/commands/agent/themes/add-names.js +1 -0
- package/dist/commands/agent/themes/create.d.ts +1 -0
- package/dist/commands/agent/themes/create.js +1 -0
- package/dist/commands/agent/themes/index.d.ts +10 -0
- package/dist/commands/agent/themes/index.js +144 -0
- package/dist/commands/agent/themes/list.d.ts +1 -0
- package/dist/commands/agent/themes/list.js +1 -0
- package/dist/commands/agent/themes/set.d.ts +1 -0
- package/dist/commands/agent/themes/set.js +1 -0
- package/dist/commands/agents/themes/add-names.d.ts +1 -0
- package/dist/commands/agents/themes/add-names.js +1 -0
- package/dist/commands/agents/themes/create.d.ts +1 -0
- package/dist/commands/agents/themes/create.js +1 -0
- package/dist/commands/agents/themes/list.d.ts +1 -0
- package/dist/commands/agents/themes/list.js +1 -0
- package/dist/commands/board/watch.js +6 -0
- package/dist/commands/branch/list.d.ts +1 -0
- package/dist/commands/branch/list.js +43 -12
- package/dist/commands/branch/where.js +3 -2
- package/dist/commands/category/list.d.ts +2 -1
- package/dist/commands/category/list.js +38 -13
- package/dist/commands/{claude.d.ts → claude/index.d.ts} +1 -1
- package/dist/commands/{claude.js → claude/index.js} +12 -12
- package/dist/commands/claude/open.d.ts +13 -0
- package/dist/commands/claude/open.js +175 -0
- package/dist/commands/diet.js +18 -2
- package/dist/commands/docker/logs.js +7 -3
- package/dist/commands/docker/shell.js +6 -0
- package/dist/commands/docker/start.js +20 -4
- package/dist/commands/docker/sync.d.ts +4 -0
- package/dist/commands/docker/sync.js +30 -2
- package/dist/commands/epic/show.d.ts +13 -0
- package/dist/commands/epic/show.js +16 -0
- package/dist/commands/epic/view.js +27 -0
- package/dist/commands/execution/config.d.ts +0 -4
- package/dist/commands/execution/config.js +10 -32
- package/dist/commands/execution/index.js +2 -1
- package/dist/commands/execution/logs.js +1 -1
- package/dist/commands/execution/stop.js +2 -1
- package/dist/commands/execution/view.js +22 -26
- package/dist/commands/init.js +2 -19
- package/dist/commands/label/create.d.ts +20 -0
- package/dist/commands/label/create.js +57 -0
- package/dist/commands/label/delete.d.ts +17 -0
- package/dist/commands/label/delete.js +32 -0
- package/dist/commands/label/group/create.d.ts +20 -0
- package/dist/commands/label/group/create.js +55 -0
- package/dist/commands/label/group/list.d.ts +14 -0
- package/dist/commands/label/group/list.js +52 -0
- package/dist/commands/label/index.d.ts +15 -0
- package/dist/commands/label/index.js +58 -0
- package/dist/commands/label/list.d.ts +16 -0
- package/dist/commands/label/list.js +83 -0
- package/dist/commands/link/list.js +3 -2
- package/dist/commands/mcp-server.js +27 -1
- package/dist/commands/phase/template/apply.d.ts +26 -0
- package/dist/commands/phase/template/apply.js +14 -0
- package/dist/commands/phase/template/create.d.ts +23 -0
- package/dist/commands/phase/template/create.js +14 -0
- package/dist/commands/phase/template/delete.d.ts +18 -0
- package/dist/commands/phase/template/delete.js +61 -0
- package/dist/commands/phase/template/list.d.ts +17 -0
- package/dist/commands/phase/template/list.js +89 -0
- package/dist/commands/phase/template/update.d.ts +1 -0
- package/dist/commands/phase/template/update.js +1 -0
- package/dist/commands/priority/add.js +1 -1
- package/dist/commands/project/create.js +3 -4
- package/dist/commands/project/update.js +5 -8
- package/dist/commands/pull.js +24 -0
- package/dist/commands/roadmap/generate.js +1 -2
- package/dist/commands/session/create.d.ts +19 -0
- package/dist/commands/session/create.js +102 -0
- package/dist/commands/session/health.js +2 -21
- package/dist/commands/session/index.js +14 -1
- package/dist/commands/session/list.js +26 -7
- package/dist/commands/session/peek.d.ts +38 -0
- package/dist/commands/session/peek.js +316 -0
- package/dist/commands/session/poke.d.ts +27 -0
- package/dist/commands/session/poke.js +219 -0
- package/dist/commands/spec/link/depends.d.ts +18 -0
- package/dist/commands/spec/link/depends.js +86 -0
- package/dist/commands/spec/link/index.d.ts +17 -0
- package/dist/commands/spec/link/index.js +92 -0
- package/dist/commands/spec/link/remove.d.ts +18 -0
- package/dist/commands/spec/link/remove.js +90 -0
- package/dist/commands/spec/view.js +29 -0
- package/dist/commands/support/logs.js +2 -2
- package/dist/commands/template/apply.js +5 -4
- package/dist/commands/template/create.js +1 -1
- package/dist/commands/template/list.js +2 -1
- package/dist/commands/theme/add-names.d.ts +4 -0
- package/dist/commands/theme/add-names.js +11 -1
- package/dist/commands/theme/create.d.ts +2 -0
- package/dist/commands/theme/create.js +8 -0
- package/dist/commands/ticket/bulk.js +2 -2
- package/dist/commands/ticket/complete.js +2 -2
- package/dist/commands/ticket/create.js +21 -0
- package/dist/commands/ticket/delete.js +8 -0
- package/dist/commands/ticket/edit.js +25 -0
- package/dist/commands/ticket/index.js +2 -2
- package/dist/commands/ticket/link/block.d.ts +15 -0
- package/dist/commands/ticket/link/block.js +95 -0
- package/dist/commands/ticket/link/index.d.ts +14 -0
- package/dist/commands/ticket/link/index.js +96 -0
- package/dist/commands/ticket/list.d.ts +1 -0
- package/dist/commands/ticket/list.js +6 -0
- package/dist/commands/ticket/move.js +25 -2
- package/dist/commands/ticket/resolve.js +4 -5
- package/dist/commands/ticket/show.d.ts +13 -0
- package/dist/commands/ticket/show.js +16 -0
- package/dist/commands/ticket/template/apply.d.ts +26 -0
- package/dist/commands/ticket/template/apply.js +14 -0
- package/dist/commands/ticket/template/delete.d.ts +18 -0
- package/dist/commands/ticket/template/delete.js +61 -0
- package/dist/commands/ticket/template/list.d.ts +17 -0
- package/dist/commands/ticket/template/list.js +78 -0
- package/dist/commands/ticket/template/save.d.ts +17 -0
- package/dist/commands/ticket/template/save.js +97 -0
- package/dist/commands/ticket/view.js +30 -0
- package/dist/commands/work/index.js +4 -0
- package/dist/commands/work/ready.js +17 -0
- package/dist/commands/work/resolve.js +1 -1
- package/dist/commands/work/spawn.js +4 -4
- package/dist/commands/work/start.d.ts +1 -0
- package/dist/commands/work/start.js +203 -93
- package/dist/commands/work/status.d.ts +14 -0
- package/dist/commands/work/status.js +60 -0
- package/dist/commands/workflow/index.js +2 -1
- package/dist/commands/workflow/show.d.ts +13 -0
- package/dist/commands/workflow/show.js +16 -0
- package/dist/commands/workspace/add.js +15 -0
- package/dist/commands/workspace/list.js +2 -1
- package/dist/commands/workspace/prune.js +5 -5
- package/dist/lib/branch/index.d.ts +1 -0
- package/dist/lib/database/index.d.ts +1 -1
- package/dist/lib/database/index.js +20 -0
- package/dist/lib/execution/config.d.ts +15 -1
- package/dist/lib/execution/config.js +28 -0
- package/dist/lib/execution/devcontainer.js +3 -1
- package/dist/lib/execution/runners.d.ts +18 -2
- package/dist/lib/execution/runners.js +71 -29
- package/dist/lib/execution/session-utils.d.ts +11 -1
- package/dist/lib/execution/session-utils.js +26 -1
- package/dist/lib/execution/storage.d.ts +5 -0
- package/dist/lib/execution/storage.js +18 -3
- package/dist/lib/execution/types.d.ts +3 -0
- package/dist/lib/flags/resolver.js +1 -0
- package/dist/lib/mcp/helpers.d.ts +1 -2
- package/dist/lib/mcp/tools/board.js +4 -6
- package/dist/lib/mcp/tools/cli-passthrough.js +25 -6
- package/dist/lib/mcp/tools/diet.js +1 -0
- package/dist/lib/mcp/tools/epic.js +8 -3
- package/dist/lib/mcp/tools/index.d.ts +1 -0
- package/dist/lib/mcp/tools/index.js +1 -0
- package/dist/lib/mcp/tools/label.d.ts +6 -0
- package/dist/lib/mcp/tools/label.js +338 -0
- package/dist/lib/mcp/tools/spec.js +1 -1
- package/dist/lib/mcp/tools/ticket.js +57 -19
- package/dist/lib/mcp/tools/work.js +96 -6
- package/dist/lib/mcp/types.d.ts +10 -0
- package/dist/lib/multiline-input.js +8 -19
- package/dist/lib/pmo/base-command.d.ts +0 -1
- package/dist/lib/pmo/base-command.js +4 -5
- package/dist/lib/pmo/schema.d.ts +6 -0
- package/dist/lib/pmo/schema.js +44 -0
- package/dist/lib/pmo/storage/actions.js +1 -1
- package/dist/lib/pmo/storage/base.d.ts +6 -0
- package/dist/lib/pmo/storage/base.js +311 -52
- package/dist/lib/pmo/storage/index.d.ts +23 -1
- package/dist/lib/pmo/storage/index.js +59 -1
- package/dist/lib/pmo/storage/labels.d.ts +55 -0
- package/dist/lib/pmo/storage/labels.js +346 -0
- package/dist/lib/pmo/storage/tickets.js +17 -0
- package/dist/lib/pmo/storage/types.d.ts +25 -0
- package/dist/lib/pmo/types.d.ts +44 -0
- package/dist/lib/pmo/utils.js +1 -1
- package/dist/lib/prompt-command.d.ts +20 -0
- package/dist/lib/prompt-command.js +38 -2
- package/dist/lib/prompt-json.d.ts +36 -4
- package/dist/lib/prompt-json.js +129 -7
- package/dist/lib/styles.d.ts +37 -0
- package/dist/lib/styles.js +73 -0
- package/oclif.manifest.json +6399 -3799
- package/package.json +1 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
2
|
+
import { styles, formatPriority } from '../../lib/styles.js';
|
|
3
|
+
import { shouldOutputJson, outputSuccessAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
4
|
+
export default class WorkStatus extends PMOCommand {
|
|
5
|
+
static description = 'Show current work status (in-progress tickets)';
|
|
6
|
+
static examples = [
|
|
7
|
+
'<%= config.bin %> <%= command.id %>',
|
|
8
|
+
'<%= config.bin %> <%= command.id %> --json',
|
|
9
|
+
];
|
|
10
|
+
static flags = {
|
|
11
|
+
...pmoBaseFlags,
|
|
12
|
+
};
|
|
13
|
+
getPMOOptions() {
|
|
14
|
+
return { promptIfMultiple: false };
|
|
15
|
+
}
|
|
16
|
+
async execute() {
|
|
17
|
+
const { flags } = await this.parse(WorkStatus);
|
|
18
|
+
const projectId = flags.project;
|
|
19
|
+
const jsonMode = shouldOutputJson(flags);
|
|
20
|
+
// List all tickets, optionally filtered by project
|
|
21
|
+
const tickets = await this.storage.listTickets(projectId, { allProjects: !projectId });
|
|
22
|
+
const inProgress = tickets.filter(t => t.statusCategory === 'started' && t.assignee);
|
|
23
|
+
if (jsonMode) {
|
|
24
|
+
outputSuccessAsJson({
|
|
25
|
+
inProgressCount: inProgress.length,
|
|
26
|
+
tickets: inProgress.map(t => ({
|
|
27
|
+
id: t.id,
|
|
28
|
+
title: t.title,
|
|
29
|
+
assignee: t.assignee,
|
|
30
|
+
statusName: t.statusName,
|
|
31
|
+
priority: t.priority,
|
|
32
|
+
projectId: t.projectId,
|
|
33
|
+
branch: t.branch,
|
|
34
|
+
})),
|
|
35
|
+
}, createMetadata('work status', flags));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
// Interactive mode
|
|
39
|
+
if (inProgress.length === 0) {
|
|
40
|
+
this.log(styles.info('No in-progress work found.'));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
this.log(styles.title(`\nWork In Progress (${inProgress.length})`));
|
|
44
|
+
this.log(styles.muted('─'.repeat(60)));
|
|
45
|
+
for (const ticket of inProgress) {
|
|
46
|
+
const priority = formatPriority(ticket.priority);
|
|
47
|
+
this.log(` ${styles.code(ticket.id)} ${ticket.title} ${priority}`);
|
|
48
|
+
const details = [
|
|
49
|
+
ticket.assignee ? `Assignee: ${ticket.assignee}` : null,
|
|
50
|
+
ticket.statusName ? `Status: ${ticket.statusName}` : null,
|
|
51
|
+
ticket.projectId ? `Project: ${ticket.projectId}` : null,
|
|
52
|
+
ticket.branch ? `Branch: ${ticket.branch}` : null,
|
|
53
|
+
].filter(Boolean).join(' | ');
|
|
54
|
+
if (details) {
|
|
55
|
+
this.log(styles.muted(` ${details}`));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
this.log('');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
3
|
+
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
3
4
|
import { styles } from '../../lib/styles.js';
|
|
4
5
|
export default class Workflow extends PMOCommand {
|
|
5
6
|
static description = 'List all available workflows (alias for workflow list)';
|
|
@@ -31,7 +32,7 @@ export default class Workflow extends PMOCommand {
|
|
|
31
32
|
if (flags.custom)
|
|
32
33
|
filter.isBuiltin = false;
|
|
33
34
|
const workflows = await this.storage.listWorkflows(Object.keys(filter).length > 0 ? filter : undefined);
|
|
34
|
-
if (flags
|
|
35
|
+
if (shouldOutputJson(flags)) {
|
|
35
36
|
this.log(JSON.stringify(workflows, null, 2));
|
|
36
37
|
return;
|
|
37
38
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import WorkflowView from './view.js';
|
|
2
|
+
export default class WorkflowShow extends WorkflowView {
|
|
3
|
+
static description: string;
|
|
4
|
+
static hidden: boolean;
|
|
5
|
+
static args: {
|
|
6
|
+
id: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Args } from '@oclif/core';
|
|
2
|
+
import { pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
3
|
+
import WorkflowView from './view.js';
|
|
4
|
+
export default class WorkflowShow extends WorkflowView {
|
|
5
|
+
static description = 'View details of a workflow (alias for workflow view)';
|
|
6
|
+
static hidden = true;
|
|
7
|
+
static args = {
|
|
8
|
+
id: Args.string({
|
|
9
|
+
description: 'Workflow ID - prompts with dropdown if not provided',
|
|
10
|
+
required: false,
|
|
11
|
+
}),
|
|
12
|
+
};
|
|
13
|
+
static flags = {
|
|
14
|
+
...pmoBaseFlags,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -4,6 +4,7 @@ import * as fs from 'node:fs';
|
|
|
4
4
|
import * as path from 'node:path';
|
|
5
5
|
import { registerWorkspace, normalizePath, isWorkspaceRegistered, getWorkspaceNameFromPath, } from '../../lib/machine-config.js';
|
|
6
6
|
import { machineOutputFlags } from '../../lib/pmo/index.js';
|
|
7
|
+
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
7
8
|
export default class WorkspaceAdd extends Command {
|
|
8
9
|
static description = 'Register an existing workspace in the machine config';
|
|
9
10
|
static examples = [
|
|
@@ -26,6 +27,7 @@ export default class WorkspaceAdd extends Command {
|
|
|
26
27
|
};
|
|
27
28
|
async run() {
|
|
28
29
|
const { args, flags } = await this.parse(WorkspaceAdd);
|
|
30
|
+
const jsonMode = shouldOutputJson(flags);
|
|
29
31
|
// Normalize the path
|
|
30
32
|
const workspacePath = normalizePath(args.path);
|
|
31
33
|
// Check if path exists
|
|
@@ -56,6 +58,10 @@ export default class WorkspaceAdd extends Command {
|
|
|
56
58
|
}
|
|
57
59
|
// Check if already registered
|
|
58
60
|
if (isWorkspaceRegistered(workspacePath)) {
|
|
61
|
+
if (jsonMode) {
|
|
62
|
+
this.log(JSON.stringify({ type: 'success', result: { path: workspacePath, status: 'already_registered' } }));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
59
65
|
this.log(chalk.yellow(`Workspace is already registered: ${workspacePath}`));
|
|
60
66
|
this.log(chalk.gray('Use "prlt workspace use" to set it as active.'));
|
|
61
67
|
return;
|
|
@@ -65,11 +71,20 @@ export default class WorkspaceAdd extends Command {
|
|
|
65
71
|
// Register the workspace
|
|
66
72
|
try {
|
|
67
73
|
const entry = registerWorkspace(workspacePath, workspaceName, true);
|
|
74
|
+
if (jsonMode) {
|
|
75
|
+
this.log(JSON.stringify({ type: 'success', result: { name: entry.name, path: entry.path, registeredAt: entry.registeredAt, status: 'registered' } }));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
68
78
|
this.log(chalk.green(`Registered workspace: ${entry.name}`));
|
|
69
79
|
this.log(chalk.gray(` Path: ${entry.path}`));
|
|
70
80
|
this.log(chalk.gray(` Registered at: ${entry.registeredAt}`));
|
|
71
81
|
}
|
|
72
82
|
catch (error) {
|
|
83
|
+
if (jsonMode) {
|
|
84
|
+
this.log(JSON.stringify({ type: 'error', error: { code: 'REGISTER_FAILED', message: error.message } }));
|
|
85
|
+
this.exit(1);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
73
88
|
this.error(`Failed to register workspace: ${error.message}`);
|
|
74
89
|
}
|
|
75
90
|
}
|
|
@@ -5,6 +5,7 @@ import { machineOutputFlags } from '../../lib/pmo/index.js';
|
|
|
5
5
|
import { getRegisteredWorkspaces, getActiveWorkspace, } from '../../lib/machine-config.js';
|
|
6
6
|
import * as fs from 'node:fs';
|
|
7
7
|
import * as path from 'node:path';
|
|
8
|
+
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
8
9
|
export default class WorkspaceList extends Command {
|
|
9
10
|
static description = 'List all registered and discovered HQ workspaces';
|
|
10
11
|
static examples = [
|
|
@@ -78,7 +79,7 @@ export default class WorkspaceList extends Command {
|
|
|
78
79
|
});
|
|
79
80
|
}
|
|
80
81
|
// JSON output
|
|
81
|
-
if (flags
|
|
82
|
+
if (shouldOutputJson(flags)) {
|
|
82
83
|
const output = {
|
|
83
84
|
workspaces: workspaces.map((w) => ({
|
|
84
85
|
name: w.name,
|
|
@@ -5,7 +5,7 @@ import { PromptCommand } from '../../lib/prompt-command.js';
|
|
|
5
5
|
import { styles } from '../../lib/styles.js';
|
|
6
6
|
import { getRegisteredHeadquarters, unregisterHeadquarters, } from '../../lib/machine-config.js';
|
|
7
7
|
import { getWorkspaceAgents, removeAgentsFromDatabase, getDatabasePath, } from '../../lib/database/index.js';
|
|
8
|
-
import { outputConfirmationNeededAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
8
|
+
import { outputConfirmationNeededAsJson, createMetadata, shouldOutputJson, isNonTTY, } from '../../lib/prompt-json.js';
|
|
9
9
|
import { machineOutputFlags } from '../../lib/pmo/index.js';
|
|
10
10
|
export default class WorkspacePrune extends PromptCommand {
|
|
11
11
|
static description = 'Remove stale workspace entries and agents with deleted worktrees';
|
|
@@ -31,14 +31,14 @@ export default class WorkspacePrune extends PromptCommand {
|
|
|
31
31
|
const { flags } = await this.parse(WorkspacePrune);
|
|
32
32
|
// In non-TTY mode without --json (CI, scripts, piped), default to dry-run unless --force is set.
|
|
33
33
|
// In --json mode, we use confirmation_needed output instead of auto-dry-run so agents can review and confirm.
|
|
34
|
-
const
|
|
35
|
-
const effectiveDryRun = flags['dry-run'] || (!(flags
|
|
34
|
+
const nonTTY = isNonTTY();
|
|
35
|
+
const effectiveDryRun = flags['dry-run'] || (!shouldOutputJson(flags) && nonTTY && !flags.force);
|
|
36
36
|
// Find stale entries
|
|
37
37
|
const staleWorkspaces = this.findStaleWorkspaces();
|
|
38
38
|
const staleAgents = this.findStaleAgents();
|
|
39
39
|
const totalStale = staleWorkspaces.length + staleAgents.length;
|
|
40
40
|
// JSON output
|
|
41
|
-
if (flags
|
|
41
|
+
if (shouldOutputJson(flags)) {
|
|
42
42
|
const output = {
|
|
43
43
|
dryRun: effectiveDryRun,
|
|
44
44
|
staleWorkspaces: staleWorkspaces.map(w => ({
|
|
@@ -112,7 +112,7 @@ export default class WorkspacePrune extends PromptCommand {
|
|
|
112
112
|
this.log(styles.muted(` • ${staleAgents.length} agent record(s)`));
|
|
113
113
|
}
|
|
114
114
|
this.log('');
|
|
115
|
-
if (
|
|
115
|
+
if (nonTTY) {
|
|
116
116
|
this.log(styles.muted('Non-TTY environment detected. Run with --force to remove these entries.'));
|
|
117
117
|
}
|
|
118
118
|
else {
|
|
@@ -52,7 +52,7 @@ export interface AgentWorktree {
|
|
|
52
52
|
is_clean: boolean;
|
|
53
53
|
last_checked?: string;
|
|
54
54
|
}
|
|
55
|
-
export declare const CREATE_TABLES_SQL = "\n-- Core workspace metadata\nCREATE TABLE IF NOT EXISTS workspace (\n id INTEGER PRIMARY KEY CHECK (id = 1),\n type TEXT NOT NULL CHECK (type IN ('hq', 'workspace')),\n workspace_name TEXT NOT NULL,\n has_pmo BOOLEAN DEFAULT FALSE,\n active_theme_id TEXT,\n created_at TEXT NOT NULL,\n FOREIGN KEY (active_theme_id) REFERENCES agent_themes(id) ON DELETE SET NULL\n);\n\n-- Repository management\nCREATE TABLE IF NOT EXISTS repositories (\n name TEXT PRIMARY KEY,\n path TEXT NOT NULL,\n type TEXT DEFAULT 'main' CHECK (type IN ('main', 'dependency')),\n source_url TEXT,\n action TEXT CHECK (action IN ('clone', 'move', 'link')),\n added_at TEXT NOT NULL\n);\n\n-- Agent naming themes (optional)\nCREATE TABLE IF NOT EXISTS agent_themes (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL UNIQUE,\n display_name TEXT NOT NULL,\n description TEXT,\n builtin BOOLEAN DEFAULT FALSE,\n created_at TEXT NOT NULL\n);\n\n-- Names available within each theme\nCREATE TABLE IF NOT EXISTS agent_theme_names (\n theme_id TEXT NOT NULL,\n name TEXT NOT NULL,\n PRIMARY KEY (theme_id, name),\n FOREIGN KEY (theme_id) REFERENCES agent_themes(id) ON DELETE CASCADE\n);\n\n-- Agent instances in workspace\nCREATE TABLE IF NOT EXISTS agents (\n name TEXT PRIMARY KEY,\n type TEXT NOT NULL DEFAULT 'persistent' CHECK (type IN ('persistent', 'ephemeral')),\n status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'cleaned')),\n base_name TEXT,\n theme_id TEXT,\n worktree_path TEXT,\n mount_mode TEXT NOT NULL DEFAULT 'worktree' CHECK (mount_mode IN ('worktree', 'clone')),\n created_at TEXT NOT NULL,\n cleaned_at TEXT,\n FOREIGN KEY (theme_id) REFERENCES agent_themes(id) ON DELETE SET NULL\n);\n\n-- Agent-owned worktrees\nCREATE TABLE IF NOT EXISTS agent_worktrees (\n agent_name TEXT NOT NULL,\n repo_name TEXT NOT NULL,\n worktree_path TEXT NOT NULL,\n branch TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (agent_name, repo_name),\n FOREIGN KEY (agent_name) REFERENCES agents(name) ON DELETE CASCADE,\n FOREIGN KEY (repo_name) REFERENCES repositories(name) ON DELETE CASCADE\n);\n\n-- Workspace-level settings (key-value store)\nCREATE TABLE IF NOT EXISTS workspace_settings (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n);\n\n-- =============================================================================\n-- Indexes\n-- =============================================================================\n\nCREATE INDEX IF NOT EXISTS idx_worktrees_agent ON agent_worktrees(agent_name);\nCREATE INDEX IF NOT EXISTS idx_worktrees_repo ON agent_worktrees(repo_name);\nCREATE INDEX IF NOT EXISTS idx_theme_names_theme ON agent_theme_names(theme_id);\nCREATE INDEX IF NOT EXISTS idx_agents_theme ON agents(theme_id);\n";
|
|
55
|
+
export declare const CREATE_TABLES_SQL = "\n-- Core workspace metadata\nCREATE TABLE IF NOT EXISTS workspace (\n id INTEGER PRIMARY KEY CHECK (id = 1),\n type TEXT NOT NULL CHECK (type IN ('hq', 'workspace')),\n workspace_name TEXT NOT NULL,\n has_pmo BOOLEAN DEFAULT FALSE,\n active_theme_id TEXT,\n created_at TEXT NOT NULL,\n FOREIGN KEY (active_theme_id) REFERENCES agent_themes(id) ON DELETE SET NULL\n);\n\n-- Repository management\nCREATE TABLE IF NOT EXISTS repositories (\n name TEXT PRIMARY KEY,\n path TEXT NOT NULL,\n type TEXT DEFAULT 'main' CHECK (type IN ('main', 'dependency')),\n source_url TEXT,\n action TEXT CHECK (action IN ('clone', 'move', 'link')),\n added_at TEXT NOT NULL\n);\n\n-- Agent naming themes (optional)\nCREATE TABLE IF NOT EXISTS agent_themes (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL UNIQUE,\n display_name TEXT NOT NULL,\n description TEXT,\n builtin BOOLEAN DEFAULT FALSE,\n created_at TEXT NOT NULL\n);\n\n-- Names available within each theme\nCREATE TABLE IF NOT EXISTS agent_theme_names (\n theme_id TEXT NOT NULL,\n name TEXT NOT NULL,\n PRIMARY KEY (theme_id, name),\n FOREIGN KEY (theme_id) REFERENCES agent_themes(id) ON DELETE CASCADE\n);\n\n-- Agent instances in workspace\nCREATE TABLE IF NOT EXISTS agents (\n name TEXT PRIMARY KEY,\n type TEXT NOT NULL DEFAULT 'persistent' CHECK (type IN ('persistent', 'ephemeral')),\n status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'cleaned')),\n base_name TEXT,\n theme_id TEXT,\n worktree_path TEXT,\n mount_mode TEXT NOT NULL DEFAULT 'worktree' CHECK (mount_mode IN ('worktree', 'clone')),\n created_at TEXT NOT NULL,\n cleaned_at TEXT,\n FOREIGN KEY (theme_id) REFERENCES agent_themes(id) ON DELETE SET NULL\n);\n\n-- Agent-owned worktrees\nCREATE TABLE IF NOT EXISTS agent_worktrees (\n agent_name TEXT NOT NULL,\n repo_name TEXT NOT NULL,\n worktree_path TEXT NOT NULL,\n branch TEXT NOT NULL,\n created_at TEXT NOT NULL,\n last_commit_hash TEXT,\n commits_ahead INTEGER NOT NULL DEFAULT 0,\n is_clean INTEGER NOT NULL DEFAULT 1,\n last_checked TEXT,\n PRIMARY KEY (agent_name, repo_name),\n FOREIGN KEY (agent_name) REFERENCES agents(name) ON DELETE CASCADE,\n FOREIGN KEY (repo_name) REFERENCES repositories(name) ON DELETE CASCADE\n);\n\n-- Workspace-level settings (key-value store)\nCREATE TABLE IF NOT EXISTS workspace_settings (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n);\n\n-- =============================================================================\n-- Indexes\n-- =============================================================================\n\nCREATE INDEX IF NOT EXISTS idx_worktrees_agent ON agent_worktrees(agent_name);\nCREATE INDEX IF NOT EXISTS idx_worktrees_repo ON agent_worktrees(repo_name);\nCREATE INDEX IF NOT EXISTS idx_theme_names_theme ON agent_theme_names(theme_id);\nCREATE INDEX IF NOT EXISTS idx_agents_theme ON agents(theme_id);\n";
|
|
56
56
|
/**
|
|
57
57
|
* Get the database path for a workspace
|
|
58
58
|
*/
|
|
@@ -64,6 +64,10 @@ CREATE TABLE IF NOT EXISTS agent_worktrees (
|
|
|
64
64
|
worktree_path TEXT NOT NULL,
|
|
65
65
|
branch TEXT NOT NULL,
|
|
66
66
|
created_at TEXT NOT NULL,
|
|
67
|
+
last_commit_hash TEXT,
|
|
68
|
+
commits_ahead INTEGER NOT NULL DEFAULT 0,
|
|
69
|
+
is_clean INTEGER NOT NULL DEFAULT 1,
|
|
70
|
+
last_checked TEXT,
|
|
67
71
|
PRIMARY KEY (agent_name, repo_name),
|
|
68
72
|
FOREIGN KEY (agent_name) REFERENCES agents(name) ON DELETE CASCADE,
|
|
69
73
|
FOREIGN KEY (repo_name) REFERENCES repositories(name) ON DELETE CASCADE
|
|
@@ -184,6 +188,22 @@ export function openWorkspaceDatabase(workspacePath) {
|
|
|
184
188
|
catch {
|
|
185
189
|
// Ignore migration errors - table might not exist yet
|
|
186
190
|
}
|
|
191
|
+
// Migration: add missing columns to agent_worktrees table (TKT-1014)
|
|
192
|
+
try {
|
|
193
|
+
const worktreesTableExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='agent_worktrees'").get();
|
|
194
|
+
if (worktreesTableExists) {
|
|
195
|
+
const worktreeTableInfo = db.prepare("PRAGMA table_info(agent_worktrees)").all();
|
|
196
|
+
if (!worktreeTableInfo.some(col => col.name === 'commits_ahead')) {
|
|
197
|
+
db.exec("ALTER TABLE agent_worktrees ADD COLUMN last_commit_hash TEXT");
|
|
198
|
+
db.exec("ALTER TABLE agent_worktrees ADD COLUMN commits_ahead INTEGER NOT NULL DEFAULT 0");
|
|
199
|
+
db.exec("ALTER TABLE agent_worktrees ADD COLUMN is_clean INTEGER NOT NULL DEFAULT 1");
|
|
200
|
+
db.exec("ALTER TABLE agent_worktrees ADD COLUMN last_checked TEXT");
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
// Ignore migration errors - table might not exist yet or columns already exist
|
|
206
|
+
}
|
|
187
207
|
// Migration: add mount_mode column to agents table (TKT-686)
|
|
188
208
|
try {
|
|
189
209
|
const agentsTableInfo = db.prepare("PRAGMA table_info(agents)").all();
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Uses the workspace_settings table (not pmo_settings - execution is workspace-level).
|
|
6
6
|
*/
|
|
7
7
|
import Database from 'better-sqlite3';
|
|
8
|
-
import { ExecutionConfig, TerminalApp, Shell, DisplayMode, OutputMode, ExecutionEnvironment } from './types.js';
|
|
8
|
+
import { ExecutionConfig, TerminalApp, Shell, DisplayMode, OutputMode, ExecutionEnvironment, AuthMethod } from './types.js';
|
|
9
9
|
import { type JsonFlags } from '../prompt-json.js';
|
|
10
10
|
declare const CONFIG_KEYS: {
|
|
11
11
|
terminalApp: string;
|
|
@@ -28,6 +28,7 @@ declare const CONFIG_KEYS: {
|
|
|
28
28
|
vmKeyPath: string;
|
|
29
29
|
vmSyncMethod: string;
|
|
30
30
|
coderName: string;
|
|
31
|
+
authMethod: string;
|
|
31
32
|
};
|
|
32
33
|
/**
|
|
33
34
|
* Load execution config from database, merging with defaults
|
|
@@ -55,6 +56,19 @@ export declare function saveTmuxControlMode(db: Database.Database, enabled: bool
|
|
|
55
56
|
* When enabled, new terminal tabs open without stealing focus from current window.
|
|
56
57
|
*/
|
|
57
58
|
export declare function saveTerminalOpenInBackground(db: Database.Database, enabled: boolean): void;
|
|
59
|
+
/**
|
|
60
|
+
* Save auth method preference (oauth or apikey)
|
|
61
|
+
*/
|
|
62
|
+
export declare function saveAuthMethod(db: Database.Database, method: AuthMethod): void;
|
|
63
|
+
/**
|
|
64
|
+
* Get saved auth method preference.
|
|
65
|
+
* Returns null if no preference has been saved (user should be prompted).
|
|
66
|
+
*/
|
|
67
|
+
export declare function getAuthMethod(db: Database.Database): AuthMethod | null;
|
|
68
|
+
/**
|
|
69
|
+
* Clear saved auth method preference (will prompt again next time)
|
|
70
|
+
*/
|
|
71
|
+
export declare function clearAuthMethod(db: Database.Database): void;
|
|
58
72
|
/**
|
|
59
73
|
* Check if terminal app preference has been set
|
|
60
74
|
*/
|
|
@@ -32,6 +32,7 @@ const CONFIG_KEYS = {
|
|
|
32
32
|
vmKeyPath: 'execution.vm.key_path',
|
|
33
33
|
vmSyncMethod: 'execution.vm.sync_method',
|
|
34
34
|
coderName: 'coder.name',
|
|
35
|
+
authMethod: 'execution.auth_method',
|
|
35
36
|
};
|
|
36
37
|
/**
|
|
37
38
|
* Get a setting value from the database
|
|
@@ -97,6 +98,11 @@ export function loadExecutionConfig(db) {
|
|
|
97
98
|
if (sandboxed !== null) {
|
|
98
99
|
config.sandboxed = sandboxed === 'true';
|
|
99
100
|
}
|
|
101
|
+
// Load auth method preference
|
|
102
|
+
const authMethod = getSetting(db, CONFIG_KEYS.authMethod);
|
|
103
|
+
if (authMethod) {
|
|
104
|
+
config.authMethod = authMethod;
|
|
105
|
+
}
|
|
100
106
|
// Load tmux settings
|
|
101
107
|
const tmuxSession = getSetting(db, CONFIG_KEYS.tmuxSession);
|
|
102
108
|
if (tmuxSession) {
|
|
@@ -178,6 +184,28 @@ export function saveTmuxControlMode(db, enabled) {
|
|
|
178
184
|
export function saveTerminalOpenInBackground(db, enabled) {
|
|
179
185
|
setSetting(db, CONFIG_KEYS.terminalOpenInBackground, enabled.toString());
|
|
180
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* Save auth method preference (oauth or apikey)
|
|
189
|
+
*/
|
|
190
|
+
export function saveAuthMethod(db, method) {
|
|
191
|
+
setSetting(db, CONFIG_KEYS.authMethod, method);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Get saved auth method preference.
|
|
195
|
+
* Returns null if no preference has been saved (user should be prompted).
|
|
196
|
+
*/
|
|
197
|
+
export function getAuthMethod(db) {
|
|
198
|
+
const value = getSetting(db, CONFIG_KEYS.authMethod);
|
|
199
|
+
if (value === 'oauth' || value === 'apikey')
|
|
200
|
+
return value;
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Clear saved auth method preference (will prompt again next time)
|
|
205
|
+
*/
|
|
206
|
+
export function clearAuthMethod(db) {
|
|
207
|
+
db.prepare(`DELETE FROM ${SETTINGS_TABLE} WHERE key = ?`).run(CONFIG_KEYS.authMethod);
|
|
208
|
+
}
|
|
181
209
|
/**
|
|
182
210
|
* Check if terminal app preference has been set
|
|
183
211
|
*/
|
|
@@ -96,7 +96,9 @@ export function generateDevcontainerJson(options, config) {
|
|
|
96
96
|
mounts,
|
|
97
97
|
containerEnv: {
|
|
98
98
|
DEVCONTAINER: 'true',
|
|
99
|
-
|
|
99
|
+
// NOTE: ANTHROPIC_API_KEY is intentionally NOT passed by default.
|
|
100
|
+
// Claude Code prefers API key over OAuth, so passing it would cause agents
|
|
101
|
+
// to burn API credits instead of using Max subscription via OAuth.
|
|
100
102
|
// GH_TOKEN enables gh CLI in container (for PR creation, etc.)
|
|
101
103
|
GH_TOKEN: '${localEnv:GH_TOKEN}',
|
|
102
104
|
GITHUB_TOKEN: '${localEnv:GITHUB_TOKEN}',
|
|
@@ -41,13 +41,29 @@ export declare function buildTmuxAttachCommand(useControlMode: boolean, includeU
|
|
|
41
41
|
*/
|
|
42
42
|
export declare function configureITermTmuxPreferences(mode: 'tab' | 'window'): void;
|
|
43
43
|
export declare function configureITermTmuxWindowMode(mode: 'tab' | 'window'): void;
|
|
44
|
+
/**
|
|
45
|
+
* Build the tmux script that runs inside the container.
|
|
46
|
+
* In background mode: kills PID 1 (sleep infinity) after Claude exits to stop/remove container.
|
|
47
|
+
* In terminal/foreground mode: drops into exec bash for user inspection.
|
|
48
|
+
*/
|
|
49
|
+
export declare function buildTmuxScript(sessionName: string, claudeCmd: string, displayMode: DisplayMode): string;
|
|
50
|
+
/**
|
|
51
|
+
* Get the auto-remove flags for docker run based on display mode.
|
|
52
|
+
* Background mode containers get --rm so Docker removes them when they stop.
|
|
53
|
+
*/
|
|
54
|
+
export declare function getDockerAutoRemoveFlags(displayMode: DisplayMode): string[];
|
|
44
55
|
/**
|
|
45
56
|
* Check if the claude-credentials Docker volume exists.
|
|
46
57
|
*/
|
|
47
58
|
export declare function credentialsVolumeExists(): boolean;
|
|
48
59
|
/**
|
|
49
|
-
* Check if valid Claude credentials exist in the Docker volume.
|
|
50
|
-
* Returns true if credentials
|
|
60
|
+
* Check if valid Claude OAuth credentials exist in the Docker volume.
|
|
61
|
+
* Returns true if OAuth credentials are stored (even if access token is expired,
|
|
62
|
+
* since Claude Code handles refresh internally using stored refresh tokens).
|
|
63
|
+
*
|
|
64
|
+
* NOTE: This intentionally does NOT check for ANTHROPIC_API_KEY. If the user
|
|
65
|
+
* has an API key but no OAuth credentials, we want to prompt them to set up
|
|
66
|
+
* OAuth (which uses their Max subscription) rather than silently burning API credits.
|
|
51
67
|
*/
|
|
52
68
|
export declare function dockerCredentialsExist(): boolean;
|
|
53
69
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable max-lines -- runner implementations require cohesive logic */
|
|
1
2
|
/**
|
|
2
3
|
* Execution Runners
|
|
3
4
|
*
|
|
@@ -86,6 +87,47 @@ export function configureITermTmuxWindowMode(mode) {
|
|
|
86
87
|
configureITermTmuxPreferences(mode);
|
|
87
88
|
}
|
|
88
89
|
// =============================================================================
|
|
90
|
+
// Background Mode Cleanup Helpers (TKT-988)
|
|
91
|
+
// =============================================================================
|
|
92
|
+
/**
|
|
93
|
+
* Build the tmux script that runs inside the container.
|
|
94
|
+
* In background mode: kills PID 1 (sleep infinity) after Claude exits to stop/remove container.
|
|
95
|
+
* In terminal/foreground mode: drops into exec bash for user inspection.
|
|
96
|
+
*/
|
|
97
|
+
export function buildTmuxScript(sessionName, claudeCmd, displayMode) {
|
|
98
|
+
if (displayMode === 'background') {
|
|
99
|
+
return `#!/bin/bash
|
|
100
|
+
export TERM=xterm-256color
|
|
101
|
+
export COLORTERM=truecolor
|
|
102
|
+
unset CI
|
|
103
|
+
echo "🚀 Starting: ${sessionName}"
|
|
104
|
+
echo ""
|
|
105
|
+
${claudeCmd}
|
|
106
|
+
echo ""
|
|
107
|
+
echo "✅ Agent work complete. Cleaning up container..."
|
|
108
|
+
kill 1
|
|
109
|
+
`;
|
|
110
|
+
}
|
|
111
|
+
return `#!/bin/bash
|
|
112
|
+
export TERM=xterm-256color
|
|
113
|
+
export COLORTERM=truecolor
|
|
114
|
+
unset CI
|
|
115
|
+
echo "🚀 Starting: ${sessionName}"
|
|
116
|
+
echo ""
|
|
117
|
+
${claudeCmd}
|
|
118
|
+
echo ""
|
|
119
|
+
echo "✅ Agent work complete. Press Enter to close or run more commands."
|
|
120
|
+
exec bash
|
|
121
|
+
`;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Get the auto-remove flags for docker run based on display mode.
|
|
125
|
+
* Background mode containers get --rm so Docker removes them when they stop.
|
|
126
|
+
*/
|
|
127
|
+
export function getDockerAutoRemoveFlags(displayMode) {
|
|
128
|
+
return displayMode === 'background' ? ['--rm'] : [];
|
|
129
|
+
}
|
|
130
|
+
// =============================================================================
|
|
89
131
|
// Docker Credential Helpers
|
|
90
132
|
// =============================================================================
|
|
91
133
|
const CLAUDE_CREDENTIALS_VOLUME = 'claude-credentials';
|
|
@@ -102,19 +144,23 @@ export function credentialsVolumeExists() {
|
|
|
102
144
|
}
|
|
103
145
|
}
|
|
104
146
|
/**
|
|
105
|
-
* Check if valid Claude credentials exist in the Docker volume.
|
|
106
|
-
* Returns true if credentials
|
|
147
|
+
* Check if valid Claude OAuth credentials exist in the Docker volume.
|
|
148
|
+
* Returns true if OAuth credentials are stored (even if access token is expired,
|
|
149
|
+
* since Claude Code handles refresh internally using stored refresh tokens).
|
|
150
|
+
*
|
|
151
|
+
* NOTE: This intentionally does NOT check for ANTHROPIC_API_KEY. If the user
|
|
152
|
+
* has an API key but no OAuth credentials, we want to prompt them to set up
|
|
153
|
+
* OAuth (which uses their Max subscription) rather than silently burning API credits.
|
|
107
154
|
*/
|
|
108
155
|
export function dockerCredentialsExist() {
|
|
109
156
|
try {
|
|
110
157
|
const result = execSync(`docker run --rm -v ${CLAUDE_CREDENTIALS_VOLUME}:/data alpine cat /data/.credentials.json 2>/dev/null`, { stdio: 'pipe', encoding: 'utf-8' });
|
|
111
158
|
const creds = JSON.parse(result);
|
|
112
|
-
if
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
159
|
+
// Check if OAuth credentials exist. Don't check expiration because
|
|
160
|
+
// access tokens are short-lived but Claude Code handles token refresh
|
|
161
|
+
// internally using stored refresh tokens.
|
|
162
|
+
if (creds.claudeAiOauth?.accessToken) {
|
|
163
|
+
return true;
|
|
118
164
|
}
|
|
119
165
|
return false;
|
|
120
166
|
}
|
|
@@ -735,7 +781,7 @@ function imageExists(imageName) {
|
|
|
735
781
|
* Create and start a Docker container for an agent.
|
|
736
782
|
* Uses raw Docker commands instead of devcontainer CLI.
|
|
737
783
|
*/
|
|
738
|
-
function createDockerContainer(context, containerName, imageName, config) {
|
|
784
|
+
function createDockerContainer(context, containerName, imageName, config, displayMode = 'terminal') {
|
|
739
785
|
// Build mount flags
|
|
740
786
|
// KEY: Use a named Docker volume for Claude credentials - this is how devcontainer.json
|
|
741
787
|
// was handling it. The volume persists across containers, so login once = logged in everywhere.
|
|
@@ -767,7 +813,10 @@ function createDockerContainer(context, containerName, imageName, config) {
|
|
|
767
813
|
`-e PRLT_HQ_PATH=/hq`,
|
|
768
814
|
`-e PRLT_AGENT_NAME="${context.agentName}"`,
|
|
769
815
|
`-e PRLT_HOST_PATH="${context.agentDir}"`,
|
|
770
|
-
|
|
816
|
+
// Only pass ANTHROPIC_API_KEY if the user explicitly chose to use it (no OAuth creds).
|
|
817
|
+
// Claude Code prefers API key over OAuth, so passing it would cause agents to burn
|
|
818
|
+
// API credits instead of using Max subscription.
|
|
819
|
+
...(context.useApiKey && process.env.ANTHROPIC_API_KEY ? [`-e ANTHROPIC_API_KEY="${process.env.ANTHROPIC_API_KEY}"`] : []),
|
|
771
820
|
...(process.env.GITHUB_TOKEN ? [`-e GITHUB_TOKEN="${process.env.GITHUB_TOKEN}"`] : []),
|
|
772
821
|
...(process.env.GH_TOKEN ? [`-e GH_TOKEN="${process.env.GH_TOKEN}"`] : []),
|
|
773
822
|
// NOTE: Do NOT pass CLAUDE_CODE_OAUTH_TOKEN - it overrides credentials file
|
|
@@ -786,12 +835,16 @@ function createDockerContainer(context, containerName, imageName, config) {
|
|
|
786
835
|
'--cap-add=NET_RAW', // For firewall setup
|
|
787
836
|
// Note: After firewall is set up, the container is network-restricted
|
|
788
837
|
];
|
|
838
|
+
// Auto-remove container on stop for background mode (R5)
|
|
839
|
+
// Background containers should be cleaned up after work completes — nobody will attach to inspect
|
|
840
|
+
const autoRemoveFlags = getDockerAutoRemoveFlags(displayMode);
|
|
789
841
|
try {
|
|
790
842
|
const createCmd = [
|
|
791
843
|
'docker run -d',
|
|
792
844
|
`--name ${containerName}`,
|
|
793
845
|
'--user node',
|
|
794
846
|
'-w /workspace',
|
|
847
|
+
...autoRemoveFlags,
|
|
795
848
|
...mounts,
|
|
796
849
|
...envVars,
|
|
797
850
|
...resourceFlags,
|
|
@@ -890,7 +943,7 @@ function runContainerSetup(containerId, sandboxed = true) {
|
|
|
890
943
|
* Builds image and creates container if needed.
|
|
891
944
|
* Returns the container ID if successful, null otherwise.
|
|
892
945
|
*/
|
|
893
|
-
function ensureDockerContainer(context, config) {
|
|
946
|
+
function ensureDockerContainer(context, config, displayMode = 'terminal') {
|
|
894
947
|
const containerName = getContainerName(context.agentName);
|
|
895
948
|
const imageName = getImageName(context.agentName);
|
|
896
949
|
// Always create fresh container to ensure mounts are up-to-date
|
|
@@ -919,7 +972,7 @@ function ensureDockerContainer(context, config) {
|
|
|
919
972
|
}
|
|
920
973
|
// Create and start container
|
|
921
974
|
console.debug(`[runners:docker] Creating container ${containerName}`);
|
|
922
|
-
if (!createDockerContainer(context, containerName, imageName, config)) {
|
|
975
|
+
if (!createDockerContainer(context, containerName, imageName, config, displayMode)) {
|
|
923
976
|
return null;
|
|
924
977
|
}
|
|
925
978
|
const containerId = getContainerId(containerName);
|
|
@@ -1097,7 +1150,7 @@ export async function runDevcontainer(context, executor, config, displayMode = '
|
|
|
1097
1150
|
copyClaudeCredentials(context.agentDir);
|
|
1098
1151
|
// Start or reuse container using raw Docker commands
|
|
1099
1152
|
// No devcontainer CLI required!
|
|
1100
|
-
const containerId = ensureDockerContainer(context, config);
|
|
1153
|
+
const containerId = ensureDockerContainer(context, config, displayMode);
|
|
1101
1154
|
if (!containerId) {
|
|
1102
1155
|
return {
|
|
1103
1156
|
success: false,
|
|
@@ -1448,21 +1501,10 @@ async function runDevcontainerInTmux(context, devcontainerCmd, config, displayMo
|
|
|
1448
1501
|
// Extract the claude command from the devcontainer command
|
|
1449
1502
|
const cmdMatch = devcontainerCmd.match(/bash -c '(.+)'$/);
|
|
1450
1503
|
const claudeCmd = cmdMatch ? cmdMatch[1] : devcontainerCmd;
|
|
1451
|
-
// Create a script inside the container that runs claude
|
|
1452
|
-
//
|
|
1453
|
-
//
|
|
1454
|
-
|
|
1455
|
-
const tmuxScript = `#!/bin/bash
|
|
1456
|
-
export TERM=xterm-256color
|
|
1457
|
-
export COLORTERM=truecolor
|
|
1458
|
-
unset CI
|
|
1459
|
-
echo "🚀 Starting: ${sessionName}"
|
|
1460
|
-
echo ""
|
|
1461
|
-
${claudeCmd}
|
|
1462
|
-
echo ""
|
|
1463
|
-
echo "✅ Agent work complete. Press Enter to close or run more commands."
|
|
1464
|
-
exec bash
|
|
1465
|
-
`;
|
|
1504
|
+
// Create a script inside the container that runs claude
|
|
1505
|
+
// Background mode (R1): kills PID 1 to stop container after completion
|
|
1506
|
+
// Terminal/foreground mode (R2): drops into exec bash for user inspection
|
|
1507
|
+
const tmuxScript = buildTmuxScript(sessionName, claudeCmd, displayMode);
|
|
1466
1508
|
const scriptPath = `/tmp/prlt-${sessionName}.sh`;
|
|
1467
1509
|
// Write script and start tmux session inside container
|
|
1468
1510
|
// IMPORTANT: We create the session with bash first, then send keys to run the script.
|
|
@@ -1509,7 +1551,7 @@ exec bash
|
|
|
1509
1551
|
try {
|
|
1510
1552
|
execSync(`docker exec ${actualContainerId} tmux has-session -t "${sessionName}" 2>&1`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
1511
1553
|
}
|
|
1512
|
-
catch
|
|
1554
|
+
catch {
|
|
1513
1555
|
return {
|
|
1514
1556
|
success: false,
|
|
1515
1557
|
error: `Failed to verify tmux session "${sessionName}" inside container. The session may not have started correctly.`,
|
|
@@ -2,8 +2,18 @@
|
|
|
2
2
|
* Session Utilities
|
|
3
3
|
*
|
|
4
4
|
* Shared utilities for tmux session naming, parsing, and discovery.
|
|
5
|
-
* Used by session/list.ts and session/
|
|
5
|
+
* Used by session/list.ts, session/attach.ts, session/health.ts, and session/peek.ts commands.
|
|
6
6
|
*/
|
|
7
|
+
/**
|
|
8
|
+
* Capture the last N lines from a tmux pane.
|
|
9
|
+
* Supports both host tmux sessions and container tmux sessions (via docker exec).
|
|
10
|
+
*
|
|
11
|
+
* @param sessionId - The tmux session ID to capture from
|
|
12
|
+
* @param lines - Number of scrollback lines to capture
|
|
13
|
+
* @param containerId - Optional container ID for container-based sessions
|
|
14
|
+
* @returns The captured pane content, or null if capture fails
|
|
15
|
+
*/
|
|
16
|
+
export declare function captureTmuxPane(sessionId: string, lines: number, containerId?: string): string | null;
|
|
7
17
|
/**
|
|
8
18
|
* Known action names used in session naming.
|
|
9
19
|
* These are the actions defined in pmo/actions/ that may be used when spawning agents.
|
|
@@ -2,9 +2,34 @@
|
|
|
2
2
|
* Session Utilities
|
|
3
3
|
*
|
|
4
4
|
* Shared utilities for tmux session naming, parsing, and discovery.
|
|
5
|
-
* Used by session/list.ts and session/
|
|
5
|
+
* Used by session/list.ts, session/attach.ts, session/health.ts, and session/peek.ts commands.
|
|
6
6
|
*/
|
|
7
7
|
import { execSync } from 'node:child_process';
|
|
8
|
+
/**
|
|
9
|
+
* Capture the last N lines from a tmux pane.
|
|
10
|
+
* Supports both host tmux sessions and container tmux sessions (via docker exec).
|
|
11
|
+
*
|
|
12
|
+
* @param sessionId - The tmux session ID to capture from
|
|
13
|
+
* @param lines - Number of scrollback lines to capture
|
|
14
|
+
* @param containerId - Optional container ID for container-based sessions
|
|
15
|
+
* @returns The captured pane content, or null if capture fails
|
|
16
|
+
*/
|
|
17
|
+
export function captureTmuxPane(sessionId, lines, containerId) {
|
|
18
|
+
try {
|
|
19
|
+
const captureCmd = `tmux capture-pane -t "${sessionId}" -p -S -${lines}`;
|
|
20
|
+
if (containerId) {
|
|
21
|
+
return execSync(`docker exec ${containerId} bash -c '${captureCmd}'`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 10000 }).trim();
|
|
22
|
+
}
|
|
23
|
+
return execSync(captureCmd, {
|
|
24
|
+
encoding: 'utf-8',
|
|
25
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
26
|
+
timeout: 5000,
|
|
27
|
+
}).trim();
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
8
33
|
/**
|
|
9
34
|
* Known action names used in session naming.
|
|
10
35
|
* These are the actions defined in pmo/actions/ that may be used when spawning agents.
|