@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
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
declare function isRateLimitError(error: unknown): boolean;
|
|
2
|
+
/**
|
|
3
|
+
* Pull the retry delay (ms) out of a Linear SDK error.
|
|
4
|
+
*
|
|
5
|
+
* RatelimitedLinearError exposes `retryAfter` (seconds) directly — the SDK
|
|
6
|
+
* already parsed the retry-after header into a number. Earlier code tried to
|
|
7
|
+
* read `error.response.headers["retry-after"]`, but `headers` is a Fetch
|
|
8
|
+
* Headers object whose values are only readable via `.get()` — bracket access
|
|
9
|
+
* always returned undefined, so the wrapper silently fell back to the 60s
|
|
10
|
+
* default on every retry instead of honouring Linear's hint.
|
|
11
|
+
*/
|
|
12
|
+
declare function parseRetryAfter(error: unknown): number;
|
|
13
|
+
/**
|
|
14
|
+
* Run fn, retry on 429 with Retry-After. Max 3 retries — after that the
|
|
15
|
+
* original error propagates.
|
|
16
|
+
*/
|
|
17
|
+
export declare function withRateLimit<T>(fn: () => Promise<T>): Promise<T>;
|
|
18
|
+
export declare const _internal: {
|
|
19
|
+
isRateLimitError: typeof isRateLimitError;
|
|
20
|
+
parseRetryAfter: typeof parseRetryAfter;
|
|
21
|
+
MAX_RETRY_AFTER_MS: number;
|
|
22
|
+
};
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=rate-limit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/utils/rate-limit.ts"],"names":[],"mappings":"AAmBA,iBAAS,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAejD;AAED;;;;;;;;;GASG;AACH,iBAAS,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAI/C;AA2BD;;;GAGG;AACH,wBAAsB,aAAa,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAgBvE;AAGD,eAAO,MAAM,SAAS;;;;CAA4D,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Rate-limit retry wrapper for Linear SDK calls.
|
|
2
|
+
//
|
|
3
|
+
// Linear returns rate-limit errors two ways depending on the surface:
|
|
4
|
+
// - GraphQL: HTTP 400 with errors[].extensions.code === "RATELIMITED"
|
|
5
|
+
// (current canonical signal — see https://linear.app/developers/rate-limiting)
|
|
6
|
+
// - Some paths: HTTP 429
|
|
7
|
+
// The SDK normalises both into RatelimitedLinearError (errors[0].type ===
|
|
8
|
+
// "ratelimited"), exposing retryAfter (seconds) parsed from the retry-after
|
|
9
|
+
// header. We catch the SDK error, honour its retryAfter, and re-run the fn up
|
|
10
|
+
// to MAX_RETRIES times.
|
|
11
|
+
import { sleep } from "./sleep.js";
|
|
12
|
+
const MAX_RETRIES = 3;
|
|
13
|
+
const DEFAULT_RETRY_AFTER_MS = 60_000;
|
|
14
|
+
// Cap so a hostile or buggy retry-after header can't stall the process for
|
|
15
|
+
// hours. Linear's rate-limit windows reset in seconds-to-minutes.
|
|
16
|
+
const MAX_RETRY_AFTER_MS = 60_000;
|
|
17
|
+
function isRateLimitError(error) {
|
|
18
|
+
if (!error || typeof error !== "object")
|
|
19
|
+
return false;
|
|
20
|
+
const e = error;
|
|
21
|
+
return (e.name === "RatelimitedLinearError" ||
|
|
22
|
+
e.type === "Ratelimited" ||
|
|
23
|
+
e.type === "ratelimited" ||
|
|
24
|
+
e.status === 429 ||
|
|
25
|
+
e.response?.status === 429);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Pull the retry delay (ms) out of a Linear SDK error.
|
|
29
|
+
*
|
|
30
|
+
* RatelimitedLinearError exposes `retryAfter` (seconds) directly — the SDK
|
|
31
|
+
* already parsed the retry-after header into a number. Earlier code tried to
|
|
32
|
+
* read `error.response.headers["retry-after"]`, but `headers` is a Fetch
|
|
33
|
+
* Headers object whose values are only readable via `.get()` — bracket access
|
|
34
|
+
* always returned undefined, so the wrapper silently fell back to the 60s
|
|
35
|
+
* default on every retry instead of honouring Linear's hint.
|
|
36
|
+
*/
|
|
37
|
+
function parseRetryAfter(error) {
|
|
38
|
+
const raw = readRetryAfterMs(error);
|
|
39
|
+
const ms = raw ?? DEFAULT_RETRY_AFTER_MS;
|
|
40
|
+
return Math.min(ms, MAX_RETRY_AFTER_MS);
|
|
41
|
+
}
|
|
42
|
+
function readRetryAfterMs(error) {
|
|
43
|
+
if (!error || typeof error !== "object")
|
|
44
|
+
return null;
|
|
45
|
+
const e = error;
|
|
46
|
+
if (typeof e.retryAfter === "number" && e.retryAfter > 0) {
|
|
47
|
+
return e.retryAfter * 1000;
|
|
48
|
+
}
|
|
49
|
+
// Defensive fallback: also try to read the raw header in case a non-SDK
|
|
50
|
+
// caller wraps an error without the parsed retryAfter property.
|
|
51
|
+
const headers = e.response?.headers;
|
|
52
|
+
let rawHeader;
|
|
53
|
+
if (headers && typeof headers.get === "function") {
|
|
54
|
+
rawHeader = headers.get("retry-after");
|
|
55
|
+
}
|
|
56
|
+
else if (headers && typeof headers === "object") {
|
|
57
|
+
rawHeader = headers["retry-after"];
|
|
58
|
+
}
|
|
59
|
+
if (rawHeader) {
|
|
60
|
+
const seconds = parseInt(rawHeader, 10);
|
|
61
|
+
if (!Number.isNaN(seconds) && seconds > 0)
|
|
62
|
+
return seconds * 1000;
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Run fn, retry on 429 with Retry-After. Max 3 retries — after that the
|
|
68
|
+
* original error propagates.
|
|
69
|
+
*/
|
|
70
|
+
export async function withRateLimit(fn) {
|
|
71
|
+
let lastError;
|
|
72
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
73
|
+
try {
|
|
74
|
+
return await fn();
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
lastError = error;
|
|
78
|
+
if (!isRateLimitError(error) || attempt === MAX_RETRIES)
|
|
79
|
+
throw error;
|
|
80
|
+
const waitMs = parseRetryAfter(error);
|
|
81
|
+
process.stderr.write(`Linear API rate-limited: retrying in ${Math.ceil(waitMs / 1000)}s (attempt ${attempt + 1}/${MAX_RETRIES})...\n`);
|
|
82
|
+
await sleep(waitMs);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
throw lastError;
|
|
86
|
+
}
|
|
87
|
+
// Exported for testing
|
|
88
|
+
export const _internal = { isRateLimitError, parseRetryAfter, MAX_RETRY_AFTER_MS };
|
|
89
|
+
//# sourceMappingURL=rate-limit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../src/utils/rate-limit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { Issue, LinearClient } from "@linear/sdk";
|
|
2
|
+
/**
|
|
3
|
+
* Minimal interface for SDK connection objects that support pagination. The
|
|
4
|
+
* SDK exports LinearConnection (no fetchNext) and internal Connection (with
|
|
5
|
+
* fetchNext). Concrete connections returned by client methods (e.g.
|
|
6
|
+
* client.projects()) implement this.
|
|
7
|
+
*/
|
|
8
|
+
interface PaginatedConnection<T> {
|
|
9
|
+
nodes: T[];
|
|
10
|
+
pageInfo: {
|
|
11
|
+
hasNextPage: boolean;
|
|
12
|
+
};
|
|
13
|
+
fetchNext(): Promise<PaginatedConnection<T>>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Paginate through all nodes in a connection. Uses the SDK's built-in
|
|
17
|
+
* fetchNext() to break the 250-item ceiling.
|
|
18
|
+
*/
|
|
19
|
+
export declare function fetchAllNodes<T>(connection: PaginatedConnection<T>): Promise<T[]>;
|
|
20
|
+
/** True if `value` is a UUID v4-shaped string. */
|
|
21
|
+
export declare function isUUID(value: string): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Resolve a team name or key to its UUID. Accepts: team name ("Engineering"),
|
|
24
|
+
* key ("ENG"), or UUID.
|
|
25
|
+
*/
|
|
26
|
+
export declare function resolveTeam(client: LinearClient, nameOrId: string): Promise<{
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
key: string;
|
|
30
|
+
}>;
|
|
31
|
+
/** Resolve a project name to its UUID. */
|
|
32
|
+
export declare function resolveProject(client: LinearClient, nameOrId: string): Promise<{
|
|
33
|
+
id: string;
|
|
34
|
+
name: string;
|
|
35
|
+
}>;
|
|
36
|
+
/** Resolve a user name, email, or "me" to UUID. */
|
|
37
|
+
export declare function resolveUser(client: LinearClient, nameOrId: string): Promise<{
|
|
38
|
+
id: string;
|
|
39
|
+
name: string;
|
|
40
|
+
}>;
|
|
41
|
+
/** Resolve comma-separated label names to UUIDs. */
|
|
42
|
+
export declare function resolveLabels(client: LinearClient, labelNames: string): Promise<{
|
|
43
|
+
id: string;
|
|
44
|
+
name: string;
|
|
45
|
+
}[]>;
|
|
46
|
+
/** Resolve a workflow state name to UUID for a given team. */
|
|
47
|
+
export declare function resolveState(client: LinearClient, stateName: string, teamId: string): Promise<{
|
|
48
|
+
id: string;
|
|
49
|
+
name: string;
|
|
50
|
+
type: string;
|
|
51
|
+
}>;
|
|
52
|
+
/** Resolve an initiative name to its UUID. */
|
|
53
|
+
export declare function resolveInitiative(client: LinearClient, nameOrId: string): Promise<{
|
|
54
|
+
id: string;
|
|
55
|
+
name: string;
|
|
56
|
+
}>;
|
|
57
|
+
/**
|
|
58
|
+
* Parse an issue identifier like "ENG-123" into a searchable format. Returns
|
|
59
|
+
* the identifier as-is if it looks like a Linear issue ID.
|
|
60
|
+
*/
|
|
61
|
+
export declare function parseIssueIdentifier(input: string): string;
|
|
62
|
+
/**
|
|
63
|
+
* Find an issue by Linear identifier (e.g., ENG-123) or UUID.
|
|
64
|
+
*
|
|
65
|
+
* Identifiers are split into team key + number and resolved through the
|
|
66
|
+
* standard client.issues({filter}) API in a single request. Earlier this
|
|
67
|
+
* function did searchIssues + client.issue() (two requests); the filter-based
|
|
68
|
+
* path returns full Issue objects directly so connection methods (.comments(),
|
|
69
|
+
* .labels(), .relations()) work the same as before.
|
|
70
|
+
*/
|
|
71
|
+
export declare function findIssueByIdentifier(client: LinearClient, id: string): Promise<Issue>;
|
|
72
|
+
/**
|
|
73
|
+
* Resolve a project status by name (e.g. "Started") OR type
|
|
74
|
+
* (backlog|planned|started|paused|completed|canceled) to its UUID. Used by
|
|
75
|
+
* `projects update --state` since the SDK's ProjectUpdateInput takes statusId,
|
|
76
|
+
* not a state string.
|
|
77
|
+
*/
|
|
78
|
+
export declare function resolveProjectStatus(client: LinearClient, nameOrType: string): Promise<{
|
|
79
|
+
id: string;
|
|
80
|
+
name: string;
|
|
81
|
+
type: string;
|
|
82
|
+
}>;
|
|
83
|
+
export {};
|
|
84
|
+
//# sourceMappingURL=resolve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/utils/resolve.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIvD;;;;;GAKG;AACH,UAAU,mBAAmB,CAAC,CAAC;IAC9B,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,QAAQ,EAAE;QAAE,WAAW,EAAE,OAAO,CAAA;KAAE,CAAC;IACnC,SAAS,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC;CAC7C;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,CAAC,EAAE,UAAU,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAQvF;AAED,kDAAkD;AAClD,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE7C;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAChC,MAAM,EAAE,YAAY,EACpB,QAAQ,EAAE,MAAM,GACd,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAWpD;AAED,0CAA0C;AAC1C,wBAAsB,cAAc,CAAC,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAWlH;AAED,mDAAmD;AACnD,wBAAsB,WAAW,CAAC,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAe/G;AAED,oDAAoD;AACpD,wBAAsB,aAAa,CAAC,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,CAkBrH;AAED,8DAA8D;AAC9D,wBAAsB,YAAY,CACjC,MAAM,EAAE,YAAY,EACpB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACZ,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CASrD;AAED,8CAA8C;AAC9C,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAWrH;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAI1D;AAED;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAW5F;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACzC,MAAM,EAAE,YAAY,EACpB,UAAU,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAWrD"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// Batch resolution of human-readable identifiers to UUIDs.
|
|
2
|
+
// Pattern borrowed from czottmann/linearis — resolve multiple entities in
|
|
3
|
+
// minimal API calls.
|
|
4
|
+
import { NotFoundError, ValidationError } from "./errors.js";
|
|
5
|
+
/**
|
|
6
|
+
* Paginate through all nodes in a connection. Uses the SDK's built-in
|
|
7
|
+
* fetchNext() to break the 250-item ceiling.
|
|
8
|
+
*/
|
|
9
|
+
export async function fetchAllNodes(connection) {
|
|
10
|
+
let current = connection;
|
|
11
|
+
const all = [...current.nodes];
|
|
12
|
+
while (current.pageInfo.hasNextPage) {
|
|
13
|
+
current = await current.fetchNext();
|
|
14
|
+
all.push(...current.nodes);
|
|
15
|
+
}
|
|
16
|
+
return all;
|
|
17
|
+
}
|
|
18
|
+
/** True if `value` is a UUID v4-shaped string. */
|
|
19
|
+
export function isUUID(value) {
|
|
20
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Resolve a team name or key to its UUID. Accepts: team name ("Engineering"),
|
|
24
|
+
* key ("ENG"), or UUID.
|
|
25
|
+
*/
|
|
26
|
+
export async function resolveTeam(client, nameOrId) {
|
|
27
|
+
if (isUUID(nameOrId)) {
|
|
28
|
+
const team = await client.team(nameOrId);
|
|
29
|
+
return { id: team.id, name: team.name, key: team.key };
|
|
30
|
+
}
|
|
31
|
+
const connection = await client.teams({ first: 250 });
|
|
32
|
+
const allTeams = await fetchAllNodes(connection);
|
|
33
|
+
const lower = nameOrId.toLowerCase();
|
|
34
|
+
const match = allTeams.find((t) => t.name.toLowerCase() === lower || t.key.toLowerCase() === lower);
|
|
35
|
+
if (!match)
|
|
36
|
+
throw new NotFoundError("Team", nameOrId);
|
|
37
|
+
return { id: match.id, name: match.name, key: match.key };
|
|
38
|
+
}
|
|
39
|
+
/** Resolve a project name to its UUID. */
|
|
40
|
+
export async function resolveProject(client, nameOrId) {
|
|
41
|
+
if (isUUID(nameOrId)) {
|
|
42
|
+
const project = await client.project(nameOrId);
|
|
43
|
+
return { id: project.id, name: project.name };
|
|
44
|
+
}
|
|
45
|
+
const connection = await client.projects({ first: 250 });
|
|
46
|
+
const allProjects = await fetchAllNodes(connection);
|
|
47
|
+
const lower = nameOrId.toLowerCase();
|
|
48
|
+
const match = allProjects.find((p) => p.name.toLowerCase() === lower);
|
|
49
|
+
if (!match)
|
|
50
|
+
throw new NotFoundError("Project", nameOrId);
|
|
51
|
+
return { id: match.id, name: match.name };
|
|
52
|
+
}
|
|
53
|
+
/** Resolve a user name, email, or "me" to UUID. */
|
|
54
|
+
export async function resolveUser(client, nameOrId) {
|
|
55
|
+
if (nameOrId.toLowerCase() === "me") {
|
|
56
|
+
const me = await client.viewer;
|
|
57
|
+
return { id: me.id, name: me.name };
|
|
58
|
+
}
|
|
59
|
+
if (isUUID(nameOrId)) {
|
|
60
|
+
const user = await client.user(nameOrId);
|
|
61
|
+
return { id: user.id, name: user.name };
|
|
62
|
+
}
|
|
63
|
+
const connection = await client.users({ first: 250 });
|
|
64
|
+
const allUsers = await fetchAllNodes(connection);
|
|
65
|
+
const lower = nameOrId.toLowerCase();
|
|
66
|
+
const match = allUsers.find((u) => u.name.toLowerCase() === lower || (u.email && u.email.toLowerCase() === lower));
|
|
67
|
+
if (!match)
|
|
68
|
+
throw new NotFoundError("User", nameOrId);
|
|
69
|
+
return { id: match.id, name: match.name };
|
|
70
|
+
}
|
|
71
|
+
/** Resolve comma-separated label names to UUIDs. */
|
|
72
|
+
export async function resolveLabels(client, labelNames) {
|
|
73
|
+
const names = labelNames
|
|
74
|
+
.split(",")
|
|
75
|
+
.map((n) => n.trim())
|
|
76
|
+
.filter(Boolean);
|
|
77
|
+
if (names.length === 0) {
|
|
78
|
+
throw new ValidationError("No valid label names provided. Separate multiple labels with commas.");
|
|
79
|
+
}
|
|
80
|
+
const connection = await client.issueLabels({ first: 250 });
|
|
81
|
+
const allLabels = await fetchAllNodes(connection);
|
|
82
|
+
const resolved = [];
|
|
83
|
+
for (const name of names) {
|
|
84
|
+
const lower = name.toLowerCase();
|
|
85
|
+
const match = allLabels.find((l) => l.name.toLowerCase() === lower);
|
|
86
|
+
if (!match)
|
|
87
|
+
throw new NotFoundError("Label", name);
|
|
88
|
+
resolved.push({ id: match.id, name: match.name });
|
|
89
|
+
}
|
|
90
|
+
return resolved;
|
|
91
|
+
}
|
|
92
|
+
/** Resolve a workflow state name to UUID for a given team. */
|
|
93
|
+
export async function resolveState(client, stateName, teamId) {
|
|
94
|
+
const states = await client.workflowStates({
|
|
95
|
+
first: 250,
|
|
96
|
+
filter: { team: { id: { eq: teamId } } },
|
|
97
|
+
});
|
|
98
|
+
const lower = stateName.toLowerCase();
|
|
99
|
+
const match = states.nodes.find((s) => s.name.toLowerCase() === lower);
|
|
100
|
+
if (!match)
|
|
101
|
+
throw new NotFoundError("State", stateName);
|
|
102
|
+
return { id: match.id, name: match.name, type: match.type };
|
|
103
|
+
}
|
|
104
|
+
/** Resolve an initiative name to its UUID. */
|
|
105
|
+
export async function resolveInitiative(client, nameOrId) {
|
|
106
|
+
if (isUUID(nameOrId)) {
|
|
107
|
+
const init = await client.initiative(nameOrId);
|
|
108
|
+
return { id: init.id, name: init.name };
|
|
109
|
+
}
|
|
110
|
+
const connection = await client.initiatives({ first: 250 });
|
|
111
|
+
const all = await fetchAllNodes(connection);
|
|
112
|
+
const lower = nameOrId.toLowerCase();
|
|
113
|
+
const match = all.find((i) => i.name.toLowerCase() === lower);
|
|
114
|
+
if (!match)
|
|
115
|
+
throw new NotFoundError("Initiative", nameOrId);
|
|
116
|
+
return { id: match.id, name: match.name };
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Parse an issue identifier like "ENG-123" into a searchable format. Returns
|
|
120
|
+
* the identifier as-is if it looks like a Linear issue ID.
|
|
121
|
+
*/
|
|
122
|
+
export function parseIssueIdentifier(input) {
|
|
123
|
+
if (isUUID(input))
|
|
124
|
+
return input;
|
|
125
|
+
if (/^[A-Za-z]+-\d+$/.test(input))
|
|
126
|
+
return input;
|
|
127
|
+
throw new NotFoundError("Issue", input);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Find an issue by Linear identifier (e.g., ENG-123) or UUID.
|
|
131
|
+
*
|
|
132
|
+
* Identifiers are split into team key + number and resolved through the
|
|
133
|
+
* standard client.issues({filter}) API in a single request. Earlier this
|
|
134
|
+
* function did searchIssues + client.issue() (two requests); the filter-based
|
|
135
|
+
* path returns full Issue objects directly so connection methods (.comments(),
|
|
136
|
+
* .labels(), .relations()) work the same as before.
|
|
137
|
+
*/
|
|
138
|
+
export async function findIssueByIdentifier(client, id) {
|
|
139
|
+
if (isUUID(id))
|
|
140
|
+
return client.issue(id);
|
|
141
|
+
const match = id.toUpperCase().match(/^([A-Z]+)-(\d+)$/);
|
|
142
|
+
if (!match)
|
|
143
|
+
throw new NotFoundError("Issue", id);
|
|
144
|
+
const result = await client.issues({
|
|
145
|
+
filter: { team: { key: { eq: match[1] } }, number: { eq: parseInt(match[2], 10) } },
|
|
146
|
+
first: 1,
|
|
147
|
+
});
|
|
148
|
+
const issue = result.nodes[0];
|
|
149
|
+
if (issue)
|
|
150
|
+
return issue;
|
|
151
|
+
throw new NotFoundError("Issue", id);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Resolve a project status by name (e.g. "Started") OR type
|
|
155
|
+
* (backlog|planned|started|paused|completed|canceled) to its UUID. Used by
|
|
156
|
+
* `projects update --state` since the SDK's ProjectUpdateInput takes statusId,
|
|
157
|
+
* not a state string.
|
|
158
|
+
*/
|
|
159
|
+
export async function resolveProjectStatus(client, nameOrType) {
|
|
160
|
+
if (isUUID(nameOrType)) {
|
|
161
|
+
const status = await client.projectStatus(nameOrType);
|
|
162
|
+
return { id: status.id, name: status.name, type: status.type };
|
|
163
|
+
}
|
|
164
|
+
const connection = await client.projectStatuses({ first: 250 });
|
|
165
|
+
const all = await fetchAllNodes(connection);
|
|
166
|
+
const lower = nameOrType.toLowerCase();
|
|
167
|
+
const match = all.find((s) => s.name.toLowerCase() === lower || s.type.toLowerCase() === lower);
|
|
168
|
+
if (!match)
|
|
169
|
+
throw new NotFoundError("Project status", nameOrType);
|
|
170
|
+
return { id: match.id, name: match.name, type: match.type };
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=resolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/utils/resolve.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sleep.d.ts","sourceRoot":"","sources":["../../src/utils/sleep.ts"],"names":[],"mappings":"AAAA,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sleep.js","sourceRoot":"","sources":["../../src/utils/sleep.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,KAAK,CAAC,EAAU;IAC/B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC1D,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The header Linear sends with every webhook payload. Re-exported as a
|
|
3
|
+
* constant so server templates can import it from one place.
|
|
4
|
+
*/
|
|
5
|
+
export declare const LINEAR_SIGNATURE_HEADER = "linear-signature";
|
|
6
|
+
/**
|
|
7
|
+
* Default replay window: 60 seconds. Matches Linear's published recommendation
|
|
8
|
+
* (https://linear.app/developers/webhooks): "verify it's within a minute of
|
|
9
|
+
* the time your system sees it to guard against replay attacks."
|
|
10
|
+
*/
|
|
11
|
+
export declare const DEFAULT_WEBHOOK_MAX_AGE_MS: number;
|
|
12
|
+
export interface VerifyOptions {
|
|
13
|
+
/** Raw request body, exactly as Linear sent it (do not re-stringify a parsed object). */
|
|
14
|
+
rawBody: string | Buffer;
|
|
15
|
+
/** Value of the `linear-signature` header. */
|
|
16
|
+
signature: string;
|
|
17
|
+
/** The webhook signing secret stored in your env. */
|
|
18
|
+
secret: string;
|
|
19
|
+
/**
|
|
20
|
+
* The `webhookTimestamp` field from the parsed body (Unix ms). When set,
|
|
21
|
+
* the verifier rejects payloads older than maxAgeMs. Strongly recommended
|
|
22
|
+
* for any production receiver — without it, captured requests replay
|
|
23
|
+
* forever.
|
|
24
|
+
*/
|
|
25
|
+
timestamp?: number;
|
|
26
|
+
/** Replay window in milliseconds. Defaults to 60 seconds. Only consulted when timestamp is set. */
|
|
27
|
+
maxAgeMs?: number;
|
|
28
|
+
/** Override the current time (for testing). Defaults to Date.now(). */
|
|
29
|
+
nowMs?: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Verify a Linear webhook payload's HMAC-SHA256 signature. Returns true on
|
|
33
|
+
* match, false otherwise. Never throws.
|
|
34
|
+
*
|
|
35
|
+
* Both inputs are length-checked before timingSafeEqual since timingSafeEqual
|
|
36
|
+
* itself throws on length mismatch — that throw would itself be a timing leak.
|
|
37
|
+
*
|
|
38
|
+
* If timestamp is provided, the function also enforces a replay window:
|
|
39
|
+
* payloads older than maxAgeMs are rejected even with a valid HMAC.
|
|
40
|
+
*/
|
|
41
|
+
export declare function verifyLinearWebhook(opts: VerifyOptions): boolean;
|
|
42
|
+
//# sourceMappingURL=webhook-verify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-verify.d.ts","sourceRoot":"","sources":["../../src/utils/webhook-verify.ts"],"names":[],"mappings":"AAeA;;;GAGG;AACH,eAAO,MAAM,uBAAuB,qBAAqB,CAAC;AAE1D;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,QAAY,CAAC;AAEpD,MAAM,WAAW,aAAa;IAC7B,yFAAyF;IACzF,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mGAAmG;IACnG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CA2BhE"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Webhook signature verification.
|
|
2
|
+
//
|
|
3
|
+
// Linear's signing scheme is HMAC-SHA256 of the raw request body, hex-encoded,
|
|
4
|
+
// delivered in the `linear-signature` header. The SDK ships a
|
|
5
|
+
// LinearWebhookClient but its verify method is private. We compute the digest
|
|
6
|
+
// ourselves with node:crypto and use timingSafeEqual to prevent timing attacks
|
|
7
|
+
// — this is also the pattern Linear's docs recommend.
|
|
8
|
+
//
|
|
9
|
+
// Beyond HMAC, the payload carries a `webhookTimestamp` (Unix ms). When the
|
|
10
|
+
// caller passes that timestamp into verify(), we reject deltas older than
|
|
11
|
+
// maxAgeMs (default 60 seconds, per Linear's published recommendation) so a
|
|
12
|
+
// captured request can't be replayed indefinitely.
|
|
13
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
14
|
+
/**
|
|
15
|
+
* The header Linear sends with every webhook payload. Re-exported as a
|
|
16
|
+
* constant so server templates can import it from one place.
|
|
17
|
+
*/
|
|
18
|
+
export const LINEAR_SIGNATURE_HEADER = "linear-signature";
|
|
19
|
+
/**
|
|
20
|
+
* Default replay window: 60 seconds. Matches Linear's published recommendation
|
|
21
|
+
* (https://linear.app/developers/webhooks): "verify it's within a minute of
|
|
22
|
+
* the time your system sees it to guard against replay attacks."
|
|
23
|
+
*/
|
|
24
|
+
export const DEFAULT_WEBHOOK_MAX_AGE_MS = 60 * 1000;
|
|
25
|
+
/**
|
|
26
|
+
* Verify a Linear webhook payload's HMAC-SHA256 signature. Returns true on
|
|
27
|
+
* match, false otherwise. Never throws.
|
|
28
|
+
*
|
|
29
|
+
* Both inputs are length-checked before timingSafeEqual since timingSafeEqual
|
|
30
|
+
* itself throws on length mismatch — that throw would itself be a timing leak.
|
|
31
|
+
*
|
|
32
|
+
* If timestamp is provided, the function also enforces a replay window:
|
|
33
|
+
* payloads older than maxAgeMs are rejected even with a valid HMAC.
|
|
34
|
+
*/
|
|
35
|
+
export function verifyLinearWebhook(opts) {
|
|
36
|
+
if (!opts.signature || !opts.secret)
|
|
37
|
+
return false;
|
|
38
|
+
// `Buffer.from(value, "hex")` silently drops invalid characters and odd
|
|
39
|
+
// trailing nibbles. Reject anything that isn't a clean even-length hex
|
|
40
|
+
// string up front so a partially-garbled header can't slip past the
|
|
41
|
+
// length-vs-expected check below by accident.
|
|
42
|
+
if (!/^[0-9a-f]+$/i.test(opts.signature) || opts.signature.length % 2 !== 0)
|
|
43
|
+
return false;
|
|
44
|
+
const body = typeof opts.rawBody === "string" ? Buffer.from(opts.rawBody, "utf-8") : opts.rawBody;
|
|
45
|
+
const expectedHex = createHmac("sha256", opts.secret).update(body).digest("hex");
|
|
46
|
+
const expected = Buffer.from(expectedHex, "hex");
|
|
47
|
+
const received = Buffer.from(opts.signature, "hex");
|
|
48
|
+
if (received.length !== expected.length)
|
|
49
|
+
return false;
|
|
50
|
+
if (!timingSafeEqual(received, expected))
|
|
51
|
+
return false;
|
|
52
|
+
if (typeof opts.timestamp === "number") {
|
|
53
|
+
const maxAgeMs = opts.maxAgeMs ?? DEFAULT_WEBHOOK_MAX_AGE_MS;
|
|
54
|
+
const now = opts.nowMs ?? Date.now();
|
|
55
|
+
// Reject anything outside [now - maxAgeMs, now + maxAgeMs]. The forward
|
|
56
|
+
// bound catches future-dated forgeries (clock skew + a small grace).
|
|
57
|
+
if (!Number.isFinite(opts.timestamp))
|
|
58
|
+
return false;
|
|
59
|
+
const delta = Math.abs(now - opts.timestamp);
|
|
60
|
+
if (delta > maxAgeMs)
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=webhook-verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-verify.js","sourceRoot":"","sources":["../../src/utils/webhook-verify.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAClC,EAAE;AACF,+EAA+E;AAC/E,8DAA8D;AAC9D,8EAA8E;AAC9E,+EAA+E;AAC/E,sDAAsD;AACtD,EAAE;AACF,4EAA4E;AAC5E,0EAA0E;AAC1E,4EAA4E;AAC5E,mDAAmD;AAEnD,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE1D;;;GAGG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,kBAAkB,CAAC;AAE1D;;;;GAIG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,EAAE,GAAG,IAAI,CAAC;AAsBpD;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAmB;IACtD,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAElD,wEAAwE;IACxE,uEAAuE;IACvE,oEAAoE;IACpE,8CAA8C;IAC9C,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAE1F,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;IAClG,MAAM,WAAW,GAAG,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACjF,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IACjD,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IACpD,IAAI,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACtD,IAAI,CAAC,eAAe,CAAC,QAAQ,EAAE,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IAEvD,IAAI,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ,EAAE,CAAC;QACxC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,0BAA0B,CAAC;QAC7D,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;QACrC,wEAAwE;QACxE,qEAAqE;QACrE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC;YAAE,OAAO,KAAK,CAAC;QACnD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC;QAC7C,IAAI,KAAK,GAAG,QAAQ;YAAE,OAAO,KAAK,CAAC;IACpC,CAAC;IAED,OAAO,IAAI,CAAC;AACb,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elnora-ai/linear",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Linear workspace for Claude Code — search, bulk edit, agents, and a config-driven curator",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,8 +13,11 @@
|
|
|
13
13
|
"commands/",
|
|
14
14
|
"skills/",
|
|
15
15
|
"schemas/",
|
|
16
|
+
"scripts/postinstall.mjs",
|
|
17
|
+
"references/*.md",
|
|
16
18
|
"references/*.placeholder.json",
|
|
17
19
|
"references/*.example.json",
|
|
20
|
+
"templates/",
|
|
18
21
|
".claude-plugin/",
|
|
19
22
|
"README.md",
|
|
20
23
|
"LICENSE",
|
|
@@ -34,7 +37,8 @@
|
|
|
34
37
|
"lint": "biome check src/ __tests__/",
|
|
35
38
|
"lint:fix": "biome check --write src/ __tests__/",
|
|
36
39
|
"format": "biome format --write src/ __tests__/",
|
|
37
|
-
"typecheck": "tsc --noEmit"
|
|
40
|
+
"typecheck": "tsc --noEmit",
|
|
41
|
+
"postinstall": "node scripts/postinstall.mjs"
|
|
38
42
|
},
|
|
39
43
|
"keywords": [
|
|
40
44
|
"linear",
|
|
@@ -66,6 +70,7 @@
|
|
|
66
70
|
"vitest": "^4.1.2"
|
|
67
71
|
},
|
|
68
72
|
"dependencies": {
|
|
73
|
+
"@anthropic-ai/sdk": "^0.96.0",
|
|
69
74
|
"@linear/sdk": "^84.0.0",
|
|
70
75
|
"ajv": "^8.20.0",
|
|
71
76
|
"ajv-formats": "^3.0.1",
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Description template (full path)
|
|
2
|
+
|
|
3
|
+
Used by `linear-issue-creator` on the full path when the caller did not supply a description body. Fast path passes the caller's description verbatim and does not read this file.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
elnora-linear issues create "Concise actionable title" \
|
|
7
|
+
--team "<your-team-name>" \
|
|
8
|
+
--description "$(cat <<'EOF'
|
|
9
|
+
## Overview
|
|
10
|
+
[What and why, 1–2 sentences]
|
|
11
|
+
|
|
12
|
+
## Problem Statement
|
|
13
|
+
[Pain point or gap]
|
|
14
|
+
|
|
15
|
+
## Proposed Solution
|
|
16
|
+
[Approach]
|
|
17
|
+
|
|
18
|
+
## Acceptance Criteria
|
|
19
|
+
- [ ] Criterion 1
|
|
20
|
+
- [ ] Criterion 2
|
|
21
|
+
|
|
22
|
+
## Resources
|
|
23
|
+
[Links if any]
|
|
24
|
+
EOF
|
|
25
|
+
)" \
|
|
26
|
+
--project "Project Name" \
|
|
27
|
+
--labels "Type: feature,Layer: frontend" \
|
|
28
|
+
--priority 3 --assignee "<assignee>"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
For compliance issues, replace the description body with the loaded compliance template content (from `templates/<template>.md` in this repo) as-is.
|