donebear 0.3.8 → 0.4.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/AGENTS.md +2 -2
- package/README.md +2 -2
- package/dist/cli.mjs +468 -359
- package/dist/cli.mjs.map +1 -1
- package/dist/{context-store-DuCNavgl.mjs → context-store-BBEVWudm.mjs} +3 -3
- package/dist/{context-store-DuCNavgl.mjs.map → context-store-BBEVWudm.mjs.map} +1 -1
- package/dist/context-store-ClAx1T2N.mjs +3 -0
- package/package.json +1 -1
- package/dist/context-store-CK4yIl9e.mjs +0 -3
package/dist/cli.mjs
CHANGED
|
@@ -1,25 +1,256 @@
|
|
|
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-
|
|
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
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
21
|
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
|
|
22
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
|
+
};
|
|
253
|
+
//#endregion
|
|
23
254
|
//#region src/jwt.ts
|
|
24
255
|
const parseJwtBase64Url = (input) => {
|
|
25
256
|
const normalized = input.replaceAll("-", "+").replaceAll("_", "/");
|
|
@@ -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";
|
|
@@ -368,153 +571,41 @@ const formatDateOnlyEpoch = (epochMs) => {
|
|
|
368
571
|
const formatInstantEpoch = (epochMs) => {
|
|
369
572
|
if (!isFiniteEpoch(epochMs)) return null;
|
|
370
573
|
const date = new Date(epochMs);
|
|
371
|
-
if (Number.isNaN(date.getTime())) return null;
|
|
372
|
-
return date.toISOString();
|
|
373
|
-
};
|
|
374
|
-
const parseDateOnlyString = (value) => {
|
|
375
|
-
if (typeof value !== "string") return null;
|
|
376
|
-
const trimmed = value.trim();
|
|
377
|
-
const match = ISO_DATE_ONLY_REGEX.exec(trimmed);
|
|
378
|
-
if (!match) return null;
|
|
379
|
-
const year = Number.parseInt(match[1] ?? "", 10);
|
|
380
|
-
const month = Number.parseInt(match[2] ?? "", 10);
|
|
381
|
-
const parts = {
|
|
382
|
-
day: Number.parseInt(match[3] ?? "", 10),
|
|
383
|
-
month,
|
|
384
|
-
year
|
|
385
|
-
};
|
|
386
|
-
return isValidDateOnlyParts(parts) ? parts : null;
|
|
387
|
-
};
|
|
388
|
-
const parseApiDateOnlyToEpoch = (value) => {
|
|
389
|
-
const parts = parseDateOnlyString(value);
|
|
390
|
-
return parts ? dateOnlyPartsToEpoch(parts) : null;
|
|
391
|
-
};
|
|
392
|
-
const parseApiInstantToEpoch = (value) => {
|
|
393
|
-
if (typeof value !== "string") return null;
|
|
394
|
-
const trimmed = value.trim();
|
|
395
|
-
if (trimmed.length === 0 || !ISO_INSTANT_REGEX.test(trimmed)) return null;
|
|
396
|
-
const parsed = Date.parse(trimmed);
|
|
397
|
-
return Number.isNaN(parsed) ? null : parsed;
|
|
398
|
-
};
|
|
399
|
-
const parseCliDateOnlyToEpoch = (value, fieldLabel) => {
|
|
400
|
-
if (!value) return null;
|
|
401
|
-
const parts = parseDateOnlyString(value);
|
|
402
|
-
if (!parts) throw new Error(`Invalid ${fieldLabel}: ${value}. Expected YYYY-MM-DD.`);
|
|
403
|
-
return dateOnlyPartsToEpoch(parts);
|
|
404
|
-
};
|
|
405
|
-
const dateOnlyEpochFromLocalDate = (now = /* @__PURE__ */ new Date()) => Date.UTC(now.getFullYear(), now.getMonth(), now.getDate());
|
|
406
|
-
//#endregion
|
|
407
|
-
//#region src/output.ts
|
|
408
|
-
const writeLine = (stream, message) => {
|
|
409
|
-
stream.write(`${message}\n`);
|
|
410
|
-
};
|
|
411
|
-
const colorize = (context, format, message) => {
|
|
412
|
-
if (!context.color) return message;
|
|
413
|
-
return styleText(format, message);
|
|
414
|
-
};
|
|
415
|
-
const writeJson = (value) => {
|
|
416
|
-
writeLine(process.stdout, JSON.stringify(value, null, 2));
|
|
417
|
-
};
|
|
418
|
-
const writeYaml = (value) => {
|
|
419
|
-
process.stdout.write(stringify(value));
|
|
420
|
-
};
|
|
421
|
-
const writeInfo = (context, message) => {
|
|
422
|
-
writeLine(process.stdout, colorize(context, "cyan", message));
|
|
423
|
-
};
|
|
424
|
-
const writeSuccess = (context, message) => {
|
|
425
|
-
writeLine(process.stdout, colorize(context, "green", message));
|
|
426
|
-
};
|
|
427
|
-
const writeWarning = (context, message) => {
|
|
428
|
-
writeLine(process.stderr, colorize(context, "yellow", message));
|
|
429
|
-
};
|
|
430
|
-
const writeError = (context, message) => {
|
|
431
|
-
writeLine(process.stderr, colorize(context, "red", message));
|
|
432
|
-
};
|
|
433
|
-
const writeDebug = (context, message) => {
|
|
434
|
-
if (!context.debug) return;
|
|
435
|
-
writeLine(process.stderr, colorize(context, "gray", `[debug] ${message}`));
|
|
436
|
-
};
|
|
437
|
-
const writeTotal = (context, count) => {
|
|
438
|
-
writeLine(process.stdout, colorize(context, "cyan", String(count)));
|
|
439
|
-
};
|
|
440
|
-
const escapeCsvField = (value) => {
|
|
441
|
-
if (value.includes(",") || value.includes("\"") || value.includes("\n")) return `"${value.replaceAll("\"", "\"\"")}"`;
|
|
442
|
-
return value;
|
|
443
|
-
};
|
|
444
|
-
const writeCsv = (headers, rows) => {
|
|
445
|
-
writeLine(process.stdout, headers.map(escapeCsvField).join(","));
|
|
446
|
-
for (const row of rows) writeLine(process.stdout, row.map(escapeCsvField).join(","));
|
|
447
|
-
};
|
|
448
|
-
const writeTsv = (headers, rows) => {
|
|
449
|
-
writeLine(process.stdout, headers.join(" "));
|
|
450
|
-
for (const row of rows) writeLine(process.stdout, row.join(" "));
|
|
451
|
-
};
|
|
452
|
-
/**
|
|
453
|
-
* Write structured data to stdout, applying --jq filter and --limit if set.
|
|
454
|
-
* Use this for all JSON/YAML/structured output from commands.
|
|
455
|
-
*/
|
|
456
|
-
const writeOutput = (context, data) => {
|
|
457
|
-
let result = data;
|
|
458
|
-
if (context.jq) try {
|
|
459
|
-
result = jmespath.search(result, context.jq);
|
|
460
|
-
} catch (error) {
|
|
461
|
-
throw new CliError(`Invalid --jq expression: ${error instanceof Error ? error.message : String(error)}`, EXIT_CODES.VALIDATION);
|
|
462
|
-
}
|
|
463
|
-
if (context.limit !== null && Array.isArray(result)) result = result.slice(0, context.limit);
|
|
464
|
-
switch (context.format) {
|
|
465
|
-
case "yaml":
|
|
466
|
-
writeYaml(result);
|
|
467
|
-
break;
|
|
468
|
-
case "json":
|
|
469
|
-
writeJson(result);
|
|
470
|
-
break;
|
|
471
|
-
default:
|
|
472
|
-
writeJson(result);
|
|
473
|
-
break;
|
|
474
|
-
}
|
|
475
|
-
if (context.copy) copyToClipboard(JSON.stringify(result, null, 2));
|
|
476
|
-
};
|
|
477
|
-
/**
|
|
478
|
-
* Handles --total, --format csv, --format tsv, --format yaml output.
|
|
479
|
-
* Returns true if the format was handled; caller renders text mode if false.
|
|
480
|
-
*/
|
|
481
|
-
const writeFormattedRows = (context, totalCount, headers, rows) => {
|
|
482
|
-
if (context.total) {
|
|
483
|
-
writeTotal(context, totalCount);
|
|
484
|
-
return true;
|
|
485
|
-
}
|
|
486
|
-
if (context.format === "yaml") {
|
|
487
|
-
const objects = rows.map((row) => Object.fromEntries(headers.map((h, i) => [h, row[i]])));
|
|
488
|
-
writeYaml(objects);
|
|
489
|
-
if (context.copy) copyToClipboard(stringify(objects));
|
|
490
|
-
return true;
|
|
491
|
-
}
|
|
492
|
-
if (context.format === "csv") {
|
|
493
|
-
writeCsv(headers, rows);
|
|
494
|
-
if (context.copy) copyToClipboard([headers.join(","), ...rows.map((r) => r.join(","))].join("\n"));
|
|
495
|
-
return true;
|
|
496
|
-
}
|
|
497
|
-
if (context.format === "tsv") {
|
|
498
|
-
writeTsv(headers, rows);
|
|
499
|
-
if (context.copy) copyToClipboard([headers.join(" "), ...rows.map((r) => r.join(" "))].join("\n"));
|
|
500
|
-
return true;
|
|
501
|
-
}
|
|
502
|
-
return false;
|
|
574
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
575
|
+
return date.toISOString();
|
|
503
576
|
};
|
|
504
|
-
const
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
577
|
+
const parseDateOnlyString = (value) => {
|
|
578
|
+
if (typeof value !== "string") return null;
|
|
579
|
+
const trimmed = value.trim();
|
|
580
|
+
const match = ISO_DATE_ONLY_REGEX.exec(trimmed);
|
|
581
|
+
if (!match) return null;
|
|
582
|
+
const year = Number.parseInt(match[1] ?? "", 10);
|
|
583
|
+
const month = Number.parseInt(match[2] ?? "", 10);
|
|
584
|
+
const parts = {
|
|
585
|
+
day: Number.parseInt(match[3] ?? "", 10),
|
|
586
|
+
month,
|
|
587
|
+
year
|
|
588
|
+
};
|
|
589
|
+
return isValidDateOnlyParts(parts) ? parts : null;
|
|
590
|
+
};
|
|
591
|
+
const parseApiDateOnlyToEpoch = (value) => {
|
|
592
|
+
const parts = parseDateOnlyString(value);
|
|
593
|
+
return parts ? dateOnlyPartsToEpoch(parts) : null;
|
|
517
594
|
};
|
|
595
|
+
const parseApiInstantToEpoch = (value) => {
|
|
596
|
+
if (typeof value !== "string") return null;
|
|
597
|
+
const trimmed = value.trim();
|
|
598
|
+
if (trimmed.length === 0 || !ISO_INSTANT_REGEX.test(trimmed)) return null;
|
|
599
|
+
const parsed = Date.parse(trimmed);
|
|
600
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
601
|
+
};
|
|
602
|
+
const parseCliDateOnlyToEpoch = (value, fieldLabel) => {
|
|
603
|
+
if (!value) return null;
|
|
604
|
+
const parts = parseDateOnlyString(value);
|
|
605
|
+
if (!parts) throw new Error(`Invalid ${fieldLabel}: ${value}. Expected YYYY-MM-DD.`);
|
|
606
|
+
return dateOnlyPartsToEpoch(parts);
|
|
607
|
+
};
|
|
608
|
+
const dateOnlyEpochFromLocalDate = (now = /* @__PURE__ */ new Date()) => Date.UTC(now.getFullYear(), now.getMonth(), now.getDate());
|
|
518
609
|
//#endregion
|
|
519
610
|
//#region src/manage-api.ts
|
|
520
611
|
const TASK_FIELDS = `
|
|
@@ -1999,11 +2090,11 @@ Examples:
|
|
|
1999
2090
|
});
|
|
2000
2091
|
});
|
|
2001
2092
|
};
|
|
2093
|
+
const packageManifest = createRequire(import.meta.url)("../package.json");
|
|
2002
2094
|
//#endregion
|
|
2003
2095
|
//#region src/commands/doctor.ts
|
|
2004
|
-
const packageManifest$1 = createRequire(import.meta.url)("../package.json");
|
|
2005
2096
|
const checkCliVersion = async () => {
|
|
2006
|
-
const current = packageManifest
|
|
2097
|
+
const current = packageManifest.version;
|
|
2007
2098
|
try {
|
|
2008
2099
|
const response = await fetch("https://registry.npmjs.org/donebear/latest", { signal: AbortSignal.timeout(5e3) });
|
|
2009
2100
|
if (!response.ok) return {
|
|
@@ -2068,7 +2159,7 @@ const checkAuth = async (context) => {
|
|
|
2068
2159
|
};
|
|
2069
2160
|
const checkWorkspace = async () => {
|
|
2070
2161
|
try {
|
|
2071
|
-
const { ensureLocalContext } = await import("./context-store-
|
|
2162
|
+
const { ensureLocalContext } = await import("./context-store-ClAx1T2N.mjs");
|
|
2072
2163
|
const localContext = await ensureLocalContext();
|
|
2073
2164
|
if (!localContext.workspaceId) return {
|
|
2074
2165
|
message: "No workspace selected (run `donebear workspace use`)",
|
|
@@ -2283,6 +2374,7 @@ const normalizeThingsLabelLookupKey = (value) => value.trim().toLocaleLowerCase(
|
|
|
2283
2374
|
//#region src/import/model-id.ts
|
|
2284
2375
|
const IMPORT_ID_NAMESPACE = "b1f9f0ac-9f76-54ed-8eb0-7d6d3a0a0a31";
|
|
2285
2376
|
const SYNC_COMPOSITE_ID_NAMESPACE = "92a73695-d772-4b43-9fb4-d79f5fbef300";
|
|
2377
|
+
const IDEMPOTENCY_ID_NAMESPACE = "4c8af5e2-1d36-5a0b-9c47-2f81b3d6e955";
|
|
2286
2378
|
const uuidToBytes = (uuid) => {
|
|
2287
2379
|
const normalized = uuid.replaceAll("-", "");
|
|
2288
2380
|
if (normalized.length !== 32) throw new Error(`Invalid UUID namespace: ${uuid}`);
|
|
@@ -2311,6 +2403,7 @@ const createUuidV5 = (name, namespace) => {
|
|
|
2311
2403
|
};
|
|
2312
2404
|
const createCompositeSyncId = (model, parts) => createUuidV5(`${model}:${JSON.stringify(parts)}`, SYNC_COMPOSITE_ID_NAMESPACE);
|
|
2313
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);
|
|
2314
2407
|
const createTaskLabelId = (params) => createCompositeSyncId("TaskLabel", [
|
|
2315
2408
|
params.workspaceId,
|
|
2316
2409
|
params.taskId,
|
|
@@ -6527,97 +6620,6 @@ Notes:
|
|
|
6527
6620
|
});
|
|
6528
6621
|
};
|
|
6529
6622
|
//#endregion
|
|
6530
|
-
//#region src/commands/interactive.ts
|
|
6531
|
-
const HISTORY_FILE = join(CONFIG_DIR, "history.json");
|
|
6532
|
-
const MAX_HISTORY = 200;
|
|
6533
|
-
const isHistoryFile = (value) => {
|
|
6534
|
-
if (typeof value !== "object" || value === null) return false;
|
|
6535
|
-
const record = value;
|
|
6536
|
-
return record.version === 1 && Array.isArray(record.entries) && record.entries.every((e) => typeof e === "string");
|
|
6537
|
-
};
|
|
6538
|
-
const loadHistory = async (historyFile) => {
|
|
6539
|
-
try {
|
|
6540
|
-
const raw = await readFile(historyFile, "utf-8");
|
|
6541
|
-
const parsed = JSON.parse(raw);
|
|
6542
|
-
if (isHistoryFile(parsed)) return parsed.entries;
|
|
6543
|
-
return [];
|
|
6544
|
-
} catch {
|
|
6545
|
-
return [];
|
|
6546
|
-
}
|
|
6547
|
-
};
|
|
6548
|
-
const tokenize = (input) => {
|
|
6549
|
-
const tokens = [];
|
|
6550
|
-
let current = "";
|
|
6551
|
-
let inQuote = false;
|
|
6552
|
-
let quoteChar = "";
|
|
6553
|
-
for (const char of input) if (inQuote) if (char === quoteChar) inQuote = false;
|
|
6554
|
-
else current += char;
|
|
6555
|
-
else if (char === "\"" || char === "'") {
|
|
6556
|
-
inQuote = true;
|
|
6557
|
-
quoteChar = char;
|
|
6558
|
-
} else if (char === " ") {
|
|
6559
|
-
if (current.length > 0) {
|
|
6560
|
-
tokens.push(current);
|
|
6561
|
-
current = "";
|
|
6562
|
-
}
|
|
6563
|
-
} else current += char;
|
|
6564
|
-
if (current.length > 0) tokens.push(current);
|
|
6565
|
-
return tokens;
|
|
6566
|
-
};
|
|
6567
|
-
const buildCompleter = (program) => (line) => {
|
|
6568
|
-
const allCommands = program.commands.flatMap((cmd) => [cmd.name(), ...cmd.aliases()]);
|
|
6569
|
-
const hits = allCommands.filter((c) => c.startsWith(line));
|
|
6570
|
-
return [hits.length > 0 ? hits : allCommands, line];
|
|
6571
|
-
};
|
|
6572
|
-
const runInteractiveMode = async (context, program) => {
|
|
6573
|
-
const history = await loadHistory(HISTORY_FILE);
|
|
6574
|
-
const rl = createInterface({
|
|
6575
|
-
completer: buildCompleter(program),
|
|
6576
|
-
historySize: MAX_HISTORY,
|
|
6577
|
-
input: process.stdin,
|
|
6578
|
-
output: process.stdout,
|
|
6579
|
-
prompt: "donebear> "
|
|
6580
|
-
});
|
|
6581
|
-
for (const entry of [...history].toReversed()) rl.history.push(entry);
|
|
6582
|
-
writeSuccess(context, "Done Bear interactive mode. Type \"exit\" or Ctrl+D to quit.");
|
|
6583
|
-
writeInfo(context, "Commands: task, workspace, project, label, team, today, search, auth, whoami");
|
|
6584
|
-
rl.prompt();
|
|
6585
|
-
const sessionEntries = [];
|
|
6586
|
-
for await (const line of rl) {
|
|
6587
|
-
const trimmed = line.trim();
|
|
6588
|
-
if (!trimmed) {
|
|
6589
|
-
rl.prompt();
|
|
6590
|
-
continue;
|
|
6591
|
-
}
|
|
6592
|
-
if (trimmed === "exit" || trimmed === "quit" || trimmed === ":q") break;
|
|
6593
|
-
const tokens = tokenize(trimmed);
|
|
6594
|
-
try {
|
|
6595
|
-
await program.parseAsync([
|
|
6596
|
-
"node",
|
|
6597
|
-
"donebear",
|
|
6598
|
-
...tokens
|
|
6599
|
-
]);
|
|
6600
|
-
} catch {}
|
|
6601
|
-
sessionEntries.push(trimmed);
|
|
6602
|
-
rl.prompt();
|
|
6603
|
-
}
|
|
6604
|
-
rl.close();
|
|
6605
|
-
if (sessionEntries.length > 0) try {
|
|
6606
|
-
const payload = {
|
|
6607
|
-
entries: [...await loadHistory(HISTORY_FILE), ...sessionEntries].slice(-MAX_HISTORY),
|
|
6608
|
-
version: 1
|
|
6609
|
-
};
|
|
6610
|
-
await mkdir(CONFIG_DIR, {
|
|
6611
|
-
mode: 448,
|
|
6612
|
-
recursive: true
|
|
6613
|
-
});
|
|
6614
|
-
await writeFile(HISTORY_FILE, `${JSON.stringify(payload, null, 2)}\n`, {
|
|
6615
|
-
encoding: "utf-8",
|
|
6616
|
-
mode: 384
|
|
6617
|
-
});
|
|
6618
|
-
} catch {}
|
|
6619
|
-
};
|
|
6620
|
-
//#endregion
|
|
6621
6623
|
//#region src/commands/label.ts
|
|
6622
6624
|
const sortLabels = (labels, sort) => {
|
|
6623
6625
|
const sorted = [...labels];
|
|
@@ -7091,17 +7093,23 @@ const runProjectAdd = async (context, name, options) => {
|
|
|
7091
7093
|
token: commandContext.token,
|
|
7092
7094
|
workspaceRef: normalizeWorkspaceRef(options.workspace)
|
|
7093
7095
|
});
|
|
7094
|
-
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();
|
|
7095
7103
|
const now = Date.now();
|
|
7096
7104
|
const key = generateProjectSlug(normalizedName, projectId);
|
|
7097
7105
|
await mutateModel(context, {
|
|
7098
7106
|
baseUrl: commandContext.apiBaseUrl,
|
|
7099
7107
|
request: {
|
|
7100
|
-
batchId: randomUUID(),
|
|
7108
|
+
batchId: idempotencyId("batch") ?? randomUUID(),
|
|
7101
7109
|
transactions: [{
|
|
7102
7110
|
action: "INSERT",
|
|
7103
|
-
clientId: commandContext.localContext.clientId,
|
|
7104
|
-
clientTxId: randomUUID(),
|
|
7111
|
+
clientId: options.idempotencyKey ? "cli-idem" : commandContext.localContext.clientId,
|
|
7112
|
+
clientTxId: idempotencyId("tx") ?? randomUUID(),
|
|
7105
7113
|
modelId: projectId,
|
|
7106
7114
|
modelName: "Project",
|
|
7107
7115
|
payload: {
|
|
@@ -7268,7 +7276,7 @@ Agent tip: run "donebear spec project add" for full option reference
|
|
|
7268
7276
|
await runProjectShow(context, projectRef, { workspace: workspaceOverride ?? normalizeWorkspaceRef(options.workspace) ?? void 0 });
|
|
7269
7277
|
});
|
|
7270
7278
|
});
|
|
7271
|
-
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) => {
|
|
7272
7280
|
const context = contextFromCommand(command);
|
|
7273
7281
|
await runWithErrorHandling(context, async () => {
|
|
7274
7282
|
await runProjectAdd(context, name, {
|
|
@@ -7387,8 +7395,8 @@ const filterTasks = (tasks, options) => {
|
|
|
7387
7395
|
if (query.length === 0) return viewFiltered;
|
|
7388
7396
|
return viewFiltered.filter((task) => {
|
|
7389
7397
|
const title = task.title.toLowerCase();
|
|
7390
|
-
const
|
|
7391
|
-
return title.includes(query) ||
|
|
7398
|
+
const description = task.description?.toLowerCase() ?? "";
|
|
7399
|
+
return title.includes(query) || description.includes(query);
|
|
7392
7400
|
});
|
|
7393
7401
|
};
|
|
7394
7402
|
const loadTasksForScope = (context, scope, apiOptions) => {
|
|
@@ -7469,7 +7477,7 @@ const runTaskShow = async (context, taskRef, options, workspaceOverride) => {
|
|
|
7469
7477
|
if (task.projectId) writeInfo(context, `projectId: ${task.projectId}`);
|
|
7470
7478
|
if (task.teamId) writeInfo(context, `teamId: ${task.teamId}`);
|
|
7471
7479
|
if (task.assigneeId) writeInfo(context, `assigneeId: ${task.assigneeId}`);
|
|
7472
|
-
if (task.description) writeInfo(context, `
|
|
7480
|
+
if (task.description) writeInfo(context, `description: ${task.description}`);
|
|
7473
7481
|
};
|
|
7474
7482
|
const resolveTitle = async (context, rawTitle) => {
|
|
7475
7483
|
if (rawTitle) {
|
|
@@ -7480,20 +7488,28 @@ const resolveTitle = async (context, rawTitle) => {
|
|
|
7480
7488
|
if (prompted) return prompted;
|
|
7481
7489
|
throw new CliError("Task title is required.", EXIT_CODES.VALIDATION, "Provide a title as an argument: donebear task add \"My task\"");
|
|
7482
7490
|
};
|
|
7483
|
-
const
|
|
7484
|
-
if (typeof
|
|
7485
|
-
|
|
7486
|
-
|
|
7491
|
+
const resolveDescription = async (context, flagDescription) => {
|
|
7492
|
+
if (typeof flagDescription !== "string") return null;
|
|
7493
|
+
if (flagDescription === "-") {
|
|
7494
|
+
const piped = await readStdin();
|
|
7495
|
+
if (!piped) {
|
|
7496
|
+
writeWarning(context, "No description received on stdin (--description -); task created without a description.");
|
|
7497
|
+
return null;
|
|
7498
|
+
}
|
|
7499
|
+
return piped;
|
|
7487
7500
|
}
|
|
7488
|
-
const
|
|
7489
|
-
if (
|
|
7490
|
-
|
|
7501
|
+
const trimmed = flagDescription.trim();
|
|
7502
|
+
if (trimmed.length === 0) {
|
|
7503
|
+
writeWarning(context, "Empty --description value; task created without a description.");
|
|
7504
|
+
return null;
|
|
7505
|
+
}
|
|
7506
|
+
return trimmed;
|
|
7491
7507
|
};
|
|
7492
7508
|
const runTaskAdd = async (context, title, options, workspaceOverride) => {
|
|
7493
7509
|
const commandContext = await resolveCommandContext(context);
|
|
7494
7510
|
const userId = await requireUserId(context, commandContext);
|
|
7495
7511
|
const normalizedTitle = await resolveTitle(context, title);
|
|
7496
|
-
const
|
|
7512
|
+
const description = await resolveDescription(context, options.description);
|
|
7497
7513
|
const { workspace, autoSelected } = await resolveWorkspace(context, {
|
|
7498
7514
|
apiBaseUrl: commandContext.apiBaseUrl,
|
|
7499
7515
|
storedWorkspaceId: commandContext.localContext.workspaceId,
|
|
@@ -7509,7 +7525,13 @@ const runTaskAdd = async (context, title, options, workspaceOverride) => {
|
|
|
7509
7525
|
token: commandContext.token,
|
|
7510
7526
|
workspaceId: workspace.id
|
|
7511
7527
|
}).then((teams) => resolveEntityId(teams, options.team, "Team")) : Promise.resolve(null)]);
|
|
7512
|
-
const
|
|
7528
|
+
const idempotencyId = (part) => options.idempotencyKey ? createIdempotentMutationId({
|
|
7529
|
+
key: options.idempotencyKey,
|
|
7530
|
+
modelName: "Task",
|
|
7531
|
+
part,
|
|
7532
|
+
workspaceId: workspace.id
|
|
7533
|
+
}) : null;
|
|
7534
|
+
const taskId = idempotencyId("model") ?? randomUUID();
|
|
7513
7535
|
const now = Date.now();
|
|
7514
7536
|
const view = parseTaskView(options.when);
|
|
7515
7537
|
const startFields = buildTaskStartFields(view);
|
|
@@ -7517,11 +7539,11 @@ const runTaskAdd = async (context, title, options, workspaceOverride) => {
|
|
|
7517
7539
|
await mutateModel(context, {
|
|
7518
7540
|
baseUrl: commandContext.apiBaseUrl,
|
|
7519
7541
|
request: {
|
|
7520
|
-
batchId: randomUUID(),
|
|
7542
|
+
batchId: idempotencyId("batch") ?? randomUUID(),
|
|
7521
7543
|
transactions: [{
|
|
7522
7544
|
action: "INSERT",
|
|
7523
|
-
clientId: commandContext.localContext.clientId,
|
|
7524
|
-
clientTxId: randomUUID(),
|
|
7545
|
+
clientId: options.idempotencyKey ? "cli-idem" : commandContext.localContext.clientId,
|
|
7546
|
+
clientTxId: idempotencyId("tx") ?? randomUUID(),
|
|
7525
7547
|
modelId: taskId,
|
|
7526
7548
|
modelName: "Task",
|
|
7527
7549
|
payload: {
|
|
@@ -7530,7 +7552,7 @@ const runTaskAdd = async (context, title, options, workspaceOverride) => {
|
|
|
7530
7552
|
createdAt: now,
|
|
7531
7553
|
creatorId: userId,
|
|
7532
7554
|
deadlineAt: deadlineEpoch,
|
|
7533
|
-
description
|
|
7555
|
+
description,
|
|
7534
7556
|
key: taskId.replaceAll("-", "").slice(0, 8),
|
|
7535
7557
|
projectId,
|
|
7536
7558
|
sortOrder: 0,
|
|
@@ -7550,6 +7572,7 @@ const runTaskAdd = async (context, title, options, workspaceOverride) => {
|
|
|
7550
7572
|
autoSelected,
|
|
7551
7573
|
created: true,
|
|
7552
7574
|
deadlineAt: deadlineEpoch,
|
|
7575
|
+
description,
|
|
7553
7576
|
id: taskId,
|
|
7554
7577
|
projectId,
|
|
7555
7578
|
teamId,
|
|
@@ -7563,6 +7586,7 @@ const runTaskAdd = async (context, title, options, workspaceOverride) => {
|
|
|
7563
7586
|
if (autoSelected) writeInfo(context, `Auto-selected workspace: ${workspace.name}`);
|
|
7564
7587
|
writeSuccess(context, `Added task: ${normalizedTitle}`);
|
|
7565
7588
|
writeInfo(context, `id: ${taskId}`);
|
|
7589
|
+
if (description) writeInfo(context, `description: ${description}`);
|
|
7566
7590
|
};
|
|
7567
7591
|
const runTaskMutation = async (context, taskRef, payload) => {
|
|
7568
7592
|
const commandContext = await resolveCommandContext(context);
|
|
@@ -7630,7 +7654,7 @@ const resolveTeamId = async (context, commandContext, teamRef, workspaceRef) =>
|
|
|
7630
7654
|
}), teamRef, "Team");
|
|
7631
7655
|
};
|
|
7632
7656
|
const validateTaskEditOptions = (options) => {
|
|
7633
|
-
if (options.
|
|
7657
|
+
if (options.clearDescription && typeof options.description === "string") throw new Error("Use either --description or --clear-description, not both.");
|
|
7634
7658
|
if (options.clearDeadline && typeof options.deadline === "string") throw new Error("Use either --deadline or --clear-deadline, not both.");
|
|
7635
7659
|
if (options.clearProject && typeof options.project === "string") throw new Error("Use either --project or --clear-project, not both.");
|
|
7636
7660
|
if (options.clearTeam && typeof options.team === "string") throw new Error("Use either --team or --clear-team, not both.");
|
|
@@ -7641,10 +7665,10 @@ const applyScalarEdits = (updates, options) => {
|
|
|
7641
7665
|
if (title.length === 0) throw new Error("Task title cannot be empty.");
|
|
7642
7666
|
updates.title = title;
|
|
7643
7667
|
}
|
|
7644
|
-
if (options.
|
|
7645
|
-
else if (typeof options.
|
|
7646
|
-
const
|
|
7647
|
-
updates.description =
|
|
7668
|
+
if (options.clearDescription) updates.description = null;
|
|
7669
|
+
else if (typeof options.description === "string") {
|
|
7670
|
+
const description = options.description.trim();
|
|
7671
|
+
updates.description = description.length > 0 ? description : null;
|
|
7648
7672
|
}
|
|
7649
7673
|
if (typeof options.when === "string") Object.assign(updates, buildTaskStartFields(parseTaskView(options.when)));
|
|
7650
7674
|
if (options.clearDeadline) updates.deadlineAt = null;
|
|
@@ -7660,7 +7684,7 @@ const runTaskEdit = async (context, taskRef, options, workspaceOverride) => {
|
|
|
7660
7684
|
else if (typeof options.project === "string") updates.projectId = await resolveProjectId(context, commandContext, options.project, wsRef);
|
|
7661
7685
|
if (options.clearTeam) updates.teamId = null;
|
|
7662
7686
|
else if (typeof options.team === "string") updates.teamId = await resolveTeamId(context, commandContext, options.team, wsRef);
|
|
7663
|
-
if (Object.keys(updates).length === 0) throw new Error("No updates provided. Use --title, --
|
|
7687
|
+
if (Object.keys(updates).length === 0) throw new Error("No updates provided. Use --title, --description, --clear-description, --when, --deadline, --clear-deadline, --project, --clear-project, --team, or --clear-team.");
|
|
7664
7688
|
updates.updatedAt = Date.now();
|
|
7665
7689
|
await runTaskMutation(context, taskRef, {
|
|
7666
7690
|
action: "UPDATE",
|
|
@@ -7767,7 +7791,7 @@ const runTaskRandom = async (context, options, workspaceOverride) => {
|
|
|
7767
7791
|
writeInfo(context, `start: ${task.start}`);
|
|
7768
7792
|
writeInfo(context, `startBucket: ${task.startBucket}`);
|
|
7769
7793
|
writeInfo(context, `deadline: ${formatDateOnlyEpoch(task.deadlineAt) ?? "(none)"}`);
|
|
7770
|
-
if (task.description) writeInfo(context, `
|
|
7794
|
+
if (task.description) writeInfo(context, `description: ${task.description}`);
|
|
7771
7795
|
};
|
|
7772
7796
|
const resolveChecklistItemReference = async (context, commandContext, taskId, itemRef) => {
|
|
7773
7797
|
const trimmed = itemRef.trim();
|
|
@@ -7838,7 +7862,7 @@ Agent tip: run "donebear spec task add" for full option reference
|
|
|
7838
7862
|
}, workspaceOverride);
|
|
7839
7863
|
});
|
|
7840
7864
|
});
|
|
7841
|
-
task.command("list").alias("ls").description("List tasks").option("-w, --workspace <workspace>", "Workspace id, slug, or exact name").option("-s, --state <state>", "open|done|archived|all", "open").option("-n, --limit <count>", "Maximum number of tasks", (value) => parsePositiveInteger(value, "Limit"), DEFAULT_TASK_LIST_LIMIT).option("-q, --search <query>", "Filter by title or
|
|
7865
|
+
task.command("list").alias("ls").description("List tasks").option("-w, --workspace <workspace>", "Workspace id, slug, or exact name").option("-s, --state <state>", "open|done|archived|all", "open").option("-n, --limit <count>", "Maximum number of tasks", (value) => parsePositiveInteger(value, "Limit"), DEFAULT_TASK_LIST_LIMIT).option("-q, --search <query>", "Filter by title or description").option("-v, --view <view>", "Filter by view (inbox|anytime|today|upcoming|someday|logbook|trash)").addHelpText("after", `
|
|
7842
7866
|
Examples:
|
|
7843
7867
|
donebear task list --json
|
|
7844
7868
|
donebear task list --state done --json
|
|
@@ -7859,7 +7883,7 @@ Views: inbox | anytime | today | upcoming | someday | logbook | trash
|
|
|
7859
7883
|
await runTaskShow(context, taskId, options, workspaceOverride);
|
|
7860
7884
|
});
|
|
7861
7885
|
});
|
|
7862
|
-
task.command("read").description("Print the description
|
|
7886
|
+
task.command("read").description("Print the description of a task").argument("<id>", "Task id or unique prefix").option("-w, --workspace <workspace>", "Workspace id, slug, or exact name").action(async (taskId, options, command) => {
|
|
7863
7887
|
const context = contextFromCommand(command);
|
|
7864
7888
|
await runWithErrorHandling(context, async () => {
|
|
7865
7889
|
await runTaskRead(context, taskId, options, workspaceOverride);
|
|
@@ -7883,15 +7907,15 @@ Views: inbox | anytime | today | upcoming | someday | logbook | trash
|
|
|
7883
7907
|
await runTaskRandom(context, options, workspaceOverride);
|
|
7884
7908
|
});
|
|
7885
7909
|
});
|
|
7886
|
-
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("-
|
|
7910
|
+
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("-d, --description <text>", "Task description (use --description - to read from piped 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", `
|
|
7887
7911
|
Examples:
|
|
7888
7912
|
donebear task add "Fix login bug" --when today --json
|
|
7889
|
-
donebear task add "Write tests" --deadline
|
|
7890
|
-
donebear task add "Review PR" --when anytime --
|
|
7891
|
-
echo "Multi-line
|
|
7913
|
+
donebear task add "Write tests" --deadline 2030-03-15 --project PROJ --json
|
|
7914
|
+
donebear task add "Review PR" --when anytime --description "Check for XSS" --json
|
|
7915
|
+
echo "Multi-line description" | donebear task add "My task" --description -
|
|
7892
7916
|
donebear task add # prompts for title interactively
|
|
7893
7917
|
|
|
7894
|
-
Date format: YYYY-MM-DD (e.g.
|
|
7918
|
+
Date format: YYYY-MM-DD (e.g. 2030-03-15)
|
|
7895
7919
|
View (--when): inbox | anytime | today | upcoming | someday
|
|
7896
7920
|
`).action(async (title, options, command) => {
|
|
7897
7921
|
const context = contextFromCommand(command);
|
|
@@ -7899,7 +7923,7 @@ View (--when): inbox | anytime | today | upcoming | someday
|
|
|
7899
7923
|
await runTaskAdd(context, title, options, workspaceOverride);
|
|
7900
7924
|
});
|
|
7901
7925
|
});
|
|
7902
|
-
task.command("edit").description("Update task fields").argument("<id>", "Task id or unique prefix").option("-w, --workspace <workspace>", "Workspace id, slug, or exact name").option("--title <title>", "Replace task title").option("--
|
|
7926
|
+
task.command("edit").description("Update task fields").argument("<id>", "Task id or unique prefix").option("-w, --workspace <workspace>", "Workspace id, slug, or exact name").option("--title <title>", "Replace task title").option("--description <text>", "Replace task description (empty string clears)").option("--clear-description", "Clear task description").option("--when <view>", "Set view bucket (inbox|anytime|today|upcoming|someday)").option("--deadline <date>", "Set deadline (YYYY-MM-DD or ISO datetime)").option("--clear-deadline", "Clear deadline").option("--project <key-or-id>", "Set project (key or id)").option("--clear-project", "Remove project assignment").option("--team <key-or-id>", "Set team (key or id)").option("--clear-team", "Remove team assignment").action(async (taskId, options, command) => {
|
|
7903
7927
|
const context = contextFromCommand(command);
|
|
7904
7928
|
await runWithErrorHandling(context, async () => {
|
|
7905
7929
|
await runTaskEdit(context, taskId, options, workspaceOverride);
|
|
@@ -8216,7 +8240,7 @@ const runSearch = async (context, query, options, workspaceOverride) => {
|
|
|
8216
8240
|
if (context.copy) copyToClipboard(taskListLines(limited).join("\n"));
|
|
8217
8241
|
};
|
|
8218
8242
|
const registerSearchCommand = (program, workspaceOverride) => {
|
|
8219
|
-
program.command("search").description("Search tasks by title or
|
|
8243
|
+
program.command("search").description("Search tasks by title or description").argument("[query]", "Search query (prompted interactively if omitted)").option("-w, --workspace <workspace>", "Workspace id, slug, or exact name").option("-s, --state <state>", "open|done|archived|all", "all").option("-n, --limit <count>", "Maximum number of results", (value) => parsePositiveInteger(value, "Limit"), DEFAULT_SEARCH_LIMIT).addHelpText("after", `
|
|
8220
8244
|
Examples:
|
|
8221
8245
|
donebear search "meeting"
|
|
8222
8246
|
donebear search "launch" --state open
|
|
@@ -8271,9 +8295,15 @@ const OVERVIEW = {
|
|
|
8271
8295
|
subcommands: ["things"]
|
|
8272
8296
|
},
|
|
8273
8297
|
label: {
|
|
8274
|
-
description: "
|
|
8298
|
+
description: "Manage workspace labels and task label assignments",
|
|
8275
8299
|
quick_example: "donebear label list --json",
|
|
8276
|
-
subcommands: [
|
|
8300
|
+
subcommands: [
|
|
8301
|
+
"list",
|
|
8302
|
+
"show",
|
|
8303
|
+
"create",
|
|
8304
|
+
"add",
|
|
8305
|
+
"remove"
|
|
8306
|
+
]
|
|
8277
8307
|
},
|
|
8278
8308
|
project: {
|
|
8279
8309
|
description: "Manage projects",
|
|
@@ -8311,6 +8341,7 @@ const OVERVIEW = {
|
|
|
8311
8341
|
"read",
|
|
8312
8342
|
"append",
|
|
8313
8343
|
"prepend",
|
|
8344
|
+
"random",
|
|
8314
8345
|
"checklist"
|
|
8315
8346
|
]
|
|
8316
8347
|
},
|
|
@@ -8436,10 +8467,21 @@ const COMMAND_SPECS = {
|
|
|
8436
8467
|
user: "{ id, email }"
|
|
8437
8468
|
},
|
|
8438
8469
|
non_interactive: "For CI/scripts, set DONEBEAR_TOKEN=<token> instead of using auth login.",
|
|
8439
|
-
options: {
|
|
8440
|
-
|
|
8441
|
-
|
|
8442
|
-
|
|
8470
|
+
options: {
|
|
8471
|
+
"--no-open": {
|
|
8472
|
+
description: "Print auth URL instead of opening browser",
|
|
8473
|
+
type: "boolean"
|
|
8474
|
+
},
|
|
8475
|
+
"--port": {
|
|
8476
|
+
default: 8787,
|
|
8477
|
+
description: "Loopback callback port for the OAuth redirect",
|
|
8478
|
+
type: "number"
|
|
8479
|
+
},
|
|
8480
|
+
"--timeout": {
|
|
8481
|
+
description: "Seconds to wait for the OAuth callback",
|
|
8482
|
+
type: "number"
|
|
8483
|
+
}
|
|
8484
|
+
},
|
|
8443
8485
|
usage: "donebear auth login [options]"
|
|
8444
8486
|
},
|
|
8445
8487
|
"auth status": {
|
|
@@ -8561,6 +8603,10 @@ const COMMAND_SPECS = {
|
|
|
8561
8603
|
enum: ["note", "none"],
|
|
8562
8604
|
type: "string"
|
|
8563
8605
|
},
|
|
8606
|
+
"--skip-teams": {
|
|
8607
|
+
description: "Skip importing Things Areas as Teams and TeamProject links",
|
|
8608
|
+
type: "boolean"
|
|
8609
|
+
},
|
|
8564
8610
|
"--workspace": {
|
|
8565
8611
|
description: "Workspace id, slug, or name",
|
|
8566
8612
|
type: "string"
|
|
@@ -8576,7 +8622,15 @@ const COMMAND_SPECS = {
|
|
|
8576
8622
|
ok: "boolean",
|
|
8577
8623
|
workspace: "WorkspaceSummary"
|
|
8578
8624
|
},
|
|
8579
|
-
options: {
|
|
8625
|
+
options: {
|
|
8626
|
+
"--sort": {
|
|
8627
|
+
default: "alpha",
|
|
8628
|
+
description: "Sort order: alpha|count",
|
|
8629
|
+
enum: ["alpha", "count"],
|
|
8630
|
+
type: "string"
|
|
8631
|
+
},
|
|
8632
|
+
"--workspace": { type: "string" }
|
|
8633
|
+
},
|
|
8580
8634
|
usage: "donebear label list [options]"
|
|
8581
8635
|
},
|
|
8582
8636
|
"project add": {
|
|
@@ -8595,6 +8649,10 @@ const COMMAND_SPECS = {
|
|
|
8595
8649
|
notes: "URL slug is auto-generated from name (e.g. 'Q2 Launch' → 'q2-launch-a1b2c3d4').",
|
|
8596
8650
|
options: {
|
|
8597
8651
|
"--description": { type: "string" },
|
|
8652
|
+
"--idempotency-key": {
|
|
8653
|
+
description: "Stable key making retries safe (repeated calls create only one project)",
|
|
8654
|
+
type: "string"
|
|
8655
|
+
},
|
|
8598
8656
|
"--target-date": {
|
|
8599
8657
|
description: "Target completion date",
|
|
8600
8658
|
format: "YYYY-MM-DD",
|
|
@@ -8633,8 +8691,25 @@ const COMMAND_SPECS = {
|
|
|
8633
8691
|
ok: "boolean"
|
|
8634
8692
|
},
|
|
8635
8693
|
options: {
|
|
8694
|
+
"--clear-description": {
|
|
8695
|
+
description: "Clear the project description",
|
|
8696
|
+
type: "boolean"
|
|
8697
|
+
},
|
|
8698
|
+
"--clear-target-date": {
|
|
8699
|
+
description: "Clear the project target date",
|
|
8700
|
+
type: "boolean"
|
|
8701
|
+
},
|
|
8636
8702
|
"--description": { type: "string" },
|
|
8637
8703
|
"--name": { type: "string" },
|
|
8704
|
+
"--status": {
|
|
8705
|
+
description: "Set status",
|
|
8706
|
+
enum: [
|
|
8707
|
+
"active",
|
|
8708
|
+
"done",
|
|
8709
|
+
"archived"
|
|
8710
|
+
],
|
|
8711
|
+
type: "string"
|
|
8712
|
+
},
|
|
8638
8713
|
"--target-date": {
|
|
8639
8714
|
format: "YYYY-MM-DD",
|
|
8640
8715
|
type: "string"
|
|
@@ -8712,13 +8787,14 @@ const COMMAND_SPECS = {
|
|
|
8712
8787
|
examples: [
|
|
8713
8788
|
"donebear task add \"Fix login bug\" --when today --json",
|
|
8714
8789
|
"donebear task add \"Write tests\" --deadline 2026-03-15 --project PROJ --json",
|
|
8715
|
-
"donebear task add \"Review PR\" --when anytime --
|
|
8716
|
-
"echo \"Multi-line
|
|
8790
|
+
"donebear task add \"Review PR\" --when anytime --description \"Triage for bugs\" --json",
|
|
8791
|
+
"echo \"Multi-line description\" | donebear task add \"My task\" --description -",
|
|
8717
8792
|
"donebear task add # prompts for title interactively in TTY"
|
|
8718
8793
|
],
|
|
8719
8794
|
json_output: {
|
|
8720
8795
|
created: "boolean",
|
|
8721
8796
|
deadlineAt: "number | null",
|
|
8797
|
+
description: "string | null",
|
|
8722
8798
|
id: "string (UUID)",
|
|
8723
8799
|
projectId: "string | null",
|
|
8724
8800
|
teamId: "string | null",
|
|
@@ -8727,15 +8803,19 @@ const COMMAND_SPECS = {
|
|
|
8727
8803
|
workspaceId: "string",
|
|
8728
8804
|
workspaceName: "string"
|
|
8729
8805
|
},
|
|
8730
|
-
notes: "Title is prompted interactively when omitted in TTY.
|
|
8806
|
+
notes: "Title is prompted interactively when omitted in TTY. The description is NOT read from stdin unless you pass --description - explicitly; without --description the task is created with no description (and the saved description is echoed back in the output). Task id accepts full UUID or prefix of >= 4 chars in all other task subcommands.",
|
|
8731
8807
|
options: {
|
|
8732
8808
|
"--deadline": {
|
|
8733
8809
|
description: "Due date",
|
|
8734
8810
|
format: "YYYY-MM-DD",
|
|
8735
8811
|
type: "string"
|
|
8736
8812
|
},
|
|
8737
|
-
"--
|
|
8738
|
-
description: "Body / description text
|
|
8813
|
+
"--description": {
|
|
8814
|
+
description: "Body / description text. Pass --description - to read from piped stdin.",
|
|
8815
|
+
type: "string"
|
|
8816
|
+
},
|
|
8817
|
+
"--idempotency-key": {
|
|
8818
|
+
description: "Stable key making retries safe (repeated calls create only one task)",
|
|
8739
8819
|
type: "string"
|
|
8740
8820
|
},
|
|
8741
8821
|
"--project": {
|
|
@@ -8778,7 +8858,7 @@ const COMMAND_SPECS = {
|
|
|
8778
8858
|
}
|
|
8779
8859
|
},
|
|
8780
8860
|
command: "task append",
|
|
8781
|
-
description: "Append text to task
|
|
8861
|
+
description: "Append text to a task's description",
|
|
8782
8862
|
examples: ["donebear task append abc1 \"Additional context\" --json"],
|
|
8783
8863
|
json_output: {
|
|
8784
8864
|
id: "string",
|
|
@@ -8824,20 +8904,40 @@ const COMMAND_SPECS = {
|
|
|
8824
8904
|
type: "string"
|
|
8825
8905
|
} },
|
|
8826
8906
|
command: "task edit",
|
|
8827
|
-
examples: [
|
|
8907
|
+
examples: [
|
|
8908
|
+
"donebear task edit abc1 --title \"Updated title\" --json",
|
|
8909
|
+
"donebear task edit abc1 --when today --deadline 2030-01-15 --json",
|
|
8910
|
+
"donebear task edit abc1 --clear-deadline --json"
|
|
8911
|
+
],
|
|
8828
8912
|
json_output: {
|
|
8829
8913
|
action: "string",
|
|
8830
8914
|
id: "string",
|
|
8831
8915
|
ok: "boolean"
|
|
8832
8916
|
},
|
|
8833
8917
|
options: {
|
|
8918
|
+
"--clear-deadline": {
|
|
8919
|
+
description: "Clear the due date",
|
|
8920
|
+
type: "boolean"
|
|
8921
|
+
},
|
|
8922
|
+
"--clear-description": {
|
|
8923
|
+
description: "Clear the description",
|
|
8924
|
+
type: "boolean"
|
|
8925
|
+
},
|
|
8926
|
+
"--clear-project": {
|
|
8927
|
+
description: "Remove the task from its project",
|
|
8928
|
+
type: "boolean"
|
|
8929
|
+
},
|
|
8930
|
+
"--clear-team": {
|
|
8931
|
+
description: "Remove the task from its team",
|
|
8932
|
+
type: "boolean"
|
|
8933
|
+
},
|
|
8834
8934
|
"--deadline": {
|
|
8835
|
-
description: "Set due date
|
|
8935
|
+
description: "Set due date",
|
|
8836
8936
|
format: "YYYY-MM-DD",
|
|
8837
8937
|
type: "string"
|
|
8838
8938
|
},
|
|
8839
|
-
"--
|
|
8840
|
-
description: "Replace
|
|
8939
|
+
"--description": {
|
|
8940
|
+
description: "Replace the task description",
|
|
8841
8941
|
type: "string"
|
|
8842
8942
|
},
|
|
8843
8943
|
"--project": {
|
|
@@ -8935,7 +9035,7 @@ const COMMAND_SPECS = {
|
|
|
8935
9035
|
}
|
|
8936
9036
|
},
|
|
8937
9037
|
command: "task prepend",
|
|
8938
|
-
description: "Prepend text to task
|
|
9038
|
+
description: "Prepend text to a task's description",
|
|
8939
9039
|
examples: ["donebear task prepend abc1 \"URGENT: \" --json"],
|
|
8940
9040
|
json_output: {
|
|
8941
9041
|
id: "string",
|
|
@@ -8951,7 +9051,7 @@ const COMMAND_SPECS = {
|
|
|
8951
9051
|
type: "string"
|
|
8952
9052
|
} },
|
|
8953
9053
|
command: "task read",
|
|
8954
|
-
description: "Print task description
|
|
9054
|
+
description: "Print task description as plain text",
|
|
8955
9055
|
examples: ["donebear task read abc1"],
|
|
8956
9056
|
options: { "--workspace": { type: "string" } },
|
|
8957
9057
|
usage: "donebear task read <id> [options]"
|
|
@@ -9098,9 +9198,12 @@ const TOP_LEVEL_SPECS = {
|
|
|
9098
9198
|
},
|
|
9099
9199
|
label: {
|
|
9100
9200
|
command: "label",
|
|
9101
|
-
description: "
|
|
9201
|
+
description: "Manage workspace labels and task label assignments",
|
|
9102
9202
|
subcommands: {
|
|
9203
|
+
add: "Add a label to a task",
|
|
9204
|
+
create: "Create a new label",
|
|
9103
9205
|
list: "List all labels",
|
|
9206
|
+
remove: "Remove a label from a task",
|
|
9104
9207
|
show: "Get label details"
|
|
9105
9208
|
}
|
|
9106
9209
|
},
|
|
@@ -9124,14 +9227,15 @@ const TOP_LEVEL_SPECS = {
|
|
|
9124
9227
|
notes: "Task id accepts full UUID or a prefix of >= 4 chars.",
|
|
9125
9228
|
subcommands: {
|
|
9126
9229
|
add: "Create a new task",
|
|
9127
|
-
append: "Append text to task
|
|
9230
|
+
append: "Append text to a task's description",
|
|
9128
9231
|
archive: "Archive a task",
|
|
9129
9232
|
checklist: "Manage task checklist items",
|
|
9130
9233
|
done: "Mark a task as complete",
|
|
9131
9234
|
edit: "Update task fields",
|
|
9132
9235
|
list: "List tasks (--state, --view, --search, --limit)",
|
|
9133
|
-
prepend: "Prepend text to task
|
|
9134
|
-
|
|
9236
|
+
prepend: "Prepend text to a task's description",
|
|
9237
|
+
random: "Pick a random task (--state to filter)",
|
|
9238
|
+
read: "Print task description as plain text",
|
|
9135
9239
|
reopen: "Reopen a completed task",
|
|
9136
9240
|
show: "Get task details by id",
|
|
9137
9241
|
unarchive: "Restore an archived task"
|
|
@@ -9757,30 +9861,8 @@ Examples:
|
|
|
9757
9861
|
});
|
|
9758
9862
|
};
|
|
9759
9863
|
//#endregion
|
|
9760
|
-
//#region src/
|
|
9761
|
-
const
|
|
9762
|
-
const WORKSPACE_PREFIX_REGEX = /^workspace=(.+)$/iu;
|
|
9763
|
-
const extractWorkspaceTarget = (argv) => {
|
|
9764
|
-
const processedArgv = [...argv];
|
|
9765
|
-
const firstArg = processedArgv[2];
|
|
9766
|
-
if (firstArg) {
|
|
9767
|
-
const match = WORKSPACE_PREFIX_REGEX.exec(firstArg);
|
|
9768
|
-
if (match?.[1]) {
|
|
9769
|
-
processedArgv.splice(2, 1);
|
|
9770
|
-
return {
|
|
9771
|
-
processedArgv,
|
|
9772
|
-
workspaceOverride: match[1].trim()
|
|
9773
|
-
};
|
|
9774
|
-
}
|
|
9775
|
-
}
|
|
9776
|
-
return {
|
|
9777
|
-
processedArgv,
|
|
9778
|
-
workspaceOverride: null
|
|
9779
|
-
};
|
|
9780
|
-
};
|
|
9781
|
-
const main = async () => {
|
|
9782
|
-
loadCliEnvironmentFiles();
|
|
9783
|
-
const { processedArgv, workspaceOverride } = extractWorkspaceTarget(process.argv);
|
|
9864
|
+
//#region src/program.ts
|
|
9865
|
+
const buildProgram = (workspaceOverride = null) => {
|
|
9784
9866
|
const program = new Command();
|
|
9785
9867
|
program.name(CLI_NAME).description("Done Bear CLI for auth, workspace, and task workflows").version(packageManifest.version, "-v, --version").showHelpAfterError().showSuggestionAfterError();
|
|
9786
9868
|
addGlobalOptions(program);
|
|
@@ -9824,6 +9906,33 @@ Filtering & output:
|
|
|
9824
9906
|
registerSearchCommand(program, workspaceOverride);
|
|
9825
9907
|
registerHistoryCommand(program, workspaceOverride);
|
|
9826
9908
|
registerWhoAmICommand(program);
|
|
9909
|
+
return program;
|
|
9910
|
+
};
|
|
9911
|
+
//#endregion
|
|
9912
|
+
//#region src/cli.ts
|
|
9913
|
+
const WORKSPACE_PREFIX_REGEX = /^workspace=(.+)$/iu;
|
|
9914
|
+
const extractWorkspaceTarget = (argv) => {
|
|
9915
|
+
const processedArgv = [...argv];
|
|
9916
|
+
const firstArg = processedArgv[2];
|
|
9917
|
+
if (firstArg) {
|
|
9918
|
+
const match = WORKSPACE_PREFIX_REGEX.exec(firstArg);
|
|
9919
|
+
if (match?.[1]) {
|
|
9920
|
+
processedArgv.splice(2, 1);
|
|
9921
|
+
return {
|
|
9922
|
+
processedArgv,
|
|
9923
|
+
workspaceOverride: match[1].trim()
|
|
9924
|
+
};
|
|
9925
|
+
}
|
|
9926
|
+
}
|
|
9927
|
+
return {
|
|
9928
|
+
processedArgv,
|
|
9929
|
+
workspaceOverride: null
|
|
9930
|
+
};
|
|
9931
|
+
};
|
|
9932
|
+
const main = async () => {
|
|
9933
|
+
loadCliEnvironmentFiles();
|
|
9934
|
+
const { processedArgv, workspaceOverride } = extractWorkspaceTarget(process.argv);
|
|
9935
|
+
const program = buildProgram(workspaceOverride);
|
|
9827
9936
|
const hasUserArgs = processedArgv.length <= 2;
|
|
9828
9937
|
const isTTY = process.stdin.isTTY === true;
|
|
9829
9938
|
if (hasUserArgs && isTTY) {
|