@workjournal/cli 0.3.4 → 0.5.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 +56 -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 +7 -5
  19. package/dist/commands/journals.d.ts.map +1 -1
  20. package/dist/commands/journals.js +48 -20
  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 +4 -0
  27. package/dist/commands/workspaces.d.ts.map +1 -0
  28. package/dist/commands/workspaces.js +50 -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 +368 -163
  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
- import { createJournal, deleteJournal, getJournal, listJournals, selectJournal, } from './commands/journals.js';
7
+ import { createJournal, deleteJournal, getJournal, listJournals, renameJournal, selectJournal, setJournalSlug, } from './commands/journals.js';
7
8
  import { deleteShare, listShares } from './commands/shares.js';
9
+ import { 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,330 @@ 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 'select': {
174
+ const slug = argv[1];
175
+ if (!slug) {
176
+ process.stderr.write('Usage: workjournal workspaces select <workspaceSlug>\n');
130
177
  process.exit(1);
131
178
  }
132
- await lastEntries(journalId, count, jsonOutput);
179
+ await selectWorkspace(slug);
133
180
  break;
134
181
  }
182
+ default:
183
+ process.stderr.write(`Unknown workspaces subcommand: ${sub}\n` +
184
+ 'Usage: workjournal workspaces <list|get|select>\n');
185
+ process.exit(1);
186
+ }
187
+ }
188
+ async function routeJournals(argv) {
189
+ const sub = argv[0];
190
+ if (sub === undefined) {
191
+ // Shortcut: workjournal journals → show selected journal details
192
+ const sel = await resolveActiveSelection();
193
+ await getJournal(sel.workspaceSlug, sel.journalSlug, jsonOutput);
194
+ return;
195
+ }
196
+ switch (sub) {
197
+ case 'list': {
198
+ // `list [workspaceSlug]` — falls back to selected workspace if omitted.
199
+ const wsSlug = argv[1] ?? readConfig().workspaceSlug;
200
+ if (!wsSlug) {
201
+ process.stderr.write('Usage: workjournal journals list <workspaceSlug>\n' +
202
+ '(or run `workjournal workspaces select <slug>` first)\n');
203
+ process.exit(1);
204
+ }
205
+ await listJournals(wsSlug, jsonOutput);
206
+ return;
207
+ }
135
208
  case 'get': {
136
- const index = argv[1];
137
- if (!index) {
138
- process.stderr.write('Usage: workjournal journals <id> entries get <index>\n');
209
+ const ws = argv[1];
210
+ const j = argv[2];
211
+ if (!ws || !j) {
212
+ process.stderr.write('Usage: workjournal journals get <workspaceSlug> <journalSlug>\n');
139
213
  process.exit(1);
140
214
  }
141
- await getEntry(journalId, index, jsonOutput);
142
- break;
215
+ await getJournal(ws, j, jsonOutput);
216
+ return;
217
+ }
218
+ case 'new':
219
+ case 'create': {
220
+ const { flags, positional } = parseFlags(argv.slice(1));
221
+ const ws = positional[0];
222
+ const name = positional[1];
223
+ if (!ws || !name) {
224
+ process.stderr.write('Usage: workjournal journals new <workspaceSlug> <name> [--slug <slug>]\n');
225
+ process.exit(1);
226
+ }
227
+ const slug = flags['--slug'];
228
+ await createJournal(ws, name, slug, jsonOutput);
229
+ return;
143
230
  }
144
231
  case 'delete': {
145
- const index = argv[1];
146
- if (!index) {
147
- process.stderr.write('Usage: workjournal journals <id> entries delete <index>\n');
232
+ const ws = argv[1];
233
+ const j = argv[2];
234
+ if (!ws || !j) {
235
+ process.stderr.write('Usage: workjournal journals delete <workspaceSlug> <journalSlug>\n');
148
236
  process.exit(1);
149
237
  }
150
- await deleteEntry(journalId, index, jsonOutput);
151
- break;
238
+ await deleteJournal(ws, j, jsonOutput);
239
+ return;
152
240
  }
153
- case 'search': {
154
- const query = argv[1];
155
- if (!query) {
156
- process.stderr.write('Usage: workjournal journals <id> entries search <query>\n');
241
+ case 'rename': {
242
+ const ws = argv[1];
243
+ const j = argv[2];
244
+ const newName = argv[3];
245
+ if (!ws || !j || !newName) {
246
+ process.stderr.write('Usage: workjournal journals rename <workspaceSlug> <journalSlug> <newName>\n');
157
247
  process.exit(1);
158
248
  }
159
- await searchEntries(journalId, query, jsonOutput);
160
- break;
249
+ await renameJournal(ws, j, newName, jsonOutput);
250
+ return;
251
+ }
252
+ case 'set-slug': {
253
+ const ws = argv[1];
254
+ const j = argv[2];
255
+ const newSlug = argv[3];
256
+ if (!ws || !j || !newSlug) {
257
+ process.stderr.write('Usage: workjournal journals set-slug <workspaceSlug> <journalSlug> <newSlug>\n');
258
+ process.exit(1);
259
+ }
260
+ await setJournalSlug(ws, j, newSlug, jsonOutput);
261
+ return;
262
+ }
263
+ case 'select': {
264
+ const ws = argv[1];
265
+ const j = argv[2];
266
+ if (!ws || !j) {
267
+ process.stderr.write('Usage: workjournal journals select <workspaceSlug> <journalSlug>\n');
268
+ process.exit(1);
269
+ }
270
+ await selectJournal(ws, j);
271
+ return;
161
272
  }
162
273
  default:
163
- process.stderr.write(`Unknown entries subcommand: ${sub}\n`);
274
+ process.stderr.write(`Unknown journals subcommand: ${sub}\n` +
275
+ 'Usage: workjournal journals <list|get|new|delete|rename|set-slug|select>\n');
164
276
  process.exit(1);
165
277
  }
166
278
  }
167
- async function routeShares(journalId, argv) {
279
+ async function routeEntries(argv) {
168
280
  const sub = argv[0];
169
281
  switch (sub) {
170
282
  case undefined:
171
- case 'list':
172
- await listShares(journalId, jsonOutput);
173
- break;
283
+ case 'list': {
284
+ const ws = argv[1];
285
+ const j = argv[2];
286
+ if (!ws || !j) {
287
+ process.stderr.write('Usage: workjournal entries list <workspaceSlug> <journalSlug>\n');
288
+ process.exit(1);
289
+ }
290
+ await listEntries(ws, j, jsonOutput);
291
+ return;
292
+ }
293
+ case 'write': {
294
+ const ws = argv[1];
295
+ const j = argv[2];
296
+ if (!ws || !j) {
297
+ process.stderr.write('Usage: workjournal entries write <workspaceSlug> <journalSlug> -s <summary> -b <body>\n');
298
+ process.exit(1);
299
+ }
300
+ const { flags } = parseFlags(argv.slice(3));
301
+ const summary = flags['-s'] ?? flags['--summary'];
302
+ const body = flags['-b'] ?? flags['--body'];
303
+ if (!summary || !body) {
304
+ process.stderr.write('Usage: workjournal entries write <workspaceSlug> <journalSlug> -s <summary> -b <body>\n');
305
+ process.exit(1);
306
+ }
307
+ await createEntry(ws, j, summary, body, jsonOutput);
308
+ return;
309
+ }
310
+ case 'last': {
311
+ const ws = argv[1];
312
+ const j = argv[2];
313
+ if (!ws || !j) {
314
+ process.stderr.write('Usage: workjournal entries last <workspaceSlug> <journalSlug> [count]\n');
315
+ process.exit(1);
316
+ }
317
+ const count = Number.parseInt(argv[3] ?? '1', 10);
318
+ if (Number.isNaN(count) || count < 1) {
319
+ process.stderr.write('Usage: workjournal entries last <workspaceSlug> <journalSlug> [count]\n');
320
+ process.exit(1);
321
+ }
322
+ await lastEntries(ws, j, count, jsonOutput);
323
+ return;
324
+ }
325
+ case 'get': {
326
+ const ws = argv[1];
327
+ const j = argv[2];
328
+ const index = argv[3];
329
+ if (!ws || !j || !index) {
330
+ process.stderr.write('Usage: workjournal entries get <workspaceSlug> <journalSlug> <index>\n');
331
+ process.exit(1);
332
+ }
333
+ await getEntry(ws, j, index, jsonOutput);
334
+ return;
335
+ }
174
336
  case 'delete': {
175
- const email = argv[1];
176
- if (!email) {
177
- process.stderr.write('Usage: workjournal journals <id> shares delete <email>\n');
337
+ const ws = argv[1];
338
+ const j = argv[2];
339
+ const index = argv[3];
340
+ if (!ws || !j || !index) {
341
+ process.stderr.write('Usage: workjournal entries delete <workspaceSlug> <journalSlug> <index>\n');
178
342
  process.exit(1);
179
343
  }
180
- await deleteShare(journalId, email, jsonOutput);
181
- break;
344
+ await deleteEntry(ws, j, index, jsonOutput);
345
+ return;
346
+ }
347
+ case 'search': {
348
+ const ws = argv[1];
349
+ const j = argv[2];
350
+ const query = argv[3];
351
+ if (!ws || !j || !query) {
352
+ process.stderr.write('Usage: workjournal entries search <workspaceSlug> <journalSlug> <query>\n');
353
+ process.exit(1);
354
+ }
355
+ await searchEntries(ws, j, query, jsonOutput);
356
+ return;
182
357
  }
183
358
  default:
184
- process.stderr.write(`Unknown shares subcommand: ${sub}\n`);
359
+ process.stderr.write(`Unknown entries subcommand: ${sub}\n` +
360
+ 'Usage: workjournal entries <list|write|last|get|delete|search>\n');
185
361
  process.exit(1);
186
362
  }
187
363
  }
188
- async function routeInvites(journalId, argv) {
364
+ async function routeShares(argv) {
189
365
  const sub = argv[0];
190
366
  switch (sub) {
191
367
  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');
368
+ case 'list': {
369
+ const ws = argv[1];
370
+ const j = argv[2];
371
+ if (!ws || !j) {
372
+ process.stderr.write('Usage: workjournal shares list <workspaceSlug> <journalSlug>\n');
199
373
  process.exit(1);
200
374
  }
201
- await newInvite(journalId, email, jsonOutput);
202
- break;
375
+ await listShares(ws, j, jsonOutput);
376
+ return;
203
377
  }
204
378
  case 'delete': {
205
- const invitationId = argv[1];
206
- if (!invitationId) {
207
- process.stderr.write('Usage: workjournal journals <id> invites delete <invitation_id>\n');
379
+ const ws = argv[1];
380
+ const j = argv[2];
381
+ const email = argv[3];
382
+ if (!ws || !j || !email) {
383
+ process.stderr.write('Usage: workjournal shares delete <workspaceSlug> <journalSlug> <email>\n');
208
384
  process.exit(1);
209
385
  }
210
- await deleteInvite(journalId, invitationId, jsonOutput);
211
- break;
386
+ await deleteShare(ws, j, email, jsonOutput);
387
+ return;
212
388
  }
213
389
  default:
214
- process.stderr.write(`Unknown invites subcommand: ${sub}\n`);
390
+ process.stderr.write(`Unknown shares subcommand: ${sub}\nUsage: workjournal shares <list|delete>\n`);
215
391
  process.exit(1);
216
392
  }
217
393
  }
218
- async function routeJournalResource(journalId, argv) {
394
+ async function routeInvites(argv) {
219
395
  const sub = argv[0];
220
396
  switch (sub) {
221
397
  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');
398
+ case 'list': {
399
+ const ws = argv[1];
400
+ const j = argv[2];
401
+ if (!ws || !j) {
402
+ process.stderr.write('Usage: workjournal invites list <workspaceSlug> <journalSlug>\n');
238
403
  process.exit(1);
239
404
  }
240
- const outputPath = flags['-p'] ?? flags['--path'];
241
- await exportJournal(journalId, format, outputPath);
242
- break;
405
+ await listInvites(ws, j, jsonOutput);
406
+ return;
407
+ }
408
+ case 'new':
409
+ case 'create': {
410
+ const ws = argv[1];
411
+ const j = argv[2];
412
+ const email = argv[3];
413
+ if (!ws || !j || !email) {
414
+ process.stderr.write('Usage: workjournal invites new <workspaceSlug> <journalSlug> <email>\n');
415
+ process.exit(1);
416
+ }
417
+ await newInvite(ws, j, email, jsonOutput);
418
+ return;
419
+ }
420
+ case 'delete': {
421
+ const ws = argv[1];
422
+ const j = argv[2];
423
+ const invitationId = argv[3];
424
+ if (!ws || !j || !invitationId) {
425
+ process.stderr.write('Usage: workjournal invites delete <workspaceSlug> <journalSlug> <invitationId>\n');
426
+ process.exit(1);
427
+ }
428
+ await deleteInvite(ws, j, invitationId, jsonOutput);
429
+ return;
243
430
  }
244
431
  default:
245
- process.stderr.write(`Unknown subcommand: ${sub}\n`);
432
+ process.stderr.write(`Unknown invites subcommand: ${sub}\nUsage: workjournal invites <list|new|delete>\n`);
246
433
  process.exit(1);
247
434
  }
248
435
  }
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;
436
+ async function routeExport(argv) {
437
+ const ws = argv[0];
438
+ const j = argv[1];
439
+ if (!ws || !j) {
440
+ process.stderr.write('Usage: workjournal export <workspaceSlug> <journalSlug> [-f json|md|csv] [-p <path>]\n');
441
+ process.exit(1);
278
442
  }
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;
443
+ const { flags } = parseFlags(argv.slice(2));
444
+ const format = flags['-f'] ?? flags['--format'] ?? 'json';
445
+ if (!['json', 'md', 'csv'].includes(format)) {
446
+ process.stderr.write('Invalid format. Use: -f json|md|csv\n');
447
+ process.exit(1);
287
448
  }
288
- // Not a known subcommand — treat as a journal ID
289
- const journalId = sub;
290
- await routeJournalResource(journalId, argv.slice(1));
449
+ const outputPath = flags['-p'] ?? flags['--path'];
450
+ await exportJournal(ws, j, format, outputPath);
291
451
  }
292
452
  async function routeConfig(argv) {
293
453
  const sub = argv[0];
294
454
  if (sub === 'show') {
295
455
  const projectPath = getProjectConfigPath();
296
- const projectCfg = findProjectConfig();
456
+ const stored = findProjectConfig();
297
457
  const globalCfg = readConfig();
298
458
  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`);
459
+ if (projectPath && stored) {
460
+ process.stdout.write(` Path: ${projectPath}\n`);
461
+ if (stored.kind === 'v2') {
462
+ process.stdout.write(` Workspace: ${stored.config.workspaceSlug}\n`);
463
+ process.stdout.write(` Journal: ${stored.config.journalSlug}\n`);
464
+ }
465
+ else {
466
+ process.stdout.write(` Legacy journal_id: ${stored.config.journal_id}\n`);
467
+ process.stdout.write(' (will migrate on next command that needs the selection)\n');
468
+ }
302
469
  }
303
470
  else {
304
471
  process.stdout.write(' (none)\n');
305
472
  }
306
473
  process.stdout.write('Global config:\n');
307
- if (globalCfg.journal) {
308
- process.stdout.write(` journal: ${globalCfg.journal}\n`);
474
+ if (globalCfg.workspaceSlug || globalCfg.journalSlug) {
475
+ if (globalCfg.workspaceSlug) {
476
+ process.stdout.write(` Workspace: ${globalCfg.workspaceSlug}\n`);
477
+ }
478
+ if (globalCfg.journalSlug) {
479
+ process.stdout.write(` Journal: ${globalCfg.journalSlug}\n`);
480
+ }
309
481
  }
310
482
  else {
311
483
  process.stdout.write(' (none)\n');
@@ -323,31 +495,42 @@ function printHelp() {
323
495
  Usage:
324
496
  workjournal List entries in selected journal
325
497
 
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
498
+ Workspaces:
499
+ workjournal workspaces list List your workspaces
500
+ workjournal workspaces get <ws> Show workspace details
501
+ workjournal workspaces select <ws> Set active workspace
502
+
503
+ Journals:
504
+ workjournal journals list [<ws>] List journals in workspace
505
+ workjournal journals get <ws> <j> Show journal details
506
+ workjournal journals new <ws> <name> [--slug <slug>] Create a new journal
507
+ workjournal journals delete <ws> <j> Delete a journal
508
+ workjournal journals rename <ws> <j> <newName> Rename a journal
509
+ workjournal journals set-slug <ws> <j> <newSlug> Change a journal's slug
510
+ workjournal journals select <ws> <j> Set active journal
511
+ workjournal journals Show selected journal details
512
+
513
+ Entries:
514
+ workjournal entries list <ws> <j> List entries
515
+ workjournal entries write <ws> <j> -s <summary> -b <body>
516
+ Create a new entry
517
+ workjournal entries last <ws> <j> [count] Show most recent entries (full body)
518
+ workjournal entries get <ws> <j> <index> Show a single entry by index
519
+ workjournal entries delete <ws> <j> <index> Delete an entry by index
520
+ workjournal entries search <ws> <j> <query> Search entries
342
521
 
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
522
+ Shares (members):
523
+ workjournal shares list <ws> <j> List members
524
+ workjournal shares delete <ws> <j> <email> Remove a member
525
+
526
+ Invites:
527
+ workjournal invites list <ws> <j> List invitations
528
+ workjournal invites new <ws> <j> <email> Invite a collaborator
529
+ workjournal invites delete <ws> <j> <invitationId> Revoke an invitation
530
+
531
+ Export:
532
+ workjournal export <ws> <j> [-f json|md|csv] [-p <path>]
533
+ Export journal data
351
534
 
352
535
  Auth:
353
536
  workjournal auth login Interactive login
@@ -374,22 +557,44 @@ async function run() {
374
557
  }
375
558
  if (command === undefined) {
376
559
  // Shortcut: workjournal → list entries in selected journal
377
- const journalId = resolveJournalId();
378
- await listEntries(journalId, jsonOutput);
560
+ const sel = await resolveActiveSelection();
561
+ await listEntries(sel.workspaceSlug, sel.journalSlug, jsonOutput);
379
562
  return;
380
563
  }
381
564
  switch (command) {
382
565
  case 'auth':
383
566
  await routeAuth(args.slice(1));
384
567
  break;
568
+ case 'workspaces':
569
+ await routeWorkspaces(args.slice(1));
570
+ break;
385
571
  case 'journal': {
386
- const journalId = resolveJournalId();
387
- await routeJournalResource(journalId, args.slice(1));
572
+ // Shortcut: show selected journal details (matches docs/cli-commands.md)
573
+ const sel = await resolveActiveSelection();
574
+ if (args.length > 1) {
575
+ process.stderr.write('`workjournal journal` shows the selected journal. Use `workjournal entries`, ' +
576
+ '`workjournal shares`, etc. with explicit <workspaceSlug> <journalSlug> for ' +
577
+ 'resource verbs.\n');
578
+ process.exit(1);
579
+ }
580
+ await getJournal(sel.workspaceSlug, sel.journalSlug, jsonOutput);
388
581
  break;
389
582
  }
390
583
  case 'journals':
391
584
  await routeJournals(args.slice(1));
392
585
  break;
586
+ case 'entries':
587
+ await routeEntries(args.slice(1));
588
+ break;
589
+ case 'shares':
590
+ await routeShares(args.slice(1));
591
+ break;
592
+ case 'invites':
593
+ await routeInvites(args.slice(1));
594
+ break;
595
+ case 'export':
596
+ await routeExport(args.slice(1));
597
+ break;
393
598
  case 'config':
394
599
  await routeConfig(args.slice(1));
395
600
  break;