@senso-ai/cli 0.2.3 → 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 (3) hide show
  1. package/README.md +53 -63
  2. package/dist/cli.js +267 -334
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -113,7 +113,7 @@ function getConfigPath() {
113
113
  }
114
114
 
115
115
  // src/utils/updater.ts
116
- var GITHUB_REPO = "AI-Template-SDK/senso-user-cli";
116
+ var NPM_PACKAGE = "@senso-ai/cli";
117
117
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
118
118
  async function checkForUpdate(quiet) {
119
119
  if (process.env.SENSO_NO_UPDATE_CHECK === "1" || quiet) {
@@ -128,16 +128,8 @@ async function checkForUpdate(quiet) {
128
128
  return;
129
129
  }
130
130
  try {
131
- const res = await fetch(
132
- `https://api.github.com/repos/${GITHUB_REPO}/releases/latest`,
133
- {
134
- headers: { Accept: "application/vnd.github.v3+json" },
135
- signal: AbortSignal.timeout(5e3)
136
- }
137
- );
138
- if (!res.ok) return;
139
- const release = await res.json();
140
- const latest = release.tag_name.replace(/^v/, "");
131
+ const latest = await getLatestVersion();
132
+ if (!latest) return;
141
133
  updateConfig({
142
134
  lastUpdateCheck: (/* @__PURE__ */ new Date()).toISOString(),
143
135
  latestVersion: latest
@@ -148,17 +140,18 @@ async function checkForUpdate(quiet) {
148
140
  } catch {
149
141
  }
150
142
  }
151
- async function getLatestRelease() {
143
+ async function getLatestVersion() {
152
144
  try {
153
145
  const res = await fetch(
154
- `https://api.github.com/repos/${GITHUB_REPO}/releases/latest`,
146
+ `https://registry.npmjs.org/${NPM_PACKAGE}`,
155
147
  {
156
- headers: { Accept: "application/vnd.github.v3+json" },
148
+ headers: { Accept: "application/json" },
157
149
  signal: AbortSignal.timeout(1e4)
158
150
  }
159
151
  );
160
152
  if (!res.ok) return null;
161
- return await res.json();
153
+ const data = await res.json();
154
+ return data["dist-tags"]?.latest ?? null;
162
155
  } catch {
163
156
  return null;
164
157
  }
@@ -211,17 +204,23 @@ async function apiRequest(opts) {
211
204
  });
212
205
  if (!res.ok) {
213
206
  let body;
207
+ const text3 = await res.text();
214
208
  try {
215
- body = await res.json();
209
+ body = JSON.parse(text3);
216
210
  } catch {
217
- body = await res.text();
211
+ body = text3;
218
212
  }
219
213
  throw new ApiError(res.status, res.statusText, body);
220
214
  }
221
215
  if (res.status === 204) {
222
216
  return void 0;
223
217
  }
224
- return await res.json();
218
+ const text2 = await res.text();
219
+ try {
220
+ return JSON.parse(text2);
221
+ } catch {
222
+ throw new Error(`Invalid JSON response from ${opts.path}`);
223
+ }
225
224
  } finally {
226
225
  clearTimeout(timeout);
227
226
  }
@@ -280,7 +279,7 @@ async function verifyApiKey(apiKey, baseUrl) {
280
279
  });
281
280
  }
282
281
  function registerAuthCommands(program2) {
283
- program2.command("login").description("Save API key to config (validates via GET /org/me)").action(async () => {
282
+ program2.command("login").description("Authenticate with Senso. Paste your API key and it will be validated against your organization, then stored locally.").action(async () => {
284
283
  const opts = program2.opts();
285
284
  banner();
286
285
  console.log(` ${pc3.bold("Welcome to Senso CLI!")}
@@ -322,11 +321,11 @@ function registerAuthCommands(program2) {
322
321
  process.exit(1);
323
322
  }
324
323
  });
325
- program2.command("logout").description("Remove stored credentials").action(() => {
324
+ program2.command("logout").description("Remove stored API key and organization info from local config.").action(() => {
326
325
  clearConfig();
327
326
  success("Credentials removed.");
328
327
  });
329
- program2.command("whoami").description("Show current auth status and org info").action(async () => {
328
+ program2.command("whoami").description("Show which organization you are authenticated as, including org ID, slug, tier, and API key prefix.").action(async () => {
330
329
  const opts = program2.opts();
331
330
  const apiKey = getApiKey({ apiKey: opts.apiKey });
332
331
  if (!apiKey) {
@@ -372,8 +371,8 @@ function registerAuthCommands(program2) {
372
371
 
373
372
  // src/commands/org.ts
374
373
  function registerOrgCommands(program2) {
375
- const org = program2.command("org").description("Organization management");
376
- org.command("get").description("Get organization details").action(async () => {
374
+ const org = program2.command("org").description("View and update organization profile and settings. Includes name, slug, logo, websites, locations, and tier information.");
375
+ org.command("get").description("Get full organization details including name, slug, tier, websites, locations, configured AI models, publishers, and schedule.").action(async () => {
377
376
  const opts = program2.opts();
378
377
  try {
379
378
  const data = await apiRequest({ path: "/org/me", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
@@ -383,12 +382,12 @@ function registerOrgCommands(program2) {
383
382
  process.exit(1);
384
383
  }
385
384
  });
386
- org.command("update").description("Update organization details").requiredOption("--data <json>", "JSON org fields to update").action(async (cmdOpts) => {
385
+ org.command("update").description("Update organization details. All fields are optional \u2014 only provided fields are changed. Pass an empty array for websites/locations to clear them.").requiredOption("--data <json>", 'JSON: { "name": "...", "slug": "...", "logo_url": "...", "websites": [...], "locations": [...] }').action(async (cmdOpts) => {
387
386
  const opts = program2.opts();
388
387
  try {
389
388
  const body = JSON.parse(cmdOpts.data);
390
389
  const data = await apiRequest({
391
- method: "PATCH",
390
+ method: "PUT",
392
391
  path: "/org/me",
393
392
  body,
394
393
  apiKey: opts.apiKey,
@@ -405,18 +404,23 @@ function registerOrgCommands(program2) {
405
404
 
406
405
  // src/commands/users.ts
407
406
  function registerUserCommands(program2) {
408
- const users = program2.command("users").description("Manage users in organization");
409
- users.command("list").description("List users in organization").action(async () => {
407
+ const users = program2.command("users").description("Manage users within the organization. Add, update roles, remove users, or set the active organization for a user.");
408
+ users.command("list").description("List all users in the organization. Returns user IDs, roles, and membership status.").option("--limit <n>", "Maximum number of users to return").option("--offset <n>", "Number of users to skip (for pagination)").action(async (cmdOpts) => {
410
409
  const opts = program2.opts();
411
410
  try {
412
- const data = await apiRequest({ path: "/org/users", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
411
+ const data = await apiRequest({
412
+ path: "/org/users",
413
+ params: { limit: cmdOpts.limit, offset: cmdOpts.offset },
414
+ apiKey: opts.apiKey,
415
+ baseUrl: opts.baseUrl
416
+ });
413
417
  console.log(JSON.stringify(data, null, 2));
414
418
  } catch (err) {
415
419
  error(formatApiError(err));
416
420
  process.exit(1);
417
421
  }
418
422
  });
419
- users.command("add").description("Add user to organization").requiredOption("--data <json>", "JSON user data").action(async (cmdOpts) => {
423
+ users.command("add").description("Add an existing platform user to the organization. Requires user_id and role_id.").requiredOption("--data <json>", 'JSON: { "user_id": "uuid", "role_id": "uuid", "is_current": false }').action(async (cmdOpts) => {
420
424
  const opts = program2.opts();
421
425
  try {
422
426
  const body = JSON.parse(cmdOpts.data);
@@ -434,7 +438,7 @@ function registerUserCommands(program2) {
434
438
  process.exit(1);
435
439
  }
436
440
  });
437
- users.command("get <userId>").description("Get a user in the organization").action(async (userId) => {
441
+ users.command("get <userId>").description("Get a user's details including their role and membership status in the organization.").action(async (userId) => {
438
442
  const opts = program2.opts();
439
443
  try {
440
444
  const data = await apiRequest({ path: `/org/users/${userId}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
@@ -444,12 +448,12 @@ function registerUserCommands(program2) {
444
448
  process.exit(1);
445
449
  }
446
450
  });
447
- users.command("update <userId>").description("Update a user's role").requiredOption("--data <json>", "JSON user update data").action(async (userId, cmdOpts) => {
451
+ users.command("update <userId>").description("Update a user's role in the organization. Requires role_id in the JSON body.").requiredOption("--data <json>", 'JSON: { "role_id": "uuid", "is_current": true }').action(async (userId, cmdOpts) => {
448
452
  const opts = program2.opts();
449
453
  try {
450
454
  const body = JSON.parse(cmdOpts.data);
451
455
  const data = await apiRequest({
452
- method: "PATCH",
456
+ method: "PUT",
453
457
  path: `/org/users/${userId}`,
454
458
  body,
455
459
  apiKey: opts.apiKey,
@@ -462,7 +466,7 @@ function registerUserCommands(program2) {
462
466
  process.exit(1);
463
467
  }
464
468
  });
465
- users.command("remove <userId>").description("Remove a user from the organization").action(async (userId) => {
469
+ users.command("remove <userId>").description("Remove a user from the organization. This does not delete the platform user account.").action(async (userId) => {
466
470
  const opts = program2.opts();
467
471
  try {
468
472
  await apiRequest({ method: "DELETE", path: `/org/users/${userId}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
@@ -472,82 +476,17 @@ function registerUserCommands(program2) {
472
476
  process.exit(1);
473
477
  }
474
478
  });
475
- }
476
-
477
- // src/commands/api-keys.ts
478
- function registerApiKeyCommands(program2) {
479
- const keys = program2.command("api-keys").description("Manage API keys");
480
- keys.command("list").description("List API keys").action(async () => {
481
- const opts = program2.opts();
482
- try {
483
- const data = await apiRequest({ path: "/org/api-keys", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
484
- console.log(JSON.stringify(data, null, 2));
485
- } catch (err) {
486
- error(formatApiError(err));
487
- process.exit(1);
488
- }
489
- });
490
- keys.command("create").description("Create API key").requiredOption("--data <json>", "JSON key configuration").action(async (cmdOpts) => {
479
+ users.command("set-current <userId>").description("Set this organization as the current (active) organization for a user.").action(async (userId) => {
491
480
  const opts = program2.opts();
492
481
  try {
493
- const body = JSON.parse(cmdOpts.data);
494
- const data = await apiRequest({
495
- method: "POST",
496
- path: "/org/api-keys",
497
- body,
498
- apiKey: opts.apiKey,
499
- baseUrl: opts.baseUrl
500
- });
501
- success("API key created.");
502
- console.log(JSON.stringify(data, null, 2));
503
- } catch (err) {
504
- error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
505
- process.exit(1);
506
- }
507
- });
508
- keys.command("get <keyId>").description("Get API key details").action(async (keyId) => {
509
- const opts = program2.opts();
510
- try {
511
- const data = await apiRequest({ path: `/org/api-keys/${keyId}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
512
- console.log(JSON.stringify(data, null, 2));
513
- } catch (err) {
514
- error(formatApiError(err));
515
- process.exit(1);
516
- }
517
- });
518
- keys.command("update <keyId>").description("Update API key").requiredOption("--data <json>", "JSON key updates").action(async (keyId, cmdOpts) => {
519
- const opts = program2.opts();
520
- try {
521
- const body = JSON.parse(cmdOpts.data);
522
- const data = await apiRequest({
482
+ await apiRequest({
523
483
  method: "PATCH",
524
- path: `/org/api-keys/${keyId}`,
525
- body,
484
+ path: `/org/users/${userId}/current`,
485
+ body: { is_current: true },
526
486
  apiKey: opts.apiKey,
527
487
  baseUrl: opts.baseUrl
528
488
  });
529
- success(`API key ${keyId} updated.`);
530
- console.log(JSON.stringify(data, null, 2));
531
- } catch (err) {
532
- error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
533
- process.exit(1);
534
- }
535
- });
536
- keys.command("delete <keyId>").description("Delete API key").action(async (keyId) => {
537
- const opts = program2.opts();
538
- try {
539
- await apiRequest({ method: "DELETE", path: `/org/api-keys/${keyId}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
540
- success(`API key ${keyId} deleted.`);
541
- } catch (err) {
542
- error(formatApiError(err));
543
- process.exit(1);
544
- }
545
- });
546
- keys.command("revoke <keyId>").description("Revoke API key").action(async (keyId) => {
547
- const opts = program2.opts();
548
- try {
549
- await apiRequest({ method: "POST", path: `/org/api-keys/${keyId}/revoke`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
550
- success(`API key ${keyId} revoked.`);
489
+ success(`Organization set as current for user ${userId}.`);
551
490
  } catch (err) {
552
491
  error(formatApiError(err));
553
492
  process.exit(1);
@@ -555,201 +494,90 @@ function registerApiKeyCommands(program2) {
555
494
  });
556
495
  }
557
496
 
558
- // src/commands/categories.ts
559
- function registerCategoryCommands(program2) {
560
- const cat = program2.command("categories").description("Manage categories");
561
- cat.command("list").description("List categories").action(async () => {
562
- const opts = program2.opts();
563
- try {
564
- const data = await apiRequest({ path: "/org/categories", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
565
- console.log(JSON.stringify(data, null, 2));
566
- } catch (err) {
567
- error(formatApiError(err));
568
- process.exit(1);
569
- }
570
- });
571
- cat.command("list-all").description("List all categories with their topics").action(async () => {
572
- const opts = program2.opts();
573
- try {
574
- const data = await apiRequest({ path: "/org/categories/all", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
575
- console.log(JSON.stringify(data, null, 2));
576
- } catch (err) {
577
- error(formatApiError(err));
578
- process.exit(1);
579
- }
580
- });
581
- cat.command("create <name>").description("Create a category").action(async (name) => {
582
- const opts = program2.opts();
583
- try {
584
- const data = await apiRequest({
585
- method: "POST",
586
- path: "/org/categories",
587
- body: { name },
588
- apiKey: opts.apiKey,
589
- baseUrl: opts.baseUrl
590
- });
591
- success(`Category "${name}" created.`);
592
- console.log(JSON.stringify(data, null, 2));
593
- } catch (err) {
594
- error(formatApiError(err));
595
- process.exit(1);
596
- }
597
- });
598
- cat.command("get <id>").description("Get category by ID").action(async (id) => {
599
- const opts = program2.opts();
600
- try {
601
- const data = await apiRequest({ path: `/org/categories/${id}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
602
- console.log(JSON.stringify(data, null, 2));
603
- } catch (err) {
604
- error(formatApiError(err));
605
- process.exit(1);
606
- }
607
- });
608
- cat.command("update <id>").description("Update a category").requiredOption("--name <name>", "New category name").action(async (id, cmdOpts) => {
497
+ // src/commands/api-keys.ts
498
+ function registerApiKeyCommands(program2) {
499
+ const keys = program2.command("api-keys").description("Manage org-scoped API keys. Create, rotate, revoke, or list API keys used to authenticate with the Senso API.");
500
+ keys.command("list").description("List all API keys for the organization. Shows name, expiry, revocation status, and last usage.").option("--limit <n>", "Maximum number of keys to return").option("--offset <n>", "Number of keys to skip (for pagination)").action(async (cmdOpts) => {
609
501
  const opts = program2.opts();
610
502
  try {
611
503
  const data = await apiRequest({
612
- method: "PATCH",
613
- path: `/org/categories/${id}`,
614
- body: { name: cmdOpts.name },
504
+ path: "/org/api-keys",
505
+ params: { limit: cmdOpts.limit, offset: cmdOpts.offset },
615
506
  apiKey: opts.apiKey,
616
507
  baseUrl: opts.baseUrl
617
508
  });
618
- success(`Category ${id} updated.`);
619
509
  console.log(JSON.stringify(data, null, 2));
620
510
  } catch (err) {
621
511
  error(formatApiError(err));
622
512
  process.exit(1);
623
513
  }
624
514
  });
625
- cat.command("delete <id>").description("Delete a category").action(async (id) => {
626
- const opts = program2.opts();
627
- try {
628
- await apiRequest({ method: "DELETE", path: `/org/categories/${id}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
629
- success(`Category ${id} deleted.`);
630
- } catch (err) {
631
- error(formatApiError(err));
632
- process.exit(1);
633
- }
634
- });
635
- cat.command("batch-create").description("Batch create categories with topics").requiredOption("--data <json>", "JSON array of categories with topics").action(async (cmdOpts) => {
515
+ keys.command("create").description("Create a new API key. The key value is returned only once \u2014 store it securely.").requiredOption("--data <json>", 'JSON: { "name": "my-key", "expires_at": "2025-12-31T00:00:00Z" }').action(async (cmdOpts) => {
636
516
  const opts = program2.opts();
637
517
  try {
638
518
  const body = JSON.parse(cmdOpts.data);
639
519
  const data = await apiRequest({
640
520
  method: "POST",
641
- path: "/org/categories/batch",
521
+ path: "/org/api-keys",
642
522
  body,
643
523
  apiKey: opts.apiKey,
644
524
  baseUrl: opts.baseUrl
645
525
  });
646
- success("Batch create completed.");
526
+ success("API key created.");
647
527
  console.log(JSON.stringify(data, null, 2));
648
528
  } catch (err) {
649
529
  error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
650
530
  process.exit(1);
651
531
  }
652
532
  });
653
- }
654
-
655
- // src/commands/topics.ts
656
- function registerTopicCommands(program2) {
657
- const topics = program2.command("topics").description("Manage topics within categories");
658
- topics.command("list <categoryId>").description("List topics for a category").action(async (categoryId) => {
533
+ keys.command("get <keyId>").description("Get details for a specific API key including name, expiry, and last used timestamp.").action(async (keyId) => {
659
534
  const opts = program2.opts();
660
535
  try {
661
- const data = await apiRequest({
662
- path: `/org/categories/${categoryId}/topics`,
663
- apiKey: opts.apiKey,
664
- baseUrl: opts.baseUrl
665
- });
666
- console.log(JSON.stringify(data, null, 2));
667
- } catch (err) {
668
- error(formatApiError(err));
669
- process.exit(1);
670
- }
671
- });
672
- topics.command("create <categoryId>").description("Create topic in category").requiredOption("--name <name>", "Topic name").action(async (categoryId, cmdOpts) => {
673
- const opts = program2.opts();
674
- try {
675
- const data = await apiRequest({
676
- method: "POST",
677
- path: `/org/categories/${categoryId}/topics`,
678
- body: { name: cmdOpts.name },
679
- apiKey: opts.apiKey,
680
- baseUrl: opts.baseUrl
681
- });
682
- success(`Topic "${cmdOpts.name}" created.`);
536
+ const data = await apiRequest({ path: `/org/api-keys/${keyId}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
683
537
  console.log(JSON.stringify(data, null, 2));
684
538
  } catch (err) {
685
539
  error(formatApiError(err));
686
540
  process.exit(1);
687
541
  }
688
542
  });
689
- topics.command("get <categoryId> <topicId>").description("Get topic by ID").action(async (categoryId, topicId) => {
543
+ keys.command("update <keyId>").description("Update an API key's name or expiry date.").requiredOption("--data <json>", 'JSON: { "name": "new-name", "expires_at": "2026-06-01T00:00:00Z" }').action(async (keyId, cmdOpts) => {
690
544
  const opts = program2.opts();
691
545
  try {
546
+ const body = JSON.parse(cmdOpts.data);
692
547
  const data = await apiRequest({
693
- path: `/org/categories/${categoryId}/topics/${topicId}`,
548
+ method: "PUT",
549
+ path: `/org/api-keys/${keyId}`,
550
+ body,
694
551
  apiKey: opts.apiKey,
695
552
  baseUrl: opts.baseUrl
696
553
  });
554
+ success(`API key ${keyId} updated.`);
697
555
  console.log(JSON.stringify(data, null, 2));
698
556
  } catch (err) {
699
- error(formatApiError(err));
557
+ error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
700
558
  process.exit(1);
701
559
  }
702
560
  });
703
- topics.command("update <categoryId> <topicId>").description("Update a topic").requiredOption("--name <name>", "New topic name").action(async (categoryId, topicId, cmdOpts) => {
561
+ keys.command("delete <keyId>").description("Permanently delete an API key. This cannot be undone.").action(async (keyId) => {
704
562
  const opts = program2.opts();
705
563
  try {
706
- const data = await apiRequest({
707
- method: "PATCH",
708
- path: `/org/categories/${categoryId}/topics/${topicId}`,
709
- body: { name: cmdOpts.name },
710
- apiKey: opts.apiKey,
711
- baseUrl: opts.baseUrl
712
- });
713
- success(`Topic ${topicId} updated.`);
714
- console.log(JSON.stringify(data, null, 2));
564
+ await apiRequest({ method: "DELETE", path: `/org/api-keys/${keyId}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
565
+ success(`API key ${keyId} deleted.`);
715
566
  } catch (err) {
716
567
  error(formatApiError(err));
717
568
  process.exit(1);
718
569
  }
719
570
  });
720
- topics.command("delete <categoryId> <topicId>").description("Delete a topic").action(async (categoryId, topicId) => {
571
+ keys.command("revoke <keyId>").description("Revoke an API key. The key remains visible but can no longer be used for authentication.").action(async (keyId) => {
721
572
  const opts = program2.opts();
722
573
  try {
723
- await apiRequest({
724
- method: "DELETE",
725
- path: `/org/categories/${categoryId}/topics/${topicId}`,
726
- apiKey: opts.apiKey,
727
- baseUrl: opts.baseUrl
728
- });
729
- success(`Topic ${topicId} deleted.`);
574
+ await apiRequest({ method: "POST", path: `/org/api-keys/${keyId}/revoke`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
575
+ success(`API key ${keyId} revoked.`);
730
576
  } catch (err) {
731
577
  error(formatApiError(err));
732
578
  process.exit(1);
733
579
  }
734
580
  });
735
- topics.command("batch-create <categoryId>").description("Batch create topics in a category").requiredOption("--data <json>", "JSON array of topics").action(async (categoryId, cmdOpts) => {
736
- const opts = program2.opts();
737
- try {
738
- const body = JSON.parse(cmdOpts.data);
739
- const data = await apiRequest({
740
- method: "POST",
741
- path: `/org/categories/${categoryId}/topics/batch`,
742
- body,
743
- apiKey: opts.apiKey,
744
- baseUrl: opts.baseUrl
745
- });
746
- success("Batch create completed.");
747
- console.log(JSON.stringify(data, null, 2));
748
- } catch (err) {
749
- error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
750
- process.exit(1);
751
- }
752
- });
753
581
  }
754
582
 
755
583
  // src/commands/search.ts
@@ -807,7 +635,7 @@ function output(format, data) {
807
635
 
808
636
  // src/commands/search.ts
809
637
  function registerSearchCommands(program2) {
810
- const search = program2.command("search").description("Semantic search over your knowledge base");
638
+ const search = program2.command("search").description("Search the knowledge base with natural language queries. Returns AI-generated answers synthesised from matching content chunks, or raw chunks/content IDs.");
811
639
  search.argument("<query>", "Search query").option("--max-results <n>", "Maximum number of results", "5").action(async (query, cmdOpts) => {
812
640
  const opts = program2.opts();
813
641
  try {
@@ -847,7 +675,7 @@ function registerSearchCommands(program2) {
847
675
  process.exit(1);
848
676
  }
849
677
  });
850
- search.command("context <query>").description("Semantic search \u2014 chunks only").option("--max-results <n>", "Maximum results", "5").action(async (query, cmdOpts) => {
678
+ search.command("context <query>").description("Search the knowledge base \u2014 returns matching content chunks only, without AI answer generation. Faster than full search.").option("--max-results <n>", "Maximum results", "5").action(async (query, cmdOpts) => {
851
679
  const opts = program2.opts();
852
680
  try {
853
681
  const data = await apiRequest({
@@ -863,7 +691,7 @@ function registerSearchCommands(program2) {
863
691
  process.exit(1);
864
692
  }
865
693
  });
866
- search.command("content <query>").description("Semantic search \u2014 content IDs only").option("--max-results <n>", "Maximum results", "5").action(async (query, cmdOpts) => {
694
+ search.command("content <query>").description("Search the knowledge base \u2014 returns deduplicated content IDs and titles only. No chunks or AI answer.").option("--max-results <n>", "Maximum results", "5").action(async (query, cmdOpts) => {
867
695
  const opts = program2.opts();
868
696
  try {
869
697
  const data = await apiRequest({
@@ -889,34 +717,119 @@ function outputByFormat(format, data) {
889
717
  }
890
718
 
891
719
  // src/commands/ingest.ts
720
+ import { createHash } from "crypto";
721
+ import { readFile, stat } from "fs/promises";
722
+ import { basename, resolve } from "path";
723
+ var MIME_TYPES = {
724
+ ".pdf": "application/pdf",
725
+ ".txt": "text/plain",
726
+ ".csv": "text/csv",
727
+ ".doc": "application/msword",
728
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
729
+ ".xls": "application/vnd.ms-excel",
730
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
731
+ ".ppt": "application/vnd.ms-powerpoint",
732
+ ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
733
+ ".html": "text/html",
734
+ ".htm": "text/html",
735
+ ".md": "text/markdown",
736
+ ".json": "application/json",
737
+ ".xml": "application/xml"
738
+ };
739
+ function getMimeType(filename) {
740
+ const ext = filename.slice(filename.lastIndexOf(".")).toLowerCase();
741
+ return MIME_TYPES[ext] || "application/octet-stream";
742
+ }
743
+ async function getFileMetadata(filePath) {
744
+ const absPath = resolve(filePath);
745
+ const buffer = await readFile(absPath);
746
+ const stats = await stat(absPath);
747
+ const hash = createHash("md5").update(buffer).digest("hex");
748
+ return {
749
+ meta: {
750
+ filename: basename(absPath),
751
+ file_size_bytes: stats.size,
752
+ content_type: getMimeType(basename(absPath)),
753
+ content_hash_md5: hash
754
+ },
755
+ buffer
756
+ };
757
+ }
758
+ async function uploadToS3(url, buffer, contentType) {
759
+ const res = await fetch(url, {
760
+ method: "PUT",
761
+ headers: { "Content-Type": contentType },
762
+ body: buffer
763
+ });
764
+ if (!res.ok) {
765
+ throw new Error(`S3 upload failed: ${res.status} ${res.statusText}`);
766
+ }
767
+ }
892
768
  function registerIngestCommands(program2) {
893
- const ingest = program2.command("ingest").description("Content ingestion (upload, reprocess)");
894
- ingest.command("upload").description("Request presigned S3 upload URLs").requiredOption("--files <filenames...>", "File names to get upload URLs for").action(async (cmdOpts) => {
769
+ const ingest = program2.command("ingest").description("Ingest files into the knowledge base. Upload documents (PDF, TXT, DOCX, etc.) to be parsed, chunked, and embedded for semantic search.");
770
+ ingest.command("upload <files...>").description("Upload files to the knowledge base. Accepts local file paths (up to 10). Files are hashed, uploaded to S3, then parsed and embedded by a background worker.").action(async (files) => {
895
771
  const opts = program2.opts();
772
+ if (files.length > 10) {
773
+ error("Maximum 10 files per upload request.");
774
+ process.exit(1);
775
+ }
896
776
  try {
897
- const data = await apiRequest({
777
+ const fileData = await Promise.all(files.map(getFileMetadata));
778
+ const results = await apiRequest({
898
779
  method: "POST",
899
780
  path: "/org/ingestion/upload",
900
- body: { files: cmdOpts.files },
781
+ body: { files: fileData.map((f) => f.meta) },
901
782
  apiKey: opts.apiKey,
902
783
  baseUrl: opts.baseUrl
903
784
  });
904
- console.log(JSON.stringify(data, null, 2));
785
+ const items = Array.isArray(results) ? results : [];
786
+ let uploaded = 0;
787
+ for (const item of items) {
788
+ if (item.status === "upload_pending" && item.upload_url) {
789
+ const match = fileData.find((f) => f.meta.filename === item.filename);
790
+ if (match) {
791
+ await uploadToS3(item.upload_url, match.buffer, match.meta.content_type);
792
+ uploaded++;
793
+ success(`Uploaded ${item.filename} (content_id: ${item.content_id})`);
794
+ }
795
+ } else {
796
+ warn(`Skipped ${item.filename}: ${item.status}${item.message ? ` \u2014 ${item.message}` : ""}`);
797
+ }
798
+ }
799
+ if (uploaded > 0) {
800
+ success(`${uploaded} file(s) uploaded. Background processing will parse, chunk, and embed them.`);
801
+ }
802
+ if (opts.output === "json") {
803
+ console.log(JSON.stringify(results, null, 2));
804
+ }
905
805
  } catch (err) {
906
806
  error(formatApiError(err));
907
807
  process.exit(1);
908
808
  }
909
809
  });
910
- ingest.command("reprocess <contentId>").description("Request re-ingestion of existing content").action(async (contentId) => {
810
+ ingest.command("reprocess <contentId> <file>").description("Re-ingest an existing content item with a new file version. Provide the content ID and the path to the replacement file.").action(async (contentId, file) => {
911
811
  const opts = program2.opts();
912
812
  try {
913
- await apiRequest({
914
- method: "POST",
915
- path: `/org/ingestion/${contentId}/reprocess`,
813
+ const { meta, buffer } = await getFileMetadata(file);
814
+ const results = await apiRequest({
815
+ method: "PUT",
816
+ path: `/org/ingestion/content/${contentId}`,
817
+ body: { file: meta },
916
818
  apiKey: opts.apiKey,
917
819
  baseUrl: opts.baseUrl
918
820
  });
919
- success(`Reprocess triggered for content ${contentId}.`);
821
+ const items = Array.isArray(results) ? results : [];
822
+ for (const item of items) {
823
+ if (item.status === "upload_pending" && item.upload_url) {
824
+ await uploadToS3(item.upload_url, buffer, meta.content_type);
825
+ success(`Uploaded ${meta.filename} for content ${contentId}. Background re-processing started.`);
826
+ } else {
827
+ warn(`Skipped: ${item.status}${item.message ? ` \u2014 ${item.message}` : ""}`);
828
+ }
829
+ }
830
+ if (opts.output === "json") {
831
+ console.log(JSON.stringify(results, null, 2));
832
+ }
920
833
  } catch (err) {
921
834
  error(formatApiError(err));
922
835
  process.exit(1);
@@ -927,13 +840,13 @@ function registerIngestCommands(program2) {
927
840
  // src/commands/content.ts
928
841
  import pc6 from "picocolors";
929
842
  function registerContentCommands(program2) {
930
- const content = program2.command("content").description("Manage content items");
931
- content.command("list").description("List content items").option("--limit <n>", "Items per page", "10").option("--offset <n>", "Pagination offset", "0").action(async (cmdOpts) => {
843
+ const content = program2.command("content").description("Manage content items in the knowledge base. List, inspect, delete, unpublish, and manage the verification workflow and ownership of content.");
844
+ content.command("list").description("List all content items in the knowledge base. Returns title, status, and ID for each item. Use --search to filter by title, --sort to order results.").option("--limit <n>", "Items per page", "10").option("--offset <n>", "Pagination offset", "0").option("--search <query>", "Filter content by title").option("--sort <order>", "Sort order: title_asc, title_desc, created_asc, created_desc").action(async (cmdOpts) => {
932
845
  const opts = program2.opts();
933
846
  try {
934
847
  const data = await apiRequest({
935
848
  path: "/org/content",
936
- params: { limit: cmdOpts.limit, offset: cmdOpts.offset },
849
+ params: { limit: cmdOpts.limit, offset: cmdOpts.offset, search: cmdOpts.search, sort: cmdOpts.sort },
937
850
  apiKey: opts.apiKey,
938
851
  baseUrl: opts.baseUrl
939
852
  });
@@ -958,7 +871,7 @@ function registerContentCommands(program2) {
958
871
  process.exit(1);
959
872
  }
960
873
  });
961
- content.command("get <id>").description("Get content item by ID").action(async (id) => {
874
+ content.command("get <id>").description("Get a content item by ID. Returns the full content detail including versions, metadata, and publish status.").action(async (id) => {
962
875
  const opts = program2.opts();
963
876
  try {
964
877
  const data = await apiRequest({ path: `/org/content/${id}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
@@ -968,7 +881,7 @@ function registerContentCommands(program2) {
968
881
  process.exit(1);
969
882
  }
970
883
  });
971
- content.command("delete <id>").description("Delete content (local + external)").action(async (id) => {
884
+ content.command("delete <id>").description("Delete a content item from the knowledge base and any external publish destinations. This cannot be undone.").action(async (id) => {
972
885
  const opts = program2.opts();
973
886
  try {
974
887
  await apiRequest({ method: "DELETE", path: `/org/content/${id}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
@@ -978,7 +891,7 @@ function registerContentCommands(program2) {
978
891
  process.exit(1);
979
892
  }
980
893
  });
981
- content.command("unpublish <id>").description("Unpublish content (external delete + set draft)").action(async (id) => {
894
+ content.command("unpublish <id>").description("Unpublish a content item. Removes it from external destinations and sets its status back to draft.").action(async (id) => {
982
895
  const opts = program2.opts();
983
896
  try {
984
897
  await apiRequest({ method: "POST", path: `/org/content/${id}/unpublish`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
@@ -988,27 +901,33 @@ function registerContentCommands(program2) {
988
901
  process.exit(1);
989
902
  }
990
903
  });
991
- content.command("verification").description("List content awaiting verification").action(async () => {
904
+ content.command("verification").description("List content items in the verification workflow. Filter by editorial status (draft, review, rejected, published) to manage the review pipeline.").option("--limit <n>", "Maximum items to return").option("--offset <n>", "Number of items to skip (for pagination)").option("--search <query>", "Filter by title").option("--status <status>", "Filter by status: all, draft, review, rejected, published").action(async (cmdOpts) => {
992
905
  const opts = program2.opts();
993
906
  try {
994
- const data = await apiRequest({ path: "/org/content/verification", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
907
+ const data = await apiRequest({
908
+ path: "/org/content/verification",
909
+ params: { limit: cmdOpts.limit, offset: cmdOpts.offset, search: cmdOpts.search, status: cmdOpts.status },
910
+ apiKey: opts.apiKey,
911
+ baseUrl: opts.baseUrl
912
+ });
995
913
  console.log(JSON.stringify(data, null, 2));
996
914
  } catch (err) {
997
915
  error(formatApiError(err));
998
916
  process.exit(1);
999
917
  }
1000
918
  });
1001
- content.command("reject <versionId>").description("Reject a content version").action(async (versionId) => {
919
+ content.command("reject <versionId>").description("Reject a content version in the verification workflow. Optionally provide a reason for the rejection.").option("--reason <text>", "Reason for rejection").action(async (versionId, cmdOpts) => {
1002
920
  const opts = program2.opts();
1003
921
  try {
1004
- await apiRequest({ method: "POST", path: `/org/content/versions/${versionId}/reject`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
922
+ const body = cmdOpts.reason ? { reason: cmdOpts.reason } : void 0;
923
+ await apiRequest({ method: "POST", path: `/org/content/versions/${versionId}/reject`, body, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
1005
924
  success(`Version ${versionId} rejected.`);
1006
925
  } catch (err) {
1007
926
  error(formatApiError(err));
1008
927
  process.exit(1);
1009
928
  }
1010
929
  });
1011
- content.command("restore <versionId>").description("Restore a content version to draft").action(async (versionId) => {
930
+ content.command("restore <versionId>").description("Restore a rejected content version back to draft status for further editing.").action(async (versionId) => {
1012
931
  const opts = program2.opts();
1013
932
  try {
1014
933
  await apiRequest({ method: "POST", path: `/org/content/versions/${versionId}/restore`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
@@ -1018,7 +937,7 @@ function registerContentCommands(program2) {
1018
937
  process.exit(1);
1019
938
  }
1020
939
  });
1021
- content.command("owners <id>").description("List content owners").action(async (id) => {
940
+ content.command("owners <id>").description("List the owners assigned to a content item. Owners are responsible for reviewing and approving content.").action(async (id) => {
1022
941
  const opts = program2.opts();
1023
942
  try {
1024
943
  const data = await apiRequest({ path: `/org/content/${id}/owners`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
@@ -1028,7 +947,7 @@ function registerContentCommands(program2) {
1028
947
  process.exit(1);
1029
948
  }
1030
949
  });
1031
- content.command("set-owners <id>").description("Replace content owners").requiredOption("--user-ids <ids...>", "User IDs to set as owners").action(async (id, cmdOpts) => {
950
+ content.command("set-owners <id>").description("Replace all owners of a content item with a new set of user IDs.").requiredOption("--user-ids <ids...>", "User IDs to set as owners").action(async (id, cmdOpts) => {
1032
951
  const opts = program2.opts();
1033
952
  try {
1034
953
  await apiRequest({
@@ -1044,7 +963,7 @@ function registerContentCommands(program2) {
1044
963
  process.exit(1);
1045
964
  }
1046
965
  });
1047
- content.command("remove-owner <id> <userId>").description("Remove content owner").action(async (id, userId) => {
966
+ content.command("remove-owner <id> <userId>").description("Remove a single owner from a content item.").action(async (id, userId) => {
1048
967
  const opts = program2.opts();
1049
968
  try {
1050
969
  await apiRequest({ method: "DELETE", path: `/org/content/${id}/owners/${userId}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
@@ -1058,8 +977,8 @@ function registerContentCommands(program2) {
1058
977
 
1059
978
  // src/commands/generate.ts
1060
979
  function registerGenerateCommands(program2) {
1061
- const gen = program2.command("generate").description("Content generation settings and triggers");
1062
- gen.command("settings").description("Get content generation settings").action(async () => {
980
+ const gen = program2.command("generate").description("AI content generation. Configure settings, generate content samples from prompts, or trigger full content engine runs.");
981
+ gen.command("settings").description("Get content generation settings. Shows whether generation and auto-publish are enabled, the content schedule, and configured publishers.").action(async () => {
1063
982
  const opts = program2.opts();
1064
983
  try {
1065
984
  const data = await apiRequest({ path: "/org/content-generation", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
@@ -1069,24 +988,32 @@ function registerGenerateCommands(program2) {
1069
988
  process.exit(1);
1070
989
  }
1071
990
  });
1072
- gen.command("update-settings").description("Update content generation settings").requiredOption("--data <json>", "JSON settings to update").action(async (cmdOpts) => {
991
+ gen.command("update-settings").description("Update content generation settings. Control auto-publish, generation toggle, and schedule (days of week 0-6).").requiredOption("--data <json>", 'JSON settings: { "enable_content_generation": bool, "content_auto_publish": bool, "content_schedule": [0-6] }').action(async (cmdOpts) => {
1073
992
  const opts = program2.opts();
1074
993
  try {
1075
994
  const body = JSON.parse(cmdOpts.data);
1076
995
  const data = await apiRequest({ method: "PATCH", path: "/org/content-generation", body, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
996
+ success("Content generation settings updated.");
1077
997
  console.log(JSON.stringify(data, null, 2));
1078
998
  } catch (err) {
1079
999
  error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
1080
1000
  process.exit(1);
1081
1001
  }
1082
1002
  });
1083
- gen.command("sample").description("Generate ad hoc content sample").requiredOption("--instructions <text>", "Instructions for generation").action(async (cmdOpts) => {
1003
+ gen.command("sample").description("Generate an ad hoc content sample for a specific prompt and content type. Returns the generated markdown, SEO title, and publish results.").requiredOption("--prompt-id <id>", "Prompt (geo question) ID to generate content for").requiredOption("--content-type-id <id>", "Content type ID that defines the output format").option("--destination <dest>", "Publish destination (e.g. citeables)").action(async (cmdOpts) => {
1084
1004
  const opts = program2.opts();
1085
1005
  try {
1006
+ const body = {
1007
+ geo_question_id: cmdOpts.promptId,
1008
+ content_type_id: cmdOpts.contentTypeId
1009
+ };
1010
+ if (cmdOpts.destination) {
1011
+ body.publish_destination = cmdOpts.destination;
1012
+ }
1086
1013
  const data = await apiRequest({
1087
1014
  method: "POST",
1088
1015
  path: "/org/content-generation/sample",
1089
- body: { instructions: cmdOpts.instructions },
1016
+ body,
1090
1017
  apiKey: opts.apiKey,
1091
1018
  baseUrl: opts.baseUrl
1092
1019
  });
@@ -1096,10 +1023,11 @@ function registerGenerateCommands(program2) {
1096
1023
  process.exit(1);
1097
1024
  }
1098
1025
  });
1099
- gen.command("run").description("Trigger content engine run").action(async () => {
1026
+ gen.command("run").description("Trigger a content generation run. Processes all prompts (or a specific subset) through the content engine. Runs asynchronously.").option("--prompt-ids <ids...>", "Optional list of prompt IDs to process (omit to run all)").action(async (cmdOpts) => {
1100
1027
  const opts = program2.opts();
1101
1028
  try {
1102
- const data = await apiRequest({ method: "POST", path: "/org/content-generation/run", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
1029
+ const body = cmdOpts.promptIds ? { prompt_ids: cmdOpts.promptIds } : void 0;
1030
+ const data = await apiRequest({ method: "POST", path: "/org/content-generation/run", body, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
1103
1031
  success("Content generation run triggered.");
1104
1032
  console.log(JSON.stringify(data, null, 2));
1105
1033
  } catch (err) {
@@ -1111,8 +1039,8 @@ function registerGenerateCommands(program2) {
1111
1039
 
1112
1040
  // src/commands/engine.ts
1113
1041
  function registerEngineCommands(program2) {
1114
- const engine = program2.command("engine").description("Content engine operations (publish/draft)");
1115
- engine.command("publish").description("Publish content via content engine").requiredOption("--data <json>", "JSON payload for publish").action(async (cmdOpts) => {
1042
+ const engine = program2.command("engine").description("Publish or draft content through the content engine. Used to push AI-generated content to external destinations or save it as a draft for review.");
1043
+ engine.command("publish").description("Publish content to external destinations via the content engine. Requires geo_question_id, raw_markdown, and seo_title.").requiredOption("--data <json>", 'JSON: { "geo_question_id": "uuid", "raw_markdown": "...", "seo_title": "...", "summary": "..." }').action(async (cmdOpts) => {
1116
1044
  const opts = program2.opts();
1117
1045
  try {
1118
1046
  const body = JSON.parse(cmdOpts.data);
@@ -1130,7 +1058,7 @@ function registerEngineCommands(program2) {
1130
1058
  process.exit(1);
1131
1059
  }
1132
1060
  });
1133
- engine.command("draft").description("Save content as draft via content engine").requiredOption("--data <json>", "JSON payload for draft").action(async (cmdOpts) => {
1061
+ engine.command("draft").description("Save content as a draft for review before publishing. Requires geo_question_id, raw_markdown, and seo_title.").requiredOption("--data <json>", 'JSON: { "geo_question_id": "uuid", "raw_markdown": "...", "seo_title": "...", "summary": "..." }').action(async (cmdOpts) => {
1134
1062
  const opts = program2.opts();
1135
1063
  try {
1136
1064
  const body = JSON.parse(cmdOpts.data);
@@ -1152,8 +1080,8 @@ function registerEngineCommands(program2) {
1152
1080
 
1153
1081
  // src/commands/brand-kit.ts
1154
1082
  function registerBrandKitCommands(program2) {
1155
- const bk = program2.command("brand-kit").description("Manage brand kit");
1156
- bk.command("get").description("Get brand kit").action(async () => {
1083
+ const bk = program2.command("brand-kit").description("Manage the organization's brand kit guidelines. The brand kit is a free-form JSON object that informs AI content generation about your brand voice, tone, and style.");
1084
+ bk.command("get").description("Get the current brand kit guidelines.").action(async () => {
1157
1085
  const opts = program2.opts();
1158
1086
  try {
1159
1087
  const data = await apiRequest({ path: "/org/brand-kit", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
@@ -1163,7 +1091,7 @@ function registerBrandKitCommands(program2) {
1163
1091
  process.exit(1);
1164
1092
  }
1165
1093
  });
1166
- bk.command("set").description("Upsert brand kit").requiredOption("--data <json>", "JSON brand kit data").action(async (cmdOpts) => {
1094
+ bk.command("set").description("Create or replace the brand kit. The guidelines field is a free-form JSON object defining your brand voice.").requiredOption("--data <json>", 'JSON: { "guidelines": { "tone": "professional", "voice": "..." } }').action(async (cmdOpts) => {
1167
1095
  const opts = program2.opts();
1168
1096
  try {
1169
1097
  const body = JSON.parse(cmdOpts.data);
@@ -1185,18 +1113,23 @@ function registerBrandKitCommands(program2) {
1185
1113
 
1186
1114
  // src/commands/content-types.ts
1187
1115
  function registerContentTypeCommands(program2) {
1188
- const ct = program2.command("content-types").description("Manage content types");
1189
- ct.command("list").description("List content types").action(async () => {
1116
+ const ct = program2.command("content-types").description("Manage content type configurations. Content types define the output format and structure for AI-generated content (e.g. blog post, FAQ, landing page).");
1117
+ ct.command("list").description("List all content types configured for the organization.").option("--limit <n>", "Maximum number of content types to return (default: 50)").option("--offset <n>", "Number of items to skip (for pagination)").action(async (cmdOpts) => {
1190
1118
  const opts = program2.opts();
1191
1119
  try {
1192
- const data = await apiRequest({ path: "/org/content-types", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
1120
+ const data = await apiRequest({
1121
+ path: "/org/content-types",
1122
+ params: { limit: cmdOpts.limit, offset: cmdOpts.offset },
1123
+ apiKey: opts.apiKey,
1124
+ baseUrl: opts.baseUrl
1125
+ });
1193
1126
  console.log(JSON.stringify(data, null, 2));
1194
1127
  } catch (err) {
1195
1128
  error(formatApiError(err));
1196
1129
  process.exit(1);
1197
1130
  }
1198
1131
  });
1199
- ct.command("create").description("Create a content type").requiredOption("--data <json>", "JSON content type definition").action(async (cmdOpts) => {
1132
+ ct.command("create").description("Create a new content type. Requires a name and configuration defining the output structure.").requiredOption("--data <json>", 'JSON: { "name": "Blog Post", "config": { ... } }').action(async (cmdOpts) => {
1200
1133
  const opts = program2.opts();
1201
1134
  try {
1202
1135
  const body = JSON.parse(cmdOpts.data);
@@ -1214,7 +1147,7 @@ function registerContentTypeCommands(program2) {
1214
1147
  process.exit(1);
1215
1148
  }
1216
1149
  });
1217
- ct.command("get <id>").description("Get content type by ID").action(async (id) => {
1150
+ ct.command("get <id>").description("Get a content type by ID, including its full configuration.").action(async (id) => {
1218
1151
  const opts = program2.opts();
1219
1152
  try {
1220
1153
  const data = await apiRequest({ path: `/org/content-types/${id}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
@@ -1224,12 +1157,12 @@ function registerContentTypeCommands(program2) {
1224
1157
  process.exit(1);
1225
1158
  }
1226
1159
  });
1227
- ct.command("update <id>").description("Update a content type").requiredOption("--data <json>", "JSON content type updates").action(async (id, cmdOpts) => {
1160
+ ct.command("update <id>").description("Update a content type's name or configuration.").requiredOption("--data <json>", 'JSON: { "name": "Updated Name", "config": { ... } }').action(async (id, cmdOpts) => {
1228
1161
  const opts = program2.opts();
1229
1162
  try {
1230
1163
  const body = JSON.parse(cmdOpts.data);
1231
1164
  const data = await apiRequest({
1232
- method: "PATCH",
1165
+ method: "PUT",
1233
1166
  path: `/org/content-types/${id}`,
1234
1167
  body,
1235
1168
  apiKey: opts.apiKey,
@@ -1242,7 +1175,7 @@ function registerContentTypeCommands(program2) {
1242
1175
  process.exit(1);
1243
1176
  }
1244
1177
  });
1245
- ct.command("delete <id>").description("Delete a content type").action(async (id) => {
1178
+ ct.command("delete <id>").description("Delete a content type. This cannot be undone.").action(async (id) => {
1246
1179
  const opts = program2.opts();
1247
1180
  try {
1248
1181
  await apiRequest({ method: "DELETE", path: `/org/content-types/${id}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
@@ -1256,18 +1189,23 @@ function registerContentTypeCommands(program2) {
1256
1189
 
1257
1190
  // src/commands/prompts.ts
1258
1191
  function registerPromptCommands(program2) {
1259
- const prompts = program2.command("prompts").description("Manage prompts (geo questions)");
1260
- prompts.command("list").description("List prompts").action(async () => {
1192
+ const prompts = program2.command("prompts").description("Manage prompts (geo questions). Prompts are the questions that drive AI content generation \u2014 each prompt is run against configured AI models to track brand mentions, claims, and competitor visibility.");
1193
+ prompts.command("list").description("List all prompts in the organization. Use --search to filter by question text, --sort to order results.").option("--limit <n>", "Maximum prompts to return (max: 100)").option("--offset <n>", "Number of prompts to skip (for pagination)").option("--search <query>", "Filter prompts by question text").option("--sort <order>", "Sort order: created_desc, created_asc, text_asc, text_desc, type_asc, type_desc").action(async (cmdOpts) => {
1261
1194
  const opts = program2.opts();
1262
1195
  try {
1263
- const data = await apiRequest({ path: "/org/prompts", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
1196
+ const data = await apiRequest({
1197
+ path: "/org/prompts",
1198
+ params: { limit: cmdOpts.limit, offset: cmdOpts.offset, search: cmdOpts.search, sort: cmdOpts.sort },
1199
+ apiKey: opts.apiKey,
1200
+ baseUrl: opts.baseUrl
1201
+ });
1264
1202
  console.log(JSON.stringify(data, null, 2));
1265
1203
  } catch (err) {
1266
1204
  error(formatApiError(err));
1267
1205
  process.exit(1);
1268
1206
  }
1269
1207
  });
1270
- prompts.command("create").description("Create a prompt").requiredOption("--data <json>", "JSON prompt definition").action(async (cmdOpts) => {
1208
+ prompts.command("create").description("Create a new prompt. Type must be one of: decision, consideration, awareness, evaluation.").requiredOption("--data <json>", 'JSON: { "question_text": "What are the best...", "type": "decision" }').action(async (cmdOpts) => {
1271
1209
  const opts = program2.opts();
1272
1210
  try {
1273
1211
  const body = JSON.parse(cmdOpts.data);
@@ -1285,7 +1223,7 @@ function registerPromptCommands(program2) {
1285
1223
  process.exit(1);
1286
1224
  }
1287
1225
  });
1288
- prompts.command("get <promptId>").description("Get prompt with full run history").action(async (promptId) => {
1226
+ prompts.command("get <promptId>").description("Get a prompt with its full run history. Includes all question runs with mentions, claims, citations, and competitor data.").action(async (promptId) => {
1289
1227
  const opts = program2.opts();
1290
1228
  try {
1291
1229
  const data = await apiRequest({ path: `/org/prompts/${promptId}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
@@ -1295,7 +1233,7 @@ function registerPromptCommands(program2) {
1295
1233
  process.exit(1);
1296
1234
  }
1297
1235
  });
1298
- prompts.command("delete <promptId>").description("Delete a prompt").action(async (promptId) => {
1236
+ prompts.command("delete <promptId>").description("Delete a prompt and all its associated run history. This cannot be undone.").action(async (promptId) => {
1299
1237
  const opts = program2.opts();
1300
1238
  try {
1301
1239
  await apiRequest({ method: "DELETE", path: `/org/prompts/${promptId}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
@@ -1309,8 +1247,8 @@ function registerPromptCommands(program2) {
1309
1247
 
1310
1248
  // src/commands/run-config.ts
1311
1249
  function registerRunConfigCommands(program2) {
1312
- const rc = program2.command("run-config").description("Run configuration (models, schedule)");
1313
- rc.command("models").description("Get configured AI models").action(async () => {
1250
+ const rc = program2.command("run-config").description("Configure which AI models are used for question runs and on which days they run. Models include chatgpt, gemini, etc.");
1251
+ rc.command("models").description("Get the AI models currently configured for question runs (e.g. chatgpt, gemini).").action(async () => {
1314
1252
  const opts = program2.opts();
1315
1253
  try {
1316
1254
  const data = await apiRequest({ path: "/org/run-models", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
@@ -1320,7 +1258,7 @@ function registerRunConfigCommands(program2) {
1320
1258
  process.exit(1);
1321
1259
  }
1322
1260
  });
1323
- rc.command("set-models").description("Set AI models").requiredOption("--data <json>", "JSON model configuration").action(async (cmdOpts) => {
1261
+ rc.command("set-models").description("Replace the configured AI models for question runs. At least one model name is required.").requiredOption("--data <json>", 'JSON: { "models": ["chatgpt", "gemini"] }').action(async (cmdOpts) => {
1324
1262
  const opts = program2.opts();
1325
1263
  try {
1326
1264
  const body = JSON.parse(cmdOpts.data);
@@ -1338,7 +1276,7 @@ function registerRunConfigCommands(program2) {
1338
1276
  process.exit(1);
1339
1277
  }
1340
1278
  });
1341
- rc.command("schedule").description("Get run schedule").action(async () => {
1279
+ rc.command("schedule").description("Get the days of the week when question runs are triggered (0=Sunday, 1=Monday, ..., 6=Saturday).").action(async () => {
1342
1280
  const opts = program2.opts();
1343
1281
  try {
1344
1282
  const data = await apiRequest({ path: "/org/run-schedule", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
@@ -1348,7 +1286,7 @@ function registerRunConfigCommands(program2) {
1348
1286
  process.exit(1);
1349
1287
  }
1350
1288
  });
1351
- rc.command("set-schedule").description("Set run schedule").requiredOption("--data <json>", "JSON schedule configuration").action(async (cmdOpts) => {
1289
+ rc.command("set-schedule").description("Set which days of the week question runs are triggered. Values must be 0-6 (Sunday-Saturday).").requiredOption("--data <json>", 'JSON: { "schedule": [1, 3, 5] }').action(async (cmdOpts) => {
1352
1290
  const opts = program2.opts();
1353
1291
  try {
1354
1292
  const body = JSON.parse(cmdOpts.data);
@@ -1370,11 +1308,16 @@ function registerRunConfigCommands(program2) {
1370
1308
 
1371
1309
  // src/commands/members.ts
1372
1310
  function registerMemberCommands(program2) {
1373
- const members = program2.command("members").description("Organization members");
1374
- members.command("list").description("List organization members").action(async () => {
1311
+ const members = program2.command("members").description("View the organization member directory. Lists all users who belong to the organization with their names and emails.");
1312
+ members.command("list").description("List all organization members. Use --search to filter by name or email, --sort to order results.").option("--limit <n>", "Maximum members to return (max: 1000)").option("--offset <n>", "Number of members to skip (for pagination)").option("--search <query>", "Filter by name or email").option("--sort <order>", "Sort order: name_asc, name_desc, email_asc, email_desc, created_asc, created_desc").action(async (cmdOpts) => {
1375
1313
  const opts = program2.opts();
1376
1314
  try {
1377
- const data = await apiRequest({ path: "/org/members", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
1315
+ const data = await apiRequest({
1316
+ path: "/org/members",
1317
+ params: { limit: cmdOpts.limit, offset: cmdOpts.offset, search: cmdOpts.search, sort: cmdOpts.sort },
1318
+ apiKey: opts.apiKey,
1319
+ baseUrl: opts.baseUrl
1320
+ });
1378
1321
  console.log(JSON.stringify(data, null, 2));
1379
1322
  } catch (err) {
1380
1323
  error(formatApiError(err));
@@ -1385,22 +1328,31 @@ function registerMemberCommands(program2) {
1385
1328
 
1386
1329
  // src/commands/notifications.ts
1387
1330
  function registerNotificationCommands(program2) {
1388
- const notif = program2.command("notifications").description("Manage notifications");
1389
- notif.command("list").description("List notifications").action(async () => {
1331
+ const notif = program2.command("notifications").description("View and manage user notifications. Notifications are triggered by content verification, generation runs, and other system events.");
1332
+ notif.command("list").description("List notifications for the current user. Use --unread-only to filter to unread notifications.").option("--limit <n>", "Maximum notifications to return (default: 50, max: 200)").option("--offset <n>", "Number of notifications to skip (for pagination)").option("--unread-only", "Only return unread notifications").action(async (cmdOpts) => {
1390
1333
  const opts = program2.opts();
1391
1334
  try {
1392
- const data = await apiRequest({ path: "/app/v1/notifications", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
1335
+ const data = await apiRequest({
1336
+ path: "/app/v1/notifications",
1337
+ params: {
1338
+ limit: cmdOpts.limit,
1339
+ offset: cmdOpts.offset,
1340
+ unread_only: cmdOpts.unreadOnly ? "true" : void 0
1341
+ },
1342
+ apiKey: opts.apiKey,
1343
+ baseUrl: opts.baseUrl
1344
+ });
1393
1345
  console.log(JSON.stringify(data, null, 2));
1394
1346
  } catch (err) {
1395
1347
  error(formatApiError(err));
1396
1348
  process.exit(1);
1397
1349
  }
1398
1350
  });
1399
- notif.command("read <id>").description("Mark notification as read").action(async (id) => {
1351
+ notif.command("read <id>").description("Mark a notification as read.").action(async (id) => {
1400
1352
  const opts = program2.opts();
1401
1353
  try {
1402
1354
  await apiRequest({
1403
- method: "POST",
1355
+ method: "PATCH",
1404
1356
  path: `/app/v1/notifications/${id}/read`,
1405
1357
  apiKey: opts.apiKey,
1406
1358
  baseUrl: opts.baseUrl
@@ -1417,16 +1369,16 @@ function registerNotificationCommands(program2) {
1417
1369
  import semver2 from "semver";
1418
1370
  import pc7 from "picocolors";
1419
1371
  import { execSync } from "child_process";
1372
+ var NPM_PACKAGE2 = "@senso-ai/cli";
1420
1373
  function registerUpdateCommand(program2) {
1421
1374
  program2.command("update").description("Update CLI to the latest version").action(async () => {
1422
1375
  info(`Current version: ${pc7.bold(version)}`);
1423
- info("Checking for updates...");
1424
- const release = await getLatestRelease();
1425
- if (!release) {
1376
+ info("Checking npm for updates...");
1377
+ const latest = await getLatestVersion();
1378
+ if (!latest) {
1426
1379
  error("Could not check for updates. Try again later.");
1427
1380
  process.exit(1);
1428
1381
  }
1429
- const latest = release.tag_name.replace(/^v/, "");
1430
1382
  if (!semver2.gt(latest, version)) {
1431
1383
  success(`Already on the latest version (${version}).`);
1432
1384
  return;
@@ -1434,33 +1386,16 @@ function registerUpdateCommand(program2) {
1434
1386
  info(`New version available: ${pc7.bold(latest)}`);
1435
1387
  info("Updating...");
1436
1388
  try {
1437
- execSync("npm install -g senso-user-cli@latest", {
1389
+ execSync(`npm install -g ${NPM_PACKAGE2}@latest`, {
1438
1390
  stdio: "inherit"
1439
1391
  });
1440
1392
  success(`Updated to v${latest}.`);
1441
- if (release.body) {
1442
- console.log();
1443
- console.log(pc7.dim("Release notes:"));
1444
- console.log(pc7.dim(release.body.slice(0, 500)));
1445
- }
1446
1393
  } catch {
1447
- warn("Global npm install failed. Trying npx reinstall...");
1448
- try {
1449
- execSync(
1450
- "npx --yes github:AI-Template-SDK/senso-user-cli --version",
1451
- { stdio: "inherit" }
1452
- );
1453
- success("Updated via npx cache refresh.");
1454
- } catch {
1455
- error("Update failed. Please reinstall manually:");
1456
- console.log(
1457
- ` ${pc7.cyan("npm install -g senso-user-cli")}`
1458
- );
1459
- console.log(
1460
- ` ${pc7.dim("or")} ${pc7.cyan("npx github:AI-Template-SDK/senso-user-cli")}`
1461
- );
1462
- process.exit(1);
1463
- }
1394
+ error("Update failed. Please reinstall manually:");
1395
+ console.log(
1396
+ ` ${pc7.cyan(`npm install -g ${NPM_PACKAGE2}`)}`
1397
+ );
1398
+ process.exit(1);
1464
1399
  }
1465
1400
  });
1466
1401
  }
@@ -1477,8 +1412,6 @@ registerAuthCommands(program);
1477
1412
  registerOrgCommands(program);
1478
1413
  registerUserCommands(program);
1479
1414
  registerApiKeyCommands(program);
1480
- registerCategoryCommands(program);
1481
- registerTopicCommands(program);
1482
1415
  registerSearchCommands(program);
1483
1416
  registerIngestCommands(program);
1484
1417
  registerContentCommands(program);