@proletariat/cli 0.3.35 → 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 +12 -2
- package/dist/commands/agent/auth.js +128 -4
- package/dist/commands/agent/list.js +16 -7
- package/dist/commands/agent/status.js +32 -4
- 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.js +2 -1
- package/dist/commands/label/delete.js +2 -1
- package/dist/commands/label/group/create.js +2 -1
- package/dist/commands/label/group/list.js +2 -1
- package/dist/commands/label/list.js +2 -1
- package/dist/commands/mcp-server.js +25 -0
- package/dist/commands/phase/template/list.js +2 -1
- package/dist/commands/project/create.js +3 -4
- package/dist/commands/project/update.js +5 -6
- package/dist/commands/pull.js +24 -0
- package/dist/commands/session/create.d.ts +19 -0
- package/dist/commands/session/create.js +102 -0
- package/dist/commands/session/health.js +1 -20
- 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/view.js +29 -0
- 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/move.js +25 -2
- package/dist/commands/ticket/resolve.js +3 -4
- package/dist/commands/ticket/show.d.ts +13 -0
- package/dist/commands/ticket/show.js +16 -0
- package/dist/commands/ticket/template/list.js +2 -1
- package/dist/commands/ticket/view.d.ts +0 -1
- package/dist/commands/ticket/view.js +30 -1
- package/dist/commands/work/index.js +4 -0
- package/dist/commands/work/start.js +169 -94
- 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/execution/config.d.ts +15 -1
- package/dist/lib/execution/config.js +28 -0
- package/dist/lib/execution/runners.d.ts +11 -0
- package/dist/lib/execution/runners.js +53 -19
- 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 +2 -0
- package/dist/lib/mcp/tools/board.js +4 -6
- package/dist/lib/mcp/tools/cli-passthrough.js +25 -6
- package/dist/lib/mcp/tools/epic.js +8 -3
- package/dist/lib/mcp/tools/spec.js +1 -1
- package/dist/lib/mcp/tools/ticket.js +11 -9
- package/dist/lib/mcp/tools/work.js +96 -6
- package/dist/lib/mcp/types.d.ts +10 -0
- package/dist/lib/multiline-input.js +2 -1
- package/dist/lib/pmo/base-command.js +4 -4
- package/dist/lib/pmo/storage/actions.js +1 -1
- package/dist/lib/pmo/storage/base.js +195 -50
- package/dist/lib/pmo/storage/types.d.ts +1 -0
- 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 +3259 -2701
- package/package.json +1 -1
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { Args } from '@oclif/core';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
4
|
+
import Database from 'better-sqlite3';
|
|
5
|
+
import { styles } from '../../lib/styles.js';
|
|
6
|
+
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
7
|
+
import { ExecutionStorage } from '../../lib/execution/index.js';
|
|
8
|
+
import { getHostTmuxSessionNames, getContainerTmuxSessionMap, findSessionForExecution, } from '../../lib/execution/session-utils.js';
|
|
9
|
+
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
10
|
+
import { shouldOutputJson, outputSuccessAsJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// tmux Helpers
|
|
13
|
+
// =============================================================================
|
|
14
|
+
/**
|
|
15
|
+
* Send a text message to a tmux session via send-keys.
|
|
16
|
+
* Sends the text first, then Enter separately with a small delay
|
|
17
|
+
* to avoid race conditions where Enter arrives before text is rendered.
|
|
18
|
+
*/
|
|
19
|
+
function sendMessage(sessionId, message, containerId) {
|
|
20
|
+
// Escape single quotes in the message for shell safety
|
|
21
|
+
const escapedMessage = message.replace(/'/g, "'\\''");
|
|
22
|
+
// Send the text first (without Enter), using -l (literal) flag so tmux
|
|
23
|
+
// does not interpret special characters - message is delivered verbatim
|
|
24
|
+
const sendTextCmd = `tmux send-keys -l -t "${sessionId}" '${escapedMessage}'`;
|
|
25
|
+
// Then send Enter separately (Enter is a tmux key name, not literal text)
|
|
26
|
+
const sendEnterCmd = `tmux send-keys -t "${sessionId}" Enter`;
|
|
27
|
+
if (containerId) {
|
|
28
|
+
execSync(`docker exec ${containerId} bash -c '${sendTextCmd}'`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 10000 });
|
|
29
|
+
// Small delay before sending Enter to avoid race conditions
|
|
30
|
+
execSync('sleep 0.1', { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
31
|
+
execSync(`docker exec ${containerId} bash -c '${sendEnterCmd}'`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 10000 });
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
execSync(sendTextCmd, {
|
|
35
|
+
encoding: 'utf-8',
|
|
36
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
37
|
+
timeout: 5000,
|
|
38
|
+
});
|
|
39
|
+
// Small delay before sending Enter
|
|
40
|
+
execSync('sleep 0.1', { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
41
|
+
execSync(sendEnterCmd, {
|
|
42
|
+
encoding: 'utf-8',
|
|
43
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
44
|
+
timeout: 5000,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// =============================================================================
|
|
49
|
+
// Command
|
|
50
|
+
// =============================================================================
|
|
51
|
+
export default class SessionPoke extends PMOCommand {
|
|
52
|
+
static description = 'Send a message to a running agent\'s Claude Code session';
|
|
53
|
+
static examples = [
|
|
54
|
+
'<%= config.bin %> session poke altman "Please focus on the tests first"',
|
|
55
|
+
'<%= config.bin %> session poke TKT-123 "Add error handling for edge cases"',
|
|
56
|
+
];
|
|
57
|
+
static args = {
|
|
58
|
+
agent: Args.string({
|
|
59
|
+
description: 'Agent name or ticket ID of the running agent',
|
|
60
|
+
required: true,
|
|
61
|
+
}),
|
|
62
|
+
message: Args.string({
|
|
63
|
+
description: 'Message to send to the agent session',
|
|
64
|
+
required: true,
|
|
65
|
+
}),
|
|
66
|
+
};
|
|
67
|
+
static flags = {
|
|
68
|
+
...pmoBaseFlags,
|
|
69
|
+
};
|
|
70
|
+
getPMOOptions() {
|
|
71
|
+
return { promptIfMultiple: false };
|
|
72
|
+
}
|
|
73
|
+
async execute() {
|
|
74
|
+
const { args, flags } = await this.parse(SessionPoke);
|
|
75
|
+
const jsonMode = shouldOutputJson(flags);
|
|
76
|
+
const { agent, message } = args;
|
|
77
|
+
// Resolve the agent's active session
|
|
78
|
+
const resolved = this.resolveAgentSession(agent, jsonMode, flags);
|
|
79
|
+
if (!resolved)
|
|
80
|
+
return;
|
|
81
|
+
// Send the message
|
|
82
|
+
try {
|
|
83
|
+
sendMessage(resolved.sessionId, message, resolved.containerId);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
87
|
+
// Check for specific container/session errors
|
|
88
|
+
if (errMsg.includes('no server running') || errMsg.includes('session not found') || errMsg.includes("can't find session")) {
|
|
89
|
+
if (jsonMode) {
|
|
90
|
+
outputErrorAsJson('SESSION_NOT_FOUND', `tmux session "${resolved.sessionId}" does not exist. The agent may have exited.`, createMetadata('session poke', flags));
|
|
91
|
+
}
|
|
92
|
+
this.log('');
|
|
93
|
+
this.log(styles.error(`tmux session "${resolved.sessionId}" does not exist. The agent may have exited.`));
|
|
94
|
+
this.log(styles.muted('Use `prlt session list` to see running sessions.'));
|
|
95
|
+
this.log('');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (errMsg.includes('No such container') || errMsg.includes('is not running')) {
|
|
99
|
+
if (jsonMode) {
|
|
100
|
+
outputErrorAsJson('CONTAINER_NOT_RUNNING', `Docker container for agent "${resolved.agentName}" is not running.`, createMetadata('session poke', flags));
|
|
101
|
+
}
|
|
102
|
+
this.log('');
|
|
103
|
+
this.log(styles.error(`Docker container for agent "${resolved.agentName}" is not running.`));
|
|
104
|
+
this.log(styles.muted('The container may have stopped. Check with `docker ps`.'));
|
|
105
|
+
this.log('');
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Generic send failure
|
|
109
|
+
if (jsonMode) {
|
|
110
|
+
outputErrorAsJson('SEND_FAILED', `Failed to send message to agent "${resolved.agentName}": ${errMsg}`, createMetadata('session poke', flags));
|
|
111
|
+
}
|
|
112
|
+
this.log('');
|
|
113
|
+
this.log(styles.error(`Failed to send message: ${errMsg}`));
|
|
114
|
+
this.log('');
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// Output result
|
|
118
|
+
if (jsonMode) {
|
|
119
|
+
outputSuccessAsJson({
|
|
120
|
+
success: true,
|
|
121
|
+
agent: resolved.agentName,
|
|
122
|
+
session: resolved.sessionId,
|
|
123
|
+
message,
|
|
124
|
+
}, createMetadata('session poke', flags));
|
|
125
|
+
}
|
|
126
|
+
this.log('');
|
|
127
|
+
this.log(styles.success(`Message sent to ${resolved.agentName} (${resolved.ticketId})`));
|
|
128
|
+
this.log('');
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Resolve an agent identifier (name or ticket ID) to a running session.
|
|
132
|
+
* Looks up active executions and matches tmux sessions.
|
|
133
|
+
*/
|
|
134
|
+
resolveAgentSession(identifier, jsonMode, flags) {
|
|
135
|
+
let executionStorage = null;
|
|
136
|
+
let db = null;
|
|
137
|
+
try {
|
|
138
|
+
const workspaceInfo = getWorkspaceInfo();
|
|
139
|
+
const dbPath = path.join(workspaceInfo.path, '.proletariat', 'workspace.db');
|
|
140
|
+
db = new Database(dbPath);
|
|
141
|
+
executionStorage = new ExecutionStorage(db);
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
if (jsonMode) {
|
|
145
|
+
outputErrorAsJson('NOT_IN_WORKSPACE', 'Not in a workspace. Run from a proletariat HQ directory.', createMetadata('session poke', flags));
|
|
146
|
+
}
|
|
147
|
+
this.log('');
|
|
148
|
+
this.log(styles.error('Not in a workspace. Run from a proletariat HQ directory.'));
|
|
149
|
+
this.log('');
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
// Get active executions
|
|
154
|
+
const runningExecutions = executionStorage.listExecutions({ status: 'running' });
|
|
155
|
+
const startingExecutions = executionStorage.listExecutions({ status: 'starting' });
|
|
156
|
+
const activeExecutions = [...runningExecutions, ...startingExecutions];
|
|
157
|
+
// Find matching execution by exact agent name or exact ticket ID
|
|
158
|
+
const match = activeExecutions.find(exec => exec.agentName === identifier || exec.ticketId === identifier);
|
|
159
|
+
if (!match) {
|
|
160
|
+
if (jsonMode) {
|
|
161
|
+
outputErrorAsJson('NO_ACTIVE_EXECUTION', `Agent "${identifier}" has no active session. Use \`prlt session list\` to see running sessions.`, createMetadata('session poke', flags));
|
|
162
|
+
}
|
|
163
|
+
this.log('');
|
|
164
|
+
this.log(styles.error(`Agent "${identifier}" has no active session.`));
|
|
165
|
+
this.log(styles.muted('Use `prlt session list` to see running sessions.'));
|
|
166
|
+
this.log('');
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
return this.resolveSessionForExecution(match, jsonMode, flags);
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
db?.close();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Resolve the tmux session for a specific execution record.
|
|
177
|
+
*/
|
|
178
|
+
resolveSessionForExecution(exec, jsonMode, flags) {
|
|
179
|
+
const isContainer = exec.environment === 'devcontainer';
|
|
180
|
+
let actualSessionId = exec.sessionId;
|
|
181
|
+
let containerId = isContainer ? exec.containerId : undefined;
|
|
182
|
+
// If sessionId is NULL, try to discover it from tmux
|
|
183
|
+
if (!exec.sessionId) {
|
|
184
|
+
if (isContainer && exec.containerId) {
|
|
185
|
+
const containerTmuxSessions = getContainerTmuxSessionMap();
|
|
186
|
+
const containerSessions = containerTmuxSessions.get(exec.containerId) || [];
|
|
187
|
+
const match = findSessionForExecution(exec.ticketId, exec.agentName, containerSessions);
|
|
188
|
+
if (match) {
|
|
189
|
+
actualSessionId = match;
|
|
190
|
+
containerId = exec.containerId;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
const hostTmuxSessions = getHostTmuxSessionNames();
|
|
195
|
+
const match = findSessionForExecution(exec.ticketId, exec.agentName, hostTmuxSessions);
|
|
196
|
+
if (match) {
|
|
197
|
+
actualSessionId = match;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (!actualSessionId) {
|
|
202
|
+
if (jsonMode) {
|
|
203
|
+
outputErrorAsJson('SESSION_NOT_FOUND', `Could not find tmux session for agent "${exec.agentName}" (${exec.ticketId}). The session may not have started yet.`, createMetadata('session poke', flags));
|
|
204
|
+
}
|
|
205
|
+
this.log('');
|
|
206
|
+
this.log(styles.error(`Could not find tmux session for agent "${exec.agentName}" (${exec.ticketId}).`));
|
|
207
|
+
this.log(styles.muted('The session may not have started yet.'));
|
|
208
|
+
this.log('');
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
sessionId: actualSessionId,
|
|
213
|
+
ticketId: exec.ticketId,
|
|
214
|
+
agentName: exec.agentName,
|
|
215
|
+
environment: isContainer ? 'container' : 'host',
|
|
216
|
+
containerId,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -83,6 +83,35 @@ export default class SpecView extends PMOCommand {
|
|
|
83
83
|
// Get dependencies
|
|
84
84
|
const dependencies = await this.storage.getSpecDependencies(spec.id);
|
|
85
85
|
const dependents = await this.storage.getSpecDependents(spec.id);
|
|
86
|
+
// JSON output mode
|
|
87
|
+
if (jsonMode) {
|
|
88
|
+
this.log(JSON.stringify({
|
|
89
|
+
success: true,
|
|
90
|
+
spec: {
|
|
91
|
+
id: spec.id,
|
|
92
|
+
title: spec.title,
|
|
93
|
+
status: spec.status,
|
|
94
|
+
type: spec.type,
|
|
95
|
+
problem: spec.problem,
|
|
96
|
+
solution: spec.solution,
|
|
97
|
+
decisions: spec.decisions,
|
|
98
|
+
notNow: spec.notNow,
|
|
99
|
+
uiUx: spec.uiUx,
|
|
100
|
+
acceptanceCriteria: spec.acceptanceCriteria,
|
|
101
|
+
openQuestions: spec.openQuestions,
|
|
102
|
+
requirementsFunctional: spec.requirementsFunctional,
|
|
103
|
+
requirementsTechnical: spec.requirementsTechnical,
|
|
104
|
+
context: spec.context,
|
|
105
|
+
tags: spec.tags,
|
|
106
|
+
createdAt: spec.createdAt.toISOString(),
|
|
107
|
+
updatedAt: spec.updatedAt.toISOString(),
|
|
108
|
+
dependencies: dependencies.map(d => ({ id: d.id, title: d.title, status: d.status })),
|
|
109
|
+
dependents: dependents.map(d => ({ id: d.id, title: d.title, status: d.status })),
|
|
110
|
+
tickets: tickets.map(t => ({ id: t.id, title: t.title, status: t.status })),
|
|
111
|
+
},
|
|
112
|
+
}, null, 2));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
86
115
|
// Display spec info
|
|
87
116
|
this.log(styles.title(`\n📄 ${spec.title}`));
|
|
88
117
|
this.log(styles.muted('═'.repeat(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 TemplateList extends PMOCommand {
|
|
5
6
|
static description = 'List all templates (ticket and phase)';
|
|
@@ -44,7 +45,7 @@ export default class TemplateList extends PMOCommand {
|
|
|
44
45
|
showTicket ? this.storage.listTicketTemplates(builtinFilter) : Promise.resolve([]),
|
|
45
46
|
showPhase ? this.storage.listPhaseTemplates(builtinFilter) : Promise.resolve([]),
|
|
46
47
|
]);
|
|
47
|
-
if (flags
|
|
48
|
+
if (shouldOutputJson(flags)) {
|
|
48
49
|
const result = {};
|
|
49
50
|
if (showTicket)
|
|
50
51
|
result.ticket = ticketTemplates;
|
|
@@ -2,6 +2,10 @@ import { Command } from '@oclif/core';
|
|
|
2
2
|
export default class ThemeAddNames extends Command {
|
|
3
3
|
static description: string;
|
|
4
4
|
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
};
|
|
5
9
|
static args: {
|
|
6
10
|
theme: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
11
|
names: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
@@ -3,12 +3,17 @@ import chalk from 'chalk';
|
|
|
3
3
|
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
4
4
|
import { isValidAgentName, normalizeAgentName } from '../../lib/themes.js';
|
|
5
5
|
import { getTheme, addThemeNames, getThemeNames } from '../../lib/database/index.js';
|
|
6
|
+
import { machineOutputFlags } from '../../lib/pmo/index.js';
|
|
7
|
+
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
6
8
|
export default class ThemeAddNames extends Command {
|
|
7
9
|
static description = 'Add names to a theme';
|
|
8
10
|
static examples = [
|
|
9
11
|
'<%= config.bin %> <%= command.id %> greek-gods zeus athena poseidon',
|
|
10
12
|
'<%= config.bin %> <%= command.id %> my-theme agent-a agent-b',
|
|
11
13
|
];
|
|
14
|
+
static flags = {
|
|
15
|
+
...machineOutputFlags,
|
|
16
|
+
};
|
|
12
17
|
static args = {
|
|
13
18
|
theme: Args.string({
|
|
14
19
|
description: 'Theme ID',
|
|
@@ -21,7 +26,8 @@ export default class ThemeAddNames extends Command {
|
|
|
21
26
|
};
|
|
22
27
|
static strict = false; // Allow multiple name arguments
|
|
23
28
|
async run() {
|
|
24
|
-
const { args, argv } = await this.parse(ThemeAddNames);
|
|
29
|
+
const { args, argv, flags } = await this.parse(ThemeAddNames);
|
|
30
|
+
const jsonMode = shouldOutputJson(flags);
|
|
25
31
|
try {
|
|
26
32
|
const workspaceInfo = getWorkspaceInfo();
|
|
27
33
|
// Validate theme exists
|
|
@@ -60,6 +66,10 @@ export default class ThemeAddNames extends Command {
|
|
|
60
66
|
addThemeNames(workspaceInfo.path, theme.id, validNames);
|
|
61
67
|
// Get updated count
|
|
62
68
|
const allNames = getThemeNames(workspaceInfo.path, theme.id);
|
|
69
|
+
if (jsonMode) {
|
|
70
|
+
this.log(JSON.stringify({ type: 'success', result: { theme: args.theme, added: validNames, totalNames: allNames.length } }, null, 2));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
63
73
|
this.log(chalk.green(`\n Added ${validNames.length} name(s) to ${theme.display_name}:`));
|
|
64
74
|
this.log(chalk.gray(` ${validNames.join(', ')}`));
|
|
65
75
|
this.log(chalk.gray(`\n Theme now has ${allNames.length} names total.`));
|
|
@@ -8,6 +8,8 @@ export default class ThemeCreate extends Command {
|
|
|
8
8
|
static flags: {
|
|
9
9
|
description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
10
|
'display-name': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
13
|
};
|
|
12
14
|
run(): Promise<void>;
|
|
13
15
|
}
|
|
@@ -2,6 +2,8 @@ import { Command, Args, Flags } from '@oclif/core';
|
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
4
4
|
import { createTheme, getTheme } from '../../lib/database/index.js';
|
|
5
|
+
import { machineOutputFlags } from '../../lib/pmo/index.js';
|
|
6
|
+
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
5
7
|
export default class ThemeCreate extends Command {
|
|
6
8
|
static description = 'Create a custom agent theme';
|
|
7
9
|
static examples = [
|
|
@@ -16,6 +18,7 @@ export default class ThemeCreate extends Command {
|
|
|
16
18
|
}),
|
|
17
19
|
};
|
|
18
20
|
static flags = {
|
|
21
|
+
...machineOutputFlags,
|
|
19
22
|
description: Flags.string({
|
|
20
23
|
char: 'd',
|
|
21
24
|
description: 'Theme description',
|
|
@@ -26,6 +29,7 @@ export default class ThemeCreate extends Command {
|
|
|
26
29
|
};
|
|
27
30
|
async run() {
|
|
28
31
|
const { args, flags } = await this.parse(ThemeCreate);
|
|
32
|
+
const jsonMode = shouldOutputJson(flags);
|
|
29
33
|
try {
|
|
30
34
|
const workspaceInfo = getWorkspaceInfo();
|
|
31
35
|
// Validate theme name format
|
|
@@ -51,6 +55,10 @@ export default class ThemeCreate extends Command {
|
|
|
51
55
|
description: flags.description,
|
|
52
56
|
builtin: false,
|
|
53
57
|
});
|
|
58
|
+
if (jsonMode) {
|
|
59
|
+
this.log(JSON.stringify({ type: 'success', result: { id: theme.id, displayName: theme.display_name, description: theme.description } }, null, 2));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
54
62
|
this.log(chalk.green(`\n Created theme: ${theme.display_name}`));
|
|
55
63
|
this.log(chalk.gray(` ID: ${theme.id}`));
|
|
56
64
|
if (theme.description) {
|
|
@@ -45,7 +45,7 @@ export default class TicketBulk extends PMOCommand {
|
|
|
45
45
|
const choice = menuChoices.find(c => c.value === value);
|
|
46
46
|
return { name: choice.emoji ? `${choice.emoji} ${choice.name}` : choice.name, value: choice.value };
|
|
47
47
|
};
|
|
48
|
-
const { action } = await
|
|
48
|
+
const { action } = await this.prompt([{
|
|
49
49
|
type: 'list',
|
|
50
50
|
name: 'action',
|
|
51
51
|
message: 'What would you like to do?',
|
|
@@ -64,7 +64,7 @@ export default class TicketBulk extends PMOCommand {
|
|
|
64
64
|
new inquirer.Separator(),
|
|
65
65
|
withEmoji('cancel'),
|
|
66
66
|
]
|
|
67
|
-
}]);
|
|
67
|
+
}], null);
|
|
68
68
|
if (action === 'cancel') {
|
|
69
69
|
this.log(styles.muted('Operation cancelled.'));
|
|
70
70
|
return;
|
|
@@ -98,11 +98,11 @@ export default class TicketComplete extends PMOCommand {
|
|
|
98
98
|
}
|
|
99
99
|
async executeBulk(incompleteTickets, doneColumnName, flags) {
|
|
100
100
|
// Only show header in interactive mode
|
|
101
|
-
if (!(flags
|
|
101
|
+
if (!shouldOutputJson(flags)) {
|
|
102
102
|
this.log(styles.emphasis('✅ Complete Multiple Tickets\n'));
|
|
103
103
|
}
|
|
104
104
|
// Agent mode config for prompts
|
|
105
|
-
const jsonModeConfig = (flags
|
|
105
|
+
const jsonModeConfig = shouldOutputJson(flags) ? { flags, commandName: 'ticket complete --bulk' } : null;
|
|
106
106
|
// Select tickets to complete (now agent-compatible!)
|
|
107
107
|
const { selectedTickets } = await this.prompt([{
|
|
108
108
|
type: 'checkbox',
|
|
@@ -284,6 +284,27 @@ export default class TicketCreate extends PMOCommand {
|
|
|
284
284
|
updateEpicTicketsSection(this.pmoPath, ticketData.epicId, epic.status, ticketInfos, projectId);
|
|
285
285
|
}
|
|
286
286
|
}
|
|
287
|
+
// JSON output mode - match MCP tool response shape
|
|
288
|
+
if (jsonMode) {
|
|
289
|
+
this.log(JSON.stringify({
|
|
290
|
+
success: true,
|
|
291
|
+
ticket: {
|
|
292
|
+
id: ticket.id,
|
|
293
|
+
title: ticket.title,
|
|
294
|
+
priority: ticket.priority,
|
|
295
|
+
category: ticket.category,
|
|
296
|
+
statusName: ticket.statusName,
|
|
297
|
+
statusCategory: ticket.statusCategory,
|
|
298
|
+
projectId: ticket.projectId,
|
|
299
|
+
assignee: ticket.assignee,
|
|
300
|
+
owner: ticket.owner,
|
|
301
|
+
branch: ticket.branch,
|
|
302
|
+
epicId: ticket.epicId,
|
|
303
|
+
position: ticket.position,
|
|
304
|
+
},
|
|
305
|
+
}, null, 2));
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
287
308
|
this.log(styles.success(`\n✅ Created ticket ${styles.emphasis(ticket.id)} in project ${styles.emphasis(projectName)}`));
|
|
288
309
|
if (template) {
|
|
289
310
|
this.log(styles.muted(` Template: ${template.name}`));
|
|
@@ -103,6 +103,14 @@ export default class TicketDelete extends PMOCommand {
|
|
|
103
103
|
await this.storage.deleteTicket(ticketId);
|
|
104
104
|
// Auto-export to board.md after write
|
|
105
105
|
await autoExportToBoard(this.pmoPath, this.storage, (msg) => this.log(styles.muted(msg)));
|
|
106
|
+
// JSON output mode - match MCP tool response shape
|
|
107
|
+
if (jsonMode) {
|
|
108
|
+
this.log(JSON.stringify({
|
|
109
|
+
success: true,
|
|
110
|
+
message: `Deleted ${ticketId}`,
|
|
111
|
+
}, null, 2));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
106
114
|
this.log(styles.success(`\n✅ Ticket ${styles.emphasis(ticketId)} deleted`));
|
|
107
115
|
this.log(styles.muted(' Removed from database and board'));
|
|
108
116
|
}
|
|
@@ -227,6 +227,31 @@ export default class TicketEdit extends PMOCommand {
|
|
|
227
227
|
const updatedTicket = await this.storage.updateTicket(ticketId, updates);
|
|
228
228
|
// Auto-export to board.md
|
|
229
229
|
await autoExportToBoard(this.pmoPath, this.storage, (msg) => this.log(styles.muted(msg)));
|
|
230
|
+
// JSON output mode - match MCP tool response shape
|
|
231
|
+
if (jsonMode) {
|
|
232
|
+
// Re-fetch to get latest state including subtasks/AC changes
|
|
233
|
+
const finalTicket = (subtasksChanged || acChanged)
|
|
234
|
+
? await this.storage.getTicket(ticketId) ?? updatedTicket
|
|
235
|
+
: updatedTicket;
|
|
236
|
+
this.log(JSON.stringify({
|
|
237
|
+
success: true,
|
|
238
|
+
ticket: {
|
|
239
|
+
id: finalTicket.id,
|
|
240
|
+
title: finalTicket.title,
|
|
241
|
+
priority: finalTicket.priority,
|
|
242
|
+
category: finalTicket.category,
|
|
243
|
+
statusName: finalTicket.statusName,
|
|
244
|
+
statusCategory: finalTicket.statusCategory,
|
|
245
|
+
projectId: finalTicket.projectId,
|
|
246
|
+
assignee: finalTicket.assignee,
|
|
247
|
+
owner: finalTicket.owner,
|
|
248
|
+
branch: finalTicket.branch,
|
|
249
|
+
epicId: finalTicket.epicId,
|
|
250
|
+
position: finalTicket.position,
|
|
251
|
+
},
|
|
252
|
+
}, null, 2));
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
230
255
|
// Display updated ticket
|
|
231
256
|
this.log(styles.success(`\n✅ Updated ticket ${styles.emphasis(updatedTicket.id)}`));
|
|
232
257
|
const changedFields = [];
|
|
@@ -41,7 +41,7 @@ export default class Ticket extends PMOCommand {
|
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
43
|
// Show interactive menu
|
|
44
|
-
const { action } = await
|
|
44
|
+
const { action } = await this.prompt([{
|
|
45
45
|
type: 'list',
|
|
46
46
|
name: 'action',
|
|
47
47
|
message: '🎫 ' + message,
|
|
@@ -52,7 +52,7 @@ export default class Ticket extends PMOCommand {
|
|
|
52
52
|
menuChoices[12],
|
|
53
53
|
menuChoices[13],
|
|
54
54
|
],
|
|
55
|
-
}]);
|
|
55
|
+
}], null);
|
|
56
56
|
if (action === 'cancel') {
|
|
57
57
|
return;
|
|
58
58
|
}
|
|
@@ -3,6 +3,7 @@ import { autoExportToBoard, PMOCommand, pmoBaseFlags, } from '../../lib/pmo/inde
|
|
|
3
3
|
import { PMOError } from '../../lib/pmo/types.js';
|
|
4
4
|
import { styles } from '../../lib/styles.js';
|
|
5
5
|
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
6
|
+
import { formatTicket } from '../../lib/mcp/helpers.js';
|
|
6
7
|
export default class TicketMove extends PMOCommand {
|
|
7
8
|
static description = 'Move ticket(s) to a different column';
|
|
8
9
|
static examples = [
|
|
@@ -195,6 +196,14 @@ export default class TicketMove extends PMOCommand {
|
|
|
195
196
|
const moved = await this.storage.moveTicket(projectId, ticketId, targetColumn, flags.position);
|
|
196
197
|
// Auto-export to board.md after write
|
|
197
198
|
await autoExportToBoard(this.pmoPath, this.storage, (msg) => this.log(styles.muted(msg)));
|
|
199
|
+
// JSON output mode - match MCP tool response shape
|
|
200
|
+
if (jsonMode) {
|
|
201
|
+
this.log(JSON.stringify({
|
|
202
|
+
success: true,
|
|
203
|
+
ticket: formatTicket(moved),
|
|
204
|
+
}, null, 2));
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
198
207
|
this.log(styles.success(`\n✅ Moved ticket ${styles.emphasis(moved.id)}`));
|
|
199
208
|
if (targetColumn !== ticket.statusName) {
|
|
200
209
|
this.log(styles.muted(` From: ${ticket.statusName}`));
|
|
@@ -206,7 +215,7 @@ export default class TicketMove extends PMOCommand {
|
|
|
206
215
|
}
|
|
207
216
|
async executeBulk(allTickets, flags, projectId) {
|
|
208
217
|
// Only show header in interactive mode
|
|
209
|
-
if (!(flags
|
|
218
|
+
if (!shouldOutputJson(flags)) {
|
|
210
219
|
this.log(styles.emphasis('📦 Move Multiple Tickets\n'));
|
|
211
220
|
}
|
|
212
221
|
// Get columns
|
|
@@ -216,7 +225,7 @@ export default class TicketMove extends PMOCommand {
|
|
|
216
225
|
}
|
|
217
226
|
const columns = board.columns.map(col => col.name);
|
|
218
227
|
// Agent mode config for prompts
|
|
219
|
-
const jsonModeConfig = (flags
|
|
228
|
+
const jsonModeConfig = shouldOutputJson(flags) ? { flags, commandName: 'ticket move --bulk' } : null;
|
|
220
229
|
// Select tickets to move (now agent-compatible!)
|
|
221
230
|
const { selectedTickets } = await this.prompt([{
|
|
222
231
|
type: 'checkbox',
|
|
@@ -328,6 +337,13 @@ export default class TicketMove extends PMOCommand {
|
|
|
328
337
|
// Refresh ticket to get updated status
|
|
329
338
|
const updatedTicket = await this.storage.getTicket(ticketId);
|
|
330
339
|
await autoExportToBoard(this.pmoPath, this.storage, (msg) => this.log(styles.muted(msg)));
|
|
340
|
+
if (jsonMode && updatedTicket) {
|
|
341
|
+
this.log(JSON.stringify({
|
|
342
|
+
success: true,
|
|
343
|
+
ticket: formatTicket(updatedTicket),
|
|
344
|
+
}, null, 2));
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
331
347
|
this.log(styles.success(`\n✅ Moved ticket ${styles.emphasis(ticketId)} to project ${styles.emphasis(targetProject.id)}`));
|
|
332
348
|
this.log(styles.muted(` From project: ${sourceProjectId}`));
|
|
333
349
|
this.log(styles.muted(` To project: ${targetProject.id}`));
|
|
@@ -345,6 +361,13 @@ export default class TicketMove extends PMOCommand {
|
|
|
345
361
|
}
|
|
346
362
|
}
|
|
347
363
|
await autoExportToBoard(this.pmoPath, this.storage, (msg) => this.log(styles.muted(msg)));
|
|
364
|
+
if (jsonMode) {
|
|
365
|
+
this.log(JSON.stringify({
|
|
366
|
+
success: true,
|
|
367
|
+
ticket: formatTicket(movedTicket),
|
|
368
|
+
}, null, 2));
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
348
371
|
this.log(styles.success(`\n✅ Moved ticket ${styles.emphasis(ticketId)} to project ${styles.emphasis(targetProject.id)}`));
|
|
349
372
|
this.log(styles.muted(` From project: ${sourceProjectId}`));
|
|
350
373
|
this.log(styles.muted(` To project: ${targetProject.id}`));
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
|
-
import inquirer from 'inquirer';
|
|
3
2
|
import { autoExportToBoard, PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
4
3
|
import { styles } from '../../lib/styles.js';
|
|
5
4
|
import { shouldOutputJson, outputErrorAsJson, outputSuccessAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
@@ -223,18 +222,18 @@ export default class TicketResolve extends PMOCommand {
|
|
|
223
222
|
for (const question of unanswered) {
|
|
224
223
|
this.log(styles.emphasis(` Q${question.number}: ${question.text}`));
|
|
225
224
|
// eslint-disable-next-line no-await-in-loop
|
|
226
|
-
const { answer } = await
|
|
225
|
+
const { answer } = await this.prompt([
|
|
227
226
|
{
|
|
228
227
|
type: 'input',
|
|
229
228
|
name: 'answer',
|
|
230
229
|
message: ` A${question.number}:`,
|
|
231
230
|
validate: (input) => {
|
|
232
|
-
if (!input.trim())
|
|
231
|
+
if (!String(input).trim())
|
|
233
232
|
return 'Please provide an answer (or type "skip" to skip)';
|
|
234
233
|
return true;
|
|
235
234
|
},
|
|
236
235
|
},
|
|
237
|
-
]);
|
|
236
|
+
], null);
|
|
238
237
|
if (answer.toLowerCase() === 'cancel') {
|
|
239
238
|
cancelled = true;
|
|
240
239
|
this.log(styles.warning('\nResolution cancelled.'));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import TicketView from './view.js';
|
|
2
|
+
export default class TicketShow extends TicketView {
|
|
3
|
+
static description: string;
|
|
4
|
+
static hidden: boolean;
|
|
5
|
+
static args: {
|
|
6
|
+
ticketId: 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 TicketView from './view.js';
|
|
4
|
+
export default class TicketShow extends TicketView {
|
|
5
|
+
static description = 'View detailed ticket information (alias for ticket view)';
|
|
6
|
+
static hidden = true;
|
|
7
|
+
static args = {
|
|
8
|
+
ticketId: Args.string({
|
|
9
|
+
description: 'Ticket ID to view - prompts with dropdown if not provided',
|
|
10
|
+
required: false,
|
|
11
|
+
}),
|
|
12
|
+
};
|
|
13
|
+
static flags = {
|
|
14
|
+
...pmoBaseFlags,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -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 TicketTemplateList extends PMOCommand {
|
|
5
6
|
static description = 'List ticket templates';
|
|
@@ -30,7 +31,7 @@ export default class TicketTemplateList extends PMOCommand {
|
|
|
30
31
|
if (flags.custom)
|
|
31
32
|
builtinFilter = { isBuiltin: false };
|
|
32
33
|
const templates = await this.storage.listTicketTemplates(builtinFilter);
|
|
33
|
-
if (flags
|
|
34
|
+
if (shouldOutputJson(flags)) {
|
|
34
35
|
this.log(JSON.stringify(templates, null, 2));
|
|
35
36
|
return;
|
|
36
37
|
}
|