donebear 0.3.8 → 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 +402 -304
- 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";
|
|
@@ -385,136 +588,24 @@ const parseDateOnlyString = (value) => {
|
|
|
385
588
|
};
|
|
386
589
|
return isValidDateOnlyParts(parts) ? parts : null;
|
|
387
590
|
};
|
|
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;
|
|
591
|
+
const parseApiDateOnlyToEpoch = (value) => {
|
|
592
|
+
const parts = parseDateOnlyString(value);
|
|
593
|
+
return parts ? dateOnlyPartsToEpoch(parts) : null;
|
|
503
594
|
};
|
|
504
|
-
const
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
} catch {
|
|
511
|
-
execSync("xsel --clipboard --input", { input: text });
|
|
512
|
-
}
|
|
513
|
-
return true;
|
|
514
|
-
} catch {
|
|
515
|
-
return false;
|
|
516
|
-
}
|
|
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;
|
|
517
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, {
|
|
@@ -7509,7 +7517,13 @@ const runTaskAdd = async (context, title, options, workspaceOverride) => {
|
|
|
7509
7517
|
token: commandContext.token,
|
|
7510
7518
|
workspaceId: workspace.id
|
|
7511
7519
|
}).then((teams) => resolveEntityId(teams, options.team, "Team")) : Promise.resolve(null)]);
|
|
7512
|
-
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();
|
|
7513
7527
|
const now = Date.now();
|
|
7514
7528
|
const view = parseTaskView(options.when);
|
|
7515
7529
|
const startFields = buildTaskStartFields(view);
|
|
@@ -7517,11 +7531,11 @@ const runTaskAdd = async (context, title, options, workspaceOverride) => {
|
|
|
7517
7531
|
await mutateModel(context, {
|
|
7518
7532
|
baseUrl: commandContext.apiBaseUrl,
|
|
7519
7533
|
request: {
|
|
7520
|
-
batchId: randomUUID(),
|
|
7534
|
+
batchId: idempotencyId("batch") ?? randomUUID(),
|
|
7521
7535
|
transactions: [{
|
|
7522
7536
|
action: "INSERT",
|
|
7523
|
-
clientId: commandContext.localContext.clientId,
|
|
7524
|
-
clientTxId: randomUUID(),
|
|
7537
|
+
clientId: options.idempotencyKey ? "cli-idem" : commandContext.localContext.clientId,
|
|
7538
|
+
clientTxId: idempotencyId("tx") ?? randomUUID(),
|
|
7525
7539
|
modelId: taskId,
|
|
7526
7540
|
modelName: "Task",
|
|
7527
7541
|
payload: {
|
|
@@ -7883,15 +7897,15 @@ Views: inbox | anytime | today | upcoming | someday | logbook | trash
|
|
|
7883
7897
|
await runTaskRandom(context, options, workspaceOverride);
|
|
7884
7898
|
});
|
|
7885
7899
|
});
|
|
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("-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", `
|
|
7887
7901
|
Examples:
|
|
7888
7902
|
donebear task add "Fix login bug" --when today --json
|
|
7889
|
-
donebear task add "Write tests" --deadline
|
|
7903
|
+
donebear task add "Write tests" --deadline 2030-03-15 --project PROJ --json
|
|
7890
7904
|
donebear task add "Review PR" --when anytime --notes "Check for XSS" --json
|
|
7891
7905
|
echo "Multi-line notes" | donebear task add "My task"
|
|
7892
7906
|
donebear task add # prompts for title interactively
|
|
7893
7907
|
|
|
7894
|
-
Date format: YYYY-MM-DD (e.g.
|
|
7908
|
+
Date format: YYYY-MM-DD (e.g. 2030-03-15)
|
|
7895
7909
|
View (--when): inbox | anytime | today | upcoming | someday
|
|
7896
7910
|
`).action(async (title, options, command) => {
|
|
7897
7911
|
const context = contextFromCommand(command);
|
|
@@ -8271,9 +8285,15 @@ const OVERVIEW = {
|
|
|
8271
8285
|
subcommands: ["things"]
|
|
8272
8286
|
},
|
|
8273
8287
|
label: {
|
|
8274
|
-
description: "
|
|
8288
|
+
description: "Manage workspace labels and task label assignments",
|
|
8275
8289
|
quick_example: "donebear label list --json",
|
|
8276
|
-
subcommands: [
|
|
8290
|
+
subcommands: [
|
|
8291
|
+
"list",
|
|
8292
|
+
"show",
|
|
8293
|
+
"create",
|
|
8294
|
+
"add",
|
|
8295
|
+
"remove"
|
|
8296
|
+
]
|
|
8277
8297
|
},
|
|
8278
8298
|
project: {
|
|
8279
8299
|
description: "Manage projects",
|
|
@@ -8311,6 +8331,7 @@ const OVERVIEW = {
|
|
|
8311
8331
|
"read",
|
|
8312
8332
|
"append",
|
|
8313
8333
|
"prepend",
|
|
8334
|
+
"random",
|
|
8314
8335
|
"checklist"
|
|
8315
8336
|
]
|
|
8316
8337
|
},
|
|
@@ -8436,10 +8457,21 @@ const COMMAND_SPECS = {
|
|
|
8436
8457
|
user: "{ id, email }"
|
|
8437
8458
|
},
|
|
8438
8459
|
non_interactive: "For CI/scripts, set DONEBEAR_TOKEN=<token> instead of using auth login.",
|
|
8439
|
-
options: {
|
|
8440
|
-
|
|
8441
|
-
|
|
8442
|
-
|
|
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
|
+
},
|
|
8443
8475
|
usage: "donebear auth login [options]"
|
|
8444
8476
|
},
|
|
8445
8477
|
"auth status": {
|
|
@@ -8561,6 +8593,10 @@ const COMMAND_SPECS = {
|
|
|
8561
8593
|
enum: ["note", "none"],
|
|
8562
8594
|
type: "string"
|
|
8563
8595
|
},
|
|
8596
|
+
"--skip-teams": {
|
|
8597
|
+
description: "Skip importing Things Areas as Teams and TeamProject links",
|
|
8598
|
+
type: "boolean"
|
|
8599
|
+
},
|
|
8564
8600
|
"--workspace": {
|
|
8565
8601
|
description: "Workspace id, slug, or name",
|
|
8566
8602
|
type: "string"
|
|
@@ -8576,7 +8612,15 @@ const COMMAND_SPECS = {
|
|
|
8576
8612
|
ok: "boolean",
|
|
8577
8613
|
workspace: "WorkspaceSummary"
|
|
8578
8614
|
},
|
|
8579
|
-
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
|
+
},
|
|
8580
8624
|
usage: "donebear label list [options]"
|
|
8581
8625
|
},
|
|
8582
8626
|
"project add": {
|
|
@@ -8595,6 +8639,10 @@ const COMMAND_SPECS = {
|
|
|
8595
8639
|
notes: "URL slug is auto-generated from name (e.g. 'Q2 Launch' → 'q2-launch-a1b2c3d4').",
|
|
8596
8640
|
options: {
|
|
8597
8641
|
"--description": { type: "string" },
|
|
8642
|
+
"--idempotency-key": {
|
|
8643
|
+
description: "Stable key making retries safe (repeated calls create only one project)",
|
|
8644
|
+
type: "string"
|
|
8645
|
+
},
|
|
8598
8646
|
"--target-date": {
|
|
8599
8647
|
description: "Target completion date",
|
|
8600
8648
|
format: "YYYY-MM-DD",
|
|
@@ -8633,8 +8681,25 @@ const COMMAND_SPECS = {
|
|
|
8633
8681
|
ok: "boolean"
|
|
8634
8682
|
},
|
|
8635
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
|
+
},
|
|
8636
8692
|
"--description": { type: "string" },
|
|
8637
8693
|
"--name": { type: "string" },
|
|
8694
|
+
"--status": {
|
|
8695
|
+
description: "Set status",
|
|
8696
|
+
enum: [
|
|
8697
|
+
"active",
|
|
8698
|
+
"done",
|
|
8699
|
+
"archived"
|
|
8700
|
+
],
|
|
8701
|
+
type: "string"
|
|
8702
|
+
},
|
|
8638
8703
|
"--target-date": {
|
|
8639
8704
|
format: "YYYY-MM-DD",
|
|
8640
8705
|
type: "string"
|
|
@@ -8734,6 +8799,10 @@ const COMMAND_SPECS = {
|
|
|
8734
8799
|
format: "YYYY-MM-DD",
|
|
8735
8800
|
type: "string"
|
|
8736
8801
|
},
|
|
8802
|
+
"--idempotency-key": {
|
|
8803
|
+
description: "Stable key making retries safe (repeated calls create only one task)",
|
|
8804
|
+
type: "string"
|
|
8805
|
+
},
|
|
8737
8806
|
"--notes": {
|
|
8738
8807
|
description: "Body / description text (or pipe via stdin)",
|
|
8739
8808
|
type: "string"
|
|
@@ -8824,15 +8893,35 @@ const COMMAND_SPECS = {
|
|
|
8824
8893
|
type: "string"
|
|
8825
8894
|
} },
|
|
8826
8895
|
command: "task edit",
|
|
8827
|
-
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
|
+
],
|
|
8828
8901
|
json_output: {
|
|
8829
8902
|
action: "string",
|
|
8830
8903
|
id: "string",
|
|
8831
8904
|
ok: "boolean"
|
|
8832
8905
|
},
|
|
8833
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
|
+
},
|
|
8834
8923
|
"--deadline": {
|
|
8835
|
-
description: "Set due date
|
|
8924
|
+
description: "Set due date",
|
|
8836
8925
|
format: "YYYY-MM-DD",
|
|
8837
8926
|
type: "string"
|
|
8838
8927
|
},
|
|
@@ -9098,9 +9187,12 @@ const TOP_LEVEL_SPECS = {
|
|
|
9098
9187
|
},
|
|
9099
9188
|
label: {
|
|
9100
9189
|
command: "label",
|
|
9101
|
-
description: "
|
|
9190
|
+
description: "Manage workspace labels and task label assignments",
|
|
9102
9191
|
subcommands: {
|
|
9192
|
+
add: "Add a label to a task",
|
|
9193
|
+
create: "Create a new label",
|
|
9103
9194
|
list: "List all labels",
|
|
9195
|
+
remove: "Remove a label from a task",
|
|
9104
9196
|
show: "Get label details"
|
|
9105
9197
|
}
|
|
9106
9198
|
},
|
|
@@ -9131,6 +9223,7 @@ const TOP_LEVEL_SPECS = {
|
|
|
9131
9223
|
edit: "Update task fields",
|
|
9132
9224
|
list: "List tasks (--state, --view, --search, --limit)",
|
|
9133
9225
|
prepend: "Prepend text to task notes",
|
|
9226
|
+
random: "Pick a random task (--state to filter)",
|
|
9134
9227
|
read: "Print task notes as plain text",
|
|
9135
9228
|
reopen: "Reopen a completed task",
|
|
9136
9229
|
show: "Get task details by id",
|
|
@@ -9757,30 +9850,8 @@ Examples:
|
|
|
9757
9850
|
});
|
|
9758
9851
|
};
|
|
9759
9852
|
//#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);
|
|
9853
|
+
//#region src/program.ts
|
|
9854
|
+
const buildProgram = (workspaceOverride = null) => {
|
|
9784
9855
|
const program = new Command();
|
|
9785
9856
|
program.name(CLI_NAME).description("Done Bear CLI for auth, workspace, and task workflows").version(packageManifest.version, "-v, --version").showHelpAfterError().showSuggestionAfterError();
|
|
9786
9857
|
addGlobalOptions(program);
|
|
@@ -9824,6 +9895,33 @@ Filtering & output:
|
|
|
9824
9895
|
registerSearchCommand(program, workspaceOverride);
|
|
9825
9896
|
registerHistoryCommand(program, workspaceOverride);
|
|
9826
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);
|
|
9827
9925
|
const hasUserArgs = processedArgv.length <= 2;
|
|
9828
9926
|
const isTTY = process.stdin.isTTY === true;
|
|
9829
9927
|
if (hasUserArgs && isTTY) {
|