@nocoo/zhe 1.16.1 → 1.18.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/dist/api/client.d.ts +25 -1
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +41 -2
- package/dist/api/client.js.map +1 -1
- package/dist/api/types.d.ts +32 -2
- package/dist/api/types.d.ts.map +1 -1
- package/dist/commands/create.js +1 -18
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/folder.d.ts +7 -0
- package/dist/commands/folder.d.ts.map +1 -0
- package/dist/commands/folder.js +226 -0
- package/dist/commands/folder.js.map +1 -0
- package/dist/commands/get.d.ts.map +1 -1
- package/dist/commands/get.js +12 -1
- package/dist/commands/get.js.map +1 -1
- package/dist/commands/idea.d.ts +1 -0
- package/dist/commands/idea.d.ts.map +1 -1
- package/dist/commands/idea.js +26 -23
- package/dist/commands/idea.js.map +1 -1
- package/dist/commands/list.d.ts +4 -0
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +81 -10
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/open.js +1 -23
- package/dist/commands/open.js.map +1 -1
- package/dist/commands/tag.d.ts +8 -0
- package/dist/commands/tag.d.ts.map +1 -0
- package/dist/commands/tag.js +289 -0
- package/dist/commands/tag.js.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/utils.d.ts +36 -2
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +127 -18
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* zhe tag — Manage tags (create, update, delete, list)
|
|
3
|
+
*
|
|
4
|
+
* The plural `zhe tags` command (list-only) is preserved for back-compat;
|
|
5
|
+
* `zhe tag list` is provided as the consistent alias.
|
|
6
|
+
*/
|
|
7
|
+
import * as readline from "node:readline";
|
|
8
|
+
import { defineCommand, pc } from "@nocoo/cli-base";
|
|
9
|
+
import { ApiClient, ApiClientError, EXIT_AUTH_REQUIRED, EXIT_ERROR, EXIT_INVALID_ARGS, EXIT_NOT_FOUND, EXIT_RATE_LIMITED, } from "../api/client.js";
|
|
10
|
+
import { getApiKey } from "../config.js";
|
|
11
|
+
import { formatTagsTable, resolveTagRef } from "../utils.js";
|
|
12
|
+
// ── Helpers ──
|
|
13
|
+
function requireAuth() {
|
|
14
|
+
const apiKey = getApiKey();
|
|
15
|
+
if (!apiKey) {
|
|
16
|
+
console.log(pc.red("Not authenticated. Run `zhe login` first."));
|
|
17
|
+
process.exit(EXIT_AUTH_REQUIRED);
|
|
18
|
+
}
|
|
19
|
+
return apiKey;
|
|
20
|
+
}
|
|
21
|
+
function handleApiError(error) {
|
|
22
|
+
if (error instanceof ApiClientError) {
|
|
23
|
+
if (error.status === 404) {
|
|
24
|
+
console.log(pc.red("Tag not found."));
|
|
25
|
+
process.exit(EXIT_NOT_FOUND);
|
|
26
|
+
}
|
|
27
|
+
console.log(pc.red(`Error: ${error.message}`));
|
|
28
|
+
if (error.status === 401) {
|
|
29
|
+
process.exit(EXIT_AUTH_REQUIRED);
|
|
30
|
+
}
|
|
31
|
+
if (error.status === 429) {
|
|
32
|
+
process.exit(EXIT_RATE_LIMITED);
|
|
33
|
+
}
|
|
34
|
+
process.exit(EXIT_ERROR);
|
|
35
|
+
}
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
async function confirm(message) {
|
|
39
|
+
return new Promise((resolve) => {
|
|
40
|
+
const rl = readline.createInterface({
|
|
41
|
+
input: process.stdin,
|
|
42
|
+
output: process.stdout,
|
|
43
|
+
});
|
|
44
|
+
rl.question(`${message} [y/N] `, (answer) => {
|
|
45
|
+
rl.close();
|
|
46
|
+
resolve(answer.toLowerCase() === "y");
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
const HEX_COLOR_PATTERN = /^#?[0-9A-Fa-f]{6}$/;
|
|
51
|
+
/**
|
|
52
|
+
* Validate hex color and normalize to `#rrggbb` form.
|
|
53
|
+
* Returns null with an error printed if invalid.
|
|
54
|
+
*/
|
|
55
|
+
function normalizeColor(input) {
|
|
56
|
+
if (!HEX_COLOR_PATTERN.test(input)) {
|
|
57
|
+
console.log(pc.red("--color must be a 6-digit hex color (e.g. '#3b82f6' or 'ff5500')."));
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
return input.startsWith("#") ? input : `#${input}`;
|
|
61
|
+
}
|
|
62
|
+
// ── Subcommands ──
|
|
63
|
+
const listSubcommand = defineCommand({
|
|
64
|
+
meta: {
|
|
65
|
+
name: "list",
|
|
66
|
+
description: "List all tags",
|
|
67
|
+
},
|
|
68
|
+
args: {
|
|
69
|
+
json: {
|
|
70
|
+
type: "boolean",
|
|
71
|
+
description: "Output as JSON",
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
async run({ args }) {
|
|
75
|
+
const apiKey = requireAuth();
|
|
76
|
+
const client = new ApiClient(apiKey);
|
|
77
|
+
try {
|
|
78
|
+
const response = await client.listTags();
|
|
79
|
+
if (args.json) {
|
|
80
|
+
console.log(JSON.stringify(response, null, 2));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (response.tags.length === 0) {
|
|
84
|
+
console.log(pc.dim("No tags found."));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
console.log(formatTagsTable(response.tags));
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
handleApiError(error);
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
const createSubcommand = defineCommand({
|
|
95
|
+
meta: {
|
|
96
|
+
name: "create",
|
|
97
|
+
description: "Create a new tag",
|
|
98
|
+
},
|
|
99
|
+
args: {
|
|
100
|
+
name: {
|
|
101
|
+
type: "positional",
|
|
102
|
+
description: "Tag name",
|
|
103
|
+
required: true,
|
|
104
|
+
},
|
|
105
|
+
color: {
|
|
106
|
+
type: "string",
|
|
107
|
+
alias: "c",
|
|
108
|
+
description: "Tag color as 6-digit hex (e.g. '#3b82f6' or 'ff5500')",
|
|
109
|
+
required: true,
|
|
110
|
+
},
|
|
111
|
+
json: {
|
|
112
|
+
type: "boolean",
|
|
113
|
+
description: "Output as JSON",
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
async run({ args }) {
|
|
117
|
+
const apiKey = requireAuth();
|
|
118
|
+
const client = new ApiClient(apiKey);
|
|
119
|
+
const name = args.name.trim();
|
|
120
|
+
if (!name) {
|
|
121
|
+
console.log(pc.red("Tag name cannot be empty."));
|
|
122
|
+
process.exit(EXIT_INVALID_ARGS);
|
|
123
|
+
}
|
|
124
|
+
const color = normalizeColor(args.color);
|
|
125
|
+
if (color === null) {
|
|
126
|
+
process.exit(EXIT_INVALID_ARGS);
|
|
127
|
+
}
|
|
128
|
+
const request = { name, color };
|
|
129
|
+
try {
|
|
130
|
+
const response = await client.createTag(request);
|
|
131
|
+
if (args.json) {
|
|
132
|
+
console.log(JSON.stringify(response, null, 2));
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const { tag } = response;
|
|
136
|
+
console.log(pc.green(`✓ Created tag ${pc.cyan(tag.name)} ${pc.dim(`(${tag.id})`)} ${tag.color}`));
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
handleApiError(error);
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
const updateSubcommand = defineCommand({
|
|
144
|
+
meta: {
|
|
145
|
+
name: "update",
|
|
146
|
+
description: "Update a tag (rename or change color)",
|
|
147
|
+
},
|
|
148
|
+
args: {
|
|
149
|
+
ref: {
|
|
150
|
+
type: "positional",
|
|
151
|
+
description: "Tag name or UUID",
|
|
152
|
+
required: true,
|
|
153
|
+
},
|
|
154
|
+
name: {
|
|
155
|
+
type: "string",
|
|
156
|
+
alias: "n",
|
|
157
|
+
description: "New tag name",
|
|
158
|
+
},
|
|
159
|
+
color: {
|
|
160
|
+
type: "string",
|
|
161
|
+
alias: "c",
|
|
162
|
+
description: "New color as 6-digit hex (e.g. '#3b82f6' or 'ff5500')",
|
|
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 ref = args.ref;
|
|
173
|
+
const request = {};
|
|
174
|
+
if (args.name !== undefined) {
|
|
175
|
+
const trimmed = args.name.trim();
|
|
176
|
+
if (!trimmed) {
|
|
177
|
+
console.log(pc.red("--name cannot be empty."));
|
|
178
|
+
process.exit(EXIT_INVALID_ARGS);
|
|
179
|
+
}
|
|
180
|
+
request.name = trimmed;
|
|
181
|
+
}
|
|
182
|
+
if (args.color !== undefined) {
|
|
183
|
+
const color = normalizeColor(args.color);
|
|
184
|
+
if (color === null) {
|
|
185
|
+
process.exit(EXIT_INVALID_ARGS);
|
|
186
|
+
}
|
|
187
|
+
request.color = color;
|
|
188
|
+
}
|
|
189
|
+
if (Object.keys(request).length === 0) {
|
|
190
|
+
console.log(pc.yellow("No changes specified. Pass --name and/or --color."));
|
|
191
|
+
process.exit(EXIT_INVALID_ARGS);
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
const resolved = await resolveTagRef(client, ref);
|
|
195
|
+
if (resolved.kind === "not_found") {
|
|
196
|
+
console.log(pc.red(`Tag not found: ${ref}.`));
|
|
197
|
+
process.exit(EXIT_NOT_FOUND);
|
|
198
|
+
}
|
|
199
|
+
if (resolved.kind === "ambiguous") {
|
|
200
|
+
console.log(pc.red(`Multiple tags match "${ref}". Please use the tag ID or rename duplicates.`));
|
|
201
|
+
process.exit(EXIT_INVALID_ARGS);
|
|
202
|
+
}
|
|
203
|
+
const response = await client.updateTag(resolved.id, request);
|
|
204
|
+
if (args.json) {
|
|
205
|
+
console.log(JSON.stringify(response, null, 2));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const { tag } = response;
|
|
209
|
+
console.log(pc.green(`✓ Updated tag ${pc.cyan(tag.name)} ${pc.dim(`(${tag.id})`)} ${tag.color}`));
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
handleApiError(error);
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
const deleteSubcommand = defineCommand({
|
|
217
|
+
meta: {
|
|
218
|
+
name: "delete",
|
|
219
|
+
description: "Delete a tag",
|
|
220
|
+
},
|
|
221
|
+
args: {
|
|
222
|
+
ref: {
|
|
223
|
+
type: "positional",
|
|
224
|
+
description: "Tag name or UUID",
|
|
225
|
+
required: true,
|
|
226
|
+
},
|
|
227
|
+
yes: {
|
|
228
|
+
type: "boolean",
|
|
229
|
+
alias: "y",
|
|
230
|
+
description: "Skip confirmation prompt",
|
|
231
|
+
},
|
|
232
|
+
json: {
|
|
233
|
+
type: "boolean",
|
|
234
|
+
description: "Output as JSON",
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
async run({ args }) {
|
|
238
|
+
const apiKey = requireAuth();
|
|
239
|
+
const client = new ApiClient(apiKey);
|
|
240
|
+
const ref = args.ref;
|
|
241
|
+
try {
|
|
242
|
+
const resolved = await resolveTagRef(client, ref);
|
|
243
|
+
if (resolved.kind === "not_found") {
|
|
244
|
+
console.log(pc.red(`Tag not found: ${ref}.`));
|
|
245
|
+
process.exit(EXIT_NOT_FOUND);
|
|
246
|
+
}
|
|
247
|
+
if (resolved.kind === "ambiguous") {
|
|
248
|
+
console.log(pc.red(`Multiple tags match "${ref}". Please use the tag ID or rename duplicates.`));
|
|
249
|
+
process.exit(EXIT_INVALID_ARGS);
|
|
250
|
+
}
|
|
251
|
+
const { id: tagId, name: tagName } = resolved;
|
|
252
|
+
const tagLabel = tagName ?? tagId;
|
|
253
|
+
if (!args.yes) {
|
|
254
|
+
if (!process.stdin.isTTY) {
|
|
255
|
+
console.log(pc.red("Refusing to delete without --yes in non-interactive mode."));
|
|
256
|
+
process.exit(EXIT_INVALID_ARGS);
|
|
257
|
+
}
|
|
258
|
+
const confirmed = await confirm(`Delete tag "${tagLabel}" (${tagId})?`);
|
|
259
|
+
if (!confirmed) {
|
|
260
|
+
console.log(pc.dim("Cancelled."));
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
await client.deleteTag(tagId);
|
|
265
|
+
if (args.json) {
|
|
266
|
+
console.log(JSON.stringify({ success: true, id: tagId, name: tagName ?? null }, null, 2));
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
console.log(pc.green(`✓ Deleted tag ${pc.cyan(tagLabel)} ${pc.dim(`(${tagId})`)}`));
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
handleApiError(error);
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
// ── Main command ──
|
|
277
|
+
export const tagCommand = defineCommand({
|
|
278
|
+
meta: {
|
|
279
|
+
name: "tag",
|
|
280
|
+
description: "Manage tags (create, update, delete, list)",
|
|
281
|
+
},
|
|
282
|
+
subCommands: {
|
|
283
|
+
create: createSubcommand,
|
|
284
|
+
update: updateSubcommand,
|
|
285
|
+
delete: deleteSubcommand,
|
|
286
|
+
list: listSubcommand,
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
//# sourceMappingURL=tag.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tag.js","sourceRoot":"","sources":["../../src/commands/tag.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { defineCommand, runMain } from "@nocoo/cli-base";
|
|
6
6
|
import { createCommand } from "./commands/create.js";
|
|
7
7
|
import { deleteCommand } from "./commands/delete.js";
|
|
8
|
+
import { folderCommand } from "./commands/folder.js";
|
|
8
9
|
import { foldersCommand } from "./commands/folders.js";
|
|
9
10
|
import { getCommand } from "./commands/get.js";
|
|
10
11
|
import { ideaCommand } from "./commands/idea.js";
|
|
@@ -13,6 +14,7 @@ import { listCommand } from "./commands/list.js";
|
|
|
13
14
|
import { loginCommand } from "./commands/login.js";
|
|
14
15
|
import { logoutCommand } from "./commands/logout.js";
|
|
15
16
|
import { openCommand } from "./commands/open.js";
|
|
17
|
+
import { tagCommand } from "./commands/tag.js";
|
|
16
18
|
import { tagsCommand } from "./commands/tags.js";
|
|
17
19
|
import { updateCommand } from "./commands/update.js";
|
|
18
20
|
import { CLI_VERSION } from "./version.js";
|
|
@@ -28,7 +30,9 @@ const main = defineCommand({
|
|
|
28
30
|
list: listCommand,
|
|
29
31
|
inbox: inboxCommand,
|
|
30
32
|
folders: foldersCommand,
|
|
33
|
+
folder: folderCommand,
|
|
31
34
|
tags: tagsCommand,
|
|
35
|
+
tag: tagCommand,
|
|
32
36
|
idea: ideaCommand,
|
|
33
37
|
create: createCommand,
|
|
34
38
|
get: getCommand,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;GAEG;AAEH,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,MAAM,IAAI,GAAG,aAAa,CAAC;IAC1B,IAAI,EAAE;QACL,IAAI,EAAE,KAAK;QACX,OAAO,EAAE,WAAW;QACpB,WAAW,EAAE,qCAAqC;KAClD;IACD,WAAW,EAAE;QACZ,KAAK,EAAE,YAAY;QACnB,MAAM,EAAE,aAAa;QACrB,IAAI,EAAE,WAAW;QACjB,KAAK,EAAE,YAAY;QACnB,OAAO,EAAE,cAAc;QACvB,IAAI,EAAE,WAAW;QACjB,IAAI,EAAE,WAAW;QACjB,MAAM,EAAE,aAAa;QACrB,GAAG,EAAE,UAAU;QACf,MAAM,EAAE,aAAa;QACrB,MAAM,EAAE,aAAa;QACrB,IAAI,EAAE,WAAW;KACjB;CACD,CAAC,CAAC;AAEH,OAAO,CAAC,IAAI,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;GAEG;AAEH,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,MAAM,IAAI,GAAG,aAAa,CAAC;IAC1B,IAAI,EAAE;QACL,IAAI,EAAE,KAAK;QACX,OAAO,EAAE,WAAW;QACpB,WAAW,EAAE,qCAAqC;KAClD;IACD,WAAW,EAAE;QACZ,KAAK,EAAE,YAAY;QACnB,MAAM,EAAE,aAAa;QACrB,IAAI,EAAE,WAAW;QACjB,KAAK,EAAE,YAAY;QACnB,OAAO,EAAE,cAAc;QACvB,MAAM,EAAE,aAAa;QACrB,IAAI,EAAE,WAAW;QACjB,GAAG,EAAE,UAAU;QACf,IAAI,EAAE,WAAW;QACjB,MAAM,EAAE,aAAa;QACrB,GAAG,EAAE,UAAU;QACf,MAAM,EAAE,aAAa;QACrB,MAAM,EAAE,aAAa;QACrB,IAAI,EAAE,WAAW;KACjB;CACD,CAAC,CAAC;AAEH,OAAO,CAAC,IAAI,CAAC,CAAC"}
|
package/dist/utils.d.ts
CHANGED
|
@@ -29,6 +29,8 @@ export declare function truncate(str: string, maxLength: number): string;
|
|
|
29
29
|
export interface FormatLinksTableOptions {
|
|
30
30
|
wide?: boolean;
|
|
31
31
|
folderMap?: Map<string, string>;
|
|
32
|
+
tagMap?: Map<string, string>;
|
|
33
|
+
showTags?: boolean;
|
|
32
34
|
}
|
|
33
35
|
/**
|
|
34
36
|
* Format links as a table
|
|
@@ -42,10 +44,13 @@ export declare function formatLinksMinimal(links: Link[]): string;
|
|
|
42
44
|
* Format a single link for detailed display
|
|
43
45
|
* @param link - The link to format
|
|
44
46
|
* @param folderName - Optional folder name (if not provided, shows folder ID)
|
|
47
|
+
* @param tagMap - Optional map of tagId → name (falls back to tagId if missing)
|
|
45
48
|
*/
|
|
46
|
-
export declare function formatLinkDetail(link: Link, folderName?: string): string;
|
|
49
|
+
export declare function formatLinkDetail(link: Link, folderName?: string, tagMap?: Map<string, string>): string;
|
|
50
|
+
export declare function parsePositiveInt(input: string): number | null;
|
|
47
51
|
/**
|
|
48
|
-
* Parse a link ID from string, return null if invalid
|
|
52
|
+
* Parse a link ID from string, return null if invalid.
|
|
53
|
+
* Uses strict positive-integer parsing.
|
|
49
54
|
*/
|
|
50
55
|
export declare function parseLinkId(input: string): number | null;
|
|
51
56
|
/**
|
|
@@ -62,10 +67,39 @@ export declare function formatTagsTable(tags: Tag[]): string;
|
|
|
62
67
|
* Returns null if folder is not found (with error message printed).
|
|
63
68
|
*/
|
|
64
69
|
export declare function resolveFolderName(client: ApiClient, input: string): Promise<string | null>;
|
|
70
|
+
/**
|
|
71
|
+
* Open a URL in the default browser.
|
|
72
|
+
* Uses spawn() with argv to prevent shell injection.
|
|
73
|
+
*/
|
|
74
|
+
export declare function openInBrowser(url: string): void;
|
|
65
75
|
/**
|
|
66
76
|
* Resolve a tag name to tag ID via API.
|
|
67
77
|
* If input looks like a UUID, use it directly.
|
|
68
78
|
* Returns null if tag is not found (with error message printed).
|
|
69
79
|
*/
|
|
70
80
|
export declare function resolveTagName(client: ApiClient, input: string): Promise<string | null>;
|
|
81
|
+
/**
|
|
82
|
+
* Resolve a tag reference (name or UUID) to its canonical id + name.
|
|
83
|
+
*
|
|
84
|
+
* Unlike `resolveTagName`, this:
|
|
85
|
+
* - Distinguishes `not_found` (zero matches) from `ambiguous` (multiple
|
|
86
|
+
* name matches), letting callers map them to different exit codes.
|
|
87
|
+
* - For UUID input, returns immediately without calling `listTags()` so
|
|
88
|
+
* that API keys with only `tags:write` (no `tags:read`) can still
|
|
89
|
+
* update/delete by ID. The server is the source of truth for whether
|
|
90
|
+
* the tag exists. `name` is `undefined` in that case; callers should
|
|
91
|
+
* fall back to the UUID in any prompt text.
|
|
92
|
+
*
|
|
93
|
+
* Does NOT print any error itself — callers decide messaging and exit codes.
|
|
94
|
+
*/
|
|
95
|
+
export type TagRef = {
|
|
96
|
+
kind: "found";
|
|
97
|
+
id: string;
|
|
98
|
+
name: string | undefined;
|
|
99
|
+
} | {
|
|
100
|
+
kind: "not_found";
|
|
101
|
+
} | {
|
|
102
|
+
kind: "ambiguous";
|
|
103
|
+
};
|
|
104
|
+
export declare function resolveTagRef(client: ApiClient, input: string): Promise<TagRef>;
|
|
71
105
|
//# sourceMappingURL=utils.d.ts.map
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAExD;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAOjD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAE3D;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAIlD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGtD;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAK/D;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACvC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,MAAM,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC/B,KAAK,EAAE,IAAI,EAAE,EACb,OAAO,GAAE,uBAA4B,GACnC,MAAM,CA+ER;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,CAKxD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC/B,IAAI,EAAE,IAAI,EACV,UAAU,CAAC,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC1B,MAAM,CAmCR;AAUD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAS7D;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAExD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAmB5D;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,MAAM,CAkBnD;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACtC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA8BxB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAgC/C;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CACnC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA4BxB;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,MAAM,GACf;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC;AAEzB,wBAAsB,aAAa,CAClC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,CAAC,CAyBjB"}
|
package/dist/utils.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared utilities for zhe CLI
|
|
3
3
|
*/
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
4
5
|
import { pc } from "@nocoo/cli-base";
|
|
5
6
|
/**
|
|
6
7
|
* Mask an API key for display: zhe_abcd...wxyz
|
|
@@ -50,13 +51,27 @@ export function formatLinksTable(links, options = {}) {
|
|
|
50
51
|
if (links.length === 0) {
|
|
51
52
|
return "No links found.";
|
|
52
53
|
}
|
|
53
|
-
const { wide = false, folderMap } = options;
|
|
54
|
+
const { wide = false, folderMap, tagMap, showTags = false } = options;
|
|
54
55
|
const showFolders = links.some((l) => l.folderId);
|
|
56
|
+
const formatTags = (link) => {
|
|
57
|
+
// Prefer embedded tags (carries names) — avoids needing a tagMap
|
|
58
|
+
// fetch from the caller when /links already returned tag details.
|
|
59
|
+
if (link.tags && link.tags.length > 0) {
|
|
60
|
+
return link.tags.map((t) => t.name).join(",");
|
|
61
|
+
}
|
|
62
|
+
const ids = link.tagIds ?? [];
|
|
63
|
+
if (ids.length === 0)
|
|
64
|
+
return "";
|
|
65
|
+
return ids.map((id) => tagMap?.get(id) ?? id.slice(0, 8)).join(",");
|
|
66
|
+
};
|
|
55
67
|
if (wide) {
|
|
56
68
|
// Wide mode: no truncation
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
69
|
+
let header = "ID SLUG URL ";
|
|
70
|
+
if (showFolders)
|
|
71
|
+
header += "FOLDER ";
|
|
72
|
+
if (showTags)
|
|
73
|
+
header += "TAGS ";
|
|
74
|
+
header += "CLICKS CREATED";
|
|
60
75
|
const separator = "─".repeat(header.length);
|
|
61
76
|
const rows = links.map((link) => {
|
|
62
77
|
const id = String(link.id).padEnd(6);
|
|
@@ -64,21 +79,28 @@ export function formatLinksTable(links, options = {}) {
|
|
|
64
79
|
const url = link.originalUrl.padEnd(48);
|
|
65
80
|
const clicks = String(link.clicks).padEnd(7);
|
|
66
81
|
const created = formatDate(link.createdAt);
|
|
82
|
+
let row = `${id} ${slug} ${url} `;
|
|
67
83
|
if (showFolders) {
|
|
68
84
|
const folderName = link.folderId
|
|
69
85
|
? (folderMap?.get(link.folderId) ?? link.folderId.slice(0, 8))
|
|
70
86
|
: "";
|
|
71
|
-
|
|
72
|
-
|
|
87
|
+
row += `${folderName.padEnd(16)} `;
|
|
88
|
+
}
|
|
89
|
+
if (showTags) {
|
|
90
|
+
row += `${formatTags(link).padEnd(26)} `;
|
|
73
91
|
}
|
|
74
|
-
|
|
92
|
+
row += `${clicks} ${created}`;
|
|
93
|
+
return row;
|
|
75
94
|
});
|
|
76
95
|
return [header, separator, ...rows].join("\n");
|
|
77
96
|
}
|
|
78
97
|
// Default compact mode with truncation
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
98
|
+
let header = "ID SLUG URL ";
|
|
99
|
+
if (showFolders)
|
|
100
|
+
header += "FOLDER ";
|
|
101
|
+
if (showTags)
|
|
102
|
+
header += "TAGS ";
|
|
103
|
+
header += "CLICKS CREATED";
|
|
82
104
|
const separator = "─".repeat(header.length);
|
|
83
105
|
const rows = links.map((link) => {
|
|
84
106
|
const id = String(link.id).padEnd(6);
|
|
@@ -86,14 +108,18 @@ export function formatLinksTable(links, options = {}) {
|
|
|
86
108
|
const url = truncate(link.originalUrl, 32).padEnd(32);
|
|
87
109
|
const clicks = String(link.clicks).padEnd(7);
|
|
88
110
|
const created = formatDate(link.createdAt);
|
|
111
|
+
let row = `${id} ${slug} ${url} `;
|
|
89
112
|
if (showFolders) {
|
|
90
113
|
const folderName = link.folderId
|
|
91
114
|
? (folderMap?.get(link.folderId) ?? link.folderId.slice(0, 8))
|
|
92
115
|
: "";
|
|
93
|
-
|
|
94
|
-
|
|
116
|
+
row += `${truncate(folderName, 12).padEnd(12)} `;
|
|
117
|
+
}
|
|
118
|
+
if (showTags) {
|
|
119
|
+
row += `${truncate(formatTags(link), 12).padEnd(12)} `;
|
|
95
120
|
}
|
|
96
|
-
|
|
121
|
+
row += `${clicks} ${created}`;
|
|
122
|
+
return row;
|
|
97
123
|
});
|
|
98
124
|
return [header, separator, ...rows].join("\n");
|
|
99
125
|
}
|
|
@@ -110,8 +136,9 @@ export function formatLinksMinimal(links) {
|
|
|
110
136
|
* Format a single link for detailed display
|
|
111
137
|
* @param link - The link to format
|
|
112
138
|
* @param folderName - Optional folder name (if not provided, shows folder ID)
|
|
139
|
+
* @param tagMap - Optional map of tagId → name (falls back to tagId if missing)
|
|
113
140
|
*/
|
|
114
|
-
export function formatLinkDetail(link, folderName) {
|
|
141
|
+
export function formatLinkDetail(link, folderName, tagMap) {
|
|
115
142
|
const lines = [
|
|
116
143
|
`Link #${link.id}`,
|
|
117
144
|
"",
|
|
@@ -124,6 +151,16 @@ export function formatLinkDetail(link, folderName) {
|
|
|
124
151
|
const folderDisplay = folderName ?? link.folderId;
|
|
125
152
|
lines.push(` Folder: ${folderDisplay}`);
|
|
126
153
|
}
|
|
154
|
+
if (link.tags && link.tags.length > 0) {
|
|
155
|
+
lines.push(` Tags: ${link.tags.map((t) => t.name).join(", ")}`);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
const tagIds = link.tagIds ?? [];
|
|
159
|
+
if (tagIds.length > 0) {
|
|
160
|
+
const tagDisplay = tagIds.map((id) => tagMap?.get(id) ?? id).join(", ");
|
|
161
|
+
lines.push(` Tags: ${tagDisplay}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
127
164
|
if (link.note) {
|
|
128
165
|
lines.push(` Note: ${link.note}`);
|
|
129
166
|
}
|
|
@@ -132,15 +169,29 @@ export function formatLinkDetail(link, folderName) {
|
|
|
132
169
|
return lines.join("\n");
|
|
133
170
|
}
|
|
134
171
|
/**
|
|
135
|
-
*
|
|
172
|
+
* Strict positive-integer parser.
|
|
173
|
+
* Rejects empty strings, 0, negatives, decimals, scientific notation,
|
|
174
|
+
* hex literals, and any junk-suffixed input (e.g. "12abc", "1e2", "0x10").
|
|
175
|
+
* Only accepts decimal digit sequences without a leading zero.
|
|
136
176
|
*/
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (
|
|
177
|
+
const STRICT_POSITIVE_INT = /^[1-9]\d*$/;
|
|
178
|
+
export function parsePositiveInt(input) {
|
|
179
|
+
if (!STRICT_POSITIVE_INT.test(input)) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
const id = Number(input);
|
|
183
|
+
if (!Number.isSafeInteger(id) || id <= 0) {
|
|
140
184
|
return null;
|
|
141
185
|
}
|
|
142
186
|
return id;
|
|
143
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Parse a link ID from string, return null if invalid.
|
|
190
|
+
* Uses strict positive-integer parsing.
|
|
191
|
+
*/
|
|
192
|
+
export function parseLinkId(input) {
|
|
193
|
+
return parsePositiveInt(input);
|
|
194
|
+
}
|
|
144
195
|
/**
|
|
145
196
|
* Format folders as a table
|
|
146
197
|
*/
|
|
@@ -203,6 +254,43 @@ export async function resolveFolderName(client, input) {
|
|
|
203
254
|
}
|
|
204
255
|
return matches[0].id;
|
|
205
256
|
}
|
|
257
|
+
/**
|
|
258
|
+
* Open a URL in the default browser.
|
|
259
|
+
* Uses spawn() with argv to prevent shell injection.
|
|
260
|
+
*/
|
|
261
|
+
export function openInBrowser(url) {
|
|
262
|
+
const platform = process.platform;
|
|
263
|
+
let command;
|
|
264
|
+
let args;
|
|
265
|
+
if (platform === "darwin") {
|
|
266
|
+
command = "open";
|
|
267
|
+
args = [url];
|
|
268
|
+
}
|
|
269
|
+
else if (platform === "linux") {
|
|
270
|
+
command = "xdg-open";
|
|
271
|
+
args = [url];
|
|
272
|
+
}
|
|
273
|
+
else if (platform === "win32") {
|
|
274
|
+
command = "cmd";
|
|
275
|
+
args = ["/c", "start", "", url];
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
console.log(pc.dim(`Unable to open browser on ${platform}. Visit: ${url}`));
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const child = spawn(command, args, { stdio: "ignore" });
|
|
282
|
+
const fallback = () => console.log(pc.dim(`Failed to open browser. Visit: ${url}`));
|
|
283
|
+
let failed = false;
|
|
284
|
+
child.on("error", () => {
|
|
285
|
+
failed = true;
|
|
286
|
+
fallback();
|
|
287
|
+
});
|
|
288
|
+
child.on("close", (code) => {
|
|
289
|
+
if (!failed && code !== 0) {
|
|
290
|
+
fallback();
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
}
|
|
206
294
|
/**
|
|
207
295
|
* Resolve a tag name to tag ID via API.
|
|
208
296
|
* If input looks like a UUID, use it directly.
|
|
@@ -226,4 +314,25 @@ export async function resolveTagName(client, input) {
|
|
|
226
314
|
}
|
|
227
315
|
return matches[0].id;
|
|
228
316
|
}
|
|
317
|
+
export async function resolveTagRef(client, input) {
|
|
318
|
+
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
319
|
+
if (uuidPattern.test(input)) {
|
|
320
|
+
// Trust the UUID; let the destructive op's PATCH/DELETE be the
|
|
321
|
+
// authoritative existence check. Avoids requiring tags:read.
|
|
322
|
+
// Normalize to lowercase: tag IDs are stored lowercase, but the
|
|
323
|
+
// regex accepts uppercase (RFC 4122 allows either case in
|
|
324
|
+
// notation). The DB is `WHERE id = ?` text-equal, so an
|
|
325
|
+
// uppercase UUID would 404 on a tag that actually exists.
|
|
326
|
+
return { kind: "found", id: input.toLowerCase(), name: undefined };
|
|
327
|
+
}
|
|
328
|
+
const { tags } = await client.listTags();
|
|
329
|
+
const matches = tags.filter((t) => t.name.toLowerCase() === input.toLowerCase());
|
|
330
|
+
if (matches.length === 0) {
|
|
331
|
+
return { kind: "not_found" };
|
|
332
|
+
}
|
|
333
|
+
if (matches.length > 1) {
|
|
334
|
+
return { kind: "ambiguous" };
|
|
335
|
+
}
|
|
336
|
+
return { kind: "found", id: matches[0].id, name: matches[0].name };
|
|
337
|
+
}
|
|
229
338
|
//# sourceMappingURL=utils.js.map
|