donebear 0.3.7 → 0.3.9
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/AGENTS.md +2 -2
- package/dist/cli.mjs +625 -362
- package/dist/cli.mjs.map +1 -1
- package/dist/{context-store-DH561Z1u.mjs → context-store-BBEVWudm.mjs} +5 -5
- package/dist/{context-store-DH561Z1u.mjs.map → context-store-BBEVWudm.mjs.map} +1 -1
- package/dist/context-store-ClAx1T2N.mjs +3 -0
- package/dist/prompt-B7mDo4K7.mjs +3 -0
- package/dist/{prompt-BrfcK2Ry.mjs → prompt-GfcWO85b.mjs} +2 -2
- package/dist/prompt-GfcWO85b.mjs.map +1 -0
- package/package.json +8 -8
- package/dist/context-store-D0Z9vbaU.mjs +0 -3
- package/dist/prompt-BrfcK2Ry.mjs.map +0 -1
- package/dist/prompt-FRvfbinv.mjs +0 -3
package/dist/cli.mjs
CHANGED
|
@@ -1,30 +1,261 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as toCliError, b as DONEBEAR_TOKEN_ENV, d as DEFAULT_OAUTH_CALLBACK_PATH, f as DEFAULT_OAUTH_CALLBACK_PORT, i as EXIT_CODES, l as CONFIG_DIR, n as setCurrentWorkspace, o as AUTH_FILE_PATH, r as CliError, s as CLI_NAME, t as ensureLocalContext } from "./context-store-
|
|
3
|
-
import { n as readStdin, t as promptForValue } from "./prompt-
|
|
2
|
+
import { a as toCliError, b as DONEBEAR_TOKEN_ENV, d as DEFAULT_OAUTH_CALLBACK_PATH, f as DEFAULT_OAUTH_CALLBACK_PORT, i as EXIT_CODES, l as CONFIG_DIR, n as setCurrentWorkspace, o as AUTH_FILE_PATH, r as CliError, s as CLI_NAME, t as ensureLocalContext } from "./context-store-BBEVWudm.mjs";
|
|
3
|
+
import { n as readStdin, t as promptForValue } from "./prompt-GfcWO85b.mjs";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
|
-
import { Command, InvalidArgumentError } from "commander";
|
|
6
|
-
import { homedir } from "node:os";
|
|
7
|
-
import { basename, join, resolve } from "node:path";
|
|
8
5
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
6
|
+
import { basename, join, resolve } from "node:path";
|
|
7
|
+
import { createInterface } from "node:readline";
|
|
8
|
+
import { homedir } from "node:os";
|
|
12
9
|
import { execSync, spawn } from "node:child_process";
|
|
13
10
|
import { styleText } from "node:util";
|
|
14
11
|
import jmespath from "jmespath";
|
|
15
12
|
import { stringify } from "yaml";
|
|
13
|
+
import { accessSync, constants, existsSync, readdirSync, statSync } from "node:fs";
|
|
14
|
+
import { config } from "dotenv";
|
|
15
|
+
import { Command, InvalidArgumentError } from "commander";
|
|
16
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
16
17
|
import { createServer } from "node:http";
|
|
17
18
|
import { fileURLToPath } from "node:url";
|
|
18
19
|
import Database from "better-sqlite3";
|
|
19
|
-
import { createInterface } from "node:readline";
|
|
20
20
|
//#region \0rolldown/runtime.js
|
|
21
|
-
var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
21
|
+
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/output.ts
|
|
24
|
+
const writeLine = (stream, message) => {
|
|
25
|
+
stream.write(`${message}\n`);
|
|
26
|
+
};
|
|
27
|
+
const colorize = (context, format, message) => {
|
|
28
|
+
if (!context.color) return message;
|
|
29
|
+
return styleText(format, message);
|
|
30
|
+
};
|
|
31
|
+
const writeJson = (value) => {
|
|
32
|
+
writeLine(process.stdout, JSON.stringify(value, null, 2));
|
|
33
|
+
};
|
|
34
|
+
const writeYaml = (value) => {
|
|
35
|
+
process.stdout.write(stringify(value));
|
|
36
|
+
};
|
|
37
|
+
const writeInfo = (context, message) => {
|
|
38
|
+
writeLine(process.stdout, colorize(context, "cyan", message));
|
|
39
|
+
};
|
|
40
|
+
const writeSuccess = (context, message) => {
|
|
41
|
+
writeLine(process.stdout, colorize(context, "green", message));
|
|
42
|
+
};
|
|
43
|
+
const writeWarning = (context, message) => {
|
|
44
|
+
writeLine(process.stderr, colorize(context, "yellow", message));
|
|
45
|
+
};
|
|
46
|
+
const writeError = (context, message) => {
|
|
47
|
+
writeLine(process.stderr, colorize(context, "red", message));
|
|
48
|
+
};
|
|
49
|
+
const writeDebug = (context, message) => {
|
|
50
|
+
if (!context.debug) return;
|
|
51
|
+
writeLine(process.stderr, colorize(context, "gray", `[debug] ${message}`));
|
|
52
|
+
};
|
|
53
|
+
const writeTotal = (context, count) => {
|
|
54
|
+
writeLine(process.stdout, colorize(context, "cyan", String(count)));
|
|
55
|
+
};
|
|
56
|
+
const escapeCsvField = (value) => {
|
|
57
|
+
if (value.includes(",") || value.includes("\"") || value.includes("\n")) return `"${value.replaceAll("\"", "\"\"")}"`;
|
|
58
|
+
return value;
|
|
59
|
+
};
|
|
60
|
+
const writeCsv = (headers, rows) => {
|
|
61
|
+
writeLine(process.stdout, headers.map(escapeCsvField).join(","));
|
|
62
|
+
for (const row of rows) writeLine(process.stdout, row.map(escapeCsvField).join(","));
|
|
63
|
+
};
|
|
64
|
+
const writeTsv = (headers, rows) => {
|
|
65
|
+
writeLine(process.stdout, headers.join(" "));
|
|
66
|
+
for (const row of rows) writeLine(process.stdout, row.join(" "));
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Write structured data to stdout, applying --jq filter and --limit if set.
|
|
70
|
+
* Use this for all JSON/YAML/structured output from commands.
|
|
71
|
+
*/
|
|
72
|
+
const writeOutput = (context, data) => {
|
|
73
|
+
let result = data;
|
|
74
|
+
if (context.jq) try {
|
|
75
|
+
result = jmespath.search(result, context.jq);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
throw new CliError(`Invalid --jq expression: ${error instanceof Error ? error.message : String(error)}`, EXIT_CODES.VALIDATION);
|
|
78
|
+
}
|
|
79
|
+
if (context.limit !== null && Array.isArray(result)) result = result.slice(0, context.limit);
|
|
80
|
+
switch (context.format) {
|
|
81
|
+
case "yaml":
|
|
82
|
+
writeYaml(result);
|
|
83
|
+
break;
|
|
84
|
+
case "json":
|
|
85
|
+
writeJson(result);
|
|
86
|
+
break;
|
|
87
|
+
default:
|
|
88
|
+
writeJson(result);
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
if (context.copy) copyToClipboard(JSON.stringify(result, null, 2));
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Handles --total, --format csv, --format tsv, --format yaml output.
|
|
95
|
+
* Returns true if the format was handled; caller renders text mode if false.
|
|
96
|
+
*/
|
|
97
|
+
const writeFormattedRows = (context, totalCount, headers, rows) => {
|
|
98
|
+
if (context.total) {
|
|
99
|
+
writeTotal(context, totalCount);
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
if (context.format === "yaml") {
|
|
103
|
+
const objects = rows.map((row) => Object.fromEntries(headers.map((h, i) => [h, row[i]])));
|
|
104
|
+
writeYaml(objects);
|
|
105
|
+
if (context.copy) copyToClipboard(stringify(objects));
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
if (context.format === "csv") {
|
|
109
|
+
writeCsv(headers, rows);
|
|
110
|
+
if (context.copy) copyToClipboard([headers.join(","), ...rows.map((r) => r.join(","))].join("\n"));
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
if (context.format === "tsv") {
|
|
114
|
+
writeTsv(headers, rows);
|
|
115
|
+
if (context.copy) copyToClipboard([headers.join(" "), ...rows.map((r) => r.join(" "))].join("\n"));
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
};
|
|
120
|
+
const copyToClipboard = (text) => {
|
|
121
|
+
try {
|
|
122
|
+
if (process.platform === "darwin") execSync("pbcopy", { input: text });
|
|
123
|
+
else if (process.platform === "win32") execSync("clip", { input: text });
|
|
124
|
+
else try {
|
|
125
|
+
execSync("xclip -selection clipboard", { input: text });
|
|
126
|
+
} catch {
|
|
127
|
+
execSync("xsel --clipboard --input", { input: text });
|
|
128
|
+
}
|
|
129
|
+
return true;
|
|
130
|
+
} catch {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
//#endregion
|
|
135
|
+
//#region src/commands/interactive.ts
|
|
136
|
+
const HISTORY_FILE = join(CONFIG_DIR, "history.json");
|
|
137
|
+
const MAX_HISTORY = 200;
|
|
138
|
+
const isHistoryFile = (value) => {
|
|
139
|
+
if (typeof value !== "object" || value === null) return false;
|
|
140
|
+
const record = value;
|
|
141
|
+
return record.version === 1 && Array.isArray(record.entries) && record.entries.every((e) => typeof e === "string");
|
|
142
|
+
};
|
|
143
|
+
const loadHistory = async (historyFile) => {
|
|
144
|
+
try {
|
|
145
|
+
const raw = await readFile(historyFile, "utf-8");
|
|
146
|
+
const parsed = JSON.parse(raw);
|
|
147
|
+
if (isHistoryFile(parsed)) return parsed.entries;
|
|
148
|
+
return [];
|
|
149
|
+
} catch {
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
const tokenize = (input) => {
|
|
154
|
+
const tokens = [];
|
|
155
|
+
let current = "";
|
|
156
|
+
let inQuote = false;
|
|
157
|
+
let quoteChar = "";
|
|
158
|
+
for (const char of input) if (inQuote) if (char === quoteChar) inQuote = false;
|
|
159
|
+
else current += char;
|
|
160
|
+
else if (char === "\"" || char === "'") {
|
|
161
|
+
inQuote = true;
|
|
162
|
+
quoteChar = char;
|
|
163
|
+
} else if (char === " ") {
|
|
164
|
+
if (current.length > 0) {
|
|
165
|
+
tokens.push(current);
|
|
166
|
+
current = "";
|
|
167
|
+
}
|
|
168
|
+
} else current += char;
|
|
169
|
+
if (current.length > 0) tokens.push(current);
|
|
170
|
+
return tokens;
|
|
171
|
+
};
|
|
172
|
+
const buildCompleter = (program) => (line) => {
|
|
173
|
+
const allCommands = program.commands.flatMap((cmd) => [cmd.name(), ...cmd.aliases()]);
|
|
174
|
+
const hits = allCommands.filter((c) => c.startsWith(line));
|
|
175
|
+
return [hits.length > 0 ? hits : allCommands, line];
|
|
176
|
+
};
|
|
177
|
+
const runInteractiveMode = async (context, program) => {
|
|
178
|
+
const history = await loadHistory(HISTORY_FILE);
|
|
179
|
+
const rl = createInterface({
|
|
180
|
+
completer: buildCompleter(program),
|
|
181
|
+
historySize: MAX_HISTORY,
|
|
182
|
+
input: process.stdin,
|
|
183
|
+
output: process.stdout,
|
|
184
|
+
prompt: "donebear> "
|
|
185
|
+
});
|
|
186
|
+
for (const entry of [...history].toReversed()) rl.history.push(entry);
|
|
187
|
+
writeSuccess(context, "Done Bear interactive mode. Type \"exit\" or Ctrl+D to quit.");
|
|
188
|
+
writeInfo(context, "Commands: task, workspace, project, label, team, today, search, auth, whoami");
|
|
189
|
+
rl.prompt();
|
|
190
|
+
const sessionEntries = [];
|
|
191
|
+
for await (const line of rl) {
|
|
192
|
+
const trimmed = line.trim();
|
|
193
|
+
if (!trimmed) {
|
|
194
|
+
rl.prompt();
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (trimmed === "exit" || trimmed === "quit" || trimmed === ":q") break;
|
|
198
|
+
const tokens = tokenize(trimmed);
|
|
199
|
+
try {
|
|
200
|
+
await program.parseAsync([
|
|
201
|
+
"node",
|
|
202
|
+
"donebear",
|
|
203
|
+
...tokens
|
|
204
|
+
]);
|
|
205
|
+
} catch {}
|
|
206
|
+
sessionEntries.push(trimmed);
|
|
207
|
+
rl.prompt();
|
|
208
|
+
}
|
|
209
|
+
rl.close();
|
|
210
|
+
if (sessionEntries.length > 0) try {
|
|
211
|
+
const payload = {
|
|
212
|
+
entries: [...await loadHistory(HISTORY_FILE), ...sessionEntries].slice(-MAX_HISTORY),
|
|
213
|
+
version: 1
|
|
214
|
+
};
|
|
215
|
+
await mkdir(CONFIG_DIR, {
|
|
216
|
+
mode: 448,
|
|
217
|
+
recursive: true
|
|
218
|
+
});
|
|
219
|
+
await writeFile(HISTORY_FILE, `${JSON.stringify(payload, null, 2)}\n`, {
|
|
220
|
+
encoding: "utf-8",
|
|
221
|
+
mode: 384
|
|
222
|
+
});
|
|
223
|
+
} catch {}
|
|
224
|
+
};
|
|
225
|
+
//#endregion
|
|
226
|
+
//#region src/env.ts
|
|
227
|
+
const DEFAULT_ENV_FILES = [
|
|
228
|
+
".env.local",
|
|
229
|
+
".env",
|
|
230
|
+
"apps/manage-frontend/.env.local",
|
|
231
|
+
"apps/manage-api/.env"
|
|
232
|
+
];
|
|
233
|
+
let loaded = false;
|
|
234
|
+
const loadCliEnvironmentFiles = () => {
|
|
235
|
+
if (loaded) return;
|
|
236
|
+
for (const relativePath of DEFAULT_ENV_FILES) {
|
|
237
|
+
const absolutePath = resolve(process.cwd(), relativePath);
|
|
238
|
+
if (!existsSync(absolutePath)) continue;
|
|
239
|
+
config({
|
|
240
|
+
override: false,
|
|
241
|
+
path: absolutePath,
|
|
242
|
+
quiet: true
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
loaded = true;
|
|
246
|
+
};
|
|
247
|
+
const readEnvValue = (key) => {
|
|
248
|
+
const value = process.env[key];
|
|
249
|
+
if (typeof value !== "string") return;
|
|
250
|
+
const trimmed = value.trim();
|
|
251
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
252
|
+
};
|
|
22
253
|
//#endregion
|
|
23
254
|
//#region src/jwt.ts
|
|
24
255
|
const parseJwtBase64Url = (input) => {
|
|
25
256
|
const normalized = input.replaceAll("-", "+").replaceAll("_", "/");
|
|
26
257
|
const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=");
|
|
27
|
-
return Buffer.from(padded, "base64").toString("
|
|
258
|
+
return Buffer.from(padded, "base64").toString("utf-8");
|
|
28
259
|
};
|
|
29
260
|
const parseJwtClaims = (token) => {
|
|
30
261
|
const payloadPart = token.split(".").at(1);
|
|
@@ -115,7 +346,7 @@ const parseAuthFile = (payload) => {
|
|
|
115
346
|
};
|
|
116
347
|
const readStoredAuthSession = async () => {
|
|
117
348
|
try {
|
|
118
|
-
return parseAuthFile(await readFile(AUTH_FILE_PATH, "
|
|
349
|
+
return parseAuthFile(await readFile(AUTH_FILE_PATH, "utf-8")).session;
|
|
119
350
|
} catch (error) {
|
|
120
351
|
if (isRecord(error) && error.code === "ENOENT") return null;
|
|
121
352
|
if (error instanceof CliError) throw error;
|
|
@@ -132,7 +363,7 @@ const writeStoredAuthSession = async (session) => {
|
|
|
132
363
|
recursive: true
|
|
133
364
|
});
|
|
134
365
|
await writeFile(AUTH_FILE_PATH, `${JSON.stringify(payload, null, 2)}\n`, {
|
|
135
|
-
encoding: "
|
|
366
|
+
encoding: "utf-8",
|
|
136
367
|
mode: 384
|
|
137
368
|
});
|
|
138
369
|
};
|
|
@@ -140,34 +371,6 @@ const clearStoredAuthSession = async () => {
|
|
|
140
371
|
await rm(AUTH_FILE_PATH, { force: true });
|
|
141
372
|
};
|
|
142
373
|
//#endregion
|
|
143
|
-
//#region src/env.ts
|
|
144
|
-
const DEFAULT_ENV_FILES = [
|
|
145
|
-
".env.local",
|
|
146
|
-
".env",
|
|
147
|
-
"apps/manage-frontend/.env.local",
|
|
148
|
-
"apps/manage-api/.env"
|
|
149
|
-
];
|
|
150
|
-
let loaded = false;
|
|
151
|
-
const loadCliEnvironmentFiles = () => {
|
|
152
|
-
if (loaded) return;
|
|
153
|
-
for (const relativePath of DEFAULT_ENV_FILES) {
|
|
154
|
-
const absolutePath = resolve(process.cwd(), relativePath);
|
|
155
|
-
if (!existsSync(absolutePath)) continue;
|
|
156
|
-
config({
|
|
157
|
-
override: false,
|
|
158
|
-
path: absolutePath,
|
|
159
|
-
quiet: true
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
loaded = true;
|
|
163
|
-
};
|
|
164
|
-
const readEnvValue = (key) => {
|
|
165
|
-
const value = process.env[key];
|
|
166
|
-
if (typeof value !== "string") return;
|
|
167
|
-
const trimmed = value.trim();
|
|
168
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
169
|
-
};
|
|
170
|
-
//#endregion
|
|
171
374
|
//#region src/supabase.ts
|
|
172
375
|
const resolveSupabaseConfig = () => {
|
|
173
376
|
const url = readEnvValue("DONEBEAR_SUPABASE_URL") ?? "https://kdarvputxmlxqemisnbh.supabase.co";
|
|
@@ -199,19 +402,6 @@ const tokenResponseToStoredSession = (response) => {
|
|
|
199
402
|
} : null
|
|
200
403
|
};
|
|
201
404
|
};
|
|
202
|
-
const getSupabaseUser = async (supabaseUrl, publishableKey, accessToken) => {
|
|
203
|
-
const response = await fetch(`${supabaseUrl}/auth/v1/user`, { headers: {
|
|
204
|
-
apikey: publishableKey,
|
|
205
|
-
authorization: `Bearer ${accessToken}`
|
|
206
|
-
} });
|
|
207
|
-
if (!response.ok) throw new CliError(`Failed to load user (${response.status})`, EXIT_CODES.ERROR);
|
|
208
|
-
const data = await response.json();
|
|
209
|
-
if (typeof data.id !== "string") throw new CliError("No user returned for current token", EXIT_CODES.ERROR);
|
|
210
|
-
return {
|
|
211
|
-
email: typeof data.email === "string" ? data.email : null,
|
|
212
|
-
id: data.id
|
|
213
|
-
};
|
|
214
|
-
};
|
|
215
405
|
//#endregion
|
|
216
406
|
//#region src/auth-session.ts
|
|
217
407
|
const expiryFromClaims = (claims) => {
|
|
@@ -346,8 +536,8 @@ const requestNdjson = async (context, endpoint, options) => {
|
|
|
346
536
|
};
|
|
347
537
|
//#endregion
|
|
348
538
|
//#region src/date-codecs.ts
|
|
349
|
-
const ISO_DATE_ONLY_REGEX = /^(\d{4})-(\d{2})-(\d{2})
|
|
350
|
-
const ISO_INSTANT_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})
|
|
539
|
+
const ISO_DATE_ONLY_REGEX = /^(\d{4})-(\d{2})-(\d{2})$/u;
|
|
540
|
+
const ISO_INSTANT_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})$/u;
|
|
351
541
|
const pad = (value, length = 2) => String(value).padStart(length, "0");
|
|
352
542
|
const isFiniteEpoch = (value) => typeof value === "number" && Number.isFinite(value);
|
|
353
543
|
const isValidDateOnlyParts = (parts) => {
|
|
@@ -417,118 +607,6 @@ const parseCliDateOnlyToEpoch = (value, fieldLabel) => {
|
|
|
417
607
|
};
|
|
418
608
|
const dateOnlyEpochFromLocalDate = (now = /* @__PURE__ */ new Date()) => Date.UTC(now.getFullYear(), now.getMonth(), now.getDate());
|
|
419
609
|
//#endregion
|
|
420
|
-
//#region src/output.ts
|
|
421
|
-
const writeLine = (stream, message) => {
|
|
422
|
-
stream.write(`${message}\n`);
|
|
423
|
-
};
|
|
424
|
-
const colorize = (context, format, message) => {
|
|
425
|
-
if (!context.color) return message;
|
|
426
|
-
return styleText(format, message);
|
|
427
|
-
};
|
|
428
|
-
const writeJson = (value) => {
|
|
429
|
-
writeLine(process.stdout, JSON.stringify(value, null, 2));
|
|
430
|
-
};
|
|
431
|
-
const writeYaml = (value) => {
|
|
432
|
-
process.stdout.write(stringify(value));
|
|
433
|
-
};
|
|
434
|
-
const writeInfo = (context, message) => {
|
|
435
|
-
writeLine(process.stdout, colorize(context, "cyan", message));
|
|
436
|
-
};
|
|
437
|
-
const writeSuccess = (context, message) => {
|
|
438
|
-
writeLine(process.stdout, colorize(context, "green", message));
|
|
439
|
-
};
|
|
440
|
-
const writeWarning = (context, message) => {
|
|
441
|
-
writeLine(process.stderr, colorize(context, "yellow", message));
|
|
442
|
-
};
|
|
443
|
-
const writeError = (context, message) => {
|
|
444
|
-
writeLine(process.stderr, colorize(context, "red", message));
|
|
445
|
-
};
|
|
446
|
-
const writeDebug = (context, message) => {
|
|
447
|
-
if (!context.debug) return;
|
|
448
|
-
writeLine(process.stderr, colorize(context, "gray", `[debug] ${message}`));
|
|
449
|
-
};
|
|
450
|
-
const writeTotal = (context, count) => {
|
|
451
|
-
writeLine(process.stdout, colorize(context, "cyan", String(count)));
|
|
452
|
-
};
|
|
453
|
-
const escapeCsvField = (value) => {
|
|
454
|
-
if (value.includes(",") || value.includes("\"") || value.includes("\n")) return `"${value.replaceAll("\"", "\"\"")}"`;
|
|
455
|
-
return value;
|
|
456
|
-
};
|
|
457
|
-
const writeCsv = (headers, rows) => {
|
|
458
|
-
writeLine(process.stdout, headers.map(escapeCsvField).join(","));
|
|
459
|
-
for (const row of rows) writeLine(process.stdout, row.map(escapeCsvField).join(","));
|
|
460
|
-
};
|
|
461
|
-
const writeTsv = (headers, rows) => {
|
|
462
|
-
writeLine(process.stdout, headers.join(" "));
|
|
463
|
-
for (const row of rows) writeLine(process.stdout, row.join(" "));
|
|
464
|
-
};
|
|
465
|
-
/**
|
|
466
|
-
* Write structured data to stdout, applying --jq filter and --limit if set.
|
|
467
|
-
* Use this for all JSON/YAML/structured output from commands.
|
|
468
|
-
*/
|
|
469
|
-
const writeOutput = (context, data) => {
|
|
470
|
-
let result = data;
|
|
471
|
-
if (context.jq) try {
|
|
472
|
-
result = jmespath.search(result, context.jq);
|
|
473
|
-
} catch (error) {
|
|
474
|
-
throw new CliError(`Invalid --jq expression: ${error instanceof Error ? error.message : String(error)}`, EXIT_CODES.VALIDATION);
|
|
475
|
-
}
|
|
476
|
-
if (context.limit !== null && Array.isArray(result)) result = result.slice(0, context.limit);
|
|
477
|
-
switch (context.format) {
|
|
478
|
-
case "yaml":
|
|
479
|
-
writeYaml(result);
|
|
480
|
-
break;
|
|
481
|
-
case "json":
|
|
482
|
-
writeJson(result);
|
|
483
|
-
break;
|
|
484
|
-
default:
|
|
485
|
-
writeJson(result);
|
|
486
|
-
break;
|
|
487
|
-
}
|
|
488
|
-
if (context.copy) copyToClipboard(JSON.stringify(result, null, 2));
|
|
489
|
-
};
|
|
490
|
-
/**
|
|
491
|
-
* Handles --total, --format csv, --format tsv, --format yaml output.
|
|
492
|
-
* Returns true if the format was handled; caller renders text mode if false.
|
|
493
|
-
*/
|
|
494
|
-
const writeFormattedRows = (context, totalCount, headers, rows) => {
|
|
495
|
-
if (context.total) {
|
|
496
|
-
writeTotal(context, totalCount);
|
|
497
|
-
return true;
|
|
498
|
-
}
|
|
499
|
-
if (context.format === "yaml") {
|
|
500
|
-
const objects = rows.map((row) => Object.fromEntries(headers.map((h, i) => [h, row[i]])));
|
|
501
|
-
writeYaml(objects);
|
|
502
|
-
if (context.copy) copyToClipboard(stringify(objects));
|
|
503
|
-
return true;
|
|
504
|
-
}
|
|
505
|
-
if (context.format === "csv") {
|
|
506
|
-
writeCsv(headers, rows);
|
|
507
|
-
if (context.copy) copyToClipboard([headers.join(","), ...rows.map((r) => r.join(","))].join("\n"));
|
|
508
|
-
return true;
|
|
509
|
-
}
|
|
510
|
-
if (context.format === "tsv") {
|
|
511
|
-
writeTsv(headers, rows);
|
|
512
|
-
if (context.copy) copyToClipboard([headers.join(" "), ...rows.map((r) => r.join(" "))].join("\n"));
|
|
513
|
-
return true;
|
|
514
|
-
}
|
|
515
|
-
return false;
|
|
516
|
-
};
|
|
517
|
-
const copyToClipboard = (text) => {
|
|
518
|
-
try {
|
|
519
|
-
if (process.platform === "darwin") execSync("pbcopy", { input: text });
|
|
520
|
-
else if (process.platform === "win32") execSync("clip", { input: text });
|
|
521
|
-
else try {
|
|
522
|
-
execSync("xclip -selection clipboard", { input: text });
|
|
523
|
-
} catch {
|
|
524
|
-
execSync("xsel --clipboard --input", { input: text });
|
|
525
|
-
}
|
|
526
|
-
return true;
|
|
527
|
-
} catch {
|
|
528
|
-
return false;
|
|
529
|
-
}
|
|
530
|
-
};
|
|
531
|
-
//#endregion
|
|
532
610
|
//#region src/manage-api.ts
|
|
533
611
|
const TASK_FIELDS = `
|
|
534
612
|
id
|
|
@@ -681,17 +759,17 @@ const WORKSPACE_HISTORY_QUERY = `
|
|
|
681
759
|
}
|
|
682
760
|
`;
|
|
683
761
|
const API_KEY_CREATE_MUTATION = `
|
|
684
|
-
mutation ApiKeyCreate($label: String!) {
|
|
685
|
-
apiKeyCreate(label: $label) {
|
|
762
|
+
mutation ApiKeyCreate($label: String!, $scopes: [String!], $workspaceIds: [ID!], $expiresAt: DateTime) {
|
|
763
|
+
apiKeyCreate(label: $label, scopes: $scopes, workspaceIds: $workspaceIds, expiresAt: $expiresAt) {
|
|
686
764
|
success lastSyncId
|
|
687
|
-
apiKey { id label keyPrefix createdAt }
|
|
765
|
+
apiKey { id label keyPrefix scopes workspaceIds expiresAt createdAt }
|
|
688
766
|
token
|
|
689
767
|
}
|
|
690
768
|
}
|
|
691
769
|
`;
|
|
692
770
|
const API_KEYS_QUERY = `
|
|
693
771
|
query ApiKeys {
|
|
694
|
-
apiKeys { id label keyPrefix lastUsedAt revokedAt createdAt }
|
|
772
|
+
apiKeys { id label keyPrefix scopes workspaceIds expiresAt lastUsedAt revokedAt createdAt }
|
|
695
773
|
}
|
|
696
774
|
`;
|
|
697
775
|
const API_KEY_REVOKE_MUTATION = `
|
|
@@ -875,11 +953,14 @@ const toWorkspaceHistoryEntryFromGraphqlNode = (node) => ({
|
|
|
875
953
|
});
|
|
876
954
|
const toApiKeyRecordFromGraphqlNode = (node) => ({
|
|
877
955
|
createdAt: node.createdAt,
|
|
956
|
+
expiresAt: node.expiresAt,
|
|
878
957
|
id: node.id,
|
|
879
958
|
keyPrefix: node.keyPrefix,
|
|
880
959
|
label: node.label,
|
|
881
960
|
lastUsedAt: node.lastUsedAt,
|
|
882
|
-
revokedAt: node.revokedAt
|
|
961
|
+
revokedAt: node.revokedAt,
|
|
962
|
+
scopes: node.scopes,
|
|
963
|
+
workspaceIds: node.workspaceIds
|
|
883
964
|
});
|
|
884
965
|
const toChecklistItemRecord = (value) => {
|
|
885
966
|
if (typeof value !== "object" || value === null) return null;
|
|
@@ -898,11 +979,31 @@ const toChecklistItemRecord = (value) => {
|
|
|
898
979
|
workspaceId: row.workspaceId
|
|
899
980
|
};
|
|
900
981
|
};
|
|
901
|
-
const resolveApiBaseUrl = (context) => {
|
|
902
|
-
const fromContext = context.apiUrlOverride?.trim();
|
|
903
|
-
if (fromContext) return normalizeApiUrl(fromContext);
|
|
904
|
-
return normalizeApiUrl(readEnvValue("DONEBEAR_API_URL") ?? "https://api.donebear.com");
|
|
905
|
-
};
|
|
982
|
+
const resolveApiBaseUrl = (context) => {
|
|
983
|
+
const fromContext = context.apiUrlOverride?.trim();
|
|
984
|
+
if (fromContext) return normalizeApiUrl(fromContext);
|
|
985
|
+
return normalizeApiUrl(readEnvValue("DONEBEAR_API_URL") ?? "https://api.donebear.com");
|
|
986
|
+
};
|
|
987
|
+
const VIEWER_QUERY = `
|
|
988
|
+
query Viewer {
|
|
989
|
+
viewer {
|
|
990
|
+
id
|
|
991
|
+
email
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
`;
|
|
995
|
+
const loadViewer = async (context, options) => {
|
|
996
|
+
const data = await requestGraphql(context, `${options.baseUrl}/graphql`, {
|
|
997
|
+
query: VIEWER_QUERY,
|
|
998
|
+
token: options.token,
|
|
999
|
+
variables: {}
|
|
1000
|
+
});
|
|
1001
|
+
if (!data.viewer) throw new CliError("Could not resolve current user. The token may be invalid or revoked.", EXIT_CODES.AUTH_REQUIRED);
|
|
1002
|
+
return {
|
|
1003
|
+
email: data.viewer.email ?? null,
|
|
1004
|
+
id: data.viewer.id
|
|
1005
|
+
};
|
|
1006
|
+
};
|
|
906
1007
|
const listWorkspaces = async (context, options) => {
|
|
907
1008
|
return (await requestGraphql(context, `${options.baseUrl}/graphql`, {
|
|
908
1009
|
query: MY_WORKSPACES_QUERY,
|
|
@@ -1089,10 +1190,15 @@ const listWorkspaceHistory = async (context, options) => {
|
|
|
1089
1190
|
})).workspaceHistory.map(toWorkspaceHistoryEntryFromGraphqlNode);
|
|
1090
1191
|
};
|
|
1091
1192
|
const createApiKey = async (context, options) => {
|
|
1092
|
-
const
|
|
1193
|
+
const endpoint = `${options.baseUrl}/graphql`;
|
|
1194
|
+
const variables = { label: options.label };
|
|
1195
|
+
if (options.scopes !== void 0) variables.scopes = options.scopes;
|
|
1196
|
+
if (options.workspaceIds !== void 0) variables.workspaceIds = options.workspaceIds;
|
|
1197
|
+
if (options.expiresAt !== void 0) variables.expiresAt = options.expiresAt;
|
|
1198
|
+
const data = await requestGraphql(context, endpoint, {
|
|
1093
1199
|
query: API_KEY_CREATE_MUTATION,
|
|
1094
1200
|
token: options.token,
|
|
1095
|
-
variables
|
|
1201
|
+
variables
|
|
1096
1202
|
});
|
|
1097
1203
|
return {
|
|
1098
1204
|
...toApiKeyRecordFromGraphqlNode(data.apiKeyCreate.apiKey),
|
|
@@ -1140,10 +1246,13 @@ const resolveCommandContext = async (context) => {
|
|
|
1140
1246
|
token
|
|
1141
1247
|
};
|
|
1142
1248
|
};
|
|
1143
|
-
const requireUserId = (
|
|
1144
|
-
const
|
|
1145
|
-
if (
|
|
1146
|
-
return
|
|
1249
|
+
const requireUserId = async (context, commandContext) => {
|
|
1250
|
+
const claimUserId = commandContext.resolvedToken.user?.id ?? null;
|
|
1251
|
+
if (typeof claimUserId === "string" && claimUserId.length > 0) return claimUserId;
|
|
1252
|
+
return (await loadViewer(context, {
|
|
1253
|
+
baseUrl: commandContext.apiBaseUrl,
|
|
1254
|
+
token: commandContext.token
|
|
1255
|
+
})).id;
|
|
1147
1256
|
};
|
|
1148
1257
|
//#endregion
|
|
1149
1258
|
//#region src/options.ts
|
|
@@ -1238,20 +1347,68 @@ const runWithErrorHandling = async (context, action) => {
|
|
|
1238
1347
|
};
|
|
1239
1348
|
//#endregion
|
|
1240
1349
|
//#region src/commands/api-key.ts
|
|
1241
|
-
const
|
|
1350
|
+
const PERMISSION_SCOPES = {
|
|
1351
|
+
"full-access": [
|
|
1352
|
+
"read",
|
|
1353
|
+
"write",
|
|
1354
|
+
"admin"
|
|
1355
|
+
],
|
|
1356
|
+
"read-only": ["read"]
|
|
1357
|
+
};
|
|
1358
|
+
const parseDuration = (duration) => {
|
|
1359
|
+
const match = duration.match(/^(\d+)(d|y)$/u);
|
|
1360
|
+
if (!match) throw new CliError(`Invalid duration "${duration}". Use a format like "30d", "90d", or "1y".`, EXIT_CODES.VALIDATION);
|
|
1361
|
+
const [, rawAmount, unit] = match;
|
|
1362
|
+
const amount = Number(rawAmount);
|
|
1363
|
+
const now = /* @__PURE__ */ new Date();
|
|
1364
|
+
if (unit === "d") now.setDate(now.getDate() + amount);
|
|
1365
|
+
else now.setDate(now.getDate() + amount * 365);
|
|
1366
|
+
return now;
|
|
1367
|
+
};
|
|
1368
|
+
const runCreate = async (context, label, options) => {
|
|
1242
1369
|
const { token } = await requireAuthToken(context);
|
|
1370
|
+
const baseUrl = resolveApiBaseUrl(context);
|
|
1371
|
+
let scopes;
|
|
1372
|
+
if (options.permissions !== void 0) {
|
|
1373
|
+
const level = options.permissions;
|
|
1374
|
+
if (!PERMISSION_SCOPES[level]) throw new CliError(`Invalid permissions "${options.permissions}". Use "full-access" or "read-only".`, EXIT_CODES.VALIDATION);
|
|
1375
|
+
scopes = PERMISSION_SCOPES[level];
|
|
1376
|
+
}
|
|
1377
|
+
let workspaceIds;
|
|
1378
|
+
if (options.workspaces !== void 0) {
|
|
1379
|
+
const slugs = options.workspaces.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1380
|
+
if (slugs.length === 0) throw new CliError("No workspace slugs provided for --workspaces.", EXIT_CODES.VALIDATION);
|
|
1381
|
+
const allWorkspaces = await listWorkspaces(context, {
|
|
1382
|
+
baseUrl,
|
|
1383
|
+
token
|
|
1384
|
+
});
|
|
1385
|
+
workspaceIds = [];
|
|
1386
|
+
for (const slug of slugs) {
|
|
1387
|
+
const match = allWorkspaces.find((ws) => ws.urlKey === slug || ws.id === slug || ws.name.toLowerCase() === slug.toLowerCase());
|
|
1388
|
+
if (!match) throw new CliError(`Workspace "${slug}" not found. Run \`donebear workspace list\` to see available workspaces.`, EXIT_CODES.VALIDATION);
|
|
1389
|
+
workspaceIds.push(match.id);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
let expiresAt;
|
|
1393
|
+
if (options.expires !== void 0) expiresAt = parseDuration(options.expires).toISOString();
|
|
1243
1394
|
const key = await createApiKey(context, {
|
|
1244
|
-
baseUrl
|
|
1395
|
+
baseUrl,
|
|
1396
|
+
expiresAt,
|
|
1245
1397
|
label,
|
|
1246
|
-
|
|
1398
|
+
scopes,
|
|
1399
|
+
token,
|
|
1400
|
+
workspaceIds
|
|
1247
1401
|
});
|
|
1248
1402
|
if (context.json) {
|
|
1249
1403
|
writeOutput(context, {
|
|
1250
1404
|
createdAt: key.createdAt,
|
|
1405
|
+
expiresAt: key.expiresAt,
|
|
1251
1406
|
id: key.id,
|
|
1252
1407
|
keyPrefix: key.keyPrefix,
|
|
1253
1408
|
label: key.label,
|
|
1254
|
-
|
|
1409
|
+
scopes: key.scopes,
|
|
1410
|
+
token: key.token,
|
|
1411
|
+
workspaceIds: key.workspaceIds
|
|
1255
1412
|
});
|
|
1256
1413
|
return;
|
|
1257
1414
|
}
|
|
@@ -1259,6 +1416,10 @@ const runCreate = async (context, label) => {
|
|
|
1259
1416
|
writeInfo(context, "");
|
|
1260
1417
|
writeInfo(context, ` ${key.token}`);
|
|
1261
1418
|
writeInfo(context, "");
|
|
1419
|
+
writeInfo(context, ` Scopes: ${key.scopes.join(", ")}`);
|
|
1420
|
+
if (key.workspaceIds && key.workspaceIds.length > 0) writeInfo(context, ` Restricted to workspaces: ${key.workspaceIds.join(", ")}`);
|
|
1421
|
+
if (key.expiresAt) writeInfo(context, ` Expires: ${new Date(key.expiresAt).toLocaleDateString()}`);
|
|
1422
|
+
writeInfo(context, "");
|
|
1262
1423
|
writeWarning(context, "Save this key now — it will not be shown again.");
|
|
1263
1424
|
};
|
|
1264
1425
|
const runList = async (context) => {
|
|
@@ -1278,7 +1439,10 @@ const runList = async (context) => {
|
|
|
1278
1439
|
for (const key of keys) {
|
|
1279
1440
|
const status = key.revokedAt ? " (revoked)" : "";
|
|
1280
1441
|
const lastUsed = key.lastUsedAt ? `Last used: ${new Date(key.lastUsedAt).toLocaleDateString()}` : "Never used";
|
|
1281
|
-
|
|
1442
|
+
const scopeStr = `[${key.scopes.join(",")}]`;
|
|
1443
|
+
const wsStr = key.workspaceIds && key.workspaceIds.length > 0 ? ` ws:${key.workspaceIds.length}` : "";
|
|
1444
|
+
const expStr = key.expiresAt ? ` exp:${new Date(key.expiresAt).toLocaleDateString()}` : "";
|
|
1445
|
+
writeInfo(context, `${key.keyPrefix}... ${key.label} ${scopeStr}${wsStr}${expStr} ${lastUsed}${status} [${key.id}]`);
|
|
1282
1446
|
}
|
|
1283
1447
|
};
|
|
1284
1448
|
const runRevoke = async (context, keyId) => {
|
|
@@ -1301,6 +1465,8 @@ const registerApiKeyCommands = (program) => {
|
|
|
1301
1465
|
const apiKey = program.command("api-key").alias("key").description("Manage personal API keys").addHelpText("after", `
|
|
1302
1466
|
Examples:
|
|
1303
1467
|
donebear api-key create "Raycast"
|
|
1468
|
+
donebear api-key create "CI" --permissions read-only --expires 90d
|
|
1469
|
+
donebear api-key create "Scoped" --workspaces my-org,side-project --expires 1y
|
|
1304
1470
|
donebear api-key list
|
|
1305
1471
|
donebear api-key revoke <key-id>
|
|
1306
1472
|
`).action(async (_options, command) => {
|
|
@@ -1309,10 +1475,10 @@ Examples:
|
|
|
1309
1475
|
await runList(context);
|
|
1310
1476
|
});
|
|
1311
1477
|
});
|
|
1312
|
-
apiKey.command("create <label>").description("Create a new personal API key").action(async (label,
|
|
1478
|
+
apiKey.command("create <label>").description("Create a new personal API key").option("--permissions <value>", "Permission level: \"full-access\" or \"read-only\"", "full-access").option("--workspaces <slugs>", "Comma-separated workspace slugs to restrict the key to").option("--expires <duration>", "Expiration duration, e.g. \"30d\", \"90d\", \"1y\"").action(async (label, options, command) => {
|
|
1313
1479
|
const context = contextFromCommand(command);
|
|
1314
1480
|
await runWithErrorHandling(context, async () => {
|
|
1315
|
-
await runCreate(context, label);
|
|
1481
|
+
await runCreate(context, label, options);
|
|
1316
1482
|
});
|
|
1317
1483
|
});
|
|
1318
1484
|
apiKey.command("list").description("List all personal API keys").action(async (_options, command) => {
|
|
@@ -1669,7 +1835,7 @@ const resolveTaskView = (task) => {
|
|
|
1669
1835
|
const formatEpochDate = (epochMs) => formatInstantEpoch(epochMs);
|
|
1670
1836
|
//#endregion
|
|
1671
1837
|
//#region src/command-helpers.ts
|
|
1672
|
-
const SHORT_ID_SUFFIX_PATTERN = /-([a-f0-9]{8})
|
|
1838
|
+
const SHORT_ID_SUFFIX_PATTERN = /-([a-f0-9]{8})$/u;
|
|
1673
1839
|
/**
|
|
1674
1840
|
* Find a record by reference: exact ID, key, ID prefix, slug shortId, or name.
|
|
1675
1841
|
* Works for any entity with id, key, and name fields.
|
|
@@ -1723,7 +1889,7 @@ const taskListLines = (tasks) => tasks.map((task) => {
|
|
|
1723
1889
|
});
|
|
1724
1890
|
//#endregion
|
|
1725
1891
|
//#region src/workspace-context.ts
|
|
1726
|
-
const WORKSPACE_URL_KEY_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)
|
|
1892
|
+
const WORKSPACE_URL_KEY_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
|
|
1727
1893
|
const fetchWorkspaces = async (context, options) => {
|
|
1728
1894
|
const workspaces = await listWorkspaces(context, {
|
|
1729
1895
|
baseUrl: options.apiBaseUrl,
|
|
@@ -1782,7 +1948,7 @@ const byReference = (workspaces, reference) => {
|
|
|
1782
1948
|
return null;
|
|
1783
1949
|
};
|
|
1784
1950
|
const toWorkspaceUrlKey = (name) => {
|
|
1785
|
-
return name.trim().toLowerCase().replaceAll(/[^a-z0-9]+/
|
|
1951
|
+
return name.trim().toLowerCase().replaceAll(/[^a-z0-9]+/gu, "-").replaceAll(/^-+|-+$/gu, "").replaceAll(/-{2,}/gu, "-").slice(0, 50);
|
|
1786
1952
|
};
|
|
1787
1953
|
const isValidWorkspaceUrlKey = (value) => WORKSPACE_URL_KEY_REGEX.test(value) && value.length >= 3;
|
|
1788
1954
|
const resolveWorkspace = async (context, options) => {
|
|
@@ -1924,11 +2090,11 @@ Examples:
|
|
|
1924
2090
|
});
|
|
1925
2091
|
});
|
|
1926
2092
|
};
|
|
2093
|
+
const packageManifest = createRequire(import.meta.url)("../package.json");
|
|
1927
2094
|
//#endregion
|
|
1928
2095
|
//#region src/commands/doctor.ts
|
|
1929
|
-
const packageManifest$1 = createRequire(import.meta.url)("../package.json");
|
|
1930
2096
|
const checkCliVersion = async () => {
|
|
1931
|
-
const current = packageManifest
|
|
2097
|
+
const current = packageManifest.version;
|
|
1932
2098
|
try {
|
|
1933
2099
|
const response = await fetch("https://registry.npmjs.org/donebear/latest", { signal: AbortSignal.timeout(5e3) });
|
|
1934
2100
|
if (!response.ok) return {
|
|
@@ -1993,7 +2159,7 @@ const checkAuth = async (context) => {
|
|
|
1993
2159
|
};
|
|
1994
2160
|
const checkWorkspace = async () => {
|
|
1995
2161
|
try {
|
|
1996
|
-
const { ensureLocalContext } = await import("./context-store-
|
|
2162
|
+
const { ensureLocalContext } = await import("./context-store-ClAx1T2N.mjs");
|
|
1997
2163
|
const localContext = await ensureLocalContext();
|
|
1998
2164
|
if (!localContext.workspaceId) return {
|
|
1999
2165
|
message: "No workspace selected (run `donebear workspace use`)",
|
|
@@ -2208,6 +2374,7 @@ const normalizeThingsLabelLookupKey = (value) => value.trim().toLocaleLowerCase(
|
|
|
2208
2374
|
//#region src/import/model-id.ts
|
|
2209
2375
|
const IMPORT_ID_NAMESPACE = "b1f9f0ac-9f76-54ed-8eb0-7d6d3a0a0a31";
|
|
2210
2376
|
const SYNC_COMPOSITE_ID_NAMESPACE = "92a73695-d772-4b43-9fb4-d79f5fbef300";
|
|
2377
|
+
const IDEMPOTENCY_ID_NAMESPACE = "4c8af5e2-1d36-5a0b-9c47-2f81b3d6e955";
|
|
2211
2378
|
const uuidToBytes = (uuid) => {
|
|
2212
2379
|
const normalized = uuid.replaceAll("-", "");
|
|
2213
2380
|
if (normalized.length !== 32) throw new Error(`Invalid UUID namespace: ${uuid}`);
|
|
@@ -2225,7 +2392,7 @@ const bytesToUuid = (bytes) => {
|
|
|
2225
2392
|
};
|
|
2226
2393
|
const createUuidV5 = (name, namespace) => {
|
|
2227
2394
|
const namespaceBytes = uuidToBytes(namespace);
|
|
2228
|
-
const hash = createHash("sha1").update(namespaceBytes).update(Buffer.from(name, "
|
|
2395
|
+
const hash = createHash("sha1").update(namespaceBytes).update(Buffer.from(name, "utf-8")).digest();
|
|
2229
2396
|
const bytes = Uint8Array.from(hash.subarray(0, 16));
|
|
2230
2397
|
const versionByte = bytes.at(6);
|
|
2231
2398
|
const variantByte = bytes.at(8);
|
|
@@ -2236,6 +2403,7 @@ const createUuidV5 = (name, namespace) => {
|
|
|
2236
2403
|
};
|
|
2237
2404
|
const createCompositeSyncId = (model, parts) => createUuidV5(`${model}:${JSON.stringify(parts)}`, SYNC_COMPOSITE_ID_NAMESPACE);
|
|
2238
2405
|
const createDeterministicImportId = (params) => createUuidV5(`things:${params.workspaceId}:${params.modelName}:${params.sourceId}`, IMPORT_ID_NAMESPACE);
|
|
2406
|
+
const createIdempotentMutationId = (params) => createUuidV5(`idem:${params.workspaceId}:${params.modelName}:${params.part}:${params.key}`, IDEMPOTENCY_ID_NAMESPACE);
|
|
2239
2407
|
const createTaskLabelId = (params) => createCompositeSyncId("TaskLabel", [
|
|
2240
2408
|
params.workspaceId,
|
|
2241
2409
|
params.taskId,
|
|
@@ -2665,7 +2833,6 @@ var import_rrule = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
2665
2833
|
};
|
|
2666
2834
|
return __assign.apply(this, arguments);
|
|
2667
2835
|
};
|
|
2668
|
-
Object.create;
|
|
2669
2836
|
function __spreadArray(to, from, pack) {
|
|
2670
2837
|
if (pack || arguments.length === 2) {
|
|
2671
2838
|
for (var i = 0, l = from.length, ar; i < l; i++) if (ar || !(i in from)) {
|
|
@@ -2678,7 +2845,6 @@ var import_rrule = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
2678
2845
|
function __await(v) {
|
|
2679
2846
|
return this instanceof __await ? (this.v = v, this) : new __await(v);
|
|
2680
2847
|
}
|
|
2681
|
-
Object.create;
|
|
2682
2848
|
const callbackiterresult = function(_super) {
|
|
2683
2849
|
__extends(CallbackIterResult, _super);
|
|
2684
2850
|
function CallbackIterResult(method, args, iterator) {
|
|
@@ -3515,7 +3681,8 @@ var import_rrule = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
3515
3681
|
this.month += months;
|
|
3516
3682
|
if (this.month > 12) {
|
|
3517
3683
|
var yearDiv = Math.floor(this.month / 12);
|
|
3518
|
-
|
|
3684
|
+
var monthMod = pymod(this.month, 12);
|
|
3685
|
+
this.month = monthMod;
|
|
3519
3686
|
this.year += yearDiv;
|
|
3520
3687
|
if (this.month === 0) {
|
|
3521
3688
|
this.month = 12;
|
|
@@ -4134,8 +4301,7 @@ var import_rrule = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
4134
4301
|
var month = Math.floor((h + l - 7 * m + 114) / 31);
|
|
4135
4302
|
var day = (h + l - 7 * m + 114) % 31 + 1;
|
|
4136
4303
|
var date = Date.UTC(y, month - 1, day + offset);
|
|
4137
|
-
|
|
4138
|
-
return [Math.ceil((date - yearStart) / (1e3 * 60 * 60 * 24))];
|
|
4304
|
+
return [Math.ceil((date - Date.UTC(y, 0, 1)) / (1e3 * 60 * 60 * 24))];
|
|
4139
4305
|
}
|
|
4140
4306
|
const iterinfo = function() {
|
|
4141
4307
|
function Iterinfo(options) {
|
|
@@ -4461,7 +4627,8 @@ var import_rrule = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
4461
4627
|
if (noCache === void 0) noCache = false;
|
|
4462
4628
|
this._cache = noCache ? null : new Cache();
|
|
4463
4629
|
this.origOptions = initializeOptions(options);
|
|
4464
|
-
|
|
4630
|
+
var parsedOptions = parseOptions(options).parsedOptions;
|
|
4631
|
+
this.options = parsedOptions;
|
|
4465
4632
|
}
|
|
4466
4633
|
RRule.parseText = function(text, language) {
|
|
4467
4634
|
return parseText(text, language);
|
|
@@ -5152,17 +5319,17 @@ const THINGS_WEEKDAY_TO_RRULE = [
|
|
|
5152
5319
|
"SA"
|
|
5153
5320
|
];
|
|
5154
5321
|
const parseNumericPlistField = (xml, key) => {
|
|
5155
|
-
const match = new RegExp(`<key>${key}</key>\\s*<(integer|real)>([^<]+)</\\1
|
|
5322
|
+
const match = new RegExp(`<key>${key}</key>\\s*<(integer|real)>([^<]+)</\\1>`, "u").exec(xml);
|
|
5156
5323
|
if (!match?.[2]) return null;
|
|
5157
5324
|
const parsed = Number(match[2]);
|
|
5158
5325
|
return Number.isFinite(parsed) ? parsed : null;
|
|
5159
5326
|
};
|
|
5160
|
-
const PLIST_OFFSETS_ARRAY_PATTERN = /<key>of<\/key>\s*<array>([\s\S]*?)<\/array
|
|
5327
|
+
const PLIST_OFFSETS_ARRAY_PATTERN = /<key>of<\/key>\s*<array>([\s\S]*?)<\/array>/u;
|
|
5161
5328
|
const parseOffsets = (xml) => {
|
|
5162
5329
|
const arrayMatch = PLIST_OFFSETS_ARRAY_PATTERN.exec(xml);
|
|
5163
5330
|
if (!arrayMatch?.[1]) return [];
|
|
5164
5331
|
const offsets = [];
|
|
5165
|
-
for (const dictMatch of arrayMatch[1].matchAll(/<dict>([\s\S]*?)<\/dict>/
|
|
5332
|
+
for (const dictMatch of arrayMatch[1].matchAll(/<dict>([\s\S]*?)<\/dict>/gu)) {
|
|
5166
5333
|
const [, fragment] = dictMatch;
|
|
5167
5334
|
if (!fragment) continue;
|
|
5168
5335
|
const weekday = parseNumericPlistField(fragment, "wd");
|
|
@@ -5290,11 +5457,11 @@ const normalizeTitle = (value, fallback) => {
|
|
|
5290
5457
|
const trimmed = value?.trim();
|
|
5291
5458
|
return trimmed && trimmed.length > 0 ? trimmed : fallback;
|
|
5292
5459
|
};
|
|
5293
|
-
const NON_ALPHANUMERIC_PATTERN = /[^A-Z0-9]+/
|
|
5294
|
-
const WHITESPACE_SPLIT_PATTERN = /\s
|
|
5295
|
-
const NON_ALPHANUMERIC_STRICT_PATTERN = /[^A-Z0-9]/
|
|
5460
|
+
const NON_ALPHANUMERIC_PATTERN = /[^A-Z0-9]+/gu;
|
|
5461
|
+
const WHITESPACE_SPLIT_PATTERN = /\s+/u;
|
|
5462
|
+
const NON_ALPHANUMERIC_STRICT_PATTERN = /[^A-Z0-9]/gu;
|
|
5296
5463
|
const READABLE_TEXT_PATTERN = /^[\t\n\r\u0020-\u007E\u00A0-\uFFFF]*$/u;
|
|
5297
|
-
const EMAIL_SPLIT_PATTERN = /[,\n;]
|
|
5464
|
+
const EMAIL_SPLIT_PATTERN = /[,\n;]/u;
|
|
5298
5465
|
const normalizeKeyBase = (value, fallback) => {
|
|
5299
5466
|
const initials = value.toUpperCase().replace(NON_ALPHANUMERIC_PATTERN, " ").trim().split(WHITESPACE_SPLIT_PATTERN).filter((word) => word.length > 0).map((word) => word[0] ?? "").join("");
|
|
5300
5467
|
const compact = value.toUpperCase().replace(NON_ALPHANUMERIC_STRICT_PATTERN, "");
|
|
@@ -5312,7 +5479,7 @@ const buildUniqueKeys = (items, options) => {
|
|
|
5312
5479
|
const base = normalizeKeyBase(normalizeTitle(item.title, options.fallback), options.fallback).slice(0, options.maxLength);
|
|
5313
5480
|
let candidate = base || options.fallback;
|
|
5314
5481
|
if (used.has(candidate)) {
|
|
5315
|
-
const suffixBase = item.uuid.replaceAll(/[^a-zA-Z0-9]/
|
|
5482
|
+
const suffixBase = item.uuid.replaceAll(/[^a-zA-Z0-9]/gu, "").slice(-4);
|
|
5316
5483
|
const suffix = suffixBase.length > 0 ? suffixBase.toUpperCase() : "X";
|
|
5317
5484
|
const headLength = Math.max(1, options.maxLength - suffix.length - 1);
|
|
5318
5485
|
candidate = `${candidate.slice(0, headLength)}-${suffix}`;
|
|
@@ -5384,7 +5551,7 @@ const pickCompletedAt = (task) => {
|
|
|
5384
5551
|
};
|
|
5385
5552
|
const sortValue = (primary, fallback) => typeof primary === "number" ? primary : fallback;
|
|
5386
5553
|
const sanitizeBuffer = (buffer) => {
|
|
5387
|
-
const asText = buffer.toString("
|
|
5554
|
+
const asText = buffer.toString("utf-8");
|
|
5388
5555
|
if (!asText.includes("�") && READABLE_TEXT_PATTERN.test(asText)) {
|
|
5389
5556
|
const trimmed = asText.trim();
|
|
5390
5557
|
if (trimmed.length > 0) return trimmed;
|
|
@@ -5998,7 +6165,7 @@ const THINGS_CONTAINER_PATH = join(homedir(), "Library/Group Containers/JLMPQHK8
|
|
|
5998
6165
|
const THINGS_BUNDLE_NAME = "Things Database.thingsdatabase";
|
|
5999
6166
|
const THINGS_SQLITE_FILE = "main.sqlite";
|
|
6000
6167
|
const LEGACY_THINGS_SQLITE_FILE = "Things.sqlite3";
|
|
6001
|
-
const URL_SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*:/
|
|
6168
|
+
const URL_SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*:/iu;
|
|
6002
6169
|
const isUrlLike = (value) => URL_SCHEME_PATTERN.test(value);
|
|
6003
6170
|
const resolveInputPath = (databaseUrl) => {
|
|
6004
6171
|
if (!isUrlLike(databaseUrl)) return resolve(databaseUrl);
|
|
@@ -6061,7 +6228,7 @@ const toStringValue = (value) => {
|
|
|
6061
6228
|
return trimmed.length > 0 ? trimmed : null;
|
|
6062
6229
|
}
|
|
6063
6230
|
if (Buffer.isBuffer(value)) {
|
|
6064
|
-
const text = value.toString("
|
|
6231
|
+
const text = value.toString("utf-8").trim();
|
|
6065
6232
|
return text.length > 0 ? text : null;
|
|
6066
6233
|
}
|
|
6067
6234
|
return null;
|
|
@@ -6407,7 +6574,7 @@ const parseMetadataMode = (value) => {
|
|
|
6407
6574
|
};
|
|
6408
6575
|
const runThingsImportCommand = async (context, options, workspaceOverride) => {
|
|
6409
6576
|
const commandContext = await resolveCommandContext(context);
|
|
6410
|
-
const creatorId = requireUserId(commandContext
|
|
6577
|
+
const creatorId = await requireUserId(context, commandContext);
|
|
6411
6578
|
const metadataMode = parseMetadataMode(options.metadataMode);
|
|
6412
6579
|
const { workspace, autoSelected } = await resolveWorkspace(context, {
|
|
6413
6580
|
apiBaseUrl: commandContext.apiBaseUrl,
|
|
@@ -6453,97 +6620,6 @@ Notes:
|
|
|
6453
6620
|
});
|
|
6454
6621
|
};
|
|
6455
6622
|
//#endregion
|
|
6456
|
-
//#region src/commands/interactive.ts
|
|
6457
|
-
const HISTORY_FILE = join(CONFIG_DIR, "history.json");
|
|
6458
|
-
const MAX_HISTORY = 200;
|
|
6459
|
-
const isHistoryFile = (value) => {
|
|
6460
|
-
if (typeof value !== "object" || value === null) return false;
|
|
6461
|
-
const record = value;
|
|
6462
|
-
return record.version === 1 && Array.isArray(record.entries) && record.entries.every((e) => typeof e === "string");
|
|
6463
|
-
};
|
|
6464
|
-
const loadHistory = async (historyFile) => {
|
|
6465
|
-
try {
|
|
6466
|
-
const raw = await readFile(historyFile, "utf8");
|
|
6467
|
-
const parsed = JSON.parse(raw);
|
|
6468
|
-
if (isHistoryFile(parsed)) return parsed.entries;
|
|
6469
|
-
return [];
|
|
6470
|
-
} catch {
|
|
6471
|
-
return [];
|
|
6472
|
-
}
|
|
6473
|
-
};
|
|
6474
|
-
const tokenize = (input) => {
|
|
6475
|
-
const tokens = [];
|
|
6476
|
-
let current = "";
|
|
6477
|
-
let inQuote = false;
|
|
6478
|
-
let quoteChar = "";
|
|
6479
|
-
for (const char of input) if (inQuote) if (char === quoteChar) inQuote = false;
|
|
6480
|
-
else current += char;
|
|
6481
|
-
else if (char === "\"" || char === "'") {
|
|
6482
|
-
inQuote = true;
|
|
6483
|
-
quoteChar = char;
|
|
6484
|
-
} else if (char === " ") {
|
|
6485
|
-
if (current.length > 0) {
|
|
6486
|
-
tokens.push(current);
|
|
6487
|
-
current = "";
|
|
6488
|
-
}
|
|
6489
|
-
} else current += char;
|
|
6490
|
-
if (current.length > 0) tokens.push(current);
|
|
6491
|
-
return tokens;
|
|
6492
|
-
};
|
|
6493
|
-
const buildCompleter = (program) => (line) => {
|
|
6494
|
-
const allCommands = program.commands.flatMap((cmd) => [cmd.name(), ...cmd.aliases()]);
|
|
6495
|
-
const hits = allCommands.filter((c) => c.startsWith(line));
|
|
6496
|
-
return [hits.length > 0 ? hits : allCommands, line];
|
|
6497
|
-
};
|
|
6498
|
-
const runInteractiveMode = async (context, program) => {
|
|
6499
|
-
const history = await loadHistory(HISTORY_FILE);
|
|
6500
|
-
const rl = createInterface({
|
|
6501
|
-
completer: buildCompleter(program),
|
|
6502
|
-
historySize: MAX_HISTORY,
|
|
6503
|
-
input: process.stdin,
|
|
6504
|
-
output: process.stdout,
|
|
6505
|
-
prompt: "donebear> "
|
|
6506
|
-
});
|
|
6507
|
-
for (const entry of [...history].toReversed()) rl.history.push(entry);
|
|
6508
|
-
writeSuccess(context, "Done Bear interactive mode. Type \"exit\" or Ctrl+D to quit.");
|
|
6509
|
-
writeInfo(context, "Commands: task, workspace, project, label, team, today, search, auth, whoami");
|
|
6510
|
-
rl.prompt();
|
|
6511
|
-
const sessionEntries = [];
|
|
6512
|
-
for await (const line of rl) {
|
|
6513
|
-
const trimmed = line.trim();
|
|
6514
|
-
if (!trimmed) {
|
|
6515
|
-
rl.prompt();
|
|
6516
|
-
continue;
|
|
6517
|
-
}
|
|
6518
|
-
if (trimmed === "exit" || trimmed === "quit" || trimmed === ":q") break;
|
|
6519
|
-
const tokens = tokenize(trimmed);
|
|
6520
|
-
try {
|
|
6521
|
-
await program.parseAsync([
|
|
6522
|
-
"node",
|
|
6523
|
-
"donebear",
|
|
6524
|
-
...tokens
|
|
6525
|
-
]);
|
|
6526
|
-
} catch {}
|
|
6527
|
-
sessionEntries.push(trimmed);
|
|
6528
|
-
rl.prompt();
|
|
6529
|
-
}
|
|
6530
|
-
rl.close();
|
|
6531
|
-
if (sessionEntries.length > 0) try {
|
|
6532
|
-
const payload = {
|
|
6533
|
-
entries: [...await loadHistory(HISTORY_FILE), ...sessionEntries].slice(-MAX_HISTORY),
|
|
6534
|
-
version: 1
|
|
6535
|
-
};
|
|
6536
|
-
await mkdir(CONFIG_DIR, {
|
|
6537
|
-
mode: 448,
|
|
6538
|
-
recursive: true
|
|
6539
|
-
});
|
|
6540
|
-
await writeFile(HISTORY_FILE, `${JSON.stringify(payload, null, 2)}\n`, {
|
|
6541
|
-
encoding: "utf8",
|
|
6542
|
-
mode: 384
|
|
6543
|
-
});
|
|
6544
|
-
} catch {}
|
|
6545
|
-
};
|
|
6546
|
-
//#endregion
|
|
6547
6623
|
//#region src/commands/label.ts
|
|
6548
6624
|
const sortLabels = (labels, sort) => {
|
|
6549
6625
|
const sorted = [...labels];
|
|
@@ -6635,7 +6711,7 @@ const runLabelShow = async (context, labelRef, options) => {
|
|
|
6635
6711
|
writeInfo(context, `createdAt: ${formatEpochDate(label.createdAt) ?? "(none)"}`);
|
|
6636
6712
|
writeInfo(context, `updatedAt: ${formatEpochDate(label.updatedAt) ?? "(none)"}`);
|
|
6637
6713
|
};
|
|
6638
|
-
const UUID_REGEX$1 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
|
|
6714
|
+
const UUID_REGEX$1 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
|
|
6639
6715
|
const resolveTaskForLabel = async (context, options) => {
|
|
6640
6716
|
const taskRef = options.taskRef.trim();
|
|
6641
6717
|
if (!taskRef) throw new Error("Task id is required.");
|
|
@@ -6882,7 +6958,7 @@ const getProjectStatus = (project) => {
|
|
|
6882
6958
|
};
|
|
6883
6959
|
const parseTargetDate = (value) => parseCliDateOnlyToEpoch(value, "target date");
|
|
6884
6960
|
const generateProjectSlug = (name, id) => {
|
|
6885
|
-
const slug = name.toLowerCase().trim().replaceAll(/[^a-z0-9]+/
|
|
6961
|
+
const slug = name.toLowerCase().trim().replaceAll(/[^a-z0-9]+/gu, "-").replaceAll(/^-|-$/gu, "").slice(0, 39);
|
|
6886
6962
|
const shortId = id.replaceAll("-", "").slice(0, 8);
|
|
6887
6963
|
return slug ? `${slug}-${shortId}` : shortId;
|
|
6888
6964
|
};
|
|
@@ -7002,14 +7078,14 @@ const resolveProjectName = async (context, rawName) => {
|
|
|
7002
7078
|
const trimmed = rawName.trim();
|
|
7003
7079
|
if (trimmed.length > 0) return trimmed;
|
|
7004
7080
|
}
|
|
7005
|
-
const { promptForValue } = await import("./prompt-
|
|
7081
|
+
const { promptForValue } = await import("./prompt-B7mDo4K7.mjs");
|
|
7006
7082
|
const prompted = await promptForValue(context, "Project name");
|
|
7007
7083
|
if (prompted) return prompted;
|
|
7008
7084
|
throw new CliError("Project name is required.", EXIT_CODES.VALIDATION, "Provide a name: donebear project add \"My project\"");
|
|
7009
7085
|
};
|
|
7010
7086
|
const runProjectAdd = async (context, name, options) => {
|
|
7011
7087
|
const commandContext = await resolveCommandContext(context);
|
|
7012
|
-
const userId = requireUserId(commandContext
|
|
7088
|
+
const userId = await requireUserId(context, commandContext);
|
|
7013
7089
|
const normalizedName = await resolveProjectName(context, name);
|
|
7014
7090
|
const { workspace, autoSelected } = await resolveWorkspace(context, {
|
|
7015
7091
|
apiBaseUrl: commandContext.apiBaseUrl,
|
|
@@ -7017,17 +7093,23 @@ const runProjectAdd = async (context, name, options) => {
|
|
|
7017
7093
|
token: commandContext.token,
|
|
7018
7094
|
workspaceRef: normalizeWorkspaceRef(options.workspace)
|
|
7019
7095
|
});
|
|
7020
|
-
const
|
|
7096
|
+
const idempotencyId = (part) => options.idempotencyKey ? createIdempotentMutationId({
|
|
7097
|
+
key: options.idempotencyKey,
|
|
7098
|
+
modelName: "Project",
|
|
7099
|
+
part,
|
|
7100
|
+
workspaceId: workspace.id
|
|
7101
|
+
}) : null;
|
|
7102
|
+
const projectId = idempotencyId("model") ?? randomUUID();
|
|
7021
7103
|
const now = Date.now();
|
|
7022
7104
|
const key = generateProjectSlug(normalizedName, projectId);
|
|
7023
7105
|
await mutateModel(context, {
|
|
7024
7106
|
baseUrl: commandContext.apiBaseUrl,
|
|
7025
7107
|
request: {
|
|
7026
|
-
batchId: randomUUID(),
|
|
7108
|
+
batchId: idempotencyId("batch") ?? randomUUID(),
|
|
7027
7109
|
transactions: [{
|
|
7028
7110
|
action: "INSERT",
|
|
7029
|
-
clientId: commandContext.localContext.clientId,
|
|
7030
|
-
clientTxId: randomUUID(),
|
|
7111
|
+
clientId: options.idempotencyKey ? "cli-idem" : commandContext.localContext.clientId,
|
|
7112
|
+
clientTxId: idempotencyId("tx") ?? randomUUID(),
|
|
7031
7113
|
modelId: projectId,
|
|
7032
7114
|
modelName: "Project",
|
|
7033
7115
|
payload: {
|
|
@@ -7194,7 +7276,7 @@ Agent tip: run "donebear spec project add" for full option reference
|
|
|
7194
7276
|
await runProjectShow(context, projectRef, { workspace: workspaceOverride ?? normalizeWorkspaceRef(options.workspace) ?? void 0 });
|
|
7195
7277
|
});
|
|
7196
7278
|
});
|
|
7197
|
-
project.command("add").alias("create").description("Create a project").argument("[name]", "Project name (prompted interactively if omitted)").option("-w, --workspace <workspace>", "Workspace id, slug, or exact name").option("-d, --description <text>", "Project description").option("--target-date <date>", "Target date (YYYY-MM-DD)").action(async (name, options, command) => {
|
|
7279
|
+
project.command("add").alias("create").description("Create a project").argument("[name]", "Project name (prompted interactively if omitted)").option("-w, --workspace <workspace>", "Workspace id, slug, or exact name").option("-d, --description <text>", "Project description").option("--target-date <date>", "Target date (YYYY-MM-DD)").option("--idempotency-key <key>", "Stable key making retries safe (repeated calls create only one project)").action(async (name, options, command) => {
|
|
7198
7280
|
const context = contextFromCommand(command);
|
|
7199
7281
|
await runWithErrorHandling(context, async () => {
|
|
7200
7282
|
await runProjectAdd(context, name, {
|
|
@@ -7253,7 +7335,7 @@ Agent tip: run "donebear spec project add" for full option reference
|
|
|
7253
7335
|
};
|
|
7254
7336
|
//#endregion
|
|
7255
7337
|
//#region src/commands/task.ts
|
|
7256
|
-
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/
|
|
7338
|
+
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
|
|
7257
7339
|
const DEFAULT_TASK_LIST_LIMIT = 20;
|
|
7258
7340
|
const VALID_TASK_VIEWS = new Set([
|
|
7259
7341
|
"inbox",
|
|
@@ -7417,7 +7499,7 @@ const resolveNotes = async (_context, flagNotes) => {
|
|
|
7417
7499
|
};
|
|
7418
7500
|
const runTaskAdd = async (context, title, options, workspaceOverride) => {
|
|
7419
7501
|
const commandContext = await resolveCommandContext(context);
|
|
7420
|
-
const userId = requireUserId(commandContext
|
|
7502
|
+
const userId = await requireUserId(context, commandContext);
|
|
7421
7503
|
const normalizedTitle = await resolveTitle(context, title);
|
|
7422
7504
|
const notes = await resolveNotes(context, options.notes);
|
|
7423
7505
|
const { workspace, autoSelected } = await resolveWorkspace(context, {
|
|
@@ -7435,7 +7517,13 @@ const runTaskAdd = async (context, title, options, workspaceOverride) => {
|
|
|
7435
7517
|
token: commandContext.token,
|
|
7436
7518
|
workspaceId: workspace.id
|
|
7437
7519
|
}).then((teams) => resolveEntityId(teams, options.team, "Team")) : Promise.resolve(null)]);
|
|
7438
|
-
const
|
|
7520
|
+
const idempotencyId = (part) => options.idempotencyKey ? createIdempotentMutationId({
|
|
7521
|
+
key: options.idempotencyKey,
|
|
7522
|
+
modelName: "Task",
|
|
7523
|
+
part,
|
|
7524
|
+
workspaceId: workspace.id
|
|
7525
|
+
}) : null;
|
|
7526
|
+
const taskId = idempotencyId("model") ?? randomUUID();
|
|
7439
7527
|
const now = Date.now();
|
|
7440
7528
|
const view = parseTaskView(options.when);
|
|
7441
7529
|
const startFields = buildTaskStartFields(view);
|
|
@@ -7443,11 +7531,11 @@ const runTaskAdd = async (context, title, options, workspaceOverride) => {
|
|
|
7443
7531
|
await mutateModel(context, {
|
|
7444
7532
|
baseUrl: commandContext.apiBaseUrl,
|
|
7445
7533
|
request: {
|
|
7446
|
-
batchId: randomUUID(),
|
|
7534
|
+
batchId: idempotencyId("batch") ?? randomUUID(),
|
|
7447
7535
|
transactions: [{
|
|
7448
7536
|
action: "INSERT",
|
|
7449
|
-
clientId: commandContext.localContext.clientId,
|
|
7450
|
-
clientTxId: randomUUID(),
|
|
7537
|
+
clientId: options.idempotencyKey ? "cli-idem" : commandContext.localContext.clientId,
|
|
7538
|
+
clientTxId: idempotencyId("tx") ?? randomUUID(),
|
|
7451
7539
|
modelId: taskId,
|
|
7452
7540
|
modelName: "Task",
|
|
7453
7541
|
payload: {
|
|
@@ -7809,15 +7897,15 @@ Views: inbox | anytime | today | upcoming | someday | logbook | trash
|
|
|
7809
7897
|
await runTaskRandom(context, options, workspaceOverride);
|
|
7810
7898
|
});
|
|
7811
7899
|
});
|
|
7812
|
-
task.command("add").alias("create").description("Create a task").argument("[title]", "Task title (prompted interactively if omitted)").option("-w, --workspace <workspace>", "Workspace id, slug, or exact name").option("-n, --notes <text>", "Task notes (or pipe via stdin)").option("--when <view>", "inbox|anytime|today|upcoming|someday", "inbox").option("--deadline <date>", "Deadline (YYYY-MM-DD or ISO datetime)").option("--project <key-or-id>", "Project key or id").option("--team <key-or-id>", "Team key or id").addHelpText("after", `
|
|
7900
|
+
task.command("add").alias("create").description("Create a task").argument("[title]", "Task title (prompted interactively if omitted)").option("-w, --workspace <workspace>", "Workspace id, slug, or exact name").option("-n, --notes <text>", "Task notes (or pipe via stdin)").option("--when <view>", "inbox|anytime|today|upcoming|someday", "inbox").option("--deadline <date>", "Deadline (YYYY-MM-DD or ISO datetime)").option("--project <key-or-id>", "Project key or id").option("--team <key-or-id>", "Team key or id").option("--idempotency-key <key>", "Stable key making retries safe (repeated calls create only one task)").addHelpText("after", `
|
|
7813
7901
|
Examples:
|
|
7814
7902
|
donebear task add "Fix login bug" --when today --json
|
|
7815
|
-
donebear task add "Write tests" --deadline
|
|
7903
|
+
donebear task add "Write tests" --deadline 2030-03-15 --project PROJ --json
|
|
7816
7904
|
donebear task add "Review PR" --when anytime --notes "Check for XSS" --json
|
|
7817
7905
|
echo "Multi-line notes" | donebear task add "My task"
|
|
7818
7906
|
donebear task add # prompts for title interactively
|
|
7819
7907
|
|
|
7820
|
-
Date format: YYYY-MM-DD (e.g.
|
|
7908
|
+
Date format: YYYY-MM-DD (e.g. 2030-03-15)
|
|
7821
7909
|
View (--when): inbox | anytime | today | upcoming | someday
|
|
7822
7910
|
`).action(async (title, options, command) => {
|
|
7823
7911
|
const context = contextFromCommand(command);
|
|
@@ -8161,6 +8249,15 @@ Examples:
|
|
|
8161
8249
|
const OVERVIEW = {
|
|
8162
8250
|
authentication: "Set DONEBEAR_TOKEN env var, or run: donebear auth login",
|
|
8163
8251
|
commands: {
|
|
8252
|
+
"api-key": {
|
|
8253
|
+
description: "Manage personal API keys",
|
|
8254
|
+
quick_example: "donebear api-key list --json",
|
|
8255
|
+
subcommands: [
|
|
8256
|
+
"create",
|
|
8257
|
+
"list",
|
|
8258
|
+
"revoke"
|
|
8259
|
+
]
|
|
8260
|
+
},
|
|
8164
8261
|
auth: {
|
|
8165
8262
|
description: "Authentication",
|
|
8166
8263
|
quick_example: "donebear auth status --json",
|
|
@@ -8188,9 +8285,15 @@ const OVERVIEW = {
|
|
|
8188
8285
|
subcommands: ["things"]
|
|
8189
8286
|
},
|
|
8190
8287
|
label: {
|
|
8191
|
-
description: "
|
|
8288
|
+
description: "Manage workspace labels and task label assignments",
|
|
8192
8289
|
quick_example: "donebear label list --json",
|
|
8193
|
-
subcommands: [
|
|
8290
|
+
subcommands: [
|
|
8291
|
+
"list",
|
|
8292
|
+
"show",
|
|
8293
|
+
"create",
|
|
8294
|
+
"add",
|
|
8295
|
+
"remove"
|
|
8296
|
+
]
|
|
8194
8297
|
},
|
|
8195
8298
|
project: {
|
|
8196
8299
|
description: "Manage projects",
|
|
@@ -8228,6 +8331,7 @@ const OVERVIEW = {
|
|
|
8228
8331
|
"read",
|
|
8229
8332
|
"append",
|
|
8230
8333
|
"prepend",
|
|
8334
|
+
"random",
|
|
8231
8335
|
"checklist"
|
|
8232
8336
|
]
|
|
8233
8337
|
},
|
|
@@ -8276,6 +8380,70 @@ const OVERVIEW = {
|
|
|
8276
8380
|
workspace_targeting: "Prefix any command: donebear workspace=<slug> task list"
|
|
8277
8381
|
};
|
|
8278
8382
|
const COMMAND_SPECS = {
|
|
8383
|
+
"api-key create": {
|
|
8384
|
+
arguments: { label: {
|
|
8385
|
+
description: "Human-readable label for the key",
|
|
8386
|
+
required: true,
|
|
8387
|
+
type: "string"
|
|
8388
|
+
} },
|
|
8389
|
+
command: "api-key create",
|
|
8390
|
+
examples: [
|
|
8391
|
+
"donebear api-key create \"Raycast\" --json",
|
|
8392
|
+
"donebear api-key create \"CI\" --permissions read-only --expires 90d --json",
|
|
8393
|
+
"donebear api-key create \"Scoped\" --workspaces my-org,side-project --expires 1y --json"
|
|
8394
|
+
],
|
|
8395
|
+
json_output: {
|
|
8396
|
+
createdAt: "string",
|
|
8397
|
+
expiresAt: "string | null",
|
|
8398
|
+
id: "string",
|
|
8399
|
+
keyPrefix: "string",
|
|
8400
|
+
label: "string",
|
|
8401
|
+
scopes: "string[]",
|
|
8402
|
+
token: "string",
|
|
8403
|
+
workspaceIds: "string[] | null"
|
|
8404
|
+
},
|
|
8405
|
+
notes: "The full token is only shown once at creation time. Scopes default to full-access (read, write, admin).",
|
|
8406
|
+
options: {
|
|
8407
|
+
"--expires": {
|
|
8408
|
+
description: "Expiration duration, e.g. \"30d\", \"90d\", \"1y\"",
|
|
8409
|
+
type: "string"
|
|
8410
|
+
},
|
|
8411
|
+
"--permissions": {
|
|
8412
|
+
default: "full-access",
|
|
8413
|
+
description: "\"full-access\" (read, write, admin) or \"read-only\" (read only)",
|
|
8414
|
+
enum: ["full-access", "read-only"],
|
|
8415
|
+
type: "string"
|
|
8416
|
+
},
|
|
8417
|
+
"--workspaces": {
|
|
8418
|
+
description: "Comma-separated workspace slugs to restrict the key to",
|
|
8419
|
+
type: "string"
|
|
8420
|
+
}
|
|
8421
|
+
},
|
|
8422
|
+
usage: "donebear api-key create <label> [options]"
|
|
8423
|
+
},
|
|
8424
|
+
"api-key list": {
|
|
8425
|
+
command: "api-key list",
|
|
8426
|
+
examples: ["donebear api-key list --json"],
|
|
8427
|
+
json_output: { keys: "ApiKeyRecord[]" },
|
|
8428
|
+
notes: "Each key includes scopes, workspaceIds, and expiresAt fields.",
|
|
8429
|
+
options: {},
|
|
8430
|
+
usage: "donebear api-key list"
|
|
8431
|
+
},
|
|
8432
|
+
"api-key revoke": {
|
|
8433
|
+
arguments: { "key-id": {
|
|
8434
|
+
description: "The UUID of the API key to revoke",
|
|
8435
|
+
required: true,
|
|
8436
|
+
type: "string"
|
|
8437
|
+
} },
|
|
8438
|
+
command: "api-key revoke",
|
|
8439
|
+
examples: ["donebear api-key revoke abc12345 --json"],
|
|
8440
|
+
json_output: {
|
|
8441
|
+
id: "string",
|
|
8442
|
+
ok: "boolean"
|
|
8443
|
+
},
|
|
8444
|
+
options: {},
|
|
8445
|
+
usage: "donebear api-key revoke <key-id>"
|
|
8446
|
+
},
|
|
8279
8447
|
"auth login": {
|
|
8280
8448
|
command: "auth login",
|
|
8281
8449
|
examples: [
|
|
@@ -8289,10 +8457,21 @@ const COMMAND_SPECS = {
|
|
|
8289
8457
|
user: "{ id, email }"
|
|
8290
8458
|
},
|
|
8291
8459
|
non_interactive: "For CI/scripts, set DONEBEAR_TOKEN=<token> instead of using auth login.",
|
|
8292
|
-
options: {
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
|
|
8460
|
+
options: {
|
|
8461
|
+
"--no-open": {
|
|
8462
|
+
description: "Print auth URL instead of opening browser",
|
|
8463
|
+
type: "boolean"
|
|
8464
|
+
},
|
|
8465
|
+
"--port": {
|
|
8466
|
+
default: 8787,
|
|
8467
|
+
description: "Loopback callback port for the OAuth redirect",
|
|
8468
|
+
type: "number"
|
|
8469
|
+
},
|
|
8470
|
+
"--timeout": {
|
|
8471
|
+
description: "Seconds to wait for the OAuth callback",
|
|
8472
|
+
type: "number"
|
|
8473
|
+
}
|
|
8474
|
+
},
|
|
8296
8475
|
usage: "donebear auth login [options]"
|
|
8297
8476
|
},
|
|
8298
8477
|
"auth status": {
|
|
@@ -8414,6 +8593,10 @@ const COMMAND_SPECS = {
|
|
|
8414
8593
|
enum: ["note", "none"],
|
|
8415
8594
|
type: "string"
|
|
8416
8595
|
},
|
|
8596
|
+
"--skip-teams": {
|
|
8597
|
+
description: "Skip importing Things Areas as Teams and TeamProject links",
|
|
8598
|
+
type: "boolean"
|
|
8599
|
+
},
|
|
8417
8600
|
"--workspace": {
|
|
8418
8601
|
description: "Workspace id, slug, or name",
|
|
8419
8602
|
type: "string"
|
|
@@ -8429,7 +8612,15 @@ const COMMAND_SPECS = {
|
|
|
8429
8612
|
ok: "boolean",
|
|
8430
8613
|
workspace: "WorkspaceSummary"
|
|
8431
8614
|
},
|
|
8432
|
-
options: {
|
|
8615
|
+
options: {
|
|
8616
|
+
"--sort": {
|
|
8617
|
+
default: "alpha",
|
|
8618
|
+
description: "Sort order: alpha|count",
|
|
8619
|
+
enum: ["alpha", "count"],
|
|
8620
|
+
type: "string"
|
|
8621
|
+
},
|
|
8622
|
+
"--workspace": { type: "string" }
|
|
8623
|
+
},
|
|
8433
8624
|
usage: "donebear label list [options]"
|
|
8434
8625
|
},
|
|
8435
8626
|
"project add": {
|
|
@@ -8448,6 +8639,10 @@ const COMMAND_SPECS = {
|
|
|
8448
8639
|
notes: "URL slug is auto-generated from name (e.g. 'Q2 Launch' → 'q2-launch-a1b2c3d4').",
|
|
8449
8640
|
options: {
|
|
8450
8641
|
"--description": { type: "string" },
|
|
8642
|
+
"--idempotency-key": {
|
|
8643
|
+
description: "Stable key making retries safe (repeated calls create only one project)",
|
|
8644
|
+
type: "string"
|
|
8645
|
+
},
|
|
8451
8646
|
"--target-date": {
|
|
8452
8647
|
description: "Target completion date",
|
|
8453
8648
|
format: "YYYY-MM-DD",
|
|
@@ -8486,8 +8681,25 @@ const COMMAND_SPECS = {
|
|
|
8486
8681
|
ok: "boolean"
|
|
8487
8682
|
},
|
|
8488
8683
|
options: {
|
|
8684
|
+
"--clear-description": {
|
|
8685
|
+
description: "Clear the project description",
|
|
8686
|
+
type: "boolean"
|
|
8687
|
+
},
|
|
8688
|
+
"--clear-target-date": {
|
|
8689
|
+
description: "Clear the project target date",
|
|
8690
|
+
type: "boolean"
|
|
8691
|
+
},
|
|
8489
8692
|
"--description": { type: "string" },
|
|
8490
8693
|
"--name": { type: "string" },
|
|
8694
|
+
"--status": {
|
|
8695
|
+
description: "Set status",
|
|
8696
|
+
enum: [
|
|
8697
|
+
"active",
|
|
8698
|
+
"done",
|
|
8699
|
+
"archived"
|
|
8700
|
+
],
|
|
8701
|
+
type: "string"
|
|
8702
|
+
},
|
|
8491
8703
|
"--target-date": {
|
|
8492
8704
|
format: "YYYY-MM-DD",
|
|
8493
8705
|
type: "string"
|
|
@@ -8587,6 +8799,10 @@ const COMMAND_SPECS = {
|
|
|
8587
8799
|
format: "YYYY-MM-DD",
|
|
8588
8800
|
type: "string"
|
|
8589
8801
|
},
|
|
8802
|
+
"--idempotency-key": {
|
|
8803
|
+
description: "Stable key making retries safe (repeated calls create only one task)",
|
|
8804
|
+
type: "string"
|
|
8805
|
+
},
|
|
8590
8806
|
"--notes": {
|
|
8591
8807
|
description: "Body / description text (or pipe via stdin)",
|
|
8592
8808
|
type: "string"
|
|
@@ -8677,15 +8893,35 @@ const COMMAND_SPECS = {
|
|
|
8677
8893
|
type: "string"
|
|
8678
8894
|
} },
|
|
8679
8895
|
command: "task edit",
|
|
8680
|
-
examples: [
|
|
8896
|
+
examples: [
|
|
8897
|
+
"donebear task edit abc1 --title \"Updated title\" --json",
|
|
8898
|
+
"donebear task edit abc1 --when today --deadline 2030-01-15 --json",
|
|
8899
|
+
"donebear task edit abc1 --clear-deadline --json"
|
|
8900
|
+
],
|
|
8681
8901
|
json_output: {
|
|
8682
8902
|
action: "string",
|
|
8683
8903
|
id: "string",
|
|
8684
8904
|
ok: "boolean"
|
|
8685
8905
|
},
|
|
8686
8906
|
options: {
|
|
8907
|
+
"--clear-deadline": {
|
|
8908
|
+
description: "Clear the due date",
|
|
8909
|
+
type: "boolean"
|
|
8910
|
+
},
|
|
8911
|
+
"--clear-notes": {
|
|
8912
|
+
description: "Clear notes/description",
|
|
8913
|
+
type: "boolean"
|
|
8914
|
+
},
|
|
8915
|
+
"--clear-project": {
|
|
8916
|
+
description: "Remove the task from its project",
|
|
8917
|
+
type: "boolean"
|
|
8918
|
+
},
|
|
8919
|
+
"--clear-team": {
|
|
8920
|
+
description: "Remove the task from its team",
|
|
8921
|
+
type: "boolean"
|
|
8922
|
+
},
|
|
8687
8923
|
"--deadline": {
|
|
8688
|
-
description: "Set due date
|
|
8924
|
+
description: "Set due date",
|
|
8689
8925
|
format: "YYYY-MM-DD",
|
|
8690
8926
|
type: "string"
|
|
8691
8927
|
},
|
|
@@ -8922,6 +9158,16 @@ const COMMAND_SPECS = {
|
|
|
8922
9158
|
}
|
|
8923
9159
|
};
|
|
8924
9160
|
const TOP_LEVEL_SPECS = {
|
|
9161
|
+
"api-key": {
|
|
9162
|
+
command: "api-key",
|
|
9163
|
+
description: "Manage personal API keys",
|
|
9164
|
+
subcommands: {
|
|
9165
|
+
create: "Create a new API key (--permissions, --workspaces, --expires)",
|
|
9166
|
+
list: "List all API keys with scopes and expiration",
|
|
9167
|
+
revoke: "Revoke an API key by id"
|
|
9168
|
+
},
|
|
9169
|
+
tip: "donebear spec api-key create — get full spec for api-key create"
|
|
9170
|
+
},
|
|
8925
9171
|
auth: {
|
|
8926
9172
|
command: "auth",
|
|
8927
9173
|
description: "Authentication",
|
|
@@ -8941,9 +9187,12 @@ const TOP_LEVEL_SPECS = {
|
|
|
8941
9187
|
},
|
|
8942
9188
|
label: {
|
|
8943
9189
|
command: "label",
|
|
8944
|
-
description: "
|
|
9190
|
+
description: "Manage workspace labels and task label assignments",
|
|
8945
9191
|
subcommands: {
|
|
9192
|
+
add: "Add a label to a task",
|
|
9193
|
+
create: "Create a new label",
|
|
8946
9194
|
list: "List all labels",
|
|
9195
|
+
remove: "Remove a label from a task",
|
|
8947
9196
|
show: "Get label details"
|
|
8948
9197
|
}
|
|
8949
9198
|
},
|
|
@@ -8974,6 +9223,7 @@ const TOP_LEVEL_SPECS = {
|
|
|
8974
9223
|
edit: "Update task fields",
|
|
8975
9224
|
list: "List tasks (--state, --view, --search, --limit)",
|
|
8976
9225
|
prepend: "Prepend text to task notes",
|
|
9226
|
+
random: "Pick a random task (--state to filter)",
|
|
8977
9227
|
read: "Print task notes as plain text",
|
|
8978
9228
|
reopen: "Reopen a completed task",
|
|
8979
9229
|
show: "Get task details by id",
|
|
@@ -9215,8 +9465,13 @@ Examples:
|
|
|
9215
9465
|
//#endregion
|
|
9216
9466
|
//#region src/commands/whoami.ts
|
|
9217
9467
|
const runWhoAmI = async (context) => {
|
|
9218
|
-
|
|
9219
|
-
|
|
9468
|
+
let supabaseConfig = null;
|
|
9469
|
+
try {
|
|
9470
|
+
supabaseConfig = resolveSupabaseConfig();
|
|
9471
|
+
} catch {
|
|
9472
|
+
supabaseConfig = null;
|
|
9473
|
+
}
|
|
9474
|
+
const resolvedToken = await resolveAuthToken(context, supabaseConfig);
|
|
9220
9475
|
if (!resolvedToken) {
|
|
9221
9476
|
process.exitCode = EXIT_CODES.AUTH_REQUIRED;
|
|
9222
9477
|
if (context.json) {
|
|
@@ -9233,7 +9488,10 @@ const runWhoAmI = async (context) => {
|
|
|
9233
9488
|
writeInfo(context, "Run `donebear auth login`.");
|
|
9234
9489
|
return;
|
|
9235
9490
|
}
|
|
9236
|
-
const user = await
|
|
9491
|
+
const user = await loadViewer(context, {
|
|
9492
|
+
baseUrl: resolveApiBaseUrl(context),
|
|
9493
|
+
token: resolvedToken.token
|
|
9494
|
+
});
|
|
9237
9495
|
if (context.json) {
|
|
9238
9496
|
writeOutput(context, {
|
|
9239
9497
|
ok: true,
|
|
@@ -9592,30 +9850,8 @@ Examples:
|
|
|
9592
9850
|
});
|
|
9593
9851
|
};
|
|
9594
9852
|
//#endregion
|
|
9595
|
-
//#region src/
|
|
9596
|
-
const
|
|
9597
|
-
const WORKSPACE_PREFIX_REGEX = /^workspace=(.+)$/i;
|
|
9598
|
-
const extractWorkspaceTarget = (argv) => {
|
|
9599
|
-
const processedArgv = [...argv];
|
|
9600
|
-
const [, , firstArg] = processedArgv;
|
|
9601
|
-
if (firstArg) {
|
|
9602
|
-
const match = WORKSPACE_PREFIX_REGEX.exec(firstArg);
|
|
9603
|
-
if (match?.[1]) {
|
|
9604
|
-
processedArgv.splice(2, 1);
|
|
9605
|
-
return {
|
|
9606
|
-
processedArgv,
|
|
9607
|
-
workspaceOverride: match[1].trim()
|
|
9608
|
-
};
|
|
9609
|
-
}
|
|
9610
|
-
}
|
|
9611
|
-
return {
|
|
9612
|
-
processedArgv,
|
|
9613
|
-
workspaceOverride: null
|
|
9614
|
-
};
|
|
9615
|
-
};
|
|
9616
|
-
const main = async () => {
|
|
9617
|
-
loadCliEnvironmentFiles();
|
|
9618
|
-
const { processedArgv, workspaceOverride } = extractWorkspaceTarget(process.argv);
|
|
9853
|
+
//#region src/program.ts
|
|
9854
|
+
const buildProgram = (workspaceOverride = null) => {
|
|
9619
9855
|
const program = new Command();
|
|
9620
9856
|
program.name(CLI_NAME).description("Done Bear CLI for auth, workspace, and task workflows").version(packageManifest.version, "-v, --version").showHelpAfterError().showSuggestionAfterError();
|
|
9621
9857
|
addGlobalOptions(program);
|
|
@@ -9659,6 +9895,33 @@ Filtering & output:
|
|
|
9659
9895
|
registerSearchCommand(program, workspaceOverride);
|
|
9660
9896
|
registerHistoryCommand(program, workspaceOverride);
|
|
9661
9897
|
registerWhoAmICommand(program);
|
|
9898
|
+
return program;
|
|
9899
|
+
};
|
|
9900
|
+
//#endregion
|
|
9901
|
+
//#region src/cli.ts
|
|
9902
|
+
const WORKSPACE_PREFIX_REGEX = /^workspace=(.+)$/iu;
|
|
9903
|
+
const extractWorkspaceTarget = (argv) => {
|
|
9904
|
+
const processedArgv = [...argv];
|
|
9905
|
+
const firstArg = processedArgv[2];
|
|
9906
|
+
if (firstArg) {
|
|
9907
|
+
const match = WORKSPACE_PREFIX_REGEX.exec(firstArg);
|
|
9908
|
+
if (match?.[1]) {
|
|
9909
|
+
processedArgv.splice(2, 1);
|
|
9910
|
+
return {
|
|
9911
|
+
processedArgv,
|
|
9912
|
+
workspaceOverride: match[1].trim()
|
|
9913
|
+
};
|
|
9914
|
+
}
|
|
9915
|
+
}
|
|
9916
|
+
return {
|
|
9917
|
+
processedArgv,
|
|
9918
|
+
workspaceOverride: null
|
|
9919
|
+
};
|
|
9920
|
+
};
|
|
9921
|
+
const main = async () => {
|
|
9922
|
+
loadCliEnvironmentFiles();
|
|
9923
|
+
const { processedArgv, workspaceOverride } = extractWorkspaceTarget(process.argv);
|
|
9924
|
+
const program = buildProgram(workspaceOverride);
|
|
9662
9925
|
const hasUserArgs = processedArgv.length <= 2;
|
|
9663
9926
|
const isTTY = process.stdin.isTTY === true;
|
|
9664
9927
|
if (hasUserArgs && isTTY) {
|