@senso-ai/cli 0.8.2 → 0.11.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/README.md +1 -1
- package/dist/cli.js +590 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -141,7 +141,7 @@ Options: `content list` supports `--limit`, `--offset`, `--search`, `--sort`. `c
|
|
|
141
141
|
```
|
|
142
142
|
senso generate settings Get generation settings
|
|
143
143
|
senso generate update-settings Update generation settings (--data)
|
|
144
|
-
senso generate sample Generate sample for a prompt (--prompt-id, --content-type-id)
|
|
144
|
+
senso generate sample Generate sample for a prompt; waits for async job by default (--prompt-id, --content-type-id, --no-wait)
|
|
145
145
|
senso generate run Trigger a content engine run (--prompt-ids)
|
|
146
146
|
```
|
|
147
147
|
|
package/dist/cli.js
CHANGED
|
@@ -1295,6 +1295,27 @@ function registerIngestCommands(program2) {
|
|
|
1295
1295
|
|
|
1296
1296
|
// src/commands/content.ts
|
|
1297
1297
|
import pc9 from "picocolors";
|
|
1298
|
+
|
|
1299
|
+
// src/lib/tag-args.ts
|
|
1300
|
+
function parseCsv(v) {
|
|
1301
|
+
if (!v) return [];
|
|
1302
|
+
return v.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1303
|
+
}
|
|
1304
|
+
function buildSetTagsBody(cmdOpts) {
|
|
1305
|
+
const body = {};
|
|
1306
|
+
const names = parseCsv(cmdOpts.names);
|
|
1307
|
+
const ids = parseCsv(cmdOpts.ids);
|
|
1308
|
+
if (names.length > 0) body.tag_names = names;
|
|
1309
|
+
if (ids.length > 0) body.tag_ids = ids;
|
|
1310
|
+
return body;
|
|
1311
|
+
}
|
|
1312
|
+
function buildAttachTagBody(cmdOpts) {
|
|
1313
|
+
if (cmdOpts.id) return { tag_id: cmdOpts.id };
|
|
1314
|
+
if (cmdOpts.name) return { tag_name: cmdOpts.name };
|
|
1315
|
+
return null;
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
// src/commands/content.ts
|
|
1298
1319
|
function registerContentCommands(program2) {
|
|
1299
1320
|
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.");
|
|
1300
1321
|
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) => {
|
|
@@ -1348,11 +1369,23 @@ function registerContentCommands(program2) {
|
|
|
1348
1369
|
process.exit(1);
|
|
1349
1370
|
}
|
|
1350
1371
|
});
|
|
1351
|
-
content.command("unpublish <id>").description("Unpublish a content item.
|
|
1372
|
+
content.command("unpublish <id>").description("Unpublish a content item. Without --publish-record-ids, removes the content from every destination it's live on and sets its status back to draft. With --publish-record-ids, only the specified publish records are retracted \u2014 use this to unpublish from a subset of destinations while leaving the rest live. The content status only flips back to draft once no publish records remain live.").option("--publish-record-ids <ids...>", "Restrict unpublish to specific publish_record UUIDs. Use 'content get <id>' to find publish record IDs for a content item.").action(async (id, cmdOpts) => {
|
|
1352
1373
|
const opts = program2.opts();
|
|
1353
1374
|
try {
|
|
1354
|
-
|
|
1355
|
-
|
|
1375
|
+
const body = cmdOpts.publishRecordIds && cmdOpts.publishRecordIds.length > 0 ? { publish_record_ids: cmdOpts.publishRecordIds } : void 0;
|
|
1376
|
+
const data = await apiRequest({
|
|
1377
|
+
method: "POST",
|
|
1378
|
+
path: `/org/content/${id}/unpublish`,
|
|
1379
|
+
body,
|
|
1380
|
+
apiKey: opts.apiKey,
|
|
1381
|
+
baseUrl: opts.baseUrl
|
|
1382
|
+
});
|
|
1383
|
+
success(
|
|
1384
|
+
cmdOpts.publishRecordIds && cmdOpts.publishRecordIds.length > 0 ? `Unpublished ${cmdOpts.publishRecordIds.length} record(s) from content ${id}.` : `Content ${id} unpublished.`
|
|
1385
|
+
);
|
|
1386
|
+
if (data) {
|
|
1387
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1388
|
+
}
|
|
1356
1389
|
} catch (err) {
|
|
1357
1390
|
error(formatApiError(err));
|
|
1358
1391
|
process.exit(1);
|
|
@@ -1430,9 +1463,90 @@ function registerContentCommands(program2) {
|
|
|
1430
1463
|
process.exit(1);
|
|
1431
1464
|
}
|
|
1432
1465
|
});
|
|
1466
|
+
const tags = content.command("tags").description("Manage tags attached to a content item (both KB-ingested and generated). Content is auto-tagged on creation (KB uploads get tagged once ingestion finishes, raw content is tagged on create) \u2014 use these commands to override, add, or remove tags afterwards. Tag names are resolved against the org's tag library; unknown names are created automatically.");
|
|
1467
|
+
tags.command("list <id>").description("List tags attached to a content item.").action(async (id) => {
|
|
1468
|
+
const opts = program2.opts();
|
|
1469
|
+
try {
|
|
1470
|
+
const data = await apiRequest({ path: `/org/content/${id}/tags`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1471
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1472
|
+
} catch (err) {
|
|
1473
|
+
error(formatApiError(err));
|
|
1474
|
+
process.exit(1);
|
|
1475
|
+
}
|
|
1476
|
+
});
|
|
1477
|
+
tags.command("set <id>").description("Replace the content item's full tag collection. Provide --names (comma-separated) and/or --ids (comma-separated UUIDs). Unknown names are created.").option("--names <list>", "Comma-separated tag names (created if missing)").option("--ids <list>", "Comma-separated existing tag UUIDs").action(async (id, cmdOpts) => {
|
|
1478
|
+
const opts = program2.opts();
|
|
1479
|
+
try {
|
|
1480
|
+
const body = buildSetTagsBody(cmdOpts);
|
|
1481
|
+
const data = await apiRequest({
|
|
1482
|
+
method: "PUT",
|
|
1483
|
+
path: `/org/content/${id}/tags`,
|
|
1484
|
+
body,
|
|
1485
|
+
apiKey: opts.apiKey,
|
|
1486
|
+
baseUrl: opts.baseUrl
|
|
1487
|
+
});
|
|
1488
|
+
success(`Content ${id} tags updated.`);
|
|
1489
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1490
|
+
} catch (err) {
|
|
1491
|
+
error(formatApiError(err));
|
|
1492
|
+
process.exit(1);
|
|
1493
|
+
}
|
|
1494
|
+
});
|
|
1495
|
+
tags.command("add <id>").description("Attach a single tag by --name (created if missing) or --id.").option("--name <name>", "Tag name (created if missing)").option("--id <tagId>", "Existing tag UUID").action(async (id, cmdOpts) => {
|
|
1496
|
+
const opts = program2.opts();
|
|
1497
|
+
const body = buildAttachTagBody(cmdOpts);
|
|
1498
|
+
if (!body) {
|
|
1499
|
+
error("Provide --name or --id.");
|
|
1500
|
+
process.exit(1);
|
|
1501
|
+
}
|
|
1502
|
+
try {
|
|
1503
|
+
await apiRequest({
|
|
1504
|
+
method: "POST",
|
|
1505
|
+
path: `/org/content/${id}/tags`,
|
|
1506
|
+
body,
|
|
1507
|
+
apiKey: opts.apiKey,
|
|
1508
|
+
baseUrl: opts.baseUrl
|
|
1509
|
+
});
|
|
1510
|
+
success(`Tag attached to content ${id}.`);
|
|
1511
|
+
} catch (err) {
|
|
1512
|
+
error(formatApiError(err));
|
|
1513
|
+
process.exit(1);
|
|
1514
|
+
}
|
|
1515
|
+
});
|
|
1516
|
+
tags.command("remove <id>").description("Detach a single tag by --name or --id. Idempotent.").option("--name <name>", "Tag name to detach").option("--id <tagId>", "Existing tag UUID to detach").action(async (id, cmdOpts) => {
|
|
1517
|
+
const opts = program2.opts();
|
|
1518
|
+
if (!cmdOpts.name && !cmdOpts.id) {
|
|
1519
|
+
error("Provide --name or --id.");
|
|
1520
|
+
process.exit(1);
|
|
1521
|
+
}
|
|
1522
|
+
try {
|
|
1523
|
+
if (cmdOpts.id) {
|
|
1524
|
+
await apiRequest({
|
|
1525
|
+
method: "DELETE",
|
|
1526
|
+
path: `/org/content/${id}/tags/${cmdOpts.id}`,
|
|
1527
|
+
apiKey: opts.apiKey,
|
|
1528
|
+
baseUrl: opts.baseUrl
|
|
1529
|
+
});
|
|
1530
|
+
} else {
|
|
1531
|
+
await apiRequest({
|
|
1532
|
+
method: "DELETE",
|
|
1533
|
+
path: `/org/content/${id}/tags`,
|
|
1534
|
+
params: { name: cmdOpts.name },
|
|
1535
|
+
apiKey: opts.apiKey,
|
|
1536
|
+
baseUrl: opts.baseUrl
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
success(`Tag detached from content ${id}.`);
|
|
1540
|
+
} catch (err) {
|
|
1541
|
+
error(formatApiError(err));
|
|
1542
|
+
process.exit(1);
|
|
1543
|
+
}
|
|
1544
|
+
});
|
|
1433
1545
|
}
|
|
1434
1546
|
|
|
1435
1547
|
// src/commands/generate.ts
|
|
1548
|
+
var SAMPLE_JOB_POLL_INTERVAL_MS = 2e3;
|
|
1549
|
+
var SAMPLE_JOB_TIMEOUT_MS = 18e4;
|
|
1436
1550
|
function registerGenerateCommands(program2) {
|
|
1437
1551
|
const gen = program2.command("generate").description("AI content generation. Configure settings, generate content samples from prompts, or trigger full content engine runs.");
|
|
1438
1552
|
gen.command("settings").description("Get content generation settings. Shows whether generation and auto-publish are enabled, the content schedule, and configured publishers.").action(async () => {
|
|
@@ -1457,7 +1571,7 @@ function registerGenerateCommands(program2) {
|
|
|
1457
1571
|
process.exit(1);
|
|
1458
1572
|
}
|
|
1459
1573
|
});
|
|
1460
|
-
gen.command("sample").description("Generate an ad hoc content sample for a specific prompt and content type.
|
|
1574
|
+
gen.command("sample").description("Generate an ad hoc content sample for a specific prompt and content type. Submits an async job, waits for completion by default, then 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.").option("--no-wait", "Return the accepted sample job immediately instead of polling for the generated content.").action(async (cmdOpts) => {
|
|
1461
1575
|
const opts = program2.opts();
|
|
1462
1576
|
try {
|
|
1463
1577
|
const body = {
|
|
@@ -1474,7 +1588,25 @@ function registerGenerateCommands(program2) {
|
|
|
1474
1588
|
apiKey: opts.apiKey,
|
|
1475
1589
|
baseUrl: opts.baseUrl
|
|
1476
1590
|
});
|
|
1477
|
-
|
|
1591
|
+
if (cmdOpts.wait === false) {
|
|
1592
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1593
|
+
return;
|
|
1594
|
+
}
|
|
1595
|
+
const quiet = opts.quiet || opts.output === "json";
|
|
1596
|
+
if (!quiet) {
|
|
1597
|
+
info(`Sample job accepted: ${data.sample_job_id}`);
|
|
1598
|
+
}
|
|
1599
|
+
const job = await waitForSampleJob(data.sample_job_id, {
|
|
1600
|
+
apiKey: opts.apiKey,
|
|
1601
|
+
baseUrl: opts.baseUrl,
|
|
1602
|
+
quiet
|
|
1603
|
+
});
|
|
1604
|
+
if (job.status === "completed") {
|
|
1605
|
+
console.log(JSON.stringify(job.result ?? job, null, 2));
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
const message = job.error?.message || `Sample job ended with status: ${job.status}`;
|
|
1609
|
+
throw new Error(job.error?.code ? `${message} (${job.error.code})` : message);
|
|
1478
1610
|
} catch (err) {
|
|
1479
1611
|
error(formatApiError(err));
|
|
1480
1612
|
process.exit(1);
|
|
@@ -1568,14 +1700,40 @@ function registerGenerateCommands(program2) {
|
|
|
1568
1700
|
}
|
|
1569
1701
|
});
|
|
1570
1702
|
}
|
|
1703
|
+
async function waitForSampleJob(sampleJobId, opts) {
|
|
1704
|
+
const deadline = Date.now() + SAMPLE_JOB_TIMEOUT_MS;
|
|
1705
|
+
let lastStatus = "";
|
|
1706
|
+
while (Date.now() < deadline) {
|
|
1707
|
+
const job = await apiRequest({
|
|
1708
|
+
path: `/org/content-generation/sample-jobs/${sampleJobId}`,
|
|
1709
|
+
apiKey: opts.apiKey,
|
|
1710
|
+
baseUrl: opts.baseUrl
|
|
1711
|
+
});
|
|
1712
|
+
if (!opts.quiet && job.status !== lastStatus) {
|
|
1713
|
+
info(`Sample job status: ${job.status}`);
|
|
1714
|
+
lastStatus = job.status;
|
|
1715
|
+
}
|
|
1716
|
+
if (job.status === "completed" || job.status === "failed" || job.status === "expired") {
|
|
1717
|
+
return job;
|
|
1718
|
+
}
|
|
1719
|
+
await sleep(SAMPLE_JOB_POLL_INTERVAL_MS);
|
|
1720
|
+
}
|
|
1721
|
+
throw new Error(`Timed out waiting for sample job ${sampleJobId}. Poll /org/content-generation/sample-jobs/${sampleJobId} for status.`);
|
|
1722
|
+
}
|
|
1723
|
+
function sleep(ms) {
|
|
1724
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
1725
|
+
}
|
|
1571
1726
|
|
|
1572
1727
|
// src/commands/engine.ts
|
|
1573
1728
|
function registerEngineCommands(program2) {
|
|
1574
|
-
const engine = program2.command("engine").description("Publish or draft content through the content engine. Used to push AI-generated content to external destinations or save it as a draft for review.");
|
|
1575
|
-
engine.command("publish").description("Publish content to external destinations via the content engine. Requires geo_question_id, raw_markdown, and seo_title.").requiredOption("--data <json>", 'JSON: { "geo_question_id": "uuid", "raw_markdown": "...", "seo_title": "...", "summary": "..." }').action(async (cmdOpts) => {
|
|
1729
|
+
const engine = program2.command("engine").description("Publish or draft content through the content engine. Used to push AI-generated content to external destinations (citeables by default) or save it as a draft for review.");
|
|
1730
|
+
engine.command("publish").description("Publish content to external destinations via the content engine. Requires geo_question_id, raw_markdown, and seo_title. By default publishes to every destination currently selected for generation (citeables is the default for most orgs \u2014 see 'senso destinations list'). Pass --publisher-ids to restrict publishing to a specific subset, or include 'publisher_ids' inside --data.").requiredOption("--data <json>", 'JSON: { "geo_question_id": "uuid", "raw_markdown": "...", "seo_title": "...", "summary": "...", "publisher_ids": ["<uuid>", ...] }').option("--publisher-ids <ids...>", "Restrict publishing to specific publisher IDs. Overrides any publisher_ids present in --data. Omit to publish to all configured destinations (citeables by default).").action(async (cmdOpts) => {
|
|
1576
1731
|
const opts = program2.opts();
|
|
1577
1732
|
try {
|
|
1578
1733
|
const body = JSON.parse(cmdOpts.data);
|
|
1734
|
+
if (cmdOpts.publisherIds && cmdOpts.publisherIds.length > 0) {
|
|
1735
|
+
body.publisher_ids = cmdOpts.publisherIds;
|
|
1736
|
+
}
|
|
1579
1737
|
const data = await apiRequest({
|
|
1580
1738
|
method: "POST",
|
|
1581
1739
|
path: "/org/content-engine/publish",
|
|
@@ -1590,7 +1748,7 @@ function registerEngineCommands(program2) {
|
|
|
1590
1748
|
process.exit(1);
|
|
1591
1749
|
}
|
|
1592
1750
|
});
|
|
1593
|
-
engine.command("draft").description("Save content as a draft for review before publishing. Requires geo_question_id, raw_markdown, and seo_title.").requiredOption("--data <json>", 'JSON: { "geo_question_id": "uuid", "raw_markdown": "...", "seo_title": "...", "summary": "..." }').action(async (cmdOpts) => {
|
|
1751
|
+
engine.command("draft").description("Save content as a draft for review before publishing. Requires geo_question_id, raw_markdown, and seo_title. Drafts do not hit any destination until you run 'senso engine publish' on them.").requiredOption("--data <json>", 'JSON: { "geo_question_id": "uuid", "raw_markdown": "...", "seo_title": "...", "summary": "..." }').action(async (cmdOpts) => {
|
|
1594
1752
|
const opts = program2.opts();
|
|
1595
1753
|
try {
|
|
1596
1754
|
const body = JSON.parse(cmdOpts.data);
|
|
@@ -1610,6 +1768,100 @@ function registerEngineCommands(program2) {
|
|
|
1610
1768
|
});
|
|
1611
1769
|
}
|
|
1612
1770
|
|
|
1771
|
+
// src/commands/destinations.ts
|
|
1772
|
+
var SUPPORTED_TYPES = ["citeables", "codeables", "cucopilot"];
|
|
1773
|
+
var REMOVE_ACTIONS = ["leave", "unpublish", "delete"];
|
|
1774
|
+
function registerDestinationsCommands(program2) {
|
|
1775
|
+
const dest = program2.command("destinations").description("Manage publish destinations. Destinations are where generated content gets published \u2014 shared domains (citeables, codeables, cucopilot) plus any custom citeables domains registered for your org. Most orgs publish to 'citeables' by default; additional destinations are opt-in.");
|
|
1776
|
+
dest.command("list").description("List all destinations available to the organization. Includes shared destinations (citeables, codeables, cucopilot) and any custom domains you've added, with per-destination live article counts and last publish timestamps. 'selected_for_generation: true' means a destination is active in your generation pipeline.").action(async () => {
|
|
1777
|
+
const opts = program2.opts();
|
|
1778
|
+
try {
|
|
1779
|
+
const data = await apiRequest({
|
|
1780
|
+
path: "/org/destinations",
|
|
1781
|
+
apiKey: opts.apiKey,
|
|
1782
|
+
baseUrl: opts.baseUrl
|
|
1783
|
+
});
|
|
1784
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1785
|
+
} catch (err) {
|
|
1786
|
+
error(formatApiError(err));
|
|
1787
|
+
process.exit(1);
|
|
1788
|
+
}
|
|
1789
|
+
});
|
|
1790
|
+
dest.command("add").description("Register a custom publish destination (a citeables-system domain owned by your org). The domain is registered synchronously with the citeables service and linked to the org. Today only citeables-type destinations (slugs: citeables, codeables, cucopilot) are supported \u2014 pass --type if you need to target one of the non-default systems; new destination types may be added in future releases.").requiredOption("--domain <domain>", 'Custom domain to register (e.g. "content.example.com")').requiredOption("--name <name>", 'Display name for the destination (e.g. "Example Citeables")').option("--type <type>", "Destination type. One of: citeables, codeables, cucopilot. Defaults to citeables.", "citeables").action(async (cmdOpts) => {
|
|
1791
|
+
const opts = program2.opts();
|
|
1792
|
+
const type = cmdOpts.type.toLowerCase();
|
|
1793
|
+
if (!SUPPORTED_TYPES.includes(type)) {
|
|
1794
|
+
error(`Invalid --type "${cmdOpts.type}". Must be one of: ${SUPPORTED_TYPES.join(", ")}.`);
|
|
1795
|
+
process.exit(1);
|
|
1796
|
+
}
|
|
1797
|
+
try {
|
|
1798
|
+
const data = await apiRequest({
|
|
1799
|
+
method: "POST",
|
|
1800
|
+
path: "/org/destinations",
|
|
1801
|
+
body: {
|
|
1802
|
+
type,
|
|
1803
|
+
name: cmdOpts.name,
|
|
1804
|
+
domain: cmdOpts.domain
|
|
1805
|
+
},
|
|
1806
|
+
apiKey: opts.apiKey,
|
|
1807
|
+
baseUrl: opts.baseUrl
|
|
1808
|
+
});
|
|
1809
|
+
success(`Destination "${cmdOpts.name}" registered for ${cmdOpts.domain}.`);
|
|
1810
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1811
|
+
} catch (err) {
|
|
1812
|
+
error(formatApiError(err));
|
|
1813
|
+
process.exit(1);
|
|
1814
|
+
}
|
|
1815
|
+
});
|
|
1816
|
+
dest.command("remove <publisherId>").description("Remove a destination from the org. --action controls what happens to live content: 'leave' keeps the articles live at the destination (org stops publishing to it but published records remain), 'unpublish' removes live articles from the destination and returns content to draft, 'delete' unpublishes AND hard-deletes the local content records. Shared destinations (citeables/codeables/cucopilot) can be removed from the org without affecting the underlying domain. --keep-domain preserves the custom domain registration on the citeables side (useful for SEO) when removing a custom destination.").requiredOption("--action <action>", "One of: leave, unpublish, delete. See command description.").option("--also-remove-destination", "Also delete the publisher row (not just the org link). Only valid for custom destinations you own.", false).option("--keep-domain", "Keep the custom domain registered on citeables after removing (custom destinations only).", false).action(async (publisherId, cmdOpts) => {
|
|
1817
|
+
const opts = program2.opts();
|
|
1818
|
+
const action = cmdOpts.action.toLowerCase();
|
|
1819
|
+
if (!REMOVE_ACTIONS.includes(action)) {
|
|
1820
|
+
error(`Invalid --action "${cmdOpts.action}". Must be one of: ${REMOVE_ACTIONS.join(", ")}.`);
|
|
1821
|
+
process.exit(1);
|
|
1822
|
+
}
|
|
1823
|
+
try {
|
|
1824
|
+
const data = await apiRequest({
|
|
1825
|
+
method: "POST",
|
|
1826
|
+
path: `/org/destinations/${publisherId}/remove`,
|
|
1827
|
+
body: {
|
|
1828
|
+
action,
|
|
1829
|
+
also_remove_destination: cmdOpts.alsoRemoveDestination ?? false,
|
|
1830
|
+
keep_domain: cmdOpts.keepDomain ?? false
|
|
1831
|
+
},
|
|
1832
|
+
apiKey: opts.apiKey,
|
|
1833
|
+
baseUrl: opts.baseUrl
|
|
1834
|
+
});
|
|
1835
|
+
success(`Destination ${publisherId} removed (action: ${action}).`);
|
|
1836
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1837
|
+
} catch (err) {
|
|
1838
|
+
error(formatApiError(err));
|
|
1839
|
+
process.exit(1);
|
|
1840
|
+
}
|
|
1841
|
+
});
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
// src/commands/publish-records.ts
|
|
1845
|
+
function registerPublishRecordsCommands(program2) {
|
|
1846
|
+
const pr = program2.command("publish-records").description("Inspect and retry publish records. A publish_record is the unit that tracks one content item's publication to one destination \u2014 published/live, pending, failed, unpublished, etc. When a publish fails for a single destination, retry it here without redoing the whole publish.");
|
|
1847
|
+
pr.command("retry <publishRecordId>").description("Retry a failed publish record. Re-runs the publish for that specific content+destination pair and flips the record's state based on the new attempt. Only works on records currently in the 'failed' state.").action(async (publishRecordId) => {
|
|
1848
|
+
const opts = program2.opts();
|
|
1849
|
+
try {
|
|
1850
|
+
const data = await apiRequest({
|
|
1851
|
+
method: "POST",
|
|
1852
|
+
path: `/org/publish-records/${publishRecordId}/retry`,
|
|
1853
|
+
apiKey: opts.apiKey,
|
|
1854
|
+
baseUrl: opts.baseUrl
|
|
1855
|
+
});
|
|
1856
|
+
success(`Publish record ${publishRecordId} retry triggered.`);
|
|
1857
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1858
|
+
} catch (err) {
|
|
1859
|
+
error(formatApiError(err));
|
|
1860
|
+
process.exit(1);
|
|
1861
|
+
}
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1613
1865
|
// src/commands/brand-kit.ts
|
|
1614
1866
|
function registerBrandKitCommands(program2) {
|
|
1615
1867
|
const bk = program2.command("brand-kit").description("Manage the organization's brand kit guidelines. The brand kit is a free-form JSON object that informs AI content generation about your brand voice, tone, and style.");
|
|
@@ -1811,6 +2063,85 @@ function registerPromptCommands(program2) {
|
|
|
1811
2063
|
process.exit(1);
|
|
1812
2064
|
}
|
|
1813
2065
|
});
|
|
2066
|
+
const tags = prompts.command("tags").description("Manage tags attached to a prompt. Prompts are auto-tagged on creation \u2014 use these commands to override, add, or remove tags afterwards. Tag names are resolved against the org's tag library; unknown names are created automatically.");
|
|
2067
|
+
tags.command("list <promptId>").description("List tags attached to a prompt.").action(async (promptId) => {
|
|
2068
|
+
const opts = program2.opts();
|
|
2069
|
+
try {
|
|
2070
|
+
const data = await apiRequest({ path: `/org/prompts/${promptId}/tags`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
2071
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2072
|
+
} catch (err) {
|
|
2073
|
+
error(formatApiError(err));
|
|
2074
|
+
process.exit(1);
|
|
2075
|
+
}
|
|
2076
|
+
});
|
|
2077
|
+
tags.command("set <promptId>").description("Replace the prompt's full tag collection. Provide --names (comma-separated) and/or --ids (comma-separated UUIDs). Unknown names are created.").option("--names <list>", "Comma-separated tag names (created if missing)").option("--ids <list>", "Comma-separated existing tag UUIDs").action(async (promptId, cmdOpts) => {
|
|
2078
|
+
const opts = program2.opts();
|
|
2079
|
+
try {
|
|
2080
|
+
const body = buildSetTagsBody(cmdOpts);
|
|
2081
|
+
const data = await apiRequest({
|
|
2082
|
+
method: "PUT",
|
|
2083
|
+
path: `/org/prompts/${promptId}/tags`,
|
|
2084
|
+
body,
|
|
2085
|
+
apiKey: opts.apiKey,
|
|
2086
|
+
baseUrl: opts.baseUrl
|
|
2087
|
+
});
|
|
2088
|
+
success(`Prompt ${promptId} tags updated.`);
|
|
2089
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2090
|
+
} catch (err) {
|
|
2091
|
+
error(formatApiError(err));
|
|
2092
|
+
process.exit(1);
|
|
2093
|
+
}
|
|
2094
|
+
});
|
|
2095
|
+
tags.command("add <promptId>").description("Attach a single tag by --name (created if missing) or --id.").option("--name <name>", "Tag name (created if missing)").option("--id <tagId>", "Existing tag UUID").action(async (promptId, cmdOpts) => {
|
|
2096
|
+
const opts = program2.opts();
|
|
2097
|
+
const body = buildAttachTagBody(cmdOpts);
|
|
2098
|
+
if (!body) {
|
|
2099
|
+
error("Provide --name or --id.");
|
|
2100
|
+
process.exit(1);
|
|
2101
|
+
}
|
|
2102
|
+
try {
|
|
2103
|
+
await apiRequest({
|
|
2104
|
+
method: "POST",
|
|
2105
|
+
path: `/org/prompts/${promptId}/tags`,
|
|
2106
|
+
body,
|
|
2107
|
+
apiKey: opts.apiKey,
|
|
2108
|
+
baseUrl: opts.baseUrl
|
|
2109
|
+
});
|
|
2110
|
+
success(`Tag attached to prompt ${promptId}.`);
|
|
2111
|
+
} catch (err) {
|
|
2112
|
+
error(formatApiError(err));
|
|
2113
|
+
process.exit(1);
|
|
2114
|
+
}
|
|
2115
|
+
});
|
|
2116
|
+
tags.command("remove <promptId>").description("Detach a single tag by --name or --id. Idempotent.").option("--name <name>", "Tag name to detach").option("--id <tagId>", "Existing tag UUID to detach").action(async (promptId, cmdOpts) => {
|
|
2117
|
+
const opts = program2.opts();
|
|
2118
|
+
if (!cmdOpts.name && !cmdOpts.id) {
|
|
2119
|
+
error("Provide --name or --id.");
|
|
2120
|
+
process.exit(1);
|
|
2121
|
+
}
|
|
2122
|
+
try {
|
|
2123
|
+
if (cmdOpts.id) {
|
|
2124
|
+
await apiRequest({
|
|
2125
|
+
method: "DELETE",
|
|
2126
|
+
path: `/org/prompts/${promptId}/tags/${cmdOpts.id}`,
|
|
2127
|
+
apiKey: opts.apiKey,
|
|
2128
|
+
baseUrl: opts.baseUrl
|
|
2129
|
+
});
|
|
2130
|
+
} else {
|
|
2131
|
+
await apiRequest({
|
|
2132
|
+
method: "DELETE",
|
|
2133
|
+
path: `/org/prompts/${promptId}/tags`,
|
|
2134
|
+
params: { name: cmdOpts.name },
|
|
2135
|
+
apiKey: opts.apiKey,
|
|
2136
|
+
baseUrl: opts.baseUrl
|
|
2137
|
+
});
|
|
2138
|
+
}
|
|
2139
|
+
success(`Tag detached from prompt ${promptId}.`);
|
|
2140
|
+
} catch (err) {
|
|
2141
|
+
error(formatApiError(err));
|
|
2142
|
+
process.exit(1);
|
|
2143
|
+
}
|
|
2144
|
+
});
|
|
1814
2145
|
}
|
|
1815
2146
|
|
|
1816
2147
|
// src/commands/run-config.ts
|
|
@@ -2426,6 +2757,85 @@ function registerKBCommands(program2) {
|
|
|
2426
2757
|
process.exit(1);
|
|
2427
2758
|
}
|
|
2428
2759
|
});
|
|
2760
|
+
const tags = kb.command("tags").description("Manage tags attached to a KB node. KB content is auto-tagged on creation (raw content on create, uploaded files once ingestion finishes) \u2014 use these commands to override, add, or remove tags afterwards. Tags can only be applied to content nodes, not folders. Names are resolved against the org's tag library; unknown names are created.");
|
|
2761
|
+
tags.command("list <id>").description("List tags attached to a KB node.").action(async (id) => {
|
|
2762
|
+
const opts = program2.opts();
|
|
2763
|
+
try {
|
|
2764
|
+
const data = await apiRequest({ path: `/org/kb/nodes/${id}/tags`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
2765
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2766
|
+
} catch (err) {
|
|
2767
|
+
error(formatApiError(err));
|
|
2768
|
+
process.exit(1);
|
|
2769
|
+
}
|
|
2770
|
+
});
|
|
2771
|
+
tags.command("set <id>").description("Replace the KB node's full tag collection. Provide --names (comma-separated) and/or --ids. Unknown names are created.").option("--names <list>", "Comma-separated tag names (created if missing)").option("--ids <list>", "Comma-separated existing tag UUIDs").action(async (id, cmdOpts) => {
|
|
2772
|
+
const opts = program2.opts();
|
|
2773
|
+
try {
|
|
2774
|
+
const body = buildSetTagsBody(cmdOpts);
|
|
2775
|
+
const data = await apiRequest({
|
|
2776
|
+
method: "PUT",
|
|
2777
|
+
path: `/org/kb/nodes/${id}/tags`,
|
|
2778
|
+
body,
|
|
2779
|
+
apiKey: opts.apiKey,
|
|
2780
|
+
baseUrl: opts.baseUrl
|
|
2781
|
+
});
|
|
2782
|
+
success(`KB node ${id} tags updated.`);
|
|
2783
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2784
|
+
} catch (err) {
|
|
2785
|
+
error(formatApiError(err));
|
|
2786
|
+
process.exit(1);
|
|
2787
|
+
}
|
|
2788
|
+
});
|
|
2789
|
+
tags.command("add <id>").description("Attach a single tag by --name (created if missing) or --id.").option("--name <name>", "Tag name (created if missing)").option("--id <tagId>", "Existing tag UUID").action(async (id, cmdOpts) => {
|
|
2790
|
+
const opts = program2.opts();
|
|
2791
|
+
const body = buildAttachTagBody(cmdOpts);
|
|
2792
|
+
if (!body) {
|
|
2793
|
+
error("Provide --name or --id.");
|
|
2794
|
+
process.exit(1);
|
|
2795
|
+
}
|
|
2796
|
+
try {
|
|
2797
|
+
await apiRequest({
|
|
2798
|
+
method: "POST",
|
|
2799
|
+
path: `/org/kb/nodes/${id}/tags`,
|
|
2800
|
+
body,
|
|
2801
|
+
apiKey: opts.apiKey,
|
|
2802
|
+
baseUrl: opts.baseUrl
|
|
2803
|
+
});
|
|
2804
|
+
success(`Tag attached to KB node ${id}.`);
|
|
2805
|
+
} catch (err) {
|
|
2806
|
+
error(formatApiError(err));
|
|
2807
|
+
process.exit(1);
|
|
2808
|
+
}
|
|
2809
|
+
});
|
|
2810
|
+
tags.command("remove <id>").description("Detach a single tag by --name or --id. Idempotent.").option("--name <name>", "Tag name to detach").option("--id <tagId>", "Existing tag UUID to detach").action(async (id, cmdOpts) => {
|
|
2811
|
+
const opts = program2.opts();
|
|
2812
|
+
if (!cmdOpts.name && !cmdOpts.id) {
|
|
2813
|
+
error("Provide --name or --id.");
|
|
2814
|
+
process.exit(1);
|
|
2815
|
+
}
|
|
2816
|
+
try {
|
|
2817
|
+
if (cmdOpts.id) {
|
|
2818
|
+
await apiRequest({
|
|
2819
|
+
method: "DELETE",
|
|
2820
|
+
path: `/org/kb/nodes/${id}/tags/${cmdOpts.id}`,
|
|
2821
|
+
apiKey: opts.apiKey,
|
|
2822
|
+
baseUrl: opts.baseUrl
|
|
2823
|
+
});
|
|
2824
|
+
} else {
|
|
2825
|
+
await apiRequest({
|
|
2826
|
+
method: "DELETE",
|
|
2827
|
+
path: `/org/kb/nodes/${id}/tags`,
|
|
2828
|
+
params: { name: cmdOpts.name },
|
|
2829
|
+
apiKey: opts.apiKey,
|
|
2830
|
+
baseUrl: opts.baseUrl
|
|
2831
|
+
});
|
|
2832
|
+
}
|
|
2833
|
+
success(`Tag detached from KB node ${id}.`);
|
|
2834
|
+
} catch (err) {
|
|
2835
|
+
error(formatApiError(err));
|
|
2836
|
+
process.exit(1);
|
|
2837
|
+
}
|
|
2838
|
+
});
|
|
2429
2839
|
}
|
|
2430
2840
|
|
|
2431
2841
|
// src/commands/permissions.ts
|
|
@@ -2443,6 +2853,174 @@ function registerPermissionsCommands(program2) {
|
|
|
2443
2853
|
});
|
|
2444
2854
|
}
|
|
2445
2855
|
|
|
2856
|
+
// src/commands/product-lines.ts
|
|
2857
|
+
function registerProductLineCommands(program2) {
|
|
2858
|
+
const pl = program2.command("product-lines").description("Manage product lines \u2014 flexible org-scoped product/service definitions. Each product line has a name and an arbitrary JSON 'details' blob carried by downstream generation and evaluation pipelines.");
|
|
2859
|
+
pl.command("list").description("List all product lines for the organization.").option("--limit <n>", "Maximum items to return (default: 50)").option("--offset <n>", "Number of items to skip (for pagination)").action(async (cmdOpts) => {
|
|
2860
|
+
const opts = program2.opts();
|
|
2861
|
+
try {
|
|
2862
|
+
const data = await apiRequest({
|
|
2863
|
+
path: "/org/product-lines",
|
|
2864
|
+
params: { limit: cmdOpts.limit, offset: cmdOpts.offset },
|
|
2865
|
+
apiKey: opts.apiKey,
|
|
2866
|
+
baseUrl: opts.baseUrl
|
|
2867
|
+
});
|
|
2868
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2869
|
+
} catch (err) {
|
|
2870
|
+
error(formatApiError(err));
|
|
2871
|
+
process.exit(1);
|
|
2872
|
+
}
|
|
2873
|
+
});
|
|
2874
|
+
pl.command("create").description("Create a new product line. 'details' is an open-ended JSON object \u2014 put whatever structured metadata (SKUs, URLs, positioning, pricing tiers) your workflows need.").requiredOption("--data <json>", 'JSON: { "name": "Pro Plan", "details": { ... } }').action(async (cmdOpts) => {
|
|
2875
|
+
const opts = program2.opts();
|
|
2876
|
+
try {
|
|
2877
|
+
const body = JSON.parse(cmdOpts.data);
|
|
2878
|
+
const data = await apiRequest({
|
|
2879
|
+
method: "POST",
|
|
2880
|
+
path: "/org/product-lines",
|
|
2881
|
+
body,
|
|
2882
|
+
apiKey: opts.apiKey,
|
|
2883
|
+
baseUrl: opts.baseUrl
|
|
2884
|
+
});
|
|
2885
|
+
success("Product line created.");
|
|
2886
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2887
|
+
} catch (err) {
|
|
2888
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
2889
|
+
process.exit(1);
|
|
2890
|
+
}
|
|
2891
|
+
});
|
|
2892
|
+
pl.command("get <id>").description("Get a product line by ID.").action(async (id) => {
|
|
2893
|
+
const opts = program2.opts();
|
|
2894
|
+
try {
|
|
2895
|
+
const data = await apiRequest({ path: `/org/product-lines/${id}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
2896
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2897
|
+
} catch (err) {
|
|
2898
|
+
error(formatApiError(err));
|
|
2899
|
+
process.exit(1);
|
|
2900
|
+
}
|
|
2901
|
+
});
|
|
2902
|
+
pl.command("update <id>").description("Replace a product line's name and details (PUT). Both fields are required \u2014 run 'get <id>' first to preserve existing values. For single-field updates, use 'product-lines patch <id>'.").requiredOption("--data <json>", 'JSON: { "name": "Updated Name", "details": { ... } }').action(async (id, cmdOpts) => {
|
|
2903
|
+
const opts = program2.opts();
|
|
2904
|
+
try {
|
|
2905
|
+
const body = JSON.parse(cmdOpts.data);
|
|
2906
|
+
const data = await apiRequest({
|
|
2907
|
+
method: "PUT",
|
|
2908
|
+
path: `/org/product-lines/${id}`,
|
|
2909
|
+
body,
|
|
2910
|
+
apiKey: opts.apiKey,
|
|
2911
|
+
baseUrl: opts.baseUrl
|
|
2912
|
+
});
|
|
2913
|
+
success(`Product line ${id} updated.`);
|
|
2914
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2915
|
+
} catch (err) {
|
|
2916
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
2917
|
+
process.exit(1);
|
|
2918
|
+
}
|
|
2919
|
+
});
|
|
2920
|
+
pl.command("patch <id>").description("Partially update a product line (PATCH). Only the fields you provide are changed \u2014 existing fields are preserved.").requiredOption("--data <json>", 'JSON: { "details": { "price": 99 } }').action(async (id, cmdOpts) => {
|
|
2921
|
+
const opts = program2.opts();
|
|
2922
|
+
try {
|
|
2923
|
+
const body = JSON.parse(cmdOpts.data);
|
|
2924
|
+
const data = await apiRequest({
|
|
2925
|
+
method: "PATCH",
|
|
2926
|
+
path: `/org/product-lines/${id}`,
|
|
2927
|
+
body,
|
|
2928
|
+
apiKey: opts.apiKey,
|
|
2929
|
+
baseUrl: opts.baseUrl
|
|
2930
|
+
});
|
|
2931
|
+
success(`Product line ${id} updated.`);
|
|
2932
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2933
|
+
} catch (err) {
|
|
2934
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
2935
|
+
process.exit(1);
|
|
2936
|
+
}
|
|
2937
|
+
});
|
|
2938
|
+
pl.command("delete <id>").description("Delete a product line. This cannot be undone.").action(async (id) => {
|
|
2939
|
+
const opts = program2.opts();
|
|
2940
|
+
try {
|
|
2941
|
+
await apiRequest({ method: "DELETE", path: `/org/product-lines/${id}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
2942
|
+
success(`Product line ${id} deleted.`);
|
|
2943
|
+
} catch (err) {
|
|
2944
|
+
error(formatApiError(err));
|
|
2945
|
+
process.exit(1);
|
|
2946
|
+
}
|
|
2947
|
+
});
|
|
2948
|
+
}
|
|
2949
|
+
|
|
2950
|
+
// src/commands/tags.ts
|
|
2951
|
+
function registerTagsCommands(program2) {
|
|
2952
|
+
const tags = program2.command("tags").description("Manage the organization's tag library. Tags are labels attached to prompts, KB nodes, and content items to group them for filtering or metric rollups. Senso auto-tags prompts, KB content, and search queries on creation, so the tag library grows automatically \u2014 most workflows skip these commands and rely on attach-by-name on the resource commands, which also creates tags on demand.");
|
|
2953
|
+
tags.command("list").description("List all tags for the organization. Pass --counts to include per-tag usage counts.").option("--counts", "Include prompt/content usage counts").action(async (cmdOpts) => {
|
|
2954
|
+
const opts = program2.opts();
|
|
2955
|
+
try {
|
|
2956
|
+
const data = await apiRequest({
|
|
2957
|
+
path: "/org/tags",
|
|
2958
|
+
params: cmdOpts.counts ? { counts: "true" } : {},
|
|
2959
|
+
apiKey: opts.apiKey,
|
|
2960
|
+
baseUrl: opts.baseUrl
|
|
2961
|
+
});
|
|
2962
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2963
|
+
} catch (err) {
|
|
2964
|
+
error(formatApiError(err));
|
|
2965
|
+
process.exit(1);
|
|
2966
|
+
}
|
|
2967
|
+
});
|
|
2968
|
+
tags.command("create").description("Create a new tag. Tag names are unique per org (case-insensitive).").requiredOption("--name <name>", "Tag name").action(async (cmdOpts) => {
|
|
2969
|
+
const opts = program2.opts();
|
|
2970
|
+
try {
|
|
2971
|
+
const data = await apiRequest({
|
|
2972
|
+
method: "POST",
|
|
2973
|
+
path: "/org/tags",
|
|
2974
|
+
body: { name: cmdOpts.name },
|
|
2975
|
+
apiKey: opts.apiKey,
|
|
2976
|
+
baseUrl: opts.baseUrl
|
|
2977
|
+
});
|
|
2978
|
+
success(`Tag "${cmdOpts.name}" created.`);
|
|
2979
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2980
|
+
} catch (err) {
|
|
2981
|
+
error(formatApiError(err));
|
|
2982
|
+
process.exit(1);
|
|
2983
|
+
}
|
|
2984
|
+
});
|
|
2985
|
+
tags.command("get <id>").description("Get a tag by ID, including usage counts.").action(async (id) => {
|
|
2986
|
+
const opts = program2.opts();
|
|
2987
|
+
try {
|
|
2988
|
+
const data = await apiRequest({ path: `/org/tags/${id}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
2989
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2990
|
+
} catch (err) {
|
|
2991
|
+
error(formatApiError(err));
|
|
2992
|
+
process.exit(1);
|
|
2993
|
+
}
|
|
2994
|
+
});
|
|
2995
|
+
tags.command("update <id>").description("Rename a tag. Existing attachments on prompts, content, and KB nodes are preserved.").requiredOption("--name <name>", "New tag name").action(async (id, cmdOpts) => {
|
|
2996
|
+
const opts = program2.opts();
|
|
2997
|
+
try {
|
|
2998
|
+
const data = await apiRequest({
|
|
2999
|
+
method: "PATCH",
|
|
3000
|
+
path: `/org/tags/${id}`,
|
|
3001
|
+
body: { name: cmdOpts.name },
|
|
3002
|
+
apiKey: opts.apiKey,
|
|
3003
|
+
baseUrl: opts.baseUrl
|
|
3004
|
+
});
|
|
3005
|
+
success(`Tag ${id} renamed to "${cmdOpts.name}".`);
|
|
3006
|
+
console.log(JSON.stringify(data, null, 2));
|
|
3007
|
+
} catch (err) {
|
|
3008
|
+
error(formatApiError(err));
|
|
3009
|
+
process.exit(1);
|
|
3010
|
+
}
|
|
3011
|
+
});
|
|
3012
|
+
tags.command("delete <id>").description("Delete a tag and detach it from every prompt, content item, and KB node it was applied to. This cannot be undone.").action(async (id) => {
|
|
3013
|
+
const opts = program2.opts();
|
|
3014
|
+
try {
|
|
3015
|
+
await apiRequest({ method: "DELETE", path: `/org/tags/${id}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
3016
|
+
success(`Tag ${id} deleted.`);
|
|
3017
|
+
} catch (err) {
|
|
3018
|
+
error(formatApiError(err));
|
|
3019
|
+
process.exit(1);
|
|
3020
|
+
}
|
|
3021
|
+
});
|
|
3022
|
+
}
|
|
3023
|
+
|
|
2446
3024
|
// src/commands/update.ts
|
|
2447
3025
|
import semver2 from "semver";
|
|
2448
3026
|
import pc10 from "picocolors";
|
|
@@ -2495,6 +3073,8 @@ registerIngestCommands(program);
|
|
|
2495
3073
|
registerContentCommands(program);
|
|
2496
3074
|
registerGenerateCommands(program);
|
|
2497
3075
|
registerEngineCommands(program);
|
|
3076
|
+
registerDestinationsCommands(program);
|
|
3077
|
+
registerPublishRecordsCommands(program);
|
|
2498
3078
|
registerBrandKitCommands(program);
|
|
2499
3079
|
registerContentTypeCommands(program);
|
|
2500
3080
|
registerPromptCommands(program);
|
|
@@ -2505,6 +3085,8 @@ registerCreditsCommands(program);
|
|
|
2505
3085
|
registerQuestionsCommands(program);
|
|
2506
3086
|
registerKBCommands(program);
|
|
2507
3087
|
registerPermissionsCommands(program);
|
|
3088
|
+
registerTagsCommands(program);
|
|
3089
|
+
registerProductLineCommands(program);
|
|
2508
3090
|
registerUpdateCommand(program);
|
|
2509
3091
|
async function main() {
|
|
2510
3092
|
const quiet = process.argv.includes("--quiet") || process.argv.includes("--output") && process.argv[process.argv.indexOf("--output") + 1] === "json";
|