@senso-ai/cli 0.3.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.
- package/dist/cli.js +91 -54
- 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
|
|
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
|
|
132
|
-
|
|
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
|
|
143
|
+
async function getLatestVersion() {
|
|
152
144
|
try {
|
|
153
145
|
const res = await fetch(
|
|
154
|
-
`https://
|
|
146
|
+
`https://registry.npmjs.org/${NPM_PACKAGE}`,
|
|
155
147
|
{
|
|
156
|
-
headers: { Accept: "application/
|
|
148
|
+
headers: { Accept: "application/json" },
|
|
157
149
|
signal: AbortSignal.timeout(1e4)
|
|
158
150
|
}
|
|
159
151
|
);
|
|
160
152
|
if (!res.ok) return null;
|
|
161
|
-
|
|
153
|
+
const data = await res.json();
|
|
154
|
+
return data["dist-tags"]?.latest ?? null;
|
|
162
155
|
} catch {
|
|
163
156
|
return null;
|
|
164
157
|
}
|
|
@@ -643,13 +636,16 @@ function output(format, data) {
|
|
|
643
636
|
// src/commands/search.ts
|
|
644
637
|
function registerSearchCommands(program2) {
|
|
645
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.");
|
|
646
|
-
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) => {
|
|
647
640
|
const opts = program2.opts();
|
|
648
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;
|
|
649
645
|
const data = await apiRequest({
|
|
650
646
|
method: "POST",
|
|
651
647
|
path: "/org/search",
|
|
652
|
-
body
|
|
648
|
+
body,
|
|
653
649
|
apiKey: opts.apiKey,
|
|
654
650
|
baseUrl: opts.baseUrl
|
|
655
651
|
});
|
|
@@ -682,13 +678,16 @@ function registerSearchCommands(program2) {
|
|
|
682
678
|
process.exit(1);
|
|
683
679
|
}
|
|
684
680
|
});
|
|
685
|
-
search.command("context <query>").description("Search the knowledge base \u2014 returns matching content chunks only, without AI answer generation.
|
|
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) => {
|
|
686
682
|
const opts = program2.opts();
|
|
687
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;
|
|
688
687
|
const data = await apiRequest({
|
|
689
688
|
method: "POST",
|
|
690
689
|
path: "/org/search/context",
|
|
691
|
-
body
|
|
690
|
+
body,
|
|
692
691
|
apiKey: opts.apiKey,
|
|
693
692
|
baseUrl: opts.baseUrl
|
|
694
693
|
});
|
|
@@ -698,13 +697,16 @@ function registerSearchCommands(program2) {
|
|
|
698
697
|
process.exit(1);
|
|
699
698
|
}
|
|
700
699
|
});
|
|
701
|
-
search.command("content <query>").description("Search the knowledge base \u2014 returns deduplicated content IDs and titles only.
|
|
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) => {
|
|
702
701
|
const opts = program2.opts();
|
|
703
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;
|
|
704
706
|
const data = await apiRequest({
|
|
705
707
|
method: "POST",
|
|
706
708
|
path: "/org/search/content",
|
|
707
|
-
body
|
|
709
|
+
body,
|
|
708
710
|
apiKey: opts.apiKey,
|
|
709
711
|
baseUrl: opts.baseUrl
|
|
710
712
|
});
|
|
@@ -774,7 +776,7 @@ async function uploadToS3(url, buffer, contentType) {
|
|
|
774
776
|
}
|
|
775
777
|
function registerIngestCommands(program2) {
|
|
776
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.");
|
|
777
|
-
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) => {
|
|
778
780
|
const opts = program2.opts();
|
|
779
781
|
if (files.length > 10) {
|
|
780
782
|
error("Maximum 10 files per upload request.");
|
|
@@ -1007,7 +1009,7 @@ function registerGenerateCommands(program2) {
|
|
|
1007
1009
|
process.exit(1);
|
|
1008
1010
|
}
|
|
1009
1011
|
});
|
|
1010
|
-
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>", "
|
|
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) => {
|
|
1011
1013
|
const opts = program2.opts();
|
|
1012
1014
|
try {
|
|
1013
1015
|
const body = {
|
|
@@ -1030,7 +1032,7 @@ function registerGenerateCommands(program2) {
|
|
|
1030
1032
|
process.exit(1);
|
|
1031
1033
|
}
|
|
1032
1034
|
});
|
|
1033
|
-
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) => {
|
|
1034
1036
|
const opts = program2.opts();
|
|
1035
1037
|
try {
|
|
1036
1038
|
const body = cmdOpts.promptIds ? { prompt_ids: cmdOpts.promptIds } : void 0;
|
|
@@ -1098,7 +1100,7 @@ function registerBrandKitCommands(program2) {
|
|
|
1098
1100
|
process.exit(1);
|
|
1099
1101
|
}
|
|
1100
1102
|
});
|
|
1101
|
-
bk.command("set").description("
|
|
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) => {
|
|
1102
1104
|
const opts = program2.opts();
|
|
1103
1105
|
try {
|
|
1104
1106
|
const body = JSON.parse(cmdOpts.data);
|
|
@@ -1116,6 +1118,24 @@ function registerBrandKitCommands(program2) {
|
|
|
1116
1118
|
process.exit(1);
|
|
1117
1119
|
}
|
|
1118
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
|
+
});
|
|
1119
1139
|
}
|
|
1120
1140
|
|
|
1121
1141
|
// src/commands/content-types.ts
|
|
@@ -1164,7 +1184,7 @@ function registerContentTypeCommands(program2) {
|
|
|
1164
1184
|
process.exit(1);
|
|
1165
1185
|
}
|
|
1166
1186
|
});
|
|
1167
|
-
ct.command("update <id>").description("
|
|
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) => {
|
|
1168
1188
|
const opts = program2.opts();
|
|
1169
1189
|
try {
|
|
1170
1190
|
const body = JSON.parse(cmdOpts.data);
|
|
@@ -1182,6 +1202,24 @@ function registerContentTypeCommands(program2) {
|
|
|
1182
1202
|
process.exit(1);
|
|
1183
1203
|
}
|
|
1184
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
|
+
});
|
|
1185
1223
|
ct.command("delete <id>").description("Delete a content type. This cannot be undone.").action(async (id) => {
|
|
1186
1224
|
const opts = program2.opts();
|
|
1187
1225
|
try {
|
|
@@ -1196,7 +1234,7 @@ function registerContentTypeCommands(program2) {
|
|
|
1196
1234
|
|
|
1197
1235
|
// src/commands/prompts.ts
|
|
1198
1236
|
function registerPromptCommands(program2) {
|
|
1199
|
-
const prompts = program2.command("prompts").description("Manage prompts (
|
|
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.");
|
|
1200
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) => {
|
|
1201
1239
|
const opts = program2.opts();
|
|
1202
1240
|
try {
|
|
@@ -1372,20 +1410,35 @@ function registerNotificationCommands(program2) {
|
|
|
1372
1410
|
});
|
|
1373
1411
|
}
|
|
1374
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
|
+
|
|
1375
1428
|
// src/commands/update.ts
|
|
1376
1429
|
import semver2 from "semver";
|
|
1377
1430
|
import pc7 from "picocolors";
|
|
1378
1431
|
import { execSync } from "child_process";
|
|
1432
|
+
var NPM_PACKAGE2 = "@senso-ai/cli";
|
|
1379
1433
|
function registerUpdateCommand(program2) {
|
|
1380
1434
|
program2.command("update").description("Update CLI to the latest version").action(async () => {
|
|
1381
1435
|
info(`Current version: ${pc7.bold(version)}`);
|
|
1382
|
-
info("Checking for updates...");
|
|
1383
|
-
const
|
|
1384
|
-
if (!
|
|
1436
|
+
info("Checking npm for updates...");
|
|
1437
|
+
const latest = await getLatestVersion();
|
|
1438
|
+
if (!latest) {
|
|
1385
1439
|
error("Could not check for updates. Try again later.");
|
|
1386
1440
|
process.exit(1);
|
|
1387
1441
|
}
|
|
1388
|
-
const latest = release.tag_name.replace(/^v/, "");
|
|
1389
1442
|
if (!semver2.gt(latest, version)) {
|
|
1390
1443
|
success(`Already on the latest version (${version}).`);
|
|
1391
1444
|
return;
|
|
@@ -1393,33 +1446,16 @@ function registerUpdateCommand(program2) {
|
|
|
1393
1446
|
info(`New version available: ${pc7.bold(latest)}`);
|
|
1394
1447
|
info("Updating...");
|
|
1395
1448
|
try {
|
|
1396
|
-
execSync(
|
|
1449
|
+
execSync(`npm install -g ${NPM_PACKAGE2}@latest`, {
|
|
1397
1450
|
stdio: "inherit"
|
|
1398
1451
|
});
|
|
1399
1452
|
success(`Updated to v${latest}.`);
|
|
1400
|
-
if (release.body) {
|
|
1401
|
-
console.log();
|
|
1402
|
-
console.log(pc7.dim("Release notes:"));
|
|
1403
|
-
console.log(pc7.dim(release.body.slice(0, 500)));
|
|
1404
|
-
}
|
|
1405
1453
|
} catch {
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
);
|
|
1412
|
-
success("Updated via npx cache refresh.");
|
|
1413
|
-
} catch {
|
|
1414
|
-
error("Update failed. Please reinstall manually:");
|
|
1415
|
-
console.log(
|
|
1416
|
-
` ${pc7.cyan("npm install -g senso-user-cli")}`
|
|
1417
|
-
);
|
|
1418
|
-
console.log(
|
|
1419
|
-
` ${pc7.dim("or")} ${pc7.cyan("npx github:AI-Template-SDK/senso-user-cli")}`
|
|
1420
|
-
);
|
|
1421
|
-
process.exit(1);
|
|
1422
|
-
}
|
|
1454
|
+
error("Update failed. Please reinstall manually:");
|
|
1455
|
+
console.log(
|
|
1456
|
+
` ${pc7.cyan(`npm install -g ${NPM_PACKAGE2}`)}`
|
|
1457
|
+
);
|
|
1458
|
+
process.exit(1);
|
|
1423
1459
|
}
|
|
1424
1460
|
});
|
|
1425
1461
|
}
|
|
@@ -1447,6 +1483,7 @@ registerPromptCommands(program);
|
|
|
1447
1483
|
registerRunConfigCommands(program);
|
|
1448
1484
|
registerMemberCommands(program);
|
|
1449
1485
|
registerNotificationCommands(program);
|
|
1486
|
+
registerCreditsCommands(program);
|
|
1450
1487
|
registerUpdateCommand(program);
|
|
1451
1488
|
async function main() {
|
|
1452
1489
|
const quiet = process.argv.includes("--quiet") || process.argv.includes("--output") && process.argv[process.argv.indexOf("--output") + 1] === "json";
|