@senso-ai/cli 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +73 -12
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -636,13 +636,16 @@ function output(format, data) {
636
636
  // src/commands/search.ts
637
637
  function registerSearchCommands(program2) {
638
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.");
639
- search.argument("<query>", "Search query").option("--max-results <n>", "Maximum number of results", "5").action(async (query, cmdOpts) => {
639
+ search.argument("<query>", "Search query").option("--max-results <n>", "Maximum number of results", "5").option("--content-ids <ids...>", "Restrict search to specific content item IDs (space-separated UUIDs)").option("--require-scoped-ids", "Only return results from the specified --content-ids (omit to allow fallback to all content)").action(async (query, cmdOpts) => {
640
640
  const opts = program2.opts();
641
641
  try {
642
+ const body = { query, max_results: parseInt(cmdOpts.maxResults) };
643
+ if (cmdOpts.contentIds) body.content_ids = cmdOpts.contentIds;
644
+ if (cmdOpts.requireScopedIds) body.require_scoped_ids = true;
642
645
  const data = await apiRequest({
643
646
  method: "POST",
644
647
  path: "/org/search",
645
- body: { query, max_results: parseInt(cmdOpts.maxResults) },
648
+ body,
646
649
  apiKey: opts.apiKey,
647
650
  baseUrl: opts.baseUrl
648
651
  });
@@ -675,13 +678,16 @@ function registerSearchCommands(program2) {
675
678
  process.exit(1);
676
679
  }
677
680
  });
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) => {
681
+ search.command("context <query>").description("Search the knowledge base \u2014 returns matching content chunks only, without AI answer generation. Use this to feed verified chunks into your own LLM pipeline instead of using Senso's generated answer.").option("--max-results <n>", "Maximum results", "5").option("--content-ids <ids...>", "Restrict search to specific content item IDs (space-separated UUIDs)").option("--require-scoped-ids", "Only return results from the specified --content-ids").action(async (query, cmdOpts) => {
679
682
  const opts = program2.opts();
680
683
  try {
684
+ const body = { query, max_results: parseInt(cmdOpts.maxResults) };
685
+ if (cmdOpts.contentIds) body.content_ids = cmdOpts.contentIds;
686
+ if (cmdOpts.requireScopedIds) body.require_scoped_ids = true;
681
687
  const data = await apiRequest({
682
688
  method: "POST",
683
689
  path: "/org/search/context",
684
- body: { query, max_results: parseInt(cmdOpts.maxResults) },
690
+ body,
685
691
  apiKey: opts.apiKey,
686
692
  baseUrl: opts.baseUrl
687
693
  });
@@ -691,13 +697,16 @@ function registerSearchCommands(program2) {
691
697
  process.exit(1);
692
698
  }
693
699
  });
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) => {
700
+ search.command("content <query>").description("Search the knowledge base \u2014 returns deduplicated content IDs and titles only. Use this to discover which documents are relevant before fetching full content with 'content get <id>'.").option("--max-results <n>", "Maximum results", "5").option("--content-ids <ids...>", "Restrict search to specific content item IDs (space-separated UUIDs)").option("--require-scoped-ids", "Only return results from the specified --content-ids").action(async (query, cmdOpts) => {
695
701
  const opts = program2.opts();
696
702
  try {
703
+ const body = { query, max_results: parseInt(cmdOpts.maxResults) };
704
+ if (cmdOpts.contentIds) body.content_ids = cmdOpts.contentIds;
705
+ if (cmdOpts.requireScopedIds) body.require_scoped_ids = true;
697
706
  const data = await apiRequest({
698
707
  method: "POST",
699
708
  path: "/org/search/content",
700
- body: { query, max_results: parseInt(cmdOpts.maxResults) },
709
+ body,
701
710
  apiKey: opts.apiKey,
702
711
  baseUrl: opts.baseUrl
703
712
  });
@@ -767,7 +776,7 @@ async function uploadToS3(url, buffer, contentType) {
767
776
  }
768
777
  function registerIngestCommands(program2) {
769
778
  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) => {
779
+ 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. Poll 'senso content get <content-id>' until processing_status is 'complete' before searching the uploaded content.").action(async (files) => {
771
780
  const opts = program2.opts();
772
781
  if (files.length > 10) {
773
782
  error("Maximum 10 files per upload request.");
@@ -1000,7 +1009,7 @@ function registerGenerateCommands(program2) {
1000
1009
  process.exit(1);
1001
1010
  }
1002
1011
  });
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) => {
1012
+ 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. Use 'prompts list' to find a prompt ID, and 'content-types list' to find a content-type ID.").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 (use 'content-types list' to find)").option("--destination <dest>", "Publisher slug to publish to immediately after generation. Omit to save as draft only.").action(async (cmdOpts) => {
1004
1013
  const opts = program2.opts();
1005
1014
  try {
1006
1015
  const body = {
@@ -1023,7 +1032,7 @@ function registerGenerateCommands(program2) {
1023
1032
  process.exit(1);
1024
1033
  }
1025
1034
  });
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) => {
1035
+ gen.command("run").description("Trigger a content generation run. Processes all prompts (or a specific subset) through the content engine. Runs asynchronously \u2014 use 'content verification' to monitor generated drafts after the run completes.").option("--prompt-ids <ids...>", "Optional list of prompt IDs to process (omit to run all)").action(async (cmdOpts) => {
1027
1036
  const opts = program2.opts();
1028
1037
  try {
1029
1038
  const body = cmdOpts.promptIds ? { prompt_ids: cmdOpts.promptIds } : void 0;
@@ -1091,7 +1100,7 @@ function registerBrandKitCommands(program2) {
1091
1100
  process.exit(1);
1092
1101
  }
1093
1102
  });
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) => {
1103
+ bk.command("set").description("Replace the entire brand kit (PUT). All existing fields are overwritten \u2014 run 'brand-kit get' first to preserve fields you are not changing. For a safe partial update, use 'brand-kit patch'.").requiredOption("--data <json>", 'JSON: { "guidelines": { "brand_name": "Acme", "voice_and_tone": "...", "author_persona": "...", "global_writing_rules": [] } }').action(async (cmdOpts) => {
1095
1104
  const opts = program2.opts();
1096
1105
  try {
1097
1106
  const body = JSON.parse(cmdOpts.data);
@@ -1109,6 +1118,24 @@ function registerBrandKitCommands(program2) {
1109
1118
  process.exit(1);
1110
1119
  }
1111
1120
  });
1121
+ bk.command("patch").description("Partially update the brand kit (PATCH). Only the fields you provide are changed \u2014 existing fields are preserved. Preferred over 'set' for targeted updates.").requiredOption("--data <json>", 'JSON: { "guidelines": { "voice_and_tone": "Warm and approachable" } }').action(async (cmdOpts) => {
1122
+ const opts = program2.opts();
1123
+ try {
1124
+ const body = JSON.parse(cmdOpts.data);
1125
+ const data = await apiRequest({
1126
+ method: "PATCH",
1127
+ path: "/org/brand-kit",
1128
+ body,
1129
+ apiKey: opts.apiKey,
1130
+ baseUrl: opts.baseUrl
1131
+ });
1132
+ success("Brand kit updated.");
1133
+ console.log(JSON.stringify(data, null, 2));
1134
+ } catch (err) {
1135
+ error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
1136
+ process.exit(1);
1137
+ }
1138
+ });
1112
1139
  }
1113
1140
 
1114
1141
  // src/commands/content-types.ts
@@ -1157,7 +1184,7 @@ function registerContentTypeCommands(program2) {
1157
1184
  process.exit(1);
1158
1185
  }
1159
1186
  });
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) => {
1187
+ ct.command("update <id>").description("Replace a content type's name and config (PUT). Both fields are required \u2014 run 'get <id>' first to preserve existing values. For single-field updates, use 'content-types patch <id>'.").requiredOption("--data <json>", 'JSON: { "name": "Updated Name", "config": { "template": "...", "cta_text": "...", "cta_destination": "...", "writing_rules": [] } }').action(async (id, cmdOpts) => {
1161
1188
  const opts = program2.opts();
1162
1189
  try {
1163
1190
  const body = JSON.parse(cmdOpts.data);
@@ -1175,6 +1202,24 @@ function registerContentTypeCommands(program2) {
1175
1202
  process.exit(1);
1176
1203
  }
1177
1204
  });
1205
+ ct.command("patch <id>").description("Partially update a content type (PATCH). Only the fields you provide are changed \u2014 existing fields are preserved. Preferred over 'update' for targeted changes like updating just the template.").requiredOption("--data <json>", 'JSON: { "config": { "template": "Updated template instruction" } }').action(async (id, cmdOpts) => {
1206
+ const opts = program2.opts();
1207
+ try {
1208
+ const body = JSON.parse(cmdOpts.data);
1209
+ const data = await apiRequest({
1210
+ method: "PATCH",
1211
+ path: `/org/content-types/${id}`,
1212
+ body,
1213
+ apiKey: opts.apiKey,
1214
+ baseUrl: opts.baseUrl
1215
+ });
1216
+ success(`Content type ${id} updated.`);
1217
+ console.log(JSON.stringify(data, null, 2));
1218
+ } catch (err) {
1219
+ error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
1220
+ process.exit(1);
1221
+ }
1222
+ });
1178
1223
  ct.command("delete <id>").description("Delete a content type. This cannot be undone.").action(async (id) => {
1179
1224
  const opts = program2.opts();
1180
1225
  try {
@@ -1189,7 +1234,7 @@ function registerContentTypeCommands(program2) {
1189
1234
 
1190
1235
  // src/commands/prompts.ts
1191
1236
  function registerPromptCommands(program2) {
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.");
1237
+ const prompts = program2.command("prompts").description("Manage prompts (GEO questions). Each prompt is a question that drives both AI content generation (use with 'generate sample --prompt-id') and brand visibility monitoring \u2014 tracking how AI models mention your brand, products, and competitors.");
1193
1238
  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) => {
1194
1239
  const opts = program2.opts();
1195
1240
  try {
@@ -1365,6 +1410,21 @@ function registerNotificationCommands(program2) {
1365
1410
  });
1366
1411
  }
1367
1412
 
1413
+ // src/commands/credits.ts
1414
+ function registerCreditsCommands(program2) {
1415
+ const credits = program2.command("credits").description("View your organisation's credit balance. Credits are consumed by AI content generation and search operations.");
1416
+ credits.command("balance").description("Get the current credit balance for the organisation. Returns available credits and any spend limit configured.").action(async () => {
1417
+ const opts = program2.opts();
1418
+ try {
1419
+ const data = await apiRequest({ path: "/org/credits/balance", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
1420
+ console.log(JSON.stringify(data, null, 2));
1421
+ } catch (err) {
1422
+ error(formatApiError(err));
1423
+ process.exit(1);
1424
+ }
1425
+ });
1426
+ }
1427
+
1368
1428
  // src/commands/update.ts
1369
1429
  import semver2 from "semver";
1370
1430
  import pc7 from "picocolors";
@@ -1423,6 +1483,7 @@ registerPromptCommands(program);
1423
1483
  registerRunConfigCommands(program);
1424
1484
  registerMemberCommands(program);
1425
1485
  registerNotificationCommands(program);
1486
+ registerCreditsCommands(program);
1426
1487
  registerUpdateCommand(program);
1427
1488
  async function main() {
1428
1489
  const quiet = process.argv.includes("--quiet") || process.argv.includes("--output") && process.argv[process.argv.indexOf("--output") + 1] === "json";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@senso-ai/cli",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Senso CLI — Infrastructure for the Agentic Web. Interact with your Senso knowledge base from the terminal.",
5
5
  "type": "module",
6
6
  "bin": {