@letterstory/cli 0.121.7 → 0.123.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 +16 -0
- package/lib/cli.mjs +23 -2
- package/lib/commands/admin.mjs +228 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -423,6 +423,22 @@ The ledger behind Kernel Tuning's Experiments tab
|
|
|
423
423
|
(docs/architecture/kernel-experiment-agent.md): every question asked of the kernels,
|
|
424
424
|
how it was tested, and what we learned and why.
|
|
425
425
|
|
|
426
|
+
### Staff: plan recipes
|
|
427
|
+
|
|
428
|
+
```bash
|
|
429
|
+
# Needs a STAFF key with recipes:read (reads) and recipes:write (writes): mint it on
|
|
430
|
+
# /superadmin/plan-recipes → "Your agent's key". Recipes are global: one library behind every
|
|
431
|
+
# account's Plan. The same routes and functions as that page.
|
|
432
|
+
export LETTERSTORY_STAFF_API_KEY=<your staff key with recipes:read,recipes:write>
|
|
433
|
+
|
|
434
|
+
letterstory admin recipes vocab # what a recipe can say: fields, goals, kinds, primitives
|
|
435
|
+
letterstory admin recipes export --out recipes.json # the library as a file
|
|
436
|
+
letterstory admin recipes import recipes.json --dry-run # what would be created / changed / retired
|
|
437
|
+
letterstory admin recipes import recipes.json # write it (matched by id, else name)
|
|
438
|
+
letterstory admin recipes save --name "Get cited" --prompt "…" --goals geo --company-types devtools
|
|
439
|
+
letterstory admin recipes save <id> --disable # retire one (delete <id> --yes removes it)
|
|
440
|
+
```
|
|
441
|
+
|
|
426
442
|
### Staff: the CRM pipeline
|
|
427
443
|
|
|
428
444
|
```bash
|
package/lib/cli.mjs
CHANGED
|
@@ -61,7 +61,7 @@ import {
|
|
|
61
61
|
} from "./commands.mjs";
|
|
62
62
|
|
|
63
63
|
// Keep in sync with cli/package.json.
|
|
64
|
-
export const VERSION = "0.
|
|
64
|
+
export const VERSION = "0.123.0";
|
|
65
65
|
|
|
66
66
|
// Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
|
|
67
67
|
// can't accidentally swallow the id as --json's value. Every `type: "bool"` flag (and its
|
|
@@ -101,6 +101,11 @@ export const BOOLEAN_FLAGS = new Set([
|
|
|
101
101
|
"verbose",
|
|
102
102
|
"no-color",
|
|
103
103
|
"dry-run",
|
|
104
|
+
// admin recipes list --enabled-only, save --enable|--disable, import --retire-missing
|
|
105
|
+
"enabled-only",
|
|
106
|
+
"enable",
|
|
107
|
+
"disable",
|
|
108
|
+
"retire-missing",
|
|
104
109
|
// admin crm record-outcome --draft
|
|
105
110
|
"draft",
|
|
106
111
|
// admin crm reach-outs --fresh
|
|
@@ -757,6 +762,21 @@ Admin (STAFF key — --staff-key / LETTERSTORY_STAFF_API_KEY; acts before an org
|
|
|
757
762
|
read-only SQL across every org; --sql prints
|
|
758
763
|
the queries behind the answer. Needs the
|
|
759
764
|
fleet:query capability on the staff key.
|
|
765
|
+
admin recipes list [--enabled-only] | show <id|name> | vocab [--json]
|
|
766
|
+
Plan recipes: Letterstory's one global library behind
|
|
767
|
+
every account's Plan (/superadmin/plan-recipes). vocab
|
|
768
|
+
says what a recipe can say (fields, goals, company
|
|
769
|
+
types, primitives). Needs recipes:read.
|
|
770
|
+
admin recipes export [--out recipes.json] The library as a file import reads back.
|
|
771
|
+
admin recipes import <file> [--dry-run] [--retire-missing]
|
|
772
|
+
Write a library in one call: each recipe matched by id,
|
|
773
|
+
else name, then changed or created. --dry-run writes
|
|
774
|
+
nothing; --retire-missing retires every enabled recipe
|
|
775
|
+
the file leaves out. Exits 1 if any row failed.
|
|
776
|
+
admin recipes save [<id>] [--file <recipe.json>] [--name …] [--prompt …] [--audience …] [--example …]...
|
|
777
|
+
[--primitives a,b|none] [--goals a,b|none] [--company-types a,b|none] [--enable|--disable] [--position n]
|
|
778
|
+
Create one (no id) or change one. Needs recipes:write.
|
|
779
|
+
admin recipes delete <id> --yes Delete for good (save --disable retires and keeps it).
|
|
760
780
|
admin brand-detection <org-id> [--term <t>]... [--exact <t>]... [--exclude <phrase>]... [--note "…"] | --clear [--note "…"]
|
|
761
781
|
How a client's brand is detected in AI answers, with
|
|
762
782
|
every earlier setting; with flags, set the exact set
|
|
@@ -831,7 +851,8 @@ Admin (STAFF key — --staff-key / LETTERSTORY_STAFF_API_KEY; acts before an org
|
|
|
831
851
|
| add --vendor V --amount N --from D [--to D] --area A --category C [--basis billed|estimated] …
|
|
832
852
|
| import <file.csv> [--vendor Concentrate] [--area A] [--ledger-key K] [--dry-run]
|
|
833
853
|
| import <file.csv> [--vendor Concentrate] [--area A] [--keep-ledger-key] [--dry-run]
|
|
834
|
-
| usage <events.json> per-call costs as kernels/lettertrace push them
|
|
854
|
+
| usage <events.json> per-call costs as kernels/lettertrace/models push them
|
|
855
|
+
| usage list --source models --from D --to D one source's calls back, newest first
|
|
835
856
|
| remove <entry-id> | services | service <slug> [--owner …] [--monthly N|none] [--renewal D] [--units N|none]
|
|
836
857
|
Where Letter's money goes (spending.letter.dev):
|
|
837
858
|
totals, run rate, area/vendor/provider/model/
|
package/lib/commands/admin.mjs
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
// admin spend summary|reconcile|pnl|entries|add|import|usage|remove|reference|stripe-sync|services|service … (spend:read / spend:write)
|
|
19
19
|
// admin brand-detection <org-id> (fleet:read)
|
|
20
20
|
// admin brand-detection <org-id> [--term t]... [--exact t]... [--exclude p]... [--note "…"] | --clear [--note "…"] (fleet:instrument)
|
|
21
|
+
// admin recipes list|show|vocab|export|import|save|delete … (recipes:read / recipes:write)
|
|
21
22
|
// admin replays [--org <id>] [--target <id>] [--status …] [--limit n] (fleet:read)
|
|
22
23
|
// admin replay --org <id> [<response-id> | --target <id>] [--surface …] (fleet:replay)
|
|
23
24
|
// admin create-org --domain acme.com [--name "Acme"] [--test] [--no-key] [--key-name N]
|
|
@@ -42,7 +43,7 @@
|
|
|
42
43
|
// The staff key comes from --staff-key or LETTERSTORY_STAFF_API_KEY; the base URL from the
|
|
43
44
|
// usual --url / LETTERSTORY_API_URL / config resolution.
|
|
44
45
|
|
|
45
|
-
import { readFileSync } from "node:fs";
|
|
46
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
46
47
|
import { CliError, LetterstoryClient } from "../client.mjs";
|
|
47
48
|
import {
|
|
48
49
|
flagStr,
|
|
@@ -4042,7 +4043,34 @@ async function cmdAdminSpend(ctx) {
|
|
|
4042
4043
|
return 0;
|
|
4043
4044
|
}
|
|
4044
4045
|
case "usage": {
|
|
4045
|
-
|
|
4046
|
+
if (positionals[1] === "list") {
|
|
4047
|
+
// One source's calls back, newest first (GET /api/superadmin/spend/usage).
|
|
4048
|
+
const source = flagStr(flags.source);
|
|
4049
|
+
const from = flagStr(flags.from);
|
|
4050
|
+
const to = flagStr(flags.to);
|
|
4051
|
+
if (!source || !from || !to)
|
|
4052
|
+
throw new CliError(
|
|
4053
|
+
"Usage: admin spend usage list --source models --from YYYY-MM-DD --to YYYY-MM-DD [--limit N]"
|
|
4054
|
+
);
|
|
4055
|
+
const params = new URLSearchParams(
|
|
4056
|
+
compact({ source, from, to, limit: flagNum(flags.limit)?.toString() })
|
|
4057
|
+
);
|
|
4058
|
+
const result = await staffRequest(ctx, "GET", `${SPEND}/usage?${params}`);
|
|
4059
|
+
printResult(
|
|
4060
|
+
io,
|
|
4061
|
+
flags,
|
|
4062
|
+
result,
|
|
4063
|
+
(r) =>
|
|
4064
|
+
(r.events ?? [])
|
|
4065
|
+
.map(
|
|
4066
|
+
(e) =>
|
|
4067
|
+
`${e.occurred_at} ${e.model.padEnd(16)} ${e.operation.padEnd(10)} ${(e.outcome ?? "").padEnd(8)} ${String(e.latency_ms ?? "").padStart(7)}ms ${usd(e.cost_usd).padStart(9)} ${e.key_label}`
|
|
4068
|
+
)
|
|
4069
|
+
.join("\n") + (r.next ? "\n… more (narrow --from/--to or raise --limit)" : "")
|
|
4070
|
+
);
|
|
4071
|
+
return 0;
|
|
4072
|
+
}
|
|
4073
|
+
// Per-call model costs, as kernels / lettertrace / models push them (POST /api/superadmin/spend/usage):
|
|
4046
4074
|
// a JSON file holding { events: [...] } or a bare array. Idempotent on each event_id.
|
|
4047
4075
|
const path = positionals[1];
|
|
4048
4076
|
if (!path) throw new CliError("Usage: admin spend usage <events.json> ({ events: [...] } or an array)");
|
|
@@ -4192,7 +4220,7 @@ async function cmdAdminSpend(ctx) {
|
|
|
4192
4220
|
}
|
|
4193
4221
|
default:
|
|
4194
4222
|
throw new CliError(
|
|
4195
|
-
"Usage: admin spend summary [--range day|week|month|year | --from D --to D] [--grain day|week|month] | reconcile [--from D --to D] | pnl [--range …] | reference add … | stripe-sync --from D --to D | entries [--from D] [--to D] [--vendor V] [--area A] [--source S] | add --vendor V --amount N --from D … | import <file.csv> … | usage <events.json> | remove <entry-id> | services | service <slug> --owner … --monthly …"
|
|
4223
|
+
"Usage: admin spend summary [--range day|week|month|year | --from D --to D] [--grain day|week|month] | reconcile [--from D --to D] | pnl [--range …] | reference add … | stripe-sync --from D --to D | entries [--from D] [--to D] [--vendor V] [--area A] [--source S] | add --vendor V --amount N --from D … | import <file.csv> … | usage <events.json> | usage list --source S --from D --to D | remove <entry-id> | services | service <slug> --owner … --monthly …"
|
|
4196
4224
|
);
|
|
4197
4225
|
}
|
|
4198
4226
|
}
|
|
@@ -4538,6 +4566,201 @@ async function cmdAdminRepublish(ctx) {
|
|
|
4538
4566
|
return failed.length ? 1 : 0;
|
|
4539
4567
|
}
|
|
4540
4568
|
|
|
4569
|
+
const RECIPES = "/api/superadmin/plan-recipes";
|
|
4570
|
+
/** The fields a recipe file carries: what import_plan_recipes reads back. */
|
|
4571
|
+
const RECIPE_FILE_FIELDS = [
|
|
4572
|
+
"id",
|
|
4573
|
+
"name",
|
|
4574
|
+
"audience",
|
|
4575
|
+
"examples",
|
|
4576
|
+
"prompt",
|
|
4577
|
+
"primitives",
|
|
4578
|
+
"goals",
|
|
4579
|
+
"company_types",
|
|
4580
|
+
"enabled",
|
|
4581
|
+
"position",
|
|
4582
|
+
];
|
|
4583
|
+
const RECIPES_USAGE =
|
|
4584
|
+
"Usage: admin recipes list [--enabled-only] | show <id|name> | vocab | export [--out <file>] | import <file> [--dry-run] [--retire-missing] | save [<id>] [--file <recipe.json>] [--name …] [--prompt …] [--audience …] [--example …]… [--primitives a,b|none] [--goals a,b|none] [--company-types a,b|none] [--enable|--disable] [--position n] | delete <id> --yes";
|
|
4585
|
+
|
|
4586
|
+
const recipeForFile = (r) => Object.fromEntries(RECIPE_FILE_FIELDS.map((k) => [k, r[k]]));
|
|
4587
|
+
|
|
4588
|
+
function recipeLine(r) {
|
|
4589
|
+
const scope = [
|
|
4590
|
+
r.goals?.length ? `goals ${r.goals.join(",")}` : "every goal",
|
|
4591
|
+
r.company_types?.length ? `kinds ${r.company_types.join(",")}` : "every kind",
|
|
4592
|
+
`stands up ${r.primitives?.length ? r.primitives.join(",") : "nothing"}`,
|
|
4593
|
+
].join(" · ");
|
|
4594
|
+
return `${String(r.position).padStart(4)} ${r.enabled ? "on " : "off"} ${r.name} [${scope}] ${r.id}`;
|
|
4595
|
+
}
|
|
4596
|
+
|
|
4597
|
+
function recipeDetail(r) {
|
|
4598
|
+
return [
|
|
4599
|
+
`${r.name} ${r.enabled ? "(enabled)" : "(retired)"} ${r.id}`,
|
|
4600
|
+
` position: ${r.position}`,
|
|
4601
|
+
` goals: ${r.goals?.join(", ") || "every goal"}`,
|
|
4602
|
+
` kinds: ${r.company_types?.join(", ") || "every kind"}`,
|
|
4603
|
+
` stands up: ${r.primitives?.join(", ") || "nothing"}`,
|
|
4604
|
+
` audience: ${r.audience || "—"}`,
|
|
4605
|
+
` examples: ${r.examples?.join("; ") || "—"}`,
|
|
4606
|
+
` prompt:\n ${r.prompt.split("\n").join("\n ")}`,
|
|
4607
|
+
].join("\n");
|
|
4608
|
+
}
|
|
4609
|
+
|
|
4610
|
+
function readJson(path) {
|
|
4611
|
+
try {
|
|
4612
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
4613
|
+
} catch (err) {
|
|
4614
|
+
throw new CliError(`${path}: ${err.message}`);
|
|
4615
|
+
}
|
|
4616
|
+
}
|
|
4617
|
+
|
|
4618
|
+
/** A recipes file: `{ recipes: [...] }` (what export writes) or a bare array. */
|
|
4619
|
+
function readRecipesFile(path) {
|
|
4620
|
+
const parsed = readJson(path);
|
|
4621
|
+
const recipes = Array.isArray(parsed) ? parsed : parsed?.recipes;
|
|
4622
|
+
if (!Array.isArray(recipes)) throw new CliError(`${path}: expected { "recipes": [...] } or an array of recipes`);
|
|
4623
|
+
return recipes.map((r) => compact(recipeForFile(r)));
|
|
4624
|
+
}
|
|
4625
|
+
|
|
4626
|
+
/** A list flag for save: `a,b` (repeatable), `none` to clear it; a bare flag is a mistake. */
|
|
4627
|
+
function recipeListFlag(flags, name) {
|
|
4628
|
+
const v = flags[name];
|
|
4629
|
+
if (v === undefined) return undefined;
|
|
4630
|
+
if (v === true) throw new CliError(`--${name} needs a value: a,b or none`);
|
|
4631
|
+
return flagStr(v) === "none" ? [] : flagCsv(v);
|
|
4632
|
+
}
|
|
4633
|
+
|
|
4634
|
+
/** The fields `admin recipes save` sets: one recipe from --file (or show --json's output), then flags over it. */
|
|
4635
|
+
function recipeFromFlags(flags) {
|
|
4636
|
+
const path = flagStr(flags.file);
|
|
4637
|
+
const parsed = path ? readJson(path) : {};
|
|
4638
|
+
const base = parsed?.recipe ?? parsed;
|
|
4639
|
+
if (!base || typeof base !== "object" || Array.isArray(base) || Array.isArray(base.recipes)) {
|
|
4640
|
+
throw new CliError("--file holds one recipe object; use admin recipes import for several");
|
|
4641
|
+
}
|
|
4642
|
+
const examples = flagList(flags.example);
|
|
4643
|
+
return compact({
|
|
4644
|
+
...compact(recipeForFile(base)),
|
|
4645
|
+
name: flagStr(flags.name),
|
|
4646
|
+
audience: flagStr(flags.audience),
|
|
4647
|
+
prompt: flagStr(flags.prompt),
|
|
4648
|
+
examples: examples.length ? examples : undefined,
|
|
4649
|
+
primitives: recipeListFlag(flags, "primitives"),
|
|
4650
|
+
goals: recipeListFlag(flags, "goals"),
|
|
4651
|
+
company_types: recipeListFlag(flags, "company-types"),
|
|
4652
|
+
enabled: flagBool(flags.disable) ? false : flagBool(flags.enable) ? true : undefined,
|
|
4653
|
+
position: flagNum(flags.position),
|
|
4654
|
+
});
|
|
4655
|
+
}
|
|
4656
|
+
|
|
4657
|
+
/**
|
|
4658
|
+
* `admin recipes …` — the Plan recipes page (/superadmin/plan-recipes) for an agent: Letterstory's
|
|
4659
|
+
* one global library of recipes behind every account's Plan. The loop for building it out is
|
|
4660
|
+
* `vocab` (what a recipe can say) → `export --out recipes.json` → edit → `import recipes.json
|
|
4661
|
+
* --dry-run` → `import recipes.json`. Reads need recipes:read on the staff key, writes
|
|
4662
|
+
* recipes:write; every write is recorded as the key holder's.
|
|
4663
|
+
*/
|
|
4664
|
+
async function cmdAdminRecipes(ctx) {
|
|
4665
|
+
const { flags, positionals, io } = ctx;
|
|
4666
|
+
const sub = positionals[0];
|
|
4667
|
+
const all = async () => (await staffRequest(ctx, "GET", RECIPES)).recipes ?? [];
|
|
4668
|
+
switch (sub) {
|
|
4669
|
+
case "list": {
|
|
4670
|
+
const recipes = (await all()).filter((r) => !flagBool(flags["enabled-only"]) || r.enabled);
|
|
4671
|
+
printResult(io, flags, { recipes }, (r) =>
|
|
4672
|
+
r.recipes.length ? r.recipes.map(recipeLine).join("\n") : "No recipes yet."
|
|
4673
|
+
);
|
|
4674
|
+
return 0;
|
|
4675
|
+
}
|
|
4676
|
+
case "show": {
|
|
4677
|
+
const key = requirePositional(positionals, 1, "id|name");
|
|
4678
|
+
const recipe = (await all()).find((r) => r.id === key || r.name === key);
|
|
4679
|
+
if (!recipe) throw new CliError(`No recipe with id or name "${key}".`);
|
|
4680
|
+
printResult(io, flags, { recipe }, (r) => recipeDetail(r.recipe));
|
|
4681
|
+
return 0;
|
|
4682
|
+
}
|
|
4683
|
+
case "vocab": {
|
|
4684
|
+
const v = await staffRequest(ctx, "GET", `${RECIPES}/vocabulary`);
|
|
4685
|
+
printResult(io, flags, v, (r) =>
|
|
4686
|
+
[
|
|
4687
|
+
...r.how_recipes_are_read.map((line) => `· ${line}`),
|
|
4688
|
+
"",
|
|
4689
|
+
"Fields:",
|
|
4690
|
+
...r.fields.map((f) => ` ${f.name}: ${f.description}`),
|
|
4691
|
+
"",
|
|
4692
|
+
"Goals:",
|
|
4693
|
+
...r.goals.map((g) => ` ${g.id}${g.available ? "" : " (not available yet)"}: ${g.brief}`),
|
|
4694
|
+
"",
|
|
4695
|
+
`Company types: ${r.company_types.map((t) => `${t.id} (${t.label})`).join(", ")}`,
|
|
4696
|
+
`Primitives: ${r.primitives.map((p) => `${p.id} (${p.label})`).join(", ")}`,
|
|
4697
|
+
].join("\n")
|
|
4698
|
+
);
|
|
4699
|
+
return 0;
|
|
4700
|
+
}
|
|
4701
|
+
case "export": {
|
|
4702
|
+
const file = { recipes: (await all()).map(recipeForFile) };
|
|
4703
|
+
const out = flagStr(flags.out);
|
|
4704
|
+
if (out) {
|
|
4705
|
+
writeFileSync(out, `${JSON.stringify(file, null, 2)}\n`);
|
|
4706
|
+
ok(ctx, `Wrote ${file.recipes.length} recipe(s) to ${out}.`);
|
|
4707
|
+
} else io.log(JSON.stringify(file, null, 2));
|
|
4708
|
+
return 0;
|
|
4709
|
+
}
|
|
4710
|
+
case "import": {
|
|
4711
|
+
const path = requirePositional(positionals, 1, "file");
|
|
4712
|
+
const result = await staffPost(ctx, `${RECIPES}/import`, {
|
|
4713
|
+
recipes: readRecipesFile(path),
|
|
4714
|
+
dry_run: flagBool(flags["dry-run"]),
|
|
4715
|
+
retire_missing: flagBool(flags["retire-missing"]),
|
|
4716
|
+
});
|
|
4717
|
+
const verb = {
|
|
4718
|
+
create: "create",
|
|
4719
|
+
update: "update",
|
|
4720
|
+
unchanged: "same ",
|
|
4721
|
+
retire: "retire",
|
|
4722
|
+
failed: "FAILED",
|
|
4723
|
+
};
|
|
4724
|
+
printResult(io, flags, result, (r) =>
|
|
4725
|
+
[
|
|
4726
|
+
...r.results.map(
|
|
4727
|
+
(row) =>
|
|
4728
|
+
`${verb[row.action] ?? row.action} ${row.name}${row.changed.length && row.action !== "create" ? ` (${row.changed.join(", ")})` : ""}${row.error ? ` → ${row.error}` : ""}`
|
|
4729
|
+
),
|
|
4730
|
+
`${r.dry_run ? "Would create" : "Created"} ${r.counts.created}, ${r.dry_run ? "update" : "updated"} ${r.counts.updated}, retire${r.dry_run ? "" : "d"} ${r.counts.retired}; ${r.counts.unchanged} unchanged, ${r.counts.failed} failed.${r.dry_run ? " Nothing was written (--dry-run)." : ""}`,
|
|
4731
|
+
].join("\n")
|
|
4732
|
+
);
|
|
4733
|
+
return result.counts?.failed ? 1 : 0;
|
|
4734
|
+
}
|
|
4735
|
+
case "save": {
|
|
4736
|
+
const fields = recipeFromFlags(flags);
|
|
4737
|
+
const id = positionals[1] ?? fields.id;
|
|
4738
|
+
delete fields.id;
|
|
4739
|
+
const result = id
|
|
4740
|
+
? await staffRequest(ctx, "PATCH", `${RECIPES}/${encodeURIComponent(id)}`, fields)
|
|
4741
|
+
: await staffPost(ctx, RECIPES, fields);
|
|
4742
|
+
ok(ctx, `${result.created ? "Created" : "Saved"} "${result.recipe.name}" (${result.recipe.id}).`);
|
|
4743
|
+
printResult(io, flags, result, (r) => recipeDetail(r.recipe));
|
|
4744
|
+
return 0;
|
|
4745
|
+
}
|
|
4746
|
+
case "delete": {
|
|
4747
|
+
const id = requirePositional(positionals, 1, "id");
|
|
4748
|
+
if (!flagBool(flags.yes)) {
|
|
4749
|
+
io.error(
|
|
4750
|
+
`Re-run with --yes to delete for good: ${ctx.bin} admin recipes delete ${id} --yes (save --disable retires one and keeps it)`
|
|
4751
|
+
);
|
|
4752
|
+
return 1;
|
|
4753
|
+
}
|
|
4754
|
+
const result = await staffRequest(ctx, "DELETE", `${RECIPES}/${encodeURIComponent(id)}`);
|
|
4755
|
+
ok(ctx, `Deleted ${id}.`);
|
|
4756
|
+
printResult(io, flags, result);
|
|
4757
|
+
return 0;
|
|
4758
|
+
}
|
|
4759
|
+
default:
|
|
4760
|
+
throw new CliError(RECIPES_USAGE);
|
|
4761
|
+
}
|
|
4762
|
+
}
|
|
4763
|
+
|
|
4541
4764
|
export async function cmdAdmin(ctx) {
|
|
4542
4765
|
const sub = ctx.positionals[0];
|
|
4543
4766
|
if (sub === "replays" || sub === "replay")
|
|
@@ -4555,8 +4778,9 @@ export async function cmdAdmin(ctx) {
|
|
|
4555
4778
|
if (sub === "evals") return cmdAdminEvals({ ...ctx, positionals: ctx.positionals.slice(1) });
|
|
4556
4779
|
if (sub === "brand-detection") return cmdAdminBrandDetection({ ...ctx, positionals: ctx.positionals.slice(1) });
|
|
4557
4780
|
if (sub === "republish") return cmdAdminRepublish({ ...ctx, positionals: ctx.positionals.slice(1) });
|
|
4781
|
+
if (sub === "recipes") return cmdAdminRecipes({ ...ctx, positionals: ctx.positionals.slice(1) });
|
|
4558
4782
|
throw new CliError(
|
|
4559
|
-
'Usage: admin republish … | admin create-org --domain <bare-domain> [--onboard] … | admin ask "<question>" | admin patterns … | admin experiments … | admin fleet-experiments … | admin crm … | admin spend … | admin evals … | admin brand-detection … | admin tuning … | admin tuning-context | admin replays … (see --help)'
|
|
4783
|
+
'Usage: admin recipes … | admin republish … | admin create-org --domain <bare-domain> [--onboard] … | admin ask "<question>" | admin patterns … | admin experiments … | admin fleet-experiments … | admin crm … | admin spend … | admin evals … | admin brand-detection … | admin tuning … | admin tuning-context | admin replays … (see --help)'
|
|
4560
4784
|
);
|
|
4561
4785
|
}
|
|
4562
4786
|
|