@workjournal/cli 0.8.0 → 0.17.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 (101) hide show
  1. package/README.md +11 -1
  2. package/dist/api-client.d.ts +26 -9
  3. package/dist/api-client.d.ts.map +1 -1
  4. package/dist/api-client.js +109 -57
  5. package/dist/api-client.js.map +1 -1
  6. package/dist/auth.d.ts +6 -0
  7. package/dist/auth.d.ts.map +1 -1
  8. package/dist/auth.js +18 -0
  9. package/dist/auth.js.map +1 -1
  10. package/dist/cli-args.d.ts +19 -0
  11. package/dist/cli-args.d.ts.map +1 -0
  12. package/dist/cli-args.js +50 -0
  13. package/dist/cli-args.js.map +1 -0
  14. package/dist/commands/entries.d.ts +8 -1
  15. package/dist/commands/entries.d.ts.map +1 -1
  16. package/dist/commands/entries.js +74 -15
  17. package/dist/commands/entries.js.map +1 -1
  18. package/dist/commands/export.d.ts.map +1 -1
  19. package/dist/commands/export.js +2 -3
  20. package/dist/commands/export.js.map +1 -1
  21. package/dist/commands/invites.d.ts.map +1 -1
  22. package/dist/commands/invites.js +6 -7
  23. package/dist/commands/invites.js.map +1 -1
  24. package/dist/commands/journals.d.ts +1 -1
  25. package/dist/commands/journals.d.ts.map +1 -1
  26. package/dist/commands/journals.js +20 -19
  27. package/dist/commands/journals.js.map +1 -1
  28. package/dist/commands/prompts.d.ts +9 -0
  29. package/dist/commands/prompts.d.ts.map +1 -0
  30. package/dist/commands/prompts.js +83 -0
  31. package/dist/commands/prompts.js.map +1 -0
  32. package/dist/commands/shares.d.ts.map +1 -1
  33. package/dist/commands/shares.js +11 -13
  34. package/dist/commands/shares.js.map +1 -1
  35. package/dist/commands/tags.d.ts +7 -0
  36. package/dist/commands/tags.d.ts.map +1 -0
  37. package/dist/commands/tags.js +87 -0
  38. package/dist/commands/tags.js.map +1 -0
  39. package/dist/commands/workspaces.d.ts.map +1 -1
  40. package/dist/commands/workspaces.js +4 -7
  41. package/dist/commands/workspaces.js.map +1 -1
  42. package/dist/config.d.ts.map +1 -1
  43. package/dist/config.js +3 -1
  44. package/dist/config.js.map +1 -1
  45. package/dist/help/_dispatch.d.ts +29 -0
  46. package/dist/help/_dispatch.d.ts.map +1 -0
  47. package/dist/help/_dispatch.js +132 -0
  48. package/dist/help/_dispatch.js.map +1 -0
  49. package/dist/help/_registry.d.ts +18 -0
  50. package/dist/help/_registry.d.ts.map +1 -0
  51. package/dist/help/_registry.js +98 -0
  52. package/dist/help/_registry.js.map +1 -0
  53. package/dist/help/_renderer.d.ts +31 -0
  54. package/dist/help/_renderer.d.ts.map +1 -0
  55. package/dist/help/_renderer.js +99 -0
  56. package/dist/help/_renderer.js.map +1 -0
  57. package/dist/help/_types.d.ts +53 -0
  58. package/dist/help/_types.d.ts.map +1 -0
  59. package/dist/help/_types.js +15 -0
  60. package/dist/help/_types.js.map +1 -0
  61. package/dist/help/auth-login.json +12 -0
  62. package/dist/help/auth-logout.json +7 -0
  63. package/dist/help/auth-status.json +7 -0
  64. package/dist/help/auth-whoami.json +7 -0
  65. package/dist/help/config-show.json +7 -0
  66. package/dist/help/entries-delete.json +12 -0
  67. package/dist/help/entries-get.json +12 -0
  68. package/dist/help/entries-last.json +19 -0
  69. package/dist/help/entries-list.json +14 -0
  70. package/dist/help/entries-search.json +15 -0
  71. package/dist/help/entries-update.json +44 -0
  72. package/dist/help/entries-write.json +39 -0
  73. package/dist/help/export.json +29 -0
  74. package/dist/help/invites-delete.json +16 -0
  75. package/dist/help/invites-list.json +11 -0
  76. package/dist/help/invites-new.json +12 -0
  77. package/dist/help/journal.json +13 -0
  78. package/dist/help/journals-assign-prompt.json +12 -0
  79. package/dist/help/journals-delete.json +11 -0
  80. package/dist/help/journals-get.json +20 -0
  81. package/dist/help/journals-list.json +14 -0
  82. package/dist/help/journals-new.json +21 -0
  83. package/dist/help/journals-rename.json +12 -0
  84. package/dist/help/journals-select.json +11 -0
  85. package/dist/help/journals-set-slug.json +16 -0
  86. package/dist/help/journals-unassign-prompt.json +11 -0
  87. package/dist/help/prompts-delete.json +11 -0
  88. package/dist/help/prompts-get.json +11 -0
  89. package/dist/help/prompts-list.json +14 -0
  90. package/dist/help/prompts-new.json +23 -0
  91. package/dist/help/prompts-update.json +29 -0
  92. package/dist/help/shares-delete.json +16 -0
  93. package/dist/help/shares-list.json +11 -0
  94. package/dist/help/tags-delete.json +29 -0
  95. package/dist/help/tags-get.json +29 -0
  96. package/dist/help/tags-list.json +25 -0
  97. package/dist/help/tags-new.json +35 -0
  98. package/dist/help/tags-update.json +41 -0
  99. package/dist/index.js +312 -79
  100. package/dist/index.js.map +1 -1
  101. package/package.json +4 -5
@@ -0,0 +1,35 @@
1
+ {
2
+ "command": "tags new",
3
+ "description": "Create a new tag in a workspace (workspace-scope) or in a specific journal (journal-scope via --journal). Workspace-scope tags require a description; journal-scope tags allow an optional description. Plus+ tier; Free returns FEATURE_NOT_IN_TIER.",
4
+ "args": [
5
+ {
6
+ "name": "workspaceSlug",
7
+ "required": true,
8
+ "description": "Slug from `workspaces list`."
9
+ },
10
+ {
11
+ "name": "name",
12
+ "required": true,
13
+ "description": "Tag name. Must be lowercase, hyphenated, alphanumeric (kebab-case). Max 50 chars."
14
+ }
15
+ ],
16
+ "flags": [
17
+ {
18
+ "short": "-d",
19
+ "long": "--description",
20
+ "value": "<text>",
21
+ "description": "Description (max 200 chars). Required at workspace scope; optional at journal scope."
22
+ },
23
+ {
24
+ "long": "--journal",
25
+ "value": "<journalSlug>",
26
+ "description": "Create as journal-scope. Omit for workspace-scope."
27
+ }
28
+ ],
29
+ "annotations": { "destructive": false, "idempotent": false },
30
+ "examples": [
31
+ "workjournal tags new acme decision -d \"Records a decision that future agents should know about\"",
32
+ "workjournal tags new acme planning -d \"Sprint planning notes\" --journal engineering"
33
+ ],
34
+ "when_to_use": "Adding a new tag to the workspace vocabulary or journal-local set before referencing it from `entries write --tags`."
35
+ }
@@ -0,0 +1,41 @@
1
+ {
2
+ "command": "tags update",
3
+ "description": "Rename a tag or replace its description. Renaming a workspace-scope tag cascades to every entry across the workspace; renaming a journal-scope tag cascades to entries in that journal. Not tier-gated — downgraded workspaces can still tidy up.",
4
+ "args": [
5
+ {
6
+ "name": "workspaceSlug",
7
+ "required": true,
8
+ "description": "Slug from `workspaces list`."
9
+ },
10
+ {
11
+ "name": "tagName",
12
+ "required": true,
13
+ "description": "Current tag name."
14
+ }
15
+ ],
16
+ "flags": [
17
+ {
18
+ "short": "-n",
19
+ "long": "--name",
20
+ "value": "<newName>",
21
+ "description": "New name (kebab-case, max 50 chars). Cascades the rename to every entry in scope."
22
+ },
23
+ {
24
+ "short": "-d",
25
+ "long": "--description",
26
+ "value": "<text>",
27
+ "description": "New description (max 200 chars). Pass an empty value to clear (journal-scope only — workspace-scope requires a non-empty description)."
28
+ },
29
+ {
30
+ "long": "--journal",
31
+ "value": "<journalSlug>",
32
+ "description": "Target the journal-scope row of this name."
33
+ }
34
+ ],
35
+ "annotations": { "destructive": false, "idempotent": true },
36
+ "examples": [
37
+ "workjournal tags update acme decision -d \"Architecturally load-bearing decisions only\"",
38
+ "workjournal tags update acme bug -n incident"
39
+ ],
40
+ "when_to_use": "Renaming a tag whose meaning shifted, or refining the description to better steer future tagging."
41
+ }
package/dist/index.js CHANGED
@@ -1,19 +1,29 @@
1
1
  #!/usr/bin/env node
2
- import { API_PATHS, apiGet, requireAuth } from './api-client.js';
3
- import { loginFinish, loginInteractive, loginStart } from './auth.js';
4
- import { createEntry, deleteEntry, getEntry, lastEntries, listEntries, searchEntries, } from './commands/entries.js';
2
+ import packageJson from '../package.json' with { type: 'json' };
3
+ import { API_PATHS, AuthRequiredError, apiGet } from './api-client.js';
4
+ import { loginFinish, loginInteractive, loginStart, revokeDeviceSession } from './auth.js';
5
+ import { parseGlobalFlags } from './cli-args.js';
6
+ import { createEntry, deleteEntry, getEntry, lastEntries, listEntries, searchEntries, updateEntry, } from './commands/entries.js';
5
7
  import { exportJournal } from './commands/export.js';
6
8
  import { deleteInvite, listInvites, newInvite } from './commands/invites.js';
7
9
  import { createJournal, deleteJournal, getJournal, listJournals, renameJournal, selectJournal, setJournalSlug, } from './commands/journals.js';
10
+ import { assignJournalPrompt, createPrompt, deletePrompt, getPrompt, listPrompts, unassignJournalPrompt, updatePrompt, } from './commands/prompts.js';
8
11
  import { deleteShare, listShares } from './commands/shares.js';
12
+ import { createTag, deleteTag, getTag, listTags, updateTag } from './commands/tags.js';
9
13
  import { getWorkspace, listWorkspaces, selectWorkspace } from './commands/workspaces.js';
10
14
  import { readConfig } from './config.js';
11
15
  import { CONFIG_DIR, clearCredentials, clearLoginState, readCredentials } from './credentials.js';
16
+ import { printTopLevelHelp, tryHandleHelp } from './help/_dispatch.js';
17
+ import { FEEDBACK_FOOTER } from './help/_renderer.js';
12
18
  import { findProjectConfig, getProjectConfigPath, rewriteProjectConfigAt, } from './project-config.js';
19
+ const FEEDBACK_URL = 'https://github.com/workjournal-pro/feedback/issues';
13
20
  // ── Arg parsing ─────────────────────────────────────────────────────────────
14
21
  const rawArgs = process.argv.slice(2);
15
- const jsonOutput = rawArgs.includes('--json');
16
- const args = rawArgs.filter((a) => a !== '--json');
22
+ // `parseGlobalFlags` strips `--json` / `--verbose`, preserves boolean flags
23
+ // (`--help` / `-h` / `--version` / `-V`) for the route handlers to see, and
24
+ // keeps `<flag> <value>` pairs intact so a literal `--json` body doesn't get
25
+ // silently swallowed. See `cli-args.ts` for the parser + its tests.
26
+ const { args, jsonOutput, verbose } = parseGlobalFlags(rawArgs);
17
27
  /** Parse args into flags (single-dash short or double-dash long) and positional args. */
18
28
  function parseFlags(argv) {
19
29
  const flags = {};
@@ -33,6 +43,23 @@ function parseFlags(argv) {
33
43
  }
34
44
  return { flags, positional };
35
45
  }
46
+ /**
47
+ * Parse the `--tags a,b,c` flag value into a deduplicated string array.
48
+ * Returns `undefined` when the flag was not provided (so the caller can
49
+ * distinguish "omit tags from PATCH" from "replace with empty set").
50
+ * An explicit empty value (`--tags ""`) clears the tag set on PATCH.
51
+ */
52
+ function parseTagsFlag(raw) {
53
+ if (raw === undefined)
54
+ return undefined;
55
+ const seen = new Set();
56
+ for (const part of raw.split(',')) {
57
+ const trimmed = part.trim();
58
+ if (trimmed.length > 0)
59
+ seen.add(trimmed);
60
+ }
61
+ return Array.from(seen);
62
+ }
36
63
  /**
37
64
  * Migrate a legacy `{ journal_id }` project config by walking workspaces and
38
65
  * journals to find the entry whose `id` matches. Rewrites the file in place
@@ -40,10 +67,9 @@ function parseFlags(argv) {
40
67
  * not authenticated).
41
68
  */
42
69
  async function migrateLegacyProjectConfig(journalId, configPath) {
43
- const token = await requireAuth();
44
70
  let workspaces;
45
71
  try {
46
- const result = await apiGet(API_PATHS.workspaces, token);
72
+ const result = await apiGet(API_PATHS.workspaces);
47
73
  workspaces = result.data;
48
74
  }
49
75
  catch {
@@ -51,7 +77,7 @@ async function migrateLegacyProjectConfig(journalId, configPath) {
51
77
  }
52
78
  for (const ws of workspaces) {
53
79
  try {
54
- const journals = await apiGet(API_PATHS.journals(ws.slug), token);
80
+ const journals = await apiGet(API_PATHS.journals(ws.slug));
55
81
  const match = journals.data.find((j) => j.id === journalId);
56
82
  if (match) {
57
83
  rewriteProjectConfigAt(configPath, ws.slug, match.slug);
@@ -97,6 +123,9 @@ async function resolveActiveSelection() {
97
123
  // ── Routing ─────────────────────────────────────────────────────────────────
98
124
  async function routeAuth(argv) {
99
125
  const sub = argv[0];
126
+ const helpKey = !sub || sub === '--help' || sub === '-h' ? 'auth' : `auth ${sub}`;
127
+ if (tryHandleHelp(helpKey, argv, jsonOutput))
128
+ return;
100
129
  switch (sub) {
101
130
  case 'login': {
102
131
  const loginSub = argv[1];
@@ -120,11 +149,16 @@ async function routeAuth(argv) {
120
149
  }
121
150
  break;
122
151
  }
123
- case 'logout':
152
+ case 'logout': {
153
+ const existing = readCredentials();
154
+ if (existing?.refresh_token) {
155
+ await revokeDeviceSession(existing.refresh_token);
156
+ }
124
157
  clearCredentials();
125
158
  clearLoginState();
126
159
  process.stdout.write('Logged out. Credentials removed.\n');
127
160
  break;
161
+ }
128
162
  case 'whoami': {
129
163
  const creds = readCredentials();
130
164
  if (!creds) {
@@ -156,6 +190,9 @@ async function routeAuth(argv) {
156
190
  }
157
191
  async function routeWorkspaces(argv) {
158
192
  const sub = argv[0];
193
+ const helpKey = !sub || sub === '--help' || sub === '-h' ? 'workspaces' : `workspaces ${sub}`;
194
+ if (tryHandleHelp(helpKey, argv, jsonOutput))
195
+ return;
159
196
  switch (sub) {
160
197
  case undefined:
161
198
  case 'list':
@@ -187,10 +224,13 @@ async function routeWorkspaces(argv) {
187
224
  }
188
225
  async function routeJournals(argv) {
189
226
  const sub = argv[0];
227
+ const helpKey = !sub || sub === '--help' || sub === '-h' ? 'journals' : `journals ${sub}`;
228
+ if (tryHandleHelp(helpKey, argv, jsonOutput))
229
+ return;
190
230
  if (sub === undefined) {
191
231
  // Shortcut: workjournal journals → show selected journal details
192
232
  const sel = await resolveActiveSelection();
193
- await getJournal(sel.workspaceSlug, sel.journalSlug, jsonOutput);
233
+ await getJournal(sel.workspaceSlug, sel.journalSlug, jsonOutput, verbose);
194
234
  return;
195
235
  }
196
236
  switch (sub) {
@@ -212,7 +252,7 @@ async function routeJournals(argv) {
212
252
  process.stderr.write('Usage: workjournal journals get <workspaceSlug> <journalSlug>\n');
213
253
  process.exit(1);
214
254
  }
215
- await getJournal(ws, j, jsonOutput);
255
+ await getJournal(ws, j, jsonOutput, verbose);
216
256
  return;
217
257
  }
218
258
  case 'new':
@@ -260,6 +300,27 @@ async function routeJournals(argv) {
260
300
  await setJournalSlug(ws, j, newSlug, jsonOutput);
261
301
  return;
262
302
  }
303
+ case 'assign-prompt': {
304
+ const ws = argv[1];
305
+ const j = argv[2];
306
+ const promptSlug = argv[3];
307
+ if (!ws || !j || !promptSlug) {
308
+ process.stderr.write('Usage: workjournal journals assign-prompt <workspaceSlug> <journalSlug> <promptSlug>\n');
309
+ process.exit(1);
310
+ }
311
+ await assignJournalPrompt(ws, j, promptSlug, jsonOutput);
312
+ return;
313
+ }
314
+ case 'unassign-prompt': {
315
+ const ws = argv[1];
316
+ const j = argv[2];
317
+ if (!ws || !j) {
318
+ process.stderr.write('Usage: workjournal journals unassign-prompt <workspaceSlug> <journalSlug>\n');
319
+ process.exit(1);
320
+ }
321
+ await unassignJournalPrompt(ws, j, jsonOutput);
322
+ return;
323
+ }
263
324
  case 'select': {
264
325
  const ws = argv[1];
265
326
  const j = argv[2];
@@ -272,12 +333,15 @@ async function routeJournals(argv) {
272
333
  }
273
334
  default:
274
335
  process.stderr.write(`Unknown journals subcommand: ${sub}\n` +
275
- 'Usage: workjournal journals <list|get|new|delete|rename|set-slug|select>\n');
336
+ 'Usage: workjournal journals <list|get|new|delete|rename|set-slug|assign-prompt|unassign-prompt|select>\n');
276
337
  process.exit(1);
277
338
  }
278
339
  }
279
340
  async function routeEntries(argv) {
280
341
  const sub = argv[0];
342
+ const helpKey = !sub || sub === '--help' || sub === '-h' ? 'entries' : `entries ${sub}`;
343
+ if (tryHandleHelp(helpKey, argv, jsonOutput))
344
+ return;
281
345
  switch (sub) {
282
346
  case undefined:
283
347
  case 'list': {
@@ -294,7 +358,7 @@ async function routeEntries(argv) {
294
358
  const ws = argv[1];
295
359
  const j = argv[2];
296
360
  if (!ws || !j) {
297
- process.stderr.write('Usage: workjournal entries write <workspaceSlug> <journalSlug> -t <title> -s <summary> -b <body>\n');
361
+ process.stderr.write('Usage: workjournal entries write <workspaceSlug> <journalSlug> -t <title> -s <summary> -b <body> [--tags <a,b,c>]\n');
298
362
  process.exit(1);
299
363
  }
300
364
  const { flags } = parseFlags(argv.slice(3));
@@ -302,10 +366,12 @@ async function routeEntries(argv) {
302
366
  const summary = flags['-s'] ?? flags['--summary'];
303
367
  const body = flags['-b'] ?? flags['--body'];
304
368
  if (!title || !summary || !body) {
305
- process.stderr.write('Usage: workjournal entries write <workspaceSlug> <journalSlug> -t <title> -s <summary> -b <body>\n');
369
+ process.stderr.write('Usage: workjournal entries write <workspaceSlug> <journalSlug> -t <title> -s <summary> -b <body> [--tags <a,b,c>]\n');
306
370
  process.exit(1);
307
371
  }
308
- await createEntry(ws, j, title, summary, body, jsonOutput);
372
+ const tagsRaw = flags['--tags'];
373
+ const tags = parseTagsFlag(tagsRaw);
374
+ await createEntry(ws, j, title, summary, body, tags, jsonOutput);
309
375
  return;
310
376
  }
311
377
  case 'last': {
@@ -334,6 +400,28 @@ async function routeEntries(argv) {
334
400
  await getEntry(ws, j, index, jsonOutput);
335
401
  return;
336
402
  }
403
+ case 'update': {
404
+ const ws = argv[1];
405
+ const j = argv[2];
406
+ const index = argv[3];
407
+ if (!ws || !j || !index) {
408
+ process.stderr.write('Usage: workjournal entries update <workspaceSlug> <journalSlug> <index> [-t <title>] [-s <summary>] [-b <file>|-] [--tags <a,b,c>]\n');
409
+ process.exit(1);
410
+ }
411
+ const { flags } = parseFlags(argv.slice(4));
412
+ const title = flags['-t'] ?? flags['--title'];
413
+ const summary = flags['-s'] ?? flags['--summary'];
414
+ const bodySource = flags['-b'] ?? flags['--body'];
415
+ const tagsRaw = flags['--tags'];
416
+ const tags = parseTagsFlag(tagsRaw);
417
+ await updateEntry(ws, j, index, {
418
+ ...(title !== undefined ? { title } : {}),
419
+ ...(summary !== undefined ? { summary } : {}),
420
+ ...(bodySource !== undefined ? { bodySource } : {}),
421
+ ...(tags !== undefined ? { tags } : {}),
422
+ }, jsonOutput);
423
+ return;
424
+ }
337
425
  case 'delete': {
338
426
  const ws = argv[1];
339
427
  const j = argv[2];
@@ -358,12 +446,180 @@ async function routeEntries(argv) {
358
446
  }
359
447
  default:
360
448
  process.stderr.write(`Unknown entries subcommand: ${sub}\n` +
361
- 'Usage: workjournal entries <list|write|last|get|delete|search>\n');
449
+ 'Usage: workjournal entries <list|write|last|get|update|delete|search>\n');
450
+ process.exit(1);
451
+ }
452
+ }
453
+ async function routePrompts(argv) {
454
+ const sub = argv[0];
455
+ const helpKey = !sub || sub === '--help' || sub === '-h' ? 'prompts' : `prompts ${sub}`;
456
+ if (tryHandleHelp(helpKey, argv, jsonOutput))
457
+ return;
458
+ switch (sub) {
459
+ case undefined:
460
+ case 'list': {
461
+ const ws = argv[1] ?? readConfig().workspaceSlug;
462
+ if (!ws) {
463
+ process.stderr.write('Usage: workjournal prompts list <workspaceSlug>\n' +
464
+ '(or run `workjournal workspaces select <slug>` first)\n');
465
+ process.exit(1);
466
+ }
467
+ await listPrompts(ws, jsonOutput);
468
+ return;
469
+ }
470
+ case 'new':
471
+ case 'create': {
472
+ const { flags, positional } = parseFlags(argv.slice(1));
473
+ const ws = positional[0];
474
+ const name = positional[1];
475
+ if (!ws || !name) {
476
+ process.stderr.write('Usage: workjournal prompts new <workspaceSlug> <name> [-b <body>] [--slug <slug>]\n');
477
+ process.exit(1);
478
+ }
479
+ const body = flags['-b'] ?? flags['--body'] ?? '';
480
+ const slug = flags['--slug'];
481
+ await createPrompt(ws, name, body, slug, jsonOutput);
482
+ return;
483
+ }
484
+ case 'get': {
485
+ const ws = argv[1];
486
+ const slug = argv[2];
487
+ if (!ws || !slug) {
488
+ process.stderr.write('Usage: workjournal prompts get <workspaceSlug> <promptSlug>\n');
489
+ process.exit(1);
490
+ }
491
+ await getPrompt(ws, slug, jsonOutput);
492
+ return;
493
+ }
494
+ case 'update': {
495
+ const { flags, positional } = parseFlags(argv.slice(1));
496
+ const ws = positional[0];
497
+ const slug = positional[1];
498
+ if (!ws || !slug) {
499
+ process.stderr.write('Usage: workjournal prompts update <workspaceSlug> <promptSlug> [-n <name>] [-b <body>]\n');
500
+ process.exit(1);
501
+ }
502
+ const updates = {};
503
+ const name = flags['-n'] ?? flags['--name'];
504
+ const body = flags['-b'] ?? flags['--body'];
505
+ if (name !== undefined)
506
+ updates.name = name;
507
+ if (body !== undefined)
508
+ updates.body = body;
509
+ if (Object.keys(updates).length === 0) {
510
+ process.stderr.write('At least one of -n/--name or -b/--body is required.\n');
511
+ process.exit(1);
512
+ }
513
+ await updatePrompt(ws, slug, updates, jsonOutput);
514
+ return;
515
+ }
516
+ case 'delete': {
517
+ const ws = argv[1];
518
+ const slug = argv[2];
519
+ if (!ws || !slug) {
520
+ process.stderr.write('Usage: workjournal prompts delete <workspaceSlug> <promptSlug>\n');
521
+ process.exit(1);
522
+ }
523
+ await deletePrompt(ws, slug, jsonOutput);
524
+ return;
525
+ }
526
+ default:
527
+ process.stderr.write(`Unknown prompts subcommand: ${sub}\n` +
528
+ 'Usage: workjournal prompts <list|new|get|update|delete>\n');
529
+ process.exit(1);
530
+ }
531
+ }
532
+ async function routeTags(argv) {
533
+ const sub = argv[0];
534
+ const helpKey = !sub || sub === '--help' || sub === '-h' ? 'tags' : `tags ${sub}`;
535
+ if (tryHandleHelp(helpKey, argv, jsonOutput))
536
+ return;
537
+ switch (sub) {
538
+ case undefined:
539
+ case 'list': {
540
+ const { flags, positional } = parseFlags(argv.slice(1));
541
+ const ws = positional[0] ?? readConfig().workspaceSlug;
542
+ if (!ws) {
543
+ process.stderr.write('Usage: workjournal tags list <workspaceSlug> [--journal <journalSlug>]\n' +
544
+ '(or run `workjournal workspaces select <slug>` first)\n');
545
+ process.exit(1);
546
+ }
547
+ const journalSlug = flags['--journal'] ?? flags['-j'];
548
+ await listTags(ws, journalSlug, jsonOutput);
549
+ return;
550
+ }
551
+ case 'new':
552
+ case 'create': {
553
+ const { flags, positional } = parseFlags(argv.slice(1));
554
+ const ws = positional[0];
555
+ const name = positional[1];
556
+ if (!ws || !name) {
557
+ process.stderr.write('Usage: workjournal tags new <workspaceSlug> <name> [-d <description>] [--journal <journalSlug>]\n');
558
+ process.exit(1);
559
+ }
560
+ const description = flags['-d'] ?? flags['--description'];
561
+ const journalSlug = flags['--journal'] ?? flags['-j'];
562
+ await createTag(ws, name, description, journalSlug, jsonOutput);
563
+ return;
564
+ }
565
+ case 'get': {
566
+ const { flags, positional } = parseFlags(argv.slice(1));
567
+ const ws = positional[0];
568
+ const name = positional[1];
569
+ if (!ws || !name) {
570
+ process.stderr.write('Usage: workjournal tags get <workspaceSlug> <tagName> [--journal <journalSlug>]\n');
571
+ process.exit(1);
572
+ }
573
+ const journalSlug = flags['--journal'] ?? flags['-j'];
574
+ await getTag(ws, name, journalSlug, jsonOutput);
575
+ return;
576
+ }
577
+ case 'update': {
578
+ const { flags, positional } = parseFlags(argv.slice(1));
579
+ const ws = positional[0];
580
+ const name = positional[1];
581
+ if (!ws || !name) {
582
+ process.stderr.write('Usage: workjournal tags update <workspaceSlug> <tagName> [-n <newName>] [-d <description>] [--journal <journalSlug>]\n');
583
+ process.exit(1);
584
+ }
585
+ const updates = {};
586
+ const newName = flags['-n'] ?? flags['--name'];
587
+ const description = flags['-d'] ?? flags['--description'];
588
+ if (newName !== undefined)
589
+ updates.name = newName;
590
+ if (description !== undefined)
591
+ updates.description = description;
592
+ if (Object.keys(updates).length === 0) {
593
+ process.stderr.write('At least one of -n/--name or -d/--description is required.\n');
594
+ process.exit(1);
595
+ }
596
+ const journalSlug = flags['--journal'] ?? flags['-j'];
597
+ await updateTag(ws, name, updates, journalSlug, jsonOutput);
598
+ return;
599
+ }
600
+ case 'delete': {
601
+ const { flags, positional } = parseFlags(argv.slice(1));
602
+ const ws = positional[0];
603
+ const name = positional[1];
604
+ if (!ws || !name) {
605
+ process.stderr.write('Usage: workjournal tags delete <workspaceSlug> <tagName> [--journal <journalSlug>]\n');
606
+ process.exit(1);
607
+ }
608
+ const journalSlug = flags['--journal'] ?? flags['-j'];
609
+ await deleteTag(ws, name, journalSlug, jsonOutput);
610
+ return;
611
+ }
612
+ default:
613
+ process.stderr.write(`Unknown tags subcommand: ${sub}\n` +
614
+ 'Usage: workjournal tags <list|new|get|update|delete>\n');
362
615
  process.exit(1);
363
616
  }
364
617
  }
365
618
  async function routeShares(argv) {
366
619
  const sub = argv[0];
620
+ const helpKey = !sub || sub === '--help' || sub === '-h' ? 'shares' : `shares ${sub}`;
621
+ if (tryHandleHelp(helpKey, argv, jsonOutput))
622
+ return;
367
623
  switch (sub) {
368
624
  case undefined:
369
625
  case 'list': {
@@ -394,6 +650,9 @@ async function routeShares(argv) {
394
650
  }
395
651
  async function routeInvites(argv) {
396
652
  const sub = argv[0];
653
+ const helpKey = !sub || sub === '--help' || sub === '-h' ? 'invites' : `invites ${sub}`;
654
+ if (tryHandleHelp(helpKey, argv, jsonOutput))
655
+ return;
397
656
  switch (sub) {
398
657
  case undefined:
399
658
  case 'list': {
@@ -435,10 +694,13 @@ async function routeInvites(argv) {
435
694
  }
436
695
  }
437
696
  async function routeExport(argv) {
697
+ if (tryHandleHelp('export', argv, jsonOutput))
698
+ return;
438
699
  const ws = argv[0];
439
700
  const j = argv[1];
440
701
  if (!ws || !j) {
441
- process.stderr.write('Usage: workjournal export <workspaceSlug> <journalSlug> [-f json|md|csv] [-p <path>]\n');
702
+ process.stderr.write('Usage: workjournal export <workspaceSlug> <journalSlug> [-f json|md|csv] [-p <path>]\n' +
703
+ 'Run `workjournal export --help` for details.\n');
442
704
  process.exit(1);
443
705
  }
444
706
  const { flags } = parseFlags(argv.slice(2));
@@ -452,6 +714,9 @@ async function routeExport(argv) {
452
714
  }
453
715
  async function routeConfig(argv) {
454
716
  const sub = argv[0];
717
+ const helpKey = !sub || sub === '--help' || sub === '-h' ? 'config show' : `config ${sub}`;
718
+ if (tryHandleHelp(helpKey, argv, jsonOutput))
719
+ return;
455
720
  if (sub === 'show') {
456
721
  const projectPath = getProjectConfigPath();
457
722
  const stored = findProjectConfig();
@@ -490,71 +755,20 @@ async function routeConfig(argv) {
490
755
  }
491
756
  }
492
757
  // ── Help ────────────────────────────────────────────────────────────────────
493
- function printHelp() {
494
- process.stdout.write(`workjournal — CLI for Workjournal (https://workjournal.pro)
495
-
496
- Usage:
497
- workjournal List entries in selected journal
498
-
499
- Workspaces:
500
- workjournal workspaces list List your workspaces
501
- workjournal workspaces get <ws> Show workspace details
502
- workjournal workspaces select <ws> Set active workspace
503
-
504
- Journals:
505
- workjournal journals list [<ws>] List journals in workspace
506
- workjournal journals list shared-with-me List journals shared with you
507
- workjournal journals get <ws> <j> Show journal details
508
- workjournal journals new <ws> <name> [--slug <slug>] Create a new journal
509
- workjournal journals delete <ws> <j> Delete a journal
510
- workjournal journals rename <ws> <j> <newName> Rename a journal
511
- workjournal journals set-slug <ws> <j> <newSlug> Change a journal's slug
512
- workjournal journals select <ws> <j> Set active journal
513
- workjournal journals Show selected journal details
514
-
515
- Entries:
516
- workjournal entries list <ws> <j> List entries
517
- workjournal entries write <ws> <j> -t <title> -s <summary> -b <body>
518
- Create a new entry
519
- workjournal entries last <ws> <j> [count] Show most recent entries (full body)
520
- workjournal entries get <ws> <j> <index> Show a single entry by index
521
- workjournal entries delete <ws> <j> <index> Delete an entry by index
522
- workjournal entries search <ws> <j> <query> Search entries
523
-
524
- Shares (members):
525
- workjournal shares list <ws> <j> List members
526
- workjournal shares delete <ws> <j> <email> Remove a member
527
-
528
- Invites:
529
- workjournal invites list <ws> <j> List invitations
530
- workjournal invites new <ws> <j> <email> Invite a collaborator
531
- workjournal invites delete <ws> <j> <invitationId> Revoke an invitation
532
-
533
- Export:
534
- workjournal export <ws> <j> [-f json|md|csv] [-p <path>]
535
- Export journal data
536
-
537
- Auth:
538
- workjournal auth login Interactive login
539
- workjournal auth login start Print authorize URL (headless)
540
- workjournal auth login finish <CODE> Exchange code for credentials
541
- workjournal auth logout Remove stored credentials
542
- workjournal auth whoami Show current auth status
543
- workjournal auth status Show detailed auth status
544
-
545
- Config:
546
- workjournal config show Show resolved config
547
-
548
- Flags:
549
- --json Output raw JSON instead of tables
550
- `);
758
+ function printVersion() {
759
+ process.stdout.write(`@workjournal/cli ${packageJson.version}\n`);
760
+ process.stdout.write(`${FEEDBACK_FOOTER}\n`);
551
761
  }
552
762
  // ── Main ────────────────────────────────────────────────────────────────────
553
763
  async function run() {
554
764
  try {
555
765
  const command = args[0];
556
- if (command === 'help' || command === '--help') {
557
- printHelp();
766
+ if (command === 'help' || command === '--help' || command === '-h') {
767
+ printTopLevelHelp(jsonOutput);
768
+ return;
769
+ }
770
+ if (command === '--version' || command === '-V') {
771
+ printVersion();
558
772
  return;
559
773
  }
560
774
  if (command === undefined) {
@@ -572,6 +786,8 @@ async function run() {
572
786
  break;
573
787
  case 'journal': {
574
788
  // Shortcut: show selected journal details (matches docs/cli-commands.md)
789
+ if (tryHandleHelp('journal', args.slice(1), jsonOutput))
790
+ break;
575
791
  const sel = await resolveActiveSelection();
576
792
  if (args.length > 1) {
577
793
  process.stderr.write('`workjournal journal` shows the selected journal. Use `workjournal entries`, ' +
@@ -579,7 +795,7 @@ async function run() {
579
795
  'resource verbs.\n');
580
796
  process.exit(1);
581
797
  }
582
- await getJournal(sel.workspaceSlug, sel.journalSlug, jsonOutput);
798
+ await getJournal(sel.workspaceSlug, sel.journalSlug, jsonOutput, verbose);
583
799
  break;
584
800
  }
585
801
  case 'journals':
@@ -588,6 +804,12 @@ async function run() {
588
804
  case 'entries':
589
805
  await routeEntries(args.slice(1));
590
806
  break;
807
+ case 'prompts':
808
+ await routePrompts(args.slice(1));
809
+ break;
810
+ case 'tags':
811
+ await routeTags(args.slice(1));
812
+ break;
591
813
  case 'shares':
592
814
  await routeShares(args.slice(1));
593
815
  break;
@@ -601,12 +823,22 @@ async function run() {
601
823
  await routeConfig(args.slice(1));
602
824
  break;
603
825
  default:
604
- printHelp();
826
+ printTopLevelHelp(jsonOutput);
605
827
  process.stderr.write(`\nUnknown command: ${command}\n`);
828
+ process.stderr.write(`If this is a bug, please report it: ${FEEDBACK_URL}\n`);
606
829
  process.exit(1);
607
830
  }
608
831
  }
609
832
  catch (err) {
833
+ if (err instanceof AuthRequiredError) {
834
+ if (err.reason === 'not-logged-in') {
835
+ process.stderr.write('Not logged in. Run `workjournal auth login` to authenticate.\n');
836
+ }
837
+ else {
838
+ process.stderr.write('Authentication expired. Run `workjournal auth login` to re-authenticate.\n');
839
+ }
840
+ process.exit(1);
841
+ }
610
842
  const message = err instanceof Error ? err.message : String(err);
611
843
  if (message.includes('fetch') ||
612
844
  message.includes('ECONNREFUSED') ||
@@ -616,6 +848,7 @@ async function run() {
616
848
  else {
617
849
  process.stderr.write(`Error: ${message}\n`);
618
850
  }
851
+ process.stderr.write(`If this looks like a bug, please report it: ${FEEDBACK_URL}\n`);
619
852
  process.exit(1);
620
853
  }
621
854
  }