@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
package/dist/hooks/init.js
CHANGED
|
@@ -13,6 +13,10 @@ const hook = async function ({ id, config }) {
|
|
|
13
13
|
if (id === 'init') {
|
|
14
14
|
return;
|
|
15
15
|
}
|
|
16
|
+
// Skip when --help flag is present - help should always be available
|
|
17
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
16
20
|
// Skip for help-related commands/flags
|
|
17
21
|
// When user runs just `prlt` with no args, id is undefined
|
|
18
22
|
if (!id || id === 'help') {
|
|
@@ -262,6 +262,8 @@ function buildPrompt(context) {
|
|
|
262
262
|
prompt += `When you have completed the task, provide a summary of what you did.`;
|
|
263
263
|
}
|
|
264
264
|
}
|
|
265
|
+
// Universal stop instruction - prevents Claude Code from making additional API calls after task completion
|
|
266
|
+
prompt += `\n\n---\n\n**STOP:** After providing your final summary, your task is complete. Do not take any further actions, do not verify your work again, and do not continue the conversation. Simply output your summary and stop.`;
|
|
265
267
|
return prompt;
|
|
266
268
|
}
|
|
267
269
|
// =============================================================================
|
|
@@ -1479,9 +1481,11 @@ exec bash
|
|
|
1479
1481
|
error: `Failed to write script to container: ${error instanceof Error ? error.message : error}`,
|
|
1480
1482
|
};
|
|
1481
1483
|
}
|
|
1482
|
-
// Step 2: Create tmux session
|
|
1483
|
-
//
|
|
1484
|
-
|
|
1484
|
+
// Step 2: Create tmux session running the script directly
|
|
1485
|
+
// Pass the script as the session command (like host runner does) instead of using send-keys.
|
|
1486
|
+
// The send-keys approach had a race condition where keys could be lost if bash hadn't
|
|
1487
|
+
// fully initialized, causing background mode to create empty sessions without running claude.
|
|
1488
|
+
const createSessionCmd = `tmux new-session -d -s "${sessionName}" -n "${sessionName}" "bash ${scriptPath}"${mouseOption} \\; set-option -g set-titles on \\; set-option -g set-titles-string "#{window_name}"`;
|
|
1485
1489
|
try {
|
|
1486
1490
|
execSync(`docker exec ${actualContainerId} bash -c '${createSessionCmd}'`, { stdio: 'pipe' });
|
|
1487
1491
|
}
|
|
@@ -1491,28 +1495,28 @@ exec bash
|
|
|
1491
1495
|
error: `Failed to create tmux session inside container: ${error instanceof Error ? error.message : error}`,
|
|
1492
1496
|
};
|
|
1493
1497
|
}
|
|
1494
|
-
// Step 3:
|
|
1495
|
-
|
|
1496
|
-
try {
|
|
1497
|
-
execSync(`docker exec ${actualContainerId} bash -c '${sendKeysCmd}'`, { stdio: 'pipe' });
|
|
1498
|
-
}
|
|
1499
|
-
catch (error) {
|
|
1500
|
-
return {
|
|
1501
|
-
success: false,
|
|
1502
|
-
error: `Failed to start script in tmux session: ${error instanceof Error ? error.message : error}`,
|
|
1503
|
-
};
|
|
1504
|
-
}
|
|
1505
|
-
// Step 2: Open iTerm tab that attaches directly to container's tmux
|
|
1506
|
-
// Skip this step for background mode - just return success after tmux session is created
|
|
1498
|
+
// Step 3: Handle display mode
|
|
1499
|
+
// For background mode, return success after tmux session is created
|
|
1507
1500
|
// User can reattach later with `prlt session attach`
|
|
1508
1501
|
if (displayMode === 'background') {
|
|
1502
|
+
// Verify the tmux session was actually created (brief delay to let tmux start)
|
|
1503
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
1504
|
+
try {
|
|
1505
|
+
execSync(`docker exec ${actualContainerId} tmux has-session -t "${sessionName}" 2>&1`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
1506
|
+
}
|
|
1507
|
+
catch (err) {
|
|
1508
|
+
return {
|
|
1509
|
+
success: false,
|
|
1510
|
+
error: `Failed to verify tmux session "${sessionName}" inside container. The session may not have started correctly.`,
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1509
1513
|
return {
|
|
1510
1514
|
success: true,
|
|
1511
1515
|
containerId: actualContainerId,
|
|
1512
1516
|
sessionId: sessionName, // Container tmux session name for tracking
|
|
1513
1517
|
};
|
|
1514
1518
|
}
|
|
1515
|
-
//
|
|
1519
|
+
// For foreground mode: attach to container's tmux session in current terminal (blocking)
|
|
1516
1520
|
if (displayMode === 'foreground') {
|
|
1517
1521
|
try {
|
|
1518
1522
|
// Clear screen and attach - this blocks until user detaches or claude exits
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Utilities
|
|
3
|
+
*
|
|
4
|
+
* Shared utilities for tmux session naming, parsing, and discovery.
|
|
5
|
+
* Used by session/list.ts and session/attach.ts commands.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Known action names used in session naming.
|
|
9
|
+
* These are the actions defined in pmo/actions/ that may be used when spawning agents.
|
|
10
|
+
*/
|
|
11
|
+
export declare const KNOWN_ACTIONS: readonly ["Implement", "Review", "Fix", "Refactor", "Test", "Document", "work"];
|
|
12
|
+
/**
|
|
13
|
+
* Parse a tmux session name following prlt naming convention.
|
|
14
|
+
* Format: {ticketId}-{action}-{agentName}
|
|
15
|
+
*
|
|
16
|
+
* Note: Agent names can contain hyphens (like "stout-page"), so we match
|
|
17
|
+
* from the end using known action names to correctly split the components.
|
|
18
|
+
*
|
|
19
|
+
* Example: "TKT-878-Implement-stout-page" -> { ticketId: "TKT-878", action: "Implement", agentName: "stout-page" }
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseSessionName(sessionName: string): {
|
|
22
|
+
ticketId: string;
|
|
23
|
+
action: string;
|
|
24
|
+
agentName: string;
|
|
25
|
+
} | null;
|
|
26
|
+
/**
|
|
27
|
+
* Build expected session name from execution data.
|
|
28
|
+
* Format: {ticketId}-{action}-{agentName}
|
|
29
|
+
* This is the same format used by runners.ts buildSessionName()
|
|
30
|
+
*/
|
|
31
|
+
export declare function buildExpectedSessionName(ticketId: string, agentName: string, action?: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Check if a session name matches the expected pattern for a ticket and agent.
|
|
34
|
+
* Parses the session name and verifies the ticket ID and agent name match exactly.
|
|
35
|
+
*/
|
|
36
|
+
export declare function sessionMatchesExecution(sessionName: string, ticketId: string, agentName: string): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Get list of host tmux session names.
|
|
39
|
+
*/
|
|
40
|
+
export declare function getHostTmuxSessionNames(): string[];
|
|
41
|
+
/**
|
|
42
|
+
* Get map of containerId -> tmux session names.
|
|
43
|
+
* Only checks containers with the devcontainer.local_folder label.
|
|
44
|
+
*/
|
|
45
|
+
export declare function getContainerTmuxSessionMap(): Map<string, string[]>;
|
|
46
|
+
/**
|
|
47
|
+
* Flatten container sessions map into an array for easier iteration.
|
|
48
|
+
*/
|
|
49
|
+
export declare function flattenContainerSessions(containerTmuxSessions: Map<string, string[]>): Array<{
|
|
50
|
+
sessionName: string;
|
|
51
|
+
containerId: string;
|
|
52
|
+
}>;
|
|
53
|
+
/**
|
|
54
|
+
* Try to find a matching tmux session for an execution with NULL sessionId.
|
|
55
|
+
* First tries exact matches with known action names, then falls back to
|
|
56
|
+
* partial matching with agent name verification.
|
|
57
|
+
*
|
|
58
|
+
* @returns The matched session name, or null if no match found
|
|
59
|
+
*/
|
|
60
|
+
export declare function findSessionForExecution(ticketId: string, agentName: string, availableSessions: string[]): string | null;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Utilities
|
|
3
|
+
*
|
|
4
|
+
* Shared utilities for tmux session naming, parsing, and discovery.
|
|
5
|
+
* Used by session/list.ts and session/attach.ts commands.
|
|
6
|
+
*/
|
|
7
|
+
import { execSync } from 'node:child_process';
|
|
8
|
+
/**
|
|
9
|
+
* Known action names used in session naming.
|
|
10
|
+
* These are the actions defined in pmo/actions/ that may be used when spawning agents.
|
|
11
|
+
*/
|
|
12
|
+
export const KNOWN_ACTIONS = [
|
|
13
|
+
'Implement',
|
|
14
|
+
'Review',
|
|
15
|
+
'Fix',
|
|
16
|
+
'Refactor',
|
|
17
|
+
'Test',
|
|
18
|
+
'Document',
|
|
19
|
+
'work', // Default fallback
|
|
20
|
+
];
|
|
21
|
+
/**
|
|
22
|
+
* Parse a tmux session name following prlt naming convention.
|
|
23
|
+
* Format: {ticketId}-{action}-{agentName}
|
|
24
|
+
*
|
|
25
|
+
* Note: Agent names can contain hyphens (like "stout-page"), so we match
|
|
26
|
+
* from the end using known action names to correctly split the components.
|
|
27
|
+
*
|
|
28
|
+
* Example: "TKT-878-Implement-stout-page" -> { ticketId: "TKT-878", action: "Implement", agentName: "stout-page" }
|
|
29
|
+
*/
|
|
30
|
+
export function parseSessionName(sessionName) {
|
|
31
|
+
// First, extract the ticket ID (format: TKT-### or PROJECT-###)
|
|
32
|
+
const ticketMatch = sessionName.match(/^(TKT-\d+|[A-Z]+-\d+)-/);
|
|
33
|
+
if (!ticketMatch) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const ticketId = ticketMatch[1];
|
|
37
|
+
const remainder = sessionName.slice(ticketMatch[0].length);
|
|
38
|
+
// Try to match known actions (case-insensitive) at the start of the remainder
|
|
39
|
+
for (const action of KNOWN_ACTIONS) {
|
|
40
|
+
const actionLower = action.toLowerCase();
|
|
41
|
+
const remainderLower = remainder.toLowerCase();
|
|
42
|
+
// Check if remainder starts with this action followed by a hyphen
|
|
43
|
+
if (remainderLower.startsWith(actionLower + '-')) {
|
|
44
|
+
const agentName = remainder.slice(action.length + 1);
|
|
45
|
+
if (agentName) {
|
|
46
|
+
return {
|
|
47
|
+
ticketId,
|
|
48
|
+
action: remainder.slice(0, action.length), // Preserve original casing
|
|
49
|
+
agentName,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Fallback: Split on first hyphen (original behavior for unknown actions)
|
|
55
|
+
const parts = remainder.split('-');
|
|
56
|
+
if (parts.length >= 2) {
|
|
57
|
+
return {
|
|
58
|
+
ticketId,
|
|
59
|
+
action: parts[0],
|
|
60
|
+
agentName: parts.slice(1).join('-'),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Build expected session name from execution data.
|
|
67
|
+
* Format: {ticketId}-{action}-{agentName}
|
|
68
|
+
* This is the same format used by runners.ts buildSessionName()
|
|
69
|
+
*/
|
|
70
|
+
export function buildExpectedSessionName(ticketId, agentName, action = 'work') {
|
|
71
|
+
return `${ticketId}-${action}-${agentName}`;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Check if a session name matches the expected pattern for a ticket and agent.
|
|
75
|
+
* Parses the session name and verifies the ticket ID and agent name match exactly.
|
|
76
|
+
*/
|
|
77
|
+
export function sessionMatchesExecution(sessionName, ticketId, agentName) {
|
|
78
|
+
const parsed = parseSessionName(sessionName);
|
|
79
|
+
if (!parsed)
|
|
80
|
+
return false;
|
|
81
|
+
return parsed.ticketId === ticketId && parsed.agentName === agentName;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get list of host tmux session names.
|
|
85
|
+
*/
|
|
86
|
+
export function getHostTmuxSessionNames() {
|
|
87
|
+
try {
|
|
88
|
+
execSync('which tmux', { stdio: 'pipe' });
|
|
89
|
+
const output = execSync('tmux list-sessions -F "#{session_name}"', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
90
|
+
if (!output)
|
|
91
|
+
return [];
|
|
92
|
+
return output.split('\n');
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get map of containerId -> tmux session names.
|
|
100
|
+
* Only checks containers with the devcontainer.local_folder label.
|
|
101
|
+
*/
|
|
102
|
+
export function getContainerTmuxSessionMap() {
|
|
103
|
+
const sessionMap = new Map();
|
|
104
|
+
try {
|
|
105
|
+
const containersOutput = execSync('docker ps --filter "label=devcontainer.local_folder" --format "{{.ID}}"', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
106
|
+
if (!containersOutput)
|
|
107
|
+
return sessionMap;
|
|
108
|
+
for (const containerId of containersOutput.split('\n')) {
|
|
109
|
+
try {
|
|
110
|
+
const tmuxOutput = execSync(`docker exec ${containerId} tmux list-sessions -F "#{session_name}" 2>/dev/null`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
111
|
+
if (tmuxOutput) {
|
|
112
|
+
sessionMap.set(containerId, tmuxOutput.split('\n'));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// Container has no tmux sessions
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// Docker not available
|
|
122
|
+
}
|
|
123
|
+
return sessionMap;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Flatten container sessions map into an array for easier iteration.
|
|
127
|
+
*/
|
|
128
|
+
export function flattenContainerSessions(containerTmuxSessions) {
|
|
129
|
+
const result = [];
|
|
130
|
+
containerTmuxSessions.forEach((sessions, containerId) => {
|
|
131
|
+
for (const sessionName of sessions) {
|
|
132
|
+
result.push({ sessionName, containerId });
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Try to find a matching tmux session for an execution with NULL sessionId.
|
|
139
|
+
* First tries exact matches with known action names, then falls back to
|
|
140
|
+
* partial matching with agent name verification.
|
|
141
|
+
*
|
|
142
|
+
* @returns The matched session name, or null if no match found
|
|
143
|
+
*/
|
|
144
|
+
export function findSessionForExecution(ticketId, agentName, availableSessions) {
|
|
145
|
+
// First, try exact matches with known action names
|
|
146
|
+
for (const action of KNOWN_ACTIONS) {
|
|
147
|
+
const expectedName = buildExpectedSessionName(ticketId, agentName, action);
|
|
148
|
+
if (availableSessions.includes(expectedName)) {
|
|
149
|
+
return expectedName;
|
|
150
|
+
}
|
|
151
|
+
// Also try lowercase variant
|
|
152
|
+
const expectedNameLower = buildExpectedSessionName(ticketId, agentName, action.toLowerCase());
|
|
153
|
+
if (availableSessions.includes(expectedNameLower)) {
|
|
154
|
+
return expectedNameLower;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Fallback: partial match with agent name verification
|
|
158
|
+
// This catches sessions with unknown action names while preventing
|
|
159
|
+
// false matches when multiple agents work on the same ticket
|
|
160
|
+
const match = availableSessions.find(s => sessionMatchesExecution(s, ticketId, agentName));
|
|
161
|
+
return match || null;
|
|
162
|
+
}
|
|
@@ -10,6 +10,7 @@ import { execSync } from 'node:child_process';
|
|
|
10
10
|
import { autoExportToBoard } from '../pmo/index.js';
|
|
11
11
|
import { getWorkColumnSetting, findColumnByName } from '../pmo/utils.js';
|
|
12
12
|
import { findHQRoot } from '../repos/index.js';
|
|
13
|
+
import { hasGitHubRemote } from '../repos/git.js';
|
|
13
14
|
import { hasDevcontainerConfig } from './devcontainer.js';
|
|
14
15
|
import { loadExecutionConfig, getOrPromptCoderName } from './config.js';
|
|
15
16
|
import { runExecution, isDockerRunning, isGitHubTokenAvailable, isDevcontainerCliInstalled } from './runners.js';
|
|
@@ -30,6 +31,21 @@ function tryGitCommand(cmd, cwd) {
|
|
|
30
31
|
return false;
|
|
31
32
|
}
|
|
32
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Check if any of the given repos have a GitHub remote
|
|
36
|
+
*/
|
|
37
|
+
function checkReposForRemote(repoPaths) {
|
|
38
|
+
const reposWithoutRemote = [];
|
|
39
|
+
for (const repoPath of repoPaths) {
|
|
40
|
+
if (isGitRepo(repoPath) && !hasGitHubRemote(repoPath)) {
|
|
41
|
+
reposWithoutRemote.push(repoPath);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
hasRemote: reposWithoutRemote.length === 0,
|
|
46
|
+
reposWithoutRemote,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
33
49
|
/**
|
|
34
50
|
* Check if a directory is a git repository
|
|
35
51
|
*/
|
|
@@ -272,6 +288,32 @@ export async function spawnAgentForTicket(ticket, agentName, storage, executionS
|
|
|
272
288
|
const gitRepos = repoWorktrees.length > 0
|
|
273
289
|
? repoWorktrees.map(r => path.join(agentDir, r))
|
|
274
290
|
: [worktreePath];
|
|
291
|
+
// Check for GitHub remote if PR creation is enabled
|
|
292
|
+
if (!options.skipRemoteCheck) {
|
|
293
|
+
const remoteCheck = checkReposForRemote(gitRepos);
|
|
294
|
+
if (!remoteCheck.hasRemote) {
|
|
295
|
+
const repoNames = remoteCheck.reposWithoutRemote.map(r => path.basename(r)).join(', ');
|
|
296
|
+
if (options.createPR) {
|
|
297
|
+
// If PR creation is requested, we must have a remote
|
|
298
|
+
return {
|
|
299
|
+
success: false,
|
|
300
|
+
ticketId: ticket.id,
|
|
301
|
+
agentName,
|
|
302
|
+
error: `No GitHub remote found for: ${repoNames}\n\n` +
|
|
303
|
+
'Cannot create PRs without a GitHub remote.\n' +
|
|
304
|
+
'Options:\n' +
|
|
305
|
+
' 1. Run "prlt repo create" to create a GitHub repo and set up remote\n' +
|
|
306
|
+
' 2. Manually add a remote: git remote add origin <url>\n' +
|
|
307
|
+
' 3. Use --skip-remote-check to spawn without PR support',
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
// Just warn if not creating PRs
|
|
312
|
+
log(`⚠️ No GitHub remote found for: ${repoNames}. PRs cannot be created.`);
|
|
313
|
+
log(' Run "prlt repo create" to set up a GitHub remote.');
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
275
317
|
// Always fetch latest from origin before branch operations
|
|
276
318
|
// This ensures all spawn actions work with the latest code
|
|
277
319
|
for (const repoPath of gitRepos) {
|
|
@@ -74,8 +74,8 @@ export interface ResolverChoice<T = string> {
|
|
|
74
74
|
export interface PromptDefinition<TValue = unknown, TFlags = Record<string, unknown>> {
|
|
75
75
|
/** The flag name this prompt resolves (e.g., 'column', 'title') */
|
|
76
76
|
flagName: string;
|
|
77
|
-
/** Prompt type */
|
|
78
|
-
type: 'list' | 'checkbox' | 'input' | 'confirm' | 'editor';
|
|
77
|
+
/** Prompt type. Use 'multiline' for inline multi-line text input (replaces 'editor') */
|
|
78
|
+
type: 'list' | 'checkbox' | 'input' | 'confirm' | 'editor' | 'multiline';
|
|
79
79
|
/** User-facing prompt message */
|
|
80
80
|
message: string | ((ctx: ResolverContext<TFlags>) => string);
|
|
81
81
|
/**
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
*/
|
|
41
41
|
import inquirer from 'inquirer';
|
|
42
42
|
import { outputPromptAsJson, createMetadata, } from '../prompt-json.js';
|
|
43
|
+
import { multiLineInput } from '../multiline-input.js';
|
|
43
44
|
/**
|
|
44
45
|
* FlagResolver handles unified flag resolution for both human and machine modes.
|
|
45
46
|
*
|
|
@@ -228,6 +229,20 @@ export class FlagResolver {
|
|
|
228
229
|
* Prompt for value in interactive mode
|
|
229
230
|
*/
|
|
230
231
|
async promptInteractive(prompt, message, choices, defaultValue) {
|
|
232
|
+
// Handle multiline type specially - use our custom multiLineInput
|
|
233
|
+
if (prompt.type === 'multiline') {
|
|
234
|
+
const result = await multiLineInput({
|
|
235
|
+
message,
|
|
236
|
+
default: typeof defaultValue === 'string' ? defaultValue : '',
|
|
237
|
+
validate: prompt.validate
|
|
238
|
+
? (value) => prompt.validate(value, this.resolverContext)
|
|
239
|
+
: undefined,
|
|
240
|
+
});
|
|
241
|
+
if (result.cancelled) {
|
|
242
|
+
throw new Error('Input cancelled');
|
|
243
|
+
}
|
|
244
|
+
return result.value;
|
|
245
|
+
}
|
|
231
246
|
// Build inquirer prompt config as a single question object
|
|
232
247
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
233
248
|
const question = {
|
package/dist/lib/init/index.js
CHANGED
|
@@ -9,6 +9,8 @@ import { addRepositoriesToHQ, isInGitRepo } from '../repos/index.js';
|
|
|
9
9
|
import { createPMO, } from '../pmo/index.js';
|
|
10
10
|
import { createWorkspaceDatabase, addRepositoriesToDatabase, addAgentsToDatabase, createTheme, addThemeNames, setActiveTheme } from '../database/index.js';
|
|
11
11
|
import { ensureMachineConfigDir, registerHeadquarters, getOrganizations, createOrganization, } from '../machine-config.js';
|
|
12
|
+
import { hasGitHubRemote } from '../repos/git.js';
|
|
13
|
+
import { isGHInstalled, isGHAuthenticated } from '../pr/index.js';
|
|
12
14
|
/**
|
|
13
15
|
* Validate that HQ path is not inside a git repository or another HQ
|
|
14
16
|
* Returns: { valid: true } or { valid: false, reason: string }
|
|
@@ -291,6 +293,22 @@ export async function initializeHQ(options) {
|
|
|
291
293
|
};
|
|
292
294
|
});
|
|
293
295
|
addRepositoriesToDatabase(hqPath, dbRepos);
|
|
296
|
+
// Check for repos without GitHub remotes (only in interactive mode)
|
|
297
|
+
if (!quiet && addedRepos.length > 0) {
|
|
298
|
+
const reposPath = path.join(hqPath, 'repos');
|
|
299
|
+
const reposWithoutRemote = [];
|
|
300
|
+
for (const repoName of addedRepos) {
|
|
301
|
+
const repoFullPath = path.join(reposPath, repoName);
|
|
302
|
+
if (fs.existsSync(repoFullPath) && !hasGitHubRemote(repoFullPath)) {
|
|
303
|
+
reposWithoutRemote.push(repoName);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (reposWithoutRemote.length > 0 && isGHInstalled() && isGHAuthenticated()) {
|
|
307
|
+
log(chalk.yellow(`\n⚠️ The following repos have no GitHub remote: ${reposWithoutRemote.join(', ')}`));
|
|
308
|
+
log(chalk.gray(' Without a remote, agents cannot create pull requests.'));
|
|
309
|
+
log(chalk.gray(' You can create GitHub repos later with: prlt repo create'));
|
|
310
|
+
}
|
|
311
|
+
}
|
|
294
312
|
// Create PMO if requested
|
|
295
313
|
if (pmoSetup.includePMO) {
|
|
296
314
|
await createPMO({
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-line text input utility for CLI.
|
|
3
|
+
*
|
|
4
|
+
* Provides inline TTY text input without opening external editors.
|
|
5
|
+
* Handles paste safely, supports cursor navigation, and provides
|
|
6
|
+
* clear visual feedback.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* ```typescript
|
|
10
|
+
* const text = await multiLineInput({
|
|
11
|
+
* message: 'Enter description:',
|
|
12
|
+
* default: 'Existing content...',
|
|
13
|
+
* });
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Options for multiLineInput
|
|
18
|
+
*/
|
|
19
|
+
export interface MultiLineInputOptions {
|
|
20
|
+
/** Prompt message displayed above the input area */
|
|
21
|
+
message: string;
|
|
22
|
+
/** Default/initial content to populate the input with */
|
|
23
|
+
default?: string;
|
|
24
|
+
/** Hint text shown below the input area (e.g., key bindings) */
|
|
25
|
+
hint?: string;
|
|
26
|
+
/** Whether input is required (empty not allowed) */
|
|
27
|
+
required?: boolean;
|
|
28
|
+
/** Validation function - returns true if valid, or error message */
|
|
29
|
+
validate?: (value: string) => boolean | string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Result of multiLineInput
|
|
33
|
+
*/
|
|
34
|
+
export interface MultiLineInputResult {
|
|
35
|
+
/** The entered text */
|
|
36
|
+
value: string;
|
|
37
|
+
/** Whether input was cancelled (Ctrl+C) */
|
|
38
|
+
cancelled: boolean;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Collect multi-line input from the user with an inline TTY editor.
|
|
42
|
+
*
|
|
43
|
+
* Features:
|
|
44
|
+
* - Arrow key navigation
|
|
45
|
+
* - Backspace/delete
|
|
46
|
+
* - Copy-paste handling (escapes special characters)
|
|
47
|
+
* - Ctrl+D to finish, Ctrl+C to cancel
|
|
48
|
+
* - Pre-populated content support
|
|
49
|
+
* - Real-time visual feedback
|
|
50
|
+
*
|
|
51
|
+
* @param options Input options
|
|
52
|
+
* @returns The entered text and cancellation status
|
|
53
|
+
*/
|
|
54
|
+
export declare function multiLineInput(options: MultiLineInputOptions): Promise<MultiLineInputResult>;
|
|
55
|
+
/**
|
|
56
|
+
* Convenience wrapper that returns just the value string.
|
|
57
|
+
* Throws if cancelled.
|
|
58
|
+
*/
|
|
59
|
+
export declare function promptMultiLine(options: MultiLineInputOptions): Promise<string>;
|
|
60
|
+
/**
|
|
61
|
+
* Integration with FlagResolver - creates a prompt-compatible function
|
|
62
|
+
*/
|
|
63
|
+
export declare function createMultiLinePrompt(message: string, defaultValue?: string, hint?: string): () => Promise<string>;
|