@reximo/cli 0.1.0-alpha.3 → 0.1.0-alpha.5

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.
@@ -33,6 +33,12 @@ function getOutputMode(command) {
33
33
  const opts = command.optsWithGlobals();
34
34
  return opts.json ? "json" /* OutputMode.Json */ : "text" /* OutputMode.Text */;
35
35
  }
36
+ function parseCsvOption(value) {
37
+ return value
38
+ .split(',')
39
+ .map((entry) => entry.trim())
40
+ .filter((entry) => entry.length > 0);
41
+ }
36
42
  function renderConfigSummary(config, includeSensitive) {
37
43
  return {
38
44
  profile: config.profile,
@@ -69,6 +75,15 @@ function requireConfiguredOrganization(config) {
69
75
  }
70
76
  return config.organizationId;
71
77
  }
78
+ function createAuthenticatedContext(dependencies, configOptions) {
79
+ const config = (0, config_1.loadResolvedConfig)(configOptions);
80
+ return {
81
+ config,
82
+ client: dependencies.clientFactory(config),
83
+ accessToken: requireConfiguredToken(config, "accessToken" /* ConfiguredTokenField.AccessToken */),
84
+ organizationId: requireConfiguredOrganization(config)
85
+ };
86
+ }
72
87
  async function resolveRequiredValue(currentValue, label, prompt) {
73
88
  if (currentValue?.trim()) {
74
89
  return currentValue.trim();
@@ -124,6 +139,214 @@ async function resolveOrganizationContext(promptAdapter, organizations, organiza
124
139
  const selectedOrganizationId = await promptAdapter.select('Select organization', choices);
125
140
  return (activeOrganizations.find((organization) => organization.organizationId === selectedOrganizationId) ?? null);
126
141
  }
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 formatContentLines(result) {
189
+ if (result.data.length === 0) {
190
+ return ['No content entries found.'];
191
+ }
192
+ return [
193
+ `Content entries: ${result.data.length}`,
194
+ ...result.data.map((content) => `${content.slug} [id:${content.id}] ${content.title} (revision ${content.revision})`)
195
+ ];
196
+ }
197
+ function formatContentDetailLines(content) {
198
+ return [
199
+ `Content: ${content.slug} [id:${content.id}]`,
200
+ `Title: ${content.title}`,
201
+ `Revision: ${content.revision}`,
202
+ `Project: ${content.projectId ?? '(none)'}`,
203
+ `Parent: ${content.parentId ?? '(none)'}`,
204
+ `Updated: ${content.updatedAt}`
205
+ ];
206
+ }
207
+ function buildUpdateIssuePayload(options) {
208
+ const payload = {};
209
+ if (options.title !== undefined) {
210
+ payload['title'] = options.title;
211
+ }
212
+ if (options.descriptionMarkdown !== undefined) {
213
+ payload['descriptionMarkdown'] = options.descriptionMarkdown;
214
+ }
215
+ if (options.status !== undefined) {
216
+ payload['status'] = options.status;
217
+ }
218
+ if (options.statusDefinitionId !== undefined) {
219
+ payload['statusDefinitionId'] = options.statusDefinitionId;
220
+ }
221
+ if (options.priority !== undefined) {
222
+ payload['priority'] = options.priority;
223
+ }
224
+ if (options.position !== undefined) {
225
+ payload['position'] = options.position;
226
+ }
227
+ if (options.baseRevision !== undefined) {
228
+ payload['baseRevision'] = options.baseRevision;
229
+ }
230
+ if (options.clearParentIssue) {
231
+ payload['parentIssueId'] = null;
232
+ }
233
+ else if (options.parentIssueId !== undefined) {
234
+ payload['parentIssueId'] = options.parentIssueId;
235
+ }
236
+ if (options.clearEstimate) {
237
+ payload['estimate'] = null;
238
+ }
239
+ else if (options.estimate !== undefined) {
240
+ payload['estimate'] = options.estimate;
241
+ }
242
+ if (options.clearDueAt) {
243
+ payload['dueAt'] = null;
244
+ }
245
+ else if (options.dueAt !== undefined) {
246
+ payload['dueAt'] = options.dueAt;
247
+ }
248
+ if (Object.keys(payload).length === 0) {
249
+ throw new Error('At least one update field is required.');
250
+ }
251
+ return payload;
252
+ }
253
+ function buildUpdateContentPayload(options) {
254
+ const payload = {};
255
+ if (options.title !== undefined) {
256
+ payload['title'] = options.title;
257
+ }
258
+ if (options.contentMarkdown !== undefined) {
259
+ payload['contentMarkdown'] = options.contentMarkdown;
260
+ }
261
+ if (options.slug !== undefined) {
262
+ payload['slug'] = options.slug;
263
+ }
264
+ if (options.position !== undefined) {
265
+ payload['position'] = options.position;
266
+ }
267
+ if (options.baseRevision !== undefined) {
268
+ payload['baseRevision'] = options.baseRevision;
269
+ }
270
+ if (options.clearParent) {
271
+ payload['parentId'] = null;
272
+ }
273
+ else if (options.parentId !== undefined) {
274
+ payload['parentId'] = options.parentId;
275
+ }
276
+ if (Object.keys(payload).length === 0) {
277
+ throw new Error('At least one content update field is required.');
278
+ }
279
+ return payload;
280
+ }
281
+ function buildIssuesSearchInput(options) {
282
+ if (options.status !== undefined || options.statuses !== undefined) {
283
+ return options;
284
+ }
285
+ return {
286
+ ...options,
287
+ statuses: ["backlog" /* IssueStatusOption.Backlog */, "in_progress" /* IssueStatusOption.InProgress */, "blocked" /* IssueStatusOption.Blocked */]
288
+ };
289
+ }
290
+ function buildAuthStatus(config, client) {
291
+ const target = client.describeTarget();
292
+ return {
293
+ profile: config.profile,
294
+ configPath: config.configPath,
295
+ configFileFound: config.configFileFound,
296
+ apiBaseUrl: config.apiBaseUrl ?? config_1.DEFAULT_API_BASE_URL,
297
+ isConfigured: client.isConfigured(),
298
+ hasAccessToken: target.hasAccessToken,
299
+ hasRefreshToken: target.hasRefreshToken,
300
+ organizationId: target.organizationId
301
+ };
302
+ }
303
+ function buildDoctorReport(config, client) {
304
+ const auth = buildAuthStatus(config, client);
305
+ return {
306
+ command: 'doctor',
307
+ auth,
308
+ checks: {
309
+ apiBaseUrlConfigured: auth.isConfigured,
310
+ accessTokenConfigured: auth.hasAccessToken,
311
+ refreshTokenConfigured: auth.hasRefreshToken,
312
+ organizationConfigured: Boolean(auth.organizationId),
313
+ readyForAuthenticatedCommands: auth.isConfigured && auth.hasAccessToken && Boolean(auth.organizationId)
314
+ },
315
+ capabilities: {
316
+ issues: {
317
+ search: true,
318
+ list: true,
319
+ get: true,
320
+ create: true,
321
+ update: true,
322
+ delete: true,
323
+ comment: true,
324
+ transition: true,
325
+ link: true
326
+ },
327
+ projects: {
328
+ list: true
329
+ },
330
+ content: {
331
+ list: true,
332
+ get: true,
333
+ create: true,
334
+ update: true,
335
+ delete: true,
336
+ publish: false,
337
+ attach: false
338
+ }
339
+ },
340
+ notes: [
341
+ 'Issue search, get, create, update, delete, comment, transition, and link are currently supported.',
342
+ 'Project listing and content list, get, create, update, and delete are currently supported.',
343
+ 'The current API surface does not expose a separate content publish endpoint.'
344
+ ]
345
+ };
346
+ }
347
+ function unsupportedContentError(subcommand) {
348
+ 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.`);
349
+ }
127
350
  function createProgram(dependencies) {
128
351
  const configOptions = {
129
352
  env: dependencies.env,
@@ -180,6 +403,393 @@ function createProgram(dependencies) {
180
403
  }
181
404
  });
182
405
  });
406
+ const authCommand = program.command('auth').description('Inspect Reximo authentication state.');
407
+ authCommand
408
+ .command('status')
409
+ .description('Show whether the current CLI profile is ready for authenticated API commands.')
410
+ .action(function action() {
411
+ const config = (0, config_1.loadResolvedConfig)(configOptions);
412
+ const client = dependencies.clientFactory(config);
413
+ const status = buildAuthStatus(config, client);
414
+ writeResult(this, dependencies, [
415
+ `Profile: ${status.profile}`,
416
+ `Config path: ${status.configPath}`,
417
+ `Config file found: ${status.configFileFound ? 'yes' : 'no'}`,
418
+ `API base URL: ${status.apiBaseUrl}`,
419
+ `Configured for API: ${status.isConfigured ? 'yes' : 'no'}`,
420
+ `Access token configured: ${status.hasAccessToken ? 'yes' : 'no'}`,
421
+ `Refresh token configured: ${status.hasRefreshToken ? 'yes' : 'no'}`,
422
+ `Organization: ${status.organizationId ?? '(not configured)'}`
423
+ ], {
424
+ command: 'auth.status',
425
+ status
426
+ });
427
+ });
428
+ program
429
+ .command('doctor')
430
+ .description('Report Reximo CLI readiness and command-surface capabilities for automation.')
431
+ .action(function action() {
432
+ const config = (0, config_1.loadResolvedConfig)(configOptions);
433
+ const client = dependencies.clientFactory(config);
434
+ const report = buildDoctorReport(config, client);
435
+ writeResult(this, dependencies, [
436
+ `Profile: ${report.auth.profile}`,
437
+ `Configured for API: ${report.auth.isConfigured ? 'yes' : 'no'}`,
438
+ `Access token configured: ${report.auth.hasAccessToken ? 'yes' : 'no'}`,
439
+ `Refresh token configured: ${report.auth.hasRefreshToken ? 'yes' : 'no'}`,
440
+ `Organization: ${report.auth.organizationId ?? '(not configured)'}`,
441
+ `Ready for authenticated commands: ${report.auth.isConfigured && report.auth.hasAccessToken && Boolean(report.auth.organizationId) ? 'yes' : 'no'}`,
442
+ `Issue commands: search, list, get, create, update, delete, comment, transition, link`,
443
+ `Content commands: list, get, create, update, delete`,
444
+ `Unsupported content commands: publish, attach`
445
+ ], report);
446
+ });
447
+ const issuesCommand = program.command('issues').description('Manage issues.');
448
+ issuesCommand
449
+ .command('search')
450
+ .alias('list')
451
+ .description('Search visible issues within the configured organization.')
452
+ .option('--page <page>', 'Page number (1-based)', Number)
453
+ .option('--page-size <pageSize>', 'Items per page', Number)
454
+ .option('--project-id <projectId>', 'Restrict to one project id', Number)
455
+ .option('--label-id <labelId>', 'Restrict to one label id', Number)
456
+ .option('--assignee-user-id <assigneeUserId>', 'Restrict to one assignee user id', Number)
457
+ .option('--watcher-user-id <watcherUserId>', 'Restrict to one watcher user id', Number)
458
+ .option('--activity-actor-user-id <activityActorUserId>', 'Restrict to issues with activity authored by one user', Number)
459
+ .option('--search <search>', 'Search by issue identifier, title, or description')
460
+ .option('--status <status>', 'Restrict to one status')
461
+ .option('--statuses <statuses>', 'Comma-separated list of statuses', parseCsvOption)
462
+ .option('--priority <priority>', 'Restrict to one priority')
463
+ .option('--sort-by <sortBy>', 'Sort field')
464
+ .option('--sort-order <sortOrder>', 'Sort direction')
465
+ .action(async function action(options) {
466
+ const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
467
+ const result = await client.listIssues({
468
+ accessToken,
469
+ organizationId,
470
+ ...buildIssuesSearchInput(options)
471
+ });
472
+ writeResult(this, dependencies, formatIssueLines(result), {
473
+ command: 'issues.search',
474
+ result
475
+ });
476
+ });
477
+ issuesCommand
478
+ .command('get <issueId>')
479
+ .alias('show')
480
+ .description('Get one issue by id in the configured organization.')
481
+ .action(async function action(issueId) {
482
+ const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
483
+ const issue = await client.getIssue({
484
+ accessToken,
485
+ organizationId,
486
+ issueId
487
+ });
488
+ writeResult(this, dependencies, formatIssueDetailLines(issue), {
489
+ command: 'issues.get',
490
+ issue
491
+ });
492
+ });
493
+ issuesCommand
494
+ .command('create')
495
+ .description('Create one issue in the configured organization.')
496
+ .option('--title <title>', 'Issue title')
497
+ .option('--description-markdown <descriptionMarkdown>', 'Issue description in markdown')
498
+ .option('--status <status>', 'Issue status')
499
+ .option('--status-definition-id <statusDefinitionId>', 'Issue status definition id', Number)
500
+ .option('--priority <priority>', 'Issue priority')
501
+ .option('--project-id <projectId>', 'Project id', Number)
502
+ .option('--parent-issue-id <parentIssueId>', 'Parent issue id', Number)
503
+ .option('--position <position>', 'Issue position', Number)
504
+ .option('--estimate <estimate>', 'Issue estimate', Number)
505
+ .option('--due-at <dueAt>', 'Issue due date as an ISO-8601 string')
506
+ .action(async function action(options) {
507
+ const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
508
+ if (!options.title?.trim()) {
509
+ throw new Error('Title is required');
510
+ }
511
+ const issue = await client.createIssue({
512
+ accessToken,
513
+ organizationId,
514
+ issue: {
515
+ ...options,
516
+ title: options.title.trim()
517
+ }
518
+ });
519
+ writeResult(this, dependencies, formatIssueDetailLines(issue), {
520
+ command: 'issues.create',
521
+ issue
522
+ });
523
+ });
524
+ issuesCommand
525
+ .command('update <issueId>')
526
+ .description('Update one issue by id in the configured organization.')
527
+ .option('--title <title>', 'Issue title')
528
+ .option('--description-markdown <descriptionMarkdown>', 'Issue description in markdown')
529
+ .option('--status <status>', 'Issue status')
530
+ .option('--status-definition-id <statusDefinitionId>', 'Issue status definition id', Number)
531
+ .option('--priority <priority>', 'Issue priority')
532
+ .option('--parent-issue-id <parentIssueId>', 'Parent issue id', Number)
533
+ .option('--position <position>', 'Issue position', Number)
534
+ .option('--estimate <estimate>', 'Issue estimate', Number)
535
+ .option('--due-at <dueAt>', 'Issue due date as an ISO-8601 string')
536
+ .option('--clear-parent-issue', 'Clear the current parent issue')
537
+ .option('--clear-estimate', 'Clear the current estimate')
538
+ .option('--clear-due-at', 'Clear the current due date')
539
+ .option('--base-revision <baseRevision>', 'Optimistic concurrency token from the last issue read')
540
+ .action(async function action(issueId, options) {
541
+ const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
542
+ const issue = await client.updateIssue({
543
+ accessToken,
544
+ organizationId,
545
+ issueId,
546
+ issue: buildUpdateIssuePayload(options)
547
+ });
548
+ writeResult(this, dependencies, formatIssueDetailLines(issue), {
549
+ command: 'issues.update',
550
+ issue
551
+ });
552
+ });
553
+ issuesCommand
554
+ .command('transition <issueId>')
555
+ .description('Transition one issue by updating its status or status definition.')
556
+ .option('--status <status>', 'Issue status')
557
+ .option('--status-definition-id <statusDefinitionId>', 'Issue status definition id', Number)
558
+ .option('--base-revision <baseRevision>', 'Optimistic concurrency token from the last issue read')
559
+ .action(async function action(issueId, options) {
560
+ const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
561
+ if (options.status === undefined && options.statusDefinitionId === undefined) {
562
+ throw new Error('Either --status or --status-definition-id is required.');
563
+ }
564
+ const issue = await client.updateIssue({
565
+ accessToken,
566
+ organizationId,
567
+ issueId,
568
+ issue: {
569
+ ...(options.status !== undefined ? { status: options.status } : {}),
570
+ ...(options.statusDefinitionId !== undefined
571
+ ? { statusDefinitionId: options.statusDefinitionId }
572
+ : {}),
573
+ ...(options.baseRevision !== undefined ? { baseRevision: options.baseRevision } : {})
574
+ }
575
+ });
576
+ writeResult(this, dependencies, formatIssueDetailLines(issue), {
577
+ command: 'issues.transition',
578
+ issue
579
+ });
580
+ });
581
+ issuesCommand
582
+ .command('comment <issueId>')
583
+ .description('Create one issue comment in the configured organization.')
584
+ .option('--body-markdown <bodyMarkdown>', 'Issue comment body in markdown')
585
+ .option('--parent-comment-id <parentCommentId>', 'Optional parent comment id', Number)
586
+ .action(async function action(issueId, options) {
587
+ const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
588
+ if (!options.bodyMarkdown?.trim()) {
589
+ throw new Error('Comment body is required');
590
+ }
591
+ const issue = await client.createIssueComment({
592
+ accessToken,
593
+ organizationId,
594
+ issueId,
595
+ comment: {
596
+ bodyMarkdown: options.bodyMarkdown.trim(),
597
+ ...(options.parentCommentId !== undefined
598
+ ? { parentCommentId: options.parentCommentId }
599
+ : {})
600
+ }
601
+ });
602
+ writeResult(this, dependencies, formatIssueDetailLines(issue), {
603
+ command: 'issues.comment',
604
+ issue
605
+ });
606
+ });
607
+ issuesCommand
608
+ .command('link <issueId>')
609
+ .description('Create one issue relation from the selected issue to another issue.')
610
+ .option('--target-issue-id <targetIssueId>', 'Target issue id', Number)
611
+ .option('--relation-type <relationType>', 'Relation type key')
612
+ .action(async function action(issueId, options) {
613
+ const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
614
+ if (options.targetIssueId === undefined) {
615
+ throw new Error('Target issue id is required');
616
+ }
617
+ if (!options.relationType?.trim()) {
618
+ throw new Error('Relation type is required');
619
+ }
620
+ const issue = await client.createIssueRelation({
621
+ accessToken,
622
+ organizationId,
623
+ issueId,
624
+ relation: {
625
+ targetIssueId: options.targetIssueId,
626
+ relationType: options.relationType
627
+ }
628
+ });
629
+ writeResult(this, dependencies, formatIssueDetailLines(issue), {
630
+ command: 'issues.link',
631
+ issue
632
+ });
633
+ });
634
+ issuesCommand
635
+ .command('delete <issueId>')
636
+ .description('Delete one issue by id in the configured organization.')
637
+ .action(async function action(issueId) {
638
+ const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
639
+ await client.deleteIssue({
640
+ accessToken,
641
+ organizationId,
642
+ issueId
643
+ });
644
+ writeResult(this, dependencies, [`Issue ${issueId} deleted.`], {
645
+ command: 'issues.delete',
646
+ issueId,
647
+ deleted: true
648
+ });
649
+ });
650
+ const projectsCommand = program.command('projects').description('Manage projects.');
651
+ projectsCommand
652
+ .command('list')
653
+ .description('List projects visible to the current user in the configured organization.')
654
+ .option('--page <page>', 'Page number (1-based)', Number)
655
+ .option('--page-size <pageSize>', 'Items per page', Number)
656
+ .option('--search <search>', 'Search by project name')
657
+ .option('--visibility <visibility>', 'Restrict to one visibility')
658
+ .option('--sort-by <sortBy>', 'Sort field')
659
+ .option('--sort-order <sortOrder>', 'Sort direction')
660
+ .action(async function action(options) {
661
+ const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
662
+ const result = await client.listProjects({
663
+ accessToken,
664
+ organizationId,
665
+ ...options
666
+ });
667
+ writeResult(this, dependencies, formatProjectLines(result), {
668
+ command: 'projects.list',
669
+ result
670
+ });
671
+ });
672
+ const contentCommand = program.command('content').description('Manage content entries.');
673
+ contentCommand
674
+ .command('list')
675
+ .description('List content entries visible to the current user in the configured organization.')
676
+ .option('--scope <scope>', 'Content scope filter')
677
+ .option('--project-id <projectId>', 'Project id filter', Number)
678
+ .option('--parent-id <parentId>', 'Parent content id filter', Number)
679
+ .option('--slug <slug>', 'Slug filter')
680
+ .action(async function action(options) {
681
+ const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
682
+ const result = await client.listContent({
683
+ accessToken,
684
+ organizationId,
685
+ ...options
686
+ });
687
+ writeResult(this, dependencies, formatContentLines(result), {
688
+ command: 'content.list',
689
+ result
690
+ });
691
+ });
692
+ contentCommand
693
+ .command('get <contentId>')
694
+ .alias('show')
695
+ .description('Get one content entry by id in the configured organization.')
696
+ .action(async function action(contentId) {
697
+ const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
698
+ const content = await client.getContent({
699
+ accessToken,
700
+ organizationId,
701
+ contentId
702
+ });
703
+ writeResult(this, dependencies, formatContentDetailLines(content), {
704
+ command: 'content.get',
705
+ content
706
+ });
707
+ });
708
+ contentCommand
709
+ .command('create')
710
+ .description('Create one content entry in the configured organization.')
711
+ .option('--title <title>', 'Content title')
712
+ .option('--content-markdown <contentMarkdown>', 'Content body in markdown')
713
+ .option('--slug <slug>', 'Content slug')
714
+ .option('--project-id <projectId>', 'Project id', Number)
715
+ .option('--parent-id <parentId>', 'Parent content id', Number)
716
+ .option('--position <position>', 'Content position', Number)
717
+ .action(async function action(options) {
718
+ const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
719
+ if (!options.title?.trim()) {
720
+ throw new Error('Title is required');
721
+ }
722
+ if (!options.contentMarkdown?.trim()) {
723
+ throw new Error('Content markdown is required');
724
+ }
725
+ const content = await client.createContent({
726
+ accessToken,
727
+ organizationId,
728
+ content: {
729
+ title: options.title.trim(),
730
+ contentMarkdown: options.contentMarkdown,
731
+ ...(options.slug !== undefined ? { slug: options.slug } : {}),
732
+ ...(options.projectId !== undefined ? { projectId: options.projectId } : {}),
733
+ ...(options.parentId !== undefined ? { parentId: options.parentId } : {}),
734
+ ...(options.position !== undefined ? { position: options.position } : {})
735
+ }
736
+ });
737
+ writeResult(this, dependencies, formatContentDetailLines(content), {
738
+ command: 'content.create',
739
+ content
740
+ });
741
+ });
742
+ contentCommand
743
+ .command('update <contentId>')
744
+ .description('Update one content entry by id in the configured organization.')
745
+ .option('--title <title>', 'Content title')
746
+ .option('--content-markdown <contentMarkdown>', 'Content body in markdown')
747
+ .option('--slug <slug>', 'Content slug')
748
+ .option('--parent-id <parentId>', 'Parent content id', Number)
749
+ .option('--position <position>', 'Content position', Number)
750
+ .option('--clear-parent', 'Clear the current parent content id')
751
+ .option('--base-revision <baseRevision>', 'Optimistic concurrency token from the last content read')
752
+ .action(async function action(contentId, options) {
753
+ const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
754
+ const content = await client.updateContent({
755
+ accessToken,
756
+ organizationId,
757
+ contentId,
758
+ content: buildUpdateContentPayload(options)
759
+ });
760
+ writeResult(this, dependencies, formatContentDetailLines(content), {
761
+ command: 'content.update',
762
+ content
763
+ });
764
+ });
765
+ contentCommand
766
+ .command('delete <contentId>')
767
+ .description('Delete one content entry by id in the configured organization.')
768
+ .action(async function action(contentId) {
769
+ const { client, accessToken, organizationId } = createAuthenticatedContext(dependencies, configOptions);
770
+ await client.deleteContent({
771
+ accessToken,
772
+ organizationId,
773
+ contentId
774
+ });
775
+ writeResult(this, dependencies, [`Content ${contentId} deleted.`], {
776
+ command: 'content.delete',
777
+ contentId,
778
+ deleted: true
779
+ });
780
+ });
781
+ contentCommand
782
+ .command('publish <contentId>')
783
+ .description('Publish one content artifact when supported by the installed CLI/API surface.')
784
+ .action(async () => {
785
+ throw unsupportedContentError("publish" /* UnsupportedContentCommand.Publish */);
786
+ });
787
+ contentCommand
788
+ .command('attach <contentId>')
789
+ .description('Attach an artifact to content when supported by the installed CLI/API surface.')
790
+ .action(async () => {
791
+ throw unsupportedContentError("attach" /* UnsupportedContentCommand.Attach */);
792
+ });
183
793
  const configCommand = program
184
794
  .command('config')
185
795
  .description('Inspect resolved CLI configuration.');