@proletariat/cli 0.3.26 → 0.3.28
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/bin/dev.js +7 -0
- package/bin/run.js +7 -0
- package/dist/commands/action/show.js +7 -1
- package/dist/commands/agent/shell.js +24 -10
- package/dist/commands/branch/list.js +14 -11
- package/dist/commands/branch/validate.js +10 -1
- package/dist/commands/claude.js +12 -40
- package/dist/commands/docker/clean.js +7 -9
- package/dist/commands/docker/index.js +5 -4
- package/dist/commands/docker/list.d.ts +1 -0
- package/dist/commands/docker/list.js +31 -17
- package/dist/commands/docker/status.d.ts +3 -1
- package/dist/commands/docker/status.js +28 -2
- package/dist/commands/docker/sync.js +7 -6
- package/dist/commands/epic/list.js +17 -2
- package/dist/commands/execution/list.js +25 -17
- package/dist/commands/pmo/init.js +22 -3
- package/dist/commands/repo/list.js +14 -8
- package/dist/commands/repo/view.js +2 -1
- package/dist/commands/roadmap/list.js +16 -1
- package/dist/commands/session/health.js +11 -10
- package/dist/commands/session/list.js +15 -8
- package/dist/commands/staff/list.d.ts +3 -1
- package/dist/commands/staff/list.js +15 -1
- package/dist/commands/theme/list.d.ts +3 -0
- package/dist/commands/theme/list.js +25 -0
- package/dist/commands/ticket/complete.js +4 -1
- package/dist/commands/ticket/create.d.ts +1 -0
- package/dist/commands/ticket/create.js +30 -0
- package/dist/commands/ticket/delete.js +3 -3
- package/dist/commands/ticket/edit.js +2 -2
- package/dist/commands/ticket/list.js +24 -5
- package/dist/commands/ticket/move.js +4 -1
- package/dist/commands/ticket/view.js +4 -2
- package/dist/commands/whoami.d.ts +3 -0
- package/dist/commands/whoami.js +22 -5
- package/dist/commands/work/complete.js +2 -2
- package/dist/commands/work/ready.js +2 -2
- package/dist/commands/work/revise.js +2 -2
- package/dist/commands/work/spawn.js +6 -21
- package/dist/commands/work/start.js +10 -25
- package/dist/commands/work/watch.js +57 -33
- package/dist/commands/workspace/prune.d.ts +3 -2
- package/dist/commands/workspace/prune.js +70 -10
- package/dist/lib/agents/commands.js +4 -0
- package/dist/lib/agents/index.js +12 -0
- package/dist/lib/execution/devcontainer.d.ts +4 -0
- package/dist/lib/execution/devcontainer.js +88 -3
- package/dist/lib/mcp/helpers.d.ts +15 -0
- package/dist/lib/mcp/helpers.js +15 -0
- package/dist/lib/mcp/tools/action.js +5 -5
- package/dist/lib/mcp/tools/board.js +7 -7
- package/dist/lib/mcp/tools/category.js +5 -5
- package/dist/lib/mcp/tools/cli-passthrough.js +30 -30
- package/dist/lib/mcp/tools/epic.js +8 -8
- package/dist/lib/mcp/tools/phase.js +7 -7
- package/dist/lib/mcp/tools/project.js +10 -10
- package/dist/lib/mcp/tools/roadmap.js +7 -7
- package/dist/lib/mcp/tools/spec.js +9 -9
- package/dist/lib/mcp/tools/status.js +6 -6
- package/dist/lib/mcp/tools/template.js +6 -6
- package/dist/lib/mcp/tools/ticket.js +19 -19
- package/dist/lib/mcp/tools/view.js +4 -4
- package/dist/lib/mcp/tools/work.js +6 -6
- package/dist/lib/mcp/tools/workflow.js +5 -5
- package/dist/lib/pmo/index.js +4 -0
- package/dist/lib/pmo/storage/base.js +49 -0
- package/dist/lib/pmo/types.d.ts +1 -1
- package/dist/lib/pmo/types.js +1 -0
- package/dist/lib/pr/index.d.ts +5 -0
- package/dist/lib/pr/index.js +69 -0
- package/dist/lib/repos/index.js +4 -0
- package/dist/lib/string-utils.d.ts +10 -0
- package/dist/lib/string-utils.js +16 -0
- package/oclif.manifest.json +3331 -3253
- package/package.json +3 -2
package/bin/dev.js
CHANGED
|
@@ -2,4 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import {execute} from '@oclif/core'
|
|
4
4
|
|
|
5
|
+
// Support -v as shorthand for --version (only when it's the sole argument,
|
|
6
|
+
// to avoid conflicts with command-specific -v flags like repo create --visibility)
|
|
7
|
+
const args = process.argv.slice(2)
|
|
8
|
+
if (args.length === 1 && args[0] === '-v') {
|
|
9
|
+
process.argv[2] = '--version'
|
|
10
|
+
}
|
|
11
|
+
|
|
5
12
|
await execute({development: true, dir: import.meta.url})
|
package/bin/run.js
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import {execute} from '@oclif/core'
|
|
4
4
|
|
|
5
|
+
// Support -v as shorthand for --version (only when it's the sole argument,
|
|
6
|
+
// to avoid conflicts with command-specific -v flags like repo create --visibility)
|
|
7
|
+
const args = process.argv.slice(2)
|
|
8
|
+
if (args.length === 1 && args[0] === '-v') {
|
|
9
|
+
process.argv[2] = '--version'
|
|
10
|
+
}
|
|
11
|
+
|
|
5
12
|
// Handle process termination gracefully
|
|
6
13
|
process.on('SIGINT', () => {
|
|
7
14
|
console.log('\n'); // Add newline for clean exit
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Args } from '@oclif/core';
|
|
2
2
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
3
3
|
import { styles } from '../../lib/styles.js';
|
|
4
|
+
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
4
5
|
export default class ActionShow extends PMOCommand {
|
|
5
6
|
static description = 'Show details of a work action';
|
|
6
7
|
static examples = [
|
|
@@ -20,11 +21,16 @@ export default class ActionShow extends PMOCommand {
|
|
|
20
21
|
return { promptIfMultiple: false };
|
|
21
22
|
}
|
|
22
23
|
async execute() {
|
|
23
|
-
const { args } = await this.parse(ActionShow);
|
|
24
|
+
const { args, flags } = await this.parse(ActionShow);
|
|
25
|
+
const jsonMode = shouldOutputJson(flags);
|
|
24
26
|
const action = await this.storage.getAction(args.id);
|
|
25
27
|
if (!action) {
|
|
26
28
|
this.error(`Action not found: ${args.id}`);
|
|
27
29
|
}
|
|
30
|
+
if (jsonMode) {
|
|
31
|
+
this.log(JSON.stringify(action, null, 2));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
28
34
|
this.log(`\n${styles.emphasis(`Action: ${action.name}`)} ${styles.muted(`(${action.id})`)}`);
|
|
29
35
|
this.log('');
|
|
30
36
|
if (action.description) {
|
|
@@ -151,16 +151,30 @@ export default class Shell extends PMOCommand {
|
|
|
151
151
|
// Interactive mode: Prompt for environment
|
|
152
152
|
let environment = 'host';
|
|
153
153
|
if (hasDevcontainer) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
154
|
+
let environmentSelected = false;
|
|
155
|
+
while (!environmentSelected) {
|
|
156
|
+
// eslint-disable-next-line no-await-in-loop -- Interactive loop with retry on Docker check
|
|
157
|
+
const { selectedEnvironment } = await this.prompt([{
|
|
158
|
+
type: 'list',
|
|
159
|
+
name: 'selectedEnvironment',
|
|
160
|
+
message: 'Where should the shell run?',
|
|
161
|
+
choices: [
|
|
162
|
+
{ name: '🐳 devcontainer (recommended)', value: 'devcontainer', command: '' },
|
|
163
|
+
{ name: '💻 host (agent worktree on your machine)', value: 'host', command: '' },
|
|
164
|
+
],
|
|
165
|
+
}], null);
|
|
166
|
+
if (selectedEnvironment === 'devcontainer') {
|
|
167
|
+
// Dynamically check Docker when selected (user may have started it)
|
|
168
|
+
if (!isDockerRunning()) {
|
|
169
|
+
this.log('');
|
|
170
|
+
this.warn('Docker is not running. Please start Docker and try again.');
|
|
171
|
+
this.log('');
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
environment = selectedEnvironment;
|
|
176
|
+
environmentSelected = true;
|
|
177
|
+
}
|
|
164
178
|
}
|
|
165
179
|
// Interactive mode: Prompt for display mode
|
|
166
180
|
const { displayMode } = await this.prompt([{
|
|
@@ -2,6 +2,8 @@ import { Flags } from '@oclif/core';
|
|
|
2
2
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
3
3
|
import { styles } from '../../lib/styles.js';
|
|
4
4
|
import { BRANCH_TYPES, listBranches, isGitRepo, } from '../../lib/branch/index.js';
|
|
5
|
+
import { isNonTTY } from '../../lib/prompt-json.js';
|
|
6
|
+
import { visualPadEnd } from '../../lib/string-utils.js';
|
|
5
7
|
export default class BranchList extends PMOCommand {
|
|
6
8
|
static description = 'List branches with conventional naming information';
|
|
7
9
|
static examples = [
|
|
@@ -34,6 +36,10 @@ export default class BranchList extends PMOCommand {
|
|
|
34
36
|
}
|
|
35
37
|
async execute() {
|
|
36
38
|
const { flags } = await this.parse(BranchList);
|
|
39
|
+
// Default format to 'json' in non-TTY environments (piped output, CI, agents)
|
|
40
|
+
if (flags.format === 'table' && isNonTTY()) {
|
|
41
|
+
flags.format = 'json';
|
|
42
|
+
}
|
|
37
43
|
// Check if in git repo
|
|
38
44
|
if (!isGitRepo()) {
|
|
39
45
|
this.error('Not in a git repository.');
|
|
@@ -72,10 +78,10 @@ export default class BranchList extends PMOCommand {
|
|
|
72
78
|
this.log(styles.header(`🌿 Branches (${branches.length})`));
|
|
73
79
|
this.log('');
|
|
74
80
|
// Header
|
|
75
|
-
this.log(styles.muted(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
this.log(styles.muted(visualPadEnd('Name', 35) +
|
|
82
|
+
visualPadEnd('Type', 8) +
|
|
83
|
+
visualPadEnd('Owner', 12) +
|
|
84
|
+
visualPadEnd('Description', 25) +
|
|
79
85
|
'Status'));
|
|
80
86
|
this.log('─'.repeat(90));
|
|
81
87
|
// Rows
|
|
@@ -93,10 +99,10 @@ export default class BranchList extends PMOCommand {
|
|
|
93
99
|
status = 'current';
|
|
94
100
|
}
|
|
95
101
|
this.log(marker +
|
|
96
|
-
nameStyle(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
102
|
+
nameStyle(visualPadEnd(branch.name.substring(0, 33), 33)) +
|
|
103
|
+
visualPadEnd(typeDisplay, 8) +
|
|
104
|
+
visualPadEnd(ownerDisplay.substring(0, 10), 12) +
|
|
105
|
+
visualPadEnd(descDisplay.substring(0, 23), 25) +
|
|
100
106
|
styles.muted(status));
|
|
101
107
|
}
|
|
102
108
|
this.log('');
|
|
@@ -115,6 +121,3 @@ export default class BranchList extends PMOCommand {
|
|
|
115
121
|
this.log('');
|
|
116
122
|
}
|
|
117
123
|
}
|
|
118
|
-
function padEnd(str, length) {
|
|
119
|
-
return str.padEnd(length);
|
|
120
|
-
}
|
|
@@ -2,6 +2,7 @@ import { Args } from '@oclif/core';
|
|
|
2
2
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
3
3
|
import { styles } from '../../lib/styles.js';
|
|
4
4
|
import { BRANCH_TYPES, validateBranchName, getCurrentBranch, isGitRepo, } from '../../lib/branch/index.js';
|
|
5
|
+
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
5
6
|
export default class BranchValidate extends PMOCommand {
|
|
6
7
|
static description = 'Validate branch name against conventional format';
|
|
7
8
|
static examples = [
|
|
@@ -22,7 +23,8 @@ export default class BranchValidate extends PMOCommand {
|
|
|
22
23
|
return { promptIfMultiple: false };
|
|
23
24
|
}
|
|
24
25
|
async execute() {
|
|
25
|
-
const { args } = await this.parse(BranchValidate);
|
|
26
|
+
const { args, flags } = await this.parse(BranchValidate);
|
|
27
|
+
const jsonMode = shouldOutputJson(flags);
|
|
26
28
|
let branchName = args.name || '';
|
|
27
29
|
// Use current branch if not provided
|
|
28
30
|
if (!branchName) {
|
|
@@ -36,6 +38,13 @@ export default class BranchValidate extends PMOCommand {
|
|
|
36
38
|
branchName = currentBranch;
|
|
37
39
|
}
|
|
38
40
|
const result = validateBranchName(branchName);
|
|
41
|
+
if (jsonMode) {
|
|
42
|
+
this.log(JSON.stringify({ branch: branchName, ...result }, null, 2));
|
|
43
|
+
if (!result.valid) {
|
|
44
|
+
this.exit(1);
|
|
45
|
+
}
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
39
48
|
this.log('');
|
|
40
49
|
if (result.valid && result.parts) {
|
|
41
50
|
if (args.name) {
|
package/dist/commands/claude.js
CHANGED
|
@@ -145,22 +145,10 @@ export default class Claude extends PromptCommand {
|
|
|
145
145
|
environment = flags.environment;
|
|
146
146
|
}
|
|
147
147
|
else {
|
|
148
|
-
//
|
|
149
|
-
const
|
|
150
|
-
const devcontainerCliInstalled = isDevcontainerCliInstalled();
|
|
151
|
-
const devcontainerReady = dockerRunning && devcontainerCliInstalled;
|
|
152
|
-
// Build devcontainer label with missing requirements
|
|
153
|
-
let devcontainerLabel = hasProjectDevcontainer
|
|
148
|
+
// Build devcontainer label
|
|
149
|
+
const devcontainerLabel = hasProjectDevcontainer
|
|
154
150
|
? '🐳 devcontainer (uses project config, sandboxed)'
|
|
155
151
|
: '🐳 devcontainer (uses catch-all container, sandboxed)';
|
|
156
|
-
if (!devcontainerReady) {
|
|
157
|
-
const missing = [];
|
|
158
|
-
if (!dockerRunning)
|
|
159
|
-
missing.push('Docker');
|
|
160
|
-
if (!devcontainerCliInstalled)
|
|
161
|
-
missing.push('devcontainer CLI');
|
|
162
|
-
devcontainerLabel = `🐳 devcontainer (requires: ${missing.join(', ')})`;
|
|
163
|
-
}
|
|
164
152
|
// In JSON mode, output environment prompt and exit
|
|
165
153
|
if (jsonMode) {
|
|
166
154
|
await this.prompt([
|
|
@@ -172,7 +160,6 @@ export default class Claude extends PromptCommand {
|
|
|
172
160
|
{
|
|
173
161
|
name: devcontainerLabel,
|
|
174
162
|
value: 'devcontainer',
|
|
175
|
-
disabled: !devcontainerReady,
|
|
176
163
|
command: `prlt claude --slug "${slug}" --environment devcontainer --json`,
|
|
177
164
|
},
|
|
178
165
|
{
|
|
@@ -181,7 +168,7 @@ export default class Claude extends PromptCommand {
|
|
|
181
168
|
command: `prlt claude --slug "${slug}" --environment host --json`,
|
|
182
169
|
},
|
|
183
170
|
],
|
|
184
|
-
default:
|
|
171
|
+
default: 'devcontainer',
|
|
185
172
|
},
|
|
186
173
|
], jsonModeConfig);
|
|
187
174
|
return; // unreachable, but satisfies TypeScript
|
|
@@ -199,18 +186,17 @@ export default class Claude extends PromptCommand {
|
|
|
199
186
|
{
|
|
200
187
|
name: devcontainerLabel,
|
|
201
188
|
value: 'devcontainer',
|
|
202
|
-
disabled: !devcontainerReady,
|
|
203
189
|
},
|
|
204
190
|
{ name: '💻 host (runs directly on your machine)', value: 'host' },
|
|
205
191
|
],
|
|
206
|
-
default:
|
|
192
|
+
default: 'devcontainer',
|
|
207
193
|
},
|
|
208
194
|
], null);
|
|
209
195
|
if (selectedEnv === 'devcontainer') {
|
|
210
|
-
//
|
|
196
|
+
// Dynamically check Docker when selected (user may have started it)
|
|
211
197
|
if (!isDockerRunning()) {
|
|
212
198
|
this.log('');
|
|
213
|
-
this.warn('Docker is not running. Please start Docker
|
|
199
|
+
this.warn('Docker is not running. Please start Docker and try again.');
|
|
214
200
|
this.log('');
|
|
215
201
|
continue;
|
|
216
202
|
}
|
|
@@ -553,22 +539,10 @@ export default class Claude extends PromptCommand {
|
|
|
553
539
|
}
|
|
554
540
|
// Prompt for environment first (before creating ticket) so user can cancel early
|
|
555
541
|
const hasProjectDevcontainer = hasDevcontainerConfig(workDir);
|
|
556
|
-
//
|
|
557
|
-
const
|
|
558
|
-
const devcontainerCliInstalled = isDevcontainerCliInstalled();
|
|
559
|
-
const devcontainerReady = dockerRunning && devcontainerCliInstalled;
|
|
560
|
-
// Build devcontainer label with missing requirements
|
|
561
|
-
let devcontainerLabel = hasProjectDevcontainer
|
|
542
|
+
// Build devcontainer label
|
|
543
|
+
const devcontainerLabel = hasProjectDevcontainer
|
|
562
544
|
? '🐳 devcontainer (uses project config, sandboxed)'
|
|
563
545
|
: '🐳 devcontainer (uses catch-all container, sandboxed)';
|
|
564
|
-
if (!devcontainerReady) {
|
|
565
|
-
const missing = [];
|
|
566
|
-
if (!dockerRunning)
|
|
567
|
-
missing.push('Docker');
|
|
568
|
-
if (!devcontainerCliInstalled)
|
|
569
|
-
missing.push('devcontainer CLI');
|
|
570
|
-
devcontainerLabel = `🐳 devcontainer (requires: ${missing.join(', ')})`;
|
|
571
|
-
}
|
|
572
546
|
let environment = 'host';
|
|
573
547
|
if (flags.environment) {
|
|
574
548
|
environment = flags.environment;
|
|
@@ -585,7 +559,6 @@ export default class Claude extends PromptCommand {
|
|
|
585
559
|
{
|
|
586
560
|
name: devcontainerLabel,
|
|
587
561
|
value: 'devcontainer',
|
|
588
|
-
disabled: !devcontainerReady,
|
|
589
562
|
command: `prlt claude --project ${projectId} --title "${ticketTitle}" --environment devcontainer --json`,
|
|
590
563
|
},
|
|
591
564
|
{
|
|
@@ -594,7 +567,7 @@ export default class Claude extends PromptCommand {
|
|
|
594
567
|
command: `prlt claude --project ${projectId} --title "${ticketTitle}" --environment host --json`,
|
|
595
568
|
},
|
|
596
569
|
],
|
|
597
|
-
default:
|
|
570
|
+
default: 'devcontainer',
|
|
598
571
|
},
|
|
599
572
|
], jsonModeConfig);
|
|
600
573
|
db.close();
|
|
@@ -613,18 +586,17 @@ export default class Claude extends PromptCommand {
|
|
|
613
586
|
{
|
|
614
587
|
name: devcontainerLabel,
|
|
615
588
|
value: 'devcontainer',
|
|
616
|
-
disabled: !devcontainerReady,
|
|
617
589
|
},
|
|
618
590
|
{ name: '💻 host (runs directly on your machine)', value: 'host' },
|
|
619
591
|
],
|
|
620
|
-
default:
|
|
592
|
+
default: 'devcontainer',
|
|
621
593
|
},
|
|
622
594
|
], null);
|
|
623
595
|
if (selectedEnv === 'devcontainer') {
|
|
624
|
-
//
|
|
596
|
+
// Dynamically check Docker when selected (user may have started it)
|
|
625
597
|
if (!isDockerRunning()) {
|
|
626
598
|
this.log('');
|
|
627
|
-
this.warn('Docker is not running. Please start Docker
|
|
599
|
+
this.warn('Docker is not running. Please start Docker and try again.');
|
|
628
600
|
this.log('');
|
|
629
601
|
continue;
|
|
630
602
|
}
|
|
@@ -9,6 +9,7 @@ import { isDockerRunning } from '../../lib/execution/runners.js';
|
|
|
9
9
|
import { sanitizeContainerId } from '../../lib/docker/resolve.js';
|
|
10
10
|
import { FlagResolver, shouldOutputJson } from '../../lib/flags/index.js';
|
|
11
11
|
import { machineOutputFlags } from '../../lib/pmo/base-command.js';
|
|
12
|
+
import { visualPadEnd } from '../../lib/string-utils.js';
|
|
12
13
|
export default class DockerClean extends Command {
|
|
13
14
|
static description = 'Remove orphaned containers (containers without running agents)';
|
|
14
15
|
static examples = [
|
|
@@ -72,15 +73,15 @@ export default class DockerClean extends Command {
|
|
|
72
73
|
// Display orphaned containers
|
|
73
74
|
this.log(`\n${styles.header('Orphaned Containers')}`);
|
|
74
75
|
this.log('='.repeat(80));
|
|
75
|
-
this.log(styles.muted(
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
this.log(styles.muted(visualPadEnd('Container ID', 15) +
|
|
77
|
+
visualPadEnd('Name', 30) +
|
|
78
|
+
visualPadEnd('Status', 15) +
|
|
78
79
|
'Reason'));
|
|
79
80
|
this.log('-'.repeat(80));
|
|
80
81
|
for (const container of orphanedContainers) {
|
|
81
|
-
this.log(
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
this.log(visualPadEnd(container.id, 15) +
|
|
83
|
+
visualPadEnd(truncate(container.name, 28), 30) +
|
|
84
|
+
visualPadEnd(container.status, 15) +
|
|
84
85
|
styles.muted(container.reason));
|
|
85
86
|
}
|
|
86
87
|
if (flags['dry-run']) {
|
|
@@ -205,9 +206,6 @@ export default class DockerClean extends Command {
|
|
|
205
206
|
}
|
|
206
207
|
}
|
|
207
208
|
}
|
|
208
|
-
function padEnd(str, length) {
|
|
209
|
-
return str.padEnd(length);
|
|
210
|
-
}
|
|
211
209
|
function truncate(str, maxLength) {
|
|
212
210
|
if (str.length <= maxLength)
|
|
213
211
|
return str;
|
|
@@ -9,6 +9,7 @@ import { ExecutionStorage, ContainerStorage } from '../../lib/execution/storage.
|
|
|
9
9
|
import { isDockerRunning } from '../../lib/execution/runners.js';
|
|
10
10
|
import { FlagResolver, shouldOutputJson } from '../../lib/flags/index.js';
|
|
11
11
|
import { machineOutputFlags } from '../../lib/pmo/base-command.js';
|
|
12
|
+
import { visualPadEnd } from '../../lib/string-utils.js';
|
|
12
13
|
export default class Docker extends PromptCommand {
|
|
13
14
|
static description = 'Manage Docker containers used by agents';
|
|
14
15
|
static examples = [
|
|
@@ -127,7 +128,7 @@ export default class Docker extends PromptCommand {
|
|
|
127
128
|
// Build choices
|
|
128
129
|
const choices = [];
|
|
129
130
|
// Column header
|
|
130
|
-
const header = styles.muted(` ${'ID'
|
|
131
|
+
const header = styles.muted(` ${visualPadEnd('ID', 14)} ${visualPadEnd('Agent', 15)} Status`);
|
|
131
132
|
// Add active executions first (with container info from DB)
|
|
132
133
|
const activeExecutions = trackedExecutions.filter(e => e.status === 'running' || e.status === 'starting');
|
|
133
134
|
if (activeExecutions.length > 0) {
|
|
@@ -143,7 +144,7 @@ export default class Docker extends PromptCommand {
|
|
|
143
144
|
const statusIcon = isRunning ? styles.success('●') : styles.warning('◐');
|
|
144
145
|
// Use agent name from execution record (DB source of truth)
|
|
145
146
|
choices.push({
|
|
146
|
-
name: `${statusIcon} ${exec.id
|
|
147
|
+
name: `${statusIcon} ${visualPadEnd(exec.id, 14)} ${visualPadEnd(exec.agentName, 15)} ${styles.success(exec.status)}`,
|
|
147
148
|
value: exec.id,
|
|
148
149
|
});
|
|
149
150
|
if (exec.containerId) {
|
|
@@ -167,7 +168,7 @@ export default class Docker extends PromptCommand {
|
|
|
167
168
|
const statusText = isRunning ? styles.success('running') : styles.muted(container.status);
|
|
168
169
|
// Agent name from DB (source of truth)
|
|
169
170
|
choices.push({
|
|
170
|
-
name: `${statusIcon} ${container.dockerId.substring(0, 12)
|
|
171
|
+
name: `${statusIcon} ${visualPadEnd(container.dockerId.substring(0, 12), 14)} ${visualPadEnd(container.agentName, 15)} ${statusText}`,
|
|
171
172
|
value: container.dockerId,
|
|
172
173
|
});
|
|
173
174
|
addedDockerIds.add(container.dockerId.substring(0, 12));
|
|
@@ -185,7 +186,7 @@ export default class Docker extends PromptCommand {
|
|
|
185
186
|
// Parse agent from image only for untracked containers
|
|
186
187
|
const agentName = this.extractAgentFromImage(container.image);
|
|
187
188
|
choices.push({
|
|
188
|
-
name: `${statusIcon} ${container.id.substring(0, 12)
|
|
189
|
+
name: `${statusIcon} ${visualPadEnd(container.id.substring(0, 12), 14)} ${visualPadEnd(agentName, 15)} ${statusText}`,
|
|
189
190
|
value: container.id,
|
|
190
191
|
});
|
|
191
192
|
}
|
|
@@ -5,6 +5,7 @@ export default class DockerList extends Command {
|
|
|
5
5
|
static flags: {
|
|
6
6
|
all: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
7
|
running: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
9
|
};
|
|
9
10
|
run(): Promise<void>;
|
|
10
11
|
private getTrackedContainers;
|
|
@@ -6,6 +6,9 @@ import { styles } from '../../lib/styles.js';
|
|
|
6
6
|
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
7
7
|
import { ExecutionStorage, ContainerStorage } from '../../lib/execution/storage.js';
|
|
8
8
|
import { isDockerRunning } from '../../lib/execution/runners.js';
|
|
9
|
+
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
10
|
+
import { machineOutputFlags } from '../../lib/pmo/index.js';
|
|
11
|
+
import { visualPadEnd } from '../../lib/string-utils.js';
|
|
9
12
|
export default class DockerList extends Command {
|
|
10
13
|
static description = 'Show Docker containers from agent_work table with status';
|
|
11
14
|
static examples = [
|
|
@@ -14,6 +17,7 @@ export default class DockerList extends Command {
|
|
|
14
17
|
'<%= config.bin %> <%= command.id %> --running',
|
|
15
18
|
];
|
|
16
19
|
static flags = {
|
|
20
|
+
...machineOutputFlags,
|
|
17
21
|
all: Flags.boolean({
|
|
18
22
|
char: 'a',
|
|
19
23
|
description: 'Show all containers (including non-devcontainer)',
|
|
@@ -27,8 +31,13 @@ export default class DockerList extends Command {
|
|
|
27
31
|
};
|
|
28
32
|
async run() {
|
|
29
33
|
const { flags } = await this.parse(DockerList);
|
|
34
|
+
const jsonMode = shouldOutputJson(flags);
|
|
30
35
|
// Check Docker status first
|
|
31
36
|
if (!isDockerRunning()) {
|
|
37
|
+
if (jsonMode) {
|
|
38
|
+
this.log(JSON.stringify({ error: 'Docker is not running' }, null, 2));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
32
41
|
this.log(`\n${styles.error('Docker is not running')}`);
|
|
33
42
|
this.log(`${styles.muted('Start Docker Desktop or the Docker daemon first.')}\n`);
|
|
34
43
|
return;
|
|
@@ -59,16 +68,24 @@ export default class DockerList extends Command {
|
|
|
59
68
|
const dbContainers = containerStorage.listContainers({ limit: 50 });
|
|
60
69
|
// Get running docker containers
|
|
61
70
|
const runningContainers = this.getDockerContainers(workspaceInfo.agentsPath, flags.all);
|
|
71
|
+
if (jsonMode) {
|
|
72
|
+
this.log(JSON.stringify({
|
|
73
|
+
executions: trackedExecutions,
|
|
74
|
+
containers: runningContainers,
|
|
75
|
+
}, null, 2));
|
|
76
|
+
db.close();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
62
79
|
this.log(`\n${styles.header('Docker Containers')}`);
|
|
63
80
|
this.log('='.repeat(100));
|
|
64
81
|
// Display active executions from database
|
|
65
82
|
const activeExecutions = trackedExecutions.filter(e => e.status === 'running' || e.status === 'starting');
|
|
66
83
|
if (activeExecutions.length > 0) {
|
|
67
84
|
this.log(styles.subheader('\nActive Executions:'));
|
|
68
|
-
this.log(styles.muted(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
85
|
+
this.log(styles.muted(visualPadEnd('ID', 15) +
|
|
86
|
+
visualPadEnd('Agent', 15) +
|
|
87
|
+
visualPadEnd('Ticket', 12) +
|
|
88
|
+
visualPadEnd('Status', 12) +
|
|
72
89
|
'Container'));
|
|
73
90
|
this.log('-'.repeat(80));
|
|
74
91
|
for (const exec of activeExecutions) {
|
|
@@ -80,10 +97,10 @@ export default class DockerList extends Command {
|
|
|
80
97
|
const isRunning = runningContainers.some(c => c.id.startsWith(exec.containerId.substring(0, 12)));
|
|
81
98
|
containerStatus = isRunning ? styles.success(' (up)') : styles.warning(' (down)');
|
|
82
99
|
}
|
|
83
|
-
this.log(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
statusColor(
|
|
100
|
+
this.log(visualPadEnd(exec.id, 15) +
|
|
101
|
+
visualPadEnd(exec.agentName, 15) +
|
|
102
|
+
visualPadEnd(exec.ticketId, 12) +
|
|
103
|
+
statusColor(visualPadEnd(exec.status, 12)) +
|
|
87
104
|
containerId + containerStatus);
|
|
88
105
|
}
|
|
89
106
|
}
|
|
@@ -93,9 +110,9 @@ export default class DockerList extends Command {
|
|
|
93
110
|
: runningContainers;
|
|
94
111
|
if (filteredContainers.length > 0) {
|
|
95
112
|
this.log(styles.subheader('\nDocker Containers' + (flags.all ? ' (all)' : ' (devcontainers)') + ':'));
|
|
96
|
-
this.log(styles.muted(
|
|
97
|
-
|
|
98
|
-
|
|
113
|
+
this.log(styles.muted(visualPadEnd('Container ID', 15) +
|
|
114
|
+
visualPadEnd('Agent', 15) +
|
|
115
|
+
visualPadEnd('Status', 15) +
|
|
99
116
|
'Uptime'));
|
|
100
117
|
this.log('-'.repeat(70));
|
|
101
118
|
for (const container of filteredContainers) {
|
|
@@ -103,9 +120,9 @@ export default class DockerList extends Command {
|
|
|
103
120
|
// Look up agent name from DB (source of truth)
|
|
104
121
|
const dbContainer = dbContainers.find(c => c.dockerId.startsWith(container.id.substring(0, 12)));
|
|
105
122
|
const agentName = dbContainer?.agentName || this.extractAgentFromImage(container.image) || container.name;
|
|
106
|
-
this.log(
|
|
107
|
-
|
|
108
|
-
statusColor(
|
|
123
|
+
this.log(visualPadEnd(container.id, 15) +
|
|
124
|
+
visualPadEnd(agentName, 15) +
|
|
125
|
+
statusColor(visualPadEnd(container.status, 15)) +
|
|
109
126
|
styles.muted(container.uptime));
|
|
110
127
|
}
|
|
111
128
|
}
|
|
@@ -174,9 +191,6 @@ export default class DockerList extends Command {
|
|
|
174
191
|
return match ? match[1] : null;
|
|
175
192
|
}
|
|
176
193
|
}
|
|
177
|
-
function padEnd(str, length) {
|
|
178
|
-
return str.padEnd(length);
|
|
179
|
-
}
|
|
180
194
|
function getStatusColor(status) {
|
|
181
195
|
switch (status) {
|
|
182
196
|
case 'running':
|
|
@@ -2,6 +2,8 @@ import { Command } from '@oclif/core';
|
|
|
2
2
|
export default class DockerStatus extends Command {
|
|
3
3
|
static description: string;
|
|
4
4
|
static examples: string[];
|
|
5
|
-
static flags: {
|
|
5
|
+
static flags: {
|
|
6
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
};
|
|
6
8
|
run(): Promise<void>;
|
|
7
9
|
}
|
|
@@ -2,16 +2,42 @@ import { Command } from '@oclif/core';
|
|
|
2
2
|
import { execSync } from 'node:child_process';
|
|
3
3
|
import { styles } from '../../lib/styles.js';
|
|
4
4
|
import { isDockerRunning } from '../../lib/execution/runners.js';
|
|
5
|
+
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
6
|
+
import { machineOutputFlags } from '../../lib/pmo/index.js';
|
|
5
7
|
export default class DockerStatus extends Command {
|
|
6
8
|
static description = 'Check if Docker daemon is running';
|
|
7
9
|
static examples = [
|
|
8
10
|
'<%= config.bin %> <%= command.id %>',
|
|
9
11
|
];
|
|
10
|
-
static flags = {
|
|
12
|
+
static flags = {
|
|
13
|
+
...machineOutputFlags,
|
|
14
|
+
};
|
|
11
15
|
async run() {
|
|
16
|
+
const { flags } = await this.parse(DockerStatus);
|
|
17
|
+
const jsonMode = shouldOutputJson(flags);
|
|
18
|
+
const running = isDockerRunning();
|
|
19
|
+
if (jsonMode) {
|
|
20
|
+
const result = { running };
|
|
21
|
+
if (running) {
|
|
22
|
+
try {
|
|
23
|
+
result.version = execSync('docker version --format "{{.Server.Version}}"', {
|
|
24
|
+
encoding: 'utf-8',
|
|
25
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
26
|
+
}).trim();
|
|
27
|
+
result.info = execSync('docker info --format "{{.Containers}} containers ({{.ContainersRunning}} running)"', {
|
|
28
|
+
encoding: 'utf-8',
|
|
29
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
30
|
+
}).trim();
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Ignore errors getting additional info
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
this.log(JSON.stringify(result, null, 2));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
12
39
|
this.log(`\n${styles.header('Docker Status')}`);
|
|
13
40
|
this.log('─'.repeat(50));
|
|
14
|
-
const running = isDockerRunning();
|
|
15
41
|
if (running) {
|
|
16
42
|
this.log(`${styles.success('Running')} Docker daemon is available`);
|
|
17
43
|
// Get more details
|
|
@@ -6,6 +6,7 @@ import { styles } from '../../lib/styles.js';
|
|
|
6
6
|
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
7
7
|
import { ContainerStorage } from '../../lib/execution/storage.js';
|
|
8
8
|
import { isDockerRunning } from '../../lib/execution/runners.js';
|
|
9
|
+
import { visualPadEnd } from '../../lib/string-utils.js';
|
|
9
10
|
export default class DockerSync extends Command {
|
|
10
11
|
static description = 'Sync container status from Docker into the database';
|
|
11
12
|
static examples = [
|
|
@@ -54,9 +55,9 @@ export default class DockerSync extends Command {
|
|
|
54
55
|
if (containers.length > 0) {
|
|
55
56
|
this.log(styles.subheader('Tracked Containers:'));
|
|
56
57
|
this.log(styles.muted(' ' +
|
|
57
|
-
'ID'
|
|
58
|
-
'Agent'
|
|
59
|
-
'Status'
|
|
58
|
+
visualPadEnd('ID', 18) +
|
|
59
|
+
visualPadEnd('Agent', 15) +
|
|
60
|
+
visualPadEnd('Status', 12) +
|
|
60
61
|
'Docker ID'));
|
|
61
62
|
this.log(styles.muted(' ' + '-'.repeat(65)));
|
|
62
63
|
for (const container of containers) {
|
|
@@ -64,9 +65,9 @@ export default class DockerSync extends Command {
|
|
|
64
65
|
container.status === 'exited' ? styles.muted :
|
|
65
66
|
container.status === 'removed' ? styles.error : styles.warning;
|
|
66
67
|
this.log(' ' +
|
|
67
|
-
container.id
|
|
68
|
-
container.agentName
|
|
69
|
-
statusColor(container.status
|
|
68
|
+
visualPadEnd(container.id, 18) +
|
|
69
|
+
visualPadEnd(container.agentName, 15) +
|
|
70
|
+
statusColor(visualPadEnd(container.status, 12)) +
|
|
70
71
|
styles.muted(container.dockerId.substring(0, 12)));
|
|
71
72
|
}
|
|
72
73
|
this.log('');
|