@elnora-ai/linear 1.0.1 → 2.0.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 (302) hide show
  1. package/.claude-plugin/marketplace.json +7 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +25 -1
  4. package/README.md +275 -25
  5. package/agents/linear-issue-creator.md +135 -17
  6. package/agents/linear-issue-reviewer.md +122 -23
  7. package/agents/linear-issue-updater.md +137 -25
  8. package/agents/linear-state-curator.md +173 -0
  9. package/agents/linear-url-to-issues.md +190 -26
  10. package/commands/linear-cleanup.md +64 -29
  11. package/dist/cli.js +69 -1
  12. package/dist/cli.js.map +1 -1
  13. package/dist/client/auth.d.ts +10 -0
  14. package/dist/client/auth.d.ts.map +1 -1
  15. package/dist/client/auth.js +50 -3
  16. package/dist/client/auth.js.map +1 -1
  17. package/dist/client/linear-client.d.ts +7 -0
  18. package/dist/client/linear-client.d.ts.map +1 -1
  19. package/dist/client/linear-client.js +13 -1
  20. package/dist/client/linear-client.js.map +1 -1
  21. package/dist/commands/agent-activities.d.ts +3 -0
  22. package/dist/commands/agent-activities.d.ts.map +1 -0
  23. package/dist/commands/agent-activities.js +144 -0
  24. package/dist/commands/agent-activities.js.map +1 -0
  25. package/dist/commands/agent-sessions.d.ts +3 -0
  26. package/dist/commands/agent-sessions.d.ts.map +1 -0
  27. package/dist/commands/agent-sessions.js +132 -0
  28. package/dist/commands/agent-sessions.js.map +1 -0
  29. package/dist/commands/attachments.d.ts +3 -0
  30. package/dist/commands/attachments.d.ts.map +1 -0
  31. package/dist/commands/attachments.js +265 -0
  32. package/dist/commands/attachments.js.map +1 -0
  33. package/dist/commands/audit.d.ts +3 -0
  34. package/dist/commands/audit.d.ts.map +1 -0
  35. package/dist/commands/audit.js +73 -0
  36. package/dist/commands/audit.js.map +1 -0
  37. package/dist/commands/comments.d.ts +3 -0
  38. package/dist/commands/comments.d.ts.map +1 -0
  39. package/dist/commands/comments.js +107 -0
  40. package/dist/commands/comments.js.map +1 -0
  41. package/dist/commands/completion.d.ts +3 -0
  42. package/dist/commands/completion.d.ts.map +1 -0
  43. package/dist/commands/completion.js +62 -0
  44. package/dist/commands/completion.js.map +1 -0
  45. package/dist/commands/context.d.ts +3 -0
  46. package/dist/commands/context.d.ts.map +1 -0
  47. package/dist/commands/context.js +94 -0
  48. package/dist/commands/context.js.map +1 -0
  49. package/dist/commands/curator.d.ts +14 -0
  50. package/dist/commands/curator.d.ts.map +1 -1
  51. package/dist/commands/curator.js +97 -19
  52. package/dist/commands/curator.js.map +1 -1
  53. package/dist/commands/customer-needs.d.ts +3 -0
  54. package/dist/commands/customer-needs.d.ts.map +1 -0
  55. package/dist/commands/customer-needs.js +198 -0
  56. package/dist/commands/customer-needs.js.map +1 -0
  57. package/dist/commands/customers.d.ts +5 -0
  58. package/dist/commands/customers.d.ts.map +1 -0
  59. package/dist/commands/customers.js +201 -0
  60. package/dist/commands/customers.js.map +1 -0
  61. package/dist/commands/cycles.d.ts +3 -0
  62. package/dist/commands/cycles.d.ts.map +1 -0
  63. package/dist/commands/cycles.js +67 -0
  64. package/dist/commands/cycles.js.map +1 -0
  65. package/dist/commands/documents.d.ts +3 -0
  66. package/dist/commands/documents.d.ts.map +1 -0
  67. package/dist/commands/documents.js +105 -0
  68. package/dist/commands/documents.js.map +1 -0
  69. package/dist/commands/favorites.d.ts +3 -0
  70. package/dist/commands/favorites.d.ts.map +1 -0
  71. package/dist/commands/favorites.js +101 -0
  72. package/dist/commands/favorites.js.map +1 -0
  73. package/dist/commands/index.d.ts +30 -0
  74. package/dist/commands/index.d.ts.map +1 -1
  75. package/dist/commands/index.js +30 -0
  76. package/dist/commands/index.js.map +1 -1
  77. package/dist/commands/initiatives.d.ts +3 -0
  78. package/dist/commands/initiatives.d.ts.map +1 -0
  79. package/dist/commands/initiatives.js +106 -0
  80. package/dist/commands/initiatives.js.map +1 -0
  81. package/dist/commands/issues.d.ts +21 -0
  82. package/dist/commands/issues.d.ts.map +1 -0
  83. package/dist/commands/issues.js +1083 -0
  84. package/dist/commands/issues.js.map +1 -0
  85. package/dist/commands/labels.d.ts +3 -0
  86. package/dist/commands/labels.d.ts.map +1 -0
  87. package/dist/commands/labels.js +111 -0
  88. package/dist/commands/labels.js.map +1 -0
  89. package/dist/commands/milestones.d.ts +3 -0
  90. package/dist/commands/milestones.d.ts.map +1 -0
  91. package/dist/commands/milestones.js +94 -0
  92. package/dist/commands/milestones.js.map +1 -0
  93. package/dist/commands/notifications.d.ts +3 -0
  94. package/dist/commands/notifications.d.ts.map +1 -0
  95. package/dist/commands/notifications.js +130 -0
  96. package/dist/commands/notifications.js.map +1 -0
  97. package/dist/commands/project-labels.d.ts +3 -0
  98. package/dist/commands/project-labels.d.ts.map +1 -0
  99. package/dist/commands/project-labels.js +80 -0
  100. package/dist/commands/project-labels.js.map +1 -0
  101. package/dist/commands/project-relations.d.ts +3 -0
  102. package/dist/commands/project-relations.d.ts.map +1 -0
  103. package/dist/commands/project-relations.js +96 -0
  104. package/dist/commands/project-relations.js.map +1 -0
  105. package/dist/commands/projects.d.ts +3 -0
  106. package/dist/commands/projects.d.ts.map +1 -0
  107. package/dist/commands/projects.js +263 -0
  108. package/dist/commands/projects.js.map +1 -0
  109. package/dist/commands/quota.d.ts +3 -0
  110. package/dist/commands/quota.d.ts.map +1 -0
  111. package/dist/commands/quota.js +28 -0
  112. package/dist/commands/quota.js.map +1 -0
  113. package/dist/commands/reactions.d.ts +7 -0
  114. package/dist/commands/reactions.d.ts.map +1 -0
  115. package/dist/commands/reactions.js +53 -0
  116. package/dist/commands/reactions.js.map +1 -0
  117. package/dist/commands/relations.d.ts +3 -0
  118. package/dist/commands/relations.d.ts.map +1 -0
  119. package/dist/commands/relations.js +73 -0
  120. package/dist/commands/relations.js.map +1 -0
  121. package/dist/commands/states.d.ts +3 -0
  122. package/dist/commands/states.d.ts.map +1 -0
  123. package/dist/commands/states.js +52 -0
  124. package/dist/commands/states.js.map +1 -0
  125. package/dist/commands/status-updates.d.ts +3 -0
  126. package/dist/commands/status-updates.d.ts.map +1 -0
  127. package/dist/commands/status-updates.js +117 -0
  128. package/dist/commands/status-updates.js.map +1 -0
  129. package/dist/commands/sync.d.ts.map +1 -1
  130. package/dist/commands/sync.js +58 -18
  131. package/dist/commands/sync.js.map +1 -1
  132. package/dist/commands/teams.d.ts +3 -0
  133. package/dist/commands/teams.d.ts.map +1 -0
  134. package/dist/commands/teams.js +135 -0
  135. package/dist/commands/teams.js.map +1 -0
  136. package/dist/commands/templates.d.ts +3 -0
  137. package/dist/commands/templates.d.ts.map +1 -0
  138. package/dist/commands/templates.js +76 -0
  139. package/dist/commands/templates.js.map +1 -0
  140. package/dist/commands/users.d.ts +3 -0
  141. package/dist/commands/users.d.ts.map +1 -0
  142. package/dist/commands/users.js +40 -0
  143. package/dist/commands/users.js.map +1 -0
  144. package/dist/commands/views.d.ts +3 -0
  145. package/dist/commands/views.d.ts.map +1 -0
  146. package/dist/commands/views.js +177 -0
  147. package/dist/commands/views.js.map +1 -0
  148. package/dist/commands/webhooks.d.ts +3 -0
  149. package/dist/commands/webhooks.d.ts.map +1 -0
  150. package/dist/commands/webhooks.js +234 -0
  151. package/dist/commands/webhooks.js.map +1 -0
  152. package/dist/config/loader.d.ts.map +1 -1
  153. package/dist/config/loader.js +3 -0
  154. package/dist/config/loader.js.map +1 -1
  155. package/dist/config/types.d.ts +15 -1
  156. package/dist/config/types.d.ts.map +1 -1
  157. package/dist/config/types.js +1 -0
  158. package/dist/config/types.js.map +1 -1
  159. package/dist/curator/dispatch.d.ts +52 -0
  160. package/dist/curator/dispatch.d.ts.map +1 -0
  161. package/dist/curator/dispatch.js +144 -0
  162. package/dist/curator/dispatch.js.map +1 -0
  163. package/dist/curator/index.d.ts +5 -0
  164. package/dist/curator/index.d.ts.map +1 -0
  165. package/dist/curator/index.js +5 -0
  166. package/dist/curator/index.js.map +1 -0
  167. package/dist/curator/llm.d.ts +70 -0
  168. package/dist/curator/llm.d.ts.map +1 -0
  169. package/dist/curator/llm.js +107 -0
  170. package/dist/curator/llm.js.map +1 -0
  171. package/dist/curator/snapshot.d.ts +34 -0
  172. package/dist/curator/snapshot.d.ts.map +1 -0
  173. package/dist/curator/snapshot.js +127 -0
  174. package/dist/curator/snapshot.js.map +1 -0
  175. package/dist/curator/state.d.ts +50 -0
  176. package/dist/curator/state.d.ts.map +1 -0
  177. package/dist/curator/state.js +125 -0
  178. package/dist/curator/state.js.map +1 -0
  179. package/dist/lib/bulk-graphql.d.ts +144 -0
  180. package/dist/lib/bulk-graphql.d.ts.map +1 -0
  181. package/dist/lib/bulk-graphql.js +380 -0
  182. package/dist/lib/bulk-graphql.js.map +1 -0
  183. package/dist/lib/index.d.ts +2 -0
  184. package/dist/lib/index.d.ts.map +1 -0
  185. package/dist/lib/index.js +2 -0
  186. package/dist/lib/index.js.map +1 -0
  187. package/dist/output/cli.d.ts +17 -0
  188. package/dist/output/cli.d.ts.map +1 -0
  189. package/dist/output/cli.js +252 -0
  190. package/dist/output/cli.js.map +1 -0
  191. package/dist/output/formatter.d.ts +6 -0
  192. package/dist/output/formatter.d.ts.map +1 -1
  193. package/dist/output/formatter.js +10 -0
  194. package/dist/output/formatter.js.map +1 -1
  195. package/dist/output/index.d.ts +1 -0
  196. package/dist/output/index.d.ts.map +1 -1
  197. package/dist/output/index.js +1 -0
  198. package/dist/output/index.js.map +1 -1
  199. package/dist/scripts/sync-linear-templates.d.ts +26 -0
  200. package/dist/scripts/sync-linear-templates.d.ts.map +1 -0
  201. package/dist/scripts/sync-linear-templates.js +115 -0
  202. package/dist/scripts/sync-linear-templates.js.map +1 -0
  203. package/dist/signals/github-commits.d.ts +31 -0
  204. package/dist/signals/github-commits.d.ts.map +1 -0
  205. package/dist/signals/github-commits.js +127 -0
  206. package/dist/signals/github-commits.js.map +1 -0
  207. package/dist/signals/github-pr.d.ts +16 -0
  208. package/dist/signals/github-pr.d.ts.map +1 -0
  209. package/dist/signals/github-pr.js +98 -0
  210. package/dist/signals/github-pr.js.map +1 -0
  211. package/dist/signals/index.d.ts +4 -0
  212. package/dist/signals/index.d.ts.map +1 -1
  213. package/dist/signals/index.js +4 -0
  214. package/dist/signals/index.js.map +1 -1
  215. package/dist/signals/linear-issues.d.ts +20 -0
  216. package/dist/signals/linear-issues.d.ts.map +1 -0
  217. package/dist/signals/linear-issues.js +115 -0
  218. package/dist/signals/linear-issues.js.map +1 -0
  219. package/dist/signals/registry.d.ts +4 -3
  220. package/dist/signals/registry.d.ts.map +1 -1
  221. package/dist/signals/registry.js +33 -11
  222. package/dist/signals/registry.js.map +1 -1
  223. package/dist/signals/slack-messages.d.ts +20 -0
  224. package/dist/signals/slack-messages.d.ts.map +1 -0
  225. package/dist/signals/slack-messages.js +129 -0
  226. package/dist/signals/slack-messages.js.map +1 -0
  227. package/dist/utils/errors.d.ts +81 -0
  228. package/dist/utils/errors.d.ts.map +1 -0
  229. package/dist/utils/errors.js +110 -0
  230. package/dist/utils/errors.js.map +1 -0
  231. package/dist/utils/index.d.ts +9 -0
  232. package/dist/utils/index.d.ts.map +1 -0
  233. package/dist/utils/index.js +9 -0
  234. package/dist/utils/index.js.map +1 -0
  235. package/dist/utils/label-policy.d.ts +60 -0
  236. package/dist/utils/label-policy.d.ts.map +1 -0
  237. package/dist/utils/label-policy.js +103 -0
  238. package/dist/utils/label-policy.js.map +1 -0
  239. package/dist/utils/parse.d.ts +48 -0
  240. package/dist/utils/parse.d.ts.map +1 -0
  241. package/dist/utils/parse.js +133 -0
  242. package/dist/utils/parse.js.map +1 -0
  243. package/dist/utils/project-status.d.ts +6 -0
  244. package/dist/utils/project-status.d.ts.map +1 -0
  245. package/dist/utils/project-status.js +33 -0
  246. package/dist/utils/project-status.js.map +1 -0
  247. package/dist/utils/rate-limit.d.ts +24 -0
  248. package/dist/utils/rate-limit.d.ts.map +1 -0
  249. package/dist/utils/rate-limit.js +89 -0
  250. package/dist/utils/rate-limit.js.map +1 -0
  251. package/dist/utils/resolve.d.ts +84 -0
  252. package/dist/utils/resolve.d.ts.map +1 -0
  253. package/dist/utils/resolve.js +172 -0
  254. package/dist/utils/resolve.js.map +1 -0
  255. package/dist/utils/sleep.d.ts +2 -0
  256. package/dist/utils/sleep.d.ts.map +1 -0
  257. package/dist/utils/sleep.js +4 -0
  258. package/dist/utils/sleep.js.map +1 -0
  259. package/dist/utils/webhook-verify.d.ts +42 -0
  260. package/dist/utils/webhook-verify.d.ts.map +1 -0
  261. package/dist/utils/webhook-verify.js +65 -0
  262. package/dist/utils/webhook-verify.js.map +1 -0
  263. package/package.json +7 -2
  264. package/references/agent-description-template.md +31 -0
  265. package/references/cli-reference.md +227 -0
  266. package/references/curator-tiering-rules.md +78 -0
  267. package/references/label-policy.example.json +37 -0
  268. package/references/label-policy.placeholder.json +6 -0
  269. package/references/settings-template.md +30 -0
  270. package/references/signal-sources.example.json +0 -8
  271. package/references/sla-reference.md +70 -0
  272. package/references/template-index.md +34 -0
  273. package/references/workspace-labels.md +124 -0
  274. package/references/workspace-projects.md +56 -0
  275. package/references/workspace-routing.md +58 -0
  276. package/schemas/label-policy.json +72 -0
  277. package/scripts/postinstall.mjs +195 -0
  278. package/skills/linear-workspace/SKILL.md +65 -4
  279. package/templates/ACC-PRO-provision.md +74 -0
  280. package/templates/ACC-PRV-privileged.md +66 -0
  281. package/templates/ACC-QTR-review.md +77 -0
  282. package/templates/ACC-REV-revoke.md +67 -0
  283. package/templates/AI-USE-capability.md +111 -0
  284. package/templates/AUD-CAP-corrective.md +89 -0
  285. package/templates/AUD-INT-internal.md +92 -0
  286. package/templates/AUD-MGT-management.md +110 -0
  287. package/templates/CHG-MAJ-major.md +110 -0
  288. package/templates/CHG-SIG-significant.md +83 -0
  289. package/templates/CHG-STD-standard.md +47 -0
  290. package/templates/LRN-DOC-lessons.md +75 -0
  291. package/templates/OPS-BCK-backup.md +99 -0
  292. package/templates/OPS-DAT-data-mod.md +98 -0
  293. package/templates/RCA-DOC-root-cause.md +105 -0
  294. package/templates/RSK-ASS-assessment.md +87 -0
  295. package/templates/RSK-VND-vendor.md +113 -0
  296. package/templates/SEC-INC-incident.md +76 -0
  297. package/templates/SEC-PEN-pentest.md +58 -0
  298. package/templates/SEC-VLN-vulnerability.md +69 -0
  299. package/templates/SLA-AVL-availability.md +86 -0
  300. package/templates/SLA-OPS-operational.md +70 -0
  301. package/templates/agent-server-template/README.md +88 -0
  302. package/templates/agent-server-template/server.example.ts +185 -0
@@ -1 +1 @@
1
- {"version":3,"file":"linear-client.d.ts","sourceRoot":"","sources":["../../src/client/linear-client.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,KAAK,gBAAgB,EAAa,MAAM,WAAW,CAAC;AAI7D,wBAAsB,eAAe,CAAC,IAAI,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC,CAKpF;AAED,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
1
+ {"version":3,"file":"linear-client.d.ts","sourceRoot":"","sources":["../../src/client/linear-client.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,KAAK,gBAAgB,EAAa,MAAM,WAAW,CAAC;AAO7D,wBAAsB,eAAe,CAAC,IAAI,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC,CAKpF;AAED;;;;;GAKG;AACH,wBAAsB,SAAS,CAAC,IAAI,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC,CAE9E;AAED,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
@@ -2,16 +2,28 @@
2
2
  //
3
3
  // Process-wide cache so a single CLI invocation only auths + constructs once,
4
4
  // no matter how many commands invoke it. Tests can reset via resetLinearClient.
5
+ import { createRequire } from "node:module";
5
6
  import { LinearClient } from "@linear/sdk";
6
7
  import { getApiKey } from "./auth.js";
8
+ const pkg = createRequire(import.meta.url)("../../package.json");
9
+ const USER_AGENT = `@elnora-ai/linear/${pkg.version}`;
7
10
  let cached = null;
8
11
  export async function getLinearClient(opts) {
9
12
  if (cached)
10
13
  return cached;
11
14
  const apiKey = await getApiKey(opts);
12
- cached = new LinearClient({ apiKey });
15
+ cached = new LinearClient({ apiKey, headers: { "User-Agent": USER_AGENT } });
13
16
  return cached;
14
17
  }
18
+ /**
19
+ * Alias for getLinearClient with allowPrompt=true.
20
+ *
21
+ * Provided so ports from the private CLI (which used a synchronous `getClient()`)
22
+ * can keep the same call site shape (`const client = await getClient()`).
23
+ */
24
+ export async function getClient(opts) {
25
+ return getLinearClient({ allowPrompt: true, ...opts });
26
+ }
15
27
  export function resetLinearClient() {
16
28
  cached = null;
17
29
  }
@@ -1 +1 @@
1
- {"version":3,"file":"linear-client.js","sourceRoot":"","sources":["../../src/client/linear-client.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,EAAE;AACF,8EAA8E;AAC9E,gFAAgF;AAEhF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAyB,SAAS,EAAE,MAAM,WAAW,CAAC;AAE7D,IAAI,MAAM,GAAwB,IAAI,CAAC;AAEvC,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAuB;IAC5D,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAC1B,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,CAAC;IACrC,MAAM,GAAG,IAAI,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IACtC,OAAO,MAAM,CAAC;AACf,CAAC;AAED,MAAM,UAAU,iBAAiB;IAChC,MAAM,GAAG,IAAI,CAAC;AACf,CAAC"}
1
+ {"version":3,"file":"linear-client.js","sourceRoot":"","sources":["../../src/client/linear-client.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,EAAE;AACF,8EAA8E;AAC9E,gFAAgF;AAEhF,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAyB,SAAS,EAAE,MAAM,WAAW,CAAC;AAE7D,MAAM,GAAG,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,oBAAoB,CAAwB,CAAC;AACxF,MAAM,UAAU,GAAG,qBAAqB,GAAG,CAAC,OAAO,EAAE,CAAC;AAEtD,IAAI,MAAM,GAAwB,IAAI,CAAC;AAEvC,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAuB;IAC5D,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAC1B,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,CAAC;IACrC,MAAM,GAAG,IAAI,YAAY,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,YAAY,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC;IAC7E,OAAO,MAAM,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAuB;IACtD,OAAO,eAAe,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,iBAAiB;IAChC,MAAM,GAAG,IAAI,CAAC;AACf,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Command } from "commander";
2
+ export declare function setupAgentActivitiesCommand(program: Command): void;
3
+ //# sourceMappingURL=agent-activities.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-activities.d.ts","sourceRoot":"","sources":["../../src/commands/agent-activities.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAwEzC,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAgFlE"}
@@ -0,0 +1,144 @@
1
+ // `elnora-linear agent-activities` — streaming output an agent emits during
2
+ // a session.
3
+ //
4
+ // Linear's agent framework expects a `thought` activity within 10 seconds of
5
+ // receiving an `agentSessionEvent.created` webhook, then any number of
6
+ // follow-ups (action / response / elicitation / error) up to ~30 minutes
7
+ // before the session goes stale.
8
+ import { readFileSync } from "node:fs";
9
+ import { resolve as resolvePath } from "node:path";
10
+ import { AgentActivitySignal } from "@linear/sdk";
11
+ import { getClient } from "../client/index.js";
12
+ import { handleAsyncCommand, outputSuccess } from "../output/index.js";
13
+ import { CliError, parseLimit, ValidationError } from "../utils/index.js";
14
+ const VALID_TYPES = ["thought", "action", "elicitation", "response", "error"];
15
+ function buildContent(type, body, action, parameter, resultJson) {
16
+ switch (type) {
17
+ case "thought":
18
+ case "elicitation":
19
+ case "response":
20
+ case "error":
21
+ if (!body) {
22
+ throw new ValidationError(`--body is required for type "${type}".`);
23
+ }
24
+ return { type, body };
25
+ case "action": {
26
+ if (!action) {
27
+ throw new ValidationError('--action is required for type "action".');
28
+ }
29
+ const content = { type: "action", action };
30
+ if (parameter !== undefined)
31
+ content.parameter = parameter;
32
+ if (resultJson !== undefined) {
33
+ try {
34
+ content.result = JSON.parse(resultJson);
35
+ }
36
+ catch (e) {
37
+ const msg = e instanceof Error ? e.message : String(e);
38
+ throw new ValidationError(`Invalid --result JSON: ${msg}`);
39
+ }
40
+ }
41
+ return content;
42
+ }
43
+ }
44
+ }
45
+ function readSignalMetadata(file) {
46
+ const raw = readFileSync(resolvePath(file), "utf-8");
47
+ let parsed;
48
+ try {
49
+ parsed = JSON.parse(raw);
50
+ }
51
+ catch (e) {
52
+ const msg = e instanceof Error ? e.message : String(e);
53
+ throw new ValidationError(`Invalid --signal-metadata JSON: ${msg}`);
54
+ }
55
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
56
+ throw new ValidationError("--signal-metadata must be a JSON object.");
57
+ }
58
+ return parsed;
59
+ }
60
+ async function formatActivity(a) {
61
+ const session = await a.agentSession;
62
+ return {
63
+ id: a.id,
64
+ sessionId: session?.id ?? null,
65
+ signal: a.signal ?? null,
66
+ ephemeral: a.ephemeral ?? null,
67
+ content: a.content,
68
+ createdAt: a.createdAt,
69
+ };
70
+ }
71
+ export function setupAgentActivitiesCommand(program) {
72
+ const activities = program
73
+ .command("agent-activities")
74
+ .description("Manage Linear agent activities (thought / action / elicitation / response / error)");
75
+ activities
76
+ .command("list <sessionId>")
77
+ .description("List activities on an agent session")
78
+ .option("--limit <n>", "Max results", "100")
79
+ .action(handleAsyncCommand(async (sessionId, opts) => {
80
+ const client = await getClient();
81
+ const session = await client.agentSession(sessionId);
82
+ const conn = await session.activities({ first: parseLimit(opts.limit, 100) });
83
+ const items = await Promise.all(conn.nodes.map(formatActivity));
84
+ outputSuccess({ activities: items, count: items.length });
85
+ }));
86
+ activities
87
+ .command("get <id>")
88
+ .description("Get a single agent activity")
89
+ .action(handleAsyncCommand(async (id) => {
90
+ const client = await getClient();
91
+ const a = await client.agentActivity(id);
92
+ outputSuccess(await formatActivity(a));
93
+ }));
94
+ activities
95
+ .command("create <sessionId>")
96
+ .description("Emit an activity into an agent session")
97
+ .requiredOption("--type <type>", `One of: ${VALID_TYPES.join(", ")}`)
98
+ .option("--body <text>", "Body text (required for thought/elicitation/response/error)")
99
+ .option("--action <name>", "Action name (required for type=action)")
100
+ .option("--parameter <text>", "Action parameter (free-form)")
101
+ .option("--result <json>", "Action result (JSON string)")
102
+ .option("--signal <signal>", "elicitation only: select | auth | continue | stop")
103
+ .option("--signal-metadata <jsonFile>", "Path to JSON metadata file for signal")
104
+ .option("--ephemeral", "Activity disappears after the next one")
105
+ .action(handleAsyncCommand(async (sessionId, opts) => {
106
+ const type = String(opts.type);
107
+ if (!VALID_TYPES.includes(type)) {
108
+ throw new ValidationError(`Invalid --type "${type}". Must be one of: ${VALID_TYPES.join(", ")}.`);
109
+ }
110
+ if (opts.signal && type !== "elicitation") {
111
+ throw new ValidationError("--signal is only valid with --type elicitation.");
112
+ }
113
+ const content = buildContent(type, typeof opts.body === "string" ? opts.body : "", typeof opts.action === "string" ? opts.action : undefined, typeof opts.parameter === "string" ? opts.parameter : undefined, typeof opts.result === "string" ? opts.result : undefined);
114
+ const input = { agentSessionId: sessionId, content };
115
+ if (opts.ephemeral)
116
+ input.ephemeral = true;
117
+ if (opts.signal) {
118
+ const sig = String(opts.signal).toLowerCase();
119
+ if (sig === "select")
120
+ input.signal = AgentActivitySignal.Select;
121
+ else if (sig === "auth")
122
+ input.signal = AgentActivitySignal.Auth;
123
+ else if (sig === "continue")
124
+ input.signal = AgentActivitySignal.Continue;
125
+ else if (sig === "stop")
126
+ input.signal = AgentActivitySignal.Stop;
127
+ else
128
+ throw new ValidationError(`Invalid --signal "${opts.signal}". Use select, auth, continue, or stop.`);
129
+ }
130
+ if (typeof opts.signalMetadata === "string") {
131
+ input.signalMetadata = readSignalMetadata(opts.signalMetadata);
132
+ }
133
+ const client = await getClient();
134
+ const payload = await client.createAgentActivity(input);
135
+ if (!payload.success)
136
+ throw new CliError("Failed to create agent activity");
137
+ const activity = await payload.agentActivity;
138
+ outputSuccess({
139
+ created: true,
140
+ activity: activity ? await formatActivity(activity) : null,
141
+ });
142
+ }));
143
+ }
144
+ //# sourceMappingURL=agent-activities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-activities.js","sourceRoot":"","sources":["../../src/commands/agent-activities.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Command } from "commander";
2
+ export declare function setupAgentSessionsCommand(program: Command): void;
3
+ //# sourceMappingURL=agent-sessions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-sessions.d.ts","sourceRoot":"","sources":["../../src/commands/agent-sessions.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA4BzC,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAgHhE"}
@@ -0,0 +1,132 @@
1
+ // `elnora-linear agent-sessions` — Linear agent framework session lifecycle.
2
+ //
3
+ // Sessions are auto-created by Linear when an agent is @mentioned or assigned
4
+ // an issue. The CLI can also create them explicitly for testing the activity
5
+ // emitter.
6
+ import { getClient } from "../client/index.js";
7
+ import { handleAsyncCommand, outputSuccess } from "../output/index.js";
8
+ import { CliError, findIssueByIdentifier, parseLimit } from "../utils/index.js";
9
+ async function formatSession(s) {
10
+ const [appUser, creator, issue, comment] = await Promise.all([s.appUser, s.creator, s.issue, s.comment]);
11
+ return {
12
+ id: s.id,
13
+ appUser: appUser?.name ?? null,
14
+ creator: creator?.name ?? null,
15
+ issue: issue?.identifier ?? null,
16
+ comment: comment?.id ?? null,
17
+ type: s.type ?? null,
18
+ status: s.status ?? null,
19
+ startedAt: s.startedAt ?? null,
20
+ endedAt: s.endedAt ?? null,
21
+ dismissedAt: s.dismissedAt ?? null,
22
+ externalLink: s.externalLink ?? null,
23
+ createdAt: s.createdAt,
24
+ };
25
+ }
26
+ export function setupAgentSessionsCommand(program) {
27
+ const sessions = program.command("agent-sessions").description("Manage Linear agent sessions (agent framework)");
28
+ sessions
29
+ .command("list")
30
+ .description("List agent sessions, newest first")
31
+ .option("--limit <n>", "Max results", "50")
32
+ .action(handleAsyncCommand(async (opts) => {
33
+ const client = await getClient();
34
+ const conn = await client.agentSessions({ first: parseLimit(opts.limit) });
35
+ const items = await Promise.all(conn.nodes.map(formatSession));
36
+ outputSuccess({ sessions: items, count: items.length });
37
+ }));
38
+ sessions
39
+ .command("get <id>")
40
+ .description("Get details of a single agent session")
41
+ .action(handleAsyncCommand(async (id) => {
42
+ const client = await getClient();
43
+ const session = await client.agentSession(id);
44
+ outputSuccess(await formatSession(session));
45
+ }));
46
+ sessions
47
+ .command("create-on-issue <issueId>")
48
+ .description("Create an agent session on an issue (issueId can be ENG-123 or UUID)")
49
+ .option("--external-link <url>", "External agent-hosted page URL")
50
+ .action(handleAsyncCommand(async (issueId, opts) => {
51
+ const client = await getClient();
52
+ const issue = await findIssueByIdentifier(client, issueId);
53
+ const input = { issueId: issue.id };
54
+ if (opts.externalLink)
55
+ input.externalLink = opts.externalLink;
56
+ const payload = await client.agentSessionCreateOnIssue(input);
57
+ if (!payload.success)
58
+ throw new CliError("Failed to create agent session");
59
+ const session = await payload.agentSession;
60
+ outputSuccess({
61
+ created: true,
62
+ session: session ? await formatSession(session) : null,
63
+ });
64
+ }));
65
+ sessions
66
+ .command("create-on-comment <commentId>")
67
+ .description("Create an agent session on a comment thread (commentId is a UUID)")
68
+ .option("--external-link <url>", "External agent-hosted page URL")
69
+ .action(handleAsyncCommand(async (commentId, opts) => {
70
+ const client = await getClient();
71
+ const input = { commentId };
72
+ if (opts.externalLink)
73
+ input.externalLink = opts.externalLink;
74
+ const payload = await client.agentSessionCreateOnComment(input);
75
+ if (!payload.success)
76
+ throw new CliError("Failed to create agent session");
77
+ const session = await payload.agentSession;
78
+ outputSuccess({
79
+ created: true,
80
+ session: session ? await formatSession(session) : null,
81
+ });
82
+ }));
83
+ sessions
84
+ .command("update <id>")
85
+ .description("Update an agent session (replace plan, externalLink, etc — owner-app only)")
86
+ .option("--external-link <url>", "Replace external link")
87
+ .option("--plan <jsonString>", "Replace plan (JSON object as string)")
88
+ .action(handleAsyncCommand(async (id, opts) => {
89
+ const client = await getClient();
90
+ const update = {};
91
+ if (opts.externalLink)
92
+ update.externalLink = opts.externalLink;
93
+ if (opts.plan) {
94
+ try {
95
+ update.plan = JSON.parse(opts.plan);
96
+ }
97
+ catch (e) {
98
+ const msg = e instanceof Error ? e.message : String(e);
99
+ throw new CliError(`Invalid --plan JSON: ${msg}`);
100
+ }
101
+ }
102
+ const payload = await client.updateAgentSession(id, update);
103
+ if (!payload.success)
104
+ throw new CliError("Failed to update agent session");
105
+ const session = await payload.agentSession;
106
+ outputSuccess({
107
+ updated: true,
108
+ session: session ? await formatSession(session) : null,
109
+ });
110
+ }));
111
+ sessions
112
+ .command("update-external-url <id>")
113
+ .description("Add/remove/replace external URLs on a session")
114
+ .option("--external-link <url>", "Replace primary external link")
115
+ .option("--add <url>", "Add a single external URL (label = url)")
116
+ .option("--remove <url>", "Remove a single external URL by URL string")
117
+ .action(handleAsyncCommand(async (id, opts) => {
118
+ const client = await getClient();
119
+ const input = {};
120
+ if (opts.externalLink)
121
+ input.externalLink = opts.externalLink;
122
+ if (opts.add)
123
+ input.addedExternalUrls = [{ url: opts.add, label: opts.add }];
124
+ if (opts.remove)
125
+ input.removedExternalUrls = [opts.remove];
126
+ const payload = await client.agentSessionUpdateExternalUrl(id, input);
127
+ if (!payload.success)
128
+ throw new CliError("Failed to update external URL");
129
+ outputSuccess({ updated: true, id });
130
+ }));
131
+ }
132
+ //# sourceMappingURL=agent-sessions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-sessions.js","sourceRoot":"","sources":["../../src/commands/agent-sessions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Command } from "commander";
2
+ export declare function setupAttachmentsCommand(program: Command): void;
3
+ //# sourceMappingURL=attachments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachments.d.ts","sourceRoot":"","sources":["../../src/commands/attachments.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmCzC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAgQ9D"}
@@ -0,0 +1,265 @@
1
+ // `elnora-linear attachments` — manage issue attachments + integration links.
2
+ //
3
+ // `upload` enforces a path safety check: --file must resolve (via realpathSync)
4
+ // under the configured upload root (default $LINEAR_UPLOAD_ROOT or cwd).
5
+ // Symlinks pointing out of the root are rejected. A prompt-injected agent
6
+ // cannot use `attachments upload` to exfiltrate ~/.ssh/id_rsa, ~/.aws/creds,
7
+ // etc.
8
+ import { readFileSync, realpathSync } from "node:fs";
9
+ import { sep as pathSep, resolve as resolvePath } from "node:path";
10
+ import { getClient } from "../client/index.js";
11
+ import { handleAsyncCommand, outputSuccess } from "../output/index.js";
12
+ import { CliError, findIssueByIdentifier, requireYes, ValidationError } from "../utils/index.js";
13
+ function resolveUploadPath(filePath, allowRoot) {
14
+ const root = resolvePath(allowRoot ?? process.env.LINEAR_UPLOAD_ROOT ?? process.cwd());
15
+ const resolvedFile = resolvePath(filePath);
16
+ let realFile;
17
+ let realRoot;
18
+ try {
19
+ realFile = realpathSync(resolvedFile);
20
+ }
21
+ catch (e) {
22
+ const msg = e instanceof Error ? e.message : String(e);
23
+ throw new CliError(`Cannot resolve file "${filePath}": ${msg}`, {
24
+ suggestion: "Check that the file path is correct and you have read permission.",
25
+ });
26
+ }
27
+ try {
28
+ realRoot = realpathSync(root);
29
+ }
30
+ catch (e) {
31
+ const msg = e instanceof Error ? e.message : String(e);
32
+ throw new CliError(`Upload root "${root}" does not exist: ${msg}`);
33
+ }
34
+ if (realFile !== realRoot && !realFile.startsWith(realRoot + pathSep)) {
35
+ throw new ValidationError(`File "${filePath}" resolves to "${realFile}" which is outside the allowed upload root "${realRoot}".`, "Move the file under the allowed root, set LINEAR_UPLOAD_ROOT, or pass --allow-root <path>.");
36
+ }
37
+ return realFile;
38
+ }
39
+ export function setupAttachmentsCommand(program) {
40
+ const attachments = program.command("attachments").description("Manage issue attachments");
41
+ attachments
42
+ .command("get <id>")
43
+ .description("Get attachment details by ID")
44
+ .action(handleAsyncCommand(async (id) => {
45
+ const client = await getClient();
46
+ const attachment = await client.attachment(id);
47
+ outputSuccess({
48
+ id: attachment.id,
49
+ title: attachment.title ?? null,
50
+ subtitle: attachment.subtitle ?? null,
51
+ url: attachment.url,
52
+ metadata: attachment.metadata ?? null,
53
+ createdAt: attachment.createdAt,
54
+ });
55
+ }));
56
+ attachments
57
+ .command("list <issueId>")
58
+ .description("List attachments on an issue")
59
+ .action(handleAsyncCommand(async (issueId) => {
60
+ const client = await getClient();
61
+ const issue = await findIssueByIdentifier(client, issueId);
62
+ const result = await issue.attachments();
63
+ const items = result.nodes.map((a) => ({
64
+ id: a.id,
65
+ title: a.title ?? null,
66
+ subtitle: a.subtitle ?? null,
67
+ url: a.url,
68
+ createdAt: a.createdAt,
69
+ }));
70
+ outputSuccess({ attachments: items, count: items.length });
71
+ }));
72
+ attachments
73
+ .command("create <issueId>")
74
+ .description("Create an attachment on an issue")
75
+ .requiredOption("--url <url>", "Attachment URL")
76
+ .requiredOption("--title <title>", "Attachment title")
77
+ .option("--subtitle <subtitle>", "Attachment subtitle")
78
+ .option("--icon <icon>", "Icon URL or emoji")
79
+ .action(handleAsyncCommand(async (issueId, opts) => {
80
+ const client = await getClient();
81
+ const issue = await findIssueByIdentifier(client, issueId);
82
+ const input = { issueId: issue.id, url: opts.url, title: opts.title };
83
+ if (opts.subtitle)
84
+ input.subtitle = opts.subtitle;
85
+ if (opts.icon)
86
+ input.iconUrl = opts.icon;
87
+ const payload = await client.createAttachment(input);
88
+ if (!payload.success)
89
+ throw new CliError("Failed to create attachment");
90
+ const attachment = await payload.attachment;
91
+ outputSuccess({
92
+ created: true,
93
+ attachment: attachment ? { id: attachment.id, title: attachment.title, url: attachment.url } : null,
94
+ });
95
+ }));
96
+ attachments
97
+ .command("upload <issueId>")
98
+ .description("Upload a file as attachment. File must live under the allowed root (default: cwd).")
99
+ .requiredOption("--file <path>", "Local file path (must be under allowed root)")
100
+ .option("--filename <name>", "Filename (e.g., screenshot.png) — required")
101
+ .option("--content-type <mime>", "MIME type (e.g., image/png) — required")
102
+ .option("--title <title>", "Attachment title")
103
+ .option("--allow-root <path>", "Override allowed upload root (default: $LINEAR_UPLOAD_ROOT or cwd)")
104
+ .action(handleAsyncCommand(async (issueId, opts) => {
105
+ const filePath = resolveUploadPath(opts.file, opts.allowRoot);
106
+ if (!opts.filename)
107
+ throw new ValidationError("--filename is required.");
108
+ if (!opts.contentType)
109
+ throw new ValidationError("--content-type is required.");
110
+ const client = await getClient();
111
+ const issue = await findIssueByIdentifier(client, issueId);
112
+ let fileContent;
113
+ try {
114
+ fileContent = readFileSync(filePath);
115
+ }
116
+ catch (e) {
117
+ const msg = e instanceof Error ? e.message : String(e);
118
+ throw new CliError(`Cannot read file "${opts.file}": ${msg}`, {
119
+ suggestion: "Check that the file path is correct and you have read permission.",
120
+ });
121
+ }
122
+ const MAX_UPLOAD_SIZE = 100 * 1024 * 1024;
123
+ if (fileContent.length > MAX_UPLOAD_SIZE) {
124
+ throw new CliError(`File too large (${fileContent.length} bytes, max ${MAX_UPLOAD_SIZE})`);
125
+ }
126
+ const uploadPayload = await client.fileUpload(opts.contentType, opts.filename, fileContent.length);
127
+ if (!uploadPayload.success)
128
+ throw new CliError("Failed to get upload URL");
129
+ const uploadFile = uploadPayload.uploadFile;
130
+ if (!uploadFile)
131
+ throw new CliError("No upload URL returned");
132
+ if (!uploadFile.uploadUrl.startsWith("https://")) {
133
+ throw new CliError("Upload URL must use HTTPS");
134
+ }
135
+ const allowedHeaderKeys = new Set([
136
+ "content-type",
137
+ "content-disposition",
138
+ "cache-control",
139
+ "x-amz-acl",
140
+ "x-goog-content-length-range",
141
+ "x-goog-acl",
142
+ ]);
143
+ const headers = { "Content-Type": opts.contentType };
144
+ for (const header of uploadFile.headers) {
145
+ if (allowedHeaderKeys.has(header.key.toLowerCase())) {
146
+ headers[header.key] = header.value;
147
+ }
148
+ }
149
+ const uploadBody = Uint8Array.from(fileContent);
150
+ // Bound the upload to UPLOAD_TIMEOUT_MS so a hung S3/GCS PUT can't
151
+ // stall the CLI indefinitely. 100MB max body / 5 min ≈ 280 KB/s,
152
+ // safely below the slowest plausible connection we'd want to wait on.
153
+ const UPLOAD_TIMEOUT_MS = 5 * 60 * 1000;
154
+ let response;
155
+ try {
156
+ response = await fetch(uploadFile.uploadUrl, {
157
+ method: "PUT",
158
+ headers,
159
+ body: uploadBody,
160
+ signal: AbortSignal.timeout(UPLOAD_TIMEOUT_MS),
161
+ });
162
+ }
163
+ catch (err) {
164
+ const msg = err instanceof Error ? err.message : String(err);
165
+ throw new CliError(`Upload failed: ${msg}`);
166
+ }
167
+ if (!response.ok)
168
+ throw new CliError(`Upload failed: ${response.statusText}`);
169
+ const attachInput = {
170
+ issueId: issue.id,
171
+ url: uploadFile.assetUrl,
172
+ title: opts.title ?? opts.filename,
173
+ };
174
+ const attachPayload = await client.createAttachment(attachInput);
175
+ if (!attachPayload.success) {
176
+ throw new CliError("File was uploaded but could not be attached to the issue.", {
177
+ suggestion: `The file is available at ${uploadFile.assetUrl}. Try creating the attachment manually.`,
178
+ });
179
+ }
180
+ outputSuccess({ uploaded: true, url: uploadFile.assetUrl });
181
+ }));
182
+ attachments
183
+ .command("delete <id>")
184
+ .description("Permanently delete an attachment (irreversible — requires --yes)")
185
+ .option("--yes", "Confirm permanent deletion")
186
+ .action(handleAsyncCommand(async (id, opts) => {
187
+ requireYes(opts, `permanently delete attachment ${id}`);
188
+ const client = await getClient();
189
+ const payload = await client.deleteAttachment(id);
190
+ outputSuccess({ deleted: payload.success });
191
+ }));
192
+ async function outputLinkResult(payload, integration) {
193
+ if (!payload.success)
194
+ throw new CliError(`Failed to link ${integration} attachment`);
195
+ const attachment = await payload.attachment;
196
+ outputSuccess({
197
+ linked: true,
198
+ integration,
199
+ attachment: attachment ? { id: attachment.id, title: attachment.title, url: attachment.url } : null,
200
+ });
201
+ }
202
+ attachments
203
+ .command("link-github-pr <issueId>")
204
+ .description("Link a GitHub pull request as an integration-aware attachment")
205
+ .requiredOption("--url <url>", "GitHub pull request URL")
206
+ .action(handleAsyncCommand(async (issueId, opts) => {
207
+ const client = await getClient();
208
+ const issue = await findIssueByIdentifier(client, issueId);
209
+ const payload = await client.attachmentLinkGitHubPR(issue.id, opts.url);
210
+ await outputLinkResult(payload, "github-pr");
211
+ }));
212
+ attachments
213
+ .command("link-slack <issueId>")
214
+ .description("Link a Slack message as an integration-aware attachment")
215
+ .requiredOption("--url <url>", "Slack permalink (https://*.slack.com/archives/...)")
216
+ .action(handleAsyncCommand(async (issueId, opts) => {
217
+ const client = await getClient();
218
+ const issue = await findIssueByIdentifier(client, issueId);
219
+ const payload = await client.attachmentLinkSlack(issue.id, opts.url);
220
+ await outputLinkResult(payload, "slack");
221
+ }));
222
+ attachments
223
+ .command("link-jira <issueId>")
224
+ .description("Link a Jira issue as an integration-aware attachment")
225
+ .requiredOption("--jira-issue-id <id>", "Jira issue ID")
226
+ .action(handleAsyncCommand(async (issueId, opts) => {
227
+ const client = await getClient();
228
+ const issue = await findIssueByIdentifier(client, issueId);
229
+ const payload = await client.attachmentLinkJiraIssue(issue.id, opts.jiraIssueId);
230
+ await outputLinkResult(payload, "jira");
231
+ }));
232
+ attachments
233
+ .command("link-url <issueId>")
234
+ .description("Link an arbitrary URL as an integration-aware attachment (dedup on issueId+url)")
235
+ .requiredOption("--url <url>", "URL to link")
236
+ .action(handleAsyncCommand(async (issueId, opts) => {
237
+ const client = await getClient();
238
+ const issue = await findIssueByIdentifier(client, issueId);
239
+ const payload = await client.attachmentLinkURL(issue.id, opts.url);
240
+ await outputLinkResult(payload, "url");
241
+ }));
242
+ attachments
243
+ .command("link-discord <issueId>")
244
+ .description("Link a Discord message as an integration-aware attachment")
245
+ .requiredOption("--channel-id <id>", "Discord channel ID")
246
+ .requiredOption("--message-id <id>", "Discord message ID")
247
+ .requiredOption("--url <url>", "Discord message URL")
248
+ .action(handleAsyncCommand(async (issueId, opts) => {
249
+ const client = await getClient();
250
+ const issue = await findIssueByIdentifier(client, issueId);
251
+ const payload = await client.attachmentLinkDiscord(opts.channelId, issue.id, opts.messageId, opts.url);
252
+ await outputLinkResult(payload, "discord");
253
+ }));
254
+ attachments
255
+ .command("link-zendesk <issueId>")
256
+ .description("Link a Zendesk ticket as an integration-aware attachment")
257
+ .requiredOption("--ticket-id <id>", "Zendesk ticket ID")
258
+ .action(handleAsyncCommand(async (issueId, opts) => {
259
+ const client = await getClient();
260
+ const issue = await findIssueByIdentifier(client, issueId);
261
+ const payload = await client.attachmentLinkZendesk(issue.id, opts.ticketId);
262
+ await outputLinkResult(payload, "zendesk");
263
+ }));
264
+ }
265
+ //# sourceMappingURL=attachments.js.map