@nocoo/zhe 1.18.0 → 1.19.3

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 (47) hide show
  1. package/dist/commands/idea/add.d.ts +22 -0
  2. package/dist/commands/idea/add.d.ts.map +1 -0
  3. package/dist/commands/idea/add.js +55 -0
  4. package/dist/commands/idea/add.js.map +1 -0
  5. package/dist/commands/idea/delete.d.ts +17 -0
  6. package/dist/commands/idea/delete.d.ts.map +1 -0
  7. package/dist/commands/idea/delete.js +47 -0
  8. package/dist/commands/idea/delete.js.map +1 -0
  9. package/dist/commands/idea/get.d.ts +12 -0
  10. package/dist/commands/idea/get.d.ts.map +1 -0
  11. package/dist/commands/idea/get.js +45 -0
  12. package/dist/commands/idea/get.js.map +1 -0
  13. package/dist/commands/idea/helpers.d.ts +13 -0
  14. package/dist/commands/idea/helpers.d.ts.map +1 -0
  15. package/dist/commands/idea/helpers.js +78 -0
  16. package/dist/commands/idea/helpers.js.map +1 -0
  17. package/dist/commands/idea/list.d.ts +22 -0
  18. package/dist/commands/idea/list.d.ts.map +1 -0
  19. package/dist/commands/idea/list.js +64 -0
  20. package/dist/commands/idea/list.js.map +1 -0
  21. package/dist/commands/idea/update.d.ts +27 -0
  22. package/dist/commands/idea/update.d.ts.map +1 -0
  23. package/dist/commands/idea/update.js +78 -0
  24. package/dist/commands/idea/update.js.map +1 -0
  25. package/dist/commands/idea.d.ts +6 -2
  26. package/dist/commands/idea.d.ts.map +1 -1
  27. package/dist/commands/idea.js +12 -393
  28. package/dist/commands/idea.js.map +1 -1
  29. package/dist/commands/list.d.ts.map +1 -1
  30. package/dist/commands/list.js +97 -96
  31. package/dist/commands/list.js.map +1 -1
  32. package/dist/commands/update.d.ts.map +1 -1
  33. package/dist/commands/update.js +56 -59
  34. package/dist/commands/update.js.map +1 -1
  35. package/dist/utils/format.d.ts +27 -0
  36. package/dist/utils/format.d.ts.map +1 -0
  37. package/dist/utils/format.js +157 -0
  38. package/dist/utils/format.js.map +1 -0
  39. package/dist/utils/resolve.d.ts +41 -0
  40. package/dist/utils/resolve.d.ts.map +1 -0
  41. package/dist/utils/resolve.js +69 -0
  42. package/dist/utils/resolve.js.map +1 -0
  43. package/dist/utils.d.ts +17 -90
  44. package/dist/utils.d.ts.map +1 -1
  45. package/dist/utils.js +20 -250
  46. package/dist/utils.js.map +1 -1
  47. package/package.json +10 -6
@@ -1,398 +1,17 @@
1
1
  /**
2
- * zhe idea — Manage ideas (Markdown notes)
2
+ * zhe idea — Manage ideas (Markdown notes).
3
+ *
4
+ * Subcommand implementations live in ./idea/*.ts to keep this file small.
5
+ * `formatTags` is re-exported here for tests that import from
6
+ * `commands/idea.js`.
3
7
  */
4
- import * as readline from "node:readline";
5
- import { defineCommand, pc } from "@nocoo/cli-base";
6
- import { ApiClient, ApiClientError, EXIT_AUTH_REQUIRED, EXIT_ERROR, EXIT_INVALID_ARGS, EXIT_NOT_FOUND, EXIT_RATE_LIMITED, } from "../api/client.js";
7
- import { getApiKey } from "../config.js";
8
- import { parsePositiveInt, resolveTagName } from "../utils.js";
9
- // ── Helpers ──
10
- function requireAuth() {
11
- const apiKey = getApiKey();
12
- if (!apiKey) {
13
- console.log(pc.red("Not authenticated. Run `zhe login` first."));
14
- process.exit(EXIT_AUTH_REQUIRED);
15
- }
16
- return apiKey;
17
- }
18
- function handleApiError(error) {
19
- if (error instanceof ApiClientError) {
20
- if (error.status === 400) {
21
- console.log(pc.red(`Error: ${error.message}`));
22
- }
23
- else if (error.status === 404) {
24
- console.log(pc.red("Idea not found."));
25
- process.exit(EXIT_NOT_FOUND);
26
- }
27
- else {
28
- console.log(pc.red(`Error: ${error.message}`));
29
- }
30
- if (error.status === 401) {
31
- process.exit(EXIT_AUTH_REQUIRED);
32
- }
33
- if (error.status === 429) {
34
- process.exit(EXIT_RATE_LIMITED);
35
- }
36
- process.exit(EXIT_ERROR);
37
- }
38
- throw error;
39
- }
40
- function formatDate(isoDate) {
41
- const date = new Date(isoDate);
42
- return new Intl.DateTimeFormat("en-US", {
43
- month: "short",
44
- day: "numeric",
45
- year: "numeric",
46
- hour: "numeric",
47
- minute: "2-digit",
48
- }).format(date);
49
- }
50
- export function formatTags(tagIds, tagMap) {
51
- if (!tagIds || tagIds.length === 0)
52
- return "";
53
- return tagIds.map((id) => `[${tagMap?.get(id) ?? id.slice(0, 8)}]`).join(" ");
54
- }
55
- /**
56
- * Build a tagId→name map by fetching all tags from API.
57
- * Returns undefined on failure (display degrades to truncated IDs).
58
- */
59
- async function buildTagMap(client, tagIds) {
60
- if (tagIds.length === 0)
61
- return undefined;
62
- try {
63
- const { tags } = await client.listTags();
64
- return new Map(tags.map((t) => [t.id, t.name]));
65
- }
66
- catch {
67
- return undefined;
68
- }
69
- }
70
- async function confirm(message) {
71
- return new Promise((resolve) => {
72
- const rl = readline.createInterface({
73
- input: process.stdin,
74
- output: process.stdout,
75
- });
76
- rl.question(`${message} [y/N] `, (answer) => {
77
- rl.close();
78
- resolve(answer.toLowerCase() === "y");
79
- });
80
- });
81
- }
82
- // ── Subcommands ──
83
- const listSubcommand = defineCommand({
84
- meta: {
85
- name: "list",
86
- description: "List all ideas",
87
- },
88
- args: {
89
- limit: {
90
- type: "string",
91
- alias: "l",
92
- description: "Max results (default: 20)",
93
- },
94
- tag: {
95
- type: "string",
96
- alias: "t",
97
- description: "Filter by tag name",
98
- },
99
- query: {
100
- type: "string",
101
- alias: "q",
102
- description: "Search title and excerpt",
103
- },
104
- json: {
105
- type: "boolean",
106
- description: "Output as JSON",
107
- },
108
- },
109
- async run({ args }) {
110
- const apiKey = requireAuth();
111
- const client = new ApiClient(apiKey);
112
- try {
113
- // Resolve tag name to ID if provided
114
- let tagId;
115
- if (args.tag) {
116
- const resolved = await resolveTagName(client, args.tag);
117
- if (resolved === null) {
118
- process.exit(EXIT_INVALID_ARGS);
119
- }
120
- tagId = resolved;
121
- }
122
- const response = await client.listIdeas({
123
- limit: args.limit ? Number.parseInt(args.limit, 10) : 20,
124
- tagId,
125
- q: args.query,
126
- });
127
- if (args.json) {
128
- console.log(JSON.stringify(response, null, 2));
129
- return;
130
- }
131
- if (response.ideas.length === 0) {
132
- console.log(pc.yellow("No ideas found."));
133
- return;
134
- }
135
- // Build tag name map for display (like links' folder prefetch)
136
- const allTagIds = response.ideas.flatMap((i) => i.tagIds ?? []);
137
- const tagMap = await buildTagMap(client, allTagIds);
138
- // Table format
139
- console.log(pc.dim("ID TIME TITLE TAGS"));
140
- console.log(pc.dim("─".repeat(56)));
141
- for (const idea of response.ideas) {
142
- const time = formatDate(idea.createdAt);
143
- const title = (idea.title || idea.excerpt || "(no content)").slice(0, 24);
144
- const tags = formatTags(idea.tagIds, tagMap);
145
- console.log(`${pc.cyan(String(idea.id).padEnd(6))}${time.padEnd(17)}${title.padEnd(25)}${pc.yellow(tags)}`);
146
- }
147
- }
148
- catch (error) {
149
- handleApiError(error);
150
- }
151
- },
152
- });
153
- const getSubcommand = defineCommand({
154
- meta: {
155
- name: "get",
156
- description: "Get full content of an idea",
157
- },
158
- args: {
159
- id: {
160
- type: "positional",
161
- description: "Idea ID",
162
- required: true,
163
- },
164
- json: {
165
- type: "boolean",
166
- description: "Output as JSON",
167
- },
168
- },
169
- async run({ args }) {
170
- const apiKey = requireAuth();
171
- const client = new ApiClient(apiKey);
172
- const id = parsePositiveInt(args.id);
173
- if (id === null) {
174
- console.log(pc.red("Invalid idea ID."));
175
- process.exit(EXIT_INVALID_ARGS);
176
- }
177
- try {
178
- const response = await client.getIdea(id);
179
- if (args.json) {
180
- console.log(JSON.stringify(response, null, 2));
181
- return;
182
- }
183
- const { idea } = response;
184
- const tagMap = await buildTagMap(client, idea.tagIds ?? []);
185
- const tags = formatTags(idea.tagIds, tagMap);
186
- const title = idea.title || formatDate(idea.createdAt);
187
- console.log(pc.dim("─".repeat(40)));
188
- console.log(`Idea ${pc.cyan(`#${idea.id}`)} — ${pc.bold(title)}`);
189
- if (tags)
190
- console.log(`Tags: ${pc.yellow(tags)}`);
191
- console.log(pc.dim("─".repeat(40)));
192
- console.log();
193
- console.log(idea.content);
194
- }
195
- catch (error) {
196
- handleApiError(error);
197
- }
198
- },
199
- });
200
- const addSubcommand = defineCommand({
201
- meta: {
202
- name: "add",
203
- description: "Create a new idea",
204
- },
205
- args: {
206
- content: {
207
- type: "positional",
208
- description: "Idea content (Markdown)",
209
- required: true,
210
- },
211
- title: {
212
- type: "string",
213
- alias: "t",
214
- description: "Optional title",
215
- },
216
- tag: {
217
- type: "string",
218
- alias: "T",
219
- description: "Add tag by name",
220
- },
221
- json: {
222
- type: "boolean",
223
- description: "Output as JSON",
224
- },
225
- },
226
- async run({ args }) {
227
- const apiKey = requireAuth();
228
- const client = new ApiClient(apiKey);
229
- const content = args.content;
230
- if (!content.trim()) {
231
- console.log(pc.red("Content cannot be empty."));
232
- process.exit(EXIT_INVALID_ARGS);
233
- }
234
- try {
235
- // Resolve tag name to ID if provided
236
- let tagIds;
237
- if (args.tag) {
238
- const tagId = await resolveTagName(client, args.tag);
239
- if (tagId === null) {
240
- process.exit(EXIT_INVALID_ARGS);
241
- }
242
- tagIds = [tagId];
243
- }
244
- const request = {
245
- content,
246
- ...(args.title && { title: args.title }),
247
- ...(tagIds && { tagIds }),
248
- };
249
- const response = await client.createIdea(request);
250
- if (args.json) {
251
- console.log(JSON.stringify(response, null, 2));
252
- return;
253
- }
254
- const { idea } = response;
255
- const title = idea.title || formatDate(idea.createdAt);
256
- console.log(pc.green(`✓ Created idea ${pc.cyan(`#${idea.id}`)} (${title})`));
257
- }
258
- catch (error) {
259
- handleApiError(error);
260
- }
261
- },
262
- });
263
- const updateSubcommand = defineCommand({
264
- meta: {
265
- name: "update",
266
- description: "Update an existing idea",
267
- },
268
- args: {
269
- id: {
270
- type: "positional",
271
- description: "Idea ID",
272
- required: true,
273
- },
274
- content: {
275
- type: "string",
276
- alias: "c",
277
- description: "New content",
278
- },
279
- title: {
280
- type: "string",
281
- alias: "t",
282
- description: "New title (use empty string to clear)",
283
- },
284
- tag: {
285
- type: "string",
286
- alias: "T",
287
- description: "Set tag by name (replaces all existing tags)",
288
- },
289
- json: {
290
- type: "boolean",
291
- description: "Output as JSON",
292
- },
293
- },
294
- async run({ args }) {
295
- const apiKey = requireAuth();
296
- const client = new ApiClient(apiKey);
297
- const id = parsePositiveInt(args.id);
298
- if (id === null) {
299
- console.log(pc.red("Invalid idea ID."));
300
- process.exit(EXIT_INVALID_ARGS);
301
- }
302
- // Build update request
303
- const request = {};
304
- if (args.content !== undefined)
305
- request.content = args.content;
306
- if (args.title !== undefined) {
307
- request.title = args.title === "" ? null : args.title;
308
- }
309
- // Resolve tag name if provided
310
- if (args.tag !== undefined) {
311
- if (args.tag === "") {
312
- // Empty string means clear all tags
313
- request.tagIds = [];
314
- }
315
- else {
316
- const tagId = await resolveTagName(client, args.tag);
317
- if (tagId === null) {
318
- process.exit(EXIT_INVALID_ARGS);
319
- }
320
- request.tagIds = [tagId];
321
- }
322
- }
323
- if (Object.keys(request).length === 0) {
324
- console.log(pc.yellow("No changes specified."));
325
- process.exit(EXIT_INVALID_ARGS);
326
- }
327
- try {
328
- const response = await client.updateIdea(id, request);
329
- if (args.json) {
330
- console.log(JSON.stringify(response, null, 2));
331
- return;
332
- }
333
- const { idea } = response;
334
- const title = idea.title || formatDate(idea.createdAt);
335
- console.log(pc.green(`✓ Updated idea ${pc.cyan(`#${idea.id}`)} (${title})`));
336
- }
337
- catch (error) {
338
- handleApiError(error);
339
- }
340
- },
341
- });
342
- const deleteSubcommand = defineCommand({
343
- meta: {
344
- name: "delete",
345
- description: "Delete an idea",
346
- },
347
- args: {
348
- id: {
349
- type: "positional",
350
- description: "Idea ID",
351
- required: true,
352
- },
353
- yes: {
354
- type: "boolean",
355
- alias: "y",
356
- description: "Skip confirmation",
357
- },
358
- json: {
359
- type: "boolean",
360
- description: "Output as JSON",
361
- },
362
- },
363
- async run({ args }) {
364
- const apiKey = requireAuth();
365
- const client = new ApiClient(apiKey);
366
- const id = parsePositiveInt(args.id);
367
- if (id === null) {
368
- console.log(pc.red("Invalid idea ID."));
369
- process.exit(EXIT_INVALID_ARGS);
370
- }
371
- try {
372
- // Only fetch idea for confirmation prompt (requires ideas:read)
373
- // With --yes flag, skip prefetch to allow write-only keys
374
- if (!args.yes) {
375
- const { idea } = await client.getIdea(id);
376
- const title = idea.title || formatDate(idea.createdAt);
377
- const confirmed = await confirm(`Delete idea #${id} (${title})?`);
378
- if (!confirmed) {
379
- console.log(pc.dim("Cancelled."));
380
- return;
381
- }
382
- }
383
- await client.deleteIdea(id);
384
- if (args.json) {
385
- console.log(JSON.stringify({ success: true, id }, null, 2));
386
- return;
387
- }
388
- console.log(pc.green(`✓ Deleted idea ${pc.cyan(`#${id}`)}`));
389
- }
390
- catch (error) {
391
- handleApiError(error);
392
- }
393
- },
394
- });
395
- // ── Main command ──
8
+ import { defineCommand } from "@nocoo/cli-base";
9
+ import { addSubcommand } from "./idea/add.js";
10
+ import { deleteSubcommand } from "./idea/delete.js";
11
+ import { getSubcommand } from "./idea/get.js";
12
+ import { listSubcommand } from "./idea/list.js";
13
+ import { updateSubcommand } from "./idea/update.js";
14
+ export { formatTags } from "./idea/helpers.js";
396
15
  export const ideaCommand = defineCommand({
397
16
  meta: {
398
17
  name: "idea",
@@ -1 +1 @@
1
- {"version":3,"file":"idea.js","sourceRoot":"","sources":["../../src/commands/idea.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"idea.js","sourceRoot":"","sources":["../../src/commands/idea.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/C,MAAM,CAAC,MAAM,WAAW,GAAG,aAAa,CAAC;IACxC,IAAI,EAAE;QACL,IAAI,EAAE,MAAM;QACZ,WAAW,EAAE,+BAA+B;KAC5C;IACD,WAAW,EAAE;QACZ,IAAI,EAAE,cAAc;QACpB,GAAG,EAAE,aAAa;QAClB,GAAG,EAAE,aAAa;QAClB,MAAM,EAAE,gBAAgB;QACxB,MAAM,EAAE,gBAAgB;KACxB;CACD,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"AAAA;;GAEG;AAmBH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqNtB,CAAC"}
1
+ {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"AAAA;;GAEG;AAkJH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6HtB,CAAC"}
@@ -5,6 +5,100 @@ import { defineCommand, pc } from "@nocoo/cli-base";
5
5
  import { ApiClient, ApiClientError, EXIT_AUTH_REQUIRED, EXIT_ERROR, EXIT_INVALID_ARGS, EXIT_RATE_LIMITED, } from "../api/client.js";
6
6
  import { getApiKey, getOutputFormat } from "../config.js";
7
7
  import { formatLinksMinimal, formatLinksTable, resolveTagName, } from "../utils.js";
8
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
9
+ /** Reject mutually-exclusive flags + invalid sort/order. Exits on error. */
10
+ function validateListArgs(args) {
11
+ if (args.inbox && args.folder) {
12
+ console.log(pc.red("Cannot use --inbox and --folder together."));
13
+ process.exit(EXIT_INVALID_ARGS);
14
+ }
15
+ if (args.sort && args.sort !== "created" && args.sort !== "clicks") {
16
+ console.log(pc.red("Invalid --sort value. Use 'created' or 'clicks'."));
17
+ process.exit(EXIT_INVALID_ARGS);
18
+ }
19
+ if (args.order && args.order !== "asc" && args.order !== "desc") {
20
+ console.log(pc.red("Invalid --order value. Use 'asc' or 'desc'."));
21
+ process.exit(EXIT_INVALID_ARGS);
22
+ }
23
+ }
24
+ /**
25
+ * Build ListLinksParams from CLI args, resolving folder/tag names where
26
+ * needed. Returns the params plus any folder list already fetched for reuse.
27
+ * Exits on invalid folder/tag.
28
+ */
29
+ async function buildListParams(client, args) {
30
+ // Pre-fetch folders only when --folder is a non-UUID name that must be
31
+ // resolved (a UUID can be used directly without folders:read scope).
32
+ const folderArg = args.folder;
33
+ const needsFolderResolution = !!folderArg && !UUID_PATTERN.test(folderArg);
34
+ let cachedFolders;
35
+ if (needsFolderResolution) {
36
+ const foldersResponse = await client.listFolders();
37
+ cachedFolders = foldersResponse.folders;
38
+ }
39
+ const params = {};
40
+ if (args.limit)
41
+ params.limit = Number.parseInt(args.limit, 10);
42
+ if (args.offset)
43
+ params.offset = Number.parseInt(args.offset, 10);
44
+ if (args.query)
45
+ params.query = args.query;
46
+ if (args.inbox)
47
+ params.inbox = true;
48
+ if (args.sort)
49
+ params.sort = args.sort;
50
+ if (args.order)
51
+ params.order = args.order;
52
+ if (folderArg) {
53
+ if (UUID_PATTERN.test(folderArg)) {
54
+ params.folderId = folderArg;
55
+ }
56
+ else if (cachedFolders) {
57
+ const folderId = resolveFolderNameFromCache(folderArg, cachedFolders);
58
+ if (folderId === null)
59
+ process.exit(EXIT_INVALID_ARGS);
60
+ params.folderId = folderId;
61
+ }
62
+ }
63
+ if (args.tag) {
64
+ const tagId = await resolveTagName(client, args.tag);
65
+ if (tagId === null)
66
+ process.exit(EXIT_INVALID_ARGS);
67
+ params.tagId = tagId;
68
+ }
69
+ return cachedFolders ? { params, cachedFolders } : { params };
70
+ }
71
+ /** Build folder + tag name maps for table output, with graceful fallback. */
72
+ async function loadDisplayMaps(client, links, cachedFolders, showTags) {
73
+ const result = {};
74
+ const hasFolders = links.some((l) => l.folderId);
75
+ if (hasFolders) {
76
+ try {
77
+ const folders = cachedFolders ?? (await client.listFolders()).folders;
78
+ result.folderMap = new Map(folders.map((f) => [f.id, f.name]));
79
+ }
80
+ catch {
81
+ // continue without folder names
82
+ }
83
+ }
84
+ // Build tag name map only when we will render tags AND the response is
85
+ // missing embedded tag details. The /links endpoint already includes
86
+ // `tags: Tag[]` per link, so a successful response makes listTags()
87
+ // unnecessary; we only fall back when some tagged link is missing its
88
+ // tags array (older server, or partial response).
89
+ const needsTagFallback = showTags &&
90
+ links.some((l) => (l.tagIds ?? []).length > 0 && (!l.tags || l.tags.length === 0));
91
+ if (needsTagFallback) {
92
+ try {
93
+ const tagsResponse = await client.listTags();
94
+ result.tagMap = new Map(tagsResponse.tags.map((t) => [t.id, t.name]));
95
+ }
96
+ catch {
97
+ // continue without tag names
98
+ }
99
+ }
100
+ return result;
101
+ }
8
102
  export const listCommand = defineCommand({
9
103
  meta: {
10
104
  name: "list",
@@ -79,110 +173,17 @@ export const listCommand = defineCommand({
79
173
  console.log(pc.red("Not authenticated. Run `zhe login` first."));
80
174
  process.exit(EXIT_AUTH_REQUIRED);
81
175
  }
82
- // Validate mutually exclusive options
83
- if (args.inbox && args.folder) {
84
- console.log(pc.red("Cannot use --inbox and --folder together."));
85
- process.exit(EXIT_INVALID_ARGS);
86
- }
87
- // Validate sort option
88
- if (args.sort && args.sort !== "created" && args.sort !== "clicks") {
89
- console.log(pc.red("Invalid --sort value. Use 'created' or 'clicks'."));
90
- process.exit(EXIT_INVALID_ARGS);
91
- }
92
- // Validate order option
93
- if (args.order && args.order !== "asc" && args.order !== "desc") {
94
- console.log(pc.red("Invalid --order value. Use 'asc' or 'desc'."));
95
- process.exit(EXIT_INVALID_ARGS);
96
- }
176
+ validateListArgs(args);
97
177
  const client = new ApiClient(apiKey);
98
178
  try {
99
- // Pre-fetch folders only when needed:
100
- // - --folder is a non-UUID name that must be resolved
101
- // (a UUID can be used directly without folders:read scope)
102
- let cachedFolders;
103
- const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
104
- const folderArg = args.folder;
105
- const needsFolderResolution = !!folderArg && !uuidPattern.test(folderArg);
106
- if (needsFolderResolution) {
107
- const foldersResponse = await client.listFolders();
108
- cachedFolders = foldersResponse.folders;
109
- }
110
- // Build params object, only including defined fields
111
- const params = {};
112
- if (args.limit)
113
- params.limit = Number.parseInt(args.limit, 10);
114
- if (args.offset)
115
- params.offset = Number.parseInt(args.offset, 10);
116
- if (args.query)
117
- params.query = args.query;
118
- if (args.inbox)
119
- params.inbox = true;
120
- if (args.sort)
121
- params.sort = args.sort;
122
- if (args.order)
123
- params.order = args.order;
124
- // Resolve folder argument: UUIDs pass through; names use cache
125
- if (folderArg) {
126
- if (uuidPattern.test(folderArg)) {
127
- params.folderId = folderArg;
128
- }
129
- else if (cachedFolders) {
130
- const folderId = resolveFolderNameFromCache(folderArg, cachedFolders);
131
- if (folderId === null) {
132
- process.exit(EXIT_INVALID_ARGS);
133
- }
134
- params.folderId = folderId;
135
- }
136
- }
137
- // Resolve tag name to ID
138
- if (args.tag) {
139
- const tagId = await resolveTagName(client, args.tag);
140
- if (tagId === null) {
141
- process.exit(EXIT_INVALID_ARGS);
142
- }
143
- params.tagId = tagId;
144
- }
179
+ const { params, cachedFolders } = await buildListParams(client, args);
145
180
  const response = await client.listLinks(params);
146
- // Count-only mode: use total from API response
147
181
  if (args.count) {
148
182
  console.log(response.total);
149
183
  return;
150
184
  }
151
- // Build folder name map for table output
152
- let folderMap;
153
- const hasFolders = response.links.some((l) => l.folderId);
154
- if (hasFolders) {
155
- try {
156
- // Reuse cached folders if available, otherwise fetch
157
- const folders = cachedFolders ?? (await client.listFolders()).folders;
158
- folderMap = new Map(folders.map((f) => [f.id, f.name]));
159
- }
160
- catch {
161
- // If folder fetch fails, continue without folder names
162
- }
163
- }
164
- // Determine whether to show TAGS column.
165
- // Auto-enable when filtering by tag, or when --show-tags is set.
166
185
  const showTags = !!(args["show-tags"] || args.tag);
167
- // Build tag name map only when we will actually render tags AND
168
- // the response is missing embedded tag details. The /links
169
- // endpoint already includes `tags: Tag[]` per link, so a
170
- // successful response makes listTags() unnecessary; we only
171
- // fall back when some tagged link is missing its tags array
172
- // (older server, or partial response).
173
- let tagMap;
174
- const needsTagFallback = showTags &&
175
- response.links.some((l) => (l.tagIds ?? []).length > 0 && (!l.tags || l.tags.length === 0));
176
- if (needsTagFallback) {
177
- try {
178
- const tagsResponse = await client.listTags();
179
- tagMap = new Map(tagsResponse.tags.map((t) => [t.id, t.name]));
180
- }
181
- catch {
182
- // If tag fetch fails, continue without tag names
183
- }
184
- }
185
- // Determine output format
186
+ const { folderMap, tagMap } = await loadDisplayMaps(client, response.links, cachedFolders, showTags);
186
187
  let format = getOutputFormat();
187
188
  if (args.json)
188
189
  format = "json";