@reximo/cli 0.1.0-alpha.4 → 0.1.0-alpha.6

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.
@@ -4,7 +4,9 @@ exports.runCli = runCli;
4
4
  const tslib_1 = require("tslib");
5
5
  const node_process_1 = tslib_1.__importDefault(require("node:process"));
6
6
  const commander_1 = require("commander");
7
+ const command_inputs_1 = require("./command-inputs");
7
8
  const config_1 = require("./config");
9
+ const formatters_1 = require("./formatters");
8
10
  const output_1 = require("./output");
9
11
  const prompts_1 = require("./prompts");
10
12
  const package_json_1 = tslib_1.__importDefault(require("../../package.json"));
@@ -84,6 +86,14 @@ function createAuthenticatedContext(dependencies, configOptions) {
84
86
  organizationId: requireConfiguredOrganization(config)
85
87
  };
86
88
  }
89
+ async function withAuthenticatedContext(dependencies, configOptions, run) {
90
+ const context = createAuthenticatedContext(dependencies, configOptions);
91
+ return run(context);
92
+ }
93
+ async function runAuthenticatedCommand(params) {
94
+ const result = await withAuthenticatedContext(params.dependencies, params.configOptions, params.operation);
95
+ writeResult(params.command, params.dependencies, params.text(result), params.json(result));
96
+ }
87
97
  async function resolveRequiredValue(currentValue, label, prompt) {
88
98
  if (currentValue?.trim()) {
89
99
  return currentValue.trim();
@@ -139,107 +149,66 @@ async function resolveOrganizationContext(promptAdapter, organizations, organiza
139
149
  const selectedOrganizationId = await promptAdapter.select('Select organization', choices);
140
150
  return (activeOrganizations.find((organization) => organization.organizationId === selectedOrganizationId) ?? null);
141
151
  }
142
- function formatIssueIdentifier(issue) {
143
- if (issue.project?.key) {
144
- return `${issue.project.key}-${issue.issueNumber}`;
145
- }
146
- return `#${issue.issueNumber}`;
147
- }
148
- function formatIssueLines(result) {
149
- if (result.data.length === 0) {
150
- return ['No issues found.'];
151
- }
152
- const lines = [
153
- `Issues: ${result.metadata.pagination.total} total (page ${result.metadata.pagination.page}/${Math.max(result.metadata.pagination.totalPages, 1)})`
154
- ];
155
- for (const issue of result.data) {
156
- lines.push(`${formatIssueIdentifier(issue)} [id:${issue.id}] ${issue.title} (${issue.status}, ${issue.priority})`);
157
- }
158
- return lines;
159
- }
160
- function formatIssueDetailLines(issue) {
161
- return [
162
- `Issue: ${formatIssueIdentifier(issue)} [id:${issue.id}]`,
163
- `Title: ${issue.title}`,
164
- `Status: ${issue.status}`,
165
- `Priority: ${issue.priority}`,
166
- `Project: ${issue.project?.name ?? '(none)'}`,
167
- `Updated: ${issue.updatedAt}`
168
- ];
169
- }
170
- function formatProjectAccess(project) {
171
- if (project.visibility === 'public') {
172
- return 'public';
173
- }
174
- return project.isMember ? 'private member' : 'private';
175
- }
176
- function formatProjectLines(result) {
177
- if (result.data.length === 0) {
178
- return ['No projects found.'];
179
- }
180
- const lines = [
181
- `Projects: ${result.metadata.pagination.total} total (page ${result.metadata.pagination.page}/${Math.max(result.metadata.pagination.totalPages, 1)})`
182
- ];
183
- for (const project of result.data) {
184
- lines.push(`${project.key} [id:${project.id}] ${project.name} (${formatProjectAccess(project)})`);
185
- }
186
- return lines;
187
- }
188
- function buildUpdateIssuePayload(options) {
189
- const payload = {};
190
- if (options.title !== undefined) {
191
- payload['title'] = options.title;
192
- }
193
- if (options.descriptionMarkdown !== undefined) {
194
- payload['descriptionMarkdown'] = options.descriptionMarkdown;
195
- }
196
- if (options.status !== undefined) {
197
- payload['status'] = options.status;
198
- }
199
- if (options.statusDefinitionId !== undefined) {
200
- payload['statusDefinitionId'] = options.statusDefinitionId;
201
- }
202
- if (options.priority !== undefined) {
203
- payload['priority'] = options.priority;
204
- }
205
- if (options.position !== undefined) {
206
- payload['position'] = options.position;
207
- }
208
- if (options.baseRevision !== undefined) {
209
- payload['baseRevision'] = options.baseRevision;
210
- }
211
- if (options.clearParentIssue) {
212
- payload['parentIssueId'] = null;
213
- }
214
- else if (options.parentIssueId !== undefined) {
215
- payload['parentIssueId'] = options.parentIssueId;
216
- }
217
- if (options.clearEstimate) {
218
- payload['estimate'] = null;
219
- }
220
- else if (options.estimate !== undefined) {
221
- payload['estimate'] = options.estimate;
222
- }
223
- if (options.clearDueAt) {
224
- payload['dueAt'] = null;
225
- }
226
- else if (options.dueAt !== undefined) {
227
- payload['dueAt'] = options.dueAt;
228
- }
229
- if (Object.keys(payload).length === 0) {
230
- throw new Error('At least one update field is required.');
231
- }
232
- return payload;
152
+ function buildAuthStatus(config, client) {
153
+ const target = client.describeTarget();
154
+ return {
155
+ profile: config.profile,
156
+ configPath: config.configPath,
157
+ configFileFound: config.configFileFound,
158
+ apiBaseUrl: config.apiBaseUrl ?? config_1.DEFAULT_API_BASE_URL,
159
+ isConfigured: client.isConfigured(),
160
+ hasAccessToken: target.hasAccessToken,
161
+ hasRefreshToken: target.hasRefreshToken,
162
+ organizationId: target.organizationId
163
+ };
233
164
  }
234
- function buildIssuesSearchInput(options) {
235
- if (options.status !== undefined || options.statuses !== undefined) {
236
- return options;
237
- }
165
+ function buildDoctorReport(config, client) {
166
+ const auth = buildAuthStatus(config, client);
238
167
  return {
239
- ...options,
240
- statuses: ["backlog" /* IssueStatusOption.Backlog */, "in_progress" /* IssueStatusOption.InProgress */, "blocked" /* IssueStatusOption.Blocked */]
168
+ command: 'doctor',
169
+ auth,
170
+ checks: {
171
+ apiBaseUrlConfigured: auth.isConfigured,
172
+ accessTokenConfigured: auth.hasAccessToken,
173
+ refreshTokenConfigured: auth.hasRefreshToken,
174
+ organizationConfigured: Boolean(auth.organizationId),
175
+ readyForAuthenticatedCommands: auth.isConfigured && auth.hasAccessToken && Boolean(auth.organizationId)
176
+ },
177
+ capabilities: {
178
+ issues: {
179
+ search: true,
180
+ list: true,
181
+ get: true,
182
+ create: true,
183
+ update: true,
184
+ delete: true,
185
+ comment: true,
186
+ transition: true,
187
+ link: true
188
+ },
189
+ projects: {
190
+ list: true
191
+ },
192
+ content: {
193
+ list: true,
194
+ get: true,
195
+ create: true,
196
+ update: true,
197
+ delete: true,
198
+ publish: false,
199
+ attach: false
200
+ }
201
+ },
202
+ notes: [
203
+ 'Issue search, get, create, update, delete, comment, transition, and link are currently supported.',
204
+ 'Project listing and content list, get, create, update, and delete are currently supported.',
205
+ 'The current API surface does not expose a separate content publish endpoint.'
206
+ ]
241
207
  };
242
208
  }
209
+ function unsupportedContentError(subcommand) {
210
+ return new Error(`Content command "${subcommand}" is not yet supported by the installed Reximo CLI/API surface. Run \`reximo doctor --json\` to inspect current capabilities.`);
211
+ }
243
212
  function createProgram(dependencies) {
244
213
  const configOptions = {
245
214
  env: dependencies.env,
@@ -260,6 +229,17 @@ function createProgram(dependencies) {
260
229
  dependencies.stderr.write(message);
261
230
  }
262
231
  });
232
+ registerInfoCommand(program, dependencies, configOptions);
233
+ registerAuthCommands(program, dependencies, configOptions);
234
+ registerDoctorCommand(program, dependencies, configOptions);
235
+ registerIssueCommands(program, dependencies, configOptions);
236
+ registerProjectCommands(program, dependencies, configOptions);
237
+ registerContentCommands(program, dependencies, configOptions);
238
+ registerConfigCommands(program, dependencies, configOptions);
239
+ registerSessionCommands(program, dependencies, configOptions);
240
+ return program;
241
+ }
242
+ function registerInfoCommand(program, dependencies, configOptions) {
263
243
  program
264
244
  .command('info')
265
245
  .description('Show package and runtime information for diagnostics.')
@@ -296,6 +276,53 @@ function createProgram(dependencies) {
296
276
  }
297
277
  });
298
278
  });
279
+ }
280
+ function registerAuthCommands(program, dependencies, configOptions) {
281
+ const authCommand = program.command('auth').description('Inspect Reximo authentication state.');
282
+ authCommand
283
+ .command('status')
284
+ .description('Show whether the current CLI profile is ready for authenticated API commands.')
285
+ .action(function action() {
286
+ const config = (0, config_1.loadResolvedConfig)(configOptions);
287
+ const client = dependencies.clientFactory(config);
288
+ const status = buildAuthStatus(config, client);
289
+ writeResult(this, dependencies, [
290
+ `Profile: ${status.profile}`,
291
+ `Config path: ${status.configPath}`,
292
+ `Config file found: ${status.configFileFound ? 'yes' : 'no'}`,
293
+ `API base URL: ${status.apiBaseUrl}`,
294
+ `Configured for API: ${status.isConfigured ? 'yes' : 'no'}`,
295
+ `Access token configured: ${status.hasAccessToken ? 'yes' : 'no'}`,
296
+ `Refresh token configured: ${status.hasRefreshToken ? 'yes' : 'no'}`,
297
+ `Organization: ${status.organizationId ?? '(not configured)'}`
298
+ ], {
299
+ command: 'auth.status',
300
+ status
301
+ });
302
+ });
303
+ }
304
+ function registerDoctorCommand(program, dependencies, configOptions) {
305
+ program
306
+ .command('doctor')
307
+ .description('Report Reximo CLI readiness and command-surface capabilities for automation.')
308
+ .action(function action() {
309
+ const config = (0, config_1.loadResolvedConfig)(configOptions);
310
+ const client = dependencies.clientFactory(config);
311
+ const report = buildDoctorReport(config, client);
312
+ writeResult(this, dependencies, [
313
+ `Profile: ${report.auth.profile}`,
314
+ `Configured for API: ${report.auth.isConfigured ? 'yes' : 'no'}`,
315
+ `Access token configured: ${report.auth.hasAccessToken ? 'yes' : 'no'}`,
316
+ `Refresh token configured: ${report.auth.hasRefreshToken ? 'yes' : 'no'}`,
317
+ `Organization: ${report.auth.organizationId ?? '(not configured)'}`,
318
+ `Ready for authenticated commands: ${report.auth.isConfigured && report.auth.hasAccessToken && Boolean(report.auth.organizationId) ? 'yes' : 'no'}`,
319
+ `Issue commands: search, list, get, create, update, delete, comment, transition, link`,
320
+ `Content commands: list, get, create, update, delete`,
321
+ `Unsupported content commands: publish, attach`
322
+ ], report);
323
+ });
324
+ }
325
+ function registerIssueCommands(program, dependencies, configOptions) {
299
326
  const issuesCommand = program.command('issues').description('Manage issues.');
300
327
  issuesCommand
301
328
  .command('search')
@@ -315,15 +342,41 @@ function createProgram(dependencies) {
315
342
  .option('--sort-by <sortBy>', 'Sort field')
316
343
  .option('--sort-order <sortOrder>', 'Sort direction')
317
344
  .action(async function action(options) {
318
- const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
319
- const result = await client.listIssues({
320
- accessToken,
321
- organizationId,
322
- ...buildIssuesSearchInput(options)
345
+ await runAuthenticatedCommand({
346
+ command: this,
347
+ dependencies,
348
+ configOptions,
349
+ operation: ({ client, accessToken, organizationId }) => client.listIssues({
350
+ accessToken,
351
+ organizationId,
352
+ ...(0, command_inputs_1.buildIssuesSearchInput)(options)
353
+ }),
354
+ text: (result) => (0, formatters_1.formatIssueLines)(result),
355
+ json: (result) => ({
356
+ command: 'issues.search',
357
+ result
358
+ })
323
359
  });
324
- writeResult(this, dependencies, formatIssueLines(result), {
325
- command: 'issues.search',
326
- result
360
+ });
361
+ issuesCommand
362
+ .command('get <issueId>')
363
+ .alias('show')
364
+ .description('Get one issue by id in the configured organization.')
365
+ .action(async function action(issueId) {
366
+ await runAuthenticatedCommand({
367
+ command: this,
368
+ dependencies,
369
+ configOptions,
370
+ operation: ({ client, accessToken, organizationId }) => client.getIssue({
371
+ accessToken,
372
+ organizationId,
373
+ issueId
374
+ }),
375
+ text: (issue) => (0, formatters_1.formatIssueDetailLines)(issue),
376
+ json: (issue) => ({
377
+ command: 'issues.get',
378
+ issue
379
+ })
327
380
  });
328
381
  });
329
382
  issuesCommand
@@ -340,21 +393,27 @@ function createProgram(dependencies) {
340
393
  .option('--estimate <estimate>', 'Issue estimate', Number)
341
394
  .option('--due-at <dueAt>', 'Issue due date as an ISO-8601 string')
342
395
  .action(async function action(options) {
343
- const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
344
396
  if (!options.title?.trim()) {
345
397
  throw new Error('Title is required');
346
398
  }
347
- const issue = await client.createIssue({
348
- accessToken,
349
- organizationId,
350
- issue: {
351
- ...options,
352
- title: options.title.trim()
353
- }
354
- });
355
- writeResult(this, dependencies, formatIssueDetailLines(issue), {
356
- command: 'issues.create',
357
- issue
399
+ const title = options.title.trim();
400
+ await runAuthenticatedCommand({
401
+ command: this,
402
+ dependencies,
403
+ configOptions,
404
+ operation: ({ client, accessToken, organizationId }) => client.createIssue({
405
+ accessToken,
406
+ organizationId,
407
+ issue: {
408
+ ...options,
409
+ title
410
+ }
411
+ }),
412
+ text: (issue) => (0, formatters_1.formatIssueDetailLines)(issue),
413
+ json: (issue) => ({
414
+ command: 'issues.create',
415
+ issue
416
+ })
358
417
  });
359
418
  });
360
419
  issuesCommand
@@ -374,34 +433,145 @@ function createProgram(dependencies) {
374
433
  .option('--clear-due-at', 'Clear the current due date')
375
434
  .option('--base-revision <baseRevision>', 'Optimistic concurrency token from the last issue read')
376
435
  .action(async function action(issueId, options) {
377
- const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
378
- const issue = await client.updateIssue({
379
- accessToken,
380
- organizationId,
381
- issueId,
382
- issue: buildUpdateIssuePayload(options)
436
+ await runAuthenticatedCommand({
437
+ command: this,
438
+ dependencies,
439
+ configOptions,
440
+ operation: ({ client, accessToken, organizationId }) => client.updateIssue({
441
+ accessToken,
442
+ organizationId,
443
+ issueId,
444
+ issue: (0, command_inputs_1.buildUpdateIssuePayload)(options)
445
+ }),
446
+ text: (issue) => (0, formatters_1.formatIssueDetailLines)(issue),
447
+ json: (issue) => ({
448
+ command: 'issues.update',
449
+ issue
450
+ })
383
451
  });
384
- writeResult(this, dependencies, formatIssueDetailLines(issue), {
385
- command: 'issues.update',
386
- issue
452
+ });
453
+ issuesCommand
454
+ .command('transition <issueId>')
455
+ .description('Transition one issue by updating its status or status definition.')
456
+ .option('--status <status>', 'Issue status')
457
+ .option('--status-definition-id <statusDefinitionId>', 'Issue status definition id', Number)
458
+ .option('--base-revision <baseRevision>', 'Optimistic concurrency token from the last issue read')
459
+ .action(async function action(issueId, options) {
460
+ if (options.status === undefined && options.statusDefinitionId === undefined) {
461
+ throw new Error('Either --status or --status-definition-id is required.');
462
+ }
463
+ await runAuthenticatedCommand({
464
+ command: this,
465
+ dependencies,
466
+ configOptions,
467
+ operation: ({ client, accessToken, organizationId }) => client.updateIssue({
468
+ accessToken,
469
+ organizationId,
470
+ issueId,
471
+ issue: {
472
+ ...(options.status !== undefined ? { status: options.status } : {}),
473
+ ...(options.statusDefinitionId !== undefined
474
+ ? { statusDefinitionId: options.statusDefinitionId }
475
+ : {}),
476
+ ...(options.baseRevision !== undefined ? { baseRevision: options.baseRevision } : {})
477
+ }
478
+ }),
479
+ text: (issue) => (0, formatters_1.formatIssueDetailLines)(issue),
480
+ json: (issue) => ({
481
+ command: 'issues.transition',
482
+ issue
483
+ })
484
+ });
485
+ });
486
+ issuesCommand
487
+ .command('comment <issueId>')
488
+ .description('Create one issue comment in the configured organization.')
489
+ .option('--body-markdown <bodyMarkdown>', 'Issue comment body in markdown')
490
+ .option('--parent-comment-id <parentCommentId>', 'Optional parent comment id', Number)
491
+ .action(async function action(issueId, options) {
492
+ if (!options.bodyMarkdown?.trim()) {
493
+ throw new Error('Comment body is required');
494
+ }
495
+ const bodyMarkdown = options.bodyMarkdown.trim();
496
+ await runAuthenticatedCommand({
497
+ command: this,
498
+ dependencies,
499
+ configOptions,
500
+ operation: ({ client, accessToken, organizationId }) => client.createIssueComment({
501
+ accessToken,
502
+ organizationId,
503
+ issueId,
504
+ comment: {
505
+ bodyMarkdown,
506
+ ...(options.parentCommentId !== undefined
507
+ ? { parentCommentId: options.parentCommentId }
508
+ : {})
509
+ }
510
+ }),
511
+ text: (issue) => (0, formatters_1.formatIssueDetailLines)(issue),
512
+ json: (issue) => ({
513
+ command: 'issues.comment',
514
+ issue
515
+ })
516
+ });
517
+ });
518
+ issuesCommand
519
+ .command('link <issueId>')
520
+ .description('Create one issue relation from the selected issue to another issue.')
521
+ .option('--target-issue-id <targetIssueId>', 'Target issue id', Number)
522
+ .option('--relation-type <relationType>', 'Relation type key')
523
+ .action(async function action(issueId, options) {
524
+ if (options.targetIssueId === undefined) {
525
+ throw new Error('Target issue id is required');
526
+ }
527
+ if (!options.relationType?.trim()) {
528
+ throw new Error('Relation type is required');
529
+ }
530
+ const targetIssueId = options.targetIssueId;
531
+ const relationType = options.relationType;
532
+ await runAuthenticatedCommand({
533
+ command: this,
534
+ dependencies,
535
+ configOptions,
536
+ operation: ({ client, accessToken, organizationId }) => client.createIssueRelation({
537
+ accessToken,
538
+ organizationId,
539
+ issueId,
540
+ relation: {
541
+ targetIssueId,
542
+ relationType
543
+ }
544
+ }),
545
+ text: (issue) => (0, formatters_1.formatIssueDetailLines)(issue),
546
+ json: (issue) => ({
547
+ command: 'issues.link',
548
+ issue
549
+ })
387
550
  });
388
551
  });
389
552
  issuesCommand
390
553
  .command('delete <issueId>')
391
554
  .description('Delete one issue by id in the configured organization.')
392
555
  .action(async function action(issueId) {
393
- const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
394
- await client.deleteIssue({
395
- accessToken,
396
- organizationId,
397
- issueId
398
- });
399
- writeResult(this, dependencies, [`Issue ${issueId} deleted.`], {
400
- command: 'issues.delete',
401
- issueId,
402
- deleted: true
556
+ await runAuthenticatedCommand({
557
+ command: this,
558
+ dependencies,
559
+ configOptions,
560
+ operation: ({ client, accessToken, organizationId }) => client.deleteIssue({
561
+ accessToken,
562
+ organizationId,
563
+ issueId
564
+ }),
565
+ text: () => [`Issue ${issueId} deleted.`],
566
+ json: () => ({
567
+ command: 'issues.delete',
568
+ issueId,
569
+ deleted: true
570
+ })
403
571
  });
404
572
  });
573
+ }
574
+ function registerProjectCommands(program, dependencies, configOptions) {
405
575
  const projectsCommand = program.command('projects').description('Manage projects.');
406
576
  projectsCommand
407
577
  .command('list')
@@ -413,17 +583,176 @@ function createProgram(dependencies) {
413
583
  .option('--sort-by <sortBy>', 'Sort field')
414
584
  .option('--sort-order <sortOrder>', 'Sort direction')
415
585
  .action(async function action(options) {
416
- const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
417
- const result = await client.listProjects({
418
- accessToken,
419
- organizationId,
420
- ...options
586
+ await runAuthenticatedCommand({
587
+ command: this,
588
+ dependencies,
589
+ configOptions,
590
+ operation: ({ client, accessToken, organizationId }) => client.listProjects({
591
+ accessToken,
592
+ organizationId,
593
+ ...options
594
+ }),
595
+ text: (result) => (0, formatters_1.formatProjectLines)(result),
596
+ json: (result) => ({
597
+ command: 'projects.list',
598
+ result
599
+ })
421
600
  });
422
- writeResult(this, dependencies, formatProjectLines(result), {
423
- command: 'projects.list',
424
- result
601
+ });
602
+ }
603
+ function registerContentCommands(program, dependencies, configOptions) {
604
+ const contentCommand = program.command('content').description('Manage content entries.');
605
+ contentCommand
606
+ .command('list')
607
+ .description('List org-scoped content by default, or project-scoped content when a project id or key is provided.')
608
+ .option('--scope <scope>', 'Content scope filter')
609
+ .option('--project-id <projectId>', 'Project id filter', Number)
610
+ .option('--project-key <projectKey>', 'Project key filter')
611
+ .option('--parent-id <parentId>', 'Parent content id filter', Number)
612
+ .option('--slug <slug>', 'Slug filter')
613
+ .action(async function action(options) {
614
+ const normalizedOptions = (0, command_inputs_1.normalizeContentListOptions)(options);
615
+ await runAuthenticatedCommand({
616
+ command: this,
617
+ dependencies,
618
+ configOptions,
619
+ operation: ({ client, accessToken, organizationId }) => client.listContent({
620
+ accessToken,
621
+ organizationId,
622
+ ...normalizedOptions
623
+ }),
624
+ text: (result) => (0, formatters_1.formatContentLines)(result),
625
+ json: (result) => ({
626
+ command: 'content.list',
627
+ result
628
+ })
629
+ });
630
+ });
631
+ contentCommand
632
+ .command('get <contentId>')
633
+ .alias('show')
634
+ .description('Get one content entry by id in the configured organization.')
635
+ .action(async function action(contentId) {
636
+ await runAuthenticatedCommand({
637
+ command: this,
638
+ dependencies,
639
+ configOptions,
640
+ operation: ({ client, accessToken, organizationId }) => client.getContent({
641
+ accessToken,
642
+ organizationId,
643
+ contentId
644
+ }),
645
+ text: (content) => (0, formatters_1.formatContentDetailLines)(content),
646
+ json: (content) => ({
647
+ command: 'content.get',
648
+ content
649
+ })
650
+ });
651
+ });
652
+ contentCommand
653
+ .command('create')
654
+ .description('Create one content entry in the configured organization.')
655
+ .option('--title <title>', 'Content title')
656
+ .option('--content-markdown <contentMarkdown>', 'Content body in markdown')
657
+ .option('--slug <slug>', 'Content slug')
658
+ .option('--project-id <projectId>', 'Project id', Number)
659
+ .option('--parent-id <parentId>', 'Parent content id', Number)
660
+ .option('--position <position>', 'Content position', Number)
661
+ .action(async function action(options) {
662
+ if (!options.title?.trim()) {
663
+ throw new Error('Title is required');
664
+ }
665
+ if (!options.contentMarkdown?.trim()) {
666
+ throw new Error('Content markdown is required');
667
+ }
668
+ const title = options.title.trim();
669
+ const contentMarkdown = options.contentMarkdown;
670
+ await runAuthenticatedCommand({
671
+ command: this,
672
+ dependencies,
673
+ configOptions,
674
+ operation: ({ client, accessToken, organizationId }) => client.createContent({
675
+ accessToken,
676
+ organizationId,
677
+ content: {
678
+ title,
679
+ contentMarkdown,
680
+ ...(options.slug !== undefined ? { slug: options.slug } : {}),
681
+ ...(options.projectId !== undefined ? { projectId: options.projectId } : {}),
682
+ ...(options.parentId !== undefined ? { parentId: options.parentId } : {}),
683
+ ...(options.position !== undefined ? { position: options.position } : {})
684
+ }
685
+ }),
686
+ text: (content) => (0, formatters_1.formatContentDetailLines)(content),
687
+ json: (content) => ({
688
+ command: 'content.create',
689
+ content
690
+ })
425
691
  });
426
692
  });
693
+ contentCommand
694
+ .command('update <contentId>')
695
+ .description('Update one content entry by id in the configured organization.')
696
+ .option('--title <title>', 'Content title')
697
+ .option('--content-markdown <contentMarkdown>', 'Content body in markdown')
698
+ .option('--slug <slug>', 'Content slug')
699
+ .option('--parent-id <parentId>', 'Parent content id', Number)
700
+ .option('--position <position>', 'Content position', Number)
701
+ .option('--clear-parent', 'Clear the current parent content id')
702
+ .option('--base-revision <baseRevision>', 'Optimistic concurrency token from the last content read')
703
+ .action(async function action(contentId, options) {
704
+ await runAuthenticatedCommand({
705
+ command: this,
706
+ dependencies,
707
+ configOptions,
708
+ operation: ({ client, accessToken, organizationId }) => client.updateContent({
709
+ accessToken,
710
+ organizationId,
711
+ contentId,
712
+ content: (0, command_inputs_1.buildUpdateContentPayload)(options)
713
+ }),
714
+ text: (content) => (0, formatters_1.formatContentDetailLines)(content),
715
+ json: (content) => ({
716
+ command: 'content.update',
717
+ content
718
+ })
719
+ });
720
+ });
721
+ contentCommand
722
+ .command('delete <contentId>')
723
+ .description('Delete one content entry by id in the configured organization.')
724
+ .action(async function action(contentId) {
725
+ await runAuthenticatedCommand({
726
+ command: this,
727
+ dependencies,
728
+ configOptions,
729
+ operation: ({ client, accessToken, organizationId }) => client.deleteContent({
730
+ accessToken,
731
+ organizationId,
732
+ contentId
733
+ }),
734
+ text: () => [`Content ${contentId} deleted.`],
735
+ json: () => ({
736
+ command: 'content.delete',
737
+ contentId,
738
+ deleted: true
739
+ })
740
+ });
741
+ });
742
+ contentCommand
743
+ .command('publish <contentId>')
744
+ .description('Publish one content artifact when supported by the installed CLI/API surface.')
745
+ .action(async () => {
746
+ throw unsupportedContentError("publish" /* UnsupportedContentCommand.Publish */);
747
+ });
748
+ contentCommand
749
+ .command('attach <contentId>')
750
+ .description('Attach an artifact to content when supported by the installed CLI/API surface.')
751
+ .action(async () => {
752
+ throw unsupportedContentError("attach" /* UnsupportedContentCommand.Attach */);
753
+ });
754
+ }
755
+ function registerConfigCommands(program, dependencies, configOptions) {
427
756
  const configCommand = program
428
757
  .command('config')
429
758
  .description('Inspect resolved CLI configuration.');
@@ -446,6 +775,8 @@ function createProgram(dependencies) {
446
775
  config: renderConfigSummary(config, Boolean(options.includeSensitive))
447
776
  });
448
777
  });
778
+ }
779
+ function registerSessionCommands(program, dependencies, configOptions) {
449
780
  program
450
781
  .command('login')
451
782
  .description('Authenticate with the Reximo API and save auth state for the active profile.')
@@ -546,7 +877,6 @@ function createProgram(dependencies) {
546
877
  tokensCleared: true
547
878
  });
548
879
  });
549
- return program;
550
880
  }
551
881
  async function runCli(args, dependencies) {
552
882
  const runtimeDependencies = toRuntimeDependencies(dependencies);