@proletariat/cli 0.3.9 ā 0.3.11
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/README.md +25 -0
- package/bin/dev.js +0 -0
- package/dist/commands/action/index.js +1 -1
- package/dist/commands/action/run.js +8 -12
- package/dist/commands/agent/auth.d.ts +30 -0
- package/dist/commands/agent/auth.js +172 -0
- package/dist/commands/agent/discover.d.ts +9 -0
- package/dist/commands/agent/discover.js +67 -0
- package/dist/commands/agent/index.js +47 -12
- package/dist/commands/agent/list.d.ts +4 -1
- package/dist/commands/agent/list.js +78 -16
- package/dist/commands/agent/login.js +35 -31
- package/dist/commands/agent/restart.js +2 -0
- package/dist/commands/agent/shell.js +78 -19
- package/dist/commands/agent/staff/add.js +1 -12
- package/dist/commands/agent/staff/remove.js +9 -7
- package/dist/commands/agent/status.js +17 -4
- package/dist/commands/agent/temp/cleanup.js +7 -3
- package/dist/commands/agent/themes/index.js +4 -5
- package/dist/commands/agent/themes/list.js +5 -5
- package/dist/commands/agent/visit.js +17 -4
- package/dist/commands/branch/create.d.ts +4 -0
- package/dist/commands/branch/create.js +16 -8
- package/dist/commands/branch/index.js +1 -1
- package/dist/commands/branch/where.js +1 -0
- package/dist/commands/claude.d.ts +38 -0
- package/dist/commands/claude.js +899 -0
- package/dist/commands/commit.js +1 -1
- package/dist/commands/config/index.d.ts +12 -0
- package/dist/commands/config/index.js +271 -0
- package/dist/commands/docker/clean.js +2 -2
- package/dist/commands/docker/index.js +2 -2
- package/dist/commands/docker/list.js +3 -8
- package/dist/commands/docker/logs.js +2 -2
- package/dist/commands/docker/prune.js +1 -1
- package/dist/commands/docker/restart.js +2 -2
- package/dist/commands/docker/shell.js +2 -2
- package/dist/commands/docker/start.js +2 -2
- package/dist/commands/docker/status.js +1 -1
- package/dist/commands/docker/stop.js +2 -2
- package/dist/commands/docker/sync.js +2 -2
- package/dist/commands/epic/index.js +1 -1
- package/dist/commands/epic/link/index.js +25 -14
- package/dist/commands/epic/link/remove.js +2 -0
- package/dist/commands/epic/list.js +5 -5
- package/dist/commands/epic/progress.js +10 -4
- package/dist/commands/epic/spec.js +2 -0
- package/dist/commands/epic/ticket.js +3 -0
- package/dist/commands/execution/stop.js +1 -0
- package/dist/commands/init.js +4 -4
- package/dist/commands/project/index.js +1 -1
- package/dist/commands/project/spec.js +7 -0
- package/dist/commands/repo/add.js +1 -0
- package/dist/commands/repo/remove.js +1 -0
- package/dist/commands/roadmap/add-project.d.ts +18 -0
- package/dist/commands/roadmap/add-project.js +135 -0
- package/dist/commands/roadmap/create.d.ts +22 -0
- package/dist/commands/roadmap/create.js +156 -0
- package/dist/commands/roadmap/delete.d.ts +17 -0
- package/dist/commands/roadmap/delete.js +104 -0
- package/dist/commands/roadmap/generate.d.ts +22 -0
- package/dist/commands/roadmap/generate.js +201 -0
- package/dist/commands/roadmap/index.d.ts +13 -0
- package/dist/commands/roadmap/index.js +61 -0
- package/dist/commands/roadmap/list.d.ts +12 -0
- package/dist/commands/roadmap/list.js +42 -0
- package/dist/commands/roadmap/remove-project.d.ts +18 -0
- package/dist/commands/roadmap/remove-project.js +147 -0
- package/dist/commands/roadmap/reorder.d.ts +17 -0
- package/dist/commands/roadmap/reorder.js +157 -0
- package/dist/commands/roadmap/update.d.ts +19 -0
- package/dist/commands/roadmap/update.js +136 -0
- package/dist/commands/roadmap/view.d.ts +16 -0
- package/dist/commands/roadmap/view.js +103 -0
- package/dist/commands/spec/index.js +1 -1
- package/dist/commands/spec/link/index.js +24 -13
- package/dist/commands/spec/link/remove.js +2 -0
- package/dist/commands/status/index.js +1 -1
- package/dist/commands/status/list.js +0 -8
- package/dist/commands/template/delete.js +2 -0
- package/dist/commands/terminal/title.d.ts +12 -0
- package/dist/commands/terminal/title.js +48 -0
- package/dist/commands/ticket/complete.js +2 -0
- package/dist/commands/ticket/create.js +4 -2
- package/dist/commands/ticket/delete.js +2 -0
- package/dist/commands/ticket/edit.js +8 -2
- package/dist/commands/ticket/link/index.js +17 -3
- package/dist/commands/ticket/link/remove.js +2 -0
- package/dist/commands/ticket/list.js +1 -2
- package/dist/commands/ticket/move.js +2 -0
- package/dist/commands/ticket/project.js +3 -1
- package/dist/commands/ticket/reassign.js +2 -0
- package/dist/commands/ticket/spec.js +4 -2
- package/dist/commands/ticket/template/apply.js +4 -3
- package/dist/commands/ticket/template/create.js +2 -0
- package/dist/commands/ticket/template/index.js +1 -1
- package/dist/commands/ticket/update.js +2 -0
- package/dist/commands/work/index.js +1 -1
- package/dist/commands/work/revise.js +7 -1
- package/dist/commands/work/spawn.d.ts +2 -1
- package/dist/commands/work/spawn.js +131 -36
- package/dist/commands/work/start.d.ts +2 -1
- package/dist/commands/work/start.js +349 -69
- package/dist/commands/work/watch.js +10 -2
- package/dist/commands/workflow/create.js +3 -3
- package/dist/commands/workflow/switch.js +2 -1
- package/dist/commands/workspace/remove.js +0 -8
- package/dist/commands/workspace/use.js +1 -9
- package/dist/lib/agents/commands.js +18 -13
- package/dist/lib/database/index.d.ts +19 -12
- package/dist/lib/database/index.js +158 -42
- package/dist/lib/docker/resolve.js +1 -1
- package/dist/lib/execution/config.d.ts +6 -0
- package/dist/lib/execution/config.js +15 -2
- package/dist/lib/execution/devcontainer.d.ts +2 -0
- package/dist/lib/execution/devcontainer.js +41 -9
- package/dist/lib/execution/runners.d.ts +85 -3
- package/dist/lib/execution/runners.js +925 -228
- package/dist/lib/execution/spawner.d.ts +2 -2
- package/dist/lib/execution/spawner.js +4 -3
- package/dist/lib/execution/storage.d.ts +2 -1
- package/dist/lib/execution/storage.js +9 -13
- package/dist/lib/execution/types.d.ts +10 -1
- package/dist/lib/execution/types.js +3 -1
- package/dist/lib/init/index.js +1 -0
- package/dist/lib/machine-config.js +1 -1
- package/dist/lib/pmo/base-command.js +5 -9
- package/dist/lib/pmo/index.js +2 -0
- package/dist/lib/pmo/schema.d.ts +6 -0
- package/dist/lib/pmo/schema.js +36 -0
- package/dist/lib/pmo/storage/base.js +3 -3
- package/dist/lib/pmo/storage/index.d.ts +16 -1
- package/dist/lib/pmo/storage/index.js +45 -0
- package/dist/lib/pmo/storage/roadmaps.d.ts +62 -0
- package/dist/lib/pmo/storage/roadmaps.js +301 -0
- package/dist/lib/pmo/storage/specs.js +2 -0
- package/dist/lib/pmo/storage/types.d.ts +14 -0
- package/dist/lib/pmo/sync-manager.d.ts +1 -1
- package/dist/lib/pmo/sync-manager.js +1 -1
- package/dist/lib/pmo/types.d.ts +41 -0
- package/dist/lib/pmo/utils.d.ts +2 -0
- package/dist/lib/pmo/utils.js +22 -1
- package/dist/lib/repos/index.js +7 -1
- package/dist/lib/terminal.d.ts +31 -0
- package/dist/lib/terminal.js +48 -0
- package/dist/lib/themes.d.ts +21 -3
- package/dist/lib/themes.js +80 -23
- package/dist/lib/workspace-config.d.ts +80 -0
- package/dist/lib/workspace-config.js +100 -0
- package/oclif.manifest.json +4065 -3225
- package/package.json +10 -6
- package/LICENSE +0 -21
package/dist/commands/commit.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command, Args, Flags } from '@oclif/core';
|
|
2
|
-
import { execSync } from 'child_process';
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
3
|
import inquirer from 'inquirer';
|
|
4
4
|
import { validateBranchName } from '../lib/branch/index.js';
|
|
5
5
|
import { styles } from '../lib/styles.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Config extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
set: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
list: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
};
|
|
10
|
+
run(): Promise<void>;
|
|
11
|
+
private setConfigValue;
|
|
12
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import Database from 'better-sqlite3';
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import { Command } from '@oclif/core';
|
|
6
|
+
import { styles } from '../../lib/styles.js';
|
|
7
|
+
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
8
|
+
import { loadExecutionConfig, saveTerminalApp, saveTerminalOpenInBackground, saveTmuxControlMode, saveShell, } from '../../lib/execution/config.js';
|
|
9
|
+
import { shouldOutputJson, outputPromptAsJson, outputSuccessAsJson, outputErrorAsJson, createMetadata, buildPromptConfig, } from '../../lib/prompt-json.js';
|
|
10
|
+
export default class Config extends Command {
|
|
11
|
+
static description = 'View and update workspace configuration';
|
|
12
|
+
static examples = [
|
|
13
|
+
'<%= config.bin %> <%= command.id %> # Interactive menu',
|
|
14
|
+
'<%= config.bin %> <%= command.id %> --json # Output current config as JSON',
|
|
15
|
+
'<%= config.bin %> <%= command.id %> --set terminal.app iTerm',
|
|
16
|
+
'<%= config.bin %> <%= command.id %> --set terminal.openInBackground true',
|
|
17
|
+
];
|
|
18
|
+
static flags = {
|
|
19
|
+
json: Flags.boolean({
|
|
20
|
+
description: 'Output configuration as JSON (for AI agents/scripts)',
|
|
21
|
+
default: false,
|
|
22
|
+
}),
|
|
23
|
+
set: Flags.string({
|
|
24
|
+
char: 's',
|
|
25
|
+
description: 'Set a config value (format: key value)',
|
|
26
|
+
multiple: true,
|
|
27
|
+
}),
|
|
28
|
+
list: Flags.boolean({
|
|
29
|
+
char: 'l',
|
|
30
|
+
description: 'List all configuration values',
|
|
31
|
+
default: false,
|
|
32
|
+
}),
|
|
33
|
+
};
|
|
34
|
+
async run() {
|
|
35
|
+
const { flags } = await this.parse(Config);
|
|
36
|
+
const jsonMode = shouldOutputJson(flags);
|
|
37
|
+
// Get workspace info
|
|
38
|
+
let workspaceInfo;
|
|
39
|
+
try {
|
|
40
|
+
workspaceInfo = getWorkspaceInfo();
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
if (jsonMode) {
|
|
44
|
+
outputErrorAsJson('NOT_IN_WORKSPACE', 'Not in a workspace. Run "prlt init" first.', createMetadata('config', flags));
|
|
45
|
+
this.exit(1);
|
|
46
|
+
}
|
|
47
|
+
this.error('Not in a workspace. Run "prlt init" first.');
|
|
48
|
+
}
|
|
49
|
+
// Open database
|
|
50
|
+
const dbPath = path.join(workspaceInfo.path, '.proletariat', 'workspace.db');
|
|
51
|
+
const db = new Database(dbPath);
|
|
52
|
+
try {
|
|
53
|
+
// Load current config
|
|
54
|
+
const config = loadExecutionConfig(db);
|
|
55
|
+
// Handle --set flag
|
|
56
|
+
if (flags.set && flags.set.length > 0) {
|
|
57
|
+
for (const setValue of flags.set) {
|
|
58
|
+
const [key, ...valueParts] = setValue.split(' ');
|
|
59
|
+
const value = valueParts.join(' ');
|
|
60
|
+
if (!key || !value) {
|
|
61
|
+
if (jsonMode) {
|
|
62
|
+
outputErrorAsJson('INVALID_SET_FORMAT', `Invalid format: "${setValue}". Use: --set "key value"`, createMetadata('config', flags));
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
this.error(`Invalid format: "${setValue}". Use: --set "key value"`);
|
|
66
|
+
}
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
this.setConfigValue(db, key, value, jsonMode);
|
|
70
|
+
}
|
|
71
|
+
db.close();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
// Handle --list or --json flag (just show config)
|
|
75
|
+
if (flags.list || flags.json) {
|
|
76
|
+
if (jsonMode) {
|
|
77
|
+
outputSuccessAsJson({
|
|
78
|
+
terminal: {
|
|
79
|
+
app: config.terminal.app,
|
|
80
|
+
openInBackground: config.terminal.openInBackground,
|
|
81
|
+
},
|
|
82
|
+
shell: config.shell,
|
|
83
|
+
tmux: {
|
|
84
|
+
controlMode: config.tmux.controlMode,
|
|
85
|
+
},
|
|
86
|
+
defaultExecutor: config.defaultExecutor,
|
|
87
|
+
defaultEnvironment: config.defaultEnvironment,
|
|
88
|
+
outputMode: config.outputMode,
|
|
89
|
+
sandboxed: config.sandboxed,
|
|
90
|
+
}, createMetadata('config', flags));
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
this.log('');
|
|
94
|
+
this.log(styles.header('Workspace Configuration'));
|
|
95
|
+
this.log('ā'.repeat(50));
|
|
96
|
+
this.log('');
|
|
97
|
+
this.log(styles.emphasis('Terminal'));
|
|
98
|
+
this.log(` app: ${config.terminal.app}`);
|
|
99
|
+
this.log(` openInBackground: ${config.terminal.openInBackground}`);
|
|
100
|
+
this.log('');
|
|
101
|
+
this.log(styles.emphasis('Shell'));
|
|
102
|
+
this.log(` shell: ${config.shell}`);
|
|
103
|
+
this.log('');
|
|
104
|
+
this.log(styles.emphasis('Tmux'));
|
|
105
|
+
this.log(` controlMode: ${config.tmux.controlMode}`);
|
|
106
|
+
this.log('');
|
|
107
|
+
this.log(styles.emphasis('Execution'));
|
|
108
|
+
this.log(` defaultExecutor: ${config.defaultExecutor}`);
|
|
109
|
+
this.log(` defaultEnvironment: ${config.defaultEnvironment}`);
|
|
110
|
+
this.log(` outputMode: ${config.outputMode}`);
|
|
111
|
+
this.log(` sandboxed: ${config.sandboxed}`);
|
|
112
|
+
this.log('');
|
|
113
|
+
}
|
|
114
|
+
db.close();
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// Interactive menu
|
|
118
|
+
const settingChoices = [
|
|
119
|
+
{ name: `Terminal App: ${config.terminal.app}`, value: 'terminal.app' },
|
|
120
|
+
{ name: `Open Tabs in Background: ${config.terminal.openInBackground}`, value: 'terminal.openInBackground' },
|
|
121
|
+
{ name: `Shell: ${config.shell}`, value: 'shell' },
|
|
122
|
+
{ name: `Tmux Control Mode (iTerm -CC): ${config.tmux.controlMode}`, value: 'tmux.controlMode' },
|
|
123
|
+
];
|
|
124
|
+
const settingMessage = 'Select setting to configure:';
|
|
125
|
+
if (jsonMode) {
|
|
126
|
+
outputPromptAsJson(buildPromptConfig('list', 'setting', settingMessage, settingChoices), createMetadata('config', flags));
|
|
127
|
+
db.close();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const { setting } = await inquirer.prompt([
|
|
131
|
+
{
|
|
132
|
+
type: 'list',
|
|
133
|
+
name: 'setting',
|
|
134
|
+
message: settingMessage,
|
|
135
|
+
choices: [
|
|
136
|
+
new inquirer.Separator('āā Terminal āā'),
|
|
137
|
+
...settingChoices.slice(0, 2),
|
|
138
|
+
new inquirer.Separator('āā Shell āā'),
|
|
139
|
+
settingChoices[2],
|
|
140
|
+
new inquirer.Separator('āā Tmux āā'),
|
|
141
|
+
settingChoices[3],
|
|
142
|
+
new inquirer.Separator(),
|
|
143
|
+
{ name: 'Exit', value: '__exit__' },
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
]);
|
|
147
|
+
if (setting === '__exit__') {
|
|
148
|
+
db.close();
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
// Handle each setting
|
|
152
|
+
switch (setting) {
|
|
153
|
+
case 'terminal.app': {
|
|
154
|
+
const appChoices = [
|
|
155
|
+
{ name: 'iTerm', value: 'iTerm' },
|
|
156
|
+
{ name: 'Terminal.app (macOS default)', value: 'Terminal' },
|
|
157
|
+
{ name: 'Ghostty', value: 'Ghostty' },
|
|
158
|
+
{ name: 'Alacritty', value: 'Alacritty' },
|
|
159
|
+
{ name: 'Kitty', value: 'Kitty' },
|
|
160
|
+
{ name: 'WezTerm', value: 'WezTerm' },
|
|
161
|
+
{ name: 'Warp', value: 'Warp' },
|
|
162
|
+
{ name: 'tmux', value: 'tmux' },
|
|
163
|
+
];
|
|
164
|
+
const { newApp } = await inquirer.prompt([
|
|
165
|
+
{
|
|
166
|
+
type: 'list',
|
|
167
|
+
name: 'newApp',
|
|
168
|
+
message: 'Select terminal app:',
|
|
169
|
+
choices: appChoices,
|
|
170
|
+
default: config.terminal.app,
|
|
171
|
+
},
|
|
172
|
+
]);
|
|
173
|
+
saveTerminalApp(db, newApp);
|
|
174
|
+
this.log(styles.success(`Terminal app set to: ${newApp}`));
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
case 'terminal.openInBackground': {
|
|
178
|
+
const bgChoices = [
|
|
179
|
+
{ name: 'Yes - Open tabs in background (don\'t steal focus)', value: true },
|
|
180
|
+
{ name: 'No - Bring terminal to foreground when opening tabs', value: false },
|
|
181
|
+
];
|
|
182
|
+
const { openInBg } = await inquirer.prompt([
|
|
183
|
+
{
|
|
184
|
+
type: 'list',
|
|
185
|
+
name: 'openInBg',
|
|
186
|
+
message: 'Open terminal tabs in background?',
|
|
187
|
+
choices: bgChoices,
|
|
188
|
+
default: config.terminal.openInBackground,
|
|
189
|
+
},
|
|
190
|
+
]);
|
|
191
|
+
saveTerminalOpenInBackground(db, openInBg);
|
|
192
|
+
this.log(styles.success(`Open in background set to: ${openInBg}`));
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
case 'shell': {
|
|
196
|
+
const shellChoices = [
|
|
197
|
+
{ name: 'zsh (macOS default)', value: 'zsh' },
|
|
198
|
+
{ name: 'bash', value: 'bash' },
|
|
199
|
+
{ name: 'fish', value: 'fish' },
|
|
200
|
+
];
|
|
201
|
+
const { newShell } = await inquirer.prompt([
|
|
202
|
+
{
|
|
203
|
+
type: 'list',
|
|
204
|
+
name: 'newShell',
|
|
205
|
+
message: 'Select shell:',
|
|
206
|
+
choices: shellChoices,
|
|
207
|
+
default: config.shell,
|
|
208
|
+
},
|
|
209
|
+
]);
|
|
210
|
+
saveShell(db, newShell);
|
|
211
|
+
this.log(styles.success(`Shell set to: ${newShell}`));
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
case 'tmux.controlMode': {
|
|
215
|
+
const ccChoices = [
|
|
216
|
+
{ name: 'Yes - Use tmux -CC for native iTerm integration', value: true },
|
|
217
|
+
{ name: 'No - Standard tmux interface', value: false },
|
|
218
|
+
];
|
|
219
|
+
const { controlMode } = await inquirer.prompt([
|
|
220
|
+
{
|
|
221
|
+
type: 'list',
|
|
222
|
+
name: 'controlMode',
|
|
223
|
+
message: 'Enable tmux control mode (-CC)?',
|
|
224
|
+
choices: ccChoices,
|
|
225
|
+
default: config.tmux.controlMode,
|
|
226
|
+
},
|
|
227
|
+
]);
|
|
228
|
+
saveTmuxControlMode(db, controlMode);
|
|
229
|
+
this.log(styles.success(`Tmux control mode set to: ${controlMode}`));
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
db.close();
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
db.close();
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
setConfigValue(db, key, value, jsonMode) {
|
|
241
|
+
const normalizedKey = key.toLowerCase();
|
|
242
|
+
switch (normalizedKey) {
|
|
243
|
+
case 'terminal.app':
|
|
244
|
+
saveTerminalApp(db, value);
|
|
245
|
+
break;
|
|
246
|
+
case 'terminal.openinbackground':
|
|
247
|
+
saveTerminalOpenInBackground(db, value.toLowerCase() === 'true');
|
|
248
|
+
break;
|
|
249
|
+
case 'shell':
|
|
250
|
+
saveShell(db, value);
|
|
251
|
+
break;
|
|
252
|
+
case 'tmux.controlmode':
|
|
253
|
+
saveTmuxControlMode(db, value.toLowerCase() === 'true');
|
|
254
|
+
break;
|
|
255
|
+
default:
|
|
256
|
+
if (jsonMode) {
|
|
257
|
+
outputErrorAsJson('UNKNOWN_KEY', `Unknown config key: ${key}`, createMetadata('config', {}));
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
this.warn(`Unknown config key: ${key}`);
|
|
261
|
+
}
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (jsonMode) {
|
|
265
|
+
outputSuccessAsJson({ key, value }, createMetadata('config', {}));
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
this.log(styles.success(`Set ${key} = ${value}`));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command, Flags } from '@oclif/core';
|
|
2
|
-
import { execSync } from 'child_process';
|
|
3
|
-
import * as path from 'path';
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
4
|
import Database from 'better-sqlite3';
|
|
5
5
|
import inquirer from 'inquirer';
|
|
6
6
|
import { styles } from '../../lib/styles.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command, Flags } from '@oclif/core';
|
|
2
2
|
import inquirer from 'inquirer';
|
|
3
|
-
import { execSync } from 'child_process';
|
|
4
|
-
import * as path from 'path';
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
5
|
import Database from 'better-sqlite3';
|
|
6
6
|
import { styles } from '../../lib/styles.js';
|
|
7
7
|
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command, Flags } from '@oclif/core';
|
|
2
|
-
import { execSync } from 'child_process';
|
|
3
|
-
import * as path from 'path';
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
4
|
import Database from 'better-sqlite3';
|
|
5
5
|
import { styles } from '../../lib/styles.js';
|
|
6
6
|
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
@@ -134,7 +134,7 @@ export default class DockerList extends Command {
|
|
|
134
134
|
getDockerContainers(agentsPath, showAll) {
|
|
135
135
|
try {
|
|
136
136
|
// Get containers - filter by devcontainer label unless --all
|
|
137
|
-
|
|
137
|
+
const cmd = 'docker ps -a --format "{{.ID}}|{{.Names}}|{{.Image}}|{{.Status}}|{{.Label \\"devcontainer.local_folder\\"}}"';
|
|
138
138
|
const output = execSync(cmd, {
|
|
139
139
|
encoding: 'utf-8',
|
|
140
140
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
@@ -177,11 +177,6 @@ export default class DockerList extends Command {
|
|
|
177
177
|
function padEnd(str, length) {
|
|
178
178
|
return str.padEnd(length);
|
|
179
179
|
}
|
|
180
|
-
function truncate(str, maxLength) {
|
|
181
|
-
if (str.length <= maxLength)
|
|
182
|
-
return str;
|
|
183
|
-
return str.substring(0, maxLength - 2) + '..';
|
|
184
|
-
}
|
|
185
180
|
function getStatusColor(status) {
|
|
186
181
|
switch (status) {
|
|
187
182
|
case 'running':
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
-
import { execSync, spawn } from 'child_process';
|
|
3
|
-
import * as path from 'path';
|
|
2
|
+
import { execSync, spawn } from 'node:child_process';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
4
|
import Database from 'better-sqlite3';
|
|
5
5
|
import { styles } from '../../lib/styles.js';
|
|
6
6
|
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command, Flags } from '@oclif/core';
|
|
2
|
-
import { execSync } from 'child_process';
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
3
|
import inquirer from 'inquirer';
|
|
4
4
|
import { styles } from '../../lib/styles.js';
|
|
5
5
|
import { isDockerRunning } from '../../lib/execution/runners.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
-
import { execSync } from 'child_process';
|
|
3
|
-
import * as path from 'path';
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
4
|
import Database from 'better-sqlite3';
|
|
5
5
|
import inquirer from 'inquirer';
|
|
6
6
|
import { styles } from '../../lib/styles.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
-
import { spawn } from 'child_process';
|
|
3
|
-
import * as path from 'path';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
4
|
import Database from 'better-sqlite3';
|
|
5
5
|
import { styles } from '../../lib/styles.js';
|
|
6
6
|
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
-
import { execSync } from 'child_process';
|
|
3
|
-
import * as path from 'path';
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
4
|
import Database from 'better-sqlite3';
|
|
5
5
|
import { styles } from '../../lib/styles.js';
|
|
6
6
|
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
|
-
import { execSync } from 'child_process';
|
|
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
5
|
export default class DockerStatus extends Command {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
-
import { execSync } from 'child_process';
|
|
3
|
-
import * as path from 'path';
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
4
|
import Database from 'better-sqlite3';
|
|
5
5
|
import inquirer from 'inquirer';
|
|
6
6
|
import { styles } from '../../lib/styles.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
|
-
import { execSync } from 'child_process';
|
|
3
|
-
import * as path from 'path';
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
4
|
import Database from 'better-sqlite3';
|
|
5
5
|
import { styles } from '../../lib/styles.js';
|
|
6
6
|
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
3
|
-
import { shouldOutputJson
|
|
3
|
+
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
4
4
|
export default class Epic extends PMOCommand {
|
|
5
5
|
static description = 'Interactive menu for epic operations';
|
|
6
6
|
static examples = [
|
|
@@ -99,8 +99,10 @@ export default class EpicLink extends PMOCommand {
|
|
|
99
99
|
// Interactive mode: show menu in a loop
|
|
100
100
|
let continueLoop = true;
|
|
101
101
|
while (continueLoop) {
|
|
102
|
+
// eslint-disable-next-line no-await-in-loop -- Interactive user loop
|
|
102
103
|
const allEpics = await this.storage.listEpics(projectId);
|
|
103
104
|
const otherEpics = allEpics.filter(e => e.id !== epicId);
|
|
105
|
+
// eslint-disable-next-line no-await-in-loop -- Interactive user prompt
|
|
104
106
|
const { action } = await inquirer.prompt([{
|
|
105
107
|
type: 'list',
|
|
106
108
|
name: 'action',
|
|
@@ -120,15 +122,18 @@ export default class EpicLink extends PMOCommand {
|
|
|
120
122
|
continue;
|
|
121
123
|
}
|
|
122
124
|
if (action === 'view') {
|
|
125
|
+
// eslint-disable-next-line no-await-in-loop -- User action handling
|
|
123
126
|
await this.viewDependencies(epicId, epic, flags.all);
|
|
124
127
|
continue;
|
|
125
128
|
}
|
|
126
129
|
if (action === 'remove') {
|
|
130
|
+
// eslint-disable-next-line no-await-in-loop -- User action handling
|
|
127
131
|
const dependencies = await this.storage.listEpicDependencies(epicId);
|
|
128
132
|
if (dependencies.length === 0) {
|
|
129
133
|
this.log(styles.muted('\nNo dependencies to remove.'));
|
|
130
134
|
continue;
|
|
131
135
|
}
|
|
136
|
+
// eslint-disable-next-line no-await-in-loop -- Building choices for current interaction
|
|
132
137
|
const choices = await Promise.all(dependencies.map(async (dep) => {
|
|
133
138
|
const depEpic = await this.storage.getEpic(dep.dependsOnEpicId);
|
|
134
139
|
return {
|
|
@@ -136,13 +141,16 @@ export default class EpicLink extends PMOCommand {
|
|
|
136
141
|
value: { targetId: dep.dependsOnEpicId, type: dep.dependencyType }
|
|
137
142
|
};
|
|
138
143
|
}));
|
|
144
|
+
// eslint-disable-next-line no-await-in-loop -- User selection prompt
|
|
139
145
|
const { selected } = await inquirer.prompt([{
|
|
140
146
|
type: 'list',
|
|
141
147
|
name: 'selected',
|
|
142
148
|
message: 'Select dependency to remove:',
|
|
143
149
|
choices,
|
|
144
150
|
}]);
|
|
151
|
+
// eslint-disable-next-line no-await-in-loop -- Action after user selection
|
|
145
152
|
await this.storage.deleteEpicDependency(epicId, selected.targetId, selected.type);
|
|
153
|
+
// eslint-disable-next-line no-await-in-loop
|
|
146
154
|
await autoExportToBoard(this.pmoPath, this.storage, (msg) => this.log(styles.muted(msg)));
|
|
147
155
|
this.log(styles.success(`\nā
Removed dependency: ${epicId} ā ${selected.targetId}`));
|
|
148
156
|
continue;
|
|
@@ -152,12 +160,14 @@ export default class EpicLink extends PMOCommand {
|
|
|
152
160
|
this.log(styles.muted('\nNo other epics to link to.'));
|
|
153
161
|
continue;
|
|
154
162
|
}
|
|
163
|
+
// eslint-disable-next-line no-await-in-loop -- User selection prompt
|
|
155
164
|
const { targetId } = await inquirer.prompt([{
|
|
156
165
|
type: 'list',
|
|
157
166
|
name: 'targetId',
|
|
158
167
|
message: `Select epic that ${epicId} ${action === 'blocks' ? 'is blocked by' : action === 'relates_to' ? 'relates to' : 'duplicates'}:`,
|
|
159
168
|
choices: otherEpics.map(e => ({ name: `${e.id} - ${e.title}`, value: e.id })),
|
|
160
169
|
}]);
|
|
170
|
+
// eslint-disable-next-line no-await-in-loop -- Action after user selection
|
|
161
171
|
await this.addDependency(epicId, targetId, action, epic.title);
|
|
162
172
|
}
|
|
163
173
|
}
|
|
@@ -197,8 +207,9 @@ export default class EpicLink extends PMOCommand {
|
|
|
197
207
|
const blockers = dependencies.filter(d => d.dependencyType === 'blocks');
|
|
198
208
|
if (blockers.length > 0) {
|
|
199
209
|
this.log(styles.muted('\n Blocked by:'));
|
|
200
|
-
|
|
201
|
-
|
|
210
|
+
// Fetch all blocker epics in parallel
|
|
211
|
+
const blockerEpics = await Promise.all(blockers.map(dep => this.storage.getEpic(dep.dependsOnEpicId)));
|
|
212
|
+
for (const blockerEpic of blockerEpics) {
|
|
202
213
|
if (blockerEpic) {
|
|
203
214
|
const status = blockerEpic.status === 'complete' ? styles.success('complete') : styles.warning(blockerEpic.status);
|
|
204
215
|
this.log(` - ${blockerEpic.id}: ${blockerEpic.title} (${status})`);
|
|
@@ -208,8 +219,9 @@ export default class EpicLink extends PMOCommand {
|
|
|
208
219
|
const otherDeps = dependencies.filter(d => d.dependencyType !== 'blocks');
|
|
209
220
|
if (otherDeps.length > 0) {
|
|
210
221
|
this.log(styles.muted('\n Related:'));
|
|
211
|
-
|
|
212
|
-
|
|
222
|
+
// Fetch all related epics in parallel
|
|
223
|
+
const relatedEpics = await Promise.all(otherDeps.map(async (dep) => ({ dep, epic: await this.storage.getEpic(dep.dependsOnEpicId) })));
|
|
224
|
+
for (const { dep, epic: relatedEpic } of relatedEpics) {
|
|
213
225
|
if (relatedEpic) {
|
|
214
226
|
this.log(` - ${dep.dependencyType}: ${relatedEpic.id} - ${relatedEpic.title}`);
|
|
215
227
|
}
|
|
@@ -217,20 +229,19 @@ export default class EpicLink extends PMOCommand {
|
|
|
217
229
|
}
|
|
218
230
|
if (showAll) {
|
|
219
231
|
const allEpics = await this.storage.listEpics(epic.projectId);
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
232
|
+
// Find all epics that depend on this epic in parallel
|
|
233
|
+
const blockingResults = await Promise.all(allEpics
|
|
234
|
+
.filter(otherEpic => otherEpic.id !== epicId)
|
|
235
|
+
.map(async (otherEpic) => {
|
|
224
236
|
const otherDeps = await this.storage.listEpicDependencies(otherEpic.id);
|
|
225
237
|
const blockingDep = otherDeps.find(d => d.dependsOnEpicId === epicId);
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
238
|
+
return blockingDep ? { epic: otherEpic, type: blockingDep.dependencyType } : null;
|
|
239
|
+
}));
|
|
240
|
+
const blocking = blockingResults.filter((b) => b !== null);
|
|
230
241
|
if (blocking.length > 0) {
|
|
231
242
|
this.log(styles.muted('\n Blocking:'));
|
|
232
|
-
for (const
|
|
233
|
-
this.log(` - ${
|
|
243
|
+
for (const item of blocking) {
|
|
244
|
+
this.log(` - ${item.epic.id}: ${item.epic.title} (${item.type})`);
|
|
234
245
|
}
|
|
235
246
|
}
|
|
236
247
|
}
|
|
@@ -62,6 +62,8 @@ export default class EpicLinkRemove extends PMOCommand {
|
|
|
62
62
|
this.log(styles.muted('\nCancelled.'));
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
65
|
+
// Delete sequentially to maintain data integrity
|
|
66
|
+
// eslint-disable-next-line no-await-in-loop
|
|
65
67
|
for (const dep of dependencies)
|
|
66
68
|
await this.storage.deleteEpicDependency(args.id, dep.dependsOnEpicId, dep.dependencyType);
|
|
67
69
|
await autoExportToBoard(this.pmoPath, this.storage, (msg) => this.log(styles.muted(msg)));
|
|
@@ -32,13 +32,13 @@ export default class EpicList extends PMOCommand {
|
|
|
32
32
|
}
|
|
33
33
|
// Group epics by status
|
|
34
34
|
const grouped = this.groupByStatus(epics);
|
|
35
|
-
// Get ticket counts for each epic
|
|
36
|
-
const
|
|
37
|
-
for (const epic of epics) {
|
|
35
|
+
// Get ticket counts for each epic in parallel
|
|
36
|
+
const ticketCounts = await Promise.all(epics.map(async (epic) => {
|
|
38
37
|
const tickets = await this.storage.getTicketsForEpic(projectId, epic.id);
|
|
39
38
|
const done = tickets.filter((t) => t.status === 'done').length;
|
|
40
|
-
|
|
41
|
-
}
|
|
39
|
+
return { epicId: epic.id, done, total: tickets.length };
|
|
40
|
+
}));
|
|
41
|
+
const epicProgress = new Map(ticketCounts.map(({ epicId, done, total }) => [epicId, { done, total }]));
|
|
42
42
|
const projectName = await this.getProjectName(projectId);
|
|
43
43
|
this.log(`\nšÆ ${styles.emphasis('Epics')} - ${projectName}`);
|
|
44
44
|
this.log('ā'.repeat(55));
|
|
@@ -145,18 +145,24 @@ export default class EpicProgress extends PMOCommand {
|
|
|
145
145
|
dropped: 'ā',
|
|
146
146
|
future: 'š®',
|
|
147
147
|
};
|
|
148
|
+
// Fetch all epic ticket counts in parallel
|
|
149
|
+
const epicProgress = new Map();
|
|
150
|
+
await Promise.all(epics.map(async (epic) => {
|
|
151
|
+
const tickets = await this.storage.getTicketsForEpic(projectId, epic.id);
|
|
152
|
+
const doneTickets = tickets.filter((t) => t.status === 'done').length;
|
|
153
|
+
epicProgress.set(epic.id, { done: doneTickets, total: tickets.length });
|
|
154
|
+
}));
|
|
148
155
|
for (const status of statusOrder) {
|
|
149
156
|
const statusEpics = grouped.get(status);
|
|
150
157
|
if (!statusEpics || statusEpics.length === 0)
|
|
151
158
|
continue;
|
|
152
159
|
this.log(`\n${statusEmoji[status]} ${status.toUpperCase()} (${statusEpics.length})`);
|
|
153
160
|
for (const epic of statusEpics) {
|
|
154
|
-
const
|
|
155
|
-
const
|
|
156
|
-
const percent = tickets.length > 0 ? Math.round((doneTickets / tickets.length) * 100) : 0;
|
|
161
|
+
const progress = epicProgress.get(epic.id) || { done: 0, total: 0 };
|
|
162
|
+
const percent = progress.total > 0 ? Math.round((progress.done / progress.total) * 100) : 0;
|
|
157
163
|
const bar = progressBar(percent);
|
|
158
164
|
const readyToArchive = percent === 100 && status === 'active' ? ' ā ready to archive' : '';
|
|
159
|
-
this.log(` ${epic.id.padEnd(10)} ${epic.title.substring(0, 30).padEnd(30)} ${bar} ${String(percent).padStart(3)}% (${
|
|
165
|
+
this.log(` ${epic.id.padEnd(10)} ${epic.title.substring(0, 30).padEnd(30)} ${bar} ${String(percent).padStart(3)}% (${progress.done}/${progress.total})${readyToArchive}`);
|
|
160
166
|
}
|
|
161
167
|
}
|
|
162
168
|
this.log(styles.muted('\nCommands:'));
|
|
@@ -176,7 +176,9 @@ export default class EpicSpec extends PMOCommand {
|
|
|
176
176
|
return;
|
|
177
177
|
}
|
|
178
178
|
if (action === 'align_all') {
|
|
179
|
+
// Update sequentially for clear logging
|
|
179
180
|
for (const t of ticketsWithDifferentSpec) {
|
|
181
|
+
// eslint-disable-next-line no-await-in-loop
|
|
180
182
|
await this.storage.updateTicket(t.id, { specId });
|
|
181
183
|
this.log(styles.muted(` Updated ${t.id} to spec "${specId}"`));
|
|
182
184
|
}
|
|
@@ -194,6 +194,7 @@ export default class EpicTicket extends PMOCommand {
|
|
|
194
194
|
// Process each ticket
|
|
195
195
|
let successCount = 0;
|
|
196
196
|
const linkedTickets = [];
|
|
197
|
+
// Process tickets - may prompt user for spec reconciliation
|
|
197
198
|
for (const ticketId of ticketIds) {
|
|
198
199
|
const ticket = allTickets.find((t) => t.id === ticketId);
|
|
199
200
|
const currentEpicId = getTicketEpicId(ticketId);
|
|
@@ -226,6 +227,7 @@ export default class EpicTicket extends PMOCommand {
|
|
|
226
227
|
if (ticketSpecId && epicSpecId && ticketSpecId !== epicSpecId) {
|
|
227
228
|
// Both have specs but they differ - warn user
|
|
228
229
|
this.log(styles.warning(` ā ļø Spec mismatch: ticket has "${ticketSpecId}", epic has "${epicSpecId}"`));
|
|
230
|
+
// eslint-disable-next-line no-await-in-loop
|
|
229
231
|
const { action } = await inquirer.prompt([{
|
|
230
232
|
type: 'list',
|
|
231
233
|
name: 'action',
|
|
@@ -252,6 +254,7 @@ export default class EpicTicket extends PMOCommand {
|
|
|
252
254
|
}
|
|
253
255
|
else if (!ticketSpecId && epicSpecId) {
|
|
254
256
|
// Ticket has no spec but epic does - offer to inherit
|
|
257
|
+
// eslint-disable-next-line no-await-in-loop
|
|
255
258
|
const { inherit } = await inquirer.prompt([{
|
|
256
259
|
type: 'confirm',
|
|
257
260
|
name: 'inherit',
|