@quire-io/quire-cli 0.1.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.
Files changed (94) hide show
  1. package/CHANGES.md +33 -0
  2. package/LICENSE +15 -0
  3. package/README.md +69 -0
  4. package/dist/cli.js +209 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/commands/chat.js +200 -0
  7. package/dist/commands/chat.js.map +1 -0
  8. package/dist/commands/colors.js +34 -0
  9. package/dist/commands/colors.js.map +1 -0
  10. package/dist/commands/comment.js +128 -0
  11. package/dist/commands/comment.js.map +1 -0
  12. package/dist/commands/doc.js +139 -0
  13. package/dist/commands/doc.js.map +1 -0
  14. package/dist/commands/insight.js +234 -0
  15. package/dist/commands/insight.js.map +1 -0
  16. package/dist/commands/login.js +78 -0
  17. package/dist/commands/login.js.map +1 -0
  18. package/dist/commands/logout.js +25 -0
  19. package/dist/commands/logout.js.map +1 -0
  20. package/dist/commands/mine.js +61 -0
  21. package/dist/commands/mine.js.map +1 -0
  22. package/dist/commands/notify.js +25 -0
  23. package/dist/commands/notify.js.map +1 -0
  24. package/dist/commands/org.js +99 -0
  25. package/dist/commands/org.js.map +1 -0
  26. package/dist/commands/partner.js +36 -0
  27. package/dist/commands/partner.js.map +1 -0
  28. package/dist/commands/project.js +346 -0
  29. package/dist/commands/project.js.map +1 -0
  30. package/dist/commands/resolve.js +61 -0
  31. package/dist/commands/resolve.js.map +1 -0
  32. package/dist/commands/status.js +130 -0
  33. package/dist/commands/status.js.map +1 -0
  34. package/dist/commands/sublist.js +143 -0
  35. package/dist/commands/sublist.js.map +1 -0
  36. package/dist/commands/tag.js +108 -0
  37. package/dist/commands/tag.js.map +1 -0
  38. package/dist/commands/task.js +767 -0
  39. package/dist/commands/task.js.map +1 -0
  40. package/dist/commands/undo.js +64 -0
  41. package/dist/commands/undo.js.map +1 -0
  42. package/dist/commands/user.js +24 -0
  43. package/dist/commands/user.js.map +1 -0
  44. package/dist/commands/whoami.js +34 -0
  45. package/dist/commands/whoami.js.map +1 -0
  46. package/dist/config.js +37 -0
  47. package/dist/config.js.map +1 -0
  48. package/dist/credentials.js +28 -0
  49. package/dist/credentials.js.map +1 -0
  50. package/dist/errors.js +56 -0
  51. package/dist/errors.js.map +1 -0
  52. package/dist/log.js +37 -0
  53. package/dist/log.js.map +1 -0
  54. package/dist/oauth/config.js +22 -0
  55. package/dist/oauth/config.js.map +1 -0
  56. package/dist/oauth/loopback.js +101 -0
  57. package/dist/oauth/loopback.js.map +1 -0
  58. package/dist/oauth/pkce.js +16 -0
  59. package/dist/oauth/pkce.js.map +1 -0
  60. package/dist/options.js +2 -0
  61. package/dist/options.js.map +1 -0
  62. package/dist/output/colors.js +77 -0
  63. package/dist/output/colors.js.map +1 -0
  64. package/dist/output/columns.js +22 -0
  65. package/dist/output/columns.js.map +1 -0
  66. package/dist/output/keyvalue.js +18 -0
  67. package/dist/output/keyvalue.js.map +1 -0
  68. package/dist/output/render.js +30 -0
  69. package/dist/output/render.js.map +1 -0
  70. package/dist/output/table.js +35 -0
  71. package/dist/output/table.js.map +1 -0
  72. package/dist/quire-client.js +74 -0
  73. package/dist/quire-client.js.map +1 -0
  74. package/dist/util/attach-input.js +65 -0
  75. package/dist/util/attach-input.js.map +1 -0
  76. package/dist/util/bulk-input.js +80 -0
  77. package/dist/util/bulk-input.js.map +1 -0
  78. package/dist/util/confirm.js +31 -0
  79. package/dist/util/confirm.js.map +1 -0
  80. package/dist/util/field-flags.js +52 -0
  81. package/dist/util/field-flags.js.map +1 -0
  82. package/dist/util/open-url.js +39 -0
  83. package/dist/util/open-url.js.map +1 -0
  84. package/dist/util/recurrence.js +53 -0
  85. package/dist/util/recurrence.js.map +1 -0
  86. package/dist/util/retry-after.js +56 -0
  87. package/dist/util/retry-after.js.map +1 -0
  88. package/dist/util/task-id.js +34 -0
  89. package/dist/util/task-id.js.map +1 -0
  90. package/dist/util/text-input.js +26 -0
  91. package/dist/util/text-input.js.map +1 -0
  92. package/dist/version.js +19 -0
  93. package/dist/version.js.map +1 -0
  94. package/package.json +62 -0
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Display helpers for Quire's 48-slot palette. The api-client exposes
3
+ * the canonical `COLOR_TABLE` (code → hex) and `NAMED_COLORS` (name →
4
+ * code); this module adds the small inverse + presentation logic the
5
+ * CLI needs.
6
+ *
7
+ * ANSI swatch rendering converts the hex to the closest color in the
8
+ * 256-color cube (16 + 36·r + 6·g + b, each in 0..5). Skipped when
9
+ * stdout is not a TTY or `NO_COLOR` is set so piped output stays clean
10
+ * for `jq` / `grep` consumers.
11
+ */
12
+ import { COLOR_TABLE, NAMED_COLORS } from "@quire-io/api-client";
13
+ const CODE_TO_NAME = new Map();
14
+ for (const [name, code] of Object.entries(NAMED_COLORS)) {
15
+ // First name registered wins — e.g. "gray" before "grey" since
16
+ // Object.entries iterates in insertion order.
17
+ if (!CODE_TO_NAME.has(code))
18
+ CODE_TO_NAME.set(code, name);
19
+ }
20
+ const stdoutTty = process.stdout.isTTY === true && !process.env.NO_COLOR;
21
+ function hexToAnsi256(hex) {
22
+ // hex is "#RRGGBB"
23
+ const r = Number.parseInt(hex.slice(1, 3), 16);
24
+ const g = Number.parseInt(hex.slice(3, 5), 16);
25
+ const b = Number.parseInt(hex.slice(5, 7), 16);
26
+ const r6 = Math.round((r / 255) * 5);
27
+ const g6 = Math.round((g / 255) * 5);
28
+ const b6 = Math.round((b / 255) * 5);
29
+ return 16 + 36 * r6 + 6 * g6 + b6;
30
+ }
31
+ /** Return a colored "●" if stdout is a TTY and the code is known; else "". */
32
+ export function ansiSwatch(code) {
33
+ if (!code || !stdoutTty)
34
+ return "";
35
+ const hex = COLOR_TABLE[code];
36
+ if (!hex)
37
+ return "";
38
+ return `\x1b[38;5;${hexToAnsi256(hex)}m●\x1b[0m`;
39
+ }
40
+ /** Reverse-lookup: code → friendly name (or undefined). */
41
+ export function friendlyColor(code) {
42
+ if (!code)
43
+ return undefined;
44
+ return CODE_TO_NAME.get(code);
45
+ }
46
+ /**
47
+ * Short form for table cells: "● red" if known and TTY, "● 06" if unknown
48
+ * but TTY, "red" if known but non-TTY, "06" otherwise. Falls back to ""
49
+ * for missing codes.
50
+ */
51
+ export function shortColor(code) {
52
+ if (!code)
53
+ return "";
54
+ const label = CODE_TO_NAME.get(code) ?? code;
55
+ const swatch = ansiSwatch(code);
56
+ return swatch ? `${swatch} ${label}` : label;
57
+ }
58
+ /**
59
+ * Long form for `get` views: "● red (06, #D81F1E)" / "red (06, #D81F1E)" /
60
+ * "06 (#D81F1E)" / "06" depending on what we know.
61
+ */
62
+ export function longColor(code) {
63
+ if (!code)
64
+ return undefined;
65
+ const name = CODE_TO_NAME.get(code);
66
+ const hex = COLOR_TABLE[code];
67
+ let label;
68
+ if (name && hex)
69
+ label = `${name} (${code}, ${hex})`;
70
+ else if (hex)
71
+ label = `${code} (${hex})`;
72
+ else
73
+ label = code;
74
+ const swatch = ansiSwatch(code);
75
+ return swatch ? `${swatch} ${label}` : label;
76
+ }
77
+ //# sourceMappingURL=colors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"colors.js","sourceRoot":"","sources":["../../src/output/colors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ export const TASK_LIST_COLUMNS = [
2
+ { header: "ID", get: (t) => `#${t.id}` },
3
+ { header: "NAME", get: (t) => t.nameText ?? t.name },
4
+ { header: "STATUS", get: (t) => t.status?.name ?? "" },
5
+ { header: "DUE", get: (t) => t.due ?? "" },
6
+ { header: "OID", get: (t) => t.oid },
7
+ ];
8
+ export const TASK_GET_FIELDS = [
9
+ { label: "Name", get: (t) => t.nameText ?? t.name },
10
+ { label: "ID", get: (t) => `#${t.id}` },
11
+ { label: "OID", get: (t) => t.oid },
12
+ { label: "Status", get: (t) => t.status?.name },
13
+ { label: "Priority", get: (t) => t.priority?.name },
14
+ { label: "Start", get: (t) => t.start },
15
+ { label: "Due", get: (t) => t.due },
16
+ { label: "Description", get: (t) => t.descriptionText },
17
+ { label: "URL", get: (t) => t.url },
18
+ { label: "Created at", get: (t) => t.createdAt },
19
+ { label: "Edited at", get: (t) => t.editedAt },
20
+ { label: "Archived at", get: (t) => t.archivedAt },
21
+ ];
22
+ //# sourceMappingURL=columns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"columns.js","sourceRoot":"","sources":["../../src/output/columns.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,iBAAiB,GAA6B;IACzD,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE;IACxC,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,IAAI,EAAE;IACpD,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,IAAI,EAAE,EAAE;IACtD,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE;IAC1C,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE;CACrC,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAA+B;IACzD,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,IAAI,EAAE;IACnD,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE;IACvC,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE;IACnC,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE;IAC/C,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,IAAI,EAAE;IACnD,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE;IACvC,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE;IACnC,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,eAAe,EAAE;IACvD,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE;IACnC,EAAE,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,EAAE;IAChD,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE;IAC9C,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,EAAE;CACnD,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Aligned key/value renderer for `get` commands. Skips empty / nullish
3
+ * values so a sparsely-populated object doesn't render a wall of "(none)".
4
+ */
5
+ export function printKeyValue(item, fields) {
6
+ const filled = fields.filter((f) => {
7
+ const v = f.get(item);
8
+ return v !== undefined && v !== null && v !== "";
9
+ });
10
+ if (filled.length === 0)
11
+ return;
12
+ const labelWidth = Math.max(...filled.map((f) => f.label.length));
13
+ for (const field of filled) {
14
+ const value = field.get(item) ?? "";
15
+ process.stdout.write(`${field.label.padEnd(labelWidth)} ${value}\n`);
16
+ }
17
+ }
18
+ //# sourceMappingURL=keyvalue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyvalue.js","sourceRoot":"","sources":["../../src/output/keyvalue.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,MAAM,UAAU,aAAa,CAAI,IAAO,EAAE,MAA0B;IAClE,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;QACjC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACtB,OAAO,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;IACnD,CAAC,CAAC,CAAC;IACH,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAEhC,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IAClE,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QACpC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC;IACxE,CAAC;AACH,CAAC"}
@@ -0,0 +1,30 @@
1
+ import { printKeyValue } from "./keyvalue.js";
2
+ import { printTable } from "./table.js";
3
+ export function renderList(items, opts, config) {
4
+ if (opts.json === true) {
5
+ process.stdout.write(`${JSON.stringify(items)}\n`);
6
+ return;
7
+ }
8
+ if (opts.quiet === true) {
9
+ if (!config.toId)
10
+ return;
11
+ for (const item of items)
12
+ process.stdout.write(`${config.toId(item)}\n`);
13
+ return;
14
+ }
15
+ printTable(items, config.columns, { noTruncate: opts.truncate === false });
16
+ }
17
+ export function renderObject(item, opts, config) {
18
+ if (opts.json === true) {
19
+ process.stdout.write(`${JSON.stringify(item)}\n`);
20
+ return;
21
+ }
22
+ if (opts.quiet === true) {
23
+ if (!config.toId)
24
+ return;
25
+ process.stdout.write(`${config.toId(item)}\n`);
26
+ return;
27
+ }
28
+ printKeyValue(item, config.fields);
29
+ }
30
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/output/render.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,aAAa,EAAsB,MAAM,eAAe,CAAC;AAClE,OAAO,EAAE,UAAU,EAAoB,MAAM,YAAY,CAAC;AAc1D,MAAM,UAAU,UAAU,CACxB,KAAU,EACV,IAAgB,EAChB,MAA2B;IAE3B,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QACvB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnD,OAAO;IACT,CAAC;IACD,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;QACxB,IAAI,CAAC,MAAM,CAAC,IAAI;YAAE,OAAO;QACzB,KAAK,MAAM,IAAI,IAAI,KAAK;YAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzE,OAAO;IACT,CAAC;IACD,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,QAAQ,KAAK,KAAK,EAAE,CAAC,CAAC;AAC7E,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,IAAO,EACP,IAAgB,EAChB,MAA6B;IAE7B,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QACvB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClD,OAAO;IACT,CAAC;IACD,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;QACxB,IAAI,CAAC,MAAM,CAAC,IAAI;YAAE,OAAO;QACzB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/C,OAAO;IACT,CAAC;IACD,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;AACrC,CAAC"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Compact spaces-aligned table renderer. Matches the gh / kubectl aesthetic
3
+ * (no border characters, lowercase header columns, two-space gutters) so
4
+ * pipes through `grep` / `awk` keep working without unicode noise.
5
+ */
6
+ const DEFAULT_MAX_CELL_WIDTH = 80;
7
+ const ELLIPSIS = "…";
8
+ function truncate(s, max) {
9
+ if (s.length <= max)
10
+ return s;
11
+ return `${s.slice(0, Math.max(0, max - 1))}${ELLIPSIS}`;
12
+ }
13
+ export function printTable(items, columns, opts = {}) {
14
+ if (items.length === 0)
15
+ return;
16
+ const max = opts.maxCellWidth ?? DEFAULT_MAX_CELL_WIDTH;
17
+ const cells = items.map((item) => columns.map((col) => col.get(item) ?? ""));
18
+ const finalCells = opts.noTruncate
19
+ ? cells
20
+ : cells.map((row) => row.map((cell) => truncate(cell, max)));
21
+ const widths = columns.map((col, i) => Math.max(col.header.length, ...finalCells.map((row) => row[i]?.length ?? 0)));
22
+ const header = columns
23
+ .map((col, i) => col.header.padEnd(widths[i] ?? 0))
24
+ .join(" ")
25
+ .trimEnd();
26
+ process.stdout.write(`${header}\n`);
27
+ for (const row of finalCells) {
28
+ const line = row
29
+ .map((cell, i) => cell.padEnd(widths[i] ?? 0))
30
+ .join(" ")
31
+ .trimEnd();
32
+ process.stdout.write(`${line}\n`);
33
+ }
34
+ }
35
+ //# sourceMappingURL=table.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table.js","sourceRoot":"","sources":["../../src/output/table.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,sBAAsB,GAAG,EAAE,CAAC;AAClC,MAAM,QAAQ,GAAG,GAAG,CAAC;AAcrB,SAAS,QAAQ,CAAC,CAAS,EAAE,GAAW;IACtC,IAAI,CAAC,CAAC,MAAM,IAAI,GAAG;QAAE,OAAO,CAAC,CAAC;IAC9B,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,QAAQ,EAAE,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,UAAU,CACxB,KAAmB,EACnB,OAAkC,EAClC,OAA0B,EAAE;IAE5B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAE/B,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,IAAI,sBAAsB,CAAC;IACxD,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC7E,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU;QAChC,CAAC,CAAC,KAAK;QACP,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IAE/D,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CACpC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,MAAM,IAAI,CAAC,CAAC,CAAC,CAC7E,CAAC;IAEF,MAAM,MAAM,GAAG,OAAO;SACnB,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;SAClD,IAAI,CAAC,IAAI,CAAC;SACV,OAAO,EAAE,CAAC;IACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC,CAAC;IAEpC,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,GAAG;aACb,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;aAC7C,IAAI,CAAC,IAAI,CAAC;aACV,OAAO,EAAE,CAAC;QACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;IACpC,CAAC;AACH,CAAC"}
@@ -0,0 +1,74 @@
1
+ import { QuireClient, refreshTokens as apiRefreshTokens, } from "@quire-io/api-client";
2
+ import { getApiServer } from "./config.js";
3
+ import { deleteCredentials, readCredentials, writeCredentials, } from "./credentials.js";
4
+ import { NotLoggedInError } from "./errors.js";
5
+ import { QUIRE_CLI_CLIENT_ID } from "./oauth/config.js";
6
+ import { withRetryOn429 } from "./util/retry-after.js";
7
+ import { readVersion } from "./version.js";
8
+ /**
9
+ * Wrap every async method on a QuireClient with `withRetryOn429`. A
10
+ * Proxy intercepts each call once and returns a thin retrying shim;
11
+ * non-function properties (the client's internal state, anything
12
+ * accessed for inspection) pass through untouched.
13
+ *
14
+ * The original client is still usable directly if a caller needs the
15
+ * raw error path (e.g. live tests asserting a specific 429 message).
16
+ */
17
+ function wrapWithRetry(client) {
18
+ return new Proxy(client, {
19
+ get(target, prop, receiver) {
20
+ const value = Reflect.get(target, prop, receiver);
21
+ if (typeof value !== "function")
22
+ return value;
23
+ return (...args) => withRetryOn429(() => value.apply(target, args), {
24
+ onRetry: (waitSec) => {
25
+ process.stderr.write(`Quire API rate limited; retrying in ${waitSec}s…\n`);
26
+ },
27
+ });
28
+ },
29
+ });
30
+ }
31
+ /**
32
+ * Build a `QuireClient` for the signed-in user on this machine. Reads
33
+ * credentials from the configured profile; throws `NotLoggedInError` if
34
+ * none exist.
35
+ *
36
+ * The returned client auto-refreshes on 401s using the refresh token in
37
+ * the public-PKCE flow (no client_secret), persists rotated tokens back
38
+ * to disk atomically, and deletes the credentials file if the refresh
39
+ * token is dead so the next command surfaces a clean "run quire login"
40
+ * message.
41
+ */
42
+ export function createQuireClient(opts = {}) {
43
+ const stored = readCredentials(opts.profile);
44
+ if (!stored)
45
+ throw new NotLoggedInError();
46
+ const apiServer = getApiServer();
47
+ const client = new QuireClient({
48
+ tokens: {
49
+ accessToken: stored.accessToken,
50
+ refreshToken: stored.refreshToken,
51
+ expiresAt: stored.expiresAt,
52
+ },
53
+ apiServer,
54
+ headers: { "User-Agent": `quire-cli/${readVersion()}` },
55
+ refreshTokens: (refreshToken) => apiRefreshTokens({
56
+ apiServer,
57
+ clientId: QUIRE_CLI_CLIENT_ID,
58
+ refreshToken,
59
+ }),
60
+ onTokenRefresh: async (tokens) => {
61
+ writeCredentials({
62
+ accessToken: tokens.accessToken,
63
+ refreshToken: tokens.refreshToken,
64
+ expiresAt: tokens.expiresAt,
65
+ userOid: stored.userOid,
66
+ }, opts.profile);
67
+ },
68
+ onAuthRevoked: async () => {
69
+ deleteCredentials(opts.profile);
70
+ },
71
+ });
72
+ return wrapWithRetry(client);
73
+ }
74
+ //# sourceMappingURL=quire-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quire-client.js","sourceRoot":"","sources":["../src/quire-client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,aAAa,IAAI,gBAAgB,GAClC,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAM3C;;;;;;;;GAQG;AACH,SAAS,aAAa,CAAC,MAAmB;IACxC,OAAO,IAAI,KAAK,CAAC,MAAM,EAAE;QACvB,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ;YACxB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;YAClD,IAAI,OAAO,KAAK,KAAK,UAAU;gBAAE,OAAO,KAAK,CAAC;YAC9C,OAAO,CAAC,GAAG,IAAe,EAAE,EAAE,CAC5B,cAAc,CACZ,GAAG,EAAE,CAAE,KAA+C,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,EAC1E;gBACE,OAAO,EAAE,CAAC,OAAO,EAAE,EAAE;oBACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,uCAAuC,OAAO,MAAM,CACrD,CAAC;gBACJ,CAAC;aACF,CACF,CAAC;QACN,CAAC;KACF,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAiC,EAAE;IACnE,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC7C,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,gBAAgB,EAAE,CAAC;IAE1C,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;IAEjC,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC;QAC7B,MAAM,EAAE;YACN,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,YAAY,EAAE,MAAM,CAAC,YAAY;YACjC,SAAS,EAAE,MAAM,CAAC,SAAS;SAC5B;QACD,SAAS;QACT,OAAO,EAAE,EAAE,YAAY,EAAE,aAAa,WAAW,EAAE,EAAE,EAAE;QACvD,aAAa,EAAE,CAAC,YAAY,EAAE,EAAE,CAC9B,gBAAgB,CAAC;YACf,SAAS;YACT,QAAQ,EAAE,mBAAmB;YAC7B,YAAY;SACb,CAAC;QACJ,cAAc,EAAE,KAAK,EAAE,MAAmB,EAAE,EAAE;YAC5C,gBAAgB,CACd;gBACE,WAAW,EAAE,MAAM,CAAC,WAAW;gBAC/B,YAAY,EAAE,MAAM,CAAC,YAAY;gBACjC,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,OAAO,EAAE,MAAM,CAAC,OAAO;aACxB,EACD,IAAI,CAAC,OAAO,CACb,CAAC;QACJ,CAAC;QACD,aAAa,EAAE,KAAK,IAAI,EAAE;YACxB,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAClC,CAAC;KACF,CAAC,CAAC;IACH,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,65 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { basename } from "node:path";
3
+ import { ValidationError } from "../errors.js";
4
+ const CONTENT_TYPE_BY_EXT = {
5
+ ".bmp": "image/bmp",
6
+ ".csv": "text/csv",
7
+ ".gif": "image/gif",
8
+ ".html": "text/html",
9
+ ".jpeg": "image/jpeg",
10
+ ".jpg": "image/jpeg",
11
+ ".json": "application/json",
12
+ ".log": "text/plain",
13
+ ".md": "text/markdown",
14
+ ".pdf": "application/pdf",
15
+ ".png": "image/png",
16
+ ".svg": "image/svg+xml",
17
+ ".tar": "application/x-tar",
18
+ ".txt": "text/plain",
19
+ ".webp": "image/webp",
20
+ ".xml": "application/xml",
21
+ ".yaml": "application/yaml",
22
+ ".yml": "application/yaml",
23
+ ".zip": "application/zip",
24
+ };
25
+ /**
26
+ * Resolve a positional `<file>` argument into bytes + filename + content-type.
27
+ *
28
+ * `path = "-"` reads stdin to EOF; `--filename` is then required (we have no
29
+ * path to derive it from). Otherwise the path is read with `readFileSync`
30
+ * and the filename defaults to its `basename`.
31
+ */
32
+ export async function resolveAttachInput(path, opts = {}) {
33
+ let bytes;
34
+ let filename;
35
+ if (path === "-") {
36
+ if (opts.filename === undefined) {
37
+ throw new ValidationError("--filename is required when reading attachment data from stdin (`-`).");
38
+ }
39
+ const chunks = [];
40
+ for await (const chunk of process.stdin)
41
+ chunks.push(chunk);
42
+ bytes = Buffer.concat(chunks);
43
+ filename = opts.filename;
44
+ }
45
+ else {
46
+ bytes = readFileSync(path);
47
+ filename = opts.filename ?? basename(path);
48
+ }
49
+ if (filename.includes("/")) {
50
+ throw new ValidationError(`Attachment filename cannot contain "/" — got "${filename}".`);
51
+ }
52
+ if (filename.length === 0) {
53
+ throw new ValidationError("Attachment filename cannot be empty.");
54
+ }
55
+ const contentType = opts.contentType ?? guessContentType(filename);
56
+ return { filename, bytes, contentType };
57
+ }
58
+ function guessContentType(filename) {
59
+ const dot = filename.lastIndexOf(".");
60
+ if (dot < 0)
61
+ return "application/octet-stream";
62
+ const ext = filename.slice(dot).toLowerCase();
63
+ return CONTENT_TYPE_BY_EXT[ext] ?? "application/octet-stream";
64
+ }
65
+ //# sourceMappingURL=attach-input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attach-input.js","sourceRoot":"","sources":["../../src/util/attach-input.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,MAAM,mBAAmB,GAAqC;IAC5D,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,UAAU;IAClB,MAAM,EAAE,WAAW;IACnB,OAAO,EAAE,WAAW;IACpB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,YAAY;IACpB,OAAO,EAAE,kBAAkB;IAC3B,MAAM,EAAE,YAAY;IACpB,KAAK,EAAE,eAAe;IACtB,MAAM,EAAE,iBAAiB;IACzB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,eAAe;IACvB,MAAM,EAAE,mBAAmB;IAC3B,MAAM,EAAE,YAAY;IACpB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,iBAAiB;IACzB,OAAO,EAAE,kBAAkB;IAC3B,MAAM,EAAE,kBAAkB;IAC1B,MAAM,EAAE,iBAAiB;CAC1B,CAAC;AAeF;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,IAAY,EACZ,OAA6B,EAAE;IAE/B,IAAI,KAAiB,CAAC;IACtB,IAAI,QAAgB,CAAC;IAErB,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;QACjB,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YAChC,MAAM,IAAI,eAAe,CAAC,uEAAuE,CAAC,CAAC;QACrG,CAAC;QACD,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,CAAC,KAAK;YAAE,MAAM,CAAC,IAAI,CAAC,KAAe,CAAC,CAAC;QACtE,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC9B,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;IAC3B,CAAC;SAAM,CAAC;QACN,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;QAC3B,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC7C,CAAC;IAED,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,eAAe,CAAC,iDAAiD,QAAQ,IAAI,CAAC,CAAC;IAC3F,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,eAAe,CAAC,sCAAsC,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACnE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;AAC1C,CAAC;AAED,SAAS,gBAAgB,CAAC,QAAgB;IACxC,MAAM,GAAG,GAAG,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACtC,IAAI,GAAG,GAAG,CAAC;QAAE,OAAO,0BAA0B,CAAC;IAC/C,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IAC9C,OAAO,mBAAmB,CAAC,GAAG,CAAC,IAAI,0BAA0B,CAAC;AAChE,CAAC"}
@@ -0,0 +1,80 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { ValidationError } from "../errors.js";
3
+ /**
4
+ * Read text from a file path or stdin (`-`). Used by the bulk subcommands
5
+ * so users can either point at a file or pipe `jq` output directly.
6
+ */
7
+ export async function readFromFile(filename) {
8
+ if (filename === "-") {
9
+ const chunks = [];
10
+ for await (const chunk of process.stdin) {
11
+ chunks.push(chunk);
12
+ }
13
+ return Buffer.concat(chunks).toString("utf8");
14
+ }
15
+ return readFileSync(filename, "utf8");
16
+ }
17
+ /**
18
+ * Parse an `--from-file` source as a JSON array of objects (for `bulk-create`
19
+ * / `bulk-update` payloads). Each element must be an object — primitives are
20
+ * rejected with a clear `ValidationError`.
21
+ */
22
+ export async function readBulkItems(filename) {
23
+ const text = await readFromFile(filename);
24
+ let parsed;
25
+ try {
26
+ parsed = JSON.parse(text);
27
+ }
28
+ catch (err) {
29
+ throw new ValidationError(`Failed to parse JSON from "${filename}": ${err.message}`);
30
+ }
31
+ if (!Array.isArray(parsed)) {
32
+ throw new ValidationError(`Expected a JSON array in "${filename}".`);
33
+ }
34
+ for (const [i, item] of parsed.entries()) {
35
+ if (item === null || typeof item !== "object" || Array.isArray(item)) {
36
+ throw new ValidationError(`Element ${i} in "${filename}" is not an object.`);
37
+ }
38
+ }
39
+ return parsed;
40
+ }
41
+ /**
42
+ * Parse an `--from-file` source as a list of task refs (OIDs or numeric
43
+ * IDs). Two accepted formats:
44
+ *
45
+ * - JSON array (file starts with `[`): `["abc", 408, "def"]`
46
+ * - one ref per line (default): blank lines and lines starting with `#`
47
+ * are skipped so users can leave inline notes
48
+ *
49
+ * Numeric strings are coerced to numbers so the api-client's
50
+ * `string | number` ref type lines up cleanly.
51
+ */
52
+ export async function readBulkRefs(filename) {
53
+ const text = await readFromFile(filename);
54
+ const trimmed = text.trim();
55
+ if (trimmed.length === 0)
56
+ return [];
57
+ if (trimmed.startsWith("[")) {
58
+ let parsed;
59
+ try {
60
+ parsed = JSON.parse(trimmed);
61
+ }
62
+ catch (err) {
63
+ throw new ValidationError(`Failed to parse JSON from "${filename}": ${err.message}`);
64
+ }
65
+ if (!Array.isArray(parsed)) {
66
+ throw new ValidationError(`Expected a JSON array of refs in "${filename}".`);
67
+ }
68
+ return parsed.map((v, i) => {
69
+ if (typeof v === "string" || typeof v === "number")
70
+ return v;
71
+ throw new ValidationError(`Element ${i} in "${filename}" is not a string or number.`);
72
+ });
73
+ }
74
+ return trimmed
75
+ .split(/\r?\n/)
76
+ .map((line) => line.trim())
77
+ .filter((line) => line.length > 0 && !line.startsWith("#"))
78
+ .map((line) => (/^\d+$/.test(line) ? Number.parseInt(line, 10) : line));
79
+ }
80
+ //# sourceMappingURL=bulk-input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bulk-input.js","sourceRoot":"","sources":["../../src/util/bulk-input.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ import { UserDeclinedError } from "../errors.js";
3
+ /**
4
+ * Block until the user explicitly confirms a destructive operation. Throws
5
+ * `UserDeclinedError` (exit 4) when:
6
+ *
7
+ * - the user types anything other than `y` / `yes` (case-insensitive)
8
+ * - stdin is not a TTY and `--yes` was not passed (so a script can't
9
+ * accidentally trigger a destructive action by piping past a stuck prompt)
10
+ *
11
+ * The prompt is written to stderr — stdout is reserved for command output
12
+ * (Phase 6 stdout discipline).
13
+ */
14
+ export async function confirmDestructive(opts) {
15
+ if (opts.yes === true)
16
+ return;
17
+ if (!process.stdin.isTTY) {
18
+ throw new UserDeclinedError("Refusing to run a destructive command in a non-interactive shell. Pass --yes to confirm.");
19
+ }
20
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
21
+ try {
22
+ const answer = await rl.question(`${opts.question} [y/N] `);
23
+ if (!/^y(es)?$/i.test(answer.trim())) {
24
+ throw new UserDeclinedError("Aborted by user.");
25
+ }
26
+ }
27
+ finally {
28
+ rl.close();
29
+ }
30
+ }
31
+ //# sourceMappingURL=confirm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confirm.js","sourceRoot":"","sources":["../../src/util/confirm.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AASjD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,IAAoB;IAC3D,IAAI,IAAI,CAAC,GAAG,KAAK,IAAI;QAAE,OAAO;IAE9B,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,iBAAiB,CACzB,0FAA0F,CAC3F,CAAC;IACJ,CAAC;IAED,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7E,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,QAAQ,SAAS,CAAC,CAAC;QAC5D,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;YACrC,MAAM,IAAI,iBAAiB,CAAC,kBAAkB,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC"}
@@ -0,0 +1,52 @@
1
+ import { ValidationError } from "../errors.js";
2
+ /**
3
+ * Build a Quire field-definition body from CLI flags. The api-client's body
4
+ * type for `addProjectField` / `addInsightField` etc. is `{ name, type,
5
+ * [key: string]: unknown }`, so we let the user pass arbitrary type-specific
6
+ * config via `--extra key=value` (JSON-parsed when possible — so
7
+ * `--extra options='[{"label":"red"}]'` works for select-type fields).
8
+ */
9
+ export function buildFieldBody(flags) {
10
+ const body = {};
11
+ if (flags.name !== undefined)
12
+ body.name = flags.name;
13
+ if (flags.type !== undefined)
14
+ body.type = flags.type;
15
+ if (flags.hidden === true)
16
+ body.hidden = true;
17
+ if (flags.private === true)
18
+ body.private = true;
19
+ if (flags.percent === true)
20
+ body.percent = true;
21
+ if (flags.multiple === true)
22
+ body.multiple = true;
23
+ if (flags.clearOnDup === true)
24
+ body.clearOnDup = true;
25
+ if (flags.extra) {
26
+ for (const pair of flags.extra) {
27
+ const eq = pair.indexOf("=");
28
+ if (eq <= 0) {
29
+ throw new ValidationError(`Invalid --extra "${pair}". Expected key=value.`);
30
+ }
31
+ const key = pair.slice(0, eq);
32
+ const raw = pair.slice(eq + 1);
33
+ try {
34
+ body[key] = JSON.parse(raw);
35
+ }
36
+ catch {
37
+ body[key] = raw;
38
+ }
39
+ }
40
+ }
41
+ return body;
42
+ }
43
+ export const FIELD_FIELDS = [
44
+ { label: "Name", get: (f) => f.name },
45
+ { label: "Type", get: (f) => f.type },
46
+ { label: "Hidden", get: (f) => (f.hidden === true ? "yes" : undefined) },
47
+ { label: "Private", get: (f) => (f.private === true ? "yes" : undefined) },
48
+ { label: "Percent", get: (f) => (f.percent === true ? "yes" : undefined) },
49
+ { label: "Multiple", get: (f) => (f.multiple === true ? "yes" : undefined) },
50
+ { label: "Clear on dup", get: (f) => (f.clearOnDup === true ? "yes" : undefined) },
51
+ ];
52
+ //# sourceMappingURL=field-flags.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"field-flags.js","sourceRoot":"","sources":["../../src/util/field-flags.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import { spawn } from "node:child_process";
2
+ /**
3
+ * Open `url` in the system default browser. Best-effort: detaches the child
4
+ * and resolves once spawn succeeds. Errors during spawn (no `xdg-open`,
5
+ * sandboxed environment, etc.) are surfaced via the returned Promise so the
6
+ * caller can fall back to printing the URL.
7
+ *
8
+ * Why not the `open` npm package? It ships its own bundled `xdg-open` shell
9
+ * script as a sibling file at runtime. That breaks Node SEA / pkg single-file
10
+ * binaries — they can't carry an external sibling script. This helper relies
11
+ * only on the platform tools that are already on the user's PATH.
12
+ */
13
+ export function openUrl(url) {
14
+ return new Promise((resolve, reject) => {
15
+ let cmd;
16
+ let args;
17
+ if (process.platform === "darwin") {
18
+ cmd = "open";
19
+ args = [url];
20
+ }
21
+ else if (process.platform === "win32") {
22
+ // `start` is a cmd.exe builtin, not a separate executable. The empty
23
+ // "" before url is the (otherwise positional) window title — without
24
+ // it, `start "https://…"` would treat the URL as the title.
25
+ cmd = "cmd";
26
+ args = ["/c", "start", "", url];
27
+ }
28
+ else {
29
+ cmd = "xdg-open";
30
+ args = [url];
31
+ }
32
+ const child = spawn(cmd, args, { stdio: "ignore", detached: true });
33
+ child.on("error", reject);
34
+ // Detach so the parent process can exit independently of the browser.
35
+ child.unref();
36
+ resolve();
37
+ });
38
+ }
39
+ //# sourceMappingURL=open-url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open-url.js","sourceRoot":"","sources":["../../src/util/open-url.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C;;;;;;;;;;GAUG;AACH,MAAM,UAAU,OAAO,CAAC,GAAW;IACjC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAI,GAAW,CAAC;QAChB,IAAI,IAAc,CAAC;QACnB,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAClC,GAAG,GAAG,MAAM,CAAC;YACb,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QACf,CAAC;aAAM,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YACxC,qEAAqE;YACrE,qEAAqE;YACrE,4DAA4D;YAC5D,GAAG,GAAG,KAAK,CAAC;YACZ,IAAI,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;QAClC,CAAC;aAAM,CAAC;YACN,GAAG,GAAG,UAAU,CAAC;YACjB,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QACf,CAAC;QAED,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QACpE,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC1B,sEAAsE;QACtE,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,53 @@
1
+ import { ValidationError } from "../errors.js";
2
+ const VALID_FREQS = ["daily", "weekly", "monthly", "yearly"];
3
+ /**
4
+ * Build a `QuireRecurrence` object from CLI flags. Returns `undefined` when
5
+ * none of the recurrence flags are set so the caller can omit the field
6
+ * entirely. Throws `ValidationError` (exit 3) on bad input — matches the
7
+ * Quire wire-shape constraints (positive integer interval, freq enum,
8
+ * 0–6 byweekday integers).
9
+ */
10
+ export function parseRecurrence(flags) {
11
+ const anyFlag = flags.recurrenceFreq !== undefined ||
12
+ flags.recurrenceInterval !== undefined ||
13
+ flags.recurrenceByweekday !== undefined ||
14
+ flags.recurrenceUntil !== undefined;
15
+ if (!anyFlag)
16
+ return undefined;
17
+ const freq = flags.recurrenceFreq?.toLowerCase();
18
+ if (!freq || !VALID_FREQS.includes(freq)) {
19
+ throw new ValidationError(`--recurrence-freq is required when any other --recurrence-* flag is set, and must be one of ${VALID_FREQS.join(" | ")}; got "${flags.recurrenceFreq ?? ""}"`);
20
+ }
21
+ if (flags.recurrenceInterval === undefined) {
22
+ throw new ValidationError("--recurrence-interval is required when any other --recurrence-* flag is set.");
23
+ }
24
+ const interval = Number.parseInt(flags.recurrenceInterval, 10);
25
+ if (!Number.isInteger(interval) || interval <= 0) {
26
+ throw new ValidationError(`--recurrence-interval must be a positive integer; got "${flags.recurrenceInterval}"`);
27
+ }
28
+ const recurrence = {
29
+ freq: freq,
30
+ interval,
31
+ };
32
+ if (flags.recurrenceByweekday !== undefined) {
33
+ const days = flags.recurrenceByweekday
34
+ .split(",")
35
+ .map((s) => s.trim())
36
+ .filter((s) => s.length > 0)
37
+ .map((s) => Number.parseInt(s, 10));
38
+ if (days.length === 0) {
39
+ throw new ValidationError(`--recurrence-byweekday must include at least one integer 0-6; got "${flags.recurrenceByweekday}"`);
40
+ }
41
+ for (const d of days) {
42
+ if (!Number.isInteger(d) || d < 0 || d > 6) {
43
+ throw new ValidationError(`--recurrence-byweekday must be comma-separated integers 0-6; got "${flags.recurrenceByweekday}"`);
44
+ }
45
+ }
46
+ recurrence.byweekday = days.length === 1 ? days[0] : days;
47
+ }
48
+ if (flags.recurrenceUntil !== undefined) {
49
+ recurrence.until = flags.recurrenceUntil;
50
+ }
51
+ return recurrence;
52
+ }
53
+ //# sourceMappingURL=recurrence.js.map