@senso-ai/cli 0.4.0 → 0.6.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 +569 -39
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -578,6 +578,38 @@ function registerApiKeyCommands(program2) {
|
|
|
578
578
|
process.exit(1);
|
|
579
579
|
}
|
|
580
580
|
});
|
|
581
|
+
keys.command("kb-permissions-get <keyId>").description("Get the knowledge base node permission grants configured for an API key.").action(async (keyId) => {
|
|
582
|
+
const opts = program2.opts();
|
|
583
|
+
try {
|
|
584
|
+
const data = await apiRequest({ path: `/org/api-keys/${keyId}/kb-permissions`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
585
|
+
console.log(JSON.stringify(data, null, 2));
|
|
586
|
+
} catch (err) {
|
|
587
|
+
error(formatApiError(err));
|
|
588
|
+
process.exit(1);
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
keys.command("kb-permissions-set <keyId>").description("Set KB node permission grants for an API key. Replaces any existing grants. Each grant requires a node_id (UUID) and role (viewer|editor|owner|admin).").requiredOption("--data <json>", 'JSON: { "grants": [{ "node_id": "<uuid>", "role": "viewer" }] }').action(async (keyId, cmdOpts) => {
|
|
592
|
+
const opts = program2.opts();
|
|
593
|
+
try {
|
|
594
|
+
const body = JSON.parse(cmdOpts.data);
|
|
595
|
+
const data = await apiRequest({ method: "PUT", path: `/org/api-keys/${keyId}/kb-permissions`, body, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
596
|
+
success(`KB permissions updated for key ${keyId}.`);
|
|
597
|
+
console.log(JSON.stringify(data, null, 2));
|
|
598
|
+
} catch (err) {
|
|
599
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
600
|
+
process.exit(1);
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
keys.command("kb-permissions-delete <keyId>").description("Remove all KB node permission grants from an API key, restoring full org-level access.").action(async (keyId) => {
|
|
604
|
+
const opts = program2.opts();
|
|
605
|
+
try {
|
|
606
|
+
await apiRequest({ method: "DELETE", path: `/org/api-keys/${keyId}/kb-permissions`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
607
|
+
success(`KB permissions removed from key ${keyId}.`);
|
|
608
|
+
} catch (err) {
|
|
609
|
+
error(formatApiError(err));
|
|
610
|
+
process.exit(1);
|
|
611
|
+
}
|
|
612
|
+
});
|
|
581
613
|
}
|
|
582
614
|
|
|
583
615
|
// src/commands/search.ts
|
|
@@ -636,13 +668,16 @@ function output(format, data) {
|
|
|
636
668
|
// src/commands/search.ts
|
|
637
669
|
function registerSearchCommands(program2) {
|
|
638
670
|
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) => {
|
|
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) => {
|
|
640
672
|
const opts = program2.opts();
|
|
641
673
|
try {
|
|
674
|
+
const body = { query, max_results: parseInt(cmdOpts.maxResults) };
|
|
675
|
+
if (cmdOpts.contentIds) body.content_ids = cmdOpts.contentIds;
|
|
676
|
+
if (cmdOpts.requireScopedIds) body.require_scoped_ids = true;
|
|
642
677
|
const data = await apiRequest({
|
|
643
678
|
method: "POST",
|
|
644
679
|
path: "/org/search",
|
|
645
|
-
body
|
|
680
|
+
body,
|
|
646
681
|
apiKey: opts.apiKey,
|
|
647
682
|
baseUrl: opts.baseUrl
|
|
648
683
|
});
|
|
@@ -675,13 +710,16 @@ function registerSearchCommands(program2) {
|
|
|
675
710
|
process.exit(1);
|
|
676
711
|
}
|
|
677
712
|
});
|
|
678
|
-
search.command("context <query>").description("Search the knowledge base \u2014 returns matching content chunks only, without AI answer generation.
|
|
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) => {
|
|
679
714
|
const opts = program2.opts();
|
|
680
715
|
try {
|
|
716
|
+
const body = { query, max_results: parseInt(cmdOpts.maxResults) };
|
|
717
|
+
if (cmdOpts.contentIds) body.content_ids = cmdOpts.contentIds;
|
|
718
|
+
if (cmdOpts.requireScopedIds) body.require_scoped_ids = true;
|
|
681
719
|
const data = await apiRequest({
|
|
682
720
|
method: "POST",
|
|
683
721
|
path: "/org/search/context",
|
|
684
|
-
body
|
|
722
|
+
body,
|
|
685
723
|
apiKey: opts.apiKey,
|
|
686
724
|
baseUrl: opts.baseUrl
|
|
687
725
|
});
|
|
@@ -691,13 +729,35 @@ function registerSearchCommands(program2) {
|
|
|
691
729
|
process.exit(1);
|
|
692
730
|
}
|
|
693
731
|
});
|
|
694
|
-
search.command("content <query>").description("Search the knowledge base \u2014 returns deduplicated content IDs and titles only.
|
|
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) => {
|
|
695
733
|
const opts = program2.opts();
|
|
696
734
|
try {
|
|
735
|
+
const body = { query, max_results: parseInt(cmdOpts.maxResults) };
|
|
736
|
+
if (cmdOpts.contentIds) body.content_ids = cmdOpts.contentIds;
|
|
737
|
+
if (cmdOpts.requireScopedIds) body.require_scoped_ids = true;
|
|
697
738
|
const data = await apiRequest({
|
|
698
739
|
method: "POST",
|
|
699
740
|
path: "/org/search/content",
|
|
700
|
-
body
|
|
741
|
+
body,
|
|
742
|
+
apiKey: opts.apiKey,
|
|
743
|
+
baseUrl: opts.baseUrl
|
|
744
|
+
});
|
|
745
|
+
outputByFormat(opts.output, data);
|
|
746
|
+
} catch (err) {
|
|
747
|
+
error(formatApiError(err));
|
|
748
|
+
process.exit(1);
|
|
749
|
+
}
|
|
750
|
+
});
|
|
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) => {
|
|
752
|
+
const opts = program2.opts();
|
|
753
|
+
try {
|
|
754
|
+
const body = { query, max_results: parseInt(cmdOpts.maxResults) };
|
|
755
|
+
if (cmdOpts.contentIds) body.content_ids = cmdOpts.contentIds;
|
|
756
|
+
if (cmdOpts.requireScopedIds) body.require_scoped_ids = true;
|
|
757
|
+
const data = await apiRequest({
|
|
758
|
+
method: "POST",
|
|
759
|
+
path: "/org/search/full",
|
|
760
|
+
body,
|
|
701
761
|
apiKey: opts.apiKey,
|
|
702
762
|
baseUrl: opts.baseUrl
|
|
703
763
|
});
|
|
@@ -767,7 +827,7 @@ async function uploadToS3(url, buffer, contentType) {
|
|
|
767
827
|
}
|
|
768
828
|
function registerIngestCommands(program2) {
|
|
769
829
|
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) => {
|
|
830
|
+
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
831
|
const opts = program2.opts();
|
|
772
832
|
if (files.length > 10) {
|
|
773
833
|
error("Maximum 10 files per upload request.");
|
|
@@ -777,7 +837,7 @@ function registerIngestCommands(program2) {
|
|
|
777
837
|
const fileData = await Promise.all(files.map(getFileMetadata));
|
|
778
838
|
const results = await apiRequest({
|
|
779
839
|
method: "POST",
|
|
780
|
-
path: "/org/
|
|
840
|
+
path: "/org/kb/upload",
|
|
781
841
|
body: { files: fileData.map((f) => f.meta) },
|
|
782
842
|
apiKey: opts.apiKey,
|
|
783
843
|
baseUrl: opts.baseUrl
|
|
@@ -813,7 +873,7 @@ function registerIngestCommands(program2) {
|
|
|
813
873
|
const { meta, buffer } = await getFileMetadata(file);
|
|
814
874
|
const results = await apiRequest({
|
|
815
875
|
method: "PUT",
|
|
816
|
-
path: `/org/
|
|
876
|
+
path: `/org/kb/nodes/${contentId}/file`,
|
|
817
877
|
body: { file: meta },
|
|
818
878
|
apiKey: opts.apiKey,
|
|
819
879
|
baseUrl: opts.baseUrl
|
|
@@ -841,29 +901,30 @@ function registerIngestCommands(program2) {
|
|
|
841
901
|
import pc6 from "picocolors";
|
|
842
902
|
function registerContentCommands(program2) {
|
|
843
903
|
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
|
|
904
|
+
content.command("list").description("List top-level files and folders in the knowledge base. Use 'kb my-files' for the same result with richer KB node output.").option("--limit <n>", "Items per page", "10").option("--offset <n>", "Pagination offset", "0").action(async (cmdOpts) => {
|
|
845
905
|
const opts = program2.opts();
|
|
846
906
|
try {
|
|
847
907
|
const data = await apiRequest({
|
|
848
|
-
path: "/org/
|
|
849
|
-
params: { limit: cmdOpts.limit, offset: cmdOpts.offset
|
|
908
|
+
path: "/org/kb/my-files",
|
|
909
|
+
params: { limit: cmdOpts.limit, offset: cmdOpts.offset },
|
|
850
910
|
apiKey: opts.apiKey,
|
|
851
911
|
baseUrl: opts.baseUrl
|
|
852
912
|
});
|
|
853
913
|
const format = opts.output || "plain";
|
|
854
|
-
const rows = Array.isArray(data) ? data : [];
|
|
914
|
+
const rows = Array.isArray(data) ? data : data.nodes ?? [];
|
|
855
915
|
output(format, {
|
|
856
916
|
json: data,
|
|
857
917
|
table: {
|
|
858
918
|
rows: rows.map((r) => ({
|
|
859
|
-
id: r.
|
|
860
|
-
|
|
861
|
-
|
|
919
|
+
id: r.node_id,
|
|
920
|
+
name: r.name,
|
|
921
|
+
type: r.type,
|
|
922
|
+
status: r.processing_status
|
|
862
923
|
})),
|
|
863
|
-
columns: ["id", "
|
|
924
|
+
columns: ["id", "name", "type", "status"]
|
|
864
925
|
},
|
|
865
926
|
plain: rows.length ? rows.map(
|
|
866
|
-
(r) => ` ${pc6.bold(String(r.
|
|
927
|
+
(r) => ` ${pc6.bold(String(r.name || "Untitled"))} ${pc6.dim(`(${r.node_id})`)} ${r.type ? pc6.dim(`[${r.type}]`) : ""}`
|
|
867
928
|
) : [" No content found."]
|
|
868
929
|
});
|
|
869
930
|
} catch (err) {
|
|
@@ -1000,7 +1061,7 @@ function registerGenerateCommands(program2) {
|
|
|
1000
1061
|
process.exit(1);
|
|
1001
1062
|
}
|
|
1002
1063
|
});
|
|
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>", "
|
|
1064
|
+
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
1065
|
const opts = program2.opts();
|
|
1005
1066
|
try {
|
|
1006
1067
|
const body = {
|
|
@@ -1023,7 +1084,7 @@ function registerGenerateCommands(program2) {
|
|
|
1023
1084
|
process.exit(1);
|
|
1024
1085
|
}
|
|
1025
1086
|
});
|
|
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) => {
|
|
1087
|
+
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) => {
|
|
1027
1088
|
const opts = program2.opts();
|
|
1028
1089
|
try {
|
|
1029
1090
|
const body = cmdOpts.promptIds ? { prompt_ids: cmdOpts.promptIds } : void 0;
|
|
@@ -1035,6 +1096,78 @@ function registerGenerateCommands(program2) {
|
|
|
1035
1096
|
process.exit(1);
|
|
1036
1097
|
}
|
|
1037
1098
|
});
|
|
1099
|
+
gen.command("job-context").description("Get the full content generation job context \u2014 all prompts with queue status (create vs update), content state, and a summary of queue counts.").action(async () => {
|
|
1100
|
+
const opts = program2.opts();
|
|
1101
|
+
try {
|
|
1102
|
+
const data = await apiRequest({ path: "/org/content-generation/job-context", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1103
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1104
|
+
} catch (err) {
|
|
1105
|
+
error(formatApiError(err));
|
|
1106
|
+
process.exit(1);
|
|
1107
|
+
}
|
|
1108
|
+
});
|
|
1109
|
+
gen.command("runs-list").description("List content generation runs for the org. Use --status to filter by run status, --active-only to show only in-progress runs.").option("--limit <n>", "Items per page", "20").option("--offset <n>", "Pagination offset", "0").option("--status <status>", "Filter by run status").option("--active-only", "Only return active (in-progress) runs").option("--start-date <date>", "Filter runs on or after this date (YYYY-MM-DD)").option("--end-date <date>", "Filter runs on or before this date (YYYY-MM-DD)").action(async (cmdOpts) => {
|
|
1110
|
+
const opts = program2.opts();
|
|
1111
|
+
try {
|
|
1112
|
+
const data = await apiRequest({
|
|
1113
|
+
path: "/org/content-generation/runs",
|
|
1114
|
+
params: {
|
|
1115
|
+
limit: cmdOpts.limit,
|
|
1116
|
+
offset: cmdOpts.offset,
|
|
1117
|
+
status: cmdOpts.status,
|
|
1118
|
+
active_only: cmdOpts.activeOnly ? "true" : void 0,
|
|
1119
|
+
start_date: cmdOpts.startDate,
|
|
1120
|
+
end_date: cmdOpts.endDate
|
|
1121
|
+
},
|
|
1122
|
+
apiKey: opts.apiKey,
|
|
1123
|
+
baseUrl: opts.baseUrl
|
|
1124
|
+
});
|
|
1125
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1126
|
+
} catch (err) {
|
|
1127
|
+
error(formatApiError(err));
|
|
1128
|
+
process.exit(1);
|
|
1129
|
+
}
|
|
1130
|
+
});
|
|
1131
|
+
gen.command("runs-get <runId>").description("Get details for a specific content generation run.").action(async (runId) => {
|
|
1132
|
+
const opts = program2.opts();
|
|
1133
|
+
try {
|
|
1134
|
+
const data = await apiRequest({ path: `/org/content-generation/runs/${runId}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1135
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1136
|
+
} catch (err) {
|
|
1137
|
+
error(formatApiError(err));
|
|
1138
|
+
process.exit(1);
|
|
1139
|
+
}
|
|
1140
|
+
});
|
|
1141
|
+
gen.command("runs-items <runId>").description("List individual prompt items within a content generation run and their per-item status.").option("--limit <n>", "Items per page", "100").option("--offset <n>", "Pagination offset", "0").action(async (runId, cmdOpts) => {
|
|
1142
|
+
const opts = program2.opts();
|
|
1143
|
+
try {
|
|
1144
|
+
const data = await apiRequest({
|
|
1145
|
+
path: `/org/content-generation/runs/${runId}/items`,
|
|
1146
|
+
params: { limit: cmdOpts.limit, offset: cmdOpts.offset },
|
|
1147
|
+
apiKey: opts.apiKey,
|
|
1148
|
+
baseUrl: opts.baseUrl
|
|
1149
|
+
});
|
|
1150
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1151
|
+
} catch (err) {
|
|
1152
|
+
error(formatApiError(err));
|
|
1153
|
+
process.exit(1);
|
|
1154
|
+
}
|
|
1155
|
+
});
|
|
1156
|
+
gen.command("runs-logs <runId>").description("List log entries for a content generation run.").option("--limit <n>", "Items per page", "100").option("--offset <n>", "Pagination offset", "0").action(async (runId, cmdOpts) => {
|
|
1157
|
+
const opts = program2.opts();
|
|
1158
|
+
try {
|
|
1159
|
+
const data = await apiRequest({
|
|
1160
|
+
path: `/org/content-generation/runs/${runId}/logs`,
|
|
1161
|
+
params: { limit: cmdOpts.limit, offset: cmdOpts.offset },
|
|
1162
|
+
apiKey: opts.apiKey,
|
|
1163
|
+
baseUrl: opts.baseUrl
|
|
1164
|
+
});
|
|
1165
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1166
|
+
} catch (err) {
|
|
1167
|
+
error(formatApiError(err));
|
|
1168
|
+
process.exit(1);
|
|
1169
|
+
}
|
|
1170
|
+
});
|
|
1038
1171
|
}
|
|
1039
1172
|
|
|
1040
1173
|
// src/commands/engine.ts
|
|
@@ -1091,7 +1224,7 @@ function registerBrandKitCommands(program2) {
|
|
|
1091
1224
|
process.exit(1);
|
|
1092
1225
|
}
|
|
1093
1226
|
});
|
|
1094
|
-
bk.command("set").description("
|
|
1227
|
+
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
1228
|
const opts = program2.opts();
|
|
1096
1229
|
try {
|
|
1097
1230
|
const body = JSON.parse(cmdOpts.data);
|
|
@@ -1109,6 +1242,24 @@ function registerBrandKitCommands(program2) {
|
|
|
1109
1242
|
process.exit(1);
|
|
1110
1243
|
}
|
|
1111
1244
|
});
|
|
1245
|
+
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) => {
|
|
1246
|
+
const opts = program2.opts();
|
|
1247
|
+
try {
|
|
1248
|
+
const body = JSON.parse(cmdOpts.data);
|
|
1249
|
+
const data = await apiRequest({
|
|
1250
|
+
method: "PATCH",
|
|
1251
|
+
path: "/org/brand-kit",
|
|
1252
|
+
body,
|
|
1253
|
+
apiKey: opts.apiKey,
|
|
1254
|
+
baseUrl: opts.baseUrl
|
|
1255
|
+
});
|
|
1256
|
+
success("Brand kit updated.");
|
|
1257
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1258
|
+
} catch (err) {
|
|
1259
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
1260
|
+
process.exit(1);
|
|
1261
|
+
}
|
|
1262
|
+
});
|
|
1112
1263
|
}
|
|
1113
1264
|
|
|
1114
1265
|
// src/commands/content-types.ts
|
|
@@ -1157,7 +1308,7 @@ function registerContentTypeCommands(program2) {
|
|
|
1157
1308
|
process.exit(1);
|
|
1158
1309
|
}
|
|
1159
1310
|
});
|
|
1160
|
-
ct.command("update <id>").description("
|
|
1311
|
+
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
1312
|
const opts = program2.opts();
|
|
1162
1313
|
try {
|
|
1163
1314
|
const body = JSON.parse(cmdOpts.data);
|
|
@@ -1175,6 +1326,24 @@ function registerContentTypeCommands(program2) {
|
|
|
1175
1326
|
process.exit(1);
|
|
1176
1327
|
}
|
|
1177
1328
|
});
|
|
1329
|
+
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) => {
|
|
1330
|
+
const opts = program2.opts();
|
|
1331
|
+
try {
|
|
1332
|
+
const body = JSON.parse(cmdOpts.data);
|
|
1333
|
+
const data = await apiRequest({
|
|
1334
|
+
method: "PATCH",
|
|
1335
|
+
path: `/org/content-types/${id}`,
|
|
1336
|
+
body,
|
|
1337
|
+
apiKey: opts.apiKey,
|
|
1338
|
+
baseUrl: opts.baseUrl
|
|
1339
|
+
});
|
|
1340
|
+
success(`Content type ${id} updated.`);
|
|
1341
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1342
|
+
} catch (err) {
|
|
1343
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
1344
|
+
process.exit(1);
|
|
1345
|
+
}
|
|
1346
|
+
});
|
|
1178
1347
|
ct.command("delete <id>").description("Delete a content type. This cannot be undone.").action(async (id) => {
|
|
1179
1348
|
const opts = program2.opts();
|
|
1180
1349
|
try {
|
|
@@ -1189,7 +1358,7 @@ function registerContentTypeCommands(program2) {
|
|
|
1189
1358
|
|
|
1190
1359
|
// src/commands/prompts.ts
|
|
1191
1360
|
function registerPromptCommands(program2) {
|
|
1192
|
-
const prompts = program2.command("prompts").description("Manage prompts (
|
|
1361
|
+
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
1362
|
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
1363
|
const opts = program2.opts();
|
|
1195
1364
|
try {
|
|
@@ -1326,19 +1495,30 @@ function registerMemberCommands(program2) {
|
|
|
1326
1495
|
});
|
|
1327
1496
|
}
|
|
1328
1497
|
|
|
1329
|
-
// src/commands/
|
|
1330
|
-
function
|
|
1331
|
-
const
|
|
1332
|
-
|
|
1498
|
+
// src/commands/credits.ts
|
|
1499
|
+
function registerCreditsCommands(program2) {
|
|
1500
|
+
const credits = program2.command("credits").description("View your organisation's credit balance. Credits are consumed by AI content generation and search operations.");
|
|
1501
|
+
credits.command("balance").description("Get the current credit balance for the organisation. Returns available credits and any spend limit configured.").action(async () => {
|
|
1502
|
+
const opts = program2.opts();
|
|
1503
|
+
try {
|
|
1504
|
+
const data = await apiRequest({ path: "/org/credits/balance", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1505
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1506
|
+
} catch (err) {
|
|
1507
|
+
error(formatApiError(err));
|
|
1508
|
+
process.exit(1);
|
|
1509
|
+
}
|
|
1510
|
+
});
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
// src/commands/questions.ts
|
|
1514
|
+
function registerQuestionsCommands(program2) {
|
|
1515
|
+
const questions = program2.command("questions").description("Manage org-scoped geo questions. These are lightweight CRUD questions distinct from prompts (which include full run history).");
|
|
1516
|
+
questions.command("list").description("List geo questions for the org.").option("--type <type>", "Filter by question type: organization | network", "organization").action(async (cmdOpts) => {
|
|
1333
1517
|
const opts = program2.opts();
|
|
1334
1518
|
try {
|
|
1335
1519
|
const data = await apiRequest({
|
|
1336
|
-
path: "/
|
|
1337
|
-
params: {
|
|
1338
|
-
limit: cmdOpts.limit,
|
|
1339
|
-
offset: cmdOpts.offset,
|
|
1340
|
-
unread_only: cmdOpts.unreadOnly ? "true" : void 0
|
|
1341
|
-
},
|
|
1520
|
+
path: "/org/questions",
|
|
1521
|
+
params: { question_type: cmdOpts.type },
|
|
1342
1522
|
apiKey: opts.apiKey,
|
|
1343
1523
|
baseUrl: opts.baseUrl
|
|
1344
1524
|
});
|
|
@@ -1348,16 +1528,363 @@ function registerNotificationCommands(program2) {
|
|
|
1348
1528
|
process.exit(1);
|
|
1349
1529
|
}
|
|
1350
1530
|
});
|
|
1351
|
-
|
|
1531
|
+
questions.command("create").description("Create a new geo question. Type must be one of: decision, consideration, awareness, evaluation.").requiredOption("--data <json>", 'JSON: { "question_text": "...", "type": "decision", "tag_ids": [] }').action(async (cmdOpts) => {
|
|
1352
1532
|
const opts = program2.opts();
|
|
1353
1533
|
try {
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1534
|
+
const body = JSON.parse(cmdOpts.data);
|
|
1535
|
+
const data = await apiRequest({ method: "POST", path: "/org/questions", body, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1536
|
+
success("Question created.");
|
|
1537
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1538
|
+
} catch (err) {
|
|
1539
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
1540
|
+
process.exit(1);
|
|
1541
|
+
}
|
|
1542
|
+
});
|
|
1543
|
+
questions.command("patch <questionId>").description("Partially update a question. Currently supports updating tag associations.").requiredOption("--data <json>", 'JSON: { "tag_ids": ["<uuid>", ...] } \u2014 pass null to clear all tags').action(async (questionId, cmdOpts) => {
|
|
1544
|
+
const opts = program2.opts();
|
|
1545
|
+
try {
|
|
1546
|
+
const body = JSON.parse(cmdOpts.data);
|
|
1547
|
+
const data = await apiRequest({ method: "PATCH", path: `/org/questions/${questionId}`, body, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1548
|
+
success(`Question ${questionId} updated.`);
|
|
1549
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1550
|
+
} catch (err) {
|
|
1551
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
1552
|
+
process.exit(1);
|
|
1553
|
+
}
|
|
1554
|
+
});
|
|
1555
|
+
questions.command("delete <questionId>").description("Delete a geo question.").action(async (questionId) => {
|
|
1556
|
+
const opts = program2.opts();
|
|
1557
|
+
try {
|
|
1558
|
+
await apiRequest({ method: "DELETE", path: `/org/questions/${questionId}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1559
|
+
success(`Question ${questionId} deleted.`);
|
|
1560
|
+
} catch (err) {
|
|
1561
|
+
error(formatApiError(err));
|
|
1562
|
+
process.exit(1);
|
|
1563
|
+
}
|
|
1564
|
+
});
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
// src/commands/kb.ts
|
|
1568
|
+
import { createHash as createHash2 } from "crypto";
|
|
1569
|
+
import { readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
1570
|
+
import { basename as basename2, resolve as resolve2 } from "path";
|
|
1571
|
+
var MIME_TYPES2 = {
|
|
1572
|
+
".pdf": "application/pdf",
|
|
1573
|
+
".txt": "text/plain",
|
|
1574
|
+
".csv": "text/csv",
|
|
1575
|
+
".doc": "application/msword",
|
|
1576
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
1577
|
+
".xls": "application/vnd.ms-excel",
|
|
1578
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
1579
|
+
".ppt": "application/vnd.ms-powerpoint",
|
|
1580
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
1581
|
+
".html": "text/html",
|
|
1582
|
+
".htm": "text/html",
|
|
1583
|
+
".md": "text/markdown",
|
|
1584
|
+
".json": "application/json",
|
|
1585
|
+
".xml": "application/xml"
|
|
1586
|
+
};
|
|
1587
|
+
function getMimeType2(filename) {
|
|
1588
|
+
const ext = filename.slice(filename.lastIndexOf(".")).toLowerCase();
|
|
1589
|
+
return MIME_TYPES2[ext] || "application/octet-stream";
|
|
1590
|
+
}
|
|
1591
|
+
async function getFileMetadata2(filePath) {
|
|
1592
|
+
const absPath = resolve2(filePath);
|
|
1593
|
+
const buffer = await readFile2(absPath);
|
|
1594
|
+
const stats = await stat2(absPath);
|
|
1595
|
+
const hash = createHash2("md5").update(buffer).digest("hex");
|
|
1596
|
+
return {
|
|
1597
|
+
meta: {
|
|
1598
|
+
filename: basename2(absPath),
|
|
1599
|
+
file_size_bytes: stats.size,
|
|
1600
|
+
content_type: getMimeType2(basename2(absPath)),
|
|
1601
|
+
content_hash_md5: hash
|
|
1602
|
+
},
|
|
1603
|
+
buffer
|
|
1604
|
+
};
|
|
1605
|
+
}
|
|
1606
|
+
async function uploadToS32(url, buffer, contentType) {
|
|
1607
|
+
const res = await fetch(url, {
|
|
1608
|
+
method: "PUT",
|
|
1609
|
+
headers: { "Content-Type": contentType },
|
|
1610
|
+
body: buffer
|
|
1611
|
+
});
|
|
1612
|
+
if (!res.ok) throw new Error(`S3 upload failed: ${res.status} ${res.statusText}`);
|
|
1613
|
+
}
|
|
1614
|
+
function registerKBCommands(program2) {
|
|
1615
|
+
const kb = program2.command("kb").description("Manage the knowledge base. Browse nodes, upload files, create folders, create raw content, and manage the KB tree.");
|
|
1616
|
+
kb.command("root").description("Get the root KB node for the org.").action(async () => {
|
|
1617
|
+
const opts = program2.opts();
|
|
1618
|
+
try {
|
|
1619
|
+
const data = await apiRequest({ path: "/org/kb/root", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1620
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1621
|
+
} catch (err) {
|
|
1622
|
+
error(formatApiError(err));
|
|
1623
|
+
process.exit(1);
|
|
1624
|
+
}
|
|
1625
|
+
});
|
|
1626
|
+
kb.command("my-files").description("List top-level files and folders in the knowledge base.").option("--limit <n>", "Items per page", "50").option("--offset <n>", "Pagination offset", "0").action(async (cmdOpts) => {
|
|
1627
|
+
const opts = program2.opts();
|
|
1628
|
+
try {
|
|
1629
|
+
const data = await apiRequest({
|
|
1630
|
+
path: "/org/kb/my-files",
|
|
1631
|
+
params: { limit: cmdOpts.limit, offset: cmdOpts.offset },
|
|
1632
|
+
apiKey: opts.apiKey,
|
|
1633
|
+
baseUrl: opts.baseUrl
|
|
1634
|
+
});
|
|
1635
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1636
|
+
} catch (err) {
|
|
1637
|
+
error(formatApiError(err));
|
|
1638
|
+
process.exit(1);
|
|
1639
|
+
}
|
|
1640
|
+
});
|
|
1641
|
+
kb.command("find").description("Search KB nodes by name.").requiredOption("--query <q>", "Name search query").option("--limit <n>", "Items per page", "20").option("--offset <n>", "Pagination offset", "0").action(async (cmdOpts) => {
|
|
1642
|
+
const opts = program2.opts();
|
|
1643
|
+
try {
|
|
1644
|
+
const data = await apiRequest({
|
|
1645
|
+
path: "/org/kb/find",
|
|
1646
|
+
params: { q: cmdOpts.query, limit: cmdOpts.limit, offset: cmdOpts.offset },
|
|
1647
|
+
apiKey: opts.apiKey,
|
|
1648
|
+
baseUrl: opts.baseUrl
|
|
1649
|
+
});
|
|
1650
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1651
|
+
} catch (err) {
|
|
1652
|
+
error(formatApiError(err));
|
|
1653
|
+
process.exit(1);
|
|
1654
|
+
}
|
|
1655
|
+
});
|
|
1656
|
+
kb.command("sync-status").description("Get the vector sync status for the org's knowledge base.").action(async () => {
|
|
1657
|
+
const opts = program2.opts();
|
|
1658
|
+
try {
|
|
1659
|
+
const data = await apiRequest({ path: "/org/kb/sync-status", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1660
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1661
|
+
} catch (err) {
|
|
1662
|
+
error(formatApiError(err));
|
|
1663
|
+
process.exit(1);
|
|
1664
|
+
}
|
|
1665
|
+
});
|
|
1666
|
+
kb.command("get <id>").description("Get a KB node by ID.").action(async (id) => {
|
|
1667
|
+
const opts = program2.opts();
|
|
1668
|
+
try {
|
|
1669
|
+
const data = await apiRequest({ path: `/org/kb/nodes/${id}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1670
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1671
|
+
} catch (err) {
|
|
1672
|
+
error(formatApiError(err));
|
|
1673
|
+
process.exit(1);
|
|
1674
|
+
}
|
|
1675
|
+
});
|
|
1676
|
+
kb.command("children <id>").description("List children of a KB folder node.").option("--limit <n>", "Items per page", "50").option("--offset <n>", "Pagination offset", "0").action(async (id, cmdOpts) => {
|
|
1677
|
+
const opts = program2.opts();
|
|
1678
|
+
try {
|
|
1679
|
+
const data = await apiRequest({
|
|
1680
|
+
path: `/org/kb/nodes/${id}/children`,
|
|
1681
|
+
params: { limit: cmdOpts.limit, offset: cmdOpts.offset },
|
|
1682
|
+
apiKey: opts.apiKey,
|
|
1683
|
+
baseUrl: opts.baseUrl
|
|
1684
|
+
});
|
|
1685
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1686
|
+
} catch (err) {
|
|
1687
|
+
error(formatApiError(err));
|
|
1688
|
+
process.exit(1);
|
|
1689
|
+
}
|
|
1690
|
+
});
|
|
1691
|
+
kb.command("ancestors <id>").description("Get the ancestor chain (breadcrumb) for a KB node.").action(async (id) => {
|
|
1692
|
+
const opts = program2.opts();
|
|
1693
|
+
try {
|
|
1694
|
+
const data = await apiRequest({ path: `/org/kb/nodes/${id}/ancestors`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1695
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1696
|
+
} catch (err) {
|
|
1697
|
+
error(formatApiError(err));
|
|
1698
|
+
process.exit(1);
|
|
1699
|
+
}
|
|
1700
|
+
});
|
|
1701
|
+
kb.command("get-content <id>").description("Get the content detail for a KB content node.").option("--version <version>", "Specific version to retrieve").action(async (id, cmdOpts) => {
|
|
1702
|
+
const opts = program2.opts();
|
|
1703
|
+
try {
|
|
1704
|
+
const data = await apiRequest({
|
|
1705
|
+
path: `/org/kb/nodes/${id}/content`,
|
|
1706
|
+
params: { version: cmdOpts.version },
|
|
1707
|
+
apiKey: opts.apiKey,
|
|
1708
|
+
baseUrl: opts.baseUrl
|
|
1709
|
+
});
|
|
1710
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1711
|
+
} catch (err) {
|
|
1712
|
+
error(formatApiError(err));
|
|
1713
|
+
process.exit(1);
|
|
1714
|
+
}
|
|
1715
|
+
});
|
|
1716
|
+
kb.command("download-url <id>").description("Get a presigned S3 download URL for a KB file node.").option("--version <version>", "Specific version to download").action(async (id, cmdOpts) => {
|
|
1717
|
+
const opts = program2.opts();
|
|
1718
|
+
try {
|
|
1719
|
+
const data = await apiRequest({
|
|
1720
|
+
path: `/org/kb/nodes/${id}/download-url`,
|
|
1721
|
+
params: { version: cmdOpts.version },
|
|
1357
1722
|
apiKey: opts.apiKey,
|
|
1358
1723
|
baseUrl: opts.baseUrl
|
|
1359
1724
|
});
|
|
1360
|
-
|
|
1725
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1726
|
+
} catch (err) {
|
|
1727
|
+
error(formatApiError(err));
|
|
1728
|
+
process.exit(1);
|
|
1729
|
+
}
|
|
1730
|
+
});
|
|
1731
|
+
kb.command("create-folder").description("Create a new folder in the knowledge base.").requiredOption("--name <name>", "Folder name").option("--parent-id <id>", "Parent folder node ID (omit to create at root)").action(async (cmdOpts) => {
|
|
1732
|
+
const opts = program2.opts();
|
|
1733
|
+
try {
|
|
1734
|
+
const body = { name: cmdOpts.name };
|
|
1735
|
+
if (cmdOpts.parentId) body.parent_node_id = cmdOpts.parentId;
|
|
1736
|
+
const data = await apiRequest({ method: "POST", path: "/org/kb/folders", body, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1737
|
+
success(`Folder "${cmdOpts.name}" created.`);
|
|
1738
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1739
|
+
} catch (err) {
|
|
1740
|
+
error(formatApiError(err));
|
|
1741
|
+
process.exit(1);
|
|
1742
|
+
}
|
|
1743
|
+
});
|
|
1744
|
+
kb.command("rename <id>").description("Rename a KB node.").requiredOption("--name <name>", "New name").action(async (id, cmdOpts) => {
|
|
1745
|
+
const opts = program2.opts();
|
|
1746
|
+
try {
|
|
1747
|
+
const data = await apiRequest({ method: "PATCH", path: `/org/kb/nodes/${id}/rename`, body: { name: cmdOpts.name }, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1748
|
+
success(`Node ${id} renamed to "${cmdOpts.name}".`);
|
|
1749
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1750
|
+
} catch (err) {
|
|
1751
|
+
error(formatApiError(err));
|
|
1752
|
+
process.exit(1);
|
|
1753
|
+
}
|
|
1754
|
+
});
|
|
1755
|
+
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) => {
|
|
1756
|
+
const opts = program2.opts();
|
|
1757
|
+
try {
|
|
1758
|
+
const data = await apiRequest({ method: "PATCH", path: `/org/kb/nodes/${id}/move`, body: { parent_node_id: cmdOpts.parentId }, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1759
|
+
success(`Node ${id} moved.`);
|
|
1760
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1761
|
+
} catch (err) {
|
|
1762
|
+
error(formatApiError(err));
|
|
1763
|
+
process.exit(1);
|
|
1764
|
+
}
|
|
1765
|
+
});
|
|
1766
|
+
kb.command("delete <id>").description("Delete a KB node (soft delete).").action(async (id) => {
|
|
1767
|
+
const opts = program2.opts();
|
|
1768
|
+
try {
|
|
1769
|
+
await apiRequest({ method: "DELETE", path: `/org/kb/nodes/${id}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1770
|
+
success(`Node ${id} deleted.`);
|
|
1771
|
+
} catch (err) {
|
|
1772
|
+
error(formatApiError(err));
|
|
1773
|
+
process.exit(1);
|
|
1774
|
+
}
|
|
1775
|
+
});
|
|
1776
|
+
kb.command("create-raw").description("Create a raw (text/markdown) content item in the knowledge base.").requiredOption("--data <json>", 'JSON: { "name": "My doc", "text": "# Hello", "kb_folder_node_id": "<uuid>" }').action(async (cmdOpts) => {
|
|
1777
|
+
const opts = program2.opts();
|
|
1778
|
+
try {
|
|
1779
|
+
const body = JSON.parse(cmdOpts.data);
|
|
1780
|
+
const data = await apiRequest({ method: "POST", path: "/org/kb/raw", body, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1781
|
+
success("Raw content node created.");
|
|
1782
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1783
|
+
} catch (err) {
|
|
1784
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
1785
|
+
process.exit(1);
|
|
1786
|
+
}
|
|
1787
|
+
});
|
|
1788
|
+
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) => {
|
|
1789
|
+
const opts = program2.opts();
|
|
1790
|
+
try {
|
|
1791
|
+
const body = JSON.parse(cmdOpts.data);
|
|
1792
|
+
const data = await apiRequest({ method: "PUT", path: `/org/kb/nodes/${id}/raw`, body, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1793
|
+
success(`Node ${id} content replaced.`);
|
|
1794
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1795
|
+
} catch (err) {
|
|
1796
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
1797
|
+
process.exit(1);
|
|
1798
|
+
}
|
|
1799
|
+
});
|
|
1800
|
+
kb.command("patch-raw <id>").description("Partially update the text content of a raw KB node.").requiredOption("--data <json>", 'JSON: { "text": "Updated text", "name": "New name" }').action(async (id, cmdOpts) => {
|
|
1801
|
+
const opts = program2.opts();
|
|
1802
|
+
try {
|
|
1803
|
+
const body = JSON.parse(cmdOpts.data);
|
|
1804
|
+
const data = await apiRequest({ method: "PATCH", path: `/org/kb/nodes/${id}/raw`, body, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1805
|
+
success(`Node ${id} content patched.`);
|
|
1806
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1807
|
+
} catch (err) {
|
|
1808
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
1809
|
+
process.exit(1);
|
|
1810
|
+
}
|
|
1811
|
+
});
|
|
1812
|
+
kb.command("upload <files...>").description("Upload files to the knowledge base (up to 10). Files are hashed, uploaded to S3, then parsed and embedded by a background worker.").option("--folder-id <id>", "Parent folder node ID to place files in (omit for root)").action(async (files, cmdOpts) => {
|
|
1813
|
+
const opts = program2.opts();
|
|
1814
|
+
if (files.length > 10) {
|
|
1815
|
+
error("Maximum 10 files per upload request.");
|
|
1816
|
+
process.exit(1);
|
|
1817
|
+
}
|
|
1818
|
+
try {
|
|
1819
|
+
const fileData = await Promise.all(files.map(getFileMetadata2));
|
|
1820
|
+
const body = { files: fileData.map((f) => f.meta) };
|
|
1821
|
+
if (cmdOpts.folderId) body.kb_folder_node_id = cmdOpts.folderId;
|
|
1822
|
+
const results = await apiRequest({
|
|
1823
|
+
method: "POST",
|
|
1824
|
+
path: "/org/kb/upload",
|
|
1825
|
+
body,
|
|
1826
|
+
apiKey: opts.apiKey,
|
|
1827
|
+
baseUrl: opts.baseUrl
|
|
1828
|
+
});
|
|
1829
|
+
const items = Array.isArray(results) ? results : [];
|
|
1830
|
+
let uploaded = 0;
|
|
1831
|
+
for (const item of items) {
|
|
1832
|
+
if (item.status === "upload_pending" && item.upload_url) {
|
|
1833
|
+
const match = fileData.find((f) => f.meta.filename === item.filename);
|
|
1834
|
+
if (match) {
|
|
1835
|
+
await uploadToS32(item.upload_url, match.buffer, match.meta.content_type);
|
|
1836
|
+
uploaded++;
|
|
1837
|
+
success(`Uploaded ${item.filename} (content_id: ${item.content_id})`);
|
|
1838
|
+
}
|
|
1839
|
+
} else {
|
|
1840
|
+
warn(`Skipped ${item.filename}: ${item.status}${item.message ? ` \u2014 ${item.message}` : ""}`);
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
if (uploaded > 0) {
|
|
1844
|
+
success(`${uploaded} file(s) uploaded. Background processing will parse, chunk, and embed them.`);
|
|
1845
|
+
}
|
|
1846
|
+
if (opts.output === "json") console.log(JSON.stringify(results, null, 2));
|
|
1847
|
+
} catch (err) {
|
|
1848
|
+
error(formatApiError(err));
|
|
1849
|
+
process.exit(1);
|
|
1850
|
+
}
|
|
1851
|
+
});
|
|
1852
|
+
kb.command("update-file <id> <file>").description("Replace the file on an existing KB file node with a new version.").action(async (id, file) => {
|
|
1853
|
+
const opts = program2.opts();
|
|
1854
|
+
try {
|
|
1855
|
+
const { meta, buffer } = await getFileMetadata2(file);
|
|
1856
|
+
const results = await apiRequest({
|
|
1857
|
+
method: "PUT",
|
|
1858
|
+
path: `/org/kb/nodes/${id}/file`,
|
|
1859
|
+
body: { file: meta },
|
|
1860
|
+
apiKey: opts.apiKey,
|
|
1861
|
+
baseUrl: opts.baseUrl
|
|
1862
|
+
});
|
|
1863
|
+
const items = Array.isArray(results) ? results : [];
|
|
1864
|
+
for (const item of items) {
|
|
1865
|
+
if (item.status === "upload_pending" && item.upload_url) {
|
|
1866
|
+
await uploadToS32(item.upload_url, buffer, meta.content_type);
|
|
1867
|
+
success(`Uploaded ${meta.filename} for node ${id}. Background re-processing started.`);
|
|
1868
|
+
} else {
|
|
1869
|
+
warn(`Skipped: ${item.status}${item.message ? ` \u2014 ${item.message}` : ""}`);
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
if (opts.output === "json") console.log(JSON.stringify(results, null, 2));
|
|
1873
|
+
} catch (err) {
|
|
1874
|
+
error(formatApiError(err));
|
|
1875
|
+
process.exit(1);
|
|
1876
|
+
}
|
|
1877
|
+
});
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
// src/commands/permissions.ts
|
|
1881
|
+
function registerPermissionsCommands(program2) {
|
|
1882
|
+
const perms = program2.command("permissions").description("View available role permissions for the organization.");
|
|
1883
|
+
perms.command("list").description("List all available permission keys with their names, descriptions, and categories. Useful for building role management UIs.").action(async () => {
|
|
1884
|
+
const opts = program2.opts();
|
|
1885
|
+
try {
|
|
1886
|
+
const data = await apiRequest({ path: "/org/permissions", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1887
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1361
1888
|
} catch (err) {
|
|
1362
1889
|
error(formatApiError(err));
|
|
1363
1890
|
process.exit(1);
|
|
@@ -1422,7 +1949,10 @@ registerContentTypeCommands(program);
|
|
|
1422
1949
|
registerPromptCommands(program);
|
|
1423
1950
|
registerRunConfigCommands(program);
|
|
1424
1951
|
registerMemberCommands(program);
|
|
1425
|
-
|
|
1952
|
+
registerCreditsCommands(program);
|
|
1953
|
+
registerQuestionsCommands(program);
|
|
1954
|
+
registerKBCommands(program);
|
|
1955
|
+
registerPermissionsCommands(program);
|
|
1426
1956
|
registerUpdateCommand(program);
|
|
1427
1957
|
async function main() {
|
|
1428
1958
|
const quiet = process.argv.includes("--quiet") || process.argv.includes("--output") && process.argv[process.argv.indexOf("--output") + 1] === "json";
|