@senso-ai/cli 0.6.1 → 0.7.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 +55 -51
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -164,7 +164,7 @@ import pc3 from "picocolors";
|
|
|
164
164
|
// src/lib/api-client.ts
|
|
165
165
|
var ApiError = class extends Error {
|
|
166
166
|
constructor(status, statusText, body) {
|
|
167
|
-
const msg = typeof body === "object" && body
|
|
167
|
+
const msg = typeof body === "object" && body ? "error" in body ? body.error : "message" in body ? body.message : statusText : statusText;
|
|
168
168
|
super(msg);
|
|
169
169
|
this.status = status;
|
|
170
170
|
this.statusText = statusText;
|
|
@@ -230,8 +230,10 @@ function formatApiError(err) {
|
|
|
230
230
|
switch (err.status) {
|
|
231
231
|
case 401:
|
|
232
232
|
return "Authentication failed. Run `senso login` to update your API key.";
|
|
233
|
+
case 402:
|
|
234
|
+
return "Insufficient credits or spending limit reached. Check your plan at https://app.senso.ai.";
|
|
233
235
|
case 403:
|
|
234
|
-
return
|
|
236
|
+
return `Permission denied: ${err.message}`;
|
|
235
237
|
case 404:
|
|
236
238
|
return "Resource not found.";
|
|
237
239
|
case 409:
|
|
@@ -666,12 +668,17 @@ function output(format, data) {
|
|
|
666
668
|
}
|
|
667
669
|
|
|
668
670
|
// src/commands/search.ts
|
|
671
|
+
function parseMaxResults(value) {
|
|
672
|
+
const n = parseInt(value);
|
|
673
|
+
if (isNaN(n) || n < 1) return 5;
|
|
674
|
+
return Math.min(n, 20);
|
|
675
|
+
}
|
|
669
676
|
function registerSearchCommands(program2) {
|
|
670
677
|
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.");
|
|
671
|
-
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) => {
|
|
678
|
+
search.argument("<query>", "Search query").option("--max-results <n>", "Maximum number of results (max: 20)", "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) => {
|
|
672
679
|
const opts = program2.opts();
|
|
673
680
|
try {
|
|
674
|
-
const body = { query, max_results:
|
|
681
|
+
const body = { query, max_results: parseMaxResults(cmdOpts.maxResults) };
|
|
675
682
|
if (cmdOpts.contentIds) body.content_ids = cmdOpts.contentIds;
|
|
676
683
|
if (cmdOpts.requireScopedIds) body.require_scoped_ids = true;
|
|
677
684
|
const data = await apiRequest({
|
|
@@ -710,10 +717,10 @@ function registerSearchCommands(program2) {
|
|
|
710
717
|
process.exit(1);
|
|
711
718
|
}
|
|
712
719
|
});
|
|
713
|
-
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) => {
|
|
720
|
+
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 (max: 20)", "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) => {
|
|
714
721
|
const opts = program2.opts();
|
|
715
722
|
try {
|
|
716
|
-
const body = { query, max_results:
|
|
723
|
+
const body = { query, max_results: parseMaxResults(cmdOpts.maxResults) };
|
|
717
724
|
if (cmdOpts.contentIds) body.content_ids = cmdOpts.contentIds;
|
|
718
725
|
if (cmdOpts.requireScopedIds) body.require_scoped_ids = true;
|
|
719
726
|
const data = await apiRequest({
|
|
@@ -729,10 +736,10 @@ function registerSearchCommands(program2) {
|
|
|
729
736
|
process.exit(1);
|
|
730
737
|
}
|
|
731
738
|
});
|
|
732
|
-
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) => {
|
|
739
|
+
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 (max: 20)", "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) => {
|
|
733
740
|
const opts = program2.opts();
|
|
734
741
|
try {
|
|
735
|
-
const body = { query, max_results:
|
|
742
|
+
const body = { query, max_results: parseMaxResults(cmdOpts.maxResults) };
|
|
736
743
|
if (cmdOpts.contentIds) body.content_ids = cmdOpts.contentIds;
|
|
737
744
|
if (cmdOpts.requireScopedIds) body.require_scoped_ids = true;
|
|
738
745
|
const data = await apiRequest({
|
|
@@ -748,10 +755,10 @@ function registerSearchCommands(program2) {
|
|
|
748
755
|
process.exit(1);
|
|
749
756
|
}
|
|
750
757
|
});
|
|
751
|
-
search.command("full <query>").description("Alias for the default search \u2014 returns AI answer plus matching chunks. Equivalent to 'senso search <query>'.").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) => {
|
|
758
|
+
search.command("full <query>").description("Alias for the default search \u2014 returns AI answer plus matching chunks. Equivalent to 'senso search <query>'.").option("--max-results <n>", "Maximum results (max: 20)", "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) => {
|
|
752
759
|
const opts = program2.opts();
|
|
753
760
|
try {
|
|
754
|
-
const body = { query, max_results:
|
|
761
|
+
const body = { query, max_results: parseMaxResults(cmdOpts.maxResults) };
|
|
755
762
|
if (cmdOpts.contentIds) body.content_ids = cmdOpts.contentIds;
|
|
756
763
|
if (cmdOpts.requireScopedIds) body.require_scoped_ids = true;
|
|
757
764
|
const data = await apiRequest({
|
|
@@ -835,14 +842,14 @@ function registerIngestCommands(program2) {
|
|
|
835
842
|
}
|
|
836
843
|
try {
|
|
837
844
|
const fileData = await Promise.all(files.map(getFileMetadata));
|
|
838
|
-
const
|
|
845
|
+
const response = await apiRequest({
|
|
839
846
|
method: "POST",
|
|
840
847
|
path: "/org/kb/upload",
|
|
841
848
|
body: { files: fileData.map((f) => f.meta) },
|
|
842
849
|
apiKey: opts.apiKey,
|
|
843
850
|
baseUrl: opts.baseUrl
|
|
844
851
|
});
|
|
845
|
-
const items =
|
|
852
|
+
const items = response.results ?? [];
|
|
846
853
|
let uploaded = 0;
|
|
847
854
|
for (const item of items) {
|
|
848
855
|
if (item.status === "upload_pending" && item.upload_url) {
|
|
@@ -859,42 +866,39 @@ function registerIngestCommands(program2) {
|
|
|
859
866
|
error(`S3 upload failed for ${item.filename}: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`);
|
|
860
867
|
}
|
|
861
868
|
} else {
|
|
862
|
-
warn(`Skipped ${item.filename}: ${item.status}${item.
|
|
869
|
+
warn(`Skipped ${item.filename}: ${item.status}${item.error ? ` \u2014 ${item.error}` : ""}`);
|
|
863
870
|
}
|
|
864
871
|
}
|
|
865
872
|
if (uploaded > 0) {
|
|
866
873
|
success(`${uploaded} file(s) uploaded. Background processing will parse, chunk, and embed them.`);
|
|
867
874
|
}
|
|
868
875
|
if (opts.output === "json") {
|
|
869
|
-
console.log(JSON.stringify(
|
|
876
|
+
console.log(JSON.stringify(response, null, 2));
|
|
870
877
|
}
|
|
871
878
|
} catch (err) {
|
|
872
879
|
error(formatApiError(err));
|
|
873
880
|
process.exit(1);
|
|
874
881
|
}
|
|
875
882
|
});
|
|
876
|
-
ingest.command("reprocess <
|
|
883
|
+
ingest.command("reprocess <nodeId> <file>").description("Re-ingest an existing document with a new file version. Provide the KB node ID (kb_node_id) and the path to the replacement file.").action(async (nodeId, file) => {
|
|
877
884
|
const opts = program2.opts();
|
|
878
885
|
try {
|
|
879
886
|
const { meta, buffer } = await getFileMetadata(file);
|
|
880
|
-
const
|
|
887
|
+
const item = await apiRequest({
|
|
881
888
|
method: "PUT",
|
|
882
|
-
path: `/org/kb/nodes/${
|
|
889
|
+
path: `/org/kb/nodes/${nodeId}/file`,
|
|
883
890
|
body: { file: meta },
|
|
884
891
|
apiKey: opts.apiKey,
|
|
885
892
|
baseUrl: opts.baseUrl
|
|
886
893
|
});
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
} else {
|
|
893
|
-
warn(`Skipped: ${item.status}${item.message ? ` \u2014 ${item.message}` : ""}`);
|
|
894
|
-
}
|
|
894
|
+
if (item.status === "upload_pending" && item.upload_url) {
|
|
895
|
+
await uploadToS3(item.upload_url, buffer, meta.content_type);
|
|
896
|
+
success(`Uploaded ${meta.filename} for node ${nodeId}. Background re-processing started.`);
|
|
897
|
+
} else {
|
|
898
|
+
warn(`Skipped: ${item.status}${item.error ? ` \u2014 ${item.error}` : ""}`);
|
|
895
899
|
}
|
|
896
900
|
if (opts.output === "json") {
|
|
897
|
-
console.log(JSON.stringify(
|
|
901
|
+
console.log(JSON.stringify(item, null, 2));
|
|
898
902
|
}
|
|
899
903
|
} catch (err) {
|
|
900
904
|
error(formatApiError(err));
|
|
@@ -922,7 +926,7 @@ function registerContentCommands(program2) {
|
|
|
922
926
|
json: data,
|
|
923
927
|
table: {
|
|
924
928
|
rows: rows.map((r) => ({
|
|
925
|
-
id: r.
|
|
929
|
+
id: r.kb_node_id,
|
|
926
930
|
name: r.name,
|
|
927
931
|
type: r.type,
|
|
928
932
|
status: r.processing_status
|
|
@@ -930,7 +934,7 @@ function registerContentCommands(program2) {
|
|
|
930
934
|
columns: ["id", "name", "type", "status"]
|
|
931
935
|
},
|
|
932
936
|
plain: rows.length ? rows.map(
|
|
933
|
-
(r) => ` ${pc6.bold(String(r.name || "Untitled"))} ${pc6.dim(`(${r.
|
|
937
|
+
(r) => ` ${pc6.bold(String(r.name || "Untitled"))} ${pc6.dim(`(${r.kb_node_id})`)} ${r.type ? pc6.dim(`[${r.type}]`) : ""}`
|
|
934
938
|
) : [" No content found."]
|
|
935
939
|
});
|
|
936
940
|
} catch (err) {
|
|
@@ -1055,7 +1059,7 @@ function registerGenerateCommands(program2) {
|
|
|
1055
1059
|
process.exit(1);
|
|
1056
1060
|
}
|
|
1057
1061
|
});
|
|
1058
|
-
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) => {
|
|
1062
|
+
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], "selected_content_type_id": "<uuid>" }').action(async (cmdOpts) => {
|
|
1059
1063
|
const opts = program2.opts();
|
|
1060
1064
|
try {
|
|
1061
1065
|
const body = JSON.parse(cmdOpts.data);
|
|
@@ -1090,11 +1094,14 @@ function registerGenerateCommands(program2) {
|
|
|
1090
1094
|
process.exit(1);
|
|
1091
1095
|
}
|
|
1092
1096
|
});
|
|
1093
|
-
gen.command("run").description("Trigger a content generation run. Processes all prompts (or a specific subset) through the content engine. Runs asynchronously \u2014 use 'generate runs-list' to monitor progress.").option("--prompt-ids <ids...>", "Optional list of prompt IDs to process (omit to run all)").action(async (cmdOpts) => {
|
|
1097
|
+
gen.command("run").description("Trigger a content generation run. Processes all prompts (or a specific subset) through the content engine. Runs asynchronously \u2014 use 'generate runs-list' to monitor progress.").option("--prompt-ids <ids...>", "Optional list of prompt IDs to process (omit to run all)").option("--content-type-id <id>", "Override the org's default content type for this run").option("--publisher-ids <ids...>", "Restrict publishing to specific publisher IDs").action(async (cmdOpts) => {
|
|
1094
1098
|
const opts = program2.opts();
|
|
1095
1099
|
try {
|
|
1096
|
-
const body =
|
|
1097
|
-
|
|
1100
|
+
const body = {};
|
|
1101
|
+
if (cmdOpts.promptIds) body.prompt_ids = cmdOpts.promptIds;
|
|
1102
|
+
if (cmdOpts.contentTypeId) body.content_type_id = cmdOpts.contentTypeId;
|
|
1103
|
+
if (cmdOpts.publisherIds) body.publisher_ids = cmdOpts.publisherIds;
|
|
1104
|
+
const data = await apiRequest({ method: "POST", path: "/org/content-generation/run", body: Object.keys(body).length > 0 ? body : {}, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1098
1105
|
success("Content generation run triggered.");
|
|
1099
1106
|
console.log(JSON.stringify(data, null, 2));
|
|
1100
1107
|
} catch (err) {
|
|
@@ -1738,7 +1745,7 @@ function registerKBCommands(program2) {
|
|
|
1738
1745
|
const opts = program2.opts();
|
|
1739
1746
|
try {
|
|
1740
1747
|
const body = { name: cmdOpts.name };
|
|
1741
|
-
if (cmdOpts.parentId) body.
|
|
1748
|
+
if (cmdOpts.parentId) body.parent_id = cmdOpts.parentId;
|
|
1742
1749
|
const data = await apiRequest({ method: "POST", path: "/org/kb/folders", body, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1743
1750
|
success(`Folder "${cmdOpts.name}" created.`);
|
|
1744
1751
|
console.log(JSON.stringify(data, null, 2));
|
|
@@ -1761,7 +1768,7 @@ function registerKBCommands(program2) {
|
|
|
1761
1768
|
kb.command("move <id>").description("Move a KB node to a different parent folder.").requiredOption("--parent-id <parentId>", "Target parent folder node ID").action(async (id, cmdOpts) => {
|
|
1762
1769
|
const opts = program2.opts();
|
|
1763
1770
|
try {
|
|
1764
|
-
const data = await apiRequest({ method: "PATCH", path: `/org/kb/nodes/${id}/move`, body: {
|
|
1771
|
+
const data = await apiRequest({ method: "PATCH", path: `/org/kb/nodes/${id}/move`, body: { new_parent_id: cmdOpts.parentId }, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1765
1772
|
success(`Node ${id} moved.`);
|
|
1766
1773
|
console.log(JSON.stringify(data, null, 2));
|
|
1767
1774
|
} catch (err) {
|
|
@@ -1779,7 +1786,7 @@ function registerKBCommands(program2) {
|
|
|
1779
1786
|
process.exit(1);
|
|
1780
1787
|
}
|
|
1781
1788
|
});
|
|
1782
|
-
kb.command("create-raw").description("Create a raw (text/markdown) content item in the knowledge base.").requiredOption("--data <json>", 'JSON: { "
|
|
1789
|
+
kb.command("create-raw").description("Create a raw (text/markdown) content item in the knowledge base.").requiredOption("--data <json>", 'JSON: { "title": "My doc", "text": "# Hello", "kb_folder_node_id": "<uuid>", "tag_ids": ["<uuid>"] }').action(async (cmdOpts) => {
|
|
1783
1790
|
const opts = program2.opts();
|
|
1784
1791
|
try {
|
|
1785
1792
|
const body = JSON.parse(cmdOpts.data);
|
|
@@ -1791,7 +1798,7 @@ function registerKBCommands(program2) {
|
|
|
1791
1798
|
process.exit(1);
|
|
1792
1799
|
}
|
|
1793
1800
|
});
|
|
1794
|
-
kb.command("update-raw <id>").description("Fully replace the text content of a raw KB node (creates a new version).").requiredOption("--data <json>", 'JSON: { "text": "# Updated content" }').action(async (id, cmdOpts) => {
|
|
1801
|
+
kb.command("update-raw <id>").description("Fully replace the text content of a raw KB node (creates a new version).").requiredOption("--data <json>", 'JSON: { "title": "Title", "text": "# Updated content", "tag_ids": ["<uuid>"] }').action(async (id, cmdOpts) => {
|
|
1795
1802
|
const opts = program2.opts();
|
|
1796
1803
|
try {
|
|
1797
1804
|
const body = JSON.parse(cmdOpts.data);
|
|
@@ -1803,7 +1810,7 @@ function registerKBCommands(program2) {
|
|
|
1803
1810
|
process.exit(1);
|
|
1804
1811
|
}
|
|
1805
1812
|
});
|
|
1806
|
-
kb.command("patch-raw <id>").description("Partially update the text content of a raw KB node.").requiredOption("--data <json>", 'JSON: { "text": "Updated text", "
|
|
1813
|
+
kb.command("patch-raw <id>").description("Partially update the text content of a raw KB node.").requiredOption("--data <json>", 'JSON: { "title": "New title", "text": "Updated text", "summary": "...", "tag_ids": ["<uuid>"] }').action(async (id, cmdOpts) => {
|
|
1807
1814
|
const opts = program2.opts();
|
|
1808
1815
|
try {
|
|
1809
1816
|
const body = JSON.parse(cmdOpts.data);
|
|
@@ -1825,14 +1832,14 @@ function registerKBCommands(program2) {
|
|
|
1825
1832
|
const fileData = await Promise.all(files.map(getFileMetadata2));
|
|
1826
1833
|
const body = { files: fileData.map((f) => f.meta) };
|
|
1827
1834
|
if (cmdOpts.folderId) body.kb_folder_node_id = cmdOpts.folderId;
|
|
1828
|
-
const
|
|
1835
|
+
const response = await apiRequest({
|
|
1829
1836
|
method: "POST",
|
|
1830
1837
|
path: "/org/kb/upload",
|
|
1831
1838
|
body,
|
|
1832
1839
|
apiKey: opts.apiKey,
|
|
1833
1840
|
baseUrl: opts.baseUrl
|
|
1834
1841
|
});
|
|
1835
|
-
const items =
|
|
1842
|
+
const items = response.results ?? [];
|
|
1836
1843
|
let uploaded = 0;
|
|
1837
1844
|
for (const item of items) {
|
|
1838
1845
|
if (item.status === "upload_pending" && item.upload_url) {
|
|
@@ -1849,13 +1856,13 @@ function registerKBCommands(program2) {
|
|
|
1849
1856
|
error(`S3 upload failed for ${item.filename}: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`);
|
|
1850
1857
|
}
|
|
1851
1858
|
} else {
|
|
1852
|
-
warn(`Skipped ${item.filename}: ${item.status}${item.
|
|
1859
|
+
warn(`Skipped ${item.filename}: ${item.status}${item.error ? ` \u2014 ${item.error}` : ""}`);
|
|
1853
1860
|
}
|
|
1854
1861
|
}
|
|
1855
1862
|
if (uploaded > 0) {
|
|
1856
1863
|
success(`${uploaded} file(s) uploaded. Background processing will parse, chunk, and embed them.`);
|
|
1857
1864
|
}
|
|
1858
|
-
if (opts.output === "json") console.log(JSON.stringify(
|
|
1865
|
+
if (opts.output === "json") console.log(JSON.stringify(response, null, 2));
|
|
1859
1866
|
} catch (err) {
|
|
1860
1867
|
error(formatApiError(err));
|
|
1861
1868
|
process.exit(1);
|
|
@@ -1865,23 +1872,20 @@ function registerKBCommands(program2) {
|
|
|
1865
1872
|
const opts = program2.opts();
|
|
1866
1873
|
try {
|
|
1867
1874
|
const { meta, buffer } = await getFileMetadata2(file);
|
|
1868
|
-
const
|
|
1875
|
+
const item = await apiRequest({
|
|
1869
1876
|
method: "PUT",
|
|
1870
1877
|
path: `/org/kb/nodes/${id}/file`,
|
|
1871
1878
|
body: { file: meta },
|
|
1872
1879
|
apiKey: opts.apiKey,
|
|
1873
1880
|
baseUrl: opts.baseUrl
|
|
1874
1881
|
});
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
} else {
|
|
1881
|
-
warn(`Skipped: ${item.status}${item.message ? ` \u2014 ${item.message}` : ""}`);
|
|
1882
|
-
}
|
|
1882
|
+
if (item.status === "upload_pending" && item.upload_url) {
|
|
1883
|
+
await uploadToS32(item.upload_url, buffer, meta.content_type);
|
|
1884
|
+
success(`Uploaded ${meta.filename} for node ${id}. Background re-processing started.`);
|
|
1885
|
+
} else {
|
|
1886
|
+
warn(`Skipped: ${item.status}${item.error ? ` \u2014 ${item.error}` : ""}`);
|
|
1883
1887
|
}
|
|
1884
|
-
if (opts.output === "json") console.log(JSON.stringify(
|
|
1888
|
+
if (opts.output === "json") console.log(JSON.stringify(item, null, 2));
|
|
1885
1889
|
} catch (err) {
|
|
1886
1890
|
error(formatApiError(err));
|
|
1887
1891
|
process.exit(1);
|