@senso-ai/cli 0.9.0 → 0.11.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/README.md +35 -3
- package/dist/cli.js +898 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -124,9 +124,11 @@ Options: `--max-results <n>`
|
|
|
124
124
|
```
|
|
125
125
|
senso content list List all knowledge base items
|
|
126
126
|
senso content get <id> Get full content detail by ID
|
|
127
|
+
senso content versions <id> List version history for a content item
|
|
127
128
|
senso content delete <id> Delete content (knowledge base + external)
|
|
128
129
|
senso content unpublish <id> Unpublish and revert to draft
|
|
129
130
|
senso content verification List items in verification workflow
|
|
131
|
+
senso content verification-counts Counts by status + published-domain summaries
|
|
130
132
|
senso content reject <versionId> Reject a content version
|
|
131
133
|
senso content restore <versionId> Restore rejected version to draft
|
|
132
134
|
senso content owners <id> List owners of a content item
|
|
@@ -134,7 +136,16 @@ senso content set-owners <id> Replace owners (--user-ids)
|
|
|
134
136
|
senso content remove-owner <id> <userId> Remove a single owner
|
|
135
137
|
```
|
|
136
138
|
|
|
137
|
-
Options: `content list` supports `--limit`, `--offset`, `--search`, `--sort`. `content verification` supports `--limit`, `--offset`, `--search`, `--status`. `content reject` supports `--reason`.
|
|
139
|
+
Options: `content list` supports `--limit`, `--offset`, `--search`, `--sort`. `content verification` supports `--limit`, `--offset`, `--search`, `--status`, `--substatus`. `content reject` supports `--reason`.
|
|
140
|
+
|
|
141
|
+
### Generated Content (GEO)
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
senso generated-content list List published or draft generated content (--status published|drafts)
|
|
145
|
+
senso generated-content get <id> Get a generated content item with its rendered body
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Options: `generated-content list` supports `--status`, `--limit`, `--offset`, `--search`.
|
|
138
149
|
|
|
139
150
|
### Content Generation
|
|
140
151
|
|
|
@@ -152,6 +163,24 @@ senso engine publish Publish content to external destinations (-
|
|
|
152
163
|
senso engine draft Save content as draft for review (--data)
|
|
153
164
|
```
|
|
154
165
|
|
|
166
|
+
### Tracked Competitors & Sources
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
senso competitors list List tracked competitors
|
|
170
|
+
senso competitors add Add a competitor (--name, --url)
|
|
171
|
+
senso competitors suggest Get AI-generated competitor suggestions
|
|
172
|
+
senso competitors batch-add Add up to 50 competitors (--data)
|
|
173
|
+
senso competitors update <id> Update a competitor (--name, --url)
|
|
174
|
+
senso competitors delete <id> Remove a competitor
|
|
175
|
+
|
|
176
|
+
senso tracked-sources list List citation-classification rules
|
|
177
|
+
senso tracked-sources add Add a rule (--pattern, --match-type, --tier)
|
|
178
|
+
senso tracked-sources update <id> Replace a rule (--pattern, --match-type, --tier)
|
|
179
|
+
senso tracked-sources delete <id> Remove a rule
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Options: `tracked-sources add`/`update` support `--category`, `--label`, `--priority`; `update` also supports `--active`/`--no-active`. `--match-type` is one of `domain | host | path_prefix | exact_url`; `--tier` is one of `primary | tracked | secondary`.
|
|
183
|
+
|
|
155
184
|
### Ingestion
|
|
156
185
|
|
|
157
186
|
```
|
|
@@ -344,12 +373,15 @@ npm test
|
|
|
344
373
|
```
|
|
345
374
|
src/
|
|
346
375
|
├── cli.ts # Entry point — arg parsing, command dispatch
|
|
347
|
-
├── commands/ # One file per command group
|
|
376
|
+
├── commands/ # One file per command group
|
|
348
377
|
│ ├── auth.ts # login, logout, whoami
|
|
349
378
|
│ ├── search.ts # search, search context, search content
|
|
350
|
-
│ ├── content.ts # CRUD + verification + owners
|
|
379
|
+
│ ├── content.ts # CRUD + versions + verification + owners
|
|
380
|
+
│ ├── generated-content.ts # GEO generated content (list, get)
|
|
351
381
|
│ ├── generate.ts # content generation settings + triggers
|
|
352
382
|
│ ├── engine.ts # publish, draft
|
|
383
|
+
│ ├── competitors.ts # tracked competitors CRUD + suggest
|
|
384
|
+
│ ├── tracked-sources.ts # citation-classification rules CRUD
|
|
353
385
|
│ ├── ingest.ts # upload, reprocess (with S3 upload)
|
|
354
386
|
│ ├── brand-kit.ts # get, set
|
|
355
387
|
│ ├── content-types.ts # CRUD
|
package/dist/cli.js
CHANGED
|
@@ -499,6 +499,29 @@ function registerOrgCommands(program2) {
|
|
|
499
499
|
process.exit(1);
|
|
500
500
|
}
|
|
501
501
|
});
|
|
502
|
+
org.command("set-runs").description("Toggle the org-wide runs master switch. Pause every scheduled prompt run and content-generation run, or re-enable them.").requiredOption("--enabled <bool>", "Set to true or false").action(async (cmdOpts) => {
|
|
503
|
+
const opts = program2.opts();
|
|
504
|
+
const raw = cmdOpts.enabled.toLowerCase();
|
|
505
|
+
if (raw !== "true" && raw !== "false") {
|
|
506
|
+
error("--enabled must be `true` or `false`.");
|
|
507
|
+
process.exit(1);
|
|
508
|
+
}
|
|
509
|
+
const enable = raw === "true";
|
|
510
|
+
try {
|
|
511
|
+
const data = await apiRequest({
|
|
512
|
+
method: "PATCH",
|
|
513
|
+
path: "/org/me/runs-enabled",
|
|
514
|
+
body: { enable_runs: enable },
|
|
515
|
+
apiKey: opts.apiKey,
|
|
516
|
+
baseUrl: opts.baseUrl
|
|
517
|
+
});
|
|
518
|
+
success(`Org-wide runs ${enable ? "enabled" : "disabled"}.`);
|
|
519
|
+
console.log(JSON.stringify(data, null, 2));
|
|
520
|
+
} catch (err) {
|
|
521
|
+
error(formatApiError(err));
|
|
522
|
+
process.exit(1);
|
|
523
|
+
}
|
|
524
|
+
});
|
|
502
525
|
}
|
|
503
526
|
|
|
504
527
|
// src/commands/users.ts
|
|
@@ -591,6 +614,50 @@ function registerUserCommands(program2) {
|
|
|
591
614
|
process.exit(1);
|
|
592
615
|
}
|
|
593
616
|
});
|
|
617
|
+
users.command("invite").description("Invite a brand-new user by email. Creates the user (in Clerk and Senso) and adds them to the organization with the given role. Use `roles list` to find a role_id. If the email already belongs to a Senso user, use `users invite-existing` instead.").requiredOption("--email <email>", "User's email address").requiredOption("--given-name <name>", "First name").requiredOption("--family-name <name>", "Last name").requiredOption("--role-id <uuid>", "Role to assign \u2014 resolve with `senso roles list`").option("--is-current", "Make this org the new user's current org").action(async (cmdOpts) => {
|
|
618
|
+
const opts = program2.opts();
|
|
619
|
+
try {
|
|
620
|
+
const data = await apiRequest({
|
|
621
|
+
method: "POST",
|
|
622
|
+
path: "/org/users/invite",
|
|
623
|
+
body: {
|
|
624
|
+
email: cmdOpts.email,
|
|
625
|
+
given_name: cmdOpts.givenName,
|
|
626
|
+
family_name: cmdOpts.familyName,
|
|
627
|
+
role_id: cmdOpts.roleId,
|
|
628
|
+
is_current: cmdOpts.isCurrent ?? false
|
|
629
|
+
},
|
|
630
|
+
apiKey: opts.apiKey,
|
|
631
|
+
baseUrl: opts.baseUrl
|
|
632
|
+
});
|
|
633
|
+
success(`Invited ${cmdOpts.email} to the organization.`);
|
|
634
|
+
console.log(JSON.stringify(data, null, 2));
|
|
635
|
+
} catch (err) {
|
|
636
|
+
error(formatApiError(err));
|
|
637
|
+
process.exit(1);
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
users.command("invite-existing").description("Add an existing Senso user to the organization by email. Returns 404 if no user with that email exists \u2014 use `users invite` for brand-new users.").requiredOption("--email <email>", "Email of an existing Senso user").requiredOption("--role-id <uuid>", "Role to assign \u2014 resolve with `senso roles list`").option("--is-current", "Make this org the user's current org").action(async (cmdOpts) => {
|
|
641
|
+
const opts = program2.opts();
|
|
642
|
+
try {
|
|
643
|
+
const data = await apiRequest({
|
|
644
|
+
method: "POST",
|
|
645
|
+
path: "/org/users/invite/existing",
|
|
646
|
+
body: {
|
|
647
|
+
email: cmdOpts.email,
|
|
648
|
+
role_id: cmdOpts.roleId,
|
|
649
|
+
is_current: cmdOpts.isCurrent ?? false
|
|
650
|
+
},
|
|
651
|
+
apiKey: opts.apiKey,
|
|
652
|
+
baseUrl: opts.baseUrl
|
|
653
|
+
});
|
|
654
|
+
success(`Added ${cmdOpts.email} to the organization.`);
|
|
655
|
+
console.log(JSON.stringify(data, null, 2));
|
|
656
|
+
} catch (err) {
|
|
657
|
+
error(formatApiError(err));
|
|
658
|
+
process.exit(1);
|
|
659
|
+
}
|
|
660
|
+
});
|
|
594
661
|
}
|
|
595
662
|
|
|
596
663
|
// src/commands/api-keys.ts
|
|
@@ -1295,6 +1362,27 @@ function registerIngestCommands(program2) {
|
|
|
1295
1362
|
|
|
1296
1363
|
// src/commands/content.ts
|
|
1297
1364
|
import pc9 from "picocolors";
|
|
1365
|
+
|
|
1366
|
+
// src/lib/tag-args.ts
|
|
1367
|
+
function parseCsv(v) {
|
|
1368
|
+
if (!v) return [];
|
|
1369
|
+
return v.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1370
|
+
}
|
|
1371
|
+
function buildSetTagsBody(cmdOpts) {
|
|
1372
|
+
const body = {};
|
|
1373
|
+
const names = parseCsv(cmdOpts.names);
|
|
1374
|
+
const ids = parseCsv(cmdOpts.ids);
|
|
1375
|
+
if (names.length > 0) body.tag_names = names;
|
|
1376
|
+
if (ids.length > 0) body.tag_ids = ids;
|
|
1377
|
+
return body;
|
|
1378
|
+
}
|
|
1379
|
+
function buildAttachTagBody(cmdOpts) {
|
|
1380
|
+
if (cmdOpts.id) return { tag_id: cmdOpts.id };
|
|
1381
|
+
if (cmdOpts.name) return { tag_name: cmdOpts.name };
|
|
1382
|
+
return null;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
// src/commands/content.ts
|
|
1298
1386
|
function registerContentCommands(program2) {
|
|
1299
1387
|
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
1388
|
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,22 +1436,62 @@ function registerContentCommands(program2) {
|
|
|
1348
1436
|
process.exit(1);
|
|
1349
1437
|
}
|
|
1350
1438
|
});
|
|
1351
|
-
content.command("unpublish <id>").description("Unpublish a content item.
|
|
1439
|
+
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
1440
|
const opts = program2.opts();
|
|
1353
1441
|
try {
|
|
1354
|
-
|
|
1355
|
-
|
|
1442
|
+
const body = cmdOpts.publishRecordIds && cmdOpts.publishRecordIds.length > 0 ? { publish_record_ids: cmdOpts.publishRecordIds } : void 0;
|
|
1443
|
+
const data = await apiRequest({
|
|
1444
|
+
method: "POST",
|
|
1445
|
+
path: `/org/content/${id}/unpublish`,
|
|
1446
|
+
body,
|
|
1447
|
+
apiKey: opts.apiKey,
|
|
1448
|
+
baseUrl: opts.baseUrl
|
|
1449
|
+
});
|
|
1450
|
+
success(
|
|
1451
|
+
cmdOpts.publishRecordIds && cmdOpts.publishRecordIds.length > 0 ? `Unpublished ${cmdOpts.publishRecordIds.length} record(s) from content ${id}.` : `Content ${id} unpublished.`
|
|
1452
|
+
);
|
|
1453
|
+
if (data) {
|
|
1454
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1455
|
+
}
|
|
1356
1456
|
} catch (err) {
|
|
1357
1457
|
error(formatApiError(err));
|
|
1358
1458
|
process.exit(1);
|
|
1359
1459
|
}
|
|
1360
1460
|
});
|
|
1361
|
-
content.command("verification").description("List content items in the verification workflow. Filter by editorial status (draft, review, rejected, published) to manage the review pipeline.").option("--limit <n>", "Maximum items to return").option("--offset <n>", "Number of items to skip (for pagination)").option("--search <query>", "Filter by title").option("--status <status>", "Filter by status: all, draft, review, rejected, published").action(async (cmdOpts) => {
|
|
1461
|
+
content.command("verification").description("List content items in the verification workflow. Filter by editorial status (draft, review, rejected, published) to manage the review pipeline.").option("--limit <n>", "Maximum items to return").option("--offset <n>", "Number of items to skip (for pagination)").option("--search <query>", "Filter by title").option("--status <status>", "Filter by status: all, draft, review, rejected, published").option("--substatus <substatus>", "Narrow further (only valid with --status published): pending_draft").action(async (cmdOpts) => {
|
|
1362
1462
|
const opts = program2.opts();
|
|
1363
1463
|
try {
|
|
1364
1464
|
const data = await apiRequest({
|
|
1365
1465
|
path: "/org/content/verification",
|
|
1366
|
-
params: { limit: cmdOpts.limit, offset: cmdOpts.offset, search: cmdOpts.search, status: cmdOpts.status },
|
|
1466
|
+
params: { limit: cmdOpts.limit, offset: cmdOpts.offset, search: cmdOpts.search, status: cmdOpts.status, substatus: cmdOpts.substatus },
|
|
1467
|
+
apiKey: opts.apiKey,
|
|
1468
|
+
baseUrl: opts.baseUrl
|
|
1469
|
+
});
|
|
1470
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1471
|
+
} catch (err) {
|
|
1472
|
+
error(formatApiError(err));
|
|
1473
|
+
process.exit(1);
|
|
1474
|
+
}
|
|
1475
|
+
});
|
|
1476
|
+
content.command("verification-counts").description("Get counts of content by editorial status (draft, published, rejected, pending published-draft) plus per-destination published-domain summaries. A lightweight alternative to paging through 'content verification'.").action(async () => {
|
|
1477
|
+
const opts = program2.opts();
|
|
1478
|
+
try {
|
|
1479
|
+
const data = await apiRequest({
|
|
1480
|
+
path: "/org/content/verification/counts",
|
|
1481
|
+
apiKey: opts.apiKey,
|
|
1482
|
+
baseUrl: opts.baseUrl
|
|
1483
|
+
});
|
|
1484
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1485
|
+
} catch (err) {
|
|
1486
|
+
error(formatApiError(err));
|
|
1487
|
+
process.exit(1);
|
|
1488
|
+
}
|
|
1489
|
+
});
|
|
1490
|
+
content.command("versions <id>").description("List the version history for a content item, newest first. The current version is flagged with is_current.").action(async (id) => {
|
|
1491
|
+
const opts = program2.opts();
|
|
1492
|
+
try {
|
|
1493
|
+
const data = await apiRequest({
|
|
1494
|
+
path: `/org/content/${id}/versions`,
|
|
1367
1495
|
apiKey: opts.apiKey,
|
|
1368
1496
|
baseUrl: opts.baseUrl
|
|
1369
1497
|
});
|
|
@@ -1430,6 +1558,85 @@ function registerContentCommands(program2) {
|
|
|
1430
1558
|
process.exit(1);
|
|
1431
1559
|
}
|
|
1432
1560
|
});
|
|
1561
|
+
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.");
|
|
1562
|
+
tags.command("list <id>").description("List tags attached to a content item.").action(async (id) => {
|
|
1563
|
+
const opts = program2.opts();
|
|
1564
|
+
try {
|
|
1565
|
+
const data = await apiRequest({ path: `/org/content/${id}/tags`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1566
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1567
|
+
} catch (err) {
|
|
1568
|
+
error(formatApiError(err));
|
|
1569
|
+
process.exit(1);
|
|
1570
|
+
}
|
|
1571
|
+
});
|
|
1572
|
+
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) => {
|
|
1573
|
+
const opts = program2.opts();
|
|
1574
|
+
try {
|
|
1575
|
+
const body = buildSetTagsBody(cmdOpts);
|
|
1576
|
+
const data = await apiRequest({
|
|
1577
|
+
method: "PUT",
|
|
1578
|
+
path: `/org/content/${id}/tags`,
|
|
1579
|
+
body,
|
|
1580
|
+
apiKey: opts.apiKey,
|
|
1581
|
+
baseUrl: opts.baseUrl
|
|
1582
|
+
});
|
|
1583
|
+
success(`Content ${id} tags updated.`);
|
|
1584
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1585
|
+
} catch (err) {
|
|
1586
|
+
error(formatApiError(err));
|
|
1587
|
+
process.exit(1);
|
|
1588
|
+
}
|
|
1589
|
+
});
|
|
1590
|
+
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) => {
|
|
1591
|
+
const opts = program2.opts();
|
|
1592
|
+
const body = buildAttachTagBody(cmdOpts);
|
|
1593
|
+
if (!body) {
|
|
1594
|
+
error("Provide --name or --id.");
|
|
1595
|
+
process.exit(1);
|
|
1596
|
+
}
|
|
1597
|
+
try {
|
|
1598
|
+
await apiRequest({
|
|
1599
|
+
method: "POST",
|
|
1600
|
+
path: `/org/content/${id}/tags`,
|
|
1601
|
+
body,
|
|
1602
|
+
apiKey: opts.apiKey,
|
|
1603
|
+
baseUrl: opts.baseUrl
|
|
1604
|
+
});
|
|
1605
|
+
success(`Tag attached to content ${id}.`);
|
|
1606
|
+
} catch (err) {
|
|
1607
|
+
error(formatApiError(err));
|
|
1608
|
+
process.exit(1);
|
|
1609
|
+
}
|
|
1610
|
+
});
|
|
1611
|
+
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) => {
|
|
1612
|
+
const opts = program2.opts();
|
|
1613
|
+
if (!cmdOpts.name && !cmdOpts.id) {
|
|
1614
|
+
error("Provide --name or --id.");
|
|
1615
|
+
process.exit(1);
|
|
1616
|
+
}
|
|
1617
|
+
try {
|
|
1618
|
+
if (cmdOpts.id) {
|
|
1619
|
+
await apiRequest({
|
|
1620
|
+
method: "DELETE",
|
|
1621
|
+
path: `/org/content/${id}/tags/${cmdOpts.id}`,
|
|
1622
|
+
apiKey: opts.apiKey,
|
|
1623
|
+
baseUrl: opts.baseUrl
|
|
1624
|
+
});
|
|
1625
|
+
} else {
|
|
1626
|
+
await apiRequest({
|
|
1627
|
+
method: "DELETE",
|
|
1628
|
+
path: `/org/content/${id}/tags`,
|
|
1629
|
+
params: { name: cmdOpts.name },
|
|
1630
|
+
apiKey: opts.apiKey,
|
|
1631
|
+
baseUrl: opts.baseUrl
|
|
1632
|
+
});
|
|
1633
|
+
}
|
|
1634
|
+
success(`Tag detached from content ${id}.`);
|
|
1635
|
+
} catch (err) {
|
|
1636
|
+
error(formatApiError(err));
|
|
1637
|
+
process.exit(1);
|
|
1638
|
+
}
|
|
1639
|
+
});
|
|
1433
1640
|
}
|
|
1434
1641
|
|
|
1435
1642
|
// src/commands/generate.ts
|
|
@@ -1557,12 +1764,12 @@ function registerGenerateCommands(program2) {
|
|
|
1557
1764
|
process.exit(1);
|
|
1558
1765
|
}
|
|
1559
1766
|
});
|
|
1560
|
-
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) => {
|
|
1767
|
+
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").option("--status <status>", "Filter by item status: pending, running, succeeded, failed, skipped, stopped").action(async (runId, cmdOpts) => {
|
|
1561
1768
|
const opts = program2.opts();
|
|
1562
1769
|
try {
|
|
1563
1770
|
const data = await apiRequest({
|
|
1564
1771
|
path: `/org/content-generation/runs/${runId}/items`,
|
|
1565
|
-
params: { limit: cmdOpts.limit, offset: cmdOpts.offset },
|
|
1772
|
+
params: { limit: cmdOpts.limit, offset: cmdOpts.offset, status: cmdOpts.status },
|
|
1566
1773
|
apiKey: opts.apiKey,
|
|
1567
1774
|
baseUrl: opts.baseUrl
|
|
1568
1775
|
});
|
|
@@ -1614,11 +1821,14 @@ function sleep(ms) {
|
|
|
1614
1821
|
|
|
1615
1822
|
// src/commands/engine.ts
|
|
1616
1823
|
function registerEngineCommands(program2) {
|
|
1617
|
-
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.");
|
|
1618
|
-
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) => {
|
|
1824
|
+
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.");
|
|
1825
|
+
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. To record content as already published externally rather than pushing it to destinations, set mark_as_published (and optionally manual_published_at) in --data.").requiredOption("--data <json>", 'JSON: { "geo_question_id": "uuid", "raw_markdown": "...", "seo_title": "...", "summary": "...", "publisher_ids": ["<uuid>", ...], "mark_as_published": false, "manual_published_at": "2026-06-11T00:00:00Z" }').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) => {
|
|
1619
1826
|
const opts = program2.opts();
|
|
1620
1827
|
try {
|
|
1621
1828
|
const body = JSON.parse(cmdOpts.data);
|
|
1829
|
+
if (cmdOpts.publisherIds && cmdOpts.publisherIds.length > 0) {
|
|
1830
|
+
body.publisher_ids = cmdOpts.publisherIds;
|
|
1831
|
+
}
|
|
1622
1832
|
const data = await apiRequest({
|
|
1623
1833
|
method: "POST",
|
|
1624
1834
|
path: "/org/content-engine/publish",
|
|
@@ -1633,7 +1843,7 @@ function registerEngineCommands(program2) {
|
|
|
1633
1843
|
process.exit(1);
|
|
1634
1844
|
}
|
|
1635
1845
|
});
|
|
1636
|
-
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) => {
|
|
1846
|
+
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) => {
|
|
1637
1847
|
const opts = program2.opts();
|
|
1638
1848
|
try {
|
|
1639
1849
|
const body = JSON.parse(cmdOpts.data);
|
|
@@ -1653,9 +1863,102 @@ function registerEngineCommands(program2) {
|
|
|
1653
1863
|
});
|
|
1654
1864
|
}
|
|
1655
1865
|
|
|
1866
|
+
// src/commands/destinations.ts
|
|
1867
|
+
var SUPPORTED_TYPES = ["citeables", "codeables", "cucopilot"];
|
|
1868
|
+
var REMOVE_ACTIONS = ["leave", "unpublish", "delete"];
|
|
1869
|
+
function registerDestinationsCommands(program2) {
|
|
1870
|
+
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.");
|
|
1871
|
+
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 () => {
|
|
1872
|
+
const opts = program2.opts();
|
|
1873
|
+
try {
|
|
1874
|
+
const data = await apiRequest({
|
|
1875
|
+
path: "/org/destinations",
|
|
1876
|
+
apiKey: opts.apiKey,
|
|
1877
|
+
baseUrl: opts.baseUrl
|
|
1878
|
+
});
|
|
1879
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1880
|
+
} catch (err) {
|
|
1881
|
+
error(formatApiError(err));
|
|
1882
|
+
process.exit(1);
|
|
1883
|
+
}
|
|
1884
|
+
});
|
|
1885
|
+
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) => {
|
|
1886
|
+
const opts = program2.opts();
|
|
1887
|
+
const type = cmdOpts.type.toLowerCase();
|
|
1888
|
+
if (!SUPPORTED_TYPES.includes(type)) {
|
|
1889
|
+
error(`Invalid --type "${cmdOpts.type}". Must be one of: ${SUPPORTED_TYPES.join(", ")}.`);
|
|
1890
|
+
process.exit(1);
|
|
1891
|
+
}
|
|
1892
|
+
try {
|
|
1893
|
+
const data = await apiRequest({
|
|
1894
|
+
method: "POST",
|
|
1895
|
+
path: "/org/destinations",
|
|
1896
|
+
body: {
|
|
1897
|
+
type,
|
|
1898
|
+
name: cmdOpts.name,
|
|
1899
|
+
domain: cmdOpts.domain
|
|
1900
|
+
},
|
|
1901
|
+
apiKey: opts.apiKey,
|
|
1902
|
+
baseUrl: opts.baseUrl
|
|
1903
|
+
});
|
|
1904
|
+
success(`Destination "${cmdOpts.name}" registered for ${cmdOpts.domain}.`);
|
|
1905
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1906
|
+
} catch (err) {
|
|
1907
|
+
error(formatApiError(err));
|
|
1908
|
+
process.exit(1);
|
|
1909
|
+
}
|
|
1910
|
+
});
|
|
1911
|
+
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) => {
|
|
1912
|
+
const opts = program2.opts();
|
|
1913
|
+
const action = cmdOpts.action.toLowerCase();
|
|
1914
|
+
if (!REMOVE_ACTIONS.includes(action)) {
|
|
1915
|
+
error(`Invalid --action "${cmdOpts.action}". Must be one of: ${REMOVE_ACTIONS.join(", ")}.`);
|
|
1916
|
+
process.exit(1);
|
|
1917
|
+
}
|
|
1918
|
+
try {
|
|
1919
|
+
const data = await apiRequest({
|
|
1920
|
+
method: "POST",
|
|
1921
|
+
path: `/org/destinations/${publisherId}/remove`,
|
|
1922
|
+
body: {
|
|
1923
|
+
action,
|
|
1924
|
+
also_remove_destination: cmdOpts.alsoRemoveDestination ?? false,
|
|
1925
|
+
keep_domain: cmdOpts.keepDomain ?? false
|
|
1926
|
+
},
|
|
1927
|
+
apiKey: opts.apiKey,
|
|
1928
|
+
baseUrl: opts.baseUrl
|
|
1929
|
+
});
|
|
1930
|
+
success(`Destination ${publisherId} removed (action: ${action}).`);
|
|
1931
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1932
|
+
} catch (err) {
|
|
1933
|
+
error(formatApiError(err));
|
|
1934
|
+
process.exit(1);
|
|
1935
|
+
}
|
|
1936
|
+
});
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
// src/commands/publish-records.ts
|
|
1940
|
+
function registerPublishRecordsCommands(program2) {
|
|
1941
|
+
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.");
|
|
1942
|
+
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) => {
|
|
1943
|
+
const opts = program2.opts();
|
|
1944
|
+
try {
|
|
1945
|
+
await apiRequest({
|
|
1946
|
+
method: "POST",
|
|
1947
|
+
path: `/org/publish-records/${publishRecordId}/retry`,
|
|
1948
|
+
apiKey: opts.apiKey,
|
|
1949
|
+
baseUrl: opts.baseUrl
|
|
1950
|
+
});
|
|
1951
|
+
success(`Publish record ${publishRecordId} retry completed.`);
|
|
1952
|
+
} catch (err) {
|
|
1953
|
+
error(formatApiError(err));
|
|
1954
|
+
process.exit(1);
|
|
1955
|
+
}
|
|
1956
|
+
});
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1656
1959
|
// src/commands/brand-kit.ts
|
|
1657
1960
|
function registerBrandKitCommands(program2) {
|
|
1658
|
-
const bk = program2.command("brand-kit").description("Manage the organization's brand kit guidelines
|
|
1961
|
+
const bk = program2.command("brand-kit").description("Manage the organization's brand kit guidelines that inform AI content generation about your brand voice, tone, and style. The guidelines object accepts a defined set of keys: brand_name, brand_domain, brand_description, voice_and_tone, author_persona, and global_writing_rules (array). Unknown keys are rejected.");
|
|
1659
1962
|
bk.command("get").description("Get the current brand kit guidelines.").action(async () => {
|
|
1660
1963
|
const opts = program2.opts();
|
|
1661
1964
|
try {
|
|
@@ -1722,7 +2025,7 @@ function registerContentTypeCommands(program2) {
|
|
|
1722
2025
|
process.exit(1);
|
|
1723
2026
|
}
|
|
1724
2027
|
});
|
|
1725
|
-
ct.command("create").description("Create a new content type. Requires a name and
|
|
2028
|
+
ct.command("create").description("Create a new content type. Requires a name and a config defining the output structure. config accepts a defined set of keys: template, template_spec, cta_text, cta_destination, writing_rules (array). Unknown keys are rejected.").requiredOption("--data <json>", 'JSON: { "name": "Blog Post", "config": { "template": "...", "cta_text": "...", "cta_destination": "...", "writing_rules": [] } }').action(async (cmdOpts) => {
|
|
1726
2029
|
const opts = program2.opts();
|
|
1727
2030
|
try {
|
|
1728
2031
|
const body = JSON.parse(cmdOpts.data);
|
|
@@ -1854,6 +2157,85 @@ function registerPromptCommands(program2) {
|
|
|
1854
2157
|
process.exit(1);
|
|
1855
2158
|
}
|
|
1856
2159
|
});
|
|
2160
|
+
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.");
|
|
2161
|
+
tags.command("list <promptId>").description("List tags attached to a prompt.").action(async (promptId) => {
|
|
2162
|
+
const opts = program2.opts();
|
|
2163
|
+
try {
|
|
2164
|
+
const data = await apiRequest({ path: `/org/prompts/${promptId}/tags`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
2165
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2166
|
+
} catch (err) {
|
|
2167
|
+
error(formatApiError(err));
|
|
2168
|
+
process.exit(1);
|
|
2169
|
+
}
|
|
2170
|
+
});
|
|
2171
|
+
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) => {
|
|
2172
|
+
const opts = program2.opts();
|
|
2173
|
+
try {
|
|
2174
|
+
const body = buildSetTagsBody(cmdOpts);
|
|
2175
|
+
const data = await apiRequest({
|
|
2176
|
+
method: "PUT",
|
|
2177
|
+
path: `/org/prompts/${promptId}/tags`,
|
|
2178
|
+
body,
|
|
2179
|
+
apiKey: opts.apiKey,
|
|
2180
|
+
baseUrl: opts.baseUrl
|
|
2181
|
+
});
|
|
2182
|
+
success(`Prompt ${promptId} tags updated.`);
|
|
2183
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2184
|
+
} catch (err) {
|
|
2185
|
+
error(formatApiError(err));
|
|
2186
|
+
process.exit(1);
|
|
2187
|
+
}
|
|
2188
|
+
});
|
|
2189
|
+
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) => {
|
|
2190
|
+
const opts = program2.opts();
|
|
2191
|
+
const body = buildAttachTagBody(cmdOpts);
|
|
2192
|
+
if (!body) {
|
|
2193
|
+
error("Provide --name or --id.");
|
|
2194
|
+
process.exit(1);
|
|
2195
|
+
}
|
|
2196
|
+
try {
|
|
2197
|
+
await apiRequest({
|
|
2198
|
+
method: "POST",
|
|
2199
|
+
path: `/org/prompts/${promptId}/tags`,
|
|
2200
|
+
body,
|
|
2201
|
+
apiKey: opts.apiKey,
|
|
2202
|
+
baseUrl: opts.baseUrl
|
|
2203
|
+
});
|
|
2204
|
+
success(`Tag attached to prompt ${promptId}.`);
|
|
2205
|
+
} catch (err) {
|
|
2206
|
+
error(formatApiError(err));
|
|
2207
|
+
process.exit(1);
|
|
2208
|
+
}
|
|
2209
|
+
});
|
|
2210
|
+
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) => {
|
|
2211
|
+
const opts = program2.opts();
|
|
2212
|
+
if (!cmdOpts.name && !cmdOpts.id) {
|
|
2213
|
+
error("Provide --name or --id.");
|
|
2214
|
+
process.exit(1);
|
|
2215
|
+
}
|
|
2216
|
+
try {
|
|
2217
|
+
if (cmdOpts.id) {
|
|
2218
|
+
await apiRequest({
|
|
2219
|
+
method: "DELETE",
|
|
2220
|
+
path: `/org/prompts/${promptId}/tags/${cmdOpts.id}`,
|
|
2221
|
+
apiKey: opts.apiKey,
|
|
2222
|
+
baseUrl: opts.baseUrl
|
|
2223
|
+
});
|
|
2224
|
+
} else {
|
|
2225
|
+
await apiRequest({
|
|
2226
|
+
method: "DELETE",
|
|
2227
|
+
path: `/org/prompts/${promptId}/tags`,
|
|
2228
|
+
params: { name: cmdOpts.name },
|
|
2229
|
+
apiKey: opts.apiKey,
|
|
2230
|
+
baseUrl: opts.baseUrl
|
|
2231
|
+
});
|
|
2232
|
+
}
|
|
2233
|
+
success(`Tag detached from prompt ${promptId}.`);
|
|
2234
|
+
} catch (err) {
|
|
2235
|
+
error(formatApiError(err));
|
|
2236
|
+
process.exit(1);
|
|
2237
|
+
}
|
|
2238
|
+
});
|
|
1857
2239
|
}
|
|
1858
2240
|
|
|
1859
2241
|
// src/commands/run-config.ts
|
|
@@ -2127,7 +2509,7 @@ function registerQuestionsCommands(program2) {
|
|
|
2127
2509
|
process.exit(1);
|
|
2128
2510
|
}
|
|
2129
2511
|
});
|
|
2130
|
-
questions.command("patch <questionId>").description("Partially update a question.
|
|
2512
|
+
questions.command("patch <questionId>").description("Partially update a question. Supports updating tag associations and/or the funnel stage (type). At least one of tag_ids or type must be provided.").requiredOption("--data <json>", 'JSON: { "tag_ids": ["<uuid>", ...], "type": "decision|consideration|awareness|evaluation" } \u2014 pass tag_ids: null to clear all tags').action(async (questionId, cmdOpts) => {
|
|
2131
2513
|
const opts = program2.opts();
|
|
2132
2514
|
try {
|
|
2133
2515
|
const body = JSON.parse(cmdOpts.data);
|
|
@@ -2469,15 +2851,503 @@ function registerKBCommands(program2) {
|
|
|
2469
2851
|
process.exit(1);
|
|
2470
2852
|
}
|
|
2471
2853
|
});
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
// src/commands/permissions.ts
|
|
2475
|
-
function registerPermissionsCommands(program2) {
|
|
2476
|
-
const perms = program2.command("permissions").description("View available role permissions for the organization.");
|
|
2477
|
-
perms.command("list").description("List all available permission keys with their names, descriptions, and categories. Useful for building role management UIs.").action(async () => {
|
|
2854
|
+
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.");
|
|
2855
|
+
tags.command("list <id>").description("List tags attached to a KB node.").action(async (id) => {
|
|
2478
2856
|
const opts = program2.opts();
|
|
2479
2857
|
try {
|
|
2480
|
-
const data = await apiRequest({ path:
|
|
2858
|
+
const data = await apiRequest({ path: `/org/kb/nodes/${id}/tags`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
2859
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2860
|
+
} catch (err) {
|
|
2861
|
+
error(formatApiError(err));
|
|
2862
|
+
process.exit(1);
|
|
2863
|
+
}
|
|
2864
|
+
});
|
|
2865
|
+
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) => {
|
|
2866
|
+
const opts = program2.opts();
|
|
2867
|
+
try {
|
|
2868
|
+
const body = buildSetTagsBody(cmdOpts);
|
|
2869
|
+
const data = await apiRequest({
|
|
2870
|
+
method: "PUT",
|
|
2871
|
+
path: `/org/kb/nodes/${id}/tags`,
|
|
2872
|
+
body,
|
|
2873
|
+
apiKey: opts.apiKey,
|
|
2874
|
+
baseUrl: opts.baseUrl
|
|
2875
|
+
});
|
|
2876
|
+
success(`KB node ${id} tags updated.`);
|
|
2877
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2878
|
+
} catch (err) {
|
|
2879
|
+
error(formatApiError(err));
|
|
2880
|
+
process.exit(1);
|
|
2881
|
+
}
|
|
2882
|
+
});
|
|
2883
|
+
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) => {
|
|
2884
|
+
const opts = program2.opts();
|
|
2885
|
+
const body = buildAttachTagBody(cmdOpts);
|
|
2886
|
+
if (!body) {
|
|
2887
|
+
error("Provide --name or --id.");
|
|
2888
|
+
process.exit(1);
|
|
2889
|
+
}
|
|
2890
|
+
try {
|
|
2891
|
+
await apiRequest({
|
|
2892
|
+
method: "POST",
|
|
2893
|
+
path: `/org/kb/nodes/${id}/tags`,
|
|
2894
|
+
body,
|
|
2895
|
+
apiKey: opts.apiKey,
|
|
2896
|
+
baseUrl: opts.baseUrl
|
|
2897
|
+
});
|
|
2898
|
+
success(`Tag attached to KB node ${id}.`);
|
|
2899
|
+
} catch (err) {
|
|
2900
|
+
error(formatApiError(err));
|
|
2901
|
+
process.exit(1);
|
|
2902
|
+
}
|
|
2903
|
+
});
|
|
2904
|
+
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) => {
|
|
2905
|
+
const opts = program2.opts();
|
|
2906
|
+
if (!cmdOpts.name && !cmdOpts.id) {
|
|
2907
|
+
error("Provide --name or --id.");
|
|
2908
|
+
process.exit(1);
|
|
2909
|
+
}
|
|
2910
|
+
try {
|
|
2911
|
+
if (cmdOpts.id) {
|
|
2912
|
+
await apiRequest({
|
|
2913
|
+
method: "DELETE",
|
|
2914
|
+
path: `/org/kb/nodes/${id}/tags/${cmdOpts.id}`,
|
|
2915
|
+
apiKey: opts.apiKey,
|
|
2916
|
+
baseUrl: opts.baseUrl
|
|
2917
|
+
});
|
|
2918
|
+
} else {
|
|
2919
|
+
await apiRequest({
|
|
2920
|
+
method: "DELETE",
|
|
2921
|
+
path: `/org/kb/nodes/${id}/tags`,
|
|
2922
|
+
params: { name: cmdOpts.name },
|
|
2923
|
+
apiKey: opts.apiKey,
|
|
2924
|
+
baseUrl: opts.baseUrl
|
|
2925
|
+
});
|
|
2926
|
+
}
|
|
2927
|
+
success(`Tag detached from KB node ${id}.`);
|
|
2928
|
+
} catch (err) {
|
|
2929
|
+
error(formatApiError(err));
|
|
2930
|
+
process.exit(1);
|
|
2931
|
+
}
|
|
2932
|
+
});
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2935
|
+
// src/commands/permissions.ts
|
|
2936
|
+
function registerPermissionsCommands(program2) {
|
|
2937
|
+
const perms = program2.command("permissions").description("View available role permissions for the organization.");
|
|
2938
|
+
perms.command("list").description("List all available permission keys with their names, descriptions, and categories. Useful for building role management UIs.").action(async () => {
|
|
2939
|
+
const opts = program2.opts();
|
|
2940
|
+
try {
|
|
2941
|
+
const data = await apiRequest({ path: "/org/permissions", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
2942
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2943
|
+
} catch (err) {
|
|
2944
|
+
error(formatApiError(err));
|
|
2945
|
+
process.exit(1);
|
|
2946
|
+
}
|
|
2947
|
+
});
|
|
2948
|
+
}
|
|
2949
|
+
|
|
2950
|
+
// src/commands/product-lines.ts
|
|
2951
|
+
function registerProductLineCommands(program2) {
|
|
2952
|
+
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.");
|
|
2953
|
+
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) => {
|
|
2954
|
+
const opts = program2.opts();
|
|
2955
|
+
try {
|
|
2956
|
+
const data = await apiRequest({
|
|
2957
|
+
path: "/org/product-lines",
|
|
2958
|
+
params: { limit: cmdOpts.limit, offset: cmdOpts.offset },
|
|
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
|
+
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) => {
|
|
2969
|
+
const opts = program2.opts();
|
|
2970
|
+
try {
|
|
2971
|
+
const body = JSON.parse(cmdOpts.data);
|
|
2972
|
+
const data = await apiRequest({
|
|
2973
|
+
method: "POST",
|
|
2974
|
+
path: "/org/product-lines",
|
|
2975
|
+
body,
|
|
2976
|
+
apiKey: opts.apiKey,
|
|
2977
|
+
baseUrl: opts.baseUrl
|
|
2978
|
+
});
|
|
2979
|
+
success("Product line created.");
|
|
2980
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2981
|
+
} catch (err) {
|
|
2982
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
2983
|
+
process.exit(1);
|
|
2984
|
+
}
|
|
2985
|
+
});
|
|
2986
|
+
pl.command("get <id>").description("Get a product line by ID.").action(async (id) => {
|
|
2987
|
+
const opts = program2.opts();
|
|
2988
|
+
try {
|
|
2989
|
+
const data = await apiRequest({ path: `/org/product-lines/${id}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
2990
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2991
|
+
} catch (err) {
|
|
2992
|
+
error(formatApiError(err));
|
|
2993
|
+
process.exit(1);
|
|
2994
|
+
}
|
|
2995
|
+
});
|
|
2996
|
+
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) => {
|
|
2997
|
+
const opts = program2.opts();
|
|
2998
|
+
try {
|
|
2999
|
+
const body = JSON.parse(cmdOpts.data);
|
|
3000
|
+
const data = await apiRequest({
|
|
3001
|
+
method: "PUT",
|
|
3002
|
+
path: `/org/product-lines/${id}`,
|
|
3003
|
+
body,
|
|
3004
|
+
apiKey: opts.apiKey,
|
|
3005
|
+
baseUrl: opts.baseUrl
|
|
3006
|
+
});
|
|
3007
|
+
success(`Product line ${id} updated.`);
|
|
3008
|
+
console.log(JSON.stringify(data, null, 2));
|
|
3009
|
+
} catch (err) {
|
|
3010
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
3011
|
+
process.exit(1);
|
|
3012
|
+
}
|
|
3013
|
+
});
|
|
3014
|
+
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) => {
|
|
3015
|
+
const opts = program2.opts();
|
|
3016
|
+
try {
|
|
3017
|
+
const body = JSON.parse(cmdOpts.data);
|
|
3018
|
+
const data = await apiRequest({
|
|
3019
|
+
method: "PATCH",
|
|
3020
|
+
path: `/org/product-lines/${id}`,
|
|
3021
|
+
body,
|
|
3022
|
+
apiKey: opts.apiKey,
|
|
3023
|
+
baseUrl: opts.baseUrl
|
|
3024
|
+
});
|
|
3025
|
+
success(`Product line ${id} updated.`);
|
|
3026
|
+
console.log(JSON.stringify(data, null, 2));
|
|
3027
|
+
} catch (err) {
|
|
3028
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
3029
|
+
process.exit(1);
|
|
3030
|
+
}
|
|
3031
|
+
});
|
|
3032
|
+
pl.command("delete <id>").description("Delete a product line. This cannot be undone.").action(async (id) => {
|
|
3033
|
+
const opts = program2.opts();
|
|
3034
|
+
try {
|
|
3035
|
+
await apiRequest({ method: "DELETE", path: `/org/product-lines/${id}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
3036
|
+
success(`Product line ${id} deleted.`);
|
|
3037
|
+
} catch (err) {
|
|
3038
|
+
error(formatApiError(err));
|
|
3039
|
+
process.exit(1);
|
|
3040
|
+
}
|
|
3041
|
+
});
|
|
3042
|
+
}
|
|
3043
|
+
|
|
3044
|
+
// src/commands/tags.ts
|
|
3045
|
+
function registerTagsCommands(program2) {
|
|
3046
|
+
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.");
|
|
3047
|
+
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) => {
|
|
3048
|
+
const opts = program2.opts();
|
|
3049
|
+
try {
|
|
3050
|
+
const data = await apiRequest({
|
|
3051
|
+
path: "/org/tags",
|
|
3052
|
+
params: cmdOpts.counts ? { counts: "true" } : {},
|
|
3053
|
+
apiKey: opts.apiKey,
|
|
3054
|
+
baseUrl: opts.baseUrl
|
|
3055
|
+
});
|
|
3056
|
+
console.log(JSON.stringify(data, null, 2));
|
|
3057
|
+
} catch (err) {
|
|
3058
|
+
error(formatApiError(err));
|
|
3059
|
+
process.exit(1);
|
|
3060
|
+
}
|
|
3061
|
+
});
|
|
3062
|
+
tags.command("create").description("Create a new tag. Tag names are unique per org (case-insensitive).").requiredOption("--name <name>", "Tag name").action(async (cmdOpts) => {
|
|
3063
|
+
const opts = program2.opts();
|
|
3064
|
+
try {
|
|
3065
|
+
const data = await apiRequest({
|
|
3066
|
+
method: "POST",
|
|
3067
|
+
path: "/org/tags",
|
|
3068
|
+
body: { name: cmdOpts.name },
|
|
3069
|
+
apiKey: opts.apiKey,
|
|
3070
|
+
baseUrl: opts.baseUrl
|
|
3071
|
+
});
|
|
3072
|
+
success(`Tag "${cmdOpts.name}" created.`);
|
|
3073
|
+
console.log(JSON.stringify(data, null, 2));
|
|
3074
|
+
} catch (err) {
|
|
3075
|
+
error(formatApiError(err));
|
|
3076
|
+
process.exit(1);
|
|
3077
|
+
}
|
|
3078
|
+
});
|
|
3079
|
+
tags.command("get <id>").description("Get a tag by ID, including usage counts.").action(async (id) => {
|
|
3080
|
+
const opts = program2.opts();
|
|
3081
|
+
try {
|
|
3082
|
+
const data = await apiRequest({ path: `/org/tags/${id}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
3083
|
+
console.log(JSON.stringify(data, null, 2));
|
|
3084
|
+
} catch (err) {
|
|
3085
|
+
error(formatApiError(err));
|
|
3086
|
+
process.exit(1);
|
|
3087
|
+
}
|
|
3088
|
+
});
|
|
3089
|
+
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) => {
|
|
3090
|
+
const opts = program2.opts();
|
|
3091
|
+
try {
|
|
3092
|
+
const data = await apiRequest({
|
|
3093
|
+
method: "PATCH",
|
|
3094
|
+
path: `/org/tags/${id}`,
|
|
3095
|
+
body: { name: cmdOpts.name },
|
|
3096
|
+
apiKey: opts.apiKey,
|
|
3097
|
+
baseUrl: opts.baseUrl
|
|
3098
|
+
});
|
|
3099
|
+
success(`Tag ${id} renamed to "${cmdOpts.name}".`);
|
|
3100
|
+
console.log(JSON.stringify(data, null, 2));
|
|
3101
|
+
} catch (err) {
|
|
3102
|
+
error(formatApiError(err));
|
|
3103
|
+
process.exit(1);
|
|
3104
|
+
}
|
|
3105
|
+
});
|
|
3106
|
+
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) => {
|
|
3107
|
+
const opts = program2.opts();
|
|
3108
|
+
try {
|
|
3109
|
+
await apiRequest({ method: "DELETE", path: `/org/tags/${id}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
3110
|
+
success(`Tag ${id} deleted.`);
|
|
3111
|
+
} catch (err) {
|
|
3112
|
+
error(formatApiError(err));
|
|
3113
|
+
process.exit(1);
|
|
3114
|
+
}
|
|
3115
|
+
});
|
|
3116
|
+
}
|
|
3117
|
+
|
|
3118
|
+
// src/commands/roles.ts
|
|
3119
|
+
function registerRolesCommands(program2) {
|
|
3120
|
+
const roles = program2.command("roles").description("Inspect the roles defined for your organization. Each organization has its own per-org role_ids \u2014 resolve a role name to its UUID here before passing role_id to `users invite`, `users add`, or `users update`.");
|
|
3121
|
+
roles.command("list").description("List every role for the current organization, including the built-in admin/collaborator/viewer roles and any custom roles.").action(async () => {
|
|
3122
|
+
const opts = program2.opts();
|
|
3123
|
+
try {
|
|
3124
|
+
const data = await apiRequest({
|
|
3125
|
+
path: "/org/roles",
|
|
3126
|
+
apiKey: opts.apiKey,
|
|
3127
|
+
baseUrl: opts.baseUrl
|
|
3128
|
+
});
|
|
3129
|
+
console.log(JSON.stringify(data, null, 2));
|
|
3130
|
+
} catch (err) {
|
|
3131
|
+
error(formatApiError(err));
|
|
3132
|
+
process.exit(1);
|
|
3133
|
+
}
|
|
3134
|
+
});
|
|
3135
|
+
}
|
|
3136
|
+
|
|
3137
|
+
// src/commands/competitors.ts
|
|
3138
|
+
function registerCompetitorsCommands(program2) {
|
|
3139
|
+
const competitors = program2.command("competitors").description("Manage the curated list of competitor brands your organization tracks. Tracked competitors feed downstream share-of-voice analytics and inform content-generation prompts.");
|
|
3140
|
+
competitors.command("list").description("List every tracked competitor for the current organization.").action(async () => {
|
|
3141
|
+
const opts = program2.opts();
|
|
3142
|
+
try {
|
|
3143
|
+
const data = await apiRequest({
|
|
3144
|
+
path: "/org/competitors",
|
|
3145
|
+
apiKey: opts.apiKey,
|
|
3146
|
+
baseUrl: opts.baseUrl
|
|
3147
|
+
});
|
|
3148
|
+
console.log(JSON.stringify(data, null, 2));
|
|
3149
|
+
} catch (err) {
|
|
3150
|
+
error(formatApiError(err));
|
|
3151
|
+
process.exit(1);
|
|
3152
|
+
}
|
|
3153
|
+
});
|
|
3154
|
+
competitors.command("add").description("Add a single tracked competitor.").requiredOption("--name <name>", "Competitor brand name").option("--url <url>", "Competitor website URL").action(async (cmdOpts) => {
|
|
3155
|
+
const opts = program2.opts();
|
|
3156
|
+
try {
|
|
3157
|
+
const body = { name: cmdOpts.name };
|
|
3158
|
+
if (cmdOpts.url) body.url = cmdOpts.url;
|
|
3159
|
+
const data = await apiRequest({
|
|
3160
|
+
method: "POST",
|
|
3161
|
+
path: "/org/competitors",
|
|
3162
|
+
body,
|
|
3163
|
+
apiKey: opts.apiKey,
|
|
3164
|
+
baseUrl: opts.baseUrl
|
|
3165
|
+
});
|
|
3166
|
+
success(`Tracked competitor "${cmdOpts.name}" added.`);
|
|
3167
|
+
console.log(JSON.stringify(data, null, 2));
|
|
3168
|
+
} catch (err) {
|
|
3169
|
+
error(formatApiError(err));
|
|
3170
|
+
process.exit(1);
|
|
3171
|
+
}
|
|
3172
|
+
});
|
|
3173
|
+
competitors.command("batch-add").description("Add up to 50 tracked competitors in one call. Designed for accepting AI-generated suggestions returned by `competitors suggest`.").requiredOption("--data <json>", 'JSON: { "items": [{ "name": "...", "url": "...", "source": "manual|suggested_run_text|suggested_web_search", "rationale": "...", "confidence": 0.85 }, ...] }').action(async (cmdOpts) => {
|
|
3174
|
+
const opts = program2.opts();
|
|
3175
|
+
try {
|
|
3176
|
+
const body = JSON.parse(cmdOpts.data);
|
|
3177
|
+
const data = await apiRequest({
|
|
3178
|
+
method: "POST",
|
|
3179
|
+
path: "/org/competitors/batch",
|
|
3180
|
+
body,
|
|
3181
|
+
apiKey: opts.apiKey,
|
|
3182
|
+
baseUrl: opts.baseUrl
|
|
3183
|
+
});
|
|
3184
|
+
success("Tracked competitors added.");
|
|
3185
|
+
console.log(JSON.stringify(data, null, 2));
|
|
3186
|
+
} catch (err) {
|
|
3187
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
3188
|
+
process.exit(1);
|
|
3189
|
+
}
|
|
3190
|
+
});
|
|
3191
|
+
competitors.command("suggest").description("Get AI-generated competitor suggestions seeded from your org's website and recent prompt-run results. Pipe accepted suggestions into `competitors batch-add`.").action(async () => {
|
|
3192
|
+
const opts = program2.opts();
|
|
3193
|
+
try {
|
|
3194
|
+
const data = await apiRequest({
|
|
3195
|
+
method: "POST",
|
|
3196
|
+
path: "/org/competitors/suggest",
|
|
3197
|
+
apiKey: opts.apiKey,
|
|
3198
|
+
baseUrl: opts.baseUrl
|
|
3199
|
+
});
|
|
3200
|
+
console.log(JSON.stringify(data, null, 2));
|
|
3201
|
+
} catch (err) {
|
|
3202
|
+
error(formatApiError(err));
|
|
3203
|
+
process.exit(1);
|
|
3204
|
+
}
|
|
3205
|
+
});
|
|
3206
|
+
competitors.command("update <competitorId>").description("Update a tracked competitor's name or URL.").requiredOption("--name <name>", "Competitor brand name").option("--url <url>", "Competitor website URL").action(async (competitorId, cmdOpts) => {
|
|
3207
|
+
const opts = program2.opts();
|
|
3208
|
+
try {
|
|
3209
|
+
const body = { name: cmdOpts.name };
|
|
3210
|
+
if (cmdOpts.url) body.url = cmdOpts.url;
|
|
3211
|
+
const data = await apiRequest({
|
|
3212
|
+
method: "PUT",
|
|
3213
|
+
path: `/org/competitors/${competitorId}`,
|
|
3214
|
+
body,
|
|
3215
|
+
apiKey: opts.apiKey,
|
|
3216
|
+
baseUrl: opts.baseUrl
|
|
3217
|
+
});
|
|
3218
|
+
success(`Tracked competitor ${competitorId} updated.`);
|
|
3219
|
+
console.log(JSON.stringify(data, null, 2));
|
|
3220
|
+
} catch (err) {
|
|
3221
|
+
error(formatApiError(err));
|
|
3222
|
+
process.exit(1);
|
|
3223
|
+
}
|
|
3224
|
+
});
|
|
3225
|
+
competitors.command("delete <competitorId>").description("Remove a tracked competitor.").action(async (competitorId) => {
|
|
3226
|
+
const opts = program2.opts();
|
|
3227
|
+
try {
|
|
3228
|
+
await apiRequest({
|
|
3229
|
+
method: "DELETE",
|
|
3230
|
+
path: `/org/competitors/${competitorId}`,
|
|
3231
|
+
apiKey: opts.apiKey,
|
|
3232
|
+
baseUrl: opts.baseUrl
|
|
3233
|
+
});
|
|
3234
|
+
success(`Tracked competitor ${competitorId} removed.`);
|
|
3235
|
+
} catch (err) {
|
|
3236
|
+
error(formatApiError(err));
|
|
3237
|
+
process.exit(1);
|
|
3238
|
+
}
|
|
3239
|
+
});
|
|
3240
|
+
}
|
|
3241
|
+
|
|
3242
|
+
// src/commands/tracked-sources.ts
|
|
3243
|
+
var MATCH_TYPES = "domain | host | path_prefix | exact_url";
|
|
3244
|
+
var TIERS = "primary (Owned) | tracked | secondary (External)";
|
|
3245
|
+
var CATEGORIES = "affiliated_domain | published_content | social | press";
|
|
3246
|
+
function buildSourceBody(cmdOpts) {
|
|
3247
|
+
const body = {};
|
|
3248
|
+
if (cmdOpts.pattern !== void 0) body.pattern = cmdOpts.pattern;
|
|
3249
|
+
if (cmdOpts.matchType !== void 0) body.match_type = cmdOpts.matchType;
|
|
3250
|
+
if (cmdOpts.tier !== void 0) body.tier = cmdOpts.tier;
|
|
3251
|
+
if (cmdOpts.category !== void 0) body.category = cmdOpts.category;
|
|
3252
|
+
if (cmdOpts.label !== void 0) body.label = cmdOpts.label;
|
|
3253
|
+
if (cmdOpts.priority !== void 0) body.priority = Number(cmdOpts.priority);
|
|
3254
|
+
if (cmdOpts.active !== void 0) body.active = cmdOpts.active;
|
|
3255
|
+
return body;
|
|
3256
|
+
}
|
|
3257
|
+
function registerTrackedSourcesCommands(program2) {
|
|
3258
|
+
const sources = program2.command("tracked-sources").description("Manage citation-classification rules that tier each cited URL as Owned (primary), Tracked, or External (secondary). Tracked sources drive share-of-voice and citation analytics. Rules created from published content are read-only.");
|
|
3259
|
+
sources.command("list").description("List every tracked source rule for the current organization.").action(async () => {
|
|
3260
|
+
const opts = program2.opts();
|
|
3261
|
+
try {
|
|
3262
|
+
const data = await apiRequest({
|
|
3263
|
+
path: "/org/tracked-sources",
|
|
3264
|
+
apiKey: opts.apiKey,
|
|
3265
|
+
baseUrl: opts.baseUrl
|
|
3266
|
+
});
|
|
3267
|
+
console.log(JSON.stringify(data, null, 2));
|
|
3268
|
+
} catch (err) {
|
|
3269
|
+
error(formatApiError(err));
|
|
3270
|
+
process.exit(1);
|
|
3271
|
+
}
|
|
3272
|
+
});
|
|
3273
|
+
sources.command("add").description("Add a tracked source rule. New rules are always created active.").requiredOption("--pattern <pattern>", "Value to match cited URLs against, interpreted per --match-type").requiredOption("--match-type <type>", `Match strategy: ${MATCH_TYPES}`).requiredOption("--tier <tier>", `Classification tier: ${TIERS}`).option("--category <category>", `Optional sub-category (only meaningful for the 'tracked' tier): ${CATEGORIES}`).option("--label <label>", "Optional human-readable label").option("--priority <n>", "Optional ordering priority (integer)").action(async (cmdOpts) => {
|
|
3274
|
+
const opts = program2.opts();
|
|
3275
|
+
try {
|
|
3276
|
+
const data = await apiRequest({
|
|
3277
|
+
method: "POST",
|
|
3278
|
+
path: "/org/tracked-sources",
|
|
3279
|
+
body: buildSourceBody(cmdOpts),
|
|
3280
|
+
apiKey: opts.apiKey,
|
|
3281
|
+
baseUrl: opts.baseUrl
|
|
3282
|
+
});
|
|
3283
|
+
success(`Tracked source "${cmdOpts.pattern}" added.`);
|
|
3284
|
+
console.log(JSON.stringify(data, null, 2));
|
|
3285
|
+
} catch (err) {
|
|
3286
|
+
error(formatApiError(err));
|
|
3287
|
+
process.exit(1);
|
|
3288
|
+
}
|
|
3289
|
+
});
|
|
3290
|
+
sources.command("update <sourceId>").description("Replace a tracked source rule (PUT). Pattern, match type, and tier are required. Published rules are read-only.").requiredOption("--pattern <pattern>", "Value to match cited URLs against, interpreted per --match-type").requiredOption("--match-type <type>", `Match strategy: ${MATCH_TYPES}`).requiredOption("--tier <tier>", `Classification tier: ${TIERS}`).option("--category <category>", `Optional sub-category (only meaningful for the 'tracked' tier): ${CATEGORIES}`).option("--label <label>", "Optional human-readable label").option("--priority <n>", "Optional ordering priority (integer)").option("--active", "Mark the rule active").option("--no-active", "Mark the rule inactive").action(async (sourceId, cmdOpts) => {
|
|
3291
|
+
const opts = program2.opts();
|
|
3292
|
+
try {
|
|
3293
|
+
const data = await apiRequest({
|
|
3294
|
+
method: "PUT",
|
|
3295
|
+
path: `/org/tracked-sources/${sourceId}`,
|
|
3296
|
+
body: buildSourceBody(cmdOpts),
|
|
3297
|
+
apiKey: opts.apiKey,
|
|
3298
|
+
baseUrl: opts.baseUrl
|
|
3299
|
+
});
|
|
3300
|
+
success(`Tracked source ${sourceId} updated.`);
|
|
3301
|
+
console.log(JSON.stringify(data, null, 2));
|
|
3302
|
+
} catch (err) {
|
|
3303
|
+
error(formatApiError(err));
|
|
3304
|
+
process.exit(1);
|
|
3305
|
+
}
|
|
3306
|
+
});
|
|
3307
|
+
sources.command("delete <sourceId>").description("Remove a tracked source rule.").action(async (sourceId) => {
|
|
3308
|
+
const opts = program2.opts();
|
|
3309
|
+
try {
|
|
3310
|
+
await apiRequest({
|
|
3311
|
+
method: "DELETE",
|
|
3312
|
+
path: `/org/tracked-sources/${sourceId}`,
|
|
3313
|
+
apiKey: opts.apiKey,
|
|
3314
|
+
baseUrl: opts.baseUrl
|
|
3315
|
+
});
|
|
3316
|
+
success(`Tracked source ${sourceId} removed.`);
|
|
3317
|
+
} catch (err) {
|
|
3318
|
+
error(formatApiError(err));
|
|
3319
|
+
process.exit(1);
|
|
3320
|
+
}
|
|
3321
|
+
});
|
|
3322
|
+
}
|
|
3323
|
+
|
|
3324
|
+
// src/commands/generated-content.ts
|
|
3325
|
+
function registerGeneratedContentCommands(program2) {
|
|
3326
|
+
const gc = program2.command("generated-content").description("Browse AI-generated content (GEO). List published or draft generated items, or fetch a single item with its rendered body. Requires the GEO product and read:content permission.");
|
|
3327
|
+
gc.command("list").description("List generated content. Use --status to switch between published and draft items.").option("--status <status>", "Which items to list: published | drafts", "published").option("--limit <n>", "Items per page (max 100)", "10").option("--offset <n>", "Pagination offset", "0").option("--search <query>", "Filter by title").action(async (cmdOpts) => {
|
|
3328
|
+
const opts = program2.opts();
|
|
3329
|
+
const status = cmdOpts.status === "drafts" || cmdOpts.status === "draft" ? "drafts" : "published";
|
|
3330
|
+
try {
|
|
3331
|
+
const data = await apiRequest({
|
|
3332
|
+
path: `/org/generated-content/${status}`,
|
|
3333
|
+
params: { limit: cmdOpts.limit, offset: cmdOpts.offset, search: cmdOpts.search },
|
|
3334
|
+
apiKey: opts.apiKey,
|
|
3335
|
+
baseUrl: opts.baseUrl
|
|
3336
|
+
});
|
|
3337
|
+
console.log(JSON.stringify(data, null, 2));
|
|
3338
|
+
} catch (err) {
|
|
3339
|
+
error(formatApiError(err));
|
|
3340
|
+
process.exit(1);
|
|
3341
|
+
}
|
|
3342
|
+
});
|
|
3343
|
+
gc.command("get <id>").description("Get a single generated content item including its question text and rendered body.").action(async (id) => {
|
|
3344
|
+
const opts = program2.opts();
|
|
3345
|
+
try {
|
|
3346
|
+
const data = await apiRequest({
|
|
3347
|
+
path: `/org/generated-content/${id}`,
|
|
3348
|
+
apiKey: opts.apiKey,
|
|
3349
|
+
baseUrl: opts.baseUrl
|
|
3350
|
+
});
|
|
2481
3351
|
console.log(JSON.stringify(data, null, 2));
|
|
2482
3352
|
} catch (err) {
|
|
2483
3353
|
error(formatApiError(err));
|
|
@@ -2538,6 +3408,8 @@ registerIngestCommands(program);
|
|
|
2538
3408
|
registerContentCommands(program);
|
|
2539
3409
|
registerGenerateCommands(program);
|
|
2540
3410
|
registerEngineCommands(program);
|
|
3411
|
+
registerDestinationsCommands(program);
|
|
3412
|
+
registerPublishRecordsCommands(program);
|
|
2541
3413
|
registerBrandKitCommands(program);
|
|
2542
3414
|
registerContentTypeCommands(program);
|
|
2543
3415
|
registerPromptCommands(program);
|
|
@@ -2548,6 +3420,12 @@ registerCreditsCommands(program);
|
|
|
2548
3420
|
registerQuestionsCommands(program);
|
|
2549
3421
|
registerKBCommands(program);
|
|
2550
3422
|
registerPermissionsCommands(program);
|
|
3423
|
+
registerTagsCommands(program);
|
|
3424
|
+
registerProductLineCommands(program);
|
|
3425
|
+
registerRolesCommands(program);
|
|
3426
|
+
registerCompetitorsCommands(program);
|
|
3427
|
+
registerTrackedSourcesCommands(program);
|
|
3428
|
+
registerGeneratedContentCommands(program);
|
|
2551
3429
|
registerUpdateCommand(program);
|
|
2552
3430
|
async function main() {
|
|
2553
3431
|
const quiet = process.argv.includes("--quiet") || process.argv.includes("--output") && process.argv[process.argv.indexOf("--output") + 1] === "json";
|