@proletariat/cli 0.3.50 → 0.3.52
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/agent/status.js +1 -0
- package/dist/commands/asana/connect.d.ts +15 -0
- package/dist/commands/asana/connect.js +267 -0
- package/dist/commands/asana/sync.d.ts +15 -0
- package/dist/commands/asana/sync.js +189 -0
- package/dist/commands/config/index.js +7 -1
- package/dist/commands/execution/list.js +3 -0
- package/dist/commands/execution/view.js +10 -0
- package/dist/commands/monday/connect.d.ts +16 -0
- package/dist/commands/monday/connect.js +212 -0
- package/dist/commands/monday/sync.d.ts +14 -0
- package/dist/commands/monday/sync.js +178 -0
- package/dist/commands/orchestrator/start.d.ts +6 -0
- package/dist/commands/orchestrator/start.js +149 -11
- package/dist/commands/session/list.js +6 -5
- package/dist/commands/work/index.js +7 -0
- package/dist/commands/work/jira.d.ts +28 -0
- package/dist/commands/work/jira.js +225 -0
- package/dist/commands/work/source/set.d.ts +12 -0
- package/dist/commands/work/source/set.js +52 -0
- package/dist/commands/work/source.d.ts +11 -0
- package/dist/commands/work/source.js +53 -0
- package/dist/commands/work/spawn.d.ts +1 -0
- package/dist/commands/work/spawn.js +73 -8
- package/dist/commands/work/start.d.ts +8 -0
- package/dist/commands/work/start.js +241 -3
- package/dist/lib/asana/client.d.ts +15 -0
- package/dist/lib/asana/client.js +120 -0
- package/dist/lib/asana/config.d.ts +9 -0
- package/dist/lib/asana/config.js +61 -0
- package/dist/lib/asana/index.d.ts +5 -0
- package/dist/lib/asana/index.js +4 -0
- package/dist/lib/asana/mapper.d.ts +13 -0
- package/dist/lib/asana/mapper.js +70 -0
- package/dist/lib/asana/sync.d.ts +13 -0
- package/dist/lib/asana/sync.js +36 -0
- package/dist/lib/asana/types.d.ts +40 -0
- package/dist/lib/asana/types.js +1 -0
- package/dist/lib/database/drizzle-schema.d.ts +393 -0
- package/dist/lib/database/drizzle-schema.js +45 -0
- package/dist/lib/execution/config.d.ts +10 -0
- package/dist/lib/execution/config.js +19 -0
- package/dist/lib/execution/runners.d.ts +10 -0
- package/dist/lib/execution/runners.js +110 -1
- package/dist/lib/execution/spawner.js +26 -0
- package/dist/lib/execution/storage.d.ts +4 -0
- package/dist/lib/execution/storage.js +8 -3
- package/dist/lib/execution/types.d.ts +4 -0
- package/dist/lib/external-issues/adapters.d.ts +18 -1
- package/dist/lib/external-issues/adapters.js +49 -1
- package/dist/lib/external-issues/index.d.ts +4 -1
- package/dist/lib/external-issues/index.js +5 -0
- package/dist/lib/external-issues/jira.d.ts +23 -0
- package/dist/lib/external-issues/jira.js +223 -0
- package/dist/lib/external-issues/linear.js +4 -3
- package/dist/lib/external-issues/mapper.d.ts +3 -2
- package/dist/lib/external-issues/mapper.js +5 -2
- package/dist/lib/external-issues/mapping-store.d.ts +12 -0
- package/dist/lib/external-issues/mapping-store.js +164 -0
- package/dist/lib/external-issues/types.d.ts +34 -0
- package/dist/lib/external-issues/validation.js +11 -0
- package/dist/lib/external-issues/work-start.d.ts +10 -0
- package/dist/lib/external-issues/work-start.js +12 -0
- package/dist/lib/linear/mapper.d.ts +2 -0
- package/dist/lib/linear/mapper.js +66 -2
- package/dist/lib/monday/client.d.ts +14 -0
- package/dist/lib/monday/client.js +113 -0
- package/dist/lib/monday/config.d.ts +10 -0
- package/dist/lib/monday/config.js +64 -0
- package/dist/lib/monday/index.d.ts +5 -0
- package/dist/lib/monday/index.js +4 -0
- package/dist/lib/monday/mapper.d.ts +14 -0
- package/dist/lib/monday/mapper.js +89 -0
- package/dist/lib/monday/sync.d.ts +13 -0
- package/dist/lib/monday/sync.js +45 -0
- package/dist/lib/monday/types.d.ts +38 -0
- package/dist/lib/monday/types.js +4 -0
- package/dist/lib/pmo/schema.d.ts +10 -1
- package/dist/lib/pmo/schema.js +73 -0
- package/dist/lib/pmo/storage/base.js +32 -0
- package/dist/lib/prompt-json.d.ts +11 -0
- package/dist/lib/work-source/config.d.ts +14 -0
- package/dist/lib/work-source/config.js +70 -0
- package/dist/lib/work-source/index.d.ts +1 -0
- package/dist/lib/work-source/index.js +1 -0
- package/oclif.manifest.json +2531 -1964
- package/package.json +1 -1
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
4
|
+
import { colors } from '../../lib/colors.js';
|
|
5
|
+
import { shouldOutputJson, outputSuccessAsJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
6
|
+
import { MondayClient, isMondayConfigured, loadMondayConfig, saveMondayApiToken, saveMondayBoard, saveMondayAccountName, clearMondayConfig, getMondayApiToken, getMondayBoardId, } from '../../lib/monday/index.js';
|
|
7
|
+
export default class MondayConnect extends PMOCommand {
|
|
8
|
+
static description = 'Connect PRLT to Monday.com and store workspace credentials';
|
|
9
|
+
static examples = [
|
|
10
|
+
'<%= config.bin %> <%= command.id %>',
|
|
11
|
+
'<%= config.bin %> <%= command.id %> --board 1234567890',
|
|
12
|
+
'<%= config.bin %> <%= command.id %> --check',
|
|
13
|
+
'<%= config.bin %> <%= command.id %> --disconnect',
|
|
14
|
+
];
|
|
15
|
+
static flags = {
|
|
16
|
+
...pmoBaseFlags,
|
|
17
|
+
board: Flags.string({
|
|
18
|
+
description: 'Monday board ID to sync tickets to',
|
|
19
|
+
}),
|
|
20
|
+
check: Flags.boolean({
|
|
21
|
+
description: 'Only check if Monday configuration is valid',
|
|
22
|
+
default: false,
|
|
23
|
+
}),
|
|
24
|
+
force: Flags.boolean({
|
|
25
|
+
description: 'Force re-authentication even if already configured',
|
|
26
|
+
default: false,
|
|
27
|
+
}),
|
|
28
|
+
disconnect: Flags.boolean({
|
|
29
|
+
description: 'Remove stored Monday credentials',
|
|
30
|
+
default: false,
|
|
31
|
+
}),
|
|
32
|
+
};
|
|
33
|
+
async execute() {
|
|
34
|
+
const { flags } = await this.parse(MondayConnect);
|
|
35
|
+
const jsonMode = shouldOutputJson(flags);
|
|
36
|
+
const db = this.storage.getDatabase();
|
|
37
|
+
if (flags.disconnect) {
|
|
38
|
+
clearMondayConfig(db);
|
|
39
|
+
if (jsonMode) {
|
|
40
|
+
outputSuccessAsJson({
|
|
41
|
+
disconnected: true,
|
|
42
|
+
message: 'Monday configuration removed.',
|
|
43
|
+
}, createMetadata('monday connect', flags));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
this.log(colors.success('Monday configuration removed.'));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (flags.check) {
|
|
50
|
+
await this.checkConnection(flags, jsonMode);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const existingConfig = loadMondayConfig(db);
|
|
54
|
+
if (existingConfig && !flags.force) {
|
|
55
|
+
try {
|
|
56
|
+
const client = new MondayClient(existingConfig.apiToken);
|
|
57
|
+
const info = await client.verify();
|
|
58
|
+
if (jsonMode) {
|
|
59
|
+
outputSuccessAsJson({
|
|
60
|
+
authenticated: true,
|
|
61
|
+
account: info.accountName,
|
|
62
|
+
user: info.userName,
|
|
63
|
+
boardId: existingConfig.boardId ?? null,
|
|
64
|
+
boardName: existingConfig.boardName ?? null,
|
|
65
|
+
message: 'Already authenticated. Use --force to re-authenticate.',
|
|
66
|
+
}, createMetadata('monday connect', flags));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
this.log(colors.success('Already connected to Monday.com'));
|
|
70
|
+
this.log(colors.textMuted(` Account: ${info.accountName}`));
|
|
71
|
+
this.log(colors.textMuted(` User: ${info.userName}`));
|
|
72
|
+
if (existingConfig.boardId) {
|
|
73
|
+
this.log(colors.textMuted(` Board: ${existingConfig.boardName ?? 'Unknown'} (${existingConfig.boardId})`));
|
|
74
|
+
}
|
|
75
|
+
this.log('');
|
|
76
|
+
this.log(colors.textMuted('Use --force to re-authenticate.'));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// Stored token is invalid, proceed with re-authentication.
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
let apiToken = getMondayApiToken(db);
|
|
84
|
+
if (!apiToken) {
|
|
85
|
+
if (jsonMode) {
|
|
86
|
+
outputErrorAsJson('API_TOKEN_REQUIRED', 'Monday API token required. Set MONDAY_API_TOKEN or PRLT_MONDAY_API_TOKEN, or run interactively.', createMetadata('monday connect', flags));
|
|
87
|
+
this.exit(1);
|
|
88
|
+
}
|
|
89
|
+
const answers = await inquirer.prompt([{
|
|
90
|
+
type: 'password',
|
|
91
|
+
name: 'token',
|
|
92
|
+
message: 'Enter your Monday API token:',
|
|
93
|
+
mask: '*',
|
|
94
|
+
validate: (value) => value.trim().length > 0 || 'API token is required',
|
|
95
|
+
}]);
|
|
96
|
+
apiToken = answers.token;
|
|
97
|
+
}
|
|
98
|
+
if (!apiToken) {
|
|
99
|
+
this.error('Monday API token is required.');
|
|
100
|
+
}
|
|
101
|
+
const client = new MondayClient(apiToken);
|
|
102
|
+
let boardId = flags.board ?? getMondayBoardId(db);
|
|
103
|
+
if (!boardId && !jsonMode) {
|
|
104
|
+
const answers = await inquirer.prompt([{
|
|
105
|
+
type: 'input',
|
|
106
|
+
name: 'boardId',
|
|
107
|
+
message: 'Enter Monday board ID for ticket sync:',
|
|
108
|
+
validate: (value) => value.trim().length > 0 || 'Board ID is required',
|
|
109
|
+
}]);
|
|
110
|
+
boardId = answers.boardId;
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
const info = await client.verify();
|
|
114
|
+
saveMondayApiToken(db, apiToken);
|
|
115
|
+
saveMondayAccountName(db, info.accountName);
|
|
116
|
+
let boardName = null;
|
|
117
|
+
if (boardId) {
|
|
118
|
+
const board = await client.getBoard(boardId);
|
|
119
|
+
if (!board) {
|
|
120
|
+
if (jsonMode) {
|
|
121
|
+
outputErrorAsJson('BOARD_NOT_FOUND', `Monday board ${boardId} was not found or is inaccessible.`, createMetadata('monday connect', flags));
|
|
122
|
+
this.exit(1);
|
|
123
|
+
}
|
|
124
|
+
this.error(`Monday board ${boardId} was not found or is inaccessible.`);
|
|
125
|
+
}
|
|
126
|
+
boardName = board.name;
|
|
127
|
+
saveMondayBoard(db, board.id, board.name);
|
|
128
|
+
boardId = board.id;
|
|
129
|
+
}
|
|
130
|
+
if (jsonMode) {
|
|
131
|
+
outputSuccessAsJson({
|
|
132
|
+
authenticated: true,
|
|
133
|
+
account: info.accountName,
|
|
134
|
+
user: info.userName,
|
|
135
|
+
email: info.email ?? null,
|
|
136
|
+
boardId: boardId ?? null,
|
|
137
|
+
boardName,
|
|
138
|
+
}, createMetadata('monday connect', flags));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
this.log(colors.success(`Connected to Monday account: ${info.accountName}`));
|
|
142
|
+
this.log(colors.textMuted(` Signed in as ${info.userName}${info.email ? ` (${info.email})` : ''}`));
|
|
143
|
+
if (boardId) {
|
|
144
|
+
this.log(colors.textMuted(` Default board: ${boardName ?? 'Unknown'} (${boardId})`));
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
this.log(colors.warning('No board selected yet.'));
|
|
148
|
+
this.log(colors.textMuted(' Run "prlt monday connect --board <board-id>" to set one.'));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
153
|
+
if (jsonMode) {
|
|
154
|
+
outputErrorAsJson('MONDAY_AUTH_FAILED', `Authentication failed: ${message}`, createMetadata('monday connect', flags));
|
|
155
|
+
this.exit(1);
|
|
156
|
+
}
|
|
157
|
+
this.error(`Authentication failed: ${message}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async checkConnection(flags, jsonMode) {
|
|
161
|
+
const db = this.storage.getDatabase();
|
|
162
|
+
if (!isMondayConfigured(db)) {
|
|
163
|
+
if (jsonMode) {
|
|
164
|
+
outputSuccessAsJson({
|
|
165
|
+
configured: false,
|
|
166
|
+
message: 'Monday is not configured. Run "prlt monday connect" first.',
|
|
167
|
+
}, createMetadata('monday connect', flags));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
this.log(colors.warning('Monday is not configured.'));
|
|
171
|
+
this.log(colors.textMuted('Run "prlt monday connect" first.'));
|
|
172
|
+
this.exit(1);
|
|
173
|
+
}
|
|
174
|
+
const config = loadMondayConfig(db);
|
|
175
|
+
try {
|
|
176
|
+
const client = new MondayClient(config.apiToken);
|
|
177
|
+
const info = await client.verify();
|
|
178
|
+
let board = null;
|
|
179
|
+
if (config.boardId) {
|
|
180
|
+
const fetched = await client.getBoard(config.boardId);
|
|
181
|
+
board = fetched ? { id: fetched.id, name: fetched.name } : null;
|
|
182
|
+
}
|
|
183
|
+
if (jsonMode) {
|
|
184
|
+
outputSuccessAsJson({
|
|
185
|
+
configured: true,
|
|
186
|
+
connected: true,
|
|
187
|
+
account: info.accountName,
|
|
188
|
+
user: info.userName,
|
|
189
|
+
boardId: config.boardId ?? null,
|
|
190
|
+
boardName: board?.name ?? config.boardName ?? null,
|
|
191
|
+
}, createMetadata('monday connect', flags));
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
this.log(colors.success('Monday connection is active'));
|
|
195
|
+
this.log(colors.textMuted(` Account: ${info.accountName}`));
|
|
196
|
+
this.log(colors.textMuted(` User: ${info.userName}`));
|
|
197
|
+
if (config.boardId) {
|
|
198
|
+
this.log(colors.textMuted(` Board: ${board?.name ?? config.boardName ?? 'Unknown'} (${config.boardId})`));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
203
|
+
if (jsonMode) {
|
|
204
|
+
outputErrorAsJson('MONDAY_AUTH_INVALID', `Stored Monday token is invalid or expired: ${message}`, createMetadata('monday connect', flags));
|
|
205
|
+
this.exit(1);
|
|
206
|
+
}
|
|
207
|
+
this.log(colors.error('Stored Monday token is invalid or expired.'));
|
|
208
|
+
this.log(colors.textMuted('Run "prlt monday connect --force" to re-authenticate.'));
|
|
209
|
+
this.exit(1);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { PMOCommand } from '../../lib/pmo/index.js';
|
|
2
|
+
export default class MondaySyncCommand extends PMOCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
ticket: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
};
|
|
12
|
+
execute(): Promise<void>;
|
|
13
|
+
private syncSingleTicket;
|
|
14
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
3
|
+
import { colors } from '../../lib/colors.js';
|
|
4
|
+
import { shouldOutputJson, outputSuccessAsJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
5
|
+
import { MondayClient, MondayMapper, MondaySync, isMondayConfigured, loadMondayConfig, } from '../../lib/monday/index.js';
|
|
6
|
+
export default class MondaySyncCommand extends PMOCommand {
|
|
7
|
+
static description = 'Sync PMO tickets to Monday.com board items';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> <%= command.id %> # Sync project tickets to Monday board',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> --ticket TKT-001 # Sync one ticket',
|
|
11
|
+
'<%= config.bin %> <%= command.id %> --dry-run # Preview sync operations',
|
|
12
|
+
];
|
|
13
|
+
static flags = {
|
|
14
|
+
...pmoBaseFlags,
|
|
15
|
+
ticket: Flags.string({
|
|
16
|
+
description: 'PMO ticket ID to sync',
|
|
17
|
+
}),
|
|
18
|
+
'dry-run': Flags.boolean({
|
|
19
|
+
description: 'Preview what would be synced without writing to Monday',
|
|
20
|
+
default: false,
|
|
21
|
+
}),
|
|
22
|
+
};
|
|
23
|
+
async execute() {
|
|
24
|
+
const { flags } = await this.parse(MondaySyncCommand);
|
|
25
|
+
const jsonMode = shouldOutputJson(flags);
|
|
26
|
+
const db = this.storage.getDatabase();
|
|
27
|
+
if (!isMondayConfigured(db)) {
|
|
28
|
+
if (jsonMode) {
|
|
29
|
+
outputErrorAsJson('MONDAY_NOT_CONFIGURED', 'Monday is not configured. Run "prlt monday connect" first.', createMetadata('monday sync', flags));
|
|
30
|
+
this.exit(1);
|
|
31
|
+
}
|
|
32
|
+
this.error('Monday is not configured. Run "prlt monday connect" first.');
|
|
33
|
+
}
|
|
34
|
+
const config = loadMondayConfig(db);
|
|
35
|
+
if (!config.boardId) {
|
|
36
|
+
if (jsonMode) {
|
|
37
|
+
outputErrorAsJson('MONDAY_BOARD_REQUIRED', 'No Monday board configured. Run "prlt monday connect --board <id>" first.', createMetadata('monday sync', flags));
|
|
38
|
+
this.exit(1);
|
|
39
|
+
}
|
|
40
|
+
this.error('No Monday board configured. Run "prlt monday connect --board <id>" first.');
|
|
41
|
+
}
|
|
42
|
+
const client = new MondayClient(config.apiToken);
|
|
43
|
+
const mapper = new MondayMapper(db);
|
|
44
|
+
const sync = new MondaySync(client, mapper);
|
|
45
|
+
if (flags.ticket) {
|
|
46
|
+
await this.syncSingleTicket(flags.ticket, config.boardId, sync, jsonMode, flags);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const projectId = await this.requireProject({
|
|
50
|
+
jsonMode: jsonMode ? {
|
|
51
|
+
flags,
|
|
52
|
+
commandName: 'monday sync',
|
|
53
|
+
baseCommand: `${this.config.bin} monday sync`,
|
|
54
|
+
} : undefined,
|
|
55
|
+
});
|
|
56
|
+
const tickets = await this.storage.listTickets(projectId);
|
|
57
|
+
if (tickets.length === 0) {
|
|
58
|
+
if (jsonMode) {
|
|
59
|
+
outputSuccessAsJson({
|
|
60
|
+
synced: 0,
|
|
61
|
+
created: 0,
|
|
62
|
+
skipped: 0,
|
|
63
|
+
message: `No tickets found for project ${projectId}.`,
|
|
64
|
+
}, createMetadata('monday sync', flags));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
this.log(colors.warning(`No tickets found for project ${projectId}.`));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (flags['dry-run']) {
|
|
71
|
+
if (jsonMode) {
|
|
72
|
+
outputSuccessAsJson({
|
|
73
|
+
dryRun: true,
|
|
74
|
+
action: 'sync-all',
|
|
75
|
+
projectId,
|
|
76
|
+
boardId: config.boardId,
|
|
77
|
+
tickets: tickets.map((ticket) => ({
|
|
78
|
+
ticketId: ticket.id,
|
|
79
|
+
itemName: sync.ticketToItemName(ticket),
|
|
80
|
+
mapped: mapper.getByTicketId(ticket.id) !== null,
|
|
81
|
+
})),
|
|
82
|
+
}, createMetadata('monday sync', flags));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
this.log(colors.textMuted(`Would sync ${tickets.length} ticket(s) from ${projectId} to Monday board ${config.boardId}.`));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
let syncedCount = 0;
|
|
89
|
+
let createdCount = 0;
|
|
90
|
+
const errors = [];
|
|
91
|
+
for (const ticket of tickets) {
|
|
92
|
+
try {
|
|
93
|
+
// eslint-disable-next-line no-await-in-loop
|
|
94
|
+
const result = await sync.syncTicket(ticket, config.boardId);
|
|
95
|
+
syncedCount++;
|
|
96
|
+
if (result.created)
|
|
97
|
+
createdCount++;
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
errors.push({
|
|
101
|
+
ticketId: ticket.id,
|
|
102
|
+
error: error instanceof Error ? error.message : String(error),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (jsonMode) {
|
|
107
|
+
outputSuccessAsJson({
|
|
108
|
+
action: 'sync-all',
|
|
109
|
+
projectId,
|
|
110
|
+
boardId: config.boardId,
|
|
111
|
+
synced: syncedCount,
|
|
112
|
+
created: createdCount,
|
|
113
|
+
skipped: tickets.length - syncedCount,
|
|
114
|
+
errors,
|
|
115
|
+
}, createMetadata('monday sync', flags));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
this.log(colors.success(`Synced ${syncedCount}/${tickets.length} ticket(s) to Monday`));
|
|
119
|
+
this.log(colors.textMuted(` Created new items: ${createdCount}`));
|
|
120
|
+
if (errors.length > 0) {
|
|
121
|
+
this.log(colors.warning(` Errors: ${errors.length}`));
|
|
122
|
+
for (const error of errors.slice(0, 5)) {
|
|
123
|
+
this.log(colors.textMuted(` ${error.ticketId}: ${error.error}`));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async syncSingleTicket(ticketId, boardId, sync, jsonMode, flags) {
|
|
128
|
+
const ticket = await this.storage.getTicket(ticketId);
|
|
129
|
+
if (!ticket) {
|
|
130
|
+
if (jsonMode) {
|
|
131
|
+
outputErrorAsJson('TICKET_NOT_FOUND', `Ticket ${ticketId} not found.`, createMetadata('monday sync', flags));
|
|
132
|
+
this.exit(1);
|
|
133
|
+
}
|
|
134
|
+
this.error(`Ticket ${ticketId} not found.`);
|
|
135
|
+
}
|
|
136
|
+
if (flags['dry-run']) {
|
|
137
|
+
if (jsonMode) {
|
|
138
|
+
outputSuccessAsJson({
|
|
139
|
+
dryRun: true,
|
|
140
|
+
action: 'sync-ticket',
|
|
141
|
+
ticketId,
|
|
142
|
+
boardId,
|
|
143
|
+
itemName: sync.ticketToItemName(ticket),
|
|
144
|
+
}, createMetadata('monday sync', flags));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
this.log(colors.textMuted(`Would sync ${ticketId} to Monday board ${boardId}.`));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
const result = await sync.syncTicket(ticket, boardId);
|
|
152
|
+
if (jsonMode) {
|
|
153
|
+
outputSuccessAsJson({
|
|
154
|
+
action: 'sync-ticket',
|
|
155
|
+
ticketId,
|
|
156
|
+
boardId,
|
|
157
|
+
itemId: result.itemId,
|
|
158
|
+
created: result.created,
|
|
159
|
+
}, createMetadata('monday sync', flags));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (result.created) {
|
|
163
|
+
this.log(colors.success(`Created Monday item for ${ticketId}`));
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
this.log(colors.success(`Updated Monday item for ${ticketId}`));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
171
|
+
if (jsonMode) {
|
|
172
|
+
outputErrorAsJson('MONDAY_SYNC_FAILED', `Failed to sync ${ticketId}: ${message}`, createMetadata('monday sync', flags));
|
|
173
|
+
this.exit(1);
|
|
174
|
+
}
|
|
175
|
+
this.error(`Failed to sync ${ticketId}: ${message}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -10,6 +10,12 @@ export declare function buildOrchestratorSessionName(hqName: string, name?: stri
|
|
|
10
10
|
* Returns all tmux session names that start with 'prlt-orchestrator-'.
|
|
11
11
|
*/
|
|
12
12
|
export declare function findRunningOrchestratorSessions(hostSessions: string[]): string[];
|
|
13
|
+
export declare function resolveOrchestratorName(name?: string): string;
|
|
14
|
+
export declare function buildOrchestratorAttachCommand(name: string): string;
|
|
15
|
+
export declare function extractOrchestratorNameFromSession(sessionName: string, hqName: string): string | null;
|
|
16
|
+
export declare function collectReservedOrchestratorNames(agentNames: string[], hostSessions: string[], hqName: string): Set<string>;
|
|
17
|
+
export declare function buildAvailableOrchestratorNames(reserved: Set<string>, maxNames?: number): string[];
|
|
18
|
+
export declare function findGlobalOrchestratorNameConflict(name: string, reserved: Set<string>): string | null;
|
|
13
19
|
export default class OrchestratorStart extends PromptCommand {
|
|
14
20
|
static description: string;
|
|
15
21
|
static examples: string[];
|
|
@@ -9,9 +9,10 @@ import { getHeadquartersNameFromPath } from '../../lib/machine-config.js';
|
|
|
9
9
|
import { shouldOutputJson, outputErrorAsJson, outputSuccessAsJson, createMetadata, buildPromptConfig, outputPromptAsJson, } from '../../lib/prompt-json.js';
|
|
10
10
|
import { styles } from '../../lib/styles.js';
|
|
11
11
|
import { DEFAULT_EXECUTION_CONFIG, } from '../../lib/execution/types.js';
|
|
12
|
-
import { runExecution } from '../../lib/execution/runners.js';
|
|
12
|
+
import { runExecution, hostCredentialsExist } from '../../lib/execution/runners.js';
|
|
13
13
|
import { getHostTmuxSessionNames } from '../../lib/execution/session-utils.js';
|
|
14
14
|
import { ExecutionStorage } from '../../lib/execution/storage.js';
|
|
15
|
+
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
15
16
|
import { loadExecutionConfig, getTerminalApp, getShell, detectShell, detectTerminalApp, } from '../../lib/execution/config.js';
|
|
16
17
|
/**
|
|
17
18
|
* Sanitize a name segment for use in tmux session names.
|
|
@@ -39,6 +40,68 @@ export function buildOrchestratorSessionName(hqName, name = 'main') {
|
|
|
39
40
|
export function findRunningOrchestratorSessions(hostSessions) {
|
|
40
41
|
return hostSessions.filter(s => s.startsWith('prlt-orchestrator-'));
|
|
41
42
|
}
|
|
43
|
+
export function resolveOrchestratorName(name) {
|
|
44
|
+
const normalized = name?.trim();
|
|
45
|
+
return normalized && normalized.length > 0 ? normalized : 'main';
|
|
46
|
+
}
|
|
47
|
+
export function buildOrchestratorAttachCommand(name) {
|
|
48
|
+
return name === 'main'
|
|
49
|
+
? 'prlt orchestrator attach'
|
|
50
|
+
: `prlt orchestrator attach --name ${name}`;
|
|
51
|
+
}
|
|
52
|
+
export function extractOrchestratorNameFromSession(sessionName, hqName) {
|
|
53
|
+
const prefix = `prlt-orchestrator-${sanitizeName(hqName) || 'default'}-`;
|
|
54
|
+
if (!sessionName.startsWith(prefix)) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const extracted = sessionName.slice(prefix.length);
|
|
58
|
+
return extracted.length > 0 ? extracted : null;
|
|
59
|
+
}
|
|
60
|
+
export function collectReservedOrchestratorNames(agentNames, hostSessions, hqName) {
|
|
61
|
+
const reserved = new Set();
|
|
62
|
+
for (const agentName of agentNames) {
|
|
63
|
+
const normalized = resolveOrchestratorName(sanitizeName(agentName).toLowerCase());
|
|
64
|
+
reserved.add(normalized);
|
|
65
|
+
}
|
|
66
|
+
for (const session of hostSessions) {
|
|
67
|
+
const extracted = extractOrchestratorNameFromSession(session, hqName);
|
|
68
|
+
if (!extracted)
|
|
69
|
+
continue;
|
|
70
|
+
reserved.add(resolveOrchestratorName(extracted.toLowerCase()));
|
|
71
|
+
}
|
|
72
|
+
return reserved;
|
|
73
|
+
}
|
|
74
|
+
function nextAvailableName(baseName, reserved) {
|
|
75
|
+
const normalizedBase = resolveOrchestratorName(baseName.toLowerCase());
|
|
76
|
+
if (!reserved.has(normalizedBase)) {
|
|
77
|
+
return normalizedBase;
|
|
78
|
+
}
|
|
79
|
+
let suffix = 2;
|
|
80
|
+
while (reserved.has(`${normalizedBase}-${suffix}`)) {
|
|
81
|
+
suffix += 1;
|
|
82
|
+
}
|
|
83
|
+
return `${normalizedBase}-${suffix}`;
|
|
84
|
+
}
|
|
85
|
+
export function buildAvailableOrchestratorNames(reserved, maxNames = 8) {
|
|
86
|
+
const orderedBases = ['main', ...Array.from(reserved).sort()];
|
|
87
|
+
const suggestions = [];
|
|
88
|
+
const taken = new Set(reserved);
|
|
89
|
+
for (const base of orderedBases) {
|
|
90
|
+
const candidate = nextAvailableName(base, taken);
|
|
91
|
+
if (suggestions.includes(candidate))
|
|
92
|
+
continue;
|
|
93
|
+
suggestions.push(candidate);
|
|
94
|
+
taken.add(candidate);
|
|
95
|
+
if (suggestions.length >= maxNames) {
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return suggestions;
|
|
100
|
+
}
|
|
101
|
+
export function findGlobalOrchestratorNameConflict(name, reserved) {
|
|
102
|
+
const normalized = resolveOrchestratorName(sanitizeName(name).toLowerCase());
|
|
103
|
+
return reserved.has(normalized) ? normalized : null;
|
|
104
|
+
}
|
|
42
105
|
export default class OrchestratorStart extends PromptCommand {
|
|
43
106
|
static description = 'Start the orchestrator agent in a tmux session';
|
|
44
107
|
static examples = [
|
|
@@ -93,7 +156,6 @@ export default class OrchestratorStart extends PromptCommand {
|
|
|
93
156
|
async run() {
|
|
94
157
|
const { flags } = await this.parse(OrchestratorStart);
|
|
95
158
|
const jsonMode = shouldOutputJson(flags);
|
|
96
|
-
const orchestratorName = flags.name || 'main';
|
|
97
159
|
// Resolve HQ path first (needed for scoped session name)
|
|
98
160
|
const hqPath = findHQRoot(process.cwd());
|
|
99
161
|
if (!hqPath) {
|
|
@@ -103,26 +165,68 @@ export default class OrchestratorStart extends PromptCommand {
|
|
|
103
165
|
}
|
|
104
166
|
this.error('Not in an HQ workspace. Run "prlt init" first.');
|
|
105
167
|
}
|
|
106
|
-
//
|
|
168
|
+
// Resolve orchestrator name (interactive prompt when --name is omitted)
|
|
169
|
+
const workspaceInfo = getWorkspaceInfo();
|
|
107
170
|
const hqName = getHeadquartersNameFromPath(hqPath);
|
|
171
|
+
const hostSessions = getHostTmuxSessionNames();
|
|
172
|
+
const reservedAgentNames = new Set(workspaceInfo.agents.map(agent => resolveOrchestratorName(sanitizeName(agent.name).toLowerCase())));
|
|
173
|
+
const reservedNames = collectReservedOrchestratorNames(workspaceInfo.agents.map(agent => agent.name), hostSessions, hqName);
|
|
174
|
+
let orchestratorName = resolveOrchestratorName(flags.name);
|
|
175
|
+
if (!flags.name && !jsonMode) {
|
|
176
|
+
const availableNames = buildAvailableOrchestratorNames(reservedNames);
|
|
177
|
+
const { selectedName } = await this.prompt([{
|
|
178
|
+
type: 'list',
|
|
179
|
+
name: 'selectedName',
|
|
180
|
+
message: 'Select orchestrator name:',
|
|
181
|
+
choices: [
|
|
182
|
+
...availableNames.map(name => ({
|
|
183
|
+
name,
|
|
184
|
+
value: name,
|
|
185
|
+
command: `prlt orchestrator start --name ${name} --json`,
|
|
186
|
+
})),
|
|
187
|
+
{ name: 'Custom...', value: '__custom__' },
|
|
188
|
+
],
|
|
189
|
+
}]);
|
|
190
|
+
if (selectedName === '__custom__') {
|
|
191
|
+
const defaultCustomName = availableNames[0] || 'main';
|
|
192
|
+
const { customName } = await this.prompt([{
|
|
193
|
+
type: 'input',
|
|
194
|
+
name: 'customName',
|
|
195
|
+
message: 'Enter orchestrator name:',
|
|
196
|
+
default: defaultCustomName,
|
|
197
|
+
validate: (input) => {
|
|
198
|
+
const normalized = resolveOrchestratorName(sanitizeName(String(input ?? '')).toLowerCase());
|
|
199
|
+
if (reservedNames.has(normalized)) {
|
|
200
|
+
return `Name "${normalized}" is already in use by an agent or orchestrator session.`;
|
|
201
|
+
}
|
|
202
|
+
return true;
|
|
203
|
+
},
|
|
204
|
+
}]);
|
|
205
|
+
orchestratorName = resolveOrchestratorName(customName);
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
orchestratorName = resolveOrchestratorName(selectedName);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const attachCommand = buildOrchestratorAttachCommand(orchestratorName);
|
|
212
|
+
const attachArgs = orchestratorName === 'main' ? [] : ['--name', orchestratorName];
|
|
213
|
+
// Build session name scoped to this HQ
|
|
108
214
|
const sessionName = buildOrchestratorSessionName(hqName, orchestratorName);
|
|
109
215
|
// Check if orchestrator is already running
|
|
110
|
-
const hostSessions = getHostTmuxSessionNames();
|
|
111
216
|
if (hostSessions.includes(sessionName)) {
|
|
112
217
|
if (jsonMode) {
|
|
113
|
-
outputErrorAsJson('ALREADY_RUNNING', `Orchestrator is already running (session: ${sessionName}). Use "
|
|
218
|
+
outputErrorAsJson('ALREADY_RUNNING', `Orchestrator is already running (session: ${sessionName}). Use "${attachCommand}" to reattach.`, createMetadata('orchestrator start', flags));
|
|
114
219
|
return;
|
|
115
220
|
}
|
|
116
221
|
this.log('');
|
|
117
222
|
this.log(styles.warning(`Orchestrator is already running (session: ${sessionName})`));
|
|
118
223
|
this.log('');
|
|
119
|
-
const attachArgs = flags.name ? ['--name', flags.name] : [];
|
|
120
224
|
const { choice } = await this.prompt([{
|
|
121
225
|
type: 'list',
|
|
122
226
|
name: 'choice',
|
|
123
227
|
message: 'What would you like to do?',
|
|
124
228
|
choices: [
|
|
125
|
-
{ name: 'Attach to running orchestrator', value: 'attach', command:
|
|
229
|
+
{ name: 'Attach to running orchestrator', value: 'attach', command: `${attachCommand} --json` },
|
|
126
230
|
{ name: 'Cancel', value: 'cancel' },
|
|
127
231
|
],
|
|
128
232
|
}], jsonMode ? { flags, commandName: 'orchestrator start' } : null);
|
|
@@ -131,6 +235,14 @@ export default class OrchestratorStart extends PromptCommand {
|
|
|
131
235
|
}
|
|
132
236
|
return;
|
|
133
237
|
}
|
|
238
|
+
const conflict = findGlobalOrchestratorNameConflict(orchestratorName, reservedAgentNames);
|
|
239
|
+
if (conflict) {
|
|
240
|
+
if (jsonMode) {
|
|
241
|
+
outputErrorAsJson('NAME_CONFLICT', `Orchestrator name "${conflict}" is already in use by a staff/temp agent. Choose a unique name with --name.`, createMetadata('orchestrator start', flags));
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
this.error(`Orchestrator name "${conflict}" is already in use by a staff/temp agent. Choose a different name.`);
|
|
245
|
+
}
|
|
134
246
|
// Executor selection
|
|
135
247
|
let selectedExecutor;
|
|
136
248
|
if (flags.executor) {
|
|
@@ -156,6 +268,32 @@ export default class OrchestratorStart extends PromptCommand {
|
|
|
156
268
|
}]);
|
|
157
269
|
selectedExecutor = executor;
|
|
158
270
|
}
|
|
271
|
+
// Validate Claude Code authentication for claude-code executor
|
|
272
|
+
if (selectedExecutor === 'claude-code' && !hostCredentialsExist()) {
|
|
273
|
+
const errorMsg = 'Claude Code authentication is not available. This usually happens when the macOS keychain is locked in SSH sessions.';
|
|
274
|
+
const remediation = [
|
|
275
|
+
'',
|
|
276
|
+
'To fix this, choose one of the following:',
|
|
277
|
+
'',
|
|
278
|
+
'1. Unlock the keychain:',
|
|
279
|
+
' security unlock-keychain',
|
|
280
|
+
'',
|
|
281
|
+
'2. Set the ANTHROPIC_API_KEY environment variable:',
|
|
282
|
+
' export ANTHROPIC_API_KEY=your-api-key',
|
|
283
|
+
'',
|
|
284
|
+
'3. Login to Claude Code:',
|
|
285
|
+
' claude /login',
|
|
286
|
+
'',
|
|
287
|
+
].join('\n');
|
|
288
|
+
if (jsonMode) {
|
|
289
|
+
outputErrorAsJson('AUTH_UNAVAILABLE', errorMsg + '\n' + remediation, createMetadata('orchestrator start', flags));
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
this.log('');
|
|
293
|
+
this.log(styles.error(errorMsg));
|
|
294
|
+
this.log(remediation);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
159
297
|
// Permission mode selection
|
|
160
298
|
let permissionMode;
|
|
161
299
|
if (flags['skip-permissions']) {
|
|
@@ -261,9 +399,9 @@ export default class OrchestratorStart extends PromptCommand {
|
|
|
261
399
|
}
|
|
262
400
|
else {
|
|
263
401
|
const displayChoices = [
|
|
264
|
-
{ name: 'New terminal tab — opens attached to the tmux session', value: 'terminal', command: `prlt orchestrator start${
|
|
265
|
-
{ name: 'Current session — attach to tmux here (foreground, blocking)', value: 'foreground', command: `prlt orchestrator start${
|
|
266
|
-
{ name: 'Background — start detached, attach later', value: 'background', command: `prlt orchestrator start${
|
|
402
|
+
{ name: 'New terminal tab — opens attached to the tmux session', value: 'terminal', command: `prlt orchestrator start${orchestratorName !== 'main' ? ` --name ${orchestratorName}` : ''} --json` },
|
|
403
|
+
{ name: 'Current session — attach to tmux here (foreground, blocking)', value: 'foreground', command: `prlt orchestrator start${orchestratorName !== 'main' ? ` --name ${orchestratorName}` : ''} --foreground --json` },
|
|
404
|
+
{ name: 'Background — start detached, attach later', value: 'background', command: `prlt orchestrator start${orchestratorName !== 'main' ? ` --name ${orchestratorName}` : ''} --background --json` },
|
|
267
405
|
];
|
|
268
406
|
const displayMessage = 'How do you want to view the orchestrator?';
|
|
269
407
|
if (jsonMode) {
|
|
@@ -346,7 +484,7 @@ export default class OrchestratorStart extends PromptCommand {
|
|
|
346
484
|
if (displayMode === 'background') {
|
|
347
485
|
this.log(styles.success(`Orchestrator started in background`));
|
|
348
486
|
this.log(styles.muted(` Session: ${result.sessionId || sessionName}`));
|
|
349
|
-
this.log(styles.muted(` Attach with:
|
|
487
|
+
this.log(styles.muted(` Attach with: ${attachCommand}`));
|
|
350
488
|
}
|
|
351
489
|
else {
|
|
352
490
|
this.log(styles.success(`Orchestrator started`));
|