@renseiai/agentfactory 0.8.0
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/LICENSE +21 -0
- package/README.md +125 -0
- package/dist/src/config/index.d.ts +3 -0
- package/dist/src/config/index.d.ts.map +1 -0
- package/dist/src/config/index.js +1 -0
- package/dist/src/config/repository-config.d.ts +44 -0
- package/dist/src/config/repository-config.d.ts.map +1 -0
- package/dist/src/config/repository-config.js +88 -0
- package/dist/src/config/repository-config.test.d.ts +2 -0
- package/dist/src/config/repository-config.test.d.ts.map +1 -0
- package/dist/src/config/repository-config.test.js +249 -0
- package/dist/src/deployment/deployment-checker.d.ts +110 -0
- package/dist/src/deployment/deployment-checker.d.ts.map +1 -0
- package/dist/src/deployment/deployment-checker.js +242 -0
- package/dist/src/deployment/index.d.ts +3 -0
- package/dist/src/deployment/index.d.ts.map +1 -0
- package/dist/src/deployment/index.js +2 -0
- package/dist/src/frontend/index.d.ts +2 -0
- package/dist/src/frontend/index.d.ts.map +1 -0
- package/dist/src/frontend/index.js +1 -0
- package/dist/src/frontend/types.d.ts +106 -0
- package/dist/src/frontend/types.d.ts.map +1 -0
- package/dist/src/frontend/types.js +11 -0
- package/dist/src/governor/decision-engine.d.ts +52 -0
- package/dist/src/governor/decision-engine.d.ts.map +1 -0
- package/dist/src/governor/decision-engine.js +220 -0
- package/dist/src/governor/decision-engine.test.d.ts +2 -0
- package/dist/src/governor/decision-engine.test.d.ts.map +1 -0
- package/dist/src/governor/decision-engine.test.js +629 -0
- package/dist/src/governor/event-bus.d.ts +43 -0
- package/dist/src/governor/event-bus.d.ts.map +1 -0
- package/dist/src/governor/event-bus.js +8 -0
- package/dist/src/governor/event-deduplicator.d.ts +43 -0
- package/dist/src/governor/event-deduplicator.d.ts.map +1 -0
- package/dist/src/governor/event-deduplicator.js +53 -0
- package/dist/src/governor/event-driven-governor.d.ts +131 -0
- package/dist/src/governor/event-driven-governor.d.ts.map +1 -0
- package/dist/src/governor/event-driven-governor.js +379 -0
- package/dist/src/governor/event-driven-governor.test.d.ts +2 -0
- package/dist/src/governor/event-driven-governor.test.d.ts.map +1 -0
- package/dist/src/governor/event-driven-governor.test.js +673 -0
- package/dist/src/governor/event-types.d.ts +78 -0
- package/dist/src/governor/event-types.d.ts.map +1 -0
- package/dist/src/governor/event-types.js +32 -0
- package/dist/src/governor/governor-types.d.ts +82 -0
- package/dist/src/governor/governor-types.d.ts.map +1 -0
- package/dist/src/governor/governor-types.js +21 -0
- package/dist/src/governor/governor.d.ts +100 -0
- package/dist/src/governor/governor.d.ts.map +1 -0
- package/dist/src/governor/governor.js +262 -0
- package/dist/src/governor/governor.test.d.ts +2 -0
- package/dist/src/governor/governor.test.d.ts.map +1 -0
- package/dist/src/governor/governor.test.js +514 -0
- package/dist/src/governor/human-touchpoints.d.ts +131 -0
- package/dist/src/governor/human-touchpoints.d.ts.map +1 -0
- package/dist/src/governor/human-touchpoints.js +251 -0
- package/dist/src/governor/human-touchpoints.test.d.ts +2 -0
- package/dist/src/governor/human-touchpoints.test.d.ts.map +1 -0
- package/dist/src/governor/human-touchpoints.test.js +366 -0
- package/dist/src/governor/in-memory-event-bus.d.ts +29 -0
- package/dist/src/governor/in-memory-event-bus.d.ts.map +1 -0
- package/dist/src/governor/in-memory-event-bus.js +79 -0
- package/dist/src/governor/index.d.ts +14 -0
- package/dist/src/governor/index.d.ts.map +1 -0
- package/dist/src/governor/index.js +13 -0
- package/dist/src/governor/override-parser.d.ts +60 -0
- package/dist/src/governor/override-parser.d.ts.map +1 -0
- package/dist/src/governor/override-parser.js +98 -0
- package/dist/src/governor/override-parser.test.d.ts +2 -0
- package/dist/src/governor/override-parser.test.d.ts.map +1 -0
- package/dist/src/governor/override-parser.test.js +312 -0
- package/dist/src/governor/platform-adapter.d.ts +69 -0
- package/dist/src/governor/platform-adapter.d.ts.map +1 -0
- package/dist/src/governor/platform-adapter.js +11 -0
- package/dist/src/governor/processing-state.d.ts +66 -0
- package/dist/src/governor/processing-state.d.ts.map +1 -0
- package/dist/src/governor/processing-state.js +43 -0
- package/dist/src/governor/processing-state.test.d.ts +2 -0
- package/dist/src/governor/processing-state.test.d.ts.map +1 -0
- package/dist/src/governor/processing-state.test.js +96 -0
- package/dist/src/governor/top-of-funnel.d.ts +118 -0
- package/dist/src/governor/top-of-funnel.d.ts.map +1 -0
- package/dist/src/governor/top-of-funnel.js +168 -0
- package/dist/src/governor/top-of-funnel.test.d.ts +2 -0
- package/dist/src/governor/top-of-funnel.test.d.ts.map +1 -0
- package/dist/src/governor/top-of-funnel.test.js +331 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +10 -0
- package/dist/src/linear-cli.d.ts +38 -0
- package/dist/src/linear-cli.d.ts.map +1 -0
- package/dist/src/linear-cli.js +674 -0
- package/dist/src/logger.d.ts +117 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +430 -0
- package/dist/src/manifest/generate.d.ts +20 -0
- package/dist/src/manifest/generate.d.ts.map +1 -0
- package/dist/src/manifest/generate.js +65 -0
- package/dist/src/manifest/index.d.ts +4 -0
- package/dist/src/manifest/index.d.ts.map +1 -0
- package/dist/src/manifest/index.js +2 -0
- package/dist/src/manifest/route-manifest.d.ts +34 -0
- package/dist/src/manifest/route-manifest.d.ts.map +1 -0
- package/dist/src/manifest/route-manifest.js +148 -0
- package/dist/src/orchestrator/activity-emitter.d.ts +119 -0
- package/dist/src/orchestrator/activity-emitter.d.ts.map +1 -0
- package/dist/src/orchestrator/activity-emitter.js +306 -0
- package/dist/src/orchestrator/api-activity-emitter.d.ts +167 -0
- package/dist/src/orchestrator/api-activity-emitter.d.ts.map +1 -0
- package/dist/src/orchestrator/api-activity-emitter.js +417 -0
- package/dist/src/orchestrator/heartbeat-writer.d.ts +57 -0
- package/dist/src/orchestrator/heartbeat-writer.d.ts.map +1 -0
- package/dist/src/orchestrator/heartbeat-writer.js +137 -0
- package/dist/src/orchestrator/index.d.ts +20 -0
- package/dist/src/orchestrator/index.d.ts.map +1 -0
- package/dist/src/orchestrator/index.js +22 -0
- package/dist/src/orchestrator/log-analyzer.d.ts +160 -0
- package/dist/src/orchestrator/log-analyzer.d.ts.map +1 -0
- package/dist/src/orchestrator/log-analyzer.js +572 -0
- package/dist/src/orchestrator/log-config.d.ts +39 -0
- package/dist/src/orchestrator/log-config.d.ts.map +1 -0
- package/dist/src/orchestrator/log-config.js +45 -0
- package/dist/src/orchestrator/orchestrator.d.ts +316 -0
- package/dist/src/orchestrator/orchestrator.d.ts.map +1 -0
- package/dist/src/orchestrator/orchestrator.js +3290 -0
- package/dist/src/orchestrator/parse-work-result.d.ts +16 -0
- package/dist/src/orchestrator/parse-work-result.d.ts.map +1 -0
- package/dist/src/orchestrator/parse-work-result.js +135 -0
- package/dist/src/orchestrator/parse-work-result.test.d.ts +2 -0
- package/dist/src/orchestrator/parse-work-result.test.d.ts.map +1 -0
- package/dist/src/orchestrator/parse-work-result.test.js +234 -0
- package/dist/src/orchestrator/progress-logger.d.ts +72 -0
- package/dist/src/orchestrator/progress-logger.d.ts.map +1 -0
- package/dist/src/orchestrator/progress-logger.js +135 -0
- package/dist/src/orchestrator/session-logger.d.ts +159 -0
- package/dist/src/orchestrator/session-logger.d.ts.map +1 -0
- package/dist/src/orchestrator/session-logger.js +275 -0
- package/dist/src/orchestrator/state-recovery.d.ts +96 -0
- package/dist/src/orchestrator/state-recovery.d.ts.map +1 -0
- package/dist/src/orchestrator/state-recovery.js +302 -0
- package/dist/src/orchestrator/state-types.d.ts +165 -0
- package/dist/src/orchestrator/state-types.d.ts.map +1 -0
- package/dist/src/orchestrator/state-types.js +7 -0
- package/dist/src/orchestrator/stream-parser.d.ts +151 -0
- package/dist/src/orchestrator/stream-parser.d.ts.map +1 -0
- package/dist/src/orchestrator/stream-parser.js +137 -0
- package/dist/src/orchestrator/types.d.ts +232 -0
- package/dist/src/orchestrator/types.d.ts.map +1 -0
- package/dist/src/orchestrator/types.js +4 -0
- package/dist/src/orchestrator/validate-git-remote.test.d.ts +2 -0
- package/dist/src/orchestrator/validate-git-remote.test.d.ts.map +1 -0
- package/dist/src/orchestrator/validate-git-remote.test.js +61 -0
- package/dist/src/providers/a2a-auth.d.ts +81 -0
- package/dist/src/providers/a2a-auth.d.ts.map +1 -0
- package/dist/src/providers/a2a-auth.js +188 -0
- package/dist/src/providers/a2a-auth.test.d.ts +2 -0
- package/dist/src/providers/a2a-auth.test.d.ts.map +1 -0
- package/dist/src/providers/a2a-auth.test.js +232 -0
- package/dist/src/providers/a2a-provider.d.ts +254 -0
- package/dist/src/providers/a2a-provider.d.ts.map +1 -0
- package/dist/src/providers/a2a-provider.integration.test.d.ts +9 -0
- package/dist/src/providers/a2a-provider.integration.test.d.ts.map +1 -0
- package/dist/src/providers/a2a-provider.integration.test.js +665 -0
- package/dist/src/providers/a2a-provider.js +811 -0
- package/dist/src/providers/a2a-provider.test.d.ts +2 -0
- package/dist/src/providers/a2a-provider.test.d.ts.map +1 -0
- package/dist/src/providers/a2a-provider.test.js +681 -0
- package/dist/src/providers/amp-provider.d.ts +20 -0
- package/dist/src/providers/amp-provider.d.ts.map +1 -0
- package/dist/src/providers/amp-provider.js +24 -0
- package/dist/src/providers/claude-provider.d.ts +18 -0
- package/dist/src/providers/claude-provider.d.ts.map +1 -0
- package/dist/src/providers/claude-provider.js +437 -0
- package/dist/src/providers/codex-provider.d.ts +133 -0
- package/dist/src/providers/codex-provider.d.ts.map +1 -0
- package/dist/src/providers/codex-provider.js +381 -0
- package/dist/src/providers/codex-provider.test.d.ts +2 -0
- package/dist/src/providers/codex-provider.test.d.ts.map +1 -0
- package/dist/src/providers/codex-provider.test.js +387 -0
- package/dist/src/providers/index.d.ts +44 -0
- package/dist/src/providers/index.d.ts.map +1 -0
- package/dist/src/providers/index.js +85 -0
- package/dist/src/providers/spring-ai-provider.d.ts +90 -0
- package/dist/src/providers/spring-ai-provider.d.ts.map +1 -0
- package/dist/src/providers/spring-ai-provider.integration.test.d.ts +13 -0
- package/dist/src/providers/spring-ai-provider.integration.test.d.ts.map +1 -0
- package/dist/src/providers/spring-ai-provider.integration.test.js +351 -0
- package/dist/src/providers/spring-ai-provider.js +317 -0
- package/dist/src/providers/spring-ai-provider.test.d.ts +2 -0
- package/dist/src/providers/spring-ai-provider.test.d.ts.map +1 -0
- package/dist/src/providers/spring-ai-provider.test.js +200 -0
- package/dist/src/providers/types.d.ts +165 -0
- package/dist/src/providers/types.d.ts.map +1 -0
- package/dist/src/providers/types.js +13 -0
- package/dist/src/templates/adapters.d.ts +51 -0
- package/dist/src/templates/adapters.d.ts.map +1 -0
- package/dist/src/templates/adapters.js +104 -0
- package/dist/src/templates/adapters.test.d.ts +2 -0
- package/dist/src/templates/adapters.test.d.ts.map +1 -0
- package/dist/src/templates/adapters.test.js +165 -0
- package/dist/src/templates/agent-definition.d.ts +85 -0
- package/dist/src/templates/agent-definition.d.ts.map +1 -0
- package/dist/src/templates/agent-definition.js +97 -0
- package/dist/src/templates/agent-definition.test.d.ts +2 -0
- package/dist/src/templates/agent-definition.test.d.ts.map +1 -0
- package/dist/src/templates/agent-definition.test.js +209 -0
- package/dist/src/templates/index.d.ts +14 -0
- package/dist/src/templates/index.d.ts.map +1 -0
- package/dist/src/templates/index.js +11 -0
- package/dist/src/templates/loader.d.ts +41 -0
- package/dist/src/templates/loader.d.ts.map +1 -0
- package/dist/src/templates/loader.js +114 -0
- package/dist/src/templates/registry.d.ts +80 -0
- package/dist/src/templates/registry.d.ts.map +1 -0
- package/dist/src/templates/registry.js +177 -0
- package/dist/src/templates/registry.test.d.ts +2 -0
- package/dist/src/templates/registry.test.d.ts.map +1 -0
- package/dist/src/templates/registry.test.js +198 -0
- package/dist/src/templates/renderer.d.ts +29 -0
- package/dist/src/templates/renderer.d.ts.map +1 -0
- package/dist/src/templates/renderer.js +35 -0
- package/dist/src/templates/strategy-templates.test.d.ts +2 -0
- package/dist/src/templates/strategy-templates.test.d.ts.map +1 -0
- package/dist/src/templates/strategy-templates.test.js +619 -0
- package/dist/src/templates/types.d.ts +233 -0
- package/dist/src/templates/types.d.ts.map +1 -0
- package/dist/src/templates/types.js +127 -0
- package/dist/src/templates/types.test.d.ts +2 -0
- package/dist/src/templates/types.test.d.ts.map +1 -0
- package/dist/src/templates/types.test.js +232 -0
- package/dist/src/tools/index.d.ts +6 -0
- package/dist/src/tools/index.d.ts.map +1 -0
- package/dist/src/tools/index.js +3 -0
- package/dist/src/tools/linear-runner.d.ts +34 -0
- package/dist/src/tools/linear-runner.d.ts.map +1 -0
- package/dist/src/tools/linear-runner.js +700 -0
- package/dist/src/tools/plugins/linear.d.ts +9 -0
- package/dist/src/tools/plugins/linear.d.ts.map +1 -0
- package/dist/src/tools/plugins/linear.js +138 -0
- package/dist/src/tools/registry.d.ts +9 -0
- package/dist/src/tools/registry.d.ts.map +1 -0
- package/dist/src/tools/registry.js +18 -0
- package/dist/src/tools/types.d.ts +18 -0
- package/dist/src/tools/types.d.ts.map +1 -0
- package/dist/src/tools/types.js +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,674 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @deprecated Use `af-linear` from `@renseiai/agentfactory-cli` instead.
|
|
4
|
+
* This file is kept for backwards compatibility. The canonical implementation
|
|
5
|
+
* is in packages/cli/src/lib/linear-runner.ts.
|
|
6
|
+
*
|
|
7
|
+
* Linear CLI - Command-line interface for Linear Agent SDK
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* pnpm af-linear <command> [options]
|
|
11
|
+
*
|
|
12
|
+
* Commands:
|
|
13
|
+
* get-issue <id> Get issue details
|
|
14
|
+
* create-issue Create a new issue
|
|
15
|
+
* update-issue <id> Update an existing issue
|
|
16
|
+
* list-comments <issueId> List comments on an issue
|
|
17
|
+
* create-comment <issueId> Create a comment on an issue
|
|
18
|
+
* list-backlog-issues List backlog issues for a project
|
|
19
|
+
* list-unblocked-backlog List unblocked backlog issues
|
|
20
|
+
* check-blocked <id> Check if an issue is blocked
|
|
21
|
+
* add-relation <id> <id> Create relation between issues
|
|
22
|
+
* list-relations <id> List relations for an issue
|
|
23
|
+
* remove-relation <id> Remove a relation by ID
|
|
24
|
+
* list-sub-issues <id> List sub-issues of a parent issue
|
|
25
|
+
* list-sub-issue-statuses <id> List sub-issue statuses (lightweight)
|
|
26
|
+
* update-sub-issue <id> Update sub-issue status with comment
|
|
27
|
+
* check-deployment <PR> Check Vercel deployment status for a PR
|
|
28
|
+
*
|
|
29
|
+
* Array Values:
|
|
30
|
+
* --labels accepts comma-separated: --labels "Bug,Feature"
|
|
31
|
+
* For values with commas, use JSON: --labels '["Bug", "UI, UX"]'
|
|
32
|
+
* Text fields (--description, --title, --body) preserve commas.
|
|
33
|
+
*
|
|
34
|
+
* Environment:
|
|
35
|
+
* LINEAR_API_KEY Required API key for authentication
|
|
36
|
+
*/
|
|
37
|
+
import { readFileSync } from 'node:fs';
|
|
38
|
+
import 'dotenv/config';
|
|
39
|
+
import { config } from 'dotenv';
|
|
40
|
+
// Load .env.local (higher priority, loaded second so it overrides)
|
|
41
|
+
config({ path: '.env.local', override: true });
|
|
42
|
+
import { createLinearAgentClient } from '@renseiai/agentfactory-linear';
|
|
43
|
+
import { checkPRDeploymentStatus, formatDeploymentStatus, } from './deployment/index.js';
|
|
44
|
+
const LINEAR_API_KEY = process.env.LINEAR_API_KEY;
|
|
45
|
+
// Commands that don't require LINEAR_API_KEY (they use gh CLI instead)
|
|
46
|
+
const NO_API_KEY_COMMANDS = ['check-deployment'];
|
|
47
|
+
function getClient() {
|
|
48
|
+
if (!LINEAR_API_KEY) {
|
|
49
|
+
console.error('Error: LINEAR_API_KEY environment variable is required');
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
return createLinearAgentClient({ apiKey: LINEAR_API_KEY });
|
|
53
|
+
}
|
|
54
|
+
// Lazy-initialized client - only created when needed
|
|
55
|
+
let _client = null;
|
|
56
|
+
function client() {
|
|
57
|
+
if (!_client) {
|
|
58
|
+
_client = getClient();
|
|
59
|
+
}
|
|
60
|
+
return _client;
|
|
61
|
+
}
|
|
62
|
+
// Fields that should be split on commas to create arrays
|
|
63
|
+
const ARRAY_FIELDS = new Set(['labels']);
|
|
64
|
+
function parseArgs(args) {
|
|
65
|
+
const result = {};
|
|
66
|
+
for (let i = 0; i < args.length; i++) {
|
|
67
|
+
const arg = args[i];
|
|
68
|
+
if (arg.startsWith('--')) {
|
|
69
|
+
const key = arg.slice(2);
|
|
70
|
+
const value = args[i + 1];
|
|
71
|
+
if (value && !value.startsWith('--')) {
|
|
72
|
+
// Support JSON array format: --labels '["Bug", "Feature"]'
|
|
73
|
+
if (value.startsWith('[') && value.endsWith(']')) {
|
|
74
|
+
try {
|
|
75
|
+
const parsed = JSON.parse(value);
|
|
76
|
+
if (Array.isArray(parsed)) {
|
|
77
|
+
result[key] = parsed;
|
|
78
|
+
i++;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Not valid JSON, fall through to normal handling
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Only split on comma for known array fields
|
|
87
|
+
if (ARRAY_FIELDS.has(key) && value.includes(',')) {
|
|
88
|
+
result[key] = value.split(',').map((v) => v.trim());
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
result[key] = value;
|
|
92
|
+
}
|
|
93
|
+
i++;
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
result[key] = 'true';
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
function resolveFileArg(value, filePath) {
|
|
103
|
+
if (filePath && typeof filePath === 'string') {
|
|
104
|
+
return readFileSync(filePath, 'utf-8');
|
|
105
|
+
}
|
|
106
|
+
return typeof value === 'string' ? value : undefined;
|
|
107
|
+
}
|
|
108
|
+
async function getIssue(issueId) {
|
|
109
|
+
const issue = await client().getIssue(issueId);
|
|
110
|
+
const state = await issue.state;
|
|
111
|
+
const team = await issue.team;
|
|
112
|
+
const project = await issue.project;
|
|
113
|
+
const labels = await issue.labels();
|
|
114
|
+
console.log(JSON.stringify({
|
|
115
|
+
id: issue.id,
|
|
116
|
+
identifier: issue.identifier,
|
|
117
|
+
title: issue.title,
|
|
118
|
+
description: issue.description,
|
|
119
|
+
url: issue.url,
|
|
120
|
+
status: state?.name,
|
|
121
|
+
team: team?.name,
|
|
122
|
+
project: project?.name,
|
|
123
|
+
labels: labels.nodes.map((l) => l.name),
|
|
124
|
+
createdAt: issue.createdAt,
|
|
125
|
+
updatedAt: issue.updatedAt,
|
|
126
|
+
}, null, 2));
|
|
127
|
+
}
|
|
128
|
+
async function createIssue(options) {
|
|
129
|
+
// Get team ID
|
|
130
|
+
const team = await client().getTeam(options.team);
|
|
131
|
+
// Build create payload
|
|
132
|
+
const createPayload = {
|
|
133
|
+
teamId: team.id,
|
|
134
|
+
title: options.title,
|
|
135
|
+
};
|
|
136
|
+
if (options.description) {
|
|
137
|
+
createPayload.description = options.description;
|
|
138
|
+
}
|
|
139
|
+
if (options.parentId) {
|
|
140
|
+
createPayload.parentId = options.parentId;
|
|
141
|
+
}
|
|
142
|
+
// Get project ID if specified
|
|
143
|
+
if (options.project) {
|
|
144
|
+
const projects = await client().linearClient.projects({
|
|
145
|
+
filter: { name: { eq: options.project } },
|
|
146
|
+
});
|
|
147
|
+
if (projects.nodes.length > 0) {
|
|
148
|
+
createPayload.projectId = projects.nodes[0].id;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Get state ID if specified
|
|
152
|
+
if (options.state) {
|
|
153
|
+
const statuses = await client().getTeamStatuses(team.id);
|
|
154
|
+
const stateId = statuses[options.state];
|
|
155
|
+
if (stateId) {
|
|
156
|
+
createPayload.stateId = stateId;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// Get label IDs if specified
|
|
160
|
+
if (options.labels && options.labels.length > 0) {
|
|
161
|
+
const allLabels = await client().linearClient.issueLabels();
|
|
162
|
+
const labelIds = [];
|
|
163
|
+
for (const labelName of options.labels) {
|
|
164
|
+
const label = allLabels.nodes.find((l) => l.name.toLowerCase() === labelName.toLowerCase());
|
|
165
|
+
if (label) {
|
|
166
|
+
labelIds.push(label.id);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (labelIds.length > 0) {
|
|
170
|
+
createPayload.labelIds = labelIds;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const payload = await client().linearClient.createIssue(createPayload);
|
|
174
|
+
if (!payload.success) {
|
|
175
|
+
console.error('Failed to create issue');
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
const issue = await payload.issue;
|
|
179
|
+
if (!issue) {
|
|
180
|
+
console.error('Issue created but not returned');
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
console.log(JSON.stringify({
|
|
184
|
+
id: issue.id,
|
|
185
|
+
identifier: issue.identifier,
|
|
186
|
+
title: issue.title,
|
|
187
|
+
url: issue.url,
|
|
188
|
+
}, null, 2));
|
|
189
|
+
}
|
|
190
|
+
async function updateIssue(issueId, options) {
|
|
191
|
+
const issue = await client().getIssue(issueId);
|
|
192
|
+
const team = await issue.team;
|
|
193
|
+
const updateData = {};
|
|
194
|
+
if (options.title) {
|
|
195
|
+
updateData.title = options.title;
|
|
196
|
+
}
|
|
197
|
+
if (options.description) {
|
|
198
|
+
updateData.description = options.description;
|
|
199
|
+
}
|
|
200
|
+
// Handle state update
|
|
201
|
+
if (options.state && team) {
|
|
202
|
+
const statuses = await client().getTeamStatuses(team.id);
|
|
203
|
+
const stateId = statuses[options.state];
|
|
204
|
+
if (stateId) {
|
|
205
|
+
updateData.stateId = stateId;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// Handle labels update
|
|
209
|
+
if (options.labels && options.labels.length > 0) {
|
|
210
|
+
const allLabels = await client().linearClient.issueLabels();
|
|
211
|
+
const labelIds = [];
|
|
212
|
+
for (const labelName of options.labels) {
|
|
213
|
+
const label = allLabels.nodes.find((l) => l.name.toLowerCase() === labelName.toLowerCase());
|
|
214
|
+
if (label) {
|
|
215
|
+
labelIds.push(label.id);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
updateData.labelIds = labelIds;
|
|
219
|
+
}
|
|
220
|
+
const updatedIssue = await client().updateIssue(issue.id, updateData);
|
|
221
|
+
const state = await updatedIssue.state;
|
|
222
|
+
console.log(JSON.stringify({
|
|
223
|
+
id: updatedIssue.id,
|
|
224
|
+
identifier: updatedIssue.identifier,
|
|
225
|
+
title: updatedIssue.title,
|
|
226
|
+
status: state?.name,
|
|
227
|
+
url: updatedIssue.url,
|
|
228
|
+
}, null, 2));
|
|
229
|
+
}
|
|
230
|
+
async function listComments(issueId) {
|
|
231
|
+
const comments = await client().getIssueComments(issueId);
|
|
232
|
+
console.log(JSON.stringify(comments.map((c) => ({
|
|
233
|
+
id: c.id,
|
|
234
|
+
body: c.body,
|
|
235
|
+
createdAt: c.createdAt,
|
|
236
|
+
})), null, 2));
|
|
237
|
+
}
|
|
238
|
+
async function createComment(issueId, body) {
|
|
239
|
+
const comment = await client().createComment(issueId, body);
|
|
240
|
+
console.log(JSON.stringify({
|
|
241
|
+
id: comment.id,
|
|
242
|
+
body: comment.body,
|
|
243
|
+
createdAt: comment.createdAt,
|
|
244
|
+
}, null, 2));
|
|
245
|
+
}
|
|
246
|
+
async function addRelation(issueId, relatedIssueId, relationType) {
|
|
247
|
+
const result = await client().createIssueRelation({
|
|
248
|
+
issueId,
|
|
249
|
+
relatedIssueId,
|
|
250
|
+
type: relationType,
|
|
251
|
+
});
|
|
252
|
+
console.log(JSON.stringify({
|
|
253
|
+
success: result.success,
|
|
254
|
+
relationId: result.relationId,
|
|
255
|
+
issueId,
|
|
256
|
+
relatedIssueId,
|
|
257
|
+
type: relationType,
|
|
258
|
+
}, null, 2));
|
|
259
|
+
}
|
|
260
|
+
async function listRelations(issueId) {
|
|
261
|
+
const result = await client().getIssueRelations(issueId);
|
|
262
|
+
console.log(JSON.stringify({
|
|
263
|
+
issueId,
|
|
264
|
+
relations: result.relations.map((r) => ({
|
|
265
|
+
id: r.id,
|
|
266
|
+
type: r.type,
|
|
267
|
+
relatedIssue: r.relatedIssueIdentifier ?? r.relatedIssueId,
|
|
268
|
+
createdAt: r.createdAt,
|
|
269
|
+
})),
|
|
270
|
+
inverseRelations: result.inverseRelations.map((r) => ({
|
|
271
|
+
id: r.id,
|
|
272
|
+
type: r.type,
|
|
273
|
+
sourceIssue: r.issueIdentifier ?? r.issueId,
|
|
274
|
+
createdAt: r.createdAt,
|
|
275
|
+
})),
|
|
276
|
+
}, null, 2));
|
|
277
|
+
}
|
|
278
|
+
async function removeRelation(relationId) {
|
|
279
|
+
const result = await client().deleteIssueRelation(relationId);
|
|
280
|
+
console.log(JSON.stringify({
|
|
281
|
+
success: result.success,
|
|
282
|
+
relationId,
|
|
283
|
+
}, null, 2));
|
|
284
|
+
}
|
|
285
|
+
async function listBacklogIssues(projectName) {
|
|
286
|
+
// Find the project
|
|
287
|
+
const projects = await client().linearClient.projects({
|
|
288
|
+
filter: { name: { eqIgnoreCase: projectName } },
|
|
289
|
+
});
|
|
290
|
+
if (projects.nodes.length === 0) {
|
|
291
|
+
console.error(`Project not found: ${projectName}`);
|
|
292
|
+
process.exit(1);
|
|
293
|
+
}
|
|
294
|
+
const project = projects.nodes[0];
|
|
295
|
+
// Get issues in project with Backlog status
|
|
296
|
+
const issues = await client().linearClient.issues({
|
|
297
|
+
filter: {
|
|
298
|
+
project: { id: { eq: project.id } },
|
|
299
|
+
state: { name: { eqIgnoreCase: 'Backlog' } },
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
const results = [];
|
|
303
|
+
for (const issue of issues.nodes) {
|
|
304
|
+
const state = await issue.state;
|
|
305
|
+
const labels = await issue.labels();
|
|
306
|
+
results.push({
|
|
307
|
+
id: issue.id,
|
|
308
|
+
identifier: issue.identifier,
|
|
309
|
+
title: issue.title,
|
|
310
|
+
description: issue.description,
|
|
311
|
+
url: issue.url,
|
|
312
|
+
priority: issue.priority,
|
|
313
|
+
status: state?.name,
|
|
314
|
+
labels: labels.nodes.map((l) => l.name),
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
// Sort by priority (higher priority = lower number, 0 = no priority goes last)
|
|
318
|
+
results.sort((a, b) => {
|
|
319
|
+
const aPriority = a.priority || 5;
|
|
320
|
+
const bPriority = b.priority || 5;
|
|
321
|
+
return aPriority - bPriority;
|
|
322
|
+
});
|
|
323
|
+
console.log(JSON.stringify(results, null, 2));
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Check if an issue is blocked by any non-Accepted issues
|
|
327
|
+
* Returns the list of blocking issues if blocked, empty array if not blocked
|
|
328
|
+
*/
|
|
329
|
+
async function getBlockingIssues(issueId) {
|
|
330
|
+
const relations = await client().getIssueRelations(issueId);
|
|
331
|
+
const blockingIssues = [];
|
|
332
|
+
// Check inverse relations for "blocks" type - these are issues blocking this one
|
|
333
|
+
for (const relation of relations.inverseRelations) {
|
|
334
|
+
if (relation.type === 'blocks') {
|
|
335
|
+
const blockingIssue = await client().getIssue(relation.issueId);
|
|
336
|
+
const state = await blockingIssue.state;
|
|
337
|
+
const statusName = state?.name ?? 'Unknown';
|
|
338
|
+
// Issue is blocked if the blocking issue is not in Accepted status
|
|
339
|
+
if (statusName !== 'Accepted') {
|
|
340
|
+
blockingIssues.push({
|
|
341
|
+
identifier: blockingIssue.identifier,
|
|
342
|
+
title: blockingIssue.title,
|
|
343
|
+
status: statusName,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return blockingIssues;
|
|
349
|
+
}
|
|
350
|
+
async function listUnblockedBacklogIssues(projectName) {
|
|
351
|
+
// Find the project
|
|
352
|
+
const projects = await client().linearClient.projects({
|
|
353
|
+
filter: { name: { eqIgnoreCase: projectName } },
|
|
354
|
+
});
|
|
355
|
+
if (projects.nodes.length === 0) {
|
|
356
|
+
console.error(`Project not found: ${projectName}`);
|
|
357
|
+
process.exit(1);
|
|
358
|
+
}
|
|
359
|
+
const project = projects.nodes[0];
|
|
360
|
+
// Get issues in project with Backlog status
|
|
361
|
+
const issues = await client().linearClient.issues({
|
|
362
|
+
filter: {
|
|
363
|
+
project: { id: { eq: project.id } },
|
|
364
|
+
state: { name: { eqIgnoreCase: 'Backlog' } },
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
const results = [];
|
|
368
|
+
for (const issue of issues.nodes) {
|
|
369
|
+
// Check if issue is blocked
|
|
370
|
+
const blockingIssues = await getBlockingIssues(issue.id);
|
|
371
|
+
const state = await issue.state;
|
|
372
|
+
const labels = await issue.labels();
|
|
373
|
+
results.push({
|
|
374
|
+
id: issue.id,
|
|
375
|
+
identifier: issue.identifier,
|
|
376
|
+
title: issue.title,
|
|
377
|
+
description: issue.description,
|
|
378
|
+
url: issue.url,
|
|
379
|
+
priority: issue.priority,
|
|
380
|
+
status: state?.name,
|
|
381
|
+
labels: labels.nodes.map((l) => l.name),
|
|
382
|
+
blocked: blockingIssues.length > 0,
|
|
383
|
+
blockedBy: blockingIssues,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
// Filter to only unblocked issues
|
|
387
|
+
const unblockedResults = results.filter((r) => !r.blocked);
|
|
388
|
+
// Sort by priority (higher priority = lower number, 0 = no priority goes last)
|
|
389
|
+
unblockedResults.sort((a, b) => {
|
|
390
|
+
const aPriority = a.priority || 5;
|
|
391
|
+
const bPriority = b.priority || 5;
|
|
392
|
+
return aPriority - bPriority;
|
|
393
|
+
});
|
|
394
|
+
console.log(JSON.stringify(unblockedResults, null, 2));
|
|
395
|
+
}
|
|
396
|
+
async function checkBlocked(issueId) {
|
|
397
|
+
const blockingIssues = await getBlockingIssues(issueId);
|
|
398
|
+
console.log(JSON.stringify({
|
|
399
|
+
issueId,
|
|
400
|
+
blocked: blockingIssues.length > 0,
|
|
401
|
+
blockedBy: blockingIssues,
|
|
402
|
+
}, null, 2));
|
|
403
|
+
}
|
|
404
|
+
async function listSubIssues(issueId) {
|
|
405
|
+
const graph = await client().getSubIssueGraph(issueId);
|
|
406
|
+
console.log(JSON.stringify({
|
|
407
|
+
parentId: graph.parentId,
|
|
408
|
+
parentIdentifier: graph.parentIdentifier,
|
|
409
|
+
subIssueCount: graph.subIssues.length,
|
|
410
|
+
subIssues: graph.subIssues.map((node) => ({
|
|
411
|
+
id: node.issue.id,
|
|
412
|
+
identifier: node.issue.identifier,
|
|
413
|
+
title: node.issue.title,
|
|
414
|
+
status: node.issue.status,
|
|
415
|
+
priority: node.issue.priority,
|
|
416
|
+
labels: node.issue.labels,
|
|
417
|
+
url: node.issue.url,
|
|
418
|
+
blockedBy: node.blockedBy,
|
|
419
|
+
blocks: node.blocks,
|
|
420
|
+
})),
|
|
421
|
+
}, null, 2));
|
|
422
|
+
}
|
|
423
|
+
async function updateSubIssue(issueId, options) {
|
|
424
|
+
const issue = await client().getIssue(issueId);
|
|
425
|
+
if (options.state) {
|
|
426
|
+
await client().updateIssueStatus(issue.id, options.state);
|
|
427
|
+
}
|
|
428
|
+
if (options.comment) {
|
|
429
|
+
await client().createComment(issue.id, options.comment);
|
|
430
|
+
}
|
|
431
|
+
const updatedIssue = await client().getIssue(issueId);
|
|
432
|
+
const state = await updatedIssue.state;
|
|
433
|
+
console.log(JSON.stringify({
|
|
434
|
+
id: updatedIssue.id,
|
|
435
|
+
identifier: updatedIssue.identifier,
|
|
436
|
+
title: updatedIssue.title,
|
|
437
|
+
status: state?.name,
|
|
438
|
+
url: updatedIssue.url,
|
|
439
|
+
}, null, 2));
|
|
440
|
+
}
|
|
441
|
+
async function listSubIssueStatuses(issueId) {
|
|
442
|
+
const statuses = await client().getSubIssueStatuses(issueId);
|
|
443
|
+
console.log(JSON.stringify({
|
|
444
|
+
parentIssue: issueId,
|
|
445
|
+
subIssueCount: statuses.length,
|
|
446
|
+
subIssues: statuses,
|
|
447
|
+
allFinishedOrLater: statuses.every((s) => ['Finished', 'Delivered', 'Accepted', 'Canceled'].includes(s.status)),
|
|
448
|
+
incomplete: statuses.filter((s) => !['Finished', 'Delivered', 'Accepted', 'Canceled'].includes(s.status)),
|
|
449
|
+
}, null, 2));
|
|
450
|
+
}
|
|
451
|
+
async function checkDeployment(prNumber, format = 'json') {
|
|
452
|
+
const result = await checkPRDeploymentStatus(prNumber);
|
|
453
|
+
if (!result) {
|
|
454
|
+
console.error(`Could not get deployment status for PR #${prNumber}`);
|
|
455
|
+
console.error('Make sure the PR exists and you have access to it.');
|
|
456
|
+
process.exit(1);
|
|
457
|
+
}
|
|
458
|
+
if (format === 'markdown') {
|
|
459
|
+
console.log(formatDeploymentStatus(result));
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
console.log(JSON.stringify(result, null, 2));
|
|
463
|
+
}
|
|
464
|
+
// Exit with error code if any deployment failed
|
|
465
|
+
if (result.anyFailed) {
|
|
466
|
+
process.exit(1);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
async function main() {
|
|
470
|
+
const args = process.argv.slice(2);
|
|
471
|
+
const command = args[0];
|
|
472
|
+
if (!command) {
|
|
473
|
+
console.error('Usage: pnpm af-linear <command> [options]');
|
|
474
|
+
console.error('');
|
|
475
|
+
console.error('Commands:');
|
|
476
|
+
console.error(' get-issue <id> Get issue details');
|
|
477
|
+
console.error(' create-issue Create a new issue');
|
|
478
|
+
console.error(' update-issue <id> Update an existing issue');
|
|
479
|
+
console.error(' list-comments <issueId> List comments on an issue');
|
|
480
|
+
console.error(' create-comment <issueId> Create a comment on an issue');
|
|
481
|
+
console.error(' list-backlog-issues List backlog issues for a project');
|
|
482
|
+
console.error(' list-unblocked-backlog List unblocked backlog issues');
|
|
483
|
+
console.error(' check-blocked <id> Check if an issue is blocked');
|
|
484
|
+
console.error(' add-relation <id> <id> Create relation between issues');
|
|
485
|
+
console.error(' list-relations <id> List relations for an issue');
|
|
486
|
+
console.error(' remove-relation <id> Remove a relation by ID');
|
|
487
|
+
console.error(' list-sub-issues <id> List sub-issues of a parent issue');
|
|
488
|
+
console.error(' list-sub-issue-statuses <id> List sub-issue statuses (lightweight)');
|
|
489
|
+
console.error(' update-sub-issue <id> Update sub-issue status with comment');
|
|
490
|
+
console.error(' check-deployment <PR> Check Vercel deployment status');
|
|
491
|
+
process.exit(1);
|
|
492
|
+
}
|
|
493
|
+
// Validate LINEAR_API_KEY for commands that need it
|
|
494
|
+
if (!NO_API_KEY_COMMANDS.includes(command) && !LINEAR_API_KEY) {
|
|
495
|
+
console.error('Error: LINEAR_API_KEY environment variable is required');
|
|
496
|
+
process.exit(1);
|
|
497
|
+
}
|
|
498
|
+
const options = parseArgs(args.slice(1));
|
|
499
|
+
switch (command) {
|
|
500
|
+
case 'get-issue': {
|
|
501
|
+
const issueId = args[1];
|
|
502
|
+
if (!issueId || issueId.startsWith('--')) {
|
|
503
|
+
console.error('Usage: pnpm af-linear get-issue <issue-id>');
|
|
504
|
+
process.exit(1);
|
|
505
|
+
}
|
|
506
|
+
await getIssue(issueId);
|
|
507
|
+
break;
|
|
508
|
+
}
|
|
509
|
+
case 'create-issue': {
|
|
510
|
+
if (!options.title || !options.team) {
|
|
511
|
+
console.error('Usage: pnpm af-linear create-issue --title "Title" --team "Team" [--description "..."] [--project "..."] [--labels "Label1,Label2"] [--state "Backlog"] [--parentId "..."]');
|
|
512
|
+
process.exit(1);
|
|
513
|
+
}
|
|
514
|
+
const createDesc = resolveFileArg(options.description, options['description-file']);
|
|
515
|
+
await createIssue({
|
|
516
|
+
title: options.title,
|
|
517
|
+
team: options.team,
|
|
518
|
+
description: createDesc,
|
|
519
|
+
project: options.project,
|
|
520
|
+
labels: options.labels,
|
|
521
|
+
state: options.state,
|
|
522
|
+
parentId: options.parentId,
|
|
523
|
+
});
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
case 'update-issue': {
|
|
527
|
+
const issueId = args[1];
|
|
528
|
+
if (!issueId || issueId.startsWith('--')) {
|
|
529
|
+
console.error('Usage: pnpm af-linear update-issue <issue-id> [--title "..."] [--description "..."] [--state "..."] [--labels "..."]');
|
|
530
|
+
process.exit(1);
|
|
531
|
+
}
|
|
532
|
+
const updateOpts = parseArgs(args.slice(2));
|
|
533
|
+
const updateDesc = resolveFileArg(updateOpts.description, updateOpts['description-file']);
|
|
534
|
+
await updateIssue(issueId, {
|
|
535
|
+
title: updateOpts.title,
|
|
536
|
+
description: updateDesc,
|
|
537
|
+
state: updateOpts.state,
|
|
538
|
+
labels: updateOpts.labels,
|
|
539
|
+
});
|
|
540
|
+
break;
|
|
541
|
+
}
|
|
542
|
+
case 'list-comments': {
|
|
543
|
+
const issueId = args[1];
|
|
544
|
+
if (!issueId || issueId.startsWith('--')) {
|
|
545
|
+
console.error('Usage: pnpm af-linear list-comments <issue-id>');
|
|
546
|
+
process.exit(1);
|
|
547
|
+
}
|
|
548
|
+
await listComments(issueId);
|
|
549
|
+
break;
|
|
550
|
+
}
|
|
551
|
+
case 'create-comment': {
|
|
552
|
+
const issueId = args[1];
|
|
553
|
+
const commentBody = resolveFileArg(options.body, options['body-file']);
|
|
554
|
+
if (!issueId || issueId.startsWith('--') || !commentBody) {
|
|
555
|
+
console.error('Usage: pnpm af-linear create-comment <issue-id> --body "Comment text" or --body-file /path/to/file');
|
|
556
|
+
process.exit(1);
|
|
557
|
+
}
|
|
558
|
+
await createComment(issueId, commentBody);
|
|
559
|
+
break;
|
|
560
|
+
}
|
|
561
|
+
case 'list-backlog-issues': {
|
|
562
|
+
if (!options.project) {
|
|
563
|
+
console.error('Usage: pnpm af-linear list-backlog-issues --project "ProjectName"');
|
|
564
|
+
process.exit(1);
|
|
565
|
+
}
|
|
566
|
+
await listBacklogIssues(options.project);
|
|
567
|
+
break;
|
|
568
|
+
}
|
|
569
|
+
case 'list-unblocked-backlog': {
|
|
570
|
+
if (!options.project) {
|
|
571
|
+
console.error('Usage: pnpm af-linear list-unblocked-backlog --project "ProjectName"');
|
|
572
|
+
process.exit(1);
|
|
573
|
+
}
|
|
574
|
+
await listUnblockedBacklogIssues(options.project);
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
577
|
+
case 'check-blocked': {
|
|
578
|
+
const issueId = args[1];
|
|
579
|
+
if (!issueId || issueId.startsWith('--')) {
|
|
580
|
+
console.error('Usage: pnpm af-linear check-blocked <issue-id>');
|
|
581
|
+
process.exit(1);
|
|
582
|
+
}
|
|
583
|
+
await checkBlocked(issueId);
|
|
584
|
+
break;
|
|
585
|
+
}
|
|
586
|
+
case 'add-relation': {
|
|
587
|
+
const issueId = args[1];
|
|
588
|
+
const relatedIssueId = args[2];
|
|
589
|
+
const relationType = options.type;
|
|
590
|
+
if (!issueId ||
|
|
591
|
+
issueId.startsWith('--') ||
|
|
592
|
+
!relatedIssueId ||
|
|
593
|
+
relatedIssueId.startsWith('--') ||
|
|
594
|
+
!relationType ||
|
|
595
|
+
!['related', 'blocks', 'duplicate'].includes(relationType)) {
|
|
596
|
+
console.error('Usage: pnpm af-linear add-relation <issue-id> <related-issue-id> --type <related|blocks|duplicate>');
|
|
597
|
+
process.exit(1);
|
|
598
|
+
}
|
|
599
|
+
await addRelation(issueId, relatedIssueId, relationType);
|
|
600
|
+
break;
|
|
601
|
+
}
|
|
602
|
+
case 'list-relations': {
|
|
603
|
+
const issueId = args[1];
|
|
604
|
+
if (!issueId || issueId.startsWith('--')) {
|
|
605
|
+
console.error('Usage: pnpm af-linear list-relations <issue-id>');
|
|
606
|
+
process.exit(1);
|
|
607
|
+
}
|
|
608
|
+
await listRelations(issueId);
|
|
609
|
+
break;
|
|
610
|
+
}
|
|
611
|
+
case 'remove-relation': {
|
|
612
|
+
const relationId = args[1];
|
|
613
|
+
if (!relationId || relationId.startsWith('--')) {
|
|
614
|
+
console.error('Usage: pnpm af-linear remove-relation <relation-id>');
|
|
615
|
+
process.exit(1);
|
|
616
|
+
}
|
|
617
|
+
await removeRelation(relationId);
|
|
618
|
+
break;
|
|
619
|
+
}
|
|
620
|
+
case 'list-sub-issues': {
|
|
621
|
+
const issueId = args[1];
|
|
622
|
+
if (!issueId || issueId.startsWith('--')) {
|
|
623
|
+
console.error('Usage: pnpm af-linear list-sub-issues <issue-id>');
|
|
624
|
+
process.exit(1);
|
|
625
|
+
}
|
|
626
|
+
await listSubIssues(issueId);
|
|
627
|
+
break;
|
|
628
|
+
}
|
|
629
|
+
case 'list-sub-issue-statuses': {
|
|
630
|
+
const issueId = args[1];
|
|
631
|
+
if (!issueId || issueId.startsWith('--')) {
|
|
632
|
+
console.error('Usage: pnpm af-linear list-sub-issue-statuses <issue-id>');
|
|
633
|
+
process.exit(1);
|
|
634
|
+
}
|
|
635
|
+
await listSubIssueStatuses(issueId);
|
|
636
|
+
break;
|
|
637
|
+
}
|
|
638
|
+
case 'update-sub-issue': {
|
|
639
|
+
const issueId = args[1];
|
|
640
|
+
if (!issueId || issueId.startsWith('--')) {
|
|
641
|
+
console.error('Usage: pnpm af-linear update-sub-issue <issue-id> [--state "Finished"] [--comment "Done"]');
|
|
642
|
+
process.exit(1);
|
|
643
|
+
}
|
|
644
|
+
const subOpts = parseArgs(args.slice(2));
|
|
645
|
+
await updateSubIssue(issueId, {
|
|
646
|
+
state: subOpts.state,
|
|
647
|
+
comment: subOpts.comment,
|
|
648
|
+
});
|
|
649
|
+
break;
|
|
650
|
+
}
|
|
651
|
+
case 'check-deployment': {
|
|
652
|
+
const prArg = args[1];
|
|
653
|
+
if (!prArg || prArg.startsWith('--')) {
|
|
654
|
+
console.error('Usage: pnpm af-linear check-deployment <pr-number> [--format json|markdown]');
|
|
655
|
+
process.exit(1);
|
|
656
|
+
}
|
|
657
|
+
const prNumber = parseInt(prArg, 10);
|
|
658
|
+
if (isNaN(prNumber)) {
|
|
659
|
+
console.error('PR number must be a valid integer');
|
|
660
|
+
process.exit(1);
|
|
661
|
+
}
|
|
662
|
+
const format = options.format || 'json';
|
|
663
|
+
await checkDeployment(prNumber, format);
|
|
664
|
+
break;
|
|
665
|
+
}
|
|
666
|
+
default:
|
|
667
|
+
console.error(`Unknown command: ${command}`);
|
|
668
|
+
process.exit(1);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
main().catch((error) => {
|
|
672
|
+
console.error('Error:', error.message);
|
|
673
|
+
process.exit(1);
|
|
674
|
+
});
|