@proletariat/cli 0.3.44 → 0.3.46
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/list.js +2 -3
- package/dist/commands/agent/login.js +2 -2
- package/dist/commands/agent/rebuild.js +2 -3
- package/dist/commands/agent/shell.js +2 -2
- package/dist/commands/agent/status.js +3 -3
- package/dist/commands/agent/visit.js +2 -2
- package/dist/commands/config/index.js +39 -1
- package/dist/commands/linear/auth.d.ts +14 -0
- package/dist/commands/linear/auth.js +211 -0
- package/dist/commands/linear/import.d.ts +21 -0
- package/dist/commands/linear/import.js +260 -0
- package/dist/commands/linear/status.d.ts +11 -0
- package/dist/commands/linear/status.js +88 -0
- package/dist/commands/linear/sync.d.ts +15 -0
- package/dist/commands/linear/sync.js +233 -0
- package/dist/commands/orchestrator/attach.d.ts +9 -1
- package/dist/commands/orchestrator/attach.js +67 -13
- package/dist/commands/orchestrator/index.js +22 -7
- package/dist/commands/staff/list.js +2 -3
- package/dist/commands/ticket/link/duplicates.d.ts +15 -0
- package/dist/commands/ticket/link/duplicates.js +95 -0
- package/dist/commands/ticket/link/index.js +14 -0
- package/dist/commands/ticket/link/relates.d.ts +15 -0
- package/dist/commands/ticket/link/relates.js +95 -0
- package/dist/commands/work/revise.js +7 -6
- package/dist/commands/work/spawn.d.ts +5 -0
- package/dist/commands/work/spawn.js +195 -14
- package/dist/commands/work/start.js +79 -23
- package/dist/commands/work/watch.js +2 -2
- package/dist/lib/agents/commands.d.ts +11 -0
- package/dist/lib/agents/commands.js +40 -10
- package/dist/lib/execution/config.d.ts +15 -0
- package/dist/lib/execution/config.js +54 -0
- package/dist/lib/execution/devcontainer.d.ts +6 -3
- package/dist/lib/execution/devcontainer.js +39 -12
- package/dist/lib/execution/runners.d.ts +28 -32
- package/dist/lib/execution/runners.js +345 -271
- package/dist/lib/execution/spawner.js +65 -7
- package/dist/lib/execution/types.d.ts +4 -0
- package/dist/lib/execution/types.js +3 -0
- package/dist/lib/external-issues/adapters.d.ts +26 -0
- package/dist/lib/external-issues/adapters.js +251 -0
- package/dist/lib/external-issues/index.d.ts +10 -0
- package/dist/lib/external-issues/index.js +14 -0
- package/dist/lib/external-issues/mapper.d.ts +21 -0
- package/dist/lib/external-issues/mapper.js +86 -0
- package/dist/lib/external-issues/types.d.ts +144 -0
- package/dist/lib/external-issues/types.js +26 -0
- package/dist/lib/external-issues/validation.d.ts +34 -0
- package/dist/lib/external-issues/validation.js +219 -0
- package/dist/lib/linear/client.d.ts +55 -0
- package/dist/lib/linear/client.js +254 -0
- package/dist/lib/linear/config.d.ts +37 -0
- package/dist/lib/linear/config.js +100 -0
- package/dist/lib/linear/index.d.ts +11 -0
- package/dist/lib/linear/index.js +10 -0
- package/dist/lib/linear/mapper.d.ts +67 -0
- package/dist/lib/linear/mapper.js +219 -0
- package/dist/lib/linear/sync.d.ts +37 -0
- package/dist/lib/linear/sync.js +89 -0
- package/dist/lib/linear/types.d.ts +139 -0
- package/dist/lib/linear/types.js +34 -0
- package/dist/lib/mcp/helpers.d.ts +8 -0
- package/dist/lib/mcp/helpers.js +10 -0
- package/dist/lib/mcp/tools/board.js +63 -11
- package/dist/lib/pmo/schema.d.ts +2 -0
- package/dist/lib/pmo/schema.js +20 -0
- package/dist/lib/pmo/storage/base.js +92 -13
- package/dist/lib/pmo/storage/dependencies.js +15 -0
- package/dist/lib/prompt-json.d.ts +4 -0
- package/dist/lib/themes.js +32 -16
- package/oclif.manifest.json +2823 -2336
- package/package.json +2 -1
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
-
import * as path from 'node:path';
|
|
4
3
|
import * as fs from 'node:fs';
|
|
5
|
-
import { getWorkspaceInfo, getAllAgentsStatus, getAgentTmuxSessions } from '../../lib/agents/commands.js';
|
|
4
|
+
import { getWorkspaceInfo, getAllAgentsStatus, getAgentTmuxSessions, resolveAgentDir } from '../../lib/agents/commands.js';
|
|
6
5
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
7
6
|
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
8
7
|
export default class List extends PMOCommand {
|
|
@@ -139,7 +138,7 @@ export default class List extends PMOCommand {
|
|
|
139
138
|
}
|
|
140
139
|
}
|
|
141
140
|
else {
|
|
142
|
-
const agentDir =
|
|
141
|
+
const agentDir = resolveAgentDir(workspaceInfo, agentStatus.name);
|
|
143
142
|
const dirExists = fs.existsSync(agentDir);
|
|
144
143
|
if (dirExists) {
|
|
145
144
|
this.log(chalk.red(` Invalid or broken worktrees`));
|
|
@@ -3,7 +3,7 @@ import * as path from 'node:path';
|
|
|
3
3
|
import * as fs from 'node:fs';
|
|
4
4
|
import { execSync } from 'node:child_process';
|
|
5
5
|
import { colors } from '../../lib/colors.js';
|
|
6
|
-
import { getWorkspaceInfo, formatAgentList } from '../../lib/agents/commands.js';
|
|
6
|
+
import { getWorkspaceInfo, formatAgentList, resolveAgentDir } from '../../lib/agents/commands.js';
|
|
7
7
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
8
8
|
import { isDockerRunning, getAgentContainerName, isContainerRunning, getContainerId, } from '../../lib/execution/runners.js';
|
|
9
9
|
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
@@ -74,7 +74,7 @@ export default class Login extends PMOCommand {
|
|
|
74
74
|
if (!agent) {
|
|
75
75
|
this.error(`Agent "${agentName}" not found. Available: ${formatAgentList(workspaceInfo.agents)}`);
|
|
76
76
|
}
|
|
77
|
-
const agentDir =
|
|
77
|
+
const agentDir = resolveAgentDir(workspaceInfo, agentName);
|
|
78
78
|
// Check if Docker config exists
|
|
79
79
|
const dockerfilePath = path.join(agentDir, '.devcontainer', 'Dockerfile');
|
|
80
80
|
if (!fs.existsSync(dockerfilePath)) {
|
|
@@ -4,7 +4,7 @@ import { promisify } from 'node:util';
|
|
|
4
4
|
import * as path from 'node:path';
|
|
5
5
|
import * as fs from 'node:fs';
|
|
6
6
|
import { colors } from '../../lib/colors.js';
|
|
7
|
-
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
7
|
+
import { getWorkspaceInfo, resolveAgentDir } from '../../lib/agents/commands.js';
|
|
8
8
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
9
9
|
import { isDockerRunning } from '../../lib/execution/runners.js';
|
|
10
10
|
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
@@ -76,8 +76,7 @@ export default class AgentRebuild extends PMOCommand {
|
|
|
76
76
|
this.error('Not in a proletariat workspace. Run `prlt init` first.');
|
|
77
77
|
}
|
|
78
78
|
this.log(colors.primary(`🔨 Rebuilding agent: ${agentName}\n`));
|
|
79
|
-
const
|
|
80
|
-
const agentDir = path.join(agentsPath, agentName);
|
|
79
|
+
const agentDir = resolveAgentDir(workspaceInfo, agentName);
|
|
81
80
|
try {
|
|
82
81
|
this.log(colors.textSecondary(' Building devcontainer...'));
|
|
83
82
|
const buildCommand = [
|
|
@@ -4,7 +4,7 @@ import * as fs from 'node:fs';
|
|
|
4
4
|
import { execSync, spawn } from 'node:child_process';
|
|
5
5
|
import Database from 'better-sqlite3';
|
|
6
6
|
import { colors } from '../../lib/colors.js';
|
|
7
|
-
import { getWorkspaceInfo, getAgentTmuxSessions, formatAgentList } from '../../lib/agents/commands.js';
|
|
7
|
+
import { getWorkspaceInfo, getAgentTmuxSessions, formatAgentList, resolveAgentDir } from '../../lib/agents/commands.js';
|
|
8
8
|
import { hasDevcontainerConfig } from '../../lib/execution/devcontainer.js';
|
|
9
9
|
import { getTerminalApp } from '../../lib/execution/config.js';
|
|
10
10
|
import { isDockerRunning, getAgentContainerName, isContainerRunning, getContainerId, } from '../../lib/execution/runners.js';
|
|
@@ -110,7 +110,7 @@ export default class Shell extends PMOCommand {
|
|
|
110
110
|
// If 'continue', proceed with opening a new shell
|
|
111
111
|
this.log(colors.warning('\nProceeding with new shell - be careful of conflicts!\n'));
|
|
112
112
|
}
|
|
113
|
-
const agentDir =
|
|
113
|
+
const agentDir = resolveAgentDir(workspaceInfo, agentName);
|
|
114
114
|
// Check if agent has devcontainer
|
|
115
115
|
const hasDevcontainer = hasDevcontainerConfig(agentDir);
|
|
116
116
|
// In JSON mode with agent name provided, output combined config prompt
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Args } from '@oclif/core';
|
|
2
2
|
import { colors, format } from '../../lib/colors.js';
|
|
3
|
-
import { getWorkspaceInfo, getAgentStatus, getAllAgentsStatus, formatAgentList } from '../../lib/agents/commands.js';
|
|
3
|
+
import { getWorkspaceInfo, getAgentStatus, getAllAgentsStatus, formatAgentList, resolveAgentDir } from '../../lib/agents/commands.js';
|
|
4
4
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
5
5
|
import { shouldOutputJson, outputErrorAsJson, outputSuccessAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
6
6
|
export default class Status extends PMOCommand {
|
|
@@ -83,7 +83,7 @@ export default class Status extends PMOCommand {
|
|
|
83
83
|
name: agentName,
|
|
84
84
|
type: agent.type,
|
|
85
85
|
exists: agentStatus.exists,
|
|
86
|
-
path:
|
|
86
|
+
path: resolveAgentDir(workspaceInfo, agentName),
|
|
87
87
|
branch: agentStatus.branch,
|
|
88
88
|
repositories: agentStatus.repositories.map(r => ({
|
|
89
89
|
name: r.name,
|
|
@@ -107,7 +107,7 @@ export default class Status extends PMOCommand {
|
|
|
107
107
|
return;
|
|
108
108
|
}
|
|
109
109
|
// Location
|
|
110
|
-
this.log(`📍 Location: ${colors.path(
|
|
110
|
+
this.log(`📍 Location: ${colors.path(resolveAgentDir(workspaceInfo, agentName))}`);
|
|
111
111
|
// Branch info
|
|
112
112
|
if (agentStatus.branch) {
|
|
113
113
|
this.log(`🌿 Branch: ${colors.warning(agentStatus.branch)}`);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Args } from '@oclif/core';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { colors } from '../../lib/colors.js';
|
|
4
|
-
import { getWorkspaceInfo, formatAgentList } from '../../lib/agents/commands.js';
|
|
4
|
+
import { getWorkspaceInfo, formatAgentList, resolveAgentDir } from '../../lib/agents/commands.js';
|
|
5
5
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
6
6
|
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
7
7
|
export default class Visit extends PMOCommand {
|
|
@@ -74,7 +74,7 @@ export default class Visit extends PMOCommand {
|
|
|
74
74
|
return handleError('AGENT_NOT_FOUND', `Agent "${agentName}" not found. Available: ${formatAgentList(workspaceInfo.agents)}`);
|
|
75
75
|
}
|
|
76
76
|
// Calculate path to agent directory
|
|
77
|
-
const agentDir =
|
|
77
|
+
const agentDir = resolveAgentDir(workspaceInfo, agentName);
|
|
78
78
|
const relativePath = path.relative(process.cwd(), agentDir);
|
|
79
79
|
// Display navigation command
|
|
80
80
|
this.log(colors.primary(`🤖 Visiting agent: ${agentName}`));
|
|
@@ -6,7 +6,7 @@ import { PromptCommand } from '../../lib/prompt-command.js';
|
|
|
6
6
|
import { machineOutputFlags } from '../../lib/pmo/index.js';
|
|
7
7
|
import { styles } from '../../lib/styles.js';
|
|
8
8
|
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
9
|
-
import { loadExecutionConfig, saveTerminalApp, saveTerminalOpenInBackground, saveTmuxControlMode, saveShell, } from '../../lib/execution/config.js';
|
|
9
|
+
import { loadExecutionConfig, saveTerminalApp, saveTerminalOpenInBackground, saveTmuxControlMode, saveShell, saveCreatePrDefault, saveFirewallAllowlistDomains, } from '../../lib/execution/config.js';
|
|
10
10
|
import { shouldOutputJson, isNonTTY, outputSuccessAsJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
11
11
|
export default class Config extends PromptCommand {
|
|
12
12
|
static description = 'View and update workspace configuration';
|
|
@@ -15,6 +15,7 @@ export default class Config extends PromptCommand {
|
|
|
15
15
|
'<%= config.bin %> <%= command.id %> --json # Output current config as JSON',
|
|
16
16
|
'<%= config.bin %> <%= command.id %> --set terminal.app iTerm',
|
|
17
17
|
'<%= config.bin %> <%= command.id %> --set terminal.openInBackground true',
|
|
18
|
+
'<%= config.bin %> <%= command.id %> --set firewall.allowlistDomains "api.staging.example.com"',
|
|
18
19
|
'<%= config.bin %> <%= command.id %> --setting terminal.app --json # Show terminal app choices',
|
|
19
20
|
];
|
|
20
21
|
static flags = {
|
|
@@ -93,6 +94,10 @@ export default class Config extends PromptCommand {
|
|
|
93
94
|
defaultEnvironment: config.defaultEnvironment,
|
|
94
95
|
outputMode: config.outputMode,
|
|
95
96
|
sandboxed: config.sandboxed,
|
|
97
|
+
createPrDefault: config.createPrDefault ?? null,
|
|
98
|
+
firewall: {
|
|
99
|
+
allowlistDomains: config.firewall.allowlistDomains,
|
|
100
|
+
},
|
|
96
101
|
}, createMetadata('config', flags));
|
|
97
102
|
}
|
|
98
103
|
else {
|
|
@@ -115,6 +120,8 @@ export default class Config extends PromptCommand {
|
|
|
115
120
|
this.log(` defaultEnvironment: ${config.defaultEnvironment}`);
|
|
116
121
|
this.log(` outputMode: ${config.outputMode}`);
|
|
117
122
|
this.log(` sandboxed: ${config.sandboxed}`);
|
|
123
|
+
this.log(` createPrDefault: ${config.createPrDefault ?? 'not set (will prompt)'}`);
|
|
124
|
+
this.log(` firewall.allowlistDomains: ${config.firewall.allowlistDomains.join(', ') || '(none)'}`);
|
|
118
125
|
this.log('');
|
|
119
126
|
}
|
|
120
127
|
db.close();
|
|
@@ -132,6 +139,7 @@ export default class Config extends PromptCommand {
|
|
|
132
139
|
{ name: `Open Tabs in Background: ${config.terminal.openInBackground}`, value: 'terminal.openInBackground', command: 'prlt config --setting terminal.openInBackground --json' },
|
|
133
140
|
{ name: `Shell: ${config.shell}`, value: 'shell', command: 'prlt config --setting shell --json' },
|
|
134
141
|
{ name: `Tmux Control Mode (iTerm -CC): ${config.tmux.controlMode}`, value: 'tmux.controlMode', command: 'prlt config --setting tmux.controlMode --json' },
|
|
142
|
+
{ name: `Firewall allowlist domains: ${config.firewall.allowlistDomains.length || 0}`, value: 'firewall.allowlistDomains', command: 'prlt config --setting firewall.allowlistDomains --json' },
|
|
135
143
|
];
|
|
136
144
|
const { setting } = await this.prompt([
|
|
137
145
|
{
|
|
@@ -145,6 +153,8 @@ export default class Config extends PromptCommand {
|
|
|
145
153
|
settingChoices[2],
|
|
146
154
|
new inquirer.Separator('── Tmux ──'),
|
|
147
155
|
settingChoices[3],
|
|
156
|
+
new inquirer.Separator('── Execution ──'),
|
|
157
|
+
settingChoices[4],
|
|
148
158
|
new inquirer.Separator(),
|
|
149
159
|
{ name: 'Exit', value: '__exit__' },
|
|
150
160
|
],
|
|
@@ -247,6 +257,23 @@ export default class Config extends PromptCommand {
|
|
|
247
257
|
this.log(styles.success(`Tmux control mode set to: ${controlMode}`));
|
|
248
258
|
break;
|
|
249
259
|
}
|
|
260
|
+
case 'firewall.allowlistDomains': {
|
|
261
|
+
const { domainsInput } = await this.prompt([
|
|
262
|
+
{
|
|
263
|
+
type: 'input',
|
|
264
|
+
name: 'domainsInput',
|
|
265
|
+
message: 'Extra firewall allowlist domains (comma-separated, leave empty to clear):',
|
|
266
|
+
default: config.firewall.allowlistDomains.join(', '),
|
|
267
|
+
},
|
|
268
|
+
], jsonModeConfig);
|
|
269
|
+
const domains = domainsInput
|
|
270
|
+
.split(',')
|
|
271
|
+
.map(domain => domain.trim())
|
|
272
|
+
.filter(Boolean);
|
|
273
|
+
saveFirewallAllowlistDomains(db, domains);
|
|
274
|
+
this.log(styles.success(`Firewall allowlist domains set (${domains.length})`));
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
250
277
|
default: {
|
|
251
278
|
const jsonMode = shouldOutputJson(jsonModeConfig?.flags ?? {});
|
|
252
279
|
if (jsonMode) {
|
|
@@ -271,6 +298,17 @@ export default class Config extends PromptCommand {
|
|
|
271
298
|
case 'tmux.controlmode':
|
|
272
299
|
saveTmuxControlMode(db, value.toLowerCase() === 'true');
|
|
273
300
|
break;
|
|
301
|
+
case 'execution.create_pr_default':
|
|
302
|
+
saveCreatePrDefault(db, value.toLowerCase() === 'true');
|
|
303
|
+
break;
|
|
304
|
+
case 'firewall.allowlistdomains': {
|
|
305
|
+
const domains = value
|
|
306
|
+
.split(',')
|
|
307
|
+
.map(domain => domain.trim())
|
|
308
|
+
.filter(Boolean);
|
|
309
|
+
saveFirewallAllowlistDomains(db, domains);
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
274
312
|
default:
|
|
275
313
|
if (jsonMode) {
|
|
276
314
|
outputErrorAsJson('UNKNOWN_KEY', `Unknown config key: ${key}`, createMetadata('config', {}));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { PMOCommand } from '../../lib/pmo/index.js';
|
|
2
|
+
export default class LinearAuth extends PMOCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
check: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
disconnect: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
};
|
|
13
|
+
execute(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
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 { LinearClient, loadLinearConfig, saveLinearApiKey, saveLinearDefaultTeam, saveLinearOrganization, clearLinearConfig, getLinearApiKey, } from '../../lib/linear/index.js';
|
|
7
|
+
export default class LinearAuth extends PMOCommand {
|
|
8
|
+
static description = 'Authenticate with Linear and configure workspace connection';
|
|
9
|
+
static examples = [
|
|
10
|
+
'<%= config.bin %> <%= command.id %>',
|
|
11
|
+
'<%= config.bin %> <%= command.id %> --check',
|
|
12
|
+
'<%= config.bin %> <%= command.id %> --force',
|
|
13
|
+
'LINEAR_API_KEY=lin_api_... <%= config.bin %> <%= command.id %>',
|
|
14
|
+
];
|
|
15
|
+
static flags = {
|
|
16
|
+
...pmoBaseFlags,
|
|
17
|
+
check: Flags.boolean({
|
|
18
|
+
description: 'Only check if Linear credentials exist (do not prompt)',
|
|
19
|
+
default: false,
|
|
20
|
+
}),
|
|
21
|
+
force: Flags.boolean({
|
|
22
|
+
description: 'Force re-authentication even if credentials exist',
|
|
23
|
+
default: false,
|
|
24
|
+
}),
|
|
25
|
+
disconnect: Flags.boolean({
|
|
26
|
+
description: 'Remove stored Linear credentials',
|
|
27
|
+
default: false,
|
|
28
|
+
}),
|
|
29
|
+
};
|
|
30
|
+
async execute() {
|
|
31
|
+
const { flags } = await this.parse(LinearAuth);
|
|
32
|
+
const jsonMode = shouldOutputJson(flags);
|
|
33
|
+
const db = this.storage.getDatabase();
|
|
34
|
+
// Handle --disconnect
|
|
35
|
+
if (flags.disconnect) {
|
|
36
|
+
clearLinearConfig(db);
|
|
37
|
+
if (jsonMode) {
|
|
38
|
+
outputSuccessAsJson({
|
|
39
|
+
disconnected: true,
|
|
40
|
+
message: 'Linear credentials removed.',
|
|
41
|
+
}, createMetadata('linear auth', flags));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
this.log(colors.success('Linear credentials removed.'));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
// Handle --check
|
|
48
|
+
if (flags.check) {
|
|
49
|
+
const config = loadLinearConfig(db);
|
|
50
|
+
if (config) {
|
|
51
|
+
// Try to verify the connection
|
|
52
|
+
try {
|
|
53
|
+
const client = new LinearClient(config.apiKey);
|
|
54
|
+
const info = await client.verify();
|
|
55
|
+
if (jsonMode) {
|
|
56
|
+
outputSuccessAsJson({
|
|
57
|
+
authenticated: true,
|
|
58
|
+
organization: info.organizationName,
|
|
59
|
+
user: info.userName,
|
|
60
|
+
email: info.email,
|
|
61
|
+
defaultTeam: config.defaultTeamKey ?? null,
|
|
62
|
+
}, createMetadata('linear auth', flags));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
this.log(colors.success('Linear connection is active'));
|
|
66
|
+
this.log(colors.textMuted(` Organization: ${info.organizationName}`));
|
|
67
|
+
this.log(colors.textMuted(` User: ${info.userName} (${info.email})`));
|
|
68
|
+
if (config.defaultTeamKey) {
|
|
69
|
+
this.log(colors.textMuted(` Default team: ${config.defaultTeamKey}`));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
if (jsonMode) {
|
|
74
|
+
outputErrorAsJson('LINEAR_AUTH_INVALID', 'Stored Linear API key is invalid or expired.', createMetadata('linear auth', flags));
|
|
75
|
+
this.exit(1);
|
|
76
|
+
}
|
|
77
|
+
this.log(colors.error('Stored Linear API key is invalid or expired.'));
|
|
78
|
+
this.log(colors.textMuted('Run "prlt linear auth --force" to re-authenticate.'));
|
|
79
|
+
this.exit(1);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
if (jsonMode) {
|
|
84
|
+
outputErrorAsJson('LINEAR_NOT_CONFIGURED', 'Linear is not configured. Run "prlt linear auth" to authenticate.', createMetadata('linear auth', flags));
|
|
85
|
+
this.exit(1);
|
|
86
|
+
}
|
|
87
|
+
this.log(colors.warning('Linear is not configured.'));
|
|
88
|
+
this.log(colors.textMuted('Run "prlt linear auth" to authenticate.'));
|
|
89
|
+
this.exit(1);
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// Check for existing config
|
|
94
|
+
const existingConfig = loadLinearConfig(db);
|
|
95
|
+
if (existingConfig && !flags.force) {
|
|
96
|
+
try {
|
|
97
|
+
const client = new LinearClient(existingConfig.apiKey);
|
|
98
|
+
const info = await client.verify();
|
|
99
|
+
if (jsonMode) {
|
|
100
|
+
outputSuccessAsJson({
|
|
101
|
+
authenticated: true,
|
|
102
|
+
organization: info.organizationName,
|
|
103
|
+
user: info.userName,
|
|
104
|
+
message: 'Already authenticated. Use --force to re-authenticate.',
|
|
105
|
+
}, createMetadata('linear auth', flags));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
this.log(colors.success('Already connected to Linear'));
|
|
109
|
+
this.log(colors.textMuted(` Organization: ${info.organizationName}`));
|
|
110
|
+
this.log(colors.textMuted(` User: ${info.userName}`));
|
|
111
|
+
this.log('');
|
|
112
|
+
this.log(colors.textMuted('Use --force to re-authenticate.'));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// Stored key is bad, proceed with re-auth
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Try environment variable first
|
|
120
|
+
const envKey = getLinearApiKey(db);
|
|
121
|
+
let apiKey = envKey;
|
|
122
|
+
if (!apiKey) {
|
|
123
|
+
// Prompt for API key
|
|
124
|
+
if (jsonMode) {
|
|
125
|
+
outputErrorAsJson('API_KEY_REQUIRED', 'Linear API key required. Set LINEAR_API_KEY or PRLT_LINEAR_API_KEY environment variable, or run interactively.', createMetadata('linear auth', flags));
|
|
126
|
+
this.exit(1);
|
|
127
|
+
}
|
|
128
|
+
this.log('');
|
|
129
|
+
this.log(colors.primary('Linear Authentication'));
|
|
130
|
+
this.log('');
|
|
131
|
+
this.log('Create a personal API key at:');
|
|
132
|
+
this.log(colors.textSecondary(' https://linear.app/settings/api'));
|
|
133
|
+
this.log('');
|
|
134
|
+
const { inputKey } = await inquirer.prompt([{
|
|
135
|
+
type: 'password',
|
|
136
|
+
name: 'inputKey',
|
|
137
|
+
message: 'Enter your Linear API key:',
|
|
138
|
+
mask: '*',
|
|
139
|
+
validate: (input) => {
|
|
140
|
+
if (!input.trim())
|
|
141
|
+
return 'API key is required';
|
|
142
|
+
if (!input.startsWith('lin_api_'))
|
|
143
|
+
return 'Linear API keys start with "lin_api_"';
|
|
144
|
+
return true;
|
|
145
|
+
},
|
|
146
|
+
}]);
|
|
147
|
+
apiKey = inputKey;
|
|
148
|
+
}
|
|
149
|
+
// Verify the API key
|
|
150
|
+
this.log('');
|
|
151
|
+
this.log(colors.textMuted('Verifying API key...'));
|
|
152
|
+
let client;
|
|
153
|
+
try {
|
|
154
|
+
client = new LinearClient(apiKey);
|
|
155
|
+
const info = await client.verify();
|
|
156
|
+
// Save API key and org name
|
|
157
|
+
saveLinearApiKey(db, apiKey);
|
|
158
|
+
saveLinearOrganization(db, info.organizationName);
|
|
159
|
+
if (jsonMode) {
|
|
160
|
+
// Don't prompt for team in JSON mode, just save the key
|
|
161
|
+
outputSuccessAsJson({
|
|
162
|
+
authenticated: true,
|
|
163
|
+
organization: info.organizationName,
|
|
164
|
+
user: info.userName,
|
|
165
|
+
email: info.email,
|
|
166
|
+
}, createMetadata('linear auth', flags));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
this.log(colors.success(`Connected to ${info.organizationName}`));
|
|
170
|
+
this.log(colors.textMuted(` Signed in as ${info.userName} (${info.email})`));
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
if (jsonMode) {
|
|
174
|
+
outputErrorAsJson('LINEAR_AUTH_FAILED', `Authentication failed: ${error instanceof Error ? error.message : String(error)}`, createMetadata('linear auth', flags));
|
|
175
|
+
this.exit(1);
|
|
176
|
+
}
|
|
177
|
+
this.error(`Authentication failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
178
|
+
}
|
|
179
|
+
// Prompt for default team selection
|
|
180
|
+
this.log('');
|
|
181
|
+
const teams = await client.listTeams();
|
|
182
|
+
if (teams.length === 0) {
|
|
183
|
+
this.log(colors.warning('No teams found in your Linear workspace.'));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (teams.length === 1) {
|
|
187
|
+
// Auto-select the only team
|
|
188
|
+
saveLinearDefaultTeam(db, teams[0].id, teams[0].key);
|
|
189
|
+
this.log(colors.textMuted(` Default team set to: ${teams[0].name} (${teams[0].key})`));
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
const teamChoices = teams.map((t) => ({
|
|
193
|
+
name: `${t.name} (${t.key})`,
|
|
194
|
+
value: t.id,
|
|
195
|
+
}));
|
|
196
|
+
const { selectedTeamId } = await inquirer.prompt([{
|
|
197
|
+
type: 'list',
|
|
198
|
+
name: 'selectedTeamId',
|
|
199
|
+
message: 'Select your default team:',
|
|
200
|
+
choices: teamChoices,
|
|
201
|
+
}]);
|
|
202
|
+
const selectedTeam = teams.find((t) => t.id === selectedTeamId);
|
|
203
|
+
saveLinearDefaultTeam(db, selectedTeam.id, selectedTeam.key);
|
|
204
|
+
this.log(colors.textMuted(` Default team set to: ${selectedTeam.name} (${selectedTeam.key})`));
|
|
205
|
+
}
|
|
206
|
+
this.log('');
|
|
207
|
+
this.log(colors.success('Linear integration configured!'));
|
|
208
|
+
this.log(colors.textMuted(' Run "prlt linear import" to pull issues into PMO'));
|
|
209
|
+
this.log(colors.textMuted(' Run "prlt work spawn --from-linear" to pull and spawn agents'));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { PMOCommand } from '../../lib/pmo/index.js';
|
|
2
|
+
export default class LinearImport extends PMOCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static strict: boolean;
|
|
5
|
+
static examples: string[];
|
|
6
|
+
static flags: {
|
|
7
|
+
team: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
state: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
'state-type': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
label: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
assignee: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
cycle: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
all: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
|
+
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
|
+
machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
18
|
+
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
19
|
+
};
|
|
20
|
+
execute(): Promise<void>;
|
|
21
|
+
}
|