@enrichlayer/el-linear 1.10.0 → 1.15.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 (106) hide show
  1. package/README.md +126 -10
  2. package/claude-skills/linear-operations/SKILL.md +41 -1
  3. package/dist/auth/linear-credential.d.ts +27 -0
  4. package/dist/auth/linear-credential.js +1 -0
  5. package/dist/auth/oauth-app-config.d.ts +4 -3
  6. package/dist/auth/oauth-app-config.js +13 -2
  7. package/dist/auth/oauth-callback.d.ts +2 -3
  8. package/dist/auth/oauth-callback.js +2 -2
  9. package/dist/auth/oauth-client.d.ts +8 -2
  10. package/dist/auth/oauth-client.js +26 -0
  11. package/dist/auth/oauth-fs.d.ts +2 -1
  12. package/dist/auth/oauth-headless.d.ts +2 -1
  13. package/dist/auth/oauth-storage.d.ts +5 -1
  14. package/dist/auth/oauth-storage.js +1 -1
  15. package/dist/auth/oauth-token.d.ts +4 -3
  16. package/dist/auth/oauth-token.js +16 -4
  17. package/dist/auth/token-resolver.d.ts +14 -5
  18. package/dist/auth/token-resolver.js +6 -1
  19. package/dist/commands/batch.js +18 -21
  20. package/dist/commands/comments.js +5 -5
  21. package/dist/commands/config.js +178 -5
  22. package/dist/commands/init/aliases.js +1 -1
  23. package/dist/commands/init/defaults.d.ts +2 -1
  24. package/dist/commands/init/index.js +45 -35
  25. package/dist/commands/init/oauth.d.ts +4 -1
  26. package/dist/commands/init/oauth.js +22 -4
  27. package/dist/commands/init/shared.d.ts +24 -2
  28. package/dist/commands/init/shared.js +35 -4
  29. package/dist/commands/init/token.d.ts +3 -3
  30. package/dist/commands/init/token.js +5 -24
  31. package/dist/commands/init/workspace.d.ts +2 -1
  32. package/dist/commands/init/workspace.js +1 -1
  33. package/dist/commands/introspect.d.ts +27 -0
  34. package/dist/commands/introspect.js +178 -0
  35. package/dist/commands/issues/branch.js +9 -1
  36. package/dist/commands/issues/relations.d.ts +3 -14
  37. package/dist/commands/issues/relations.js +3 -3
  38. package/dist/commands/issues.js +222 -43
  39. package/dist/commands/labels.js +2 -1
  40. package/dist/commands/profile.js +1 -0
  41. package/dist/commands/projects.d.ts +2 -0
  42. package/dist/commands/projects.js +91 -7
  43. package/dist/commands/read-shortcut.d.ts +1 -1
  44. package/dist/commands/read-shortcut.js +28 -8
  45. package/dist/commands/refs.js +67 -8
  46. package/dist/commands/search.js +30 -5
  47. package/dist/commands/users.js +4 -2
  48. package/dist/config/config.d.ts +99 -1
  49. package/dist/config/config.js +264 -52
  50. package/dist/config/error-enrichment.d.ts +62 -0
  51. package/dist/config/error-enrichment.js +417 -0
  52. package/dist/config/issue-validation.d.ts +37 -0
  53. package/dist/config/issue-validation.js +63 -1
  54. package/dist/config/paths.d.ts +2 -8
  55. package/dist/config/paths.js +4 -2
  56. package/dist/config/resolver.d.ts +8 -1
  57. package/dist/config/resolver.js +9 -2
  58. package/dist/main.js +13 -1
  59. package/dist/queries/comments-types.d.ts +15 -9
  60. package/dist/queries/common.d.ts +2 -2
  61. package/dist/queries/common.js +8 -0
  62. package/dist/queries/documents-types.d.ts +4 -3
  63. package/dist/queries/introspect-types.d.ts +8 -7
  64. package/dist/queries/issues-types.d.ts +92 -27
  65. package/dist/queries/issues.d.ts +49 -10
  66. package/dist/queries/issues.js +125 -5
  67. package/dist/queries/labels-types.d.ts +7 -6
  68. package/dist/queries/project-milestones-types.d.ts +5 -4
  69. package/dist/queries/project-milestones.d.ts +1 -1
  70. package/dist/queries/projects-types.d.ts +8 -7
  71. package/dist/queries/releases-types.d.ts +5 -4
  72. package/dist/queries/search-types.d.ts +28 -12
  73. package/dist/queries/templates-types.d.ts +3 -2
  74. package/dist/types/linear.d.ts +13 -1
  75. package/dist/utils/auto-link-references.d.ts +3 -3
  76. package/dist/utils/auto-link-references.js +1 -10
  77. package/dist/utils/extract-field.d.ts +19 -0
  78. package/dist/utils/extract-field.js +99 -0
  79. package/dist/utils/file-service.d.ts +6 -13
  80. package/dist/utils/file-service.js +0 -2
  81. package/dist/utils/formatters/summary.js +6 -1
  82. package/dist/utils/graphql-issues-service.d.ts +101 -45
  83. package/dist/utils/graphql-issues-service.js +252 -39
  84. package/dist/utils/graphql-service.d.ts +10 -12
  85. package/dist/utils/graphql-service.js +0 -3
  86. package/dist/utils/issue-reference-extractor.d.ts +7 -0
  87. package/dist/utils/issue-reference-extractor.js +5 -3
  88. package/dist/utils/issues-service-bootstrap.d.ts +28 -0
  89. package/dist/utils/issues-service-bootstrap.js +27 -0
  90. package/dist/utils/linear-service.d.ts +21 -14
  91. package/dist/utils/linear-service.js +73 -11
  92. package/dist/utils/markdown-prosemirror.js +12 -12
  93. package/dist/utils/mention-resolver.js +1 -1
  94. package/dist/utils/output.d.ts +81 -3
  95. package/dist/utils/output.js +61 -6
  96. package/dist/utils/project-slug.d.ts +21 -0
  97. package/dist/utils/project-slug.js +45 -0
  98. package/dist/utils/protected-ranges.d.ts +14 -0
  99. package/dist/utils/protected-ranges.js +88 -2
  100. package/dist/utils/sanitize-for-log.d.ts +24 -0
  101. package/dist/utils/sanitize-for-log.js +38 -0
  102. package/dist/utils/table-formatter.js +24 -0
  103. package/dist/utils/validators.d.ts +7 -2
  104. package/dist/utils/validators.js +6 -0
  105. package/dist/utils/workspace-url.js +20 -4
  106. package/package.json +2 -2
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Strip anything that looks like a Linear API/OAuth token from a string.
3
+ *
4
+ * Defense in depth: today the @linear/sdk error message embeds `{ query,
5
+ * variables }` but not the Authorization header. A future SDK upgrade that
6
+ * includes headers (which upstream graphql-request has done historically)
7
+ * would otherwise silently write `Bearer lin_api_…` into stdout, shell
8
+ * history, or CI logs. The regex also catches token shapes that may show
9
+ * up in custom error wrappers — e.g. a network proxy that echoes the
10
+ * Bearer header in its 502 body.
11
+ *
12
+ * Originally lived in `commands/init/token.ts` for the wizard's error
13
+ * formatting. Hoisted to `utils/` so the central error path
14
+ * (`output.ts`'s `outputError`) can use it too — that path runs on
15
+ * every non-wizard CLI invocation. `init/token.ts` re-exports the
16
+ * symbol so existing imports under `init/` keep working.
17
+ *
18
+ * The OAuth token-exchange / refresh / revoke paths also call this
19
+ * function at source (`auth/oauth-token.ts`'s `postForm` + `revokeToken`,
20
+ * `auth/token-resolver.ts`'s refresh-failure rewrap) so a future caller
21
+ * that catches+rethrows or logs mid-chain can't leak a token before the
22
+ * error reaches `outputError`. Defense in depth (DEV-4065).
23
+ */
24
+ // Personal-API tokens (`lin_api_…`) and OAuth access/refresh tokens
25
+ // (`lin_oauth_…`).
26
+ const TOKEN_PREFIX_RE = /lin_(api|oauth)_[A-Za-z0-9_-]{16,}/g;
27
+ // High-entropy bearer payload fallback: catches generic Bearer-style
28
+ // strings adjacent to Authorization / Bearer keywords. Useful for
29
+ // future SDK error wrappers that might leak headers without the
30
+ // `lin_` prefix.
31
+ const BEARER_PAYLOAD_RE = /(\b(?:Authorization|Bearer)\b[:\s]*)([A-Za-z0-9_\-/+=]{40,})/gi;
32
+ export function sanitizeForLog(text) {
33
+ return text
34
+ .replace(TOKEN_PREFIX_RE, (m) => m.startsWith("lin_oauth_")
35
+ ? "lin_oauth_***REDACTED***"
36
+ : "lin_api_***REDACTED***")
37
+ .replace(BEARER_PAYLOAD_RE, "$1***REDACTED***");
38
+ }
@@ -86,6 +86,19 @@ const ALL_COLUMNS = {
86
86
  return atIdx > 0 ? name.slice(0, atIdx) : name;
87
87
  },
88
88
  },
89
+ delegate: {
90
+ key: "delegate",
91
+ header: "Delegate",
92
+ width: 20,
93
+ extract: (i) => {
94
+ if (!i.delegate) {
95
+ return "—";
96
+ }
97
+ const name = i.delegate.name;
98
+ const atIdx = name.indexOf("@");
99
+ return atIdx > 0 ? name.slice(0, atIdx) : name;
100
+ },
101
+ },
89
102
  project: {
90
103
  key: "project",
91
104
  header: "Project",
@@ -229,6 +242,17 @@ const MD_COLUMNS = {
229
242
  return atIdx > 0 ? name.slice(0, atIdx) : name;
230
243
  },
231
244
  },
245
+ delegate: {
246
+ header: "Delegate",
247
+ extract: (i) => {
248
+ if (!i.delegate) {
249
+ return "—";
250
+ }
251
+ const name = i.delegate.name;
252
+ const atIdx = name.indexOf("@");
253
+ return atIdx > 0 ? name.slice(0, atIdx) : name;
254
+ },
255
+ },
232
256
  project: {
233
257
  header: "Project",
234
258
  extract: (i) => i.project?.name ?? "—",
@@ -1,3 +1,4 @@
1
+ import type { LinearPriority } from "../types/linear.js";
1
2
  export declare function parsePositiveInt(value: string, flagName: string): number;
2
3
  /**
3
4
  * Parse a single priority value (for `issues create --priority`, `issues update --priority`).
@@ -5,8 +6,12 @@ export declare function parsePositiveInt(value: string, flagName: string): numbe
5
6
  * Accepts:
6
7
  * - keywords: none | urgent | high | medium | normal | low
7
8
  * - numbers: 0 (no priority), 1 (urgent), 2 (high), 3 (medium), 4 (low)
9
+ *
10
+ * Narrows arbitrary string input to the `LinearPriority` literal union so
11
+ * downstream call sites get type-level guarantees that the value is in
12
+ * range — no `as 0 | 1 | 2 | 3 | 4` cast at the boundary.
8
13
  */
9
- export declare function validatePriority(value: string): number;
14
+ export declare function validatePriority(value: string): LinearPriority;
10
15
  export declare function validateHexColor(value: string): string;
11
16
  export declare function validateIsoDate(value: string): string;
12
17
  /**
@@ -18,5 +23,5 @@ export declare function validateIsoDate(value: string): string;
18
23
  * resolve to `[]`.
19
24
  */
20
25
  export declare function splitList(value: string | undefined | null | false): string[];
21
- export declare function parsePriorityFilter(value: string): number[];
26
+ export declare function parsePriorityFilter(value: string): LinearPriority[];
22
27
  export declare const PRIORITY_LABELS: Record<number, string>;
@@ -14,6 +14,10 @@ export function parsePositiveInt(value, flagName) {
14
14
  * Accepts:
15
15
  * - keywords: none | urgent | high | medium | normal | low
16
16
  * - numbers: 0 (no priority), 1 (urgent), 2 (high), 3 (medium), 4 (low)
17
+ *
18
+ * Narrows arbitrary string input to the `LinearPriority` literal union so
19
+ * downstream call sites get type-level guarantees that the value is in
20
+ * range — no `as 0 | 1 | 2 | 3 | 4` cast at the boundary.
17
21
  */
18
22
  export function validatePriority(value) {
19
23
  const asName = PRIORITY_NAMES[value.toLowerCase()];
@@ -24,6 +28,8 @@ export function validatePriority(value) {
24
28
  if (Number.isNaN(n) || n < 0 || n > 4) {
25
29
  throw invalidParameterError("--priority", `"${value}" is not valid. Use names (none, urgent, high, medium/normal, low) or numbers (0-4).`);
26
30
  }
31
+ // The 0-4 range check above is what narrows `n` to LinearPriority;
32
+ // TypeScript can't infer that automatically.
27
33
  return n;
28
34
  }
29
35
  export function validateHexColor(value) {
@@ -29,18 +29,33 @@ const VIEWER_ORG_URL_KEY_QUERY = /* GraphQL */ `
29
29
  }
30
30
  `;
31
31
  const WORKSPACE_URL_KEY_ENV = "EL_LINEAR_WORKSPACE_URL_KEY";
32
+ // Linear workspace URL keys are URL-safe slugs. The Linear API itself
33
+ // validates this shape (`viewerIsValid` in init/token.ts uses the same
34
+ // regex). Applying the check on env + config reads closes the gap where
35
+ // a malformed key (`EL_LINEAR_WORKSPACE_URL_KEY=" javascript:alert(1)#"`)
36
+ // would otherwise flow into markdown link URLs verbatim. Live-API path
37
+ // is already trusted because the value comes from `viewer.organization`.
38
+ // DEV-4067.
39
+ const VALID_URL_KEY_RE = /^[a-z0-9-]+$/i;
40
+ function validateUrlKey(value, source) {
41
+ if (!VALID_URL_KEY_RE.test(value)) {
42
+ throw new Error(`Invalid Linear workspace URL key from ${source}: ${JSON.stringify(value)}. ` +
43
+ "Must match /^[a-z0-9-]+$/i (e.g. 'verticalint').");
44
+ }
45
+ return value;
46
+ }
32
47
  let cachedUrlKey;
33
48
  export async function getWorkspaceUrlKey(graphQLService, options = {}) {
34
49
  // 1. Per-invocation override — highest priority.
35
50
  if (options.override) {
36
- return options.override;
51
+ return validateUrlKey(options.override, "--workspace-url-key flag");
37
52
  }
38
53
  // 2. Env var. Read on every call (cheap; lets tests/CI flip the value
39
54
  // mid-process without restarting). Not cached, so a test that sets
40
55
  // EL_LINEAR_WORKSPACE_URL_KEY then unsets it sees the unset state.
41
56
  const envKey = process.env[WORKSPACE_URL_KEY_ENV];
42
57
  if (envKey) {
43
- return envKey;
58
+ return validateUrlKey(envKey, `${WORKSPACE_URL_KEY_ENV} env var`);
44
59
  }
45
60
  // Layers 3-4 are cached in-process — config and the live lookup are
46
61
  // both stable for the CLI invocation's lifetime.
@@ -50,8 +65,9 @@ export async function getWorkspaceUrlKey(graphQLService, options = {}) {
50
65
  // 3. Config override.
51
66
  const { workspaceUrlKey } = loadConfig();
52
67
  if (workspaceUrlKey) {
53
- cachedUrlKey = workspaceUrlKey;
54
- return workspaceUrlKey;
68
+ const validated = validateUrlKey(workspaceUrlKey, "config.workspaceUrlKey");
69
+ cachedUrlKey = validated;
70
+ return validated;
55
71
  }
56
72
  // 4. Live API lookup — requires a GraphQL service.
57
73
  if (!graphQLService) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enrichlayer/el-linear",
3
- "version": "1.10.0",
3
+ "version": "1.15.0",
4
4
  "description": "A pragmatic CLI for Linear.app — deterministic team/label/member resolution, structured issue validation, configurable term enforcement, and a GraphQL escape hatch.",
5
5
  "main": "dist/main.js",
6
6
  "types": "dist/main.d.ts",
@@ -47,7 +47,7 @@
47
47
  },
48
48
  "dependencies": {
49
49
  "@inquirer/prompts": "^8.4.2",
50
- "@linear/sdk": "^83.0.0",
50
+ "@linear/sdk": "^84.0.0",
51
51
  "commander": "^14.0.0",
52
52
  "picocolors": "^1.1.1"
53
53
  },