@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.
- package/.claude-plugin/marketplace.json +7 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +25 -1
- package/README.md +275 -25
- package/agents/linear-issue-creator.md +135 -17
- package/agents/linear-issue-reviewer.md +122 -23
- package/agents/linear-issue-updater.md +137 -25
- package/agents/linear-state-curator.md +173 -0
- package/agents/linear-url-to-issues.md +190 -26
- package/commands/linear-cleanup.md +64 -29
- package/dist/cli.js +69 -1
- package/dist/cli.js.map +1 -1
- package/dist/client/auth.d.ts +10 -0
- package/dist/client/auth.d.ts.map +1 -1
- package/dist/client/auth.js +50 -3
- package/dist/client/auth.js.map +1 -1
- package/dist/client/linear-client.d.ts +7 -0
- package/dist/client/linear-client.d.ts.map +1 -1
- package/dist/client/linear-client.js +13 -1
- package/dist/client/linear-client.js.map +1 -1
- package/dist/commands/agent-activities.d.ts +3 -0
- package/dist/commands/agent-activities.d.ts.map +1 -0
- package/dist/commands/agent-activities.js +144 -0
- package/dist/commands/agent-activities.js.map +1 -0
- package/dist/commands/agent-sessions.d.ts +3 -0
- package/dist/commands/agent-sessions.d.ts.map +1 -0
- package/dist/commands/agent-sessions.js +132 -0
- package/dist/commands/agent-sessions.js.map +1 -0
- package/dist/commands/attachments.d.ts +3 -0
- package/dist/commands/attachments.d.ts.map +1 -0
- package/dist/commands/attachments.js +265 -0
- package/dist/commands/attachments.js.map +1 -0
- package/dist/commands/audit.d.ts +3 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +73 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/comments.d.ts +3 -0
- package/dist/commands/comments.d.ts.map +1 -0
- package/dist/commands/comments.js +107 -0
- package/dist/commands/comments.js.map +1 -0
- package/dist/commands/completion.d.ts +3 -0
- package/dist/commands/completion.d.ts.map +1 -0
- package/dist/commands/completion.js +62 -0
- package/dist/commands/completion.js.map +1 -0
- package/dist/commands/context.d.ts +3 -0
- package/dist/commands/context.d.ts.map +1 -0
- package/dist/commands/context.js +94 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/curator.d.ts +14 -0
- package/dist/commands/curator.d.ts.map +1 -1
- package/dist/commands/curator.js +97 -19
- package/dist/commands/curator.js.map +1 -1
- package/dist/commands/customer-needs.d.ts +3 -0
- package/dist/commands/customer-needs.d.ts.map +1 -0
- package/dist/commands/customer-needs.js +198 -0
- package/dist/commands/customer-needs.js.map +1 -0
- package/dist/commands/customers.d.ts +5 -0
- package/dist/commands/customers.d.ts.map +1 -0
- package/dist/commands/customers.js +201 -0
- package/dist/commands/customers.js.map +1 -0
- package/dist/commands/cycles.d.ts +3 -0
- package/dist/commands/cycles.d.ts.map +1 -0
- package/dist/commands/cycles.js +67 -0
- package/dist/commands/cycles.js.map +1 -0
- package/dist/commands/documents.d.ts +3 -0
- package/dist/commands/documents.d.ts.map +1 -0
- package/dist/commands/documents.js +105 -0
- package/dist/commands/documents.js.map +1 -0
- package/dist/commands/favorites.d.ts +3 -0
- package/dist/commands/favorites.d.ts.map +1 -0
- package/dist/commands/favorites.js +101 -0
- package/dist/commands/favorites.js.map +1 -0
- package/dist/commands/index.d.ts +30 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +30 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/initiatives.d.ts +3 -0
- package/dist/commands/initiatives.d.ts.map +1 -0
- package/dist/commands/initiatives.js +106 -0
- package/dist/commands/initiatives.js.map +1 -0
- package/dist/commands/issues.d.ts +21 -0
- package/dist/commands/issues.d.ts.map +1 -0
- package/dist/commands/issues.js +1083 -0
- package/dist/commands/issues.js.map +1 -0
- package/dist/commands/labels.d.ts +3 -0
- package/dist/commands/labels.d.ts.map +1 -0
- package/dist/commands/labels.js +111 -0
- package/dist/commands/labels.js.map +1 -0
- package/dist/commands/milestones.d.ts +3 -0
- package/dist/commands/milestones.d.ts.map +1 -0
- package/dist/commands/milestones.js +94 -0
- package/dist/commands/milestones.js.map +1 -0
- package/dist/commands/notifications.d.ts +3 -0
- package/dist/commands/notifications.d.ts.map +1 -0
- package/dist/commands/notifications.js +130 -0
- package/dist/commands/notifications.js.map +1 -0
- package/dist/commands/project-labels.d.ts +3 -0
- package/dist/commands/project-labels.d.ts.map +1 -0
- package/dist/commands/project-labels.js +80 -0
- package/dist/commands/project-labels.js.map +1 -0
- package/dist/commands/project-relations.d.ts +3 -0
- package/dist/commands/project-relations.d.ts.map +1 -0
- package/dist/commands/project-relations.js +96 -0
- package/dist/commands/project-relations.js.map +1 -0
- package/dist/commands/projects.d.ts +3 -0
- package/dist/commands/projects.d.ts.map +1 -0
- package/dist/commands/projects.js +263 -0
- package/dist/commands/projects.js.map +1 -0
- package/dist/commands/quota.d.ts +3 -0
- package/dist/commands/quota.d.ts.map +1 -0
- package/dist/commands/quota.js +28 -0
- package/dist/commands/quota.js.map +1 -0
- package/dist/commands/reactions.d.ts +7 -0
- package/dist/commands/reactions.d.ts.map +1 -0
- package/dist/commands/reactions.js +53 -0
- package/dist/commands/reactions.js.map +1 -0
- package/dist/commands/relations.d.ts +3 -0
- package/dist/commands/relations.d.ts.map +1 -0
- package/dist/commands/relations.js +73 -0
- package/dist/commands/relations.js.map +1 -0
- package/dist/commands/states.d.ts +3 -0
- package/dist/commands/states.d.ts.map +1 -0
- package/dist/commands/states.js +52 -0
- package/dist/commands/states.js.map +1 -0
- package/dist/commands/status-updates.d.ts +3 -0
- package/dist/commands/status-updates.d.ts.map +1 -0
- package/dist/commands/status-updates.js +117 -0
- package/dist/commands/status-updates.js.map +1 -0
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +58 -18
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/teams.d.ts +3 -0
- package/dist/commands/teams.d.ts.map +1 -0
- package/dist/commands/teams.js +135 -0
- package/dist/commands/teams.js.map +1 -0
- package/dist/commands/templates.d.ts +3 -0
- package/dist/commands/templates.d.ts.map +1 -0
- package/dist/commands/templates.js +76 -0
- package/dist/commands/templates.js.map +1 -0
- package/dist/commands/users.d.ts +3 -0
- package/dist/commands/users.d.ts.map +1 -0
- package/dist/commands/users.js +40 -0
- package/dist/commands/users.js.map +1 -0
- package/dist/commands/views.d.ts +3 -0
- package/dist/commands/views.d.ts.map +1 -0
- package/dist/commands/views.js +177 -0
- package/dist/commands/views.js.map +1 -0
- package/dist/commands/webhooks.d.ts +3 -0
- package/dist/commands/webhooks.d.ts.map +1 -0
- package/dist/commands/webhooks.js +234 -0
- package/dist/commands/webhooks.js.map +1 -0
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +3 -0
- package/dist/config/loader.js.map +1 -1
- package/dist/config/types.d.ts +15 -1
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +1 -0
- package/dist/config/types.js.map +1 -1
- package/dist/curator/dispatch.d.ts +52 -0
- package/dist/curator/dispatch.d.ts.map +1 -0
- package/dist/curator/dispatch.js +144 -0
- package/dist/curator/dispatch.js.map +1 -0
- package/dist/curator/index.d.ts +5 -0
- package/dist/curator/index.d.ts.map +1 -0
- package/dist/curator/index.js +5 -0
- package/dist/curator/index.js.map +1 -0
- package/dist/curator/llm.d.ts +70 -0
- package/dist/curator/llm.d.ts.map +1 -0
- package/dist/curator/llm.js +107 -0
- package/dist/curator/llm.js.map +1 -0
- package/dist/curator/snapshot.d.ts +34 -0
- package/dist/curator/snapshot.d.ts.map +1 -0
- package/dist/curator/snapshot.js +127 -0
- package/dist/curator/snapshot.js.map +1 -0
- package/dist/curator/state.d.ts +50 -0
- package/dist/curator/state.d.ts.map +1 -0
- package/dist/curator/state.js +125 -0
- package/dist/curator/state.js.map +1 -0
- package/dist/lib/bulk-graphql.d.ts +144 -0
- package/dist/lib/bulk-graphql.d.ts.map +1 -0
- package/dist/lib/bulk-graphql.js +380 -0
- package/dist/lib/bulk-graphql.js.map +1 -0
- package/dist/lib/index.d.ts +2 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +2 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/output/cli.d.ts +17 -0
- package/dist/output/cli.d.ts.map +1 -0
- package/dist/output/cli.js +252 -0
- package/dist/output/cli.js.map +1 -0
- package/dist/output/formatter.d.ts +6 -0
- package/dist/output/formatter.d.ts.map +1 -1
- package/dist/output/formatter.js +10 -0
- package/dist/output/formatter.js.map +1 -1
- package/dist/output/index.d.ts +1 -0
- package/dist/output/index.d.ts.map +1 -1
- package/dist/output/index.js +1 -0
- package/dist/output/index.js.map +1 -1
- package/dist/scripts/sync-linear-templates.d.ts +26 -0
- package/dist/scripts/sync-linear-templates.d.ts.map +1 -0
- package/dist/scripts/sync-linear-templates.js +115 -0
- package/dist/scripts/sync-linear-templates.js.map +1 -0
- package/dist/signals/github-commits.d.ts +31 -0
- package/dist/signals/github-commits.d.ts.map +1 -0
- package/dist/signals/github-commits.js +127 -0
- package/dist/signals/github-commits.js.map +1 -0
- package/dist/signals/github-pr.d.ts +16 -0
- package/dist/signals/github-pr.d.ts.map +1 -0
- package/dist/signals/github-pr.js +98 -0
- package/dist/signals/github-pr.js.map +1 -0
- package/dist/signals/index.d.ts +4 -0
- package/dist/signals/index.d.ts.map +1 -1
- package/dist/signals/index.js +4 -0
- package/dist/signals/index.js.map +1 -1
- package/dist/signals/linear-issues.d.ts +20 -0
- package/dist/signals/linear-issues.d.ts.map +1 -0
- package/dist/signals/linear-issues.js +115 -0
- package/dist/signals/linear-issues.js.map +1 -0
- package/dist/signals/registry.d.ts +4 -3
- package/dist/signals/registry.d.ts.map +1 -1
- package/dist/signals/registry.js +33 -11
- package/dist/signals/registry.js.map +1 -1
- package/dist/signals/slack-messages.d.ts +20 -0
- package/dist/signals/slack-messages.d.ts.map +1 -0
- package/dist/signals/slack-messages.js +129 -0
- package/dist/signals/slack-messages.js.map +1 -0
- package/dist/utils/errors.d.ts +81 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +110 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/index.d.ts +9 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +9 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/label-policy.d.ts +60 -0
- package/dist/utils/label-policy.d.ts.map +1 -0
- package/dist/utils/label-policy.js +103 -0
- package/dist/utils/label-policy.js.map +1 -0
- package/dist/utils/parse.d.ts +48 -0
- package/dist/utils/parse.d.ts.map +1 -0
- package/dist/utils/parse.js +133 -0
- package/dist/utils/parse.js.map +1 -0
- package/dist/utils/project-status.d.ts +6 -0
- package/dist/utils/project-status.d.ts.map +1 -0
- package/dist/utils/project-status.js +33 -0
- package/dist/utils/project-status.js.map +1 -0
- package/dist/utils/rate-limit.d.ts +24 -0
- package/dist/utils/rate-limit.d.ts.map +1 -0
- package/dist/utils/rate-limit.js +89 -0
- package/dist/utils/rate-limit.js.map +1 -0
- package/dist/utils/resolve.d.ts +84 -0
- package/dist/utils/resolve.d.ts.map +1 -0
- package/dist/utils/resolve.js +172 -0
- package/dist/utils/resolve.js.map +1 -0
- package/dist/utils/sleep.d.ts +2 -0
- package/dist/utils/sleep.d.ts.map +1 -0
- package/dist/utils/sleep.js +4 -0
- package/dist/utils/sleep.js.map +1 -0
- package/dist/utils/webhook-verify.d.ts +42 -0
- package/dist/utils/webhook-verify.d.ts.map +1 -0
- package/dist/utils/webhook-verify.js +65 -0
- package/dist/utils/webhook-verify.js.map +1 -0
- package/package.json +7 -2
- package/references/agent-description-template.md +31 -0
- package/references/cli-reference.md +227 -0
- package/references/curator-tiering-rules.md +78 -0
- package/references/label-policy.example.json +37 -0
- package/references/label-policy.placeholder.json +6 -0
- package/references/settings-template.md +30 -0
- package/references/signal-sources.example.json +0 -8
- package/references/sla-reference.md +70 -0
- package/references/template-index.md +34 -0
- package/references/workspace-labels.md +124 -0
- package/references/workspace-projects.md +56 -0
- package/references/workspace-routing.md +58 -0
- package/schemas/label-policy.json +72 -0
- package/scripts/postinstall.mjs +195 -0
- package/skills/linear-workspace/SKILL.md +65 -4
- package/templates/ACC-PRO-provision.md +74 -0
- package/templates/ACC-PRV-privileged.md +66 -0
- package/templates/ACC-QTR-review.md +77 -0
- package/templates/ACC-REV-revoke.md +67 -0
- package/templates/AI-USE-capability.md +111 -0
- package/templates/AUD-CAP-corrective.md +89 -0
- package/templates/AUD-INT-internal.md +92 -0
- package/templates/AUD-MGT-management.md +110 -0
- package/templates/CHG-MAJ-major.md +110 -0
- package/templates/CHG-SIG-significant.md +83 -0
- package/templates/CHG-STD-standard.md +47 -0
- package/templates/LRN-DOC-lessons.md +75 -0
- package/templates/OPS-BCK-backup.md +99 -0
- package/templates/OPS-DAT-data-mod.md +98 -0
- package/templates/RCA-DOC-root-cause.md +105 -0
- package/templates/RSK-ASS-assessment.md +87 -0
- package/templates/RSK-VND-vendor.md +113 -0
- package/templates/SEC-INC-incident.md +76 -0
- package/templates/SEC-PEN-pentest.md +58 -0
- package/templates/SEC-VLN-vulnerability.md +69 -0
- package/templates/SLA-AVL-availability.md +86 -0
- package/templates/SLA-OPS-operational.md +70 -0
- package/templates/agent-server-template/README.md +88 -0
- 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":"
|
|
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;
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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
|