@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.
Files changed (134) hide show
  1. package/dist/commands/action/create.js +3 -3
  2. package/dist/commands/action/index.js +2 -2
  3. package/dist/commands/action/update.js +3 -3
  4. package/dist/commands/agent/auth.js +1 -1
  5. package/dist/commands/agent/cleanup.js +6 -6
  6. package/dist/commands/agent/discover.js +1 -1
  7. package/dist/commands/agent/remove.js +4 -4
  8. package/dist/commands/autocomplete/setup.d.ts +2 -2
  9. package/dist/commands/autocomplete/setup.js +5 -5
  10. package/dist/commands/branch/create.js +31 -30
  11. package/dist/commands/category/create.js +4 -5
  12. package/dist/commands/category/delete.js +2 -3
  13. package/dist/commands/category/rename.js +2 -3
  14. package/dist/commands/claude.d.ts +2 -8
  15. package/dist/commands/claude.js +26 -26
  16. package/dist/commands/commit.d.ts +2 -8
  17. package/dist/commands/commit.js +4 -26
  18. package/dist/commands/config/index.d.ts +2 -10
  19. package/dist/commands/config/index.js +8 -34
  20. package/dist/commands/docker/index.d.ts +2 -2
  21. package/dist/commands/docker/index.js +8 -8
  22. package/dist/commands/epic/activate.js +9 -17
  23. package/dist/commands/epic/archive.js +13 -24
  24. package/dist/commands/epic/create.js +7 -6
  25. package/dist/commands/epic/delete.js +4 -5
  26. package/dist/commands/epic/move.js +28 -47
  27. package/dist/commands/epic/progress.js +10 -14
  28. package/dist/commands/epic/project.js +42 -59
  29. package/dist/commands/epic/reorder.js +25 -30
  30. package/dist/commands/epic/spec.d.ts +1 -0
  31. package/dist/commands/epic/spec.js +39 -40
  32. package/dist/commands/epic/ticket.d.ts +2 -0
  33. package/dist/commands/epic/ticket.js +63 -37
  34. package/dist/commands/feedback/index.d.ts +10 -0
  35. package/dist/commands/feedback/index.js +60 -0
  36. package/dist/commands/feedback/list.d.ts +12 -0
  37. package/dist/commands/feedback/list.js +126 -0
  38. package/dist/commands/feedback/submit.d.ts +16 -0
  39. package/dist/commands/feedback/submit.js +220 -0
  40. package/dist/commands/feedback/view.d.ts +15 -0
  41. package/dist/commands/feedback/view.js +109 -0
  42. package/dist/commands/gh/index.js +4 -0
  43. package/dist/commands/link/index.js +2 -2
  44. package/dist/commands/pmo/init.d.ts +2 -2
  45. package/dist/commands/pmo/init.js +7 -7
  46. package/dist/commands/project/spec.js +6 -6
  47. package/dist/commands/repo/create.d.ts +38 -0
  48. package/dist/commands/repo/create.js +283 -0
  49. package/dist/commands/repo/index.js +7 -0
  50. package/dist/commands/roadmap/add-project.js +9 -22
  51. package/dist/commands/roadmap/create.d.ts +0 -1
  52. package/dist/commands/roadmap/create.js +46 -40
  53. package/dist/commands/roadmap/delete.js +10 -24
  54. package/dist/commands/roadmap/generate.d.ts +1 -0
  55. package/dist/commands/roadmap/generate.js +21 -22
  56. package/dist/commands/roadmap/remove-project.js +14 -34
  57. package/dist/commands/roadmap/reorder.js +19 -26
  58. package/dist/commands/roadmap/update.js +27 -26
  59. package/dist/commands/roadmap/view.js +5 -12
  60. package/dist/commands/session/attach.d.ts +1 -8
  61. package/dist/commands/session/attach.js +93 -59
  62. package/dist/commands/session/health.d.ts +29 -0
  63. package/dist/commands/session/health.js +495 -0
  64. package/dist/commands/session/index.js +4 -0
  65. package/dist/commands/session/list.d.ts +0 -8
  66. package/dist/commands/session/list.js +130 -81
  67. package/dist/commands/spec/create.js +1 -1
  68. package/dist/commands/spec/edit.js +64 -35
  69. package/dist/commands/staff/add.d.ts +2 -2
  70. package/dist/commands/staff/add.js +15 -14
  71. package/dist/commands/staff/index.js +2 -2
  72. package/dist/commands/staff/remove.js +4 -4
  73. package/dist/commands/status/index.js +6 -7
  74. package/dist/commands/support/book.d.ts +10 -0
  75. package/dist/commands/support/book.js +54 -0
  76. package/dist/commands/support/discord.d.ts +10 -0
  77. package/dist/commands/support/discord.js +54 -0
  78. package/dist/commands/support/docs.d.ts +10 -0
  79. package/dist/commands/support/docs.js +54 -0
  80. package/dist/commands/support/index.d.ts +19 -0
  81. package/dist/commands/support/index.js +81 -0
  82. package/dist/commands/support/issues.d.ts +11 -0
  83. package/dist/commands/support/issues.js +77 -0
  84. package/dist/commands/support/logs.d.ts +18 -0
  85. package/dist/commands/support/logs.js +247 -0
  86. package/dist/commands/template/apply.js +10 -11
  87. package/dist/commands/template/create.js +18 -17
  88. package/dist/commands/template/index.d.ts +2 -2
  89. package/dist/commands/template/index.js +6 -6
  90. package/dist/commands/template/save.js +8 -7
  91. package/dist/commands/template/update.js +6 -7
  92. package/dist/commands/terminal/title.d.ts +2 -26
  93. package/dist/commands/terminal/title.js +4 -33
  94. package/dist/commands/theme/index.d.ts +2 -2
  95. package/dist/commands/theme/index.js +19 -18
  96. package/dist/commands/theme/set.d.ts +2 -2
  97. package/dist/commands/theme/set.js +5 -5
  98. package/dist/commands/ticket/create.js +52 -26
  99. package/dist/commands/ticket/delete.js +15 -13
  100. package/dist/commands/ticket/edit.js +59 -20
  101. package/dist/commands/ticket/epic.js +12 -10
  102. package/dist/commands/ticket/move.d.ts +7 -0
  103. package/dist/commands/ticket/move.js +132 -0
  104. package/dist/commands/ticket/project.js +11 -9
  105. package/dist/commands/ticket/reassign.js +23 -19
  106. package/dist/commands/ticket/spec.js +7 -5
  107. package/dist/commands/ticket/update.js +55 -53
  108. package/dist/commands/whoami.js +1 -0
  109. package/dist/commands/work/ready.js +7 -7
  110. package/dist/commands/work/revise.js +13 -11
  111. package/dist/commands/work/spawn.d.ts +1 -0
  112. package/dist/commands/work/spawn.js +225 -64
  113. package/dist/commands/work/start.d.ts +1 -0
  114. package/dist/commands/work/start.js +301 -173
  115. package/dist/hooks/init.js +4 -0
  116. package/dist/lib/execution/runners.js +21 -17
  117. package/dist/lib/execution/session-utils.d.ts +60 -0
  118. package/dist/lib/execution/session-utils.js +162 -0
  119. package/dist/lib/execution/spawner.d.ts +2 -0
  120. package/dist/lib/execution/spawner.js +42 -0
  121. package/dist/lib/flags/resolver.d.ts +2 -2
  122. package/dist/lib/flags/resolver.js +15 -0
  123. package/dist/lib/init/index.js +18 -0
  124. package/dist/lib/multiline-input.d.ts +63 -0
  125. package/dist/lib/multiline-input.js +360 -0
  126. package/dist/lib/pr/index.d.ts +4 -0
  127. package/dist/lib/pr/index.js +32 -14
  128. package/dist/lib/prompt-command.d.ts +3 -0
  129. package/dist/lib/prompt-json.d.ts +77 -6
  130. package/dist/lib/prompt-json.js +46 -0
  131. package/dist/lib/repos/git.d.ts +7 -0
  132. package/dist/lib/repos/git.js +20 -0
  133. package/oclif.manifest.json +2913 -2246
  134. 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, outputPromptAsJson, outputErrorAsJson, createMetadata, buildPromptConfig, } from '../../lib/prompt-json.js';
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
- if (jsonMode) {
75
- const choices = roadmaps.map(r => ({
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 tickets) {
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
- 'completed': 4,
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'] || 6;
152
- const catB = categoryOrder[b.statusCategory || 'backlog'] || 6;
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, outputPromptAsJson, outputErrorAsJson, createMetadata, buildPromptConfig, } from '../../lib/prompt-json.js';
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
- if (jsonMode) {
55
- const choices = roadmaps.map(r => ({
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
- if (jsonMode) {
94
- const choices = projects.map((p, i) => ({
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
- if (jsonMode) {
125
- const confirmChoices = [
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, outputPromptAsJson, outputErrorAsJson, createMetadata, buildPromptConfig, } from '../../lib/prompt-json.js';
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
- if (jsonMode) {
53
- const choices = roadmaps.map(r => ({
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
- if (jsonMode) {
111
- const choices = projects.map((p, i) => ({
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 { name: `Position ${i + 1}: ${label}`, value: i };
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 { newPosition } = await inquirer.prompt([{
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, outputPromptAsJson, outputErrorAsJson, createMetadata, buildPromptConfig, buildFormPromptConfig, } from '../../lib/prompt-json.js';
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
- if (jsonMode) {
57
- const choices = roadmaps.map(r => ({
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 fields = [
87
- { type: 'input', name: 'name', message: 'New name (leave blank to keep current):', default: roadmap.name },
88
- { type: 'input', name: 'description', message: 'New description (leave blank to keep current):', default: roadmap.description || '' },
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, outputPromptAsJson, outputErrorAsJson, createMetadata, buildPromptConfig, } from '../../lib/prompt-json.js';
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
- if (jsonMode) {
44
- const choices = roadmaps.map(r => ({
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
- return sessions; // Not in workspace
139
+ // Not in workspace, but we can still discover tmux sessions
138
140
  }
139
141
  try {
140
- // Get active executions from DB
141
- const activeExecutions = [
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
- if (isContainer && exec.containerId) {
154
- const containerSessions = containerTmuxSessions.get(exec.containerId);
155
- exists = containerSessions?.includes(exec.sessionId) ?? false;
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
- exists = hostTmuxSessions.includes(exec.sessionId);
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
- // Only include sessions that actually exist
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: exec.sessionId,
164
- sessionId: exec.sessionId,
203
+ name: actualSessionId,
204
+ sessionId: actualSessionId,
165
205
  type: isContainer ? 'container' : 'host',
166
- containerId: exec.containerId,
206
+ containerId,
167
207
  ticketId: exec.ticketId,
168
208
  agentName: exec.agentName,
209
+ source: 'db',
169
210
  });
170
211
  }
171
212
  }
172
- }
173
- finally {
174
- db?.close();
175
- }
176
- return sessions;
177
- }
178
- /**
179
- * Get list of host tmux session names
180
- */
181
- getHostTmuxSessionNames() {
182
- try {
183
- execSync('which tmux', { stdio: 'pipe' });
184
- const output = execSync('tmux list-sessions -F "#{session_name}"', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
185
- if (!output)
186
- return [];
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
- catch {
210
- // Container has no tmux sessions
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
- catch {
215
- // Docker not available
248
+ finally {
249
+ db?.close();
216
250
  }
217
- return sessionMap;
251
+ return sessions;
218
252
  }
219
253
  /**
220
254
  * Attach to session in current terminal