@workjournal/cli 0.3.4 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +47 -19
  2. package/dist/api-client.d.ts +23 -10
  3. package/dist/api-client.d.ts.map +1 -1
  4. package/dist/api-client.js +7 -13
  5. package/dist/api-client.js.map +1 -1
  6. package/dist/commands/entries.d.ts +6 -6
  7. package/dist/commands/entries.d.ts.map +1 -1
  8. package/dist/commands/entries.js +12 -12
  9. package/dist/commands/entries.js.map +1 -1
  10. package/dist/commands/export.d.ts +1 -1
  11. package/dist/commands/export.d.ts.map +1 -1
  12. package/dist/commands/export.js +2 -2
  13. package/dist/commands/export.js.map +1 -1
  14. package/dist/commands/invites.d.ts +3 -3
  15. package/dist/commands/invites.d.ts.map +1 -1
  16. package/dist/commands/invites.js +6 -8
  17. package/dist/commands/invites.js.map +1 -1
  18. package/dist/commands/journals.d.ts +5 -5
  19. package/dist/commands/journals.d.ts.map +1 -1
  20. package/dist/commands/journals.js +24 -19
  21. package/dist/commands/journals.js.map +1 -1
  22. package/dist/commands/shares.d.ts +2 -2
  23. package/dist/commands/shares.d.ts.map +1 -1
  24. package/dist/commands/shares.js +5 -5
  25. package/dist/commands/shares.js.map +1 -1
  26. package/dist/commands/workspaces.d.ts +5 -0
  27. package/dist/commands/workspaces.d.ts.map +1 -0
  28. package/dist/commands/workspaces.js +62 -0
  29. package/dist/commands/workspaces.js.map +1 -0
  30. package/dist/config.d.ts +10 -1
  31. package/dist/config.d.ts.map +1 -1
  32. package/dist/config.js +9 -1
  33. package/dist/config.js.map +1 -1
  34. package/dist/index.js +356 -162
  35. package/dist/index.js.map +1 -1
  36. package/dist/project-config.d.ts +34 -4
  37. package/dist/project-config.d.ts.map +1 -1
  38. package/dist/project-config.js +39 -8
  39. package/dist/project-config.js.map +1 -1
  40. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -1,13 +1,15 @@
1
1
  #!/usr/bin/env node
2
+ import { API_PATHS, apiGet, requireAuth } from './api-client.js';
2
3
  import { loginFinish, loginInteractive, loginStart } from './auth.js';
3
4
  import { createEntry, deleteEntry, getEntry, lastEntries, listEntries, searchEntries, } from './commands/entries.js';
4
5
  import { exportJournal } from './commands/export.js';
5
6
  import { deleteInvite, listInvites, newInvite } from './commands/invites.js';
6
7
  import { createJournal, deleteJournal, getJournal, listJournals, selectJournal, } from './commands/journals.js';
7
8
  import { deleteShare, listShares } from './commands/shares.js';
9
+ import { createWorkspace, getWorkspace, listWorkspaces, selectWorkspace, } from './commands/workspaces.js';
8
10
  import { readConfig } from './config.js';
9
11
  import { CONFIG_DIR, clearCredentials, clearLoginState, readCredentials } from './credentials.js';
10
- import { findProjectConfig, getProjectConfigPath } from './project-config.js';
12
+ import { findProjectConfig, getProjectConfigPath, rewriteProjectConfigAt, } from './project-config.js';
11
13
  // ── Arg parsing ─────────────────────────────────────────────────────────────
12
14
  const rawArgs = process.argv.slice(2);
13
15
  const jsonOutput = rawArgs.includes('--json');
@@ -31,17 +33,64 @@ function parseFlags(argv) {
31
33
  }
32
34
  return { flags, positional };
33
35
  }
34
- // ── Journal ID resolution ───────────────────────────────────────────────────
35
- function resolveJournalId() {
36
- const projectConfig = findProjectConfig();
37
- if (projectConfig)
38
- return projectConfig.journal_id;
36
+ /**
37
+ * Migrate a legacy `{ journal_id }` project config by walking workspaces and
38
+ * journals to find the entry whose `id` matches. Rewrites the file in place
39
+ * on success. Returns `null` if migration can't run (offline, deleted journal,
40
+ * not authenticated).
41
+ */
42
+ async function migrateLegacyProjectConfig(journalId, configPath) {
43
+ const token = await requireAuth();
44
+ let workspaces;
45
+ try {
46
+ const result = await apiGet(API_PATHS.workspaces, token);
47
+ workspaces = result.data;
48
+ }
49
+ catch {
50
+ return null;
51
+ }
52
+ for (const ws of workspaces) {
53
+ try {
54
+ const journals = await apiGet(API_PATHS.journals(ws.slug), token);
55
+ const match = journals.data.find((j) => j.id === journalId);
56
+ if (match) {
57
+ rewriteProjectConfigAt(configPath, ws.slug, match.slug);
58
+ process.stderr.write(`Migrated .workjournal: journal_id ${journalId} → ${ws.slug}/${match.slug}\n`);
59
+ return { workspaceSlug: ws.slug, journalSlug: match.slug };
60
+ }
61
+ }
62
+ catch {
63
+ // Skip this workspace and try the next one.
64
+ }
65
+ }
66
+ return null;
67
+ }
68
+ async function resolveActiveSelection() {
69
+ const stored = findProjectConfig();
70
+ if (stored) {
71
+ if (stored.kind === 'v2')
72
+ return stored.config;
73
+ // Legacy v1 — try to migrate by resolving the UUID via the API.
74
+ process.stderr.write(`Found legacy .workjournal at ${stored.path} (journal_id: ${stored.config.journal_id}).\n` +
75
+ 'Attempting one-shot migration to slug-based selection…\n');
76
+ const migrated = await migrateLegacyProjectConfig(stored.config.journal_id, stored.path);
77
+ if (migrated)
78
+ return migrated;
79
+ process.stderr.write('\nCould not migrate the legacy selection. The journal may have been deleted, or you are offline.\n' +
80
+ 'Re-select with:\n' +
81
+ ' workjournal workspaces list\n' +
82
+ ' workjournal journals list <workspaceSlug>\n' +
83
+ ' workjournal journals select <workspaceSlug> <journalSlug>\n');
84
+ process.exit(1);
85
+ }
39
86
  const config = readConfig();
40
- if (config.journal)
41
- return config.journal;
87
+ if (config.workspaceSlug && config.journalSlug) {
88
+ return { workspaceSlug: config.workspaceSlug, journalSlug: config.journalSlug };
89
+ }
42
90
  process.stderr.write(`No journal selected. To select one:
43
- workjournal journals list List available journals
44
- workjournal journals select <id> Set active journal
91
+ workjournal workspaces list
92
+ workjournal journals list <workspaceSlug>
93
+ workjournal journals select <workspaceSlug> <journalSlug>
45
94
  `);
46
95
  process.exit(1);
47
96
  }
@@ -105,207 +154,320 @@ async function routeAuth(argv) {
105
154
  process.exit(1);
106
155
  }
107
156
  }
108
- async function routeEntries(journalId, argv) {
157
+ async function routeWorkspaces(argv) {
109
158
  const sub = argv[0];
110
159
  switch (sub) {
111
160
  case undefined:
112
161
  case 'list':
113
- await listEntries(journalId, jsonOutput);
162
+ await listWorkspaces(jsonOutput);
114
163
  break;
115
- case 'write': {
116
- const { flags } = parseFlags(argv.slice(1));
117
- const summary = flags['-s'] ?? flags['--summary'];
118
- const body = flags['-b'] ?? flags['--body'];
119
- if (!summary || !body) {
120
- process.stderr.write('Usage: workjournal journals <id> entries write -s <summary> -b <body>\n');
164
+ case 'get': {
165
+ const slug = argv[1];
166
+ if (!slug) {
167
+ process.stderr.write('Usage: workjournal workspaces get <workspaceSlug>\n');
121
168
  process.exit(1);
122
169
  }
123
- await createEntry(journalId, summary, body, jsonOutput);
170
+ await getWorkspace(slug, jsonOutput);
124
171
  break;
125
172
  }
126
- case 'last': {
127
- const count = Number.parseInt(argv[1] ?? '1', 10);
128
- if (Number.isNaN(count) || count < 1) {
129
- process.stderr.write('Usage: workjournal journals <id> entries last [count]\n');
173
+ case 'new':
174
+ case 'create': {
175
+ const { flags, positional } = parseFlags(argv.slice(1));
176
+ const name = positional[0];
177
+ if (!name) {
178
+ process.stderr.write('Usage: workjournal workspaces new <name> [--slug <slug>]\n');
130
179
  process.exit(1);
131
180
  }
132
- await lastEntries(journalId, count, jsonOutput);
181
+ const slug = flags['--slug'];
182
+ await createWorkspace(name, slug, jsonOutput);
133
183
  break;
134
184
  }
135
- case 'get': {
136
- const index = argv[1];
137
- if (!index) {
138
- process.stderr.write('Usage: workjournal journals <id> entries get <index>\n');
185
+ case 'select': {
186
+ const slug = argv[1];
187
+ if (!slug) {
188
+ process.stderr.write('Usage: workjournal workspaces select <workspaceSlug>\n');
139
189
  process.exit(1);
140
190
  }
141
- await getEntry(journalId, index, jsonOutput);
191
+ await selectWorkspace(slug);
142
192
  break;
143
193
  }
194
+ default:
195
+ process.stderr.write(`Unknown workspaces subcommand: ${sub}\n` +
196
+ 'Usage: workjournal workspaces <list|get|new|select>\n');
197
+ process.exit(1);
198
+ }
199
+ }
200
+ async function routeJournals(argv) {
201
+ const sub = argv[0];
202
+ if (sub === undefined) {
203
+ // Shortcut: workjournal journals → show selected journal details
204
+ const sel = await resolveActiveSelection();
205
+ await getJournal(sel.workspaceSlug, sel.journalSlug, jsonOutput);
206
+ return;
207
+ }
208
+ switch (sub) {
209
+ case 'list': {
210
+ // `list [workspaceSlug]` — falls back to selected workspace if omitted.
211
+ const wsSlug = argv[1] ?? readConfig().workspaceSlug;
212
+ if (!wsSlug) {
213
+ process.stderr.write('Usage: workjournal journals list <workspaceSlug>\n' +
214
+ '(or run `workjournal workspaces select <slug>` first)\n');
215
+ process.exit(1);
216
+ }
217
+ await listJournals(wsSlug, jsonOutput);
218
+ return;
219
+ }
220
+ case 'get': {
221
+ const ws = argv[1];
222
+ const j = argv[2];
223
+ if (!ws || !j) {
224
+ process.stderr.write('Usage: workjournal journals get <workspaceSlug> <journalSlug>\n');
225
+ process.exit(1);
226
+ }
227
+ await getJournal(ws, j, jsonOutput);
228
+ return;
229
+ }
230
+ case 'new':
231
+ case 'create': {
232
+ const { flags, positional } = parseFlags(argv.slice(1));
233
+ const ws = positional[0];
234
+ const name = positional[1];
235
+ if (!ws || !name) {
236
+ process.stderr.write('Usage: workjournal journals new <workspaceSlug> <name> [--slug <slug>]\n');
237
+ process.exit(1);
238
+ }
239
+ const slug = flags['--slug'];
240
+ await createJournal(ws, name, slug, jsonOutput);
241
+ return;
242
+ }
144
243
  case 'delete': {
145
- const index = argv[1];
146
- if (!index) {
147
- process.stderr.write('Usage: workjournal journals <id> entries delete <index>\n');
244
+ const ws = argv[1];
245
+ const j = argv[2];
246
+ if (!ws || !j) {
247
+ process.stderr.write('Usage: workjournal journals delete <workspaceSlug> <journalSlug>\n');
148
248
  process.exit(1);
149
249
  }
150
- await deleteEntry(journalId, index, jsonOutput);
151
- break;
250
+ await deleteJournal(ws, j, jsonOutput);
251
+ return;
152
252
  }
153
- case 'search': {
154
- const query = argv[1];
155
- if (!query) {
156
- process.stderr.write('Usage: workjournal journals <id> entries search <query>\n');
253
+ case 'select': {
254
+ const ws = argv[1];
255
+ const j = argv[2];
256
+ if (!ws || !j) {
257
+ process.stderr.write('Usage: workjournal journals select <workspaceSlug> <journalSlug>\n');
157
258
  process.exit(1);
158
259
  }
159
- await searchEntries(journalId, query, jsonOutput);
160
- break;
260
+ await selectJournal(ws, j);
261
+ return;
161
262
  }
162
263
  default:
163
- process.stderr.write(`Unknown entries subcommand: ${sub}\n`);
264
+ process.stderr.write(`Unknown journals subcommand: ${sub}\n` +
265
+ 'Usage: workjournal journals <list|get|new|delete|select>\n');
164
266
  process.exit(1);
165
267
  }
166
268
  }
167
- async function routeShares(journalId, argv) {
269
+ async function routeEntries(argv) {
168
270
  const sub = argv[0];
169
271
  switch (sub) {
170
272
  case undefined:
171
- case 'list':
172
- await listShares(journalId, jsonOutput);
173
- break;
273
+ case 'list': {
274
+ const ws = argv[1];
275
+ const j = argv[2];
276
+ if (!ws || !j) {
277
+ process.stderr.write('Usage: workjournal entries list <workspaceSlug> <journalSlug>\n');
278
+ process.exit(1);
279
+ }
280
+ await listEntries(ws, j, jsonOutput);
281
+ return;
282
+ }
283
+ case 'write': {
284
+ const ws = argv[1];
285
+ const j = argv[2];
286
+ if (!ws || !j) {
287
+ process.stderr.write('Usage: workjournal entries write <workspaceSlug> <journalSlug> -s <summary> -b <body>\n');
288
+ process.exit(1);
289
+ }
290
+ const { flags } = parseFlags(argv.slice(3));
291
+ const summary = flags['-s'] ?? flags['--summary'];
292
+ const body = flags['-b'] ?? flags['--body'];
293
+ if (!summary || !body) {
294
+ process.stderr.write('Usage: workjournal entries write <workspaceSlug> <journalSlug> -s <summary> -b <body>\n');
295
+ process.exit(1);
296
+ }
297
+ await createEntry(ws, j, summary, body, jsonOutput);
298
+ return;
299
+ }
300
+ case 'last': {
301
+ const ws = argv[1];
302
+ const j = argv[2];
303
+ if (!ws || !j) {
304
+ process.stderr.write('Usage: workjournal entries last <workspaceSlug> <journalSlug> [count]\n');
305
+ process.exit(1);
306
+ }
307
+ const count = Number.parseInt(argv[3] ?? '1', 10);
308
+ if (Number.isNaN(count) || count < 1) {
309
+ process.stderr.write('Usage: workjournal entries last <workspaceSlug> <journalSlug> [count]\n');
310
+ process.exit(1);
311
+ }
312
+ await lastEntries(ws, j, count, jsonOutput);
313
+ return;
314
+ }
315
+ case 'get': {
316
+ const ws = argv[1];
317
+ const j = argv[2];
318
+ const index = argv[3];
319
+ if (!ws || !j || !index) {
320
+ process.stderr.write('Usage: workjournal entries get <workspaceSlug> <journalSlug> <index>\n');
321
+ process.exit(1);
322
+ }
323
+ await getEntry(ws, j, index, jsonOutput);
324
+ return;
325
+ }
174
326
  case 'delete': {
175
- const email = argv[1];
176
- if (!email) {
177
- process.stderr.write('Usage: workjournal journals <id> shares delete <email>\n');
327
+ const ws = argv[1];
328
+ const j = argv[2];
329
+ const index = argv[3];
330
+ if (!ws || !j || !index) {
331
+ process.stderr.write('Usage: workjournal entries delete <workspaceSlug> <journalSlug> <index>\n');
178
332
  process.exit(1);
179
333
  }
180
- await deleteShare(journalId, email, jsonOutput);
181
- break;
334
+ await deleteEntry(ws, j, index, jsonOutput);
335
+ return;
336
+ }
337
+ case 'search': {
338
+ const ws = argv[1];
339
+ const j = argv[2];
340
+ const query = argv[3];
341
+ if (!ws || !j || !query) {
342
+ process.stderr.write('Usage: workjournal entries search <workspaceSlug> <journalSlug> <query>\n');
343
+ process.exit(1);
344
+ }
345
+ await searchEntries(ws, j, query, jsonOutput);
346
+ return;
182
347
  }
183
348
  default:
184
- process.stderr.write(`Unknown shares subcommand: ${sub}\n`);
349
+ process.stderr.write(`Unknown entries subcommand: ${sub}\n` +
350
+ 'Usage: workjournal entries <list|write|last|get|delete|search>\n');
185
351
  process.exit(1);
186
352
  }
187
353
  }
188
- async function routeInvites(journalId, argv) {
354
+ async function routeShares(argv) {
189
355
  const sub = argv[0];
190
356
  switch (sub) {
191
357
  case undefined:
192
- case 'list':
193
- await listInvites(journalId, jsonOutput);
194
- break;
195
- case 'new': {
196
- const email = argv[1];
197
- if (!email) {
198
- process.stderr.write('Usage: workjournal journals <id> invites new <email>\n');
358
+ case 'list': {
359
+ const ws = argv[1];
360
+ const j = argv[2];
361
+ if (!ws || !j) {
362
+ process.stderr.write('Usage: workjournal shares list <workspaceSlug> <journalSlug>\n');
199
363
  process.exit(1);
200
364
  }
201
- await newInvite(journalId, email, jsonOutput);
202
- break;
365
+ await listShares(ws, j, jsonOutput);
366
+ return;
203
367
  }
204
368
  case 'delete': {
205
- const invitationId = argv[1];
206
- if (!invitationId) {
207
- process.stderr.write('Usage: workjournal journals <id> invites delete <invitation_id>\n');
369
+ const ws = argv[1];
370
+ const j = argv[2];
371
+ const email = argv[3];
372
+ if (!ws || !j || !email) {
373
+ process.stderr.write('Usage: workjournal shares delete <workspaceSlug> <journalSlug> <email>\n');
208
374
  process.exit(1);
209
375
  }
210
- await deleteInvite(journalId, invitationId, jsonOutput);
211
- break;
376
+ await deleteShare(ws, j, email, jsonOutput);
377
+ return;
212
378
  }
213
379
  default:
214
- process.stderr.write(`Unknown invites subcommand: ${sub}\n`);
380
+ process.stderr.write(`Unknown shares subcommand: ${sub}\nUsage: workjournal shares <list|delete>\n`);
215
381
  process.exit(1);
216
382
  }
217
383
  }
218
- async function routeJournalResource(journalId, argv) {
384
+ async function routeInvites(argv) {
219
385
  const sub = argv[0];
220
386
  switch (sub) {
221
387
  case undefined:
222
- await getJournal(journalId, jsonOutput);
223
- break;
224
- case 'entries':
225
- await routeEntries(journalId, argv.slice(1));
226
- break;
227
- case 'shares':
228
- await routeShares(journalId, argv.slice(1));
229
- break;
230
- case 'invites':
231
- await routeInvites(journalId, argv.slice(1));
232
- break;
233
- case 'export': {
234
- const { flags } = parseFlags(argv.slice(1));
235
- const format = flags['-f'] ?? flags['--format'] ?? 'json';
236
- if (!['json', 'md', 'csv'].includes(format)) {
237
- process.stderr.write('Invalid format. Use: -f json|md|csv\n');
388
+ case 'list': {
389
+ const ws = argv[1];
390
+ const j = argv[2];
391
+ if (!ws || !j) {
392
+ process.stderr.write('Usage: workjournal invites list <workspaceSlug> <journalSlug>\n');
238
393
  process.exit(1);
239
394
  }
240
- const outputPath = flags['-p'] ?? flags['--path'];
241
- await exportJournal(journalId, format, outputPath);
242
- break;
395
+ await listInvites(ws, j, jsonOutput);
396
+ return;
397
+ }
398
+ case 'new':
399
+ case 'create': {
400
+ const ws = argv[1];
401
+ const j = argv[2];
402
+ const email = argv[3];
403
+ if (!ws || !j || !email) {
404
+ process.stderr.write('Usage: workjournal invites new <workspaceSlug> <journalSlug> <email>\n');
405
+ process.exit(1);
406
+ }
407
+ await newInvite(ws, j, email, jsonOutput);
408
+ return;
409
+ }
410
+ case 'delete': {
411
+ const ws = argv[1];
412
+ const j = argv[2];
413
+ const invitationId = argv[3];
414
+ if (!ws || !j || !invitationId) {
415
+ process.stderr.write('Usage: workjournal invites delete <workspaceSlug> <journalSlug> <invitationId>\n');
416
+ process.exit(1);
417
+ }
418
+ await deleteInvite(ws, j, invitationId, jsonOutput);
419
+ return;
243
420
  }
244
421
  default:
245
- process.stderr.write(`Unknown subcommand: ${sub}\n`);
422
+ process.stderr.write(`Unknown invites subcommand: ${sub}\nUsage: workjournal invites <list|new|delete>\n`);
246
423
  process.exit(1);
247
424
  }
248
425
  }
249
- async function routeJournals(argv) {
250
- const sub = argv[0];
251
- if (sub === undefined) {
252
- // Shortcut: workjournal journals → show selected journal details
253
- const journalId = resolveJournalId();
254
- await getJournal(journalId, jsonOutput);
255
- return;
256
- }
257
- if (sub === 'list') {
258
- await listJournals(jsonOutput);
259
- return;
260
- }
261
- if (sub === 'new') {
262
- const name = argv[1];
263
- if (!name) {
264
- process.stderr.write('Usage: workjournal journals new <name>\n');
265
- process.exit(1);
266
- }
267
- await createJournal(name, jsonOutput);
268
- return;
269
- }
270
- if (sub === 'delete') {
271
- const id = argv[1];
272
- if (!id) {
273
- process.stderr.write('Usage: workjournal journals delete <journal_id>\n');
274
- process.exit(1);
275
- }
276
- await deleteJournal(id, jsonOutput);
277
- return;
426
+ async function routeExport(argv) {
427
+ const ws = argv[0];
428
+ const j = argv[1];
429
+ if (!ws || !j) {
430
+ process.stderr.write('Usage: workjournal export <workspaceSlug> <journalSlug> [-f json|md|csv] [-p <path>]\n');
431
+ process.exit(1);
278
432
  }
279
- if (sub === 'select') {
280
- const id = argv[1];
281
- if (!id) {
282
- process.stderr.write('Usage: workjournal journals select <journal_id>\n');
283
- process.exit(1);
284
- }
285
- await selectJournal(id);
286
- return;
433
+ const { flags } = parseFlags(argv.slice(2));
434
+ const format = flags['-f'] ?? flags['--format'] ?? 'json';
435
+ if (!['json', 'md', 'csv'].includes(format)) {
436
+ process.stderr.write('Invalid format. Use: -f json|md|csv\n');
437
+ process.exit(1);
287
438
  }
288
- // Not a known subcommand — treat as a journal ID
289
- const journalId = sub;
290
- await routeJournalResource(journalId, argv.slice(1));
439
+ const outputPath = flags['-p'] ?? flags['--path'];
440
+ await exportJournal(ws, j, format, outputPath);
291
441
  }
292
442
  async function routeConfig(argv) {
293
443
  const sub = argv[0];
294
444
  if (sub === 'show') {
295
445
  const projectPath = getProjectConfigPath();
296
- const projectCfg = findProjectConfig();
446
+ const stored = findProjectConfig();
297
447
  const globalCfg = readConfig();
298
448
  process.stdout.write('Project config:\n');
299
- if (projectPath && projectCfg) {
300
- process.stdout.write(` Path: ${projectPath}\n`);
301
- process.stdout.write(` journal_id: ${projectCfg.journal_id}\n`);
449
+ if (projectPath && stored) {
450
+ process.stdout.write(` Path: ${projectPath}\n`);
451
+ if (stored.kind === 'v2') {
452
+ process.stdout.write(` Workspace: ${stored.config.workspaceSlug}\n`);
453
+ process.stdout.write(` Journal: ${stored.config.journalSlug}\n`);
454
+ }
455
+ else {
456
+ process.stdout.write(` Legacy journal_id: ${stored.config.journal_id}\n`);
457
+ process.stdout.write(' (will migrate on next command that needs the selection)\n');
458
+ }
302
459
  }
303
460
  else {
304
461
  process.stdout.write(' (none)\n');
305
462
  }
306
463
  process.stdout.write('Global config:\n');
307
- if (globalCfg.journal) {
308
- process.stdout.write(` journal: ${globalCfg.journal}\n`);
464
+ if (globalCfg.workspaceSlug || globalCfg.journalSlug) {
465
+ if (globalCfg.workspaceSlug) {
466
+ process.stdout.write(` Workspace: ${globalCfg.workspaceSlug}\n`);
467
+ }
468
+ if (globalCfg.journalSlug) {
469
+ process.stdout.write(` Journal: ${globalCfg.journalSlug}\n`);
470
+ }
309
471
  }
310
472
  else {
311
473
  process.stdout.write(' (none)\n');
@@ -323,31 +485,41 @@ function printHelp() {
323
485
  Usage:
324
486
  workjournal List entries in selected journal
325
487
 
326
- Selected journal (workjournal journal ...):
327
- workjournal journal Show selected journal details
328
- workjournal journal entries List entries
329
- workjournal journal entries write -s <summary> -b <body>
330
- Create a new entry
331
- workjournal journal entries last [count] Show most recent entries (full body)
332
- workjournal journal entries get <index> Show a single entry by index
333
- workjournal journal entries delete <index> Delete an entry by index
334
- workjournal journal entries search <query> Search entries
335
- workjournal journal shares List members
336
- workjournal journal shares delete <email> Remove a member
337
- workjournal journal invites List invitations
338
- workjournal journal invites new <email> Invite a collaborator
339
- workjournal journal invites delete <id> Revoke an invitation
340
- workjournal journal export [-f json|md|csv] [-p <path>]
341
- Export journal data
488
+ Workspaces:
489
+ workjournal workspaces list List your workspaces
490
+ workjournal workspaces get <ws> Show workspace details
491
+ workjournal workspaces new <name> [--slug <slug>] Create a new workspace
492
+ workjournal workspaces select <ws> Set active workspace
493
+
494
+ Journals:
495
+ workjournal journals list [<ws>] List journals in workspace
496
+ workjournal journals get <ws> <j> Show journal details
497
+ workjournal journals new <ws> <name> [--slug <slug>] Create a new journal
498
+ workjournal journals delete <ws> <j> Delete a journal
499
+ workjournal journals select <ws> <j> Set active journal
500
+ workjournal journals Show selected journal details
501
+
502
+ Entries:
503
+ workjournal entries list <ws> <j> List entries
504
+ workjournal entries write <ws> <j> -s <summary> -b <body>
505
+ Create a new entry
506
+ workjournal entries last <ws> <j> [count] Show most recent entries (full body)
507
+ workjournal entries get <ws> <j> <index> Show a single entry by index
508
+ workjournal entries delete <ws> <j> <index> Delete an entry by index
509
+ workjournal entries search <ws> <j> <query> Search entries
510
+
511
+ Shares (members):
512
+ workjournal shares list <ws> <j> List members
513
+ workjournal shares delete <ws> <j> <email> Remove a member
342
514
 
343
- Manage journals (workjournal journals ...):
344
- workjournal journals list List your journals
345
- workjournal journals new <name> Create a new journal
346
- workjournal journals delete <id> Delete a journal
347
- workjournal journals select <id> Set active journal
348
- workjournal journals <id> Show journal details
349
- workjournal journals <id> entries|shares|invites|export
350
- Access resources by journal ID
515
+ Invites:
516
+ workjournal invites list <ws> <j> List invitations
517
+ workjournal invites new <ws> <j> <email> Invite a collaborator
518
+ workjournal invites delete <ws> <j> <invitationId> Revoke an invitation
519
+
520
+ Export:
521
+ workjournal export <ws> <j> [-f json|md|csv] [-p <path>]
522
+ Export journal data
351
523
 
352
524
  Auth:
353
525
  workjournal auth login Interactive login
@@ -374,22 +546,44 @@ async function run() {
374
546
  }
375
547
  if (command === undefined) {
376
548
  // Shortcut: workjournal → list entries in selected journal
377
- const journalId = resolveJournalId();
378
- await listEntries(journalId, jsonOutput);
549
+ const sel = await resolveActiveSelection();
550
+ await listEntries(sel.workspaceSlug, sel.journalSlug, jsonOutput);
379
551
  return;
380
552
  }
381
553
  switch (command) {
382
554
  case 'auth':
383
555
  await routeAuth(args.slice(1));
384
556
  break;
557
+ case 'workspaces':
558
+ await routeWorkspaces(args.slice(1));
559
+ break;
385
560
  case 'journal': {
386
- const journalId = resolveJournalId();
387
- await routeJournalResource(journalId, args.slice(1));
561
+ // Shortcut: show selected journal details (matches docs/cli-commands.md)
562
+ const sel = await resolveActiveSelection();
563
+ if (args.length > 1) {
564
+ process.stderr.write('`workjournal journal` shows the selected journal. Use `workjournal entries`, ' +
565
+ '`workjournal shares`, etc. with explicit <workspaceSlug> <journalSlug> for ' +
566
+ 'resource verbs.\n');
567
+ process.exit(1);
568
+ }
569
+ await getJournal(sel.workspaceSlug, sel.journalSlug, jsonOutput);
388
570
  break;
389
571
  }
390
572
  case 'journals':
391
573
  await routeJournals(args.slice(1));
392
574
  break;
575
+ case 'entries':
576
+ await routeEntries(args.slice(1));
577
+ break;
578
+ case 'shares':
579
+ await routeShares(args.slice(1));
580
+ break;
581
+ case 'invites':
582
+ await routeInvites(args.slice(1));
583
+ break;
584
+ case 'export':
585
+ await routeExport(args.slice(1));
586
+ break;
393
587
  case 'config':
394
588
  await routeConfig(args.slice(1));
395
589
  break;