@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.
Files changed (246) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +125 -0
  3. package/dist/src/config/index.d.ts +3 -0
  4. package/dist/src/config/index.d.ts.map +1 -0
  5. package/dist/src/config/index.js +1 -0
  6. package/dist/src/config/repository-config.d.ts +44 -0
  7. package/dist/src/config/repository-config.d.ts.map +1 -0
  8. package/dist/src/config/repository-config.js +88 -0
  9. package/dist/src/config/repository-config.test.d.ts +2 -0
  10. package/dist/src/config/repository-config.test.d.ts.map +1 -0
  11. package/dist/src/config/repository-config.test.js +249 -0
  12. package/dist/src/deployment/deployment-checker.d.ts +110 -0
  13. package/dist/src/deployment/deployment-checker.d.ts.map +1 -0
  14. package/dist/src/deployment/deployment-checker.js +242 -0
  15. package/dist/src/deployment/index.d.ts +3 -0
  16. package/dist/src/deployment/index.d.ts.map +1 -0
  17. package/dist/src/deployment/index.js +2 -0
  18. package/dist/src/frontend/index.d.ts +2 -0
  19. package/dist/src/frontend/index.d.ts.map +1 -0
  20. package/dist/src/frontend/index.js +1 -0
  21. package/dist/src/frontend/types.d.ts +106 -0
  22. package/dist/src/frontend/types.d.ts.map +1 -0
  23. package/dist/src/frontend/types.js +11 -0
  24. package/dist/src/governor/decision-engine.d.ts +52 -0
  25. package/dist/src/governor/decision-engine.d.ts.map +1 -0
  26. package/dist/src/governor/decision-engine.js +220 -0
  27. package/dist/src/governor/decision-engine.test.d.ts +2 -0
  28. package/dist/src/governor/decision-engine.test.d.ts.map +1 -0
  29. package/dist/src/governor/decision-engine.test.js +629 -0
  30. package/dist/src/governor/event-bus.d.ts +43 -0
  31. package/dist/src/governor/event-bus.d.ts.map +1 -0
  32. package/dist/src/governor/event-bus.js +8 -0
  33. package/dist/src/governor/event-deduplicator.d.ts +43 -0
  34. package/dist/src/governor/event-deduplicator.d.ts.map +1 -0
  35. package/dist/src/governor/event-deduplicator.js +53 -0
  36. package/dist/src/governor/event-driven-governor.d.ts +131 -0
  37. package/dist/src/governor/event-driven-governor.d.ts.map +1 -0
  38. package/dist/src/governor/event-driven-governor.js +379 -0
  39. package/dist/src/governor/event-driven-governor.test.d.ts +2 -0
  40. package/dist/src/governor/event-driven-governor.test.d.ts.map +1 -0
  41. package/dist/src/governor/event-driven-governor.test.js +673 -0
  42. package/dist/src/governor/event-types.d.ts +78 -0
  43. package/dist/src/governor/event-types.d.ts.map +1 -0
  44. package/dist/src/governor/event-types.js +32 -0
  45. package/dist/src/governor/governor-types.d.ts +82 -0
  46. package/dist/src/governor/governor-types.d.ts.map +1 -0
  47. package/dist/src/governor/governor-types.js +21 -0
  48. package/dist/src/governor/governor.d.ts +100 -0
  49. package/dist/src/governor/governor.d.ts.map +1 -0
  50. package/dist/src/governor/governor.js +262 -0
  51. package/dist/src/governor/governor.test.d.ts +2 -0
  52. package/dist/src/governor/governor.test.d.ts.map +1 -0
  53. package/dist/src/governor/governor.test.js +514 -0
  54. package/dist/src/governor/human-touchpoints.d.ts +131 -0
  55. package/dist/src/governor/human-touchpoints.d.ts.map +1 -0
  56. package/dist/src/governor/human-touchpoints.js +251 -0
  57. package/dist/src/governor/human-touchpoints.test.d.ts +2 -0
  58. package/dist/src/governor/human-touchpoints.test.d.ts.map +1 -0
  59. package/dist/src/governor/human-touchpoints.test.js +366 -0
  60. package/dist/src/governor/in-memory-event-bus.d.ts +29 -0
  61. package/dist/src/governor/in-memory-event-bus.d.ts.map +1 -0
  62. package/dist/src/governor/in-memory-event-bus.js +79 -0
  63. package/dist/src/governor/index.d.ts +14 -0
  64. package/dist/src/governor/index.d.ts.map +1 -0
  65. package/dist/src/governor/index.js +13 -0
  66. package/dist/src/governor/override-parser.d.ts +60 -0
  67. package/dist/src/governor/override-parser.d.ts.map +1 -0
  68. package/dist/src/governor/override-parser.js +98 -0
  69. package/dist/src/governor/override-parser.test.d.ts +2 -0
  70. package/dist/src/governor/override-parser.test.d.ts.map +1 -0
  71. package/dist/src/governor/override-parser.test.js +312 -0
  72. package/dist/src/governor/platform-adapter.d.ts +69 -0
  73. package/dist/src/governor/platform-adapter.d.ts.map +1 -0
  74. package/dist/src/governor/platform-adapter.js +11 -0
  75. package/dist/src/governor/processing-state.d.ts +66 -0
  76. package/dist/src/governor/processing-state.d.ts.map +1 -0
  77. package/dist/src/governor/processing-state.js +43 -0
  78. package/dist/src/governor/processing-state.test.d.ts +2 -0
  79. package/dist/src/governor/processing-state.test.d.ts.map +1 -0
  80. package/dist/src/governor/processing-state.test.js +96 -0
  81. package/dist/src/governor/top-of-funnel.d.ts +118 -0
  82. package/dist/src/governor/top-of-funnel.d.ts.map +1 -0
  83. package/dist/src/governor/top-of-funnel.js +168 -0
  84. package/dist/src/governor/top-of-funnel.test.d.ts +2 -0
  85. package/dist/src/governor/top-of-funnel.test.d.ts.map +1 -0
  86. package/dist/src/governor/top-of-funnel.test.js +331 -0
  87. package/dist/src/index.d.ts +11 -0
  88. package/dist/src/index.d.ts.map +1 -0
  89. package/dist/src/index.js +10 -0
  90. package/dist/src/linear-cli.d.ts +38 -0
  91. package/dist/src/linear-cli.d.ts.map +1 -0
  92. package/dist/src/linear-cli.js +674 -0
  93. package/dist/src/logger.d.ts +117 -0
  94. package/dist/src/logger.d.ts.map +1 -0
  95. package/dist/src/logger.js +430 -0
  96. package/dist/src/manifest/generate.d.ts +20 -0
  97. package/dist/src/manifest/generate.d.ts.map +1 -0
  98. package/dist/src/manifest/generate.js +65 -0
  99. package/dist/src/manifest/index.d.ts +4 -0
  100. package/dist/src/manifest/index.d.ts.map +1 -0
  101. package/dist/src/manifest/index.js +2 -0
  102. package/dist/src/manifest/route-manifest.d.ts +34 -0
  103. package/dist/src/manifest/route-manifest.d.ts.map +1 -0
  104. package/dist/src/manifest/route-manifest.js +148 -0
  105. package/dist/src/orchestrator/activity-emitter.d.ts +119 -0
  106. package/dist/src/orchestrator/activity-emitter.d.ts.map +1 -0
  107. package/dist/src/orchestrator/activity-emitter.js +306 -0
  108. package/dist/src/orchestrator/api-activity-emitter.d.ts +167 -0
  109. package/dist/src/orchestrator/api-activity-emitter.d.ts.map +1 -0
  110. package/dist/src/orchestrator/api-activity-emitter.js +417 -0
  111. package/dist/src/orchestrator/heartbeat-writer.d.ts +57 -0
  112. package/dist/src/orchestrator/heartbeat-writer.d.ts.map +1 -0
  113. package/dist/src/orchestrator/heartbeat-writer.js +137 -0
  114. package/dist/src/orchestrator/index.d.ts +20 -0
  115. package/dist/src/orchestrator/index.d.ts.map +1 -0
  116. package/dist/src/orchestrator/index.js +22 -0
  117. package/dist/src/orchestrator/log-analyzer.d.ts +160 -0
  118. package/dist/src/orchestrator/log-analyzer.d.ts.map +1 -0
  119. package/dist/src/orchestrator/log-analyzer.js +572 -0
  120. package/dist/src/orchestrator/log-config.d.ts +39 -0
  121. package/dist/src/orchestrator/log-config.d.ts.map +1 -0
  122. package/dist/src/orchestrator/log-config.js +45 -0
  123. package/dist/src/orchestrator/orchestrator.d.ts +316 -0
  124. package/dist/src/orchestrator/orchestrator.d.ts.map +1 -0
  125. package/dist/src/orchestrator/orchestrator.js +3290 -0
  126. package/dist/src/orchestrator/parse-work-result.d.ts +16 -0
  127. package/dist/src/orchestrator/parse-work-result.d.ts.map +1 -0
  128. package/dist/src/orchestrator/parse-work-result.js +135 -0
  129. package/dist/src/orchestrator/parse-work-result.test.d.ts +2 -0
  130. package/dist/src/orchestrator/parse-work-result.test.d.ts.map +1 -0
  131. package/dist/src/orchestrator/parse-work-result.test.js +234 -0
  132. package/dist/src/orchestrator/progress-logger.d.ts +72 -0
  133. package/dist/src/orchestrator/progress-logger.d.ts.map +1 -0
  134. package/dist/src/orchestrator/progress-logger.js +135 -0
  135. package/dist/src/orchestrator/session-logger.d.ts +159 -0
  136. package/dist/src/orchestrator/session-logger.d.ts.map +1 -0
  137. package/dist/src/orchestrator/session-logger.js +275 -0
  138. package/dist/src/orchestrator/state-recovery.d.ts +96 -0
  139. package/dist/src/orchestrator/state-recovery.d.ts.map +1 -0
  140. package/dist/src/orchestrator/state-recovery.js +302 -0
  141. package/dist/src/orchestrator/state-types.d.ts +165 -0
  142. package/dist/src/orchestrator/state-types.d.ts.map +1 -0
  143. package/dist/src/orchestrator/state-types.js +7 -0
  144. package/dist/src/orchestrator/stream-parser.d.ts +151 -0
  145. package/dist/src/orchestrator/stream-parser.d.ts.map +1 -0
  146. package/dist/src/orchestrator/stream-parser.js +137 -0
  147. package/dist/src/orchestrator/types.d.ts +232 -0
  148. package/dist/src/orchestrator/types.d.ts.map +1 -0
  149. package/dist/src/orchestrator/types.js +4 -0
  150. package/dist/src/orchestrator/validate-git-remote.test.d.ts +2 -0
  151. package/dist/src/orchestrator/validate-git-remote.test.d.ts.map +1 -0
  152. package/dist/src/orchestrator/validate-git-remote.test.js +61 -0
  153. package/dist/src/providers/a2a-auth.d.ts +81 -0
  154. package/dist/src/providers/a2a-auth.d.ts.map +1 -0
  155. package/dist/src/providers/a2a-auth.js +188 -0
  156. package/dist/src/providers/a2a-auth.test.d.ts +2 -0
  157. package/dist/src/providers/a2a-auth.test.d.ts.map +1 -0
  158. package/dist/src/providers/a2a-auth.test.js +232 -0
  159. package/dist/src/providers/a2a-provider.d.ts +254 -0
  160. package/dist/src/providers/a2a-provider.d.ts.map +1 -0
  161. package/dist/src/providers/a2a-provider.integration.test.d.ts +9 -0
  162. package/dist/src/providers/a2a-provider.integration.test.d.ts.map +1 -0
  163. package/dist/src/providers/a2a-provider.integration.test.js +665 -0
  164. package/dist/src/providers/a2a-provider.js +811 -0
  165. package/dist/src/providers/a2a-provider.test.d.ts +2 -0
  166. package/dist/src/providers/a2a-provider.test.d.ts.map +1 -0
  167. package/dist/src/providers/a2a-provider.test.js +681 -0
  168. package/dist/src/providers/amp-provider.d.ts +20 -0
  169. package/dist/src/providers/amp-provider.d.ts.map +1 -0
  170. package/dist/src/providers/amp-provider.js +24 -0
  171. package/dist/src/providers/claude-provider.d.ts +18 -0
  172. package/dist/src/providers/claude-provider.d.ts.map +1 -0
  173. package/dist/src/providers/claude-provider.js +437 -0
  174. package/dist/src/providers/codex-provider.d.ts +133 -0
  175. package/dist/src/providers/codex-provider.d.ts.map +1 -0
  176. package/dist/src/providers/codex-provider.js +381 -0
  177. package/dist/src/providers/codex-provider.test.d.ts +2 -0
  178. package/dist/src/providers/codex-provider.test.d.ts.map +1 -0
  179. package/dist/src/providers/codex-provider.test.js +387 -0
  180. package/dist/src/providers/index.d.ts +44 -0
  181. package/dist/src/providers/index.d.ts.map +1 -0
  182. package/dist/src/providers/index.js +85 -0
  183. package/dist/src/providers/spring-ai-provider.d.ts +90 -0
  184. package/dist/src/providers/spring-ai-provider.d.ts.map +1 -0
  185. package/dist/src/providers/spring-ai-provider.integration.test.d.ts +13 -0
  186. package/dist/src/providers/spring-ai-provider.integration.test.d.ts.map +1 -0
  187. package/dist/src/providers/spring-ai-provider.integration.test.js +351 -0
  188. package/dist/src/providers/spring-ai-provider.js +317 -0
  189. package/dist/src/providers/spring-ai-provider.test.d.ts +2 -0
  190. package/dist/src/providers/spring-ai-provider.test.d.ts.map +1 -0
  191. package/dist/src/providers/spring-ai-provider.test.js +200 -0
  192. package/dist/src/providers/types.d.ts +165 -0
  193. package/dist/src/providers/types.d.ts.map +1 -0
  194. package/dist/src/providers/types.js +13 -0
  195. package/dist/src/templates/adapters.d.ts +51 -0
  196. package/dist/src/templates/adapters.d.ts.map +1 -0
  197. package/dist/src/templates/adapters.js +104 -0
  198. package/dist/src/templates/adapters.test.d.ts +2 -0
  199. package/dist/src/templates/adapters.test.d.ts.map +1 -0
  200. package/dist/src/templates/adapters.test.js +165 -0
  201. package/dist/src/templates/agent-definition.d.ts +85 -0
  202. package/dist/src/templates/agent-definition.d.ts.map +1 -0
  203. package/dist/src/templates/agent-definition.js +97 -0
  204. package/dist/src/templates/agent-definition.test.d.ts +2 -0
  205. package/dist/src/templates/agent-definition.test.d.ts.map +1 -0
  206. package/dist/src/templates/agent-definition.test.js +209 -0
  207. package/dist/src/templates/index.d.ts +14 -0
  208. package/dist/src/templates/index.d.ts.map +1 -0
  209. package/dist/src/templates/index.js +11 -0
  210. package/dist/src/templates/loader.d.ts +41 -0
  211. package/dist/src/templates/loader.d.ts.map +1 -0
  212. package/dist/src/templates/loader.js +114 -0
  213. package/dist/src/templates/registry.d.ts +80 -0
  214. package/dist/src/templates/registry.d.ts.map +1 -0
  215. package/dist/src/templates/registry.js +177 -0
  216. package/dist/src/templates/registry.test.d.ts +2 -0
  217. package/dist/src/templates/registry.test.d.ts.map +1 -0
  218. package/dist/src/templates/registry.test.js +198 -0
  219. package/dist/src/templates/renderer.d.ts +29 -0
  220. package/dist/src/templates/renderer.d.ts.map +1 -0
  221. package/dist/src/templates/renderer.js +35 -0
  222. package/dist/src/templates/strategy-templates.test.d.ts +2 -0
  223. package/dist/src/templates/strategy-templates.test.d.ts.map +1 -0
  224. package/dist/src/templates/strategy-templates.test.js +619 -0
  225. package/dist/src/templates/types.d.ts +233 -0
  226. package/dist/src/templates/types.d.ts.map +1 -0
  227. package/dist/src/templates/types.js +127 -0
  228. package/dist/src/templates/types.test.d.ts +2 -0
  229. package/dist/src/templates/types.test.d.ts.map +1 -0
  230. package/dist/src/templates/types.test.js +232 -0
  231. package/dist/src/tools/index.d.ts +6 -0
  232. package/dist/src/tools/index.d.ts.map +1 -0
  233. package/dist/src/tools/index.js +3 -0
  234. package/dist/src/tools/linear-runner.d.ts +34 -0
  235. package/dist/src/tools/linear-runner.d.ts.map +1 -0
  236. package/dist/src/tools/linear-runner.js +700 -0
  237. package/dist/src/tools/plugins/linear.d.ts +9 -0
  238. package/dist/src/tools/plugins/linear.d.ts.map +1 -0
  239. package/dist/src/tools/plugins/linear.js +138 -0
  240. package/dist/src/tools/registry.d.ts +9 -0
  241. package/dist/src/tools/registry.d.ts.map +1 -0
  242. package/dist/src/tools/registry.js +18 -0
  243. package/dist/src/tools/types.d.ts +18 -0
  244. package/dist/src/tools/types.d.ts.map +1 -0
  245. package/dist/src/tools/types.js +1 -0
  246. 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
+ });