@proletariat/cli 0.3.24 → 0.3.26
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/action/create.js +3 -3
- package/dist/commands/action/index.js +2 -2
- package/dist/commands/action/update.js +3 -3
- package/dist/commands/agent/auth.js +1 -1
- package/dist/commands/agent/cleanup.js +6 -6
- package/dist/commands/agent/discover.js +1 -1
- package/dist/commands/agent/remove.js +4 -4
- package/dist/commands/autocomplete/setup.d.ts +2 -2
- package/dist/commands/autocomplete/setup.js +5 -5
- package/dist/commands/branch/create.js +31 -30
- package/dist/commands/category/create.js +4 -5
- package/dist/commands/category/delete.js +2 -3
- package/dist/commands/category/rename.js +2 -3
- package/dist/commands/claude.d.ts +2 -8
- package/dist/commands/claude.js +26 -26
- package/dist/commands/commit.d.ts +2 -8
- package/dist/commands/commit.js +4 -26
- package/dist/commands/config/index.d.ts +2 -10
- package/dist/commands/config/index.js +8 -34
- package/dist/commands/docker/index.d.ts +2 -2
- package/dist/commands/docker/index.js +8 -8
- package/dist/commands/epic/activate.js +9 -17
- package/dist/commands/epic/archive.js +13 -24
- package/dist/commands/epic/create.js +7 -6
- package/dist/commands/epic/delete.js +4 -5
- package/dist/commands/epic/move.js +28 -47
- package/dist/commands/epic/progress.js +10 -14
- package/dist/commands/epic/project.js +42 -59
- package/dist/commands/epic/reorder.js +25 -30
- package/dist/commands/epic/spec.d.ts +1 -0
- package/dist/commands/epic/spec.js +39 -40
- package/dist/commands/epic/ticket.d.ts +2 -0
- package/dist/commands/epic/ticket.js +63 -37
- package/dist/commands/feedback/index.d.ts +10 -0
- package/dist/commands/feedback/index.js +60 -0
- package/dist/commands/feedback/list.d.ts +12 -0
- package/dist/commands/feedback/list.js +126 -0
- package/dist/commands/feedback/submit.d.ts +16 -0
- package/dist/commands/feedback/submit.js +220 -0
- package/dist/commands/feedback/view.d.ts +15 -0
- package/dist/commands/feedback/view.js +109 -0
- package/dist/commands/gh/index.js +4 -0
- package/dist/commands/link/index.js +2 -2
- package/dist/commands/pmo/init.d.ts +2 -2
- package/dist/commands/pmo/init.js +7 -7
- package/dist/commands/project/spec.js +6 -6
- package/dist/commands/repo/create.d.ts +38 -0
- package/dist/commands/repo/create.js +283 -0
- package/dist/commands/repo/index.js +7 -0
- package/dist/commands/roadmap/add-project.js +9 -22
- package/dist/commands/roadmap/create.d.ts +0 -1
- package/dist/commands/roadmap/create.js +46 -40
- package/dist/commands/roadmap/delete.js +10 -24
- package/dist/commands/roadmap/generate.d.ts +1 -0
- package/dist/commands/roadmap/generate.js +21 -22
- package/dist/commands/roadmap/remove-project.js +14 -34
- package/dist/commands/roadmap/reorder.js +19 -26
- package/dist/commands/roadmap/update.js +27 -26
- package/dist/commands/roadmap/view.js +5 -12
- package/dist/commands/session/attach.d.ts +1 -8
- package/dist/commands/session/attach.js +93 -59
- package/dist/commands/session/health.d.ts +29 -0
- package/dist/commands/session/health.js +495 -0
- package/dist/commands/session/index.js +4 -0
- package/dist/commands/session/list.d.ts +0 -8
- package/dist/commands/session/list.js +130 -81
- package/dist/commands/spec/create.js +1 -1
- package/dist/commands/spec/edit.js +64 -35
- package/dist/commands/staff/add.d.ts +2 -2
- package/dist/commands/staff/add.js +15 -14
- package/dist/commands/staff/index.js +2 -2
- package/dist/commands/staff/remove.js +4 -4
- package/dist/commands/status/index.js +6 -7
- package/dist/commands/support/book.d.ts +10 -0
- package/dist/commands/support/book.js +54 -0
- package/dist/commands/support/discord.d.ts +10 -0
- package/dist/commands/support/discord.js +54 -0
- package/dist/commands/support/docs.d.ts +10 -0
- package/dist/commands/support/docs.js +54 -0
- package/dist/commands/support/index.d.ts +19 -0
- package/dist/commands/support/index.js +81 -0
- package/dist/commands/support/issues.d.ts +11 -0
- package/dist/commands/support/issues.js +77 -0
- package/dist/commands/support/logs.d.ts +18 -0
- package/dist/commands/support/logs.js +247 -0
- package/dist/commands/template/apply.js +10 -11
- package/dist/commands/template/create.js +18 -17
- package/dist/commands/template/index.d.ts +2 -2
- package/dist/commands/template/index.js +6 -6
- package/dist/commands/template/save.js +8 -7
- package/dist/commands/template/update.js +6 -7
- package/dist/commands/terminal/title.d.ts +2 -26
- package/dist/commands/terminal/title.js +4 -33
- package/dist/commands/theme/index.d.ts +2 -2
- package/dist/commands/theme/index.js +19 -18
- package/dist/commands/theme/set.d.ts +2 -2
- package/dist/commands/theme/set.js +5 -5
- package/dist/commands/ticket/create.js +52 -26
- package/dist/commands/ticket/delete.js +15 -13
- package/dist/commands/ticket/edit.js +59 -20
- package/dist/commands/ticket/epic.js +12 -10
- package/dist/commands/ticket/move.d.ts +7 -0
- package/dist/commands/ticket/move.js +132 -0
- package/dist/commands/ticket/project.js +11 -9
- package/dist/commands/ticket/reassign.js +23 -19
- package/dist/commands/ticket/spec.js +7 -5
- package/dist/commands/ticket/update.js +55 -53
- package/dist/commands/whoami.js +1 -0
- package/dist/commands/work/ready.js +7 -7
- package/dist/commands/work/revise.js +13 -11
- package/dist/commands/work/spawn.d.ts +1 -0
- package/dist/commands/work/spawn.js +225 -64
- package/dist/commands/work/start.d.ts +1 -0
- package/dist/commands/work/start.js +301 -173
- package/dist/hooks/init.js +4 -0
- package/dist/lib/execution/runners.js +21 -17
- package/dist/lib/execution/session-utils.d.ts +60 -0
- package/dist/lib/execution/session-utils.js +162 -0
- package/dist/lib/execution/spawner.d.ts +2 -0
- package/dist/lib/execution/spawner.js +42 -0
- package/dist/lib/flags/resolver.d.ts +2 -2
- package/dist/lib/flags/resolver.js +15 -0
- package/dist/lib/init/index.js +18 -0
- package/dist/lib/multiline-input.d.ts +63 -0
- package/dist/lib/multiline-input.js +360 -0
- package/dist/lib/pr/index.d.ts +4 -0
- package/dist/lib/pr/index.js +32 -14
- package/dist/lib/prompt-command.d.ts +3 -0
- package/dist/lib/prompt-json.d.ts +77 -6
- package/dist/lib/prompt-json.js +46 -0
- package/dist/lib/repos/git.d.ts +7 -0
- package/dist/lib/repos/git.js +20 -0
- package/oclif.manifest.json +2913 -2246
- package/package.json +1 -1
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
|
-
import inquirer from 'inquirer';
|
|
5
4
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
6
5
|
import { styles } from '../../lib/styles.js';
|
|
7
6
|
import { slugify } from '../../lib/pmo/utils.js';
|
|
8
7
|
import { normalizePriority, PRIORITIES } from '../../lib/pmo/types.js';
|
|
9
|
-
import { shouldOutputJson,
|
|
8
|
+
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
10
9
|
export default class RoadmapGenerate extends PMOCommand {
|
|
11
10
|
static description = 'Generate roadmap markdown file';
|
|
12
11
|
static examples = [
|
|
@@ -30,6 +29,10 @@ export default class RoadmapGenerate extends PMOCommand {
|
|
|
30
29
|
char: 'o',
|
|
31
30
|
description: 'Output directory (default: {pmoPath}/roadmaps)',
|
|
32
31
|
}),
|
|
32
|
+
'exclude-done': Flags.boolean({
|
|
33
|
+
description: 'Exclude completed tickets from roadmap',
|
|
34
|
+
default: false,
|
|
35
|
+
}),
|
|
33
36
|
json: Flags.boolean({
|
|
34
37
|
char: 'm',
|
|
35
38
|
aliases: ['machine'],
|
|
@@ -55,7 +58,7 @@ export default class RoadmapGenerate extends PMOCommand {
|
|
|
55
58
|
}
|
|
56
59
|
for (const roadmap of roadmaps) {
|
|
57
60
|
// eslint-disable-next-line no-await-in-loop -- Sequential generation with user feedback
|
|
58
|
-
await this.generateRoadmap(roadmap.id, outputDir);
|
|
61
|
+
await this.generateRoadmap(roadmap.id, outputDir, flags['exclude-done']);
|
|
59
62
|
}
|
|
60
63
|
this.log(styles.success(`\nGenerated ${roadmaps.length} roadmap(s) to ${outputDir}`));
|
|
61
64
|
return;
|
|
@@ -71,28 +74,22 @@ export default class RoadmapGenerate extends PMOCommand {
|
|
|
71
74
|
}
|
|
72
75
|
this.error('No roadmaps found. Create one with: prlt roadmap create');
|
|
73
76
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
name: `${r.name}${r.isDefault ? ' (default)' : ''}`,
|
|
77
|
-
value: r.id,
|
|
78
|
-
}));
|
|
79
|
-
outputPromptAsJson(buildPromptConfig('list', 'id', 'Select roadmap to generate:', choices), createMetadata('roadmap generate', flags));
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
const { selected } = await inquirer.prompt([{
|
|
77
|
+
const jsonModeConfig = jsonMode ? { flags, commandName: 'roadmap generate' } : null;
|
|
78
|
+
const { selected } = await this.prompt([{
|
|
83
79
|
type: 'list',
|
|
84
80
|
name: 'selected',
|
|
85
81
|
message: 'Select roadmap to generate:',
|
|
86
82
|
choices: roadmaps.map(r => ({
|
|
87
83
|
name: `${r.name}${r.isDefault ? ' (default)' : ''}`,
|
|
88
84
|
value: r.id,
|
|
85
|
+
command: `prlt roadmap generate "${r.id}" --json`,
|
|
89
86
|
})),
|
|
90
|
-
}]);
|
|
87
|
+
}], jsonModeConfig);
|
|
91
88
|
roadmapId = selected;
|
|
92
89
|
}
|
|
93
|
-
await this.generateRoadmap(roadmapId, outputDir);
|
|
90
|
+
await this.generateRoadmap(roadmapId, outputDir, flags['exclude-done']);
|
|
94
91
|
}
|
|
95
|
-
async generateRoadmap(roadmapId, outputDir) {
|
|
92
|
+
async generateRoadmap(roadmapId, outputDir, excludeDone = false) {
|
|
96
93
|
const roadmap = await this.storage.getRoadmap(roadmapId);
|
|
97
94
|
if (!roadmap) {
|
|
98
95
|
this.error(`Roadmap not found: ${roadmapId}`);
|
|
@@ -124,13 +121,17 @@ export default class RoadmapGenerate extends PMOCommand {
|
|
|
124
121
|
}
|
|
125
122
|
lines.push(`## ${i + 1}. ${displayName}`);
|
|
126
123
|
lines.push('');
|
|
124
|
+
// Filter tickets based on excludeDone flag
|
|
125
|
+
const filteredTickets = excludeDone
|
|
126
|
+
? tickets.filter(t => t.statusCategory !== 'completed')
|
|
127
|
+
: tickets;
|
|
127
128
|
// Group tickets by priority
|
|
128
129
|
const ticketsByPriority = new Map();
|
|
129
130
|
for (const priority of PRIORITIES) {
|
|
130
131
|
ticketsByPriority.set(priority, []);
|
|
131
132
|
}
|
|
132
133
|
ticketsByPriority.set('unset', []);
|
|
133
|
-
for (const ticket of
|
|
134
|
+
for (const ticket of filteredTickets) {
|
|
134
135
|
const normalizedPriority = normalizePriority(ticket.priority) || 'unset';
|
|
135
136
|
const group = ticketsByPriority.get(normalizedPriority) || ticketsByPriority.get('unset');
|
|
136
137
|
group.push(ticket);
|
|
@@ -140,16 +141,15 @@ export default class RoadmapGenerate extends PMOCommand {
|
|
|
140
141
|
'started': 1,
|
|
141
142
|
'unstarted': 2,
|
|
142
143
|
'backlog': 3,
|
|
143
|
-
'
|
|
144
|
-
'canceled': 5,
|
|
145
|
-
'triage': 6,
|
|
144
|
+
'triage': 4,
|
|
146
145
|
};
|
|
146
|
+
// Output active tickets by priority
|
|
147
147
|
for (const [priority, priorityTickets] of ticketsByPriority) {
|
|
148
148
|
if (priorityTickets.length === 0)
|
|
149
149
|
continue;
|
|
150
150
|
priorityTickets.sort((a, b) => {
|
|
151
|
-
const catA = categoryOrder[a.statusCategory || 'backlog'] ||
|
|
152
|
-
const catB = categoryOrder[b.statusCategory || 'backlog'] ||
|
|
151
|
+
const catA = categoryOrder[a.statusCategory || 'backlog'] || 5;
|
|
152
|
+
const catB = categoryOrder[b.statusCategory || 'backlog'] || 5;
|
|
153
153
|
if (catA !== catB)
|
|
154
154
|
return catA - catB;
|
|
155
155
|
return a.id.localeCompare(b.id);
|
|
@@ -158,7 +158,6 @@ export default class RoadmapGenerate extends PMOCommand {
|
|
|
158
158
|
const priorityLabel = priority === 'unset' ? 'Unset Priority' : `${priority} - ${this.getPriorityLabel(priority)}`;
|
|
159
159
|
lines.push(`### ${priorityLabel} (${priorityTickets.length} ticket${priorityTickets.length > 1 ? 's' : ''})`);
|
|
160
160
|
lines.push('');
|
|
161
|
-
// Table header
|
|
162
161
|
lines.push('| Ticket | Title | Status | Category | Description |');
|
|
163
162
|
lines.push('| ------ | ----- | ------ | -------- | ----------- |');
|
|
164
163
|
for (const ticket of priorityTickets) {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
|
-
import inquirer from 'inquirer';
|
|
3
2
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
4
3
|
import { styles } from '../../lib/styles.js';
|
|
5
|
-
import { shouldOutputJson,
|
|
4
|
+
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
6
5
|
export default class RoadmapRemoveProject extends PMOCommand {
|
|
7
6
|
static description = 'Remove a project from a roadmap';
|
|
8
7
|
static examples = [
|
|
@@ -51,23 +50,17 @@ export default class RoadmapRemoveProject extends PMOCommand {
|
|
|
51
50
|
}
|
|
52
51
|
this.error('No roadmaps found');
|
|
53
52
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
name: `${r.name}${r.isDefault ? ' (default)' : ''}`,
|
|
57
|
-
value: r.id,
|
|
58
|
-
}));
|
|
59
|
-
outputPromptAsJson(buildPromptConfig('list', 'roadmap', 'Select roadmap:', choices), createMetadata('roadmap remove-project', flags));
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
const { selected } = await inquirer.prompt([{
|
|
53
|
+
const jsonModeConfig = jsonMode ? { flags, commandName: 'roadmap remove-project' } : null;
|
|
54
|
+
const { selected } = await this.prompt([{
|
|
63
55
|
type: 'list',
|
|
64
56
|
name: 'selected',
|
|
65
57
|
message: 'Select roadmap:',
|
|
66
58
|
choices: roadmaps.map(r => ({
|
|
67
59
|
name: `${r.name}${r.isDefault ? ' (default)' : ''}`,
|
|
68
60
|
value: r.id,
|
|
61
|
+
command: `prlt roadmap remove-project "${r.id}" --json`,
|
|
69
62
|
})),
|
|
70
|
-
}]);
|
|
63
|
+
}], jsonModeConfig);
|
|
71
64
|
roadmapId = selected;
|
|
72
65
|
}
|
|
73
66
|
const roadmap = await this.storage.getRoadmap(roadmapId);
|
|
@@ -90,23 +83,17 @@ export default class RoadmapRemoveProject extends PMOCommand {
|
|
|
90
83
|
// Select project
|
|
91
84
|
let projectId = args.project;
|
|
92
85
|
if (!projectId) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
name: `${i + 1}. ${p.name}`,
|
|
96
|
-
value: p.id,
|
|
97
|
-
}));
|
|
98
|
-
outputPromptAsJson(buildPromptConfig('list', 'project', 'Select project to remove:', choices), createMetadata('roadmap remove-project', flags));
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
const { selected } = await inquirer.prompt([{
|
|
86
|
+
const projectJsonModeConfig = jsonMode ? { flags, commandName: 'roadmap remove-project' } : null;
|
|
87
|
+
const { selected } = await this.prompt([{
|
|
102
88
|
type: 'list',
|
|
103
89
|
name: 'selected',
|
|
104
90
|
message: 'Select project to remove:',
|
|
105
91
|
choices: projects.map((p, i) => ({
|
|
106
92
|
name: `${i + 1}. ${p.name}`,
|
|
107
93
|
value: p.id,
|
|
94
|
+
command: `prlt roadmap remove-project "${roadmapId}" "${p.id}" --json`,
|
|
108
95
|
})),
|
|
109
|
-
}]);
|
|
96
|
+
}], projectJsonModeConfig);
|
|
110
97
|
projectId = selected;
|
|
111
98
|
}
|
|
112
99
|
// Verify project is in roadmap
|
|
@@ -121,23 +108,16 @@ export default class RoadmapRemoveProject extends PMOCommand {
|
|
|
121
108
|
// Confirm removal
|
|
122
109
|
if (!flags.force) {
|
|
123
110
|
const message = `Remove "${project.name}" from "${roadmap.name}"?`;
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
{ name: 'No, cancel', value: 'false' },
|
|
127
|
-
{ name: 'Yes, remove', value: 'true' },
|
|
128
|
-
];
|
|
129
|
-
outputPromptAsJson(buildPromptConfig('list', 'confirmed', message, confirmChoices), createMetadata('roadmap remove-project', flags));
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
const { confirm } = await inquirer.prompt([{
|
|
111
|
+
const confirmJsonModeConfig = jsonMode ? { flags, commandName: 'roadmap remove-project' } : null;
|
|
112
|
+
const { confirm } = await this.prompt([{
|
|
133
113
|
type: 'list',
|
|
134
114
|
name: 'confirm',
|
|
135
115
|
message,
|
|
136
116
|
choices: [
|
|
137
|
-
{ name: 'No, cancel', value: false },
|
|
138
|
-
{ name: 'Yes, remove', value: true },
|
|
117
|
+
{ name: 'No, cancel', value: false, command: '' },
|
|
118
|
+
{ name: 'Yes, remove', value: true, command: `prlt roadmap remove-project "${roadmapId}" "${projectId}" --force --json` },
|
|
139
119
|
],
|
|
140
|
-
}]);
|
|
120
|
+
}], confirmJsonModeConfig);
|
|
141
121
|
if (!confirm) {
|
|
142
122
|
this.log(styles.muted('Cancelled.'));
|
|
143
123
|
return;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
|
-
import inquirer from 'inquirer';
|
|
3
2
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
4
3
|
import { styles } from '../../lib/styles.js';
|
|
5
|
-
import { shouldOutputJson,
|
|
4
|
+
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
6
5
|
export default class RoadmapReorder extends PMOCommand {
|
|
7
6
|
static description = 'Reorder projects in a roadmap';
|
|
8
7
|
static examples = [
|
|
@@ -49,23 +48,17 @@ export default class RoadmapReorder extends PMOCommand {
|
|
|
49
48
|
}
|
|
50
49
|
this.error('No roadmaps found');
|
|
51
50
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
name: `${r.name}${r.isDefault ? ' (default)' : ''}`,
|
|
55
|
-
value: r.id,
|
|
56
|
-
}));
|
|
57
|
-
outputPromptAsJson(buildPromptConfig('list', 'roadmap', 'Select roadmap:', choices), createMetadata('roadmap reorder', flags));
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
const { selected } = await inquirer.prompt([{
|
|
51
|
+
const jsonModeConfig = jsonMode ? { flags, commandName: 'roadmap reorder' } : null;
|
|
52
|
+
const { selected } = await this.prompt([{
|
|
61
53
|
type: 'list',
|
|
62
54
|
name: 'selected',
|
|
63
55
|
message: 'Select roadmap:',
|
|
64
56
|
choices: roadmaps.map(r => ({
|
|
65
57
|
name: `${r.name}${r.isDefault ? ' (default)' : ''}`,
|
|
66
58
|
value: r.id,
|
|
59
|
+
command: `prlt roadmap reorder "${r.id}" --json`,
|
|
67
60
|
})),
|
|
68
|
-
}]);
|
|
61
|
+
}], jsonModeConfig);
|
|
69
62
|
roadmapId = selected;
|
|
70
63
|
}
|
|
71
64
|
const roadmap = await this.storage.getRoadmap(roadmapId);
|
|
@@ -107,46 +100,46 @@ export default class RoadmapReorder extends PMOCommand {
|
|
|
107
100
|
}
|
|
108
101
|
this.log('');
|
|
109
102
|
// Prompt to select project to move
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
name: `${i + 1}. ${p.name}`,
|
|
113
|
-
value: p.id,
|
|
114
|
-
}));
|
|
115
|
-
outputPromptAsJson(buildPromptConfig('list', 'project', 'Select project to move:', choices), createMetadata('roadmap reorder', flags));
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
const { projectToMove } = await inquirer.prompt([{
|
|
103
|
+
const projectJsonModeConfig = jsonMode ? { flags, commandName: 'roadmap reorder' } : null;
|
|
104
|
+
const { projectToMove } = await this.prompt([{
|
|
119
105
|
type: 'list',
|
|
120
106
|
name: 'projectToMove',
|
|
121
107
|
message: 'Select project to move:',
|
|
122
108
|
choices: projects.map((p, i) => ({
|
|
123
109
|
name: `${i + 1}. ${p.name}`,
|
|
124
110
|
value: p.id,
|
|
111
|
+
command: `prlt roadmap reorder "${roadmapId}" --project "${p.id}" --json`,
|
|
125
112
|
})),
|
|
126
|
-
}]);
|
|
113
|
+
}], projectJsonModeConfig);
|
|
127
114
|
const project = projects.find(p => p.id === projectToMove);
|
|
128
115
|
const currentPosition = projects.findIndex(p => p.id === projectToMove);
|
|
129
116
|
// Generate position choices
|
|
130
117
|
const positionChoices = projects.map((p, i) => {
|
|
131
118
|
if (i === currentPosition) {
|
|
132
|
-
return { name: `${i + 1}. ${p.name} (current position)`, value: i, disabled: true };
|
|
119
|
+
return { name: `${i + 1}. ${p.name} (current position)`, value: i, disabled: true, command: '' };
|
|
133
120
|
}
|
|
134
121
|
const label = i < currentPosition ? `Move before ${i + 1}. ${p.name}` : `Move after ${i}. ${projects[i - 1]?.name || ''}`;
|
|
135
|
-
return {
|
|
122
|
+
return {
|
|
123
|
+
name: `Position ${i + 1}: ${label}`,
|
|
124
|
+
value: i,
|
|
125
|
+
command: `prlt roadmap reorder "${roadmapId}" --project "${projectToMove}" --position ${i} --json`,
|
|
126
|
+
};
|
|
136
127
|
}).filter(c => !c.disabled);
|
|
137
128
|
// Add "move to end" option if not already at the end
|
|
138
129
|
if (currentPosition !== projects.length - 1) {
|
|
139
130
|
positionChoices.push({
|
|
140
131
|
name: `Position ${projects.length}: Move to end`,
|
|
141
132
|
value: projects.length - 1,
|
|
133
|
+
command: `prlt roadmap reorder "${roadmapId}" --project "${projectToMove}" --position ${projects.length - 1} --json`,
|
|
142
134
|
});
|
|
143
135
|
}
|
|
144
|
-
const {
|
|
136
|
+
const positionJsonModeConfig = jsonMode ? { flags, commandName: 'roadmap reorder' } : null;
|
|
137
|
+
const { newPosition } = await this.prompt([{
|
|
145
138
|
type: 'list',
|
|
146
139
|
name: 'newPosition',
|
|
147
140
|
message: `Move "${project.name}" to which position?`,
|
|
148
141
|
choices: positionChoices,
|
|
149
|
-
}]);
|
|
142
|
+
}], positionJsonModeConfig);
|
|
150
143
|
await this.storage.reorderRoadmapProject(roadmapId, projectToMove, newPosition);
|
|
151
144
|
// Show new order
|
|
152
145
|
projects = await this.storage.listRoadmapProjects(roadmapId);
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
|
-
import inquirer from 'inquirer';
|
|
3
2
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
4
3
|
import { styles } from '../../lib/styles.js';
|
|
5
|
-
import { shouldOutputJson,
|
|
4
|
+
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
6
5
|
export default class RoadmapUpdate extends PMOCommand {
|
|
7
6
|
static description = 'Update a roadmap';
|
|
8
7
|
static examples = [
|
|
@@ -53,23 +52,17 @@ export default class RoadmapUpdate extends PMOCommand {
|
|
|
53
52
|
}
|
|
54
53
|
this.error('No roadmaps found. Create one with: prlt roadmap create');
|
|
55
54
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
name: `${r.name}${r.isDefault ? ' (default)' : ''}`,
|
|
59
|
-
value: r.id,
|
|
60
|
-
}));
|
|
61
|
-
outputPromptAsJson(buildPromptConfig('list', 'id', 'Select roadmap to update:', choices), createMetadata('roadmap update', flags));
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
const { selected } = await inquirer.prompt([{
|
|
55
|
+
const jsonModeConfig = jsonMode ? { flags, commandName: 'roadmap update' } : null;
|
|
56
|
+
const { selected } = await this.prompt([{
|
|
65
57
|
type: 'list',
|
|
66
58
|
name: 'selected',
|
|
67
59
|
message: 'Select roadmap to update:',
|
|
68
60
|
choices: roadmaps.map(r => ({
|
|
69
61
|
name: `${r.name}${r.isDefault ? ' (default)' : ''}`,
|
|
70
62
|
value: r.id,
|
|
63
|
+
command: `prlt roadmap update "${r.id}" --json`,
|
|
71
64
|
})),
|
|
72
|
-
}]);
|
|
65
|
+
}], jsonModeConfig);
|
|
73
66
|
roadmapId = selected;
|
|
74
67
|
}
|
|
75
68
|
const roadmap = await this.storage.getRoadmap(roadmapId);
|
|
@@ -83,25 +76,33 @@ export default class RoadmapUpdate extends PMOCommand {
|
|
|
83
76
|
// If no flags provided, prompt for updates
|
|
84
77
|
const hasUpdateFlags = flags.name !== undefined || flags.description !== undefined || flags.default !== undefined;
|
|
85
78
|
if (!hasUpdateFlags) {
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
79
|
+
const formJsonModeConfig = jsonMode ? { flags, commandName: 'roadmap update' } : null;
|
|
80
|
+
// Prompt for name - in JSON mode, outputs prompt and exits; agent re-runs with --name flag
|
|
81
|
+
const { name } = await this.prompt([{
|
|
82
|
+
type: 'input',
|
|
83
|
+
name: 'name',
|
|
84
|
+
message: 'New name (leave blank to keep current):',
|
|
85
|
+
default: roadmap.name,
|
|
86
|
+
}], formJsonModeConfig);
|
|
87
|
+
// Prompt for description
|
|
88
|
+
const { description } = await this.prompt([{
|
|
89
|
+
type: 'input',
|
|
90
|
+
name: 'description',
|
|
91
|
+
message: 'New description (leave blank to keep current):',
|
|
92
|
+
default: roadmap.description || '',
|
|
93
|
+
}], formJsonModeConfig);
|
|
94
|
+
// Prompt for isDefault
|
|
95
|
+
const { isDefault } = await this.prompt([{
|
|
90
96
|
type: 'list',
|
|
91
97
|
name: 'isDefault',
|
|
92
98
|
message: 'Set as default roadmap?',
|
|
93
99
|
choices: [
|
|
94
|
-
{ name: 'No change', value: 'no-change' },
|
|
95
|
-
{ name: 'Yes', value: 'yes' },
|
|
96
|
-
{ name: 'No', value: 'no' },
|
|
100
|
+
{ name: 'No change', value: 'no-change', command: '' },
|
|
101
|
+
{ name: 'Yes', value: 'yes', command: `prlt roadmap update "${roadmapId}" --default --json` },
|
|
102
|
+
{ name: 'No', value: 'no', command: `prlt roadmap update "${roadmapId}" --no-default --json` },
|
|
97
103
|
],
|
|
98
|
-
},
|
|
99
|
-
|
|
100
|
-
if (jsonMode) {
|
|
101
|
-
outputPromptAsJson(buildFormPromptConfig(fields), createMetadata('roadmap update', flags));
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
const answers = await inquirer.prompt(fields);
|
|
104
|
+
}], formJsonModeConfig);
|
|
105
|
+
const answers = { name, description, isDefault };
|
|
105
106
|
const changes = {};
|
|
106
107
|
if (answers.name && answers.name !== roadmap.name) {
|
|
107
108
|
changes.name = answers.name;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
|
-
import inquirer from 'inquirer';
|
|
3
2
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
4
3
|
import { styles } from '../../lib/styles.js';
|
|
5
|
-
import { shouldOutputJson,
|
|
4
|
+
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
6
5
|
export default class RoadmapView extends PMOCommand {
|
|
7
6
|
static description = 'View roadmap details and its projects';
|
|
8
7
|
static examples = [
|
|
@@ -40,23 +39,17 @@ export default class RoadmapView extends PMOCommand {
|
|
|
40
39
|
}
|
|
41
40
|
this.error('No roadmaps found. Create one with: prlt roadmap create');
|
|
42
41
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
name: `${r.name}${r.isDefault ? ' (default)' : ''}`,
|
|
46
|
-
value: r.id,
|
|
47
|
-
}));
|
|
48
|
-
outputPromptAsJson(buildPromptConfig('list', 'id', 'Select roadmap to view:', choices), createMetadata('roadmap view', flags));
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
const { selected } = await inquirer.prompt([{
|
|
42
|
+
const jsonModeConfig = jsonMode ? { flags, commandName: 'roadmap view' } : null;
|
|
43
|
+
const { selected } = await this.prompt([{
|
|
52
44
|
type: 'list',
|
|
53
45
|
name: 'selected',
|
|
54
46
|
message: 'Select roadmap to view:',
|
|
55
47
|
choices: roadmaps.map(r => ({
|
|
56
48
|
name: `${r.name}${r.isDefault ? ' (default)' : ''}`,
|
|
57
49
|
value: r.id,
|
|
50
|
+
command: `prlt roadmap view "${r.id}" --json`,
|
|
58
51
|
})),
|
|
59
|
-
}]);
|
|
52
|
+
}], jsonModeConfig);
|
|
60
53
|
roadmapId = selected;
|
|
61
54
|
}
|
|
62
55
|
const roadmap = await this.storage.getRoadmap(roadmapId);
|
|
@@ -19,16 +19,9 @@ export default class SessionAttach extends PMOCommand {
|
|
|
19
19
|
/**
|
|
20
20
|
* Get verified sessions from DB that have actual tmux processes
|
|
21
21
|
* DB-driven approach: Start with executions, verify tmux sessions exist
|
|
22
|
+
* Also discovers orphan sessions matching prlt naming pattern but not in DB
|
|
22
23
|
*/
|
|
23
24
|
private getVerifiedSessions;
|
|
24
|
-
/**
|
|
25
|
-
* Get list of host tmux session names
|
|
26
|
-
*/
|
|
27
|
-
private getHostTmuxSessionNames;
|
|
28
|
-
/**
|
|
29
|
-
* Get map of containerId -> tmux session names
|
|
30
|
-
*/
|
|
31
|
-
private getContainerTmuxSessionMap;
|
|
32
25
|
/**
|
|
33
26
|
* Attach to session in current terminal
|
|
34
27
|
*/
|
|
@@ -7,6 +7,7 @@ import Database from 'better-sqlite3';
|
|
|
7
7
|
import { styles } from '../../lib/styles.js';
|
|
8
8
|
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
9
9
|
import { ExecutionStorage } from '../../lib/execution/index.js';
|
|
10
|
+
import { parseSessionName, getHostTmuxSessionNames, getContainerTmuxSessionMap, flattenContainerSessions, findSessionForExecution, } from '../../lib/execution/session-utils.js';
|
|
10
11
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
11
12
|
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
12
13
|
export default class SessionAttach extends PMOCommand {
|
|
@@ -122,6 +123,7 @@ export default class SessionAttach extends PMOCommand {
|
|
|
122
123
|
/**
|
|
123
124
|
* Get verified sessions from DB that have actual tmux processes
|
|
124
125
|
* DB-driven approach: Start with executions, verify tmux sessions exist
|
|
126
|
+
* Also discovers orphan sessions matching prlt naming pattern but not in DB
|
|
125
127
|
*/
|
|
126
128
|
getVerifiedSessions() {
|
|
127
129
|
const sessions = [];
|
|
@@ -134,87 +136,119 @@ export default class SessionAttach extends PMOCommand {
|
|
|
134
136
|
executionStorage = new ExecutionStorage(db);
|
|
135
137
|
}
|
|
136
138
|
catch {
|
|
137
|
-
|
|
139
|
+
// Not in workspace, but we can still discover tmux sessions
|
|
138
140
|
}
|
|
139
141
|
try {
|
|
140
|
-
// Get
|
|
141
|
-
const
|
|
142
|
+
// Get actual tmux sessions for verification
|
|
143
|
+
const hostTmuxSessions = getHostTmuxSessionNames();
|
|
144
|
+
const containerTmuxSessions = getContainerTmuxSessionMap();
|
|
145
|
+
// Flatten all container sessions for orphan detection
|
|
146
|
+
const allContainerSessions = flattenContainerSessions(containerTmuxSessions);
|
|
147
|
+
// Track which tmux sessions we've matched to DB records
|
|
148
|
+
const matchedHostSessions = new Set();
|
|
149
|
+
const matchedContainerSessions = new Set();
|
|
150
|
+
// Get active executions from DB (if available)
|
|
151
|
+
const activeExecutions = executionStorage ? [
|
|
142
152
|
...(executionStorage.listExecutions({ status: 'running' }) || []),
|
|
143
153
|
...(executionStorage.listExecutions({ status: 'starting' }) || []),
|
|
144
|
-
];
|
|
145
|
-
// Get actual tmux sessions for verification
|
|
146
|
-
const hostTmuxSessions = this.getHostTmuxSessionNames();
|
|
147
|
-
const containerTmuxSessions = this.getContainerTmuxSessionMap();
|
|
154
|
+
] : [];
|
|
148
155
|
for (const exec of activeExecutions) {
|
|
149
|
-
if (!exec.sessionId)
|
|
150
|
-
continue;
|
|
151
156
|
const isContainer = exec.environment === 'devcontainer';
|
|
152
157
|
let exists = false;
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
158
|
+
let containerId;
|
|
159
|
+
let actualSessionId = exec.sessionId;
|
|
160
|
+
// If sessionId is NULL, try to find session by naming convention
|
|
161
|
+
if (!exec.sessionId) {
|
|
162
|
+
if (isContainer && exec.containerId) {
|
|
163
|
+
const containerSessions = containerTmuxSessions.get(exec.containerId) || [];
|
|
164
|
+
const match = findSessionForExecution(exec.ticketId, exec.agentName, containerSessions);
|
|
165
|
+
if (match) {
|
|
166
|
+
actualSessionId = match;
|
|
167
|
+
exists = true;
|
|
168
|
+
containerId = exec.containerId;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
const match = findSessionForExecution(exec.ticketId, exec.agentName, hostTmuxSessions);
|
|
173
|
+
if (match) {
|
|
174
|
+
actualSessionId = match;
|
|
175
|
+
exists = true;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// If still no match, skip this execution
|
|
179
|
+
if (!actualSessionId) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
156
182
|
}
|
|
157
183
|
else {
|
|
158
|
-
|
|
184
|
+
// sessionId is set, verify it exists
|
|
185
|
+
if (isContainer && exec.containerId) {
|
|
186
|
+
const containerSessions = containerTmuxSessions.get(exec.containerId);
|
|
187
|
+
exists = containerSessions?.includes(exec.sessionId) ?? false;
|
|
188
|
+
containerId = exec.containerId;
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
exists = hostTmuxSessions.includes(exec.sessionId);
|
|
192
|
+
}
|
|
159
193
|
}
|
|
160
|
-
//
|
|
161
|
-
if (exists) {
|
|
194
|
+
// Track matched sessions
|
|
195
|
+
if (exists && actualSessionId) {
|
|
196
|
+
if (isContainer && containerId) {
|
|
197
|
+
matchedContainerSessions.add(`${containerId}:${actualSessionId}`);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
matchedHostSessions.add(actualSessionId);
|
|
201
|
+
}
|
|
162
202
|
sessions.push({
|
|
163
|
-
name:
|
|
164
|
-
sessionId:
|
|
203
|
+
name: actualSessionId,
|
|
204
|
+
sessionId: actualSessionId,
|
|
165
205
|
type: isContainer ? 'container' : 'host',
|
|
166
|
-
containerId
|
|
206
|
+
containerId,
|
|
167
207
|
ticketId: exec.ticketId,
|
|
168
208
|
agentName: exec.agentName,
|
|
209
|
+
source: 'db',
|
|
169
210
|
});
|
|
170
211
|
}
|
|
171
212
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
return output.split('\n');
|
|
188
|
-
}
|
|
189
|
-
catch {
|
|
190
|
-
return [];
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* Get map of containerId -> tmux session names
|
|
195
|
-
*/
|
|
196
|
-
getContainerTmuxSessionMap() {
|
|
197
|
-
const sessionMap = new Map();
|
|
198
|
-
try {
|
|
199
|
-
const containersOutput = execSync('docker ps --filter "label=devcontainer.local_folder" --format "{{.ID}}"', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
200
|
-
if (!containersOutput)
|
|
201
|
-
return sessionMap;
|
|
202
|
-
for (const containerId of containersOutput.split('\n')) {
|
|
203
|
-
try {
|
|
204
|
-
const tmuxOutput = execSync(`docker exec ${containerId} tmux list-sessions -F "#{session_name}" 2>/dev/null`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
205
|
-
if (tmuxOutput) {
|
|
206
|
-
sessionMap.set(containerId, tmuxOutput.split('\n'));
|
|
207
|
-
}
|
|
213
|
+
// Discover orphan sessions: tmux sessions matching prlt pattern but not in DB
|
|
214
|
+
// Host sessions
|
|
215
|
+
for (const sessionName of hostTmuxSessions) {
|
|
216
|
+
if (matchedHostSessions.has(sessionName))
|
|
217
|
+
continue;
|
|
218
|
+
const parsed = parseSessionName(sessionName);
|
|
219
|
+
if (parsed) {
|
|
220
|
+
sessions.push({
|
|
221
|
+
name: sessionName,
|
|
222
|
+
sessionId: sessionName,
|
|
223
|
+
type: 'host',
|
|
224
|
+
ticketId: parsed.ticketId,
|
|
225
|
+
agentName: parsed.agentName,
|
|
226
|
+
source: 'discovered',
|
|
227
|
+
});
|
|
208
228
|
}
|
|
209
|
-
|
|
210
|
-
|
|
229
|
+
}
|
|
230
|
+
// Container sessions
|
|
231
|
+
for (const { sessionName, containerId } of allContainerSessions) {
|
|
232
|
+
if (matchedContainerSessions.has(`${containerId}:${sessionName}`))
|
|
233
|
+
continue;
|
|
234
|
+
const parsed = parseSessionName(sessionName);
|
|
235
|
+
if (parsed) {
|
|
236
|
+
sessions.push({
|
|
237
|
+
name: sessionName,
|
|
238
|
+
sessionId: sessionName,
|
|
239
|
+
type: 'container',
|
|
240
|
+
containerId,
|
|
241
|
+
ticketId: parsed.ticketId,
|
|
242
|
+
agentName: parsed.agentName,
|
|
243
|
+
source: 'discovered',
|
|
244
|
+
});
|
|
211
245
|
}
|
|
212
246
|
}
|
|
213
247
|
}
|
|
214
|
-
|
|
215
|
-
|
|
248
|
+
finally {
|
|
249
|
+
db?.close();
|
|
216
250
|
}
|
|
217
|
-
return
|
|
251
|
+
return sessions;
|
|
218
252
|
}
|
|
219
253
|
/**
|
|
220
254
|
* Attach to session in current terminal
|