@proletariat/cli 0.3.35 → 0.3.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/dist/commands/agent/auth.d.ts +12 -2
  2. package/dist/commands/agent/auth.js +128 -4
  3. package/dist/commands/agent/list.js +16 -7
  4. package/dist/commands/agent/status.js +32 -4
  5. package/dist/commands/board/watch.js +6 -0
  6. package/dist/commands/branch/list.d.ts +1 -0
  7. package/dist/commands/branch/list.js +43 -12
  8. package/dist/commands/branch/where.js +3 -2
  9. package/dist/commands/category/list.d.ts +2 -1
  10. package/dist/commands/category/list.js +38 -13
  11. package/dist/commands/{claude.d.ts → claude/index.d.ts} +1 -1
  12. package/dist/commands/{claude.js → claude/index.js} +12 -12
  13. package/dist/commands/claude/open.d.ts +13 -0
  14. package/dist/commands/claude/open.js +175 -0
  15. package/dist/commands/diet.js +18 -2
  16. package/dist/commands/docker/logs.js +7 -3
  17. package/dist/commands/docker/shell.js +6 -0
  18. package/dist/commands/docker/start.js +20 -4
  19. package/dist/commands/docker/sync.d.ts +4 -0
  20. package/dist/commands/docker/sync.js +30 -2
  21. package/dist/commands/epic/show.d.ts +13 -0
  22. package/dist/commands/epic/show.js +16 -0
  23. package/dist/commands/epic/view.js +27 -0
  24. package/dist/commands/execution/config.d.ts +0 -4
  25. package/dist/commands/execution/config.js +10 -32
  26. package/dist/commands/execution/index.js +2 -1
  27. package/dist/commands/execution/logs.js +1 -1
  28. package/dist/commands/execution/stop.js +2 -1
  29. package/dist/commands/execution/view.js +22 -26
  30. package/dist/commands/init.js +2 -19
  31. package/dist/commands/label/create.js +2 -1
  32. package/dist/commands/label/delete.js +2 -1
  33. package/dist/commands/label/group/create.js +2 -1
  34. package/dist/commands/label/group/list.js +2 -1
  35. package/dist/commands/label/list.js +2 -1
  36. package/dist/commands/mcp-server.js +25 -0
  37. package/dist/commands/phase/template/list.js +2 -1
  38. package/dist/commands/project/create.js +3 -4
  39. package/dist/commands/project/update.js +5 -6
  40. package/dist/commands/pull.js +24 -0
  41. package/dist/commands/session/create.d.ts +19 -0
  42. package/dist/commands/session/create.js +102 -0
  43. package/dist/commands/session/health.js +1 -20
  44. package/dist/commands/session/index.js +14 -1
  45. package/dist/commands/session/list.js +26 -7
  46. package/dist/commands/session/peek.d.ts +38 -0
  47. package/dist/commands/session/peek.js +316 -0
  48. package/dist/commands/session/poke.d.ts +27 -0
  49. package/dist/commands/session/poke.js +219 -0
  50. package/dist/commands/spec/view.js +29 -0
  51. package/dist/commands/template/list.js +2 -1
  52. package/dist/commands/theme/add-names.d.ts +4 -0
  53. package/dist/commands/theme/add-names.js +11 -1
  54. package/dist/commands/theme/create.d.ts +2 -0
  55. package/dist/commands/theme/create.js +8 -0
  56. package/dist/commands/ticket/bulk.js +2 -2
  57. package/dist/commands/ticket/complete.js +2 -2
  58. package/dist/commands/ticket/create.js +21 -0
  59. package/dist/commands/ticket/delete.js +8 -0
  60. package/dist/commands/ticket/edit.js +25 -0
  61. package/dist/commands/ticket/index.js +2 -2
  62. package/dist/commands/ticket/move.js +25 -2
  63. package/dist/commands/ticket/resolve.js +3 -4
  64. package/dist/commands/ticket/show.d.ts +13 -0
  65. package/dist/commands/ticket/show.js +16 -0
  66. package/dist/commands/ticket/template/list.js +2 -1
  67. package/dist/commands/ticket/view.d.ts +0 -1
  68. package/dist/commands/ticket/view.js +30 -1
  69. package/dist/commands/work/index.js +4 -0
  70. package/dist/commands/work/start.js +169 -94
  71. package/dist/commands/work/status.d.ts +14 -0
  72. package/dist/commands/work/status.js +60 -0
  73. package/dist/commands/workflow/index.js +2 -1
  74. package/dist/commands/workflow/show.d.ts +13 -0
  75. package/dist/commands/workflow/show.js +16 -0
  76. package/dist/commands/workspace/add.js +15 -0
  77. package/dist/commands/workspace/list.js +2 -1
  78. package/dist/commands/workspace/prune.js +5 -5
  79. package/dist/lib/branch/index.d.ts +1 -0
  80. package/dist/lib/execution/config.d.ts +15 -1
  81. package/dist/lib/execution/config.js +28 -0
  82. package/dist/lib/execution/runners.d.ts +11 -0
  83. package/dist/lib/execution/runners.js +53 -19
  84. package/dist/lib/execution/session-utils.d.ts +11 -1
  85. package/dist/lib/execution/session-utils.js +26 -1
  86. package/dist/lib/execution/storage.d.ts +5 -0
  87. package/dist/lib/execution/storage.js +18 -3
  88. package/dist/lib/execution/types.d.ts +2 -0
  89. package/dist/lib/mcp/tools/board.js +4 -6
  90. package/dist/lib/mcp/tools/cli-passthrough.js +25 -6
  91. package/dist/lib/mcp/tools/epic.js +8 -3
  92. package/dist/lib/mcp/tools/spec.js +1 -1
  93. package/dist/lib/mcp/tools/ticket.js +11 -9
  94. package/dist/lib/mcp/tools/work.js +96 -6
  95. package/dist/lib/mcp/types.d.ts +10 -0
  96. package/dist/lib/multiline-input.js +2 -1
  97. package/dist/lib/pmo/base-command.js +4 -4
  98. package/dist/lib/pmo/storage/actions.js +1 -1
  99. package/dist/lib/pmo/storage/base.js +195 -50
  100. package/dist/lib/pmo/storage/types.d.ts +1 -0
  101. package/dist/lib/prompt-command.d.ts +20 -0
  102. package/dist/lib/prompt-command.js +38 -2
  103. package/dist/lib/prompt-json.d.ts +36 -4
  104. package/dist/lib/prompt-json.js +129 -7
  105. package/dist/lib/styles.d.ts +37 -0
  106. package/dist/lib/styles.js +73 -0
  107. package/oclif.manifest.json +3259 -2701
  108. package/package.json +1 -1
@@ -5,32 +5,13 @@ import Database from 'better-sqlite3';
5
5
  import { styles } from '../../lib/styles.js';
6
6
  import { getWorkspaceInfo } from '../../lib/agents/commands.js';
7
7
  import { ExecutionStorage } from '../../lib/execution/index.js';
8
- import { parseSessionName, getHostTmuxSessionNames, getContainerTmuxSessionMap, flattenContainerSessions, findSessionForExecution, } from '../../lib/execution/session-utils.js';
8
+ import { parseSessionName, getHostTmuxSessionNames, getContainerTmuxSessionMap, flattenContainerSessions, findSessionForExecution, captureTmuxPane, } from '../../lib/execution/session-utils.js';
9
9
  import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
10
10
  import { visualPadEnd } from '../../lib/string-utils.js';
11
11
  import { shouldOutputJson, outputSuccessAsJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
12
12
  // =============================================================================
13
13
  // Detection Logic
14
14
  // =============================================================================
15
- /**
16
- * Capture the last N lines from a tmux pane.
17
- */
18
- function captureTmuxPane(sessionId, lines, containerId) {
19
- try {
20
- const captureCmd = `tmux capture-pane -t "${sessionId}" -p -S -${lines}`;
21
- if (containerId) {
22
- return execSync(`docker exec ${containerId} bash -c '${captureCmd}'`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 10000 }).trim();
23
- }
24
- return execSync(captureCmd, {
25
- encoding: 'utf-8',
26
- stdio: ['pipe', 'pipe', 'pipe'],
27
- timeout: 5000,
28
- }).trim();
29
- }
30
- catch {
31
- return null;
32
- }
33
- }
34
15
  /**
35
16
  * Detect agent health state from tmux pane content.
36
17
  *
@@ -1,11 +1,12 @@
1
1
  import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
2
2
  import { shouldOutputJson } from '../../lib/prompt-json.js';
3
3
  export default class Session extends PMOCommand {
4
- static description = 'Manage agent tmux sessions (list, attach, detach)';
4
+ static description = 'Manage agent tmux sessions (list, attach, create, detach)';
5
5
  static examples = [
6
6
  '<%= config.bin %> <%= command.id %>',
7
7
  '<%= config.bin %> <%= command.id %> list',
8
8
  '<%= config.bin %> <%= command.id %> attach TKT-347-implement',
9
+ '<%= config.bin %> <%= command.id %> create my-session',
9
10
  ];
10
11
  static flags = {
11
12
  ...pmoBaseFlags,
@@ -22,8 +23,11 @@ export default class Session extends PMOCommand {
22
23
  message: 'Session Management - What would you like to do?',
23
24
  choices: [
24
25
  { name: 'List active sessions', value: 'list', command: 'prlt session list --json' },
26
+ { name: 'Create a new session', value: 'create', command: 'prlt session create --json' },
25
27
  { name: 'Attach to a session', value: 'attach', command: 'prlt session attach --json' },
28
+ { name: 'Peek at agent output', value: 'peek', command: 'prlt session peek --json' },
26
29
  { name: 'Check agent health', value: 'health', command: 'prlt session health --json' },
30
+ { name: 'Poke a running agent', value: 'poke', command: 'prlt session poke --json' },
27
31
  { name: 'Cancel', value: 'cancel' },
28
32
  ],
29
33
  }], jsonModeConfig);
@@ -35,12 +39,21 @@ export default class Session extends PMOCommand {
35
39
  case 'list':
36
40
  await this.config.runCommand('session:list', []);
37
41
  break;
42
+ case 'create':
43
+ await this.config.runCommand('session:create', []);
44
+ break;
38
45
  case 'attach':
39
46
  await this.config.runCommand('session:attach', []);
40
47
  break;
48
+ case 'peek':
49
+ await this.config.runCommand('session:peek', []);
50
+ break;
41
51
  case 'health':
42
52
  await this.config.runCommand('session:health', []);
43
53
  break;
54
+ case 'poke':
55
+ await this.config.runCommand('session:poke', []);
56
+ break;
44
57
  }
45
58
  }
46
59
  }
@@ -8,6 +8,24 @@ import { parseSessionName, getHostTmuxSessionNames, getContainerTmuxSessionMap,
8
8
  import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
9
9
  import { shouldOutputJson } from '../../lib/prompt-json.js';
10
10
  import { visualPadEnd } from '../../lib/string-utils.js';
11
+ /**
12
+ * Find container sessions using prefix matching.
13
+ * Handles cases where the stored containerId format differs from docker ps output
14
+ * (e.g., full 64-char ID vs 12-char short ID).
15
+ */
16
+ function findContainerSessionsByPrefix(containerTmuxSessions, containerId) {
17
+ // Try exact match first
18
+ const exact = containerTmuxSessions.get(containerId);
19
+ if (exact)
20
+ return exact;
21
+ // Fall back to prefix matching (handles short vs full ID mismatches)
22
+ for (const [key, sessions] of containerTmuxSessions) {
23
+ if (key.startsWith(containerId) || containerId.startsWith(key)) {
24
+ return sessions;
25
+ }
26
+ }
27
+ return [];
28
+ }
11
29
  export default class SessionList extends PMOCommand {
12
30
  static description = 'List active tmux sessions (host and container)';
13
31
  static examples = [
@@ -65,7 +83,7 @@ export default class SessionList extends PMOCommand {
65
83
  // If sessionId is NULL, try to find session by naming convention
66
84
  if (!exec.sessionId) {
67
85
  if (isContainer && exec.containerId) {
68
- const containerSessions = containerTmuxSessions.get(exec.containerId) || [];
86
+ const containerSessions = findContainerSessionsByPrefix(containerTmuxSessions, exec.containerId);
69
87
  const match = findSessionForExecution(exec.ticketId, exec.agentName, containerSessions);
70
88
  if (match) {
71
89
  actualSessionId = match;
@@ -88,8 +106,8 @@ export default class SessionList extends PMOCommand {
88
106
  else {
89
107
  // sessionId is set, verify it exists
90
108
  if (isContainer && exec.containerId) {
91
- const containerSessions = containerTmuxSessions.get(exec.containerId);
92
- exists = containerSessions?.includes(exec.sessionId) ?? false;
109
+ const containerSessions = findContainerSessionsByPrefix(containerTmuxSessions, exec.containerId);
110
+ exists = containerSessions.includes(exec.sessionId);
93
111
  containerId = exec.containerId;
94
112
  }
95
113
  else {
@@ -105,14 +123,15 @@ export default class SessionList extends PMOCommand {
105
123
  matchedHostSessions.add(actualSessionId);
106
124
  }
107
125
  }
108
- // Only include if session exists, unless --all flag
109
- // Note: actualSessionId is guaranteed non-null here due to continue above
110
- if ((exists || flags.all) && actualSessionId) {
126
+ // Always include sessions from DB that have a sessionId.
127
+ // When tmux verification fails (e.g., Docker/tmux not accessible from MCP context),
128
+ // show the session with the DB status instead of silently dropping it.
129
+ if (actualSessionId) {
111
130
  sessions.push({
112
131
  sessionId: actualSessionId,
113
132
  ticketId: exec.ticketId,
114
133
  agentName: exec.agentName,
115
- status: exists ? exec.status : 'stale',
134
+ status: exists ? exec.status : (flags.all ? 'stale' : exec.status),
116
135
  environment: isContainer ? 'container' : 'host',
117
136
  containerId,
118
137
  exists,
@@ -0,0 +1,38 @@
1
+ import { PMOCommand } from '../../lib/pmo/index.js';
2
+ export default class SessionPeek extends PMOCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ target: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ lines: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
10
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
+ project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ };
14
+ protected getPMOOptions(): {
15
+ promptIfMultiple: boolean;
16
+ };
17
+ execute(): Promise<void>;
18
+ /**
19
+ * Resolve a target identifier to matching sessions.
20
+ * Supports: agent name, ticket ID (TKT-XXX), execution ID (WORK-XXX), or session ID.
21
+ */
22
+ private resolveTarget;
23
+ /**
24
+ * Resolve an execution ID (WORK-XXX) to matching sessions.
25
+ */
26
+ private resolveFromExecution;
27
+ /**
28
+ * Output peek content for a session.
29
+ * In raw mode: outputs plain text to stdout.
30
+ * In JSON mode: outputs structured JSON.
31
+ */
32
+ private outputPeek;
33
+ /**
34
+ * Get verified sessions from DB that have actual tmux processes.
35
+ * Same discovery pattern as attach.ts and list.ts.
36
+ */
37
+ private getVerifiedSessions;
38
+ }
@@ -0,0 +1,316 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import * as path from 'node:path';
3
+ import Database from 'better-sqlite3';
4
+ import { styles } from '../../lib/styles.js';
5
+ import { getWorkspaceInfo } from '../../lib/agents/commands.js';
6
+ import { ExecutionStorage } from '../../lib/execution/index.js';
7
+ import { parseSessionName, getHostTmuxSessionNames, getContainerTmuxSessionMap, flattenContainerSessions, findSessionForExecution, captureTmuxPane, } from '../../lib/execution/session-utils.js';
8
+ import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
9
+ import { shouldOutputJson, outputSuccessAsJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
10
+ // =============================================================================
11
+ // Command
12
+ // =============================================================================
13
+ export default class SessionPeek extends PMOCommand {
14
+ static description = 'View agent tmux pane content without attaching (non-interactive)';
15
+ static examples = [
16
+ '<%= config.bin %> session peek altman',
17
+ '<%= config.bin %> session peek TKT-123',
18
+ '<%= config.bin %> session peek WORK-ABCD1234',
19
+ '<%= config.bin %> session peek altman --lines 100',
20
+ '<%= config.bin %> session peek TKT-123 --json',
21
+ '<%= config.bin %> session peek altman | grep error',
22
+ ];
23
+ static args = {
24
+ target: Args.string({
25
+ description: 'Agent name, ticket ID (e.g. TKT-123), or execution ID (e.g. WORK-XXXXXXXX)',
26
+ required: false,
27
+ }),
28
+ };
29
+ static flags = {
30
+ ...pmoBaseFlags,
31
+ lines: Flags.integer({
32
+ char: 'l',
33
+ description: 'Number of scrollback lines to capture',
34
+ default: 50,
35
+ }),
36
+ };
37
+ getPMOOptions() {
38
+ return { promptIfMultiple: false };
39
+ }
40
+ async execute() {
41
+ const { args, flags } = await this.parse(SessionPeek);
42
+ const jsonMode = shouldOutputJson(flags);
43
+ // Discover all verified sessions
44
+ const sessions = this.getVerifiedSessions(jsonMode, flags);
45
+ if (sessions.length === 0) {
46
+ if (jsonMode) {
47
+ outputErrorAsJson('NO_SESSIONS', 'No active sessions found.', createMetadata('session peek', flags));
48
+ return;
49
+ }
50
+ this.log('');
51
+ this.log(styles.muted('No active sessions found.'));
52
+ this.log('');
53
+ this.log(styles.muted('Start work with: prlt work start <ticket-id>'));
54
+ this.log('');
55
+ return;
56
+ }
57
+ if (args.target) {
58
+ // Resolve target to matching session(s)
59
+ const matched = this.resolveTarget(args.target, sessions);
60
+ if (matched.length === 0) {
61
+ if (jsonMode) {
62
+ outputErrorAsJson('SESSION_NOT_FOUND', `No matching session found for "${args.target}".`, createMetadata('session peek', flags));
63
+ return;
64
+ }
65
+ this.error(`No matching session found for "${args.target}". Run "prlt session list" to see available sessions.`);
66
+ }
67
+ // Output all matched sessions
68
+ if (jsonMode && matched.length > 1) {
69
+ // Collect all captures into a single JSON response
70
+ const results = matched.map(session => {
71
+ const containerId = session.environment === 'container' ? session.containerId : undefined;
72
+ const content = captureTmuxPane(session.sessionId, flags.lines, containerId);
73
+ return {
74
+ sessionId: session.sessionId,
75
+ ticketId: session.ticketId,
76
+ agentName: session.agentName,
77
+ environment: session.environment,
78
+ containerId: session.containerId,
79
+ lines: flags.lines,
80
+ content,
81
+ captureError: content === null
82
+ ? `Failed to capture pane content for session "${session.sessionId}".`
83
+ : undefined,
84
+ };
85
+ });
86
+ outputSuccessAsJson({ sessions: results }, createMetadata('session peek', flags));
87
+ return;
88
+ }
89
+ for (const session of matched) {
90
+ this.outputPeek(session, flags.lines, jsonMode, flags);
91
+ }
92
+ }
93
+ else {
94
+ // No target: interactive selection
95
+ const selected = await this.selectFromList({
96
+ message: 'Select a session to peek at:',
97
+ items: sessions,
98
+ getName: (s) => `${s.sessionId} (${s.ticketId}) - ${s.agentName} [${s.environment}]`,
99
+ getValue: (s) => s.sessionId,
100
+ getCommand: (s) => `prlt session peek "${s.sessionId}" --json`,
101
+ jsonMode: jsonMode ? { flags, commandName: 'session peek' } : null,
102
+ });
103
+ if (!selected) {
104
+ return;
105
+ }
106
+ const session = sessions.find(s => s.sessionId === selected);
107
+ if (!session) {
108
+ this.error('No session selected');
109
+ }
110
+ this.outputPeek(session, flags.lines, jsonMode, flags);
111
+ }
112
+ }
113
+ /**
114
+ * Resolve a target identifier to matching sessions.
115
+ * Supports: agent name, ticket ID (TKT-XXX), execution ID (WORK-XXX), or session ID.
116
+ */
117
+ resolveTarget(target, sessions) {
118
+ // Try exact session ID match
119
+ const exactSession = sessions.filter(s => s.sessionId === target);
120
+ if (exactSession.length > 0)
121
+ return exactSession;
122
+ // Try ticket ID match (e.g. TKT-123)
123
+ if (/^[A-Z]+-\d+$/i.test(target)) {
124
+ const ticketTarget = target.toUpperCase();
125
+ const ticketMatches = sessions.filter(s => s.ticketId === ticketTarget);
126
+ if (ticketMatches.length > 0)
127
+ return ticketMatches;
128
+ }
129
+ // Try execution ID match (WORK-XXX) — resolve via DB to find session
130
+ if (target.toUpperCase().startsWith('WORK-')) {
131
+ const executionMatch = this.resolveFromExecution(target.toUpperCase(), sessions);
132
+ if (executionMatch.length > 0)
133
+ return executionMatch;
134
+ }
135
+ // Try agent name match
136
+ const agentMatches = sessions.filter(s => s.agentName === target);
137
+ if (agentMatches.length > 0)
138
+ return agentMatches;
139
+ // Try partial session ID match
140
+ const partialMatches = sessions.filter(s => s.sessionId.includes(target) ||
141
+ s.ticketId.includes(target.toUpperCase()) ||
142
+ s.agentName.includes(target));
143
+ return partialMatches;
144
+ }
145
+ /**
146
+ * Resolve an execution ID (WORK-XXX) to matching sessions.
147
+ */
148
+ resolveFromExecution(executionId, sessions) {
149
+ let db = null;
150
+ try {
151
+ const workspaceInfo = getWorkspaceInfo();
152
+ const dbPath = path.join(workspaceInfo.path, '.proletariat', 'workspace.db');
153
+ db = new Database(dbPath);
154
+ const executionStorage = new ExecutionStorage(db);
155
+ const execution = executionStorage.getExecution(executionId);
156
+ if (execution) {
157
+ return sessions.filter(s => s.ticketId === execution.ticketId && s.agentName === execution.agentName);
158
+ }
159
+ }
160
+ catch {
161
+ // Workspace not available
162
+ }
163
+ finally {
164
+ db?.close();
165
+ }
166
+ return [];
167
+ }
168
+ /**
169
+ * Output peek content for a session.
170
+ * In raw mode: outputs plain text to stdout.
171
+ * In JSON mode: outputs structured JSON.
172
+ */
173
+ outputPeek(session, lines, jsonMode, flags) {
174
+ const containerId = session.environment === 'container' ? session.containerId : undefined;
175
+ const content = captureTmuxPane(session.sessionId, lines, containerId);
176
+ if (content === null) {
177
+ if (jsonMode) {
178
+ outputErrorAsJson('CAPTURE_FAILED', `Failed to capture pane content for session "${session.sessionId}". The session may no longer exist or tmux may not be available.`, createMetadata('session peek', flags));
179
+ return;
180
+ }
181
+ this.error(`Failed to capture pane content for session "${session.sessionId}". ` +
182
+ 'The session may no longer exist or tmux may not be available.');
183
+ }
184
+ if (jsonMode) {
185
+ outputSuccessAsJson({
186
+ sessionId: session.sessionId,
187
+ ticketId: session.ticketId,
188
+ agentName: session.agentName,
189
+ environment: session.environment,
190
+ containerId: session.containerId,
191
+ lines,
192
+ content,
193
+ }, createMetadata('session peek', flags));
194
+ }
195
+ else {
196
+ // Raw text output — pipeable and scriptable
197
+ process.stdout.write(content + '\n');
198
+ }
199
+ }
200
+ /**
201
+ * Get verified sessions from DB that have actual tmux processes.
202
+ * Same discovery pattern as attach.ts and list.ts.
203
+ */
204
+ getVerifiedSessions(jsonMode, flags) {
205
+ const sessions = [];
206
+ let executionStorage = null;
207
+ let db = null;
208
+ try {
209
+ const workspaceInfo = getWorkspaceInfo();
210
+ const dbPath = path.join(workspaceInfo.path, '.proletariat', 'workspace.db');
211
+ db = new Database(dbPath);
212
+ executionStorage = new ExecutionStorage(db);
213
+ }
214
+ catch {
215
+ // Not in workspace — can still discover tmux sessions
216
+ }
217
+ try {
218
+ const hostTmuxSessions = getHostTmuxSessionNames();
219
+ const containerTmuxSessions = getContainerTmuxSessionMap();
220
+ const allContainerSessions = flattenContainerSessions(containerTmuxSessions);
221
+ const matchedHostSessions = new Set();
222
+ const matchedContainerSessions = new Set();
223
+ // Get active executions from DB
224
+ const activeExecutions = executionStorage ? [
225
+ ...(executionStorage.listExecutions({ status: 'running' }) || []),
226
+ ...(executionStorage.listExecutions({ status: 'starting' }) || []),
227
+ ] : [];
228
+ for (const exec of activeExecutions) {
229
+ const isContainer = exec.environment === 'devcontainer';
230
+ let exists = false;
231
+ let containerId;
232
+ let actualSessionId = exec.sessionId;
233
+ if (!exec.sessionId) {
234
+ if (isContainer && exec.containerId) {
235
+ const containerSessions = containerTmuxSessions.get(exec.containerId) || [];
236
+ const match = findSessionForExecution(exec.ticketId, exec.agentName, containerSessions);
237
+ if (match) {
238
+ actualSessionId = match;
239
+ exists = true;
240
+ containerId = exec.containerId;
241
+ }
242
+ }
243
+ else {
244
+ const match = findSessionForExecution(exec.ticketId, exec.agentName, hostTmuxSessions);
245
+ if (match) {
246
+ actualSessionId = match;
247
+ exists = true;
248
+ }
249
+ }
250
+ if (!actualSessionId)
251
+ continue;
252
+ }
253
+ else {
254
+ if (isContainer && exec.containerId) {
255
+ const containerSessions = containerTmuxSessions.get(exec.containerId);
256
+ exists = containerSessions?.includes(exec.sessionId) ?? false;
257
+ containerId = exec.containerId;
258
+ }
259
+ else {
260
+ exists = hostTmuxSessions.includes(exec.sessionId);
261
+ }
262
+ }
263
+ if (exists && actualSessionId) {
264
+ if (isContainer && containerId) {
265
+ matchedContainerSessions.add(`${containerId}:${actualSessionId}`);
266
+ }
267
+ else {
268
+ matchedHostSessions.add(actualSessionId);
269
+ }
270
+ sessions.push({
271
+ sessionId: actualSessionId,
272
+ ticketId: exec.ticketId,
273
+ agentName: exec.agentName,
274
+ environment: isContainer ? 'container' : 'host',
275
+ containerId,
276
+ source: 'db',
277
+ });
278
+ }
279
+ }
280
+ // Discover orphan sessions matching prlt pattern
281
+ for (const sessionName of hostTmuxSessions) {
282
+ if (matchedHostSessions.has(sessionName))
283
+ continue;
284
+ const parsed = parseSessionName(sessionName);
285
+ if (parsed) {
286
+ sessions.push({
287
+ sessionId: sessionName,
288
+ ticketId: parsed.ticketId,
289
+ agentName: parsed.agentName,
290
+ environment: 'host',
291
+ source: 'discovered',
292
+ });
293
+ }
294
+ }
295
+ for (const { sessionName, containerId } of allContainerSessions) {
296
+ if (matchedContainerSessions.has(`${containerId}:${sessionName}`))
297
+ continue;
298
+ const parsed = parseSessionName(sessionName);
299
+ if (parsed) {
300
+ sessions.push({
301
+ sessionId: sessionName,
302
+ ticketId: parsed.ticketId,
303
+ agentName: parsed.agentName,
304
+ environment: 'container',
305
+ containerId,
306
+ source: 'discovered',
307
+ });
308
+ }
309
+ }
310
+ }
311
+ finally {
312
+ db?.close();
313
+ }
314
+ return sessions;
315
+ }
316
+ }
@@ -0,0 +1,27 @@
1
+ import { PMOCommand } from '../../lib/pmo/index.js';
2
+ export default class SessionPoke extends PMOCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ agent: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
7
+ message: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
8
+ };
9
+ static flags: {
10
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
+ project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ };
14
+ protected getPMOOptions(): {
15
+ promptIfMultiple: boolean;
16
+ };
17
+ execute(): Promise<void>;
18
+ /**
19
+ * Resolve an agent identifier (name or ticket ID) to a running session.
20
+ * Looks up active executions and matches tmux sessions.
21
+ */
22
+ private resolveAgentSession;
23
+ /**
24
+ * Resolve the tmux session for a specific execution record.
25
+ */
26
+ private resolveSessionForExecution;
27
+ }