@senso-ai/cli 0.5.0 → 0.6.1
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 +515 -34
- 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
|
|
@@ -716,6 +748,25 @@ function registerSearchCommands(program2) {
|
|
|
716
748
|
process.exit(1);
|
|
717
749
|
}
|
|
718
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,
|
|
761
|
+
apiKey: opts.apiKey,
|
|
762
|
+
baseUrl: opts.baseUrl
|
|
763
|
+
});
|
|
764
|
+
outputByFormat(opts.output, data);
|
|
765
|
+
} catch (err) {
|
|
766
|
+
error(formatApiError(err));
|
|
767
|
+
process.exit(1);
|
|
768
|
+
}
|
|
769
|
+
});
|
|
719
770
|
}
|
|
720
771
|
function outputByFormat(format, data) {
|
|
721
772
|
if (format === "json") {
|
|
@@ -786,7 +837,7 @@ function registerIngestCommands(program2) {
|
|
|
786
837
|
const fileData = await Promise.all(files.map(getFileMetadata));
|
|
787
838
|
const results = await apiRequest({
|
|
788
839
|
method: "POST",
|
|
789
|
-
path: "/org/
|
|
840
|
+
path: "/org/kb/upload",
|
|
790
841
|
body: { files: fileData.map((f) => f.meta) },
|
|
791
842
|
apiKey: opts.apiKey,
|
|
792
843
|
baseUrl: opts.baseUrl
|
|
@@ -796,10 +847,16 @@ function registerIngestCommands(program2) {
|
|
|
796
847
|
for (const item of items) {
|
|
797
848
|
if (item.status === "upload_pending" && item.upload_url) {
|
|
798
849
|
const match = fileData.find((f) => f.meta.filename === item.filename);
|
|
799
|
-
if (match) {
|
|
850
|
+
if (!match) {
|
|
851
|
+
error(`Could not match server filename "${item.filename}" to a local file \u2014 skipping upload.`);
|
|
852
|
+
continue;
|
|
853
|
+
}
|
|
854
|
+
try {
|
|
800
855
|
await uploadToS3(item.upload_url, match.buffer, match.meta.content_type);
|
|
801
856
|
uploaded++;
|
|
802
857
|
success(`Uploaded ${item.filename} (content_id: ${item.content_id})`);
|
|
858
|
+
} catch (uploadErr) {
|
|
859
|
+
error(`S3 upload failed for ${item.filename}: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`);
|
|
803
860
|
}
|
|
804
861
|
} else {
|
|
805
862
|
warn(`Skipped ${item.filename}: ${item.status}${item.message ? ` \u2014 ${item.message}` : ""}`);
|
|
@@ -822,7 +879,7 @@ function registerIngestCommands(program2) {
|
|
|
822
879
|
const { meta, buffer } = await getFileMetadata(file);
|
|
823
880
|
const results = await apiRequest({
|
|
824
881
|
method: "PUT",
|
|
825
|
-
path: `/org/
|
|
882
|
+
path: `/org/kb/nodes/${contentId}/file`,
|
|
826
883
|
body: { file: meta },
|
|
827
884
|
apiKey: opts.apiKey,
|
|
828
885
|
baseUrl: opts.baseUrl
|
|
@@ -850,29 +907,30 @@ function registerIngestCommands(program2) {
|
|
|
850
907
|
import pc6 from "picocolors";
|
|
851
908
|
function registerContentCommands(program2) {
|
|
852
909
|
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.");
|
|
853
|
-
content.command("list").description("List
|
|
910
|
+
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) => {
|
|
854
911
|
const opts = program2.opts();
|
|
855
912
|
try {
|
|
856
913
|
const data = await apiRequest({
|
|
857
|
-
path: "/org/
|
|
858
|
-
params: { limit: cmdOpts.limit, offset: cmdOpts.offset
|
|
914
|
+
path: "/org/kb/my-files",
|
|
915
|
+
params: { limit: cmdOpts.limit, offset: cmdOpts.offset },
|
|
859
916
|
apiKey: opts.apiKey,
|
|
860
917
|
baseUrl: opts.baseUrl
|
|
861
918
|
});
|
|
862
919
|
const format = opts.output || "plain";
|
|
863
|
-
const rows = Array.isArray(data) ? data : [];
|
|
920
|
+
const rows = Array.isArray(data) ? data : data.nodes ?? [];
|
|
864
921
|
output(format, {
|
|
865
922
|
json: data,
|
|
866
923
|
table: {
|
|
867
924
|
rows: rows.map((r) => ({
|
|
868
|
-
id: r.
|
|
869
|
-
|
|
870
|
-
|
|
925
|
+
id: r.node_id,
|
|
926
|
+
name: r.name,
|
|
927
|
+
type: r.type,
|
|
928
|
+
status: r.processing_status
|
|
871
929
|
})),
|
|
872
|
-
columns: ["id", "
|
|
930
|
+
columns: ["id", "name", "type", "status"]
|
|
873
931
|
},
|
|
874
932
|
plain: rows.length ? rows.map(
|
|
875
|
-
(r) => ` ${pc6.bold(String(r.
|
|
933
|
+
(r) => ` ${pc6.bold(String(r.name || "Untitled"))} ${pc6.dim(`(${r.node_id})`)} ${r.type ? pc6.dim(`[${r.type}]`) : ""}`
|
|
876
934
|
) : [" No content found."]
|
|
877
935
|
});
|
|
878
936
|
} catch (err) {
|
|
@@ -1032,7 +1090,7 @@ function registerGenerateCommands(program2) {
|
|
|
1032
1090
|
process.exit(1);
|
|
1033
1091
|
}
|
|
1034
1092
|
});
|
|
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 '
|
|
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) => {
|
|
1036
1094
|
const opts = program2.opts();
|
|
1037
1095
|
try {
|
|
1038
1096
|
const body = cmdOpts.promptIds ? { prompt_ids: cmdOpts.promptIds } : void 0;
|
|
@@ -1044,6 +1102,78 @@ function registerGenerateCommands(program2) {
|
|
|
1044
1102
|
process.exit(1);
|
|
1045
1103
|
}
|
|
1046
1104
|
});
|
|
1105
|
+
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 () => {
|
|
1106
|
+
const opts = program2.opts();
|
|
1107
|
+
try {
|
|
1108
|
+
const data = await apiRequest({ path: "/org/content-generation/job-context", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1109
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1110
|
+
} catch (err) {
|
|
1111
|
+
error(formatApiError(err));
|
|
1112
|
+
process.exit(1);
|
|
1113
|
+
}
|
|
1114
|
+
});
|
|
1115
|
+
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) => {
|
|
1116
|
+
const opts = program2.opts();
|
|
1117
|
+
try {
|
|
1118
|
+
const data = await apiRequest({
|
|
1119
|
+
path: "/org/content-generation/runs",
|
|
1120
|
+
params: {
|
|
1121
|
+
limit: cmdOpts.limit,
|
|
1122
|
+
offset: cmdOpts.offset,
|
|
1123
|
+
status: cmdOpts.status,
|
|
1124
|
+
active_only: cmdOpts.activeOnly ? "true" : void 0,
|
|
1125
|
+
start_date: cmdOpts.startDate,
|
|
1126
|
+
end_date: cmdOpts.endDate
|
|
1127
|
+
},
|
|
1128
|
+
apiKey: opts.apiKey,
|
|
1129
|
+
baseUrl: opts.baseUrl
|
|
1130
|
+
});
|
|
1131
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1132
|
+
} catch (err) {
|
|
1133
|
+
error(formatApiError(err));
|
|
1134
|
+
process.exit(1);
|
|
1135
|
+
}
|
|
1136
|
+
});
|
|
1137
|
+
gen.command("runs-get <runId>").description("Get details for a specific content generation run.").action(async (runId) => {
|
|
1138
|
+
const opts = program2.opts();
|
|
1139
|
+
try {
|
|
1140
|
+
const data = await apiRequest({ path: `/org/content-generation/runs/${runId}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1141
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1142
|
+
} catch (err) {
|
|
1143
|
+
error(formatApiError(err));
|
|
1144
|
+
process.exit(1);
|
|
1145
|
+
}
|
|
1146
|
+
});
|
|
1147
|
+
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) => {
|
|
1148
|
+
const opts = program2.opts();
|
|
1149
|
+
try {
|
|
1150
|
+
const data = await apiRequest({
|
|
1151
|
+
path: `/org/content-generation/runs/${runId}/items`,
|
|
1152
|
+
params: { limit: cmdOpts.limit, offset: cmdOpts.offset },
|
|
1153
|
+
apiKey: opts.apiKey,
|
|
1154
|
+
baseUrl: opts.baseUrl
|
|
1155
|
+
});
|
|
1156
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1157
|
+
} catch (err) {
|
|
1158
|
+
error(formatApiError(err));
|
|
1159
|
+
process.exit(1);
|
|
1160
|
+
}
|
|
1161
|
+
});
|
|
1162
|
+
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) => {
|
|
1163
|
+
const opts = program2.opts();
|
|
1164
|
+
try {
|
|
1165
|
+
const data = await apiRequest({
|
|
1166
|
+
path: `/org/content-generation/runs/${runId}/logs`,
|
|
1167
|
+
params: { limit: cmdOpts.limit, offset: cmdOpts.offset },
|
|
1168
|
+
apiKey: opts.apiKey,
|
|
1169
|
+
baseUrl: opts.baseUrl
|
|
1170
|
+
});
|
|
1171
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1172
|
+
} catch (err) {
|
|
1173
|
+
error(formatApiError(err));
|
|
1174
|
+
process.exit(1);
|
|
1175
|
+
}
|
|
1176
|
+
});
|
|
1047
1177
|
}
|
|
1048
1178
|
|
|
1049
1179
|
// src/commands/engine.ts
|
|
@@ -1371,19 +1501,30 @@ function registerMemberCommands(program2) {
|
|
|
1371
1501
|
});
|
|
1372
1502
|
}
|
|
1373
1503
|
|
|
1374
|
-
// src/commands/
|
|
1375
|
-
function
|
|
1376
|
-
const
|
|
1377
|
-
|
|
1504
|
+
// src/commands/credits.ts
|
|
1505
|
+
function registerCreditsCommands(program2) {
|
|
1506
|
+
const credits = program2.command("credits").description("View your organisation's credit balance. Credits are consumed by AI content generation and search operations.");
|
|
1507
|
+
credits.command("balance").description("Get the current credit balance for the organisation. Returns available credits and any spend limit configured.").action(async () => {
|
|
1508
|
+
const opts = program2.opts();
|
|
1509
|
+
try {
|
|
1510
|
+
const data = await apiRequest({ path: "/org/credits/balance", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1511
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1512
|
+
} catch (err) {
|
|
1513
|
+
error(formatApiError(err));
|
|
1514
|
+
process.exit(1);
|
|
1515
|
+
}
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
// src/commands/questions.ts
|
|
1520
|
+
function registerQuestionsCommands(program2) {
|
|
1521
|
+
const questions = program2.command("questions").description("Manage org-scoped geo questions. These are lightweight CRUD questions distinct from prompts (which include full run history).");
|
|
1522
|
+
questions.command("list").description("List geo questions for the org.").option("--type <type>", "Filter by question type: organization | network", "organization").action(async (cmdOpts) => {
|
|
1378
1523
|
const opts = program2.opts();
|
|
1379
1524
|
try {
|
|
1380
1525
|
const data = await apiRequest({
|
|
1381
|
-
path: "/
|
|
1382
|
-
params: {
|
|
1383
|
-
limit: cmdOpts.limit,
|
|
1384
|
-
offset: cmdOpts.offset,
|
|
1385
|
-
unread_only: cmdOpts.unreadOnly ? "true" : void 0
|
|
1386
|
-
},
|
|
1526
|
+
path: "/org/questions",
|
|
1527
|
+
params: { question_type: cmdOpts.type },
|
|
1387
1528
|
apiKey: opts.apiKey,
|
|
1388
1529
|
baseUrl: opts.baseUrl
|
|
1389
1530
|
});
|
|
@@ -1393,16 +1534,354 @@ function registerNotificationCommands(program2) {
|
|
|
1393
1534
|
process.exit(1);
|
|
1394
1535
|
}
|
|
1395
1536
|
});
|
|
1396
|
-
|
|
1537
|
+
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) => {
|
|
1397
1538
|
const opts = program2.opts();
|
|
1398
1539
|
try {
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1540
|
+
const body = JSON.parse(cmdOpts.data);
|
|
1541
|
+
const data = await apiRequest({ method: "POST", path: "/org/questions", body, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1542
|
+
success("Question created.");
|
|
1543
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1544
|
+
} catch (err) {
|
|
1545
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
1546
|
+
process.exit(1);
|
|
1547
|
+
}
|
|
1548
|
+
});
|
|
1549
|
+
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) => {
|
|
1550
|
+
const opts = program2.opts();
|
|
1551
|
+
try {
|
|
1552
|
+
const body = JSON.parse(cmdOpts.data);
|
|
1553
|
+
const data = await apiRequest({ method: "PATCH", path: `/org/questions/${questionId}`, body, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1554
|
+
success(`Question ${questionId} updated.`);
|
|
1555
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1556
|
+
} catch (err) {
|
|
1557
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
1558
|
+
process.exit(1);
|
|
1559
|
+
}
|
|
1560
|
+
});
|
|
1561
|
+
questions.command("delete <questionId>").description("Delete a geo question.").action(async (questionId) => {
|
|
1562
|
+
const opts = program2.opts();
|
|
1563
|
+
try {
|
|
1564
|
+
await apiRequest({ method: "DELETE", path: `/org/questions/${questionId}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1565
|
+
success(`Question ${questionId} deleted.`);
|
|
1566
|
+
} catch (err) {
|
|
1567
|
+
error(formatApiError(err));
|
|
1568
|
+
process.exit(1);
|
|
1569
|
+
}
|
|
1570
|
+
});
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
// src/commands/kb.ts
|
|
1574
|
+
import { createHash as createHash2 } from "crypto";
|
|
1575
|
+
import { readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
1576
|
+
import { basename as basename2, resolve as resolve2 } from "path";
|
|
1577
|
+
var MIME_TYPES2 = {
|
|
1578
|
+
".pdf": "application/pdf",
|
|
1579
|
+
".txt": "text/plain",
|
|
1580
|
+
".csv": "text/csv",
|
|
1581
|
+
".doc": "application/msword",
|
|
1582
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
1583
|
+
".xls": "application/vnd.ms-excel",
|
|
1584
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
1585
|
+
".ppt": "application/vnd.ms-powerpoint",
|
|
1586
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
1587
|
+
".html": "text/html",
|
|
1588
|
+
".htm": "text/html",
|
|
1589
|
+
".md": "text/markdown",
|
|
1590
|
+
".json": "application/json",
|
|
1591
|
+
".xml": "application/xml"
|
|
1592
|
+
};
|
|
1593
|
+
function getMimeType2(filename) {
|
|
1594
|
+
const ext = filename.slice(filename.lastIndexOf(".")).toLowerCase();
|
|
1595
|
+
return MIME_TYPES2[ext] || "application/octet-stream";
|
|
1596
|
+
}
|
|
1597
|
+
async function getFileMetadata2(filePath) {
|
|
1598
|
+
const absPath = resolve2(filePath);
|
|
1599
|
+
const buffer = await readFile2(absPath);
|
|
1600
|
+
const stats = await stat2(absPath);
|
|
1601
|
+
const hash = createHash2("md5").update(buffer).digest("hex");
|
|
1602
|
+
return {
|
|
1603
|
+
meta: {
|
|
1604
|
+
filename: basename2(absPath),
|
|
1605
|
+
file_size_bytes: stats.size,
|
|
1606
|
+
content_type: getMimeType2(basename2(absPath)),
|
|
1607
|
+
content_hash_md5: hash
|
|
1608
|
+
},
|
|
1609
|
+
buffer
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1612
|
+
async function uploadToS32(url, buffer, contentType) {
|
|
1613
|
+
const res = await fetch(url, {
|
|
1614
|
+
method: "PUT",
|
|
1615
|
+
headers: { "Content-Type": contentType },
|
|
1616
|
+
body: buffer
|
|
1617
|
+
});
|
|
1618
|
+
if (!res.ok) throw new Error(`S3 upload failed: ${res.status} ${res.statusText}`);
|
|
1619
|
+
}
|
|
1620
|
+
function registerKBCommands(program2) {
|
|
1621
|
+
const kb = program2.command("kb").description("Manage the knowledge base. Browse nodes, upload files, create folders, create raw content, and manage the KB tree.");
|
|
1622
|
+
kb.command("root").description("Get the root KB node for the org.").action(async () => {
|
|
1623
|
+
const opts = program2.opts();
|
|
1624
|
+
try {
|
|
1625
|
+
const data = await apiRequest({ path: "/org/kb/root", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1626
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1627
|
+
} catch (err) {
|
|
1628
|
+
error(formatApiError(err));
|
|
1629
|
+
process.exit(1);
|
|
1630
|
+
}
|
|
1631
|
+
});
|
|
1632
|
+
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) => {
|
|
1633
|
+
const opts = program2.opts();
|
|
1634
|
+
try {
|
|
1635
|
+
const data = await apiRequest({
|
|
1636
|
+
path: "/org/kb/my-files",
|
|
1637
|
+
params: { limit: cmdOpts.limit, offset: cmdOpts.offset },
|
|
1638
|
+
apiKey: opts.apiKey,
|
|
1639
|
+
baseUrl: opts.baseUrl
|
|
1640
|
+
});
|
|
1641
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1642
|
+
} catch (err) {
|
|
1643
|
+
error(formatApiError(err));
|
|
1644
|
+
process.exit(1);
|
|
1645
|
+
}
|
|
1646
|
+
});
|
|
1647
|
+
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) => {
|
|
1648
|
+
const opts = program2.opts();
|
|
1649
|
+
try {
|
|
1650
|
+
const data = await apiRequest({
|
|
1651
|
+
path: "/org/kb/find",
|
|
1652
|
+
params: { q: cmdOpts.query, limit: cmdOpts.limit, offset: cmdOpts.offset },
|
|
1653
|
+
apiKey: opts.apiKey,
|
|
1654
|
+
baseUrl: opts.baseUrl
|
|
1655
|
+
});
|
|
1656
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1657
|
+
} catch (err) {
|
|
1658
|
+
error(formatApiError(err));
|
|
1659
|
+
process.exit(1);
|
|
1660
|
+
}
|
|
1661
|
+
});
|
|
1662
|
+
kb.command("sync-status").description("Get the vector sync status for the org's knowledge base.").action(async () => {
|
|
1663
|
+
const opts = program2.opts();
|
|
1664
|
+
try {
|
|
1665
|
+
const data = await apiRequest({ path: "/org/kb/sync-status", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1666
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1667
|
+
} catch (err) {
|
|
1668
|
+
error(formatApiError(err));
|
|
1669
|
+
process.exit(1);
|
|
1670
|
+
}
|
|
1671
|
+
});
|
|
1672
|
+
kb.command("get <id>").description("Get a KB node by ID.").action(async (id) => {
|
|
1673
|
+
const opts = program2.opts();
|
|
1674
|
+
try {
|
|
1675
|
+
const data = await apiRequest({ path: `/org/kb/nodes/${id}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1676
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1677
|
+
} catch (err) {
|
|
1678
|
+
error(formatApiError(err));
|
|
1679
|
+
process.exit(1);
|
|
1680
|
+
}
|
|
1681
|
+
});
|
|
1682
|
+
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) => {
|
|
1683
|
+
const opts = program2.opts();
|
|
1684
|
+
try {
|
|
1685
|
+
const data = await apiRequest({
|
|
1686
|
+
path: `/org/kb/nodes/${id}/children`,
|
|
1687
|
+
params: { limit: cmdOpts.limit, offset: cmdOpts.offset },
|
|
1688
|
+
apiKey: opts.apiKey,
|
|
1689
|
+
baseUrl: opts.baseUrl
|
|
1690
|
+
});
|
|
1691
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1692
|
+
} catch (err) {
|
|
1693
|
+
error(formatApiError(err));
|
|
1694
|
+
process.exit(1);
|
|
1695
|
+
}
|
|
1696
|
+
});
|
|
1697
|
+
kb.command("ancestors <id>").description("Get the ancestor chain (breadcrumb) for a KB node.").action(async (id) => {
|
|
1698
|
+
const opts = program2.opts();
|
|
1699
|
+
try {
|
|
1700
|
+
const data = await apiRequest({ path: `/org/kb/nodes/${id}/ancestors`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1701
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1702
|
+
} catch (err) {
|
|
1703
|
+
error(formatApiError(err));
|
|
1704
|
+
process.exit(1);
|
|
1705
|
+
}
|
|
1706
|
+
});
|
|
1707
|
+
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) => {
|
|
1708
|
+
const opts = program2.opts();
|
|
1709
|
+
try {
|
|
1710
|
+
const data = await apiRequest({
|
|
1711
|
+
path: `/org/kb/nodes/${id}/content`,
|
|
1712
|
+
params: { version: cmdOpts.version },
|
|
1713
|
+
apiKey: opts.apiKey,
|
|
1714
|
+
baseUrl: opts.baseUrl
|
|
1715
|
+
});
|
|
1716
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1717
|
+
} catch (err) {
|
|
1718
|
+
error(formatApiError(err));
|
|
1719
|
+
process.exit(1);
|
|
1720
|
+
}
|
|
1721
|
+
});
|
|
1722
|
+
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) => {
|
|
1723
|
+
const opts = program2.opts();
|
|
1724
|
+
try {
|
|
1725
|
+
const data = await apiRequest({
|
|
1726
|
+
path: `/org/kb/nodes/${id}/download-url`,
|
|
1727
|
+
params: { version: cmdOpts.version },
|
|
1402
1728
|
apiKey: opts.apiKey,
|
|
1403
1729
|
baseUrl: opts.baseUrl
|
|
1404
1730
|
});
|
|
1405
|
-
|
|
1731
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1732
|
+
} catch (err) {
|
|
1733
|
+
error(formatApiError(err));
|
|
1734
|
+
process.exit(1);
|
|
1735
|
+
}
|
|
1736
|
+
});
|
|
1737
|
+
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) => {
|
|
1738
|
+
const opts = program2.opts();
|
|
1739
|
+
try {
|
|
1740
|
+
const body = { name: cmdOpts.name };
|
|
1741
|
+
if (cmdOpts.parentId) body.parent_node_id = cmdOpts.parentId;
|
|
1742
|
+
const data = await apiRequest({ method: "POST", path: "/org/kb/folders", body, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1743
|
+
success(`Folder "${cmdOpts.name}" created.`);
|
|
1744
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1745
|
+
} catch (err) {
|
|
1746
|
+
error(formatApiError(err));
|
|
1747
|
+
process.exit(1);
|
|
1748
|
+
}
|
|
1749
|
+
});
|
|
1750
|
+
kb.command("rename <id>").description("Rename a KB node.").requiredOption("--name <name>", "New name").action(async (id, cmdOpts) => {
|
|
1751
|
+
const opts = program2.opts();
|
|
1752
|
+
try {
|
|
1753
|
+
const data = await apiRequest({ method: "PATCH", path: `/org/kb/nodes/${id}/rename`, body: { name: cmdOpts.name }, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1754
|
+
success(`Node ${id} renamed to "${cmdOpts.name}".`);
|
|
1755
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1756
|
+
} catch (err) {
|
|
1757
|
+
error(formatApiError(err));
|
|
1758
|
+
process.exit(1);
|
|
1759
|
+
}
|
|
1760
|
+
});
|
|
1761
|
+
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
|
+
const opts = program2.opts();
|
|
1763
|
+
try {
|
|
1764
|
+
const data = await apiRequest({ method: "PATCH", path: `/org/kb/nodes/${id}/move`, body: { parent_node_id: cmdOpts.parentId }, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1765
|
+
success(`Node ${id} moved.`);
|
|
1766
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1767
|
+
} catch (err) {
|
|
1768
|
+
error(formatApiError(err));
|
|
1769
|
+
process.exit(1);
|
|
1770
|
+
}
|
|
1771
|
+
});
|
|
1772
|
+
kb.command("delete <id>").description("Delete a KB node (soft delete).").action(async (id) => {
|
|
1773
|
+
const opts = program2.opts();
|
|
1774
|
+
try {
|
|
1775
|
+
await apiRequest({ method: "DELETE", path: `/org/kb/nodes/${id}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1776
|
+
success(`Node ${id} deleted.`);
|
|
1777
|
+
} catch (err) {
|
|
1778
|
+
error(formatApiError(err));
|
|
1779
|
+
process.exit(1);
|
|
1780
|
+
}
|
|
1781
|
+
});
|
|
1782
|
+
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) => {
|
|
1783
|
+
const opts = program2.opts();
|
|
1784
|
+
try {
|
|
1785
|
+
const body = JSON.parse(cmdOpts.data);
|
|
1786
|
+
const data = await apiRequest({ method: "POST", path: "/org/kb/raw", body, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1787
|
+
success("Raw content node created.");
|
|
1788
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1789
|
+
} catch (err) {
|
|
1790
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
1791
|
+
process.exit(1);
|
|
1792
|
+
}
|
|
1793
|
+
});
|
|
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) => {
|
|
1795
|
+
const opts = program2.opts();
|
|
1796
|
+
try {
|
|
1797
|
+
const body = JSON.parse(cmdOpts.data);
|
|
1798
|
+
const data = await apiRequest({ method: "PUT", path: `/org/kb/nodes/${id}/raw`, body, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1799
|
+
success(`Node ${id} content replaced.`);
|
|
1800
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1801
|
+
} catch (err) {
|
|
1802
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
1803
|
+
process.exit(1);
|
|
1804
|
+
}
|
|
1805
|
+
});
|
|
1806
|
+
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) => {
|
|
1807
|
+
const opts = program2.opts();
|
|
1808
|
+
try {
|
|
1809
|
+
const body = JSON.parse(cmdOpts.data);
|
|
1810
|
+
const data = await apiRequest({ method: "PATCH", path: `/org/kb/nodes/${id}/raw`, body, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1811
|
+
success(`Node ${id} content patched.`);
|
|
1812
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1813
|
+
} catch (err) {
|
|
1814
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
1815
|
+
process.exit(1);
|
|
1816
|
+
}
|
|
1817
|
+
});
|
|
1818
|
+
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) => {
|
|
1819
|
+
const opts = program2.opts();
|
|
1820
|
+
if (files.length > 10) {
|
|
1821
|
+
error("Maximum 10 files per upload request.");
|
|
1822
|
+
process.exit(1);
|
|
1823
|
+
}
|
|
1824
|
+
try {
|
|
1825
|
+
const fileData = await Promise.all(files.map(getFileMetadata2));
|
|
1826
|
+
const body = { files: fileData.map((f) => f.meta) };
|
|
1827
|
+
if (cmdOpts.folderId) body.kb_folder_node_id = cmdOpts.folderId;
|
|
1828
|
+
const results = await apiRequest({
|
|
1829
|
+
method: "POST",
|
|
1830
|
+
path: "/org/kb/upload",
|
|
1831
|
+
body,
|
|
1832
|
+
apiKey: opts.apiKey,
|
|
1833
|
+
baseUrl: opts.baseUrl
|
|
1834
|
+
});
|
|
1835
|
+
const items = Array.isArray(results) ? results : [];
|
|
1836
|
+
let uploaded = 0;
|
|
1837
|
+
for (const item of items) {
|
|
1838
|
+
if (item.status === "upload_pending" && item.upload_url) {
|
|
1839
|
+
const match = fileData.find((f) => f.meta.filename === item.filename);
|
|
1840
|
+
if (!match) {
|
|
1841
|
+
error(`Could not match server filename "${item.filename}" to a local file \u2014 skipping upload.`);
|
|
1842
|
+
continue;
|
|
1843
|
+
}
|
|
1844
|
+
try {
|
|
1845
|
+
await uploadToS32(item.upload_url, match.buffer, match.meta.content_type);
|
|
1846
|
+
uploaded++;
|
|
1847
|
+
success(`Uploaded ${item.filename} (content_id: ${item.content_id})`);
|
|
1848
|
+
} catch (uploadErr) {
|
|
1849
|
+
error(`S3 upload failed for ${item.filename}: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`);
|
|
1850
|
+
}
|
|
1851
|
+
} else {
|
|
1852
|
+
warn(`Skipped ${item.filename}: ${item.status}${item.message ? ` \u2014 ${item.message}` : ""}`);
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
if (uploaded > 0) {
|
|
1856
|
+
success(`${uploaded} file(s) uploaded. Background processing will parse, chunk, and embed them.`);
|
|
1857
|
+
}
|
|
1858
|
+
if (opts.output === "json") console.log(JSON.stringify(results, null, 2));
|
|
1859
|
+
} catch (err) {
|
|
1860
|
+
error(formatApiError(err));
|
|
1861
|
+
process.exit(1);
|
|
1862
|
+
}
|
|
1863
|
+
});
|
|
1864
|
+
kb.command("update-file <id> <file>").description("Replace the file on an existing KB file node with a new version.").action(async (id, file) => {
|
|
1865
|
+
const opts = program2.opts();
|
|
1866
|
+
try {
|
|
1867
|
+
const { meta, buffer } = await getFileMetadata2(file);
|
|
1868
|
+
const results = await apiRequest({
|
|
1869
|
+
method: "PUT",
|
|
1870
|
+
path: `/org/kb/nodes/${id}/file`,
|
|
1871
|
+
body: { file: meta },
|
|
1872
|
+
apiKey: opts.apiKey,
|
|
1873
|
+
baseUrl: opts.baseUrl
|
|
1874
|
+
});
|
|
1875
|
+
const items = Array.isArray(results) ? results : [];
|
|
1876
|
+
for (const item of items) {
|
|
1877
|
+
if (item.status === "upload_pending" && item.upload_url) {
|
|
1878
|
+
await uploadToS32(item.upload_url, buffer, meta.content_type);
|
|
1879
|
+
success(`Uploaded ${meta.filename} for node ${id}. Background re-processing started.`);
|
|
1880
|
+
} else {
|
|
1881
|
+
warn(`Skipped: ${item.status}${item.message ? ` \u2014 ${item.message}` : ""}`);
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
if (opts.output === "json") console.log(JSON.stringify(results, null, 2));
|
|
1406
1885
|
} catch (err) {
|
|
1407
1886
|
error(formatApiError(err));
|
|
1408
1887
|
process.exit(1);
|
|
@@ -1410,13 +1889,13 @@ function registerNotificationCommands(program2) {
|
|
|
1410
1889
|
});
|
|
1411
1890
|
}
|
|
1412
1891
|
|
|
1413
|
-
// src/commands/
|
|
1414
|
-
function
|
|
1415
|
-
const
|
|
1416
|
-
|
|
1892
|
+
// src/commands/permissions.ts
|
|
1893
|
+
function registerPermissionsCommands(program2) {
|
|
1894
|
+
const perms = program2.command("permissions").description("View available role permissions for the organization.");
|
|
1895
|
+
perms.command("list").description("List all available permission keys with their names, descriptions, and categories. Useful for building role management UIs.").action(async () => {
|
|
1417
1896
|
const opts = program2.opts();
|
|
1418
1897
|
try {
|
|
1419
|
-
const data = await apiRequest({ path: "/org/
|
|
1898
|
+
const data = await apiRequest({ path: "/org/permissions", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1420
1899
|
console.log(JSON.stringify(data, null, 2));
|
|
1421
1900
|
} catch (err) {
|
|
1422
1901
|
error(formatApiError(err));
|
|
@@ -1482,8 +1961,10 @@ registerContentTypeCommands(program);
|
|
|
1482
1961
|
registerPromptCommands(program);
|
|
1483
1962
|
registerRunConfigCommands(program);
|
|
1484
1963
|
registerMemberCommands(program);
|
|
1485
|
-
registerNotificationCommands(program);
|
|
1486
1964
|
registerCreditsCommands(program);
|
|
1965
|
+
registerQuestionsCommands(program);
|
|
1966
|
+
registerKBCommands(program);
|
|
1967
|
+
registerPermissionsCommands(program);
|
|
1487
1968
|
registerUpdateCommand(program);
|
|
1488
1969
|
async function main() {
|
|
1489
1970
|
const quiet = process.argv.includes("--quiet") || process.argv.includes("--output") && process.argv[process.argv.indexOf("--output") + 1] === "json";
|