@2kw/ai 6.3.0-dev.98 → 6.4.0-dev.10

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 CHANGED
@@ -112,7 +112,7 @@ kubectl-style contexts switch between organizations and environments:
112
112
  | `providers` | Manage BYOK AI providers |
113
113
  | `analytics` | Usage analytics: spend, quality, providers, errors |
114
114
  | `billing` | Check subscription tier and usage limits |
115
- | `agents` | Run agents and decide approvals; apply, export and scaffold agent.yaml; manage versions, labels, approvals and tool catalogs |
115
+ | `agents` | Run agents and answer paused runs (approvals and relayed tool calls); apply, export and scaffold agent.yaml; manage versions, labels, approvals and tool catalogs |
116
116
  | `skills` | Import and export SKILL.md bundles; manage skill versions and labels |
117
117
  | `plugins` | Install plugin repositories, sync them, and inspect their reports |
118
118
  | `conversations` | Create and manage conversations and their items |
@@ -221,6 +221,25 @@ export declare const agentSchema: {
221
221
  };
222
222
  };
223
223
  };
224
+ readonly bashTool: {
225
+ readonly type: "object";
226
+ readonly additionalProperties: false;
227
+ readonly required: readonly ["type"];
228
+ readonly properties: {
229
+ readonly type: {
230
+ readonly const: "backbone.bash";
231
+ };
232
+ readonly approval: {
233
+ readonly enum: readonly ["sandbox", "pause"];
234
+ };
235
+ readonly outputs: {
236
+ readonly enum: readonly ["immediate", "held"];
237
+ };
238
+ readonly annotations: {
239
+ readonly $ref: "#/$defs/annotations";
240
+ };
241
+ };
242
+ };
224
243
  readonly functionTool: {
225
244
  readonly type: "object";
226
245
  readonly additionalProperties: false;
@@ -335,6 +354,18 @@ export declare const agentSchema: {
335
354
  readonly then: {
336
355
  readonly $ref: "#/$defs/skillTool";
337
356
  };
357
+ }, {
358
+ readonly if: {
359
+ required: string[];
360
+ properties: {
361
+ type: {
362
+ const: string;
363
+ };
364
+ };
365
+ };
366
+ readonly then: {
367
+ readonly $ref: "#/$defs/bashTool";
368
+ };
338
369
  }, {
339
370
  readonly if: {
340
371
  required: string[];
@@ -21,6 +21,12 @@ const oneOfLowercase = (...values) => ({ type: "string", enum: values });
21
21
  export const NON_AUTHORABLE_TOOL_TYPES = {
22
22
  "backbone.tool_search": "the planner adds it automatically",
23
23
  "backbone.skill_read": "it is added at run time when skills are pinned",
24
+ // #944: SkillsetPinner injects it for eligible callers and strips any stored copy (#779).
25
+ "backbone.skills_apply": "it is added at run time for callers allowed to author skills",
26
+ // #1241: injected while the conversation's outputs are held; an authored copy is dropped.
27
+ "backbone.deliver": "it is added at run time while sandbox outputs are held",
28
+ // #1244: injected wherever backbone.bash is advertised; an authored copy is dropped.
29
+ "backbone.view_image": "it is added at run time wherever the sandboxed shell is",
24
30
  web_search: "it has no executor and is skipped",
25
31
  };
26
32
  const BUILTIN_TYPES = [
@@ -29,6 +35,7 @@ const BUILTIN_TYPES = [
29
35
  "backbone.document_convert",
30
36
  "backbone.todo",
31
37
  "backbone.skill",
38
+ "backbone.bash",
32
39
  "function",
33
40
  ];
34
41
  const typeIs = (t) => ({ required: ["type"], properties: { type: { const: t } } });
@@ -155,6 +162,19 @@ export const agentSchema = {
155
162
  skills: { type: "array", minItems: 1, maxItems: 20, uniqueItems: true, items: { type: "string", pattern: SKILL_REF } },
156
163
  },
157
164
  },
165
+ // The envelope BashToolEntryValidator accepts: no name, no requiresConfirmation, and none of
166
+ // the egress of the withdrawn per-agent network design (#925).
167
+ bashTool: {
168
+ type: "object",
169
+ additionalProperties: false,
170
+ required: ["type"],
171
+ properties: {
172
+ type: { const: "backbone.bash" },
173
+ approval: { enum: ["sandbox", "pause"] },
174
+ outputs: { enum: ["immediate", "held"] },
175
+ annotations: { $ref: "#/$defs/annotations" },
176
+ },
177
+ },
158
178
  functionTool: {
159
179
  type: "object",
160
180
  additionalProperties: false,
@@ -188,6 +208,7 @@ export const agentSchema = {
188
208
  { if: typeIs("backbone.document_convert"), then: { $ref: "#/$defs/documentConvertTool" } },
189
209
  { if: typeIs("backbone.todo"), then: { $ref: "#/$defs/todoTool" } },
190
210
  { if: typeIs("backbone.skill"), then: { $ref: "#/$defs/skillTool" } },
211
+ { if: typeIs("backbone.bash"), then: { $ref: "#/$defs/bashTool" } },
191
212
  { if: typeIs("function"), then: { $ref: "#/$defs/functionTool" } },
192
213
  {
193
214
  // The validator reports these with the reason from NON_AUTHORABLE_TOOL_TYPES; this keeps the published schema honest.
@@ -33,6 +33,8 @@ tools: []
33
33
  # - type: backbone.todo
34
34
  # - type: backbone.skill
35
35
  # skills: [skill-name@latest]
36
+ # - type: backbone.bash
37
+ # approval: sandbox
36
38
  # - type: function
37
39
  # name: lookup_order
38
40
  # description: Look up an order by number
@@ -6,7 +6,7 @@ import { BackboneApiError, CliUsageError } from "../lib/errors.js";
6
6
  import { withSpinner } from "../lib/output.js";
7
7
  import { resolveAgent, splitAgentRef, suggestAgents } from "../lib/agent-lookup.js";
8
8
  import { buildRunEnvelope, EXIT_CODES, MODE_OPTION_HELP, modeItem, parseModeOption, printRunText, stripControl, } from "../lib/agent-run.js";
9
- import { continueWithDecisions, decisionsFromAnswers, fetchPendingApprovals, planDecisions, } from "../lib/agent-decide.js";
9
+ import { continueWithDecisions, decisionsFromAnswers, fetchPendingApprovals, parseToolAnswers, planContinuation, } from "../lib/agent-decide.js";
10
10
  import { PromptAbortedError, promptApprovals, readlineAsk } from "../lib/approval-prompt.js";
11
11
  import { withModelOverride } from "../lib/agent-models.js";
12
12
  import { readStdinInput } from "./ai.js";
@@ -171,9 +171,11 @@ export function makeAgentRunCommand() {
171
171
  });
172
172
  });
173
173
  }
174
+ /** Repeatable `--output` / `--fail`: a variadic `<v...>` would swallow the arguments that follow it. */
175
+ const collect = (value, previous = []) => [...previous, value];
174
176
  export function makeAgentDecideCommand() {
175
177
  return new Command("decide")
176
- .description("Decide every pending approval of a paused run and continue it")
178
+ .description("Answer a paused run: decide every pending approval and answer every relayed tool call in one call, then continue it")
177
179
  .argument("<agent>", "Agent name or id, optionally with @label and #model (use the same @label and #model the run used)")
178
180
  .requiredOption("--response <responseId>", "The paused response id (responseId in the run envelope)")
179
181
  .option("--approve <ids...>", "Approval ids to approve")
@@ -182,11 +184,15 @@ export function makeAgentDecideCommand() {
182
184
  .option("--reject-all", "Reject every pending approval of the response")
183
185
  .option("--reason <text>", "Reason recorded on each decision (the model sees it on rejects)")
184
186
  .option("--remember", "Also approve later calls of the same tool in this conversation")
187
+ .option("--output <callId=value>", "Answer a relayed tool call: <callId>=<text>, <callId>=@<file> or <callId>=@- for stdin; within one hour of the pause (repeatable)", collect)
188
+ .option("--fail <callId=message>", "Answer a relayed tool call as failed: <callId>=<message> (same @file / @- forms; repeatable)", collect)
185
189
  .option("--mode <mode>", MODE_OPTION_HELP)
186
190
  .option("--raw", "Print the untouched API response")
187
191
  .action(async (agentRef, opts, command) => {
188
192
  await runAction(command, async () => {
189
193
  const mode = parseModeOption(opts.mode);
194
+ // Syntax, duplicates and file reads are checked before any request (#671 D4).
195
+ const toolAnswers = parseToolAnswers(opts.output, opts.fail);
190
196
  const client = getClient(command);
191
197
  const { name, label, model } = splitAgentRef(agentRef);
192
198
  // Dropped silently, an empty override would continue the run on the default model.
@@ -194,15 +200,15 @@ export function makeAgentDecideCommand() {
194
200
  throw new CliUsageError(`Empty #model in '${agentRef}': use the model the run used, or drop the '#'.`);
195
201
  const agent = await resolveAgent(client, name);
196
202
  const pending = await fetchPendingApprovals(client, String(agent.id), opts.response);
197
- const decisions = planDecisions(pending, {
203
+ const decisions = planContinuation(pending, {
198
204
  approve: opts.approve,
199
205
  reject: opts.reject,
200
206
  approveAll: opts.approveAll,
201
207
  rejectAll: opts.rejectAll,
202
208
  reason: opts.reason,
203
209
  remember: opts.remember,
204
- }, opts.response);
205
- const result = await withSpinner("Continuing...", () => continueWithDecisions(client, String(agent.id), opts.response, decisions, label, model, mode));
210
+ }, toolAnswers, opts.response);
211
+ const result = await withSpinner("Continuing...", () => continueWithDecisions(client, String(agent.id), opts.response, decisions, label, model, mode, toolAnswers));
206
212
  await finishRun(command, client, { name, label, model, mode }, result, { raw: !!opts.raw, interactive: false });
207
213
  });
208
214
  });
@@ -106,11 +106,11 @@ export function makeAiCommand() {
106
106
  .option("--agent <id>", "Invoke a stored agent by id or name, optionally with @label")
107
107
  .option("--model <id>", "Model in provider/model format; with --agent, one of the agent's models to run this request on")
108
108
  .option("--conversation <id>", "Conversation ID this response belongs to")
109
- .option("--stream", "Stream the response (not supported by the server yet, #148)")
109
+ .option("--stream", "Stream the response (not supported by the CLI yet)")
110
110
  .action(async (input, opts, command) => {
111
111
  await runAction(command, async () => {
112
112
  if (opts.stream) {
113
- throw new CliUsageError("Streaming is not supported by the server yet (#148). Run without --stream.");
113
+ throw new CliUsageError("Streaming is not supported by the CLI yet. Run without --stream.");
114
114
  }
115
115
  const model = resolveModel(opts);
116
116
  const inputText = input ?? readStdinInput();
@@ -1,6 +1,7 @@
1
1
  import { Command } from "commander";
2
2
  import { getClient, runAction } from "../lib/client.js";
3
3
  import { isJsonOutput } from "../lib/config.js";
4
+ import { CliUsageError } from "../lib/errors.js";
4
5
  import { formatDetail, formatList } from "../lib/output.js";
5
6
  import chalk from "chalk";
6
7
  export function makeBillingCommand() {
@@ -133,6 +134,107 @@ export function makeBillingCommand() {
133
134
  }
134
135
  });
135
136
  });
137
+ cmd.addCommand(makeOverageCommand());
138
+ cmd.addCommand(makeTopUpsCommand());
139
+ return cmd;
140
+ }
141
+ /** Whole euros from cents, e.g. 1234 → "€12.34". Amounts are net of VAT (#973 D-O). */
142
+ function euros(cents) {
143
+ return `€${((cents ?? 0) / 100).toFixed(2)}`;
144
+ }
145
+ function parseWhole(value, name, min, max) {
146
+ const n = /^\d+$/.test(value) ? Number(value) : NaN;
147
+ if (!Number.isInteger(n) || n < min || n > max) {
148
+ throw new CliUsageError(`${name} must be a whole number between ${min} and ${max}, not '${value}'`);
149
+ }
150
+ return n;
151
+ }
152
+ /** `bb billing overage get|set` — the opt-in overage cap (#973, plan D-O). Reads need a member login. */
153
+ function makeOverageCommand() {
154
+ const cmd = new Command("overage").description("Show or set the opt-in overage cap (euros, net of VAT)");
155
+ cmd
156
+ .command("get")
157
+ .description("Show the overage setting and this period's overage (needs `bb auth login`)")
158
+ .action(async (_opts, command) => {
159
+ await runAction(command, async () => {
160
+ const client = getClient(command);
161
+ const { data } = await client.GET("/v1/billing/overage");
162
+ if (isJsonOutput(command)) {
163
+ console.log(JSON.stringify(data, null, 2));
164
+ return;
165
+ }
166
+ console.log(`Overage: ${data?.enabled ? chalk.green("on") : chalk.dim("off")}`);
167
+ console.log(`Cap: €${data?.capEuros ?? 0} (max €${data?.maxCapEuros ?? 0})`);
168
+ console.log(`Available: ${data?.available ? chalk.green("yes") : chalk.yellow(`no (${data?.unavailableReason ?? "unknown"})`)}`);
169
+ console.log(`Spent: ${euros(data?.spentCents)}`);
170
+ console.log(`Reserved: ${euros(data?.reservedCents)}`);
171
+ if (data?.periodStart || data?.periodEnd) {
172
+ console.log(chalk.dim(`Period: ${data?.periodStart?.slice(0, 10) ?? "?"} → ${data?.periodEnd?.slice(0, 10) ?? "?"}`));
173
+ }
174
+ });
175
+ });
176
+ cmd
177
+ .command("set")
178
+ .description("Turn overage on with a cap, or off (admin)")
179
+ .option("--cap <euros>", "Monthly cap in whole euros, 1-100; turns overage on")
180
+ .option("--off", "Turn overage off, keeping the stored cap")
181
+ .action(async (opts, command) => {
182
+ await runAction(command, async () => {
183
+ if (opts.off === Boolean(opts.cap)) {
184
+ throw new CliUsageError("Pass exactly one of --cap <euros> or --off");
185
+ }
186
+ // --off sends no cap: the backend keeps the stored one, and no read is needed (the read
187
+ // refuses API keys, the write admits an admin's).
188
+ const body = opts.off
189
+ ? { enabled: false }
190
+ : { enabled: true, capEuros: parseWhole(String(opts.cap), "cap", 1, 100) };
191
+ const client = getClient(command);
192
+ const { data } = await client.PUT("/v1/billing/overage", { body });
193
+ formatDetail(data, command);
194
+ });
195
+ });
196
+ return cmd;
197
+ }
198
+ /** `bb billing top-ups list|buy` — prepaid credit packs (#973, plan D-E). Never opens a browser. */
199
+ function makeTopUpsCommand() {
200
+ const cmd = new Command("top-ups").description("List or buy prepaid credit (packs of 1,000 credits at €15)");
201
+ cmd
202
+ .command("list")
203
+ .description("List unexpired prepaid credit (needs `bb auth login`)")
204
+ .action(async (_opts, command) => {
205
+ await runAction(command, async () => {
206
+ const client = getClient(command);
207
+ const { data } = await client.GET("/v1/billing/top-ups");
208
+ if (isJsonOutput(command)) {
209
+ console.log(JSON.stringify(data, null, 2));
210
+ return;
211
+ }
212
+ const rows = (data ?? []).map((lot) => ({
213
+ kind: lot.kind,
214
+ purchased: euros(lot.purchasedCents),
215
+ remaining: euros(lot.remainingCents),
216
+ expires: lot.expiresAt?.slice(0, 10),
217
+ }));
218
+ formatList(rows, command, ["kind", "purchased", "remaining", "expires"]);
219
+ });
220
+ });
221
+ cmd
222
+ .command("buy")
223
+ .description("Start a Stripe Checkout for top-up packs and print its URL (admin)")
224
+ .requiredOption("--packs <n>", "Number of packs, 1-20")
225
+ .action(async (opts, command) => {
226
+ await runAction(command, async () => {
227
+ const packs = parseWhole(String(opts.packs), "packs", 1, 20);
228
+ const client = getClient(command);
229
+ const { data } = await client.POST("/v1/billing/top-ups", { body: { packs } });
230
+ if (isJsonOutput(command)) {
231
+ console.log(JSON.stringify(data, null, 2));
232
+ return;
233
+ }
234
+ console.log(`Open this page to pay: ${data?.checkoutUrl}`);
235
+ console.log(chalk.dim(`Expires: ${data?.expiresAt ?? "?"}`));
236
+ });
237
+ });
136
238
  return cmd;
137
239
  }
138
240
  //# sourceMappingURL=billing.js.map
@@ -1,9 +1,8 @@
1
1
  import { Command } from "commander";
2
2
  import { readFileSync, writeFileSync } from "node:fs";
3
3
  import chalk from "chalk";
4
- import { getClient, resolveAuthHeader, runAction } from "../lib/client.js";
5
- import { resolveConfig, isJsonOutput } from "../lib/config.js";
6
- import { BackboneApiError } from "../lib/errors.js";
4
+ import { getClient, runAction } from "../lib/client.js";
5
+ import { isJsonOutput } from "../lib/config.js";
7
6
  import { formatDetail, withSpinner } from "../lib/output.js";
8
7
  import { fileToBlob, getMimeType } from "../lib/multipart.js";
9
8
  function mapFormat(format) {
@@ -136,34 +135,19 @@ function buildFormData(paths, opts) {
136
135
  formData.append("options", new Blob([JSON.stringify(pipelineOptions)], { type: "application/json" }));
137
136
  return formData;
138
137
  }
138
+ /**
139
+ * POST the multipart body through the typed client. The generated type describes `files` as string[],
140
+ * which FormData cannot match, so the placeholder body satisfies the type and bodySerializer sends the
141
+ * real parts; the client's auth and error middleware apply.
142
+ */
139
143
  async function multipartConvert(command, formData, endpoint, pipeline) {
140
- const config = resolveConfig(command);
141
- const authHeader = await resolveAuthHeader(config);
142
- const baseUrl = config.baseUrl.replace(/\/+$/, "");
143
- const url = pipeline
144
- ? `${baseUrl}${endpoint}?pipeline=${encodeURIComponent(pipeline)}`
145
- : `${baseUrl}${endpoint}`;
146
- const res = await fetch(url, {
147
- method: "POST",
148
- headers: { Authorization: authHeader },
149
- body: formData,
144
+ const client = getClient(command);
145
+ const { data } = await client.POST(endpoint, {
146
+ params: { query: { pipeline } },
147
+ body: { files: [] },
148
+ bodySerializer: () => formData,
150
149
  });
151
- if (!res.ok) {
152
- let body;
153
- try {
154
- body = await res.json();
155
- }
156
- catch {
157
- body = {
158
- error: res.statusText,
159
- message: `HTTP ${res.status}: ${res.statusText}`,
160
- status: res.status,
161
- timestamp: new Date().toISOString(),
162
- };
163
- }
164
- throw new BackboneApiError(body);
165
- }
166
- return (await res.json());
150
+ return data;
167
151
  }
168
152
  export function makeConvertCommand() {
169
153
  const cmd = new Command("convert").description("Convert documents");
@@ -16,8 +16,18 @@ function formatComparisonCell(result) {
16
16
  return chalk.red("ERROR");
17
17
  const scores = result.scores ?? [];
18
18
  if (scores.length > 0) {
19
- const avg = scores.reduce((sum, s) => sum + (s.score ?? 0), 0) / scores.length;
20
- return avg.toFixed(2);
19
+ // A SKIP carries a placeholder 0.0 that is not a rating, and a comment-only
20
+ // score has no number; neither takes part in the average (#693).
21
+ const rated = scores.filter((s) => s.label !== "SKIP" && s.score != null);
22
+ if (rated.length > 0) {
23
+ return (rated.reduce((sum, s) => sum + s.score, 0) / rated.length).toFixed(2);
24
+ }
25
+ // A label-only verdict (#1163) has no number; show its label, as the console does.
26
+ const verdict = scores.find((s) => s.label && s.label !== "SKIP");
27
+ if (verdict)
28
+ return String(verdict.label);
29
+ if (scores.every((s) => s.label === "SKIP"))
30
+ return chalk.dim("skipped");
21
31
  }
22
32
  return result.durationMs != null ? `${result.durationMs}ms` : chalk.dim("done");
23
33
  }
@@ -392,8 +402,14 @@ export function makeExperimentsCommand() {
392
402
  console.log(chalk.dim(`Page ${(page.number ?? 0) + 1} of ${page.totalPages ?? 0} (${page.totalElements ?? 0} total)`));
393
403
  }
394
404
  if (aggregates.length > 0) {
405
+ const skippedOf = (agg) => Object.entries((agg.skippedEvaluatorCounts ?? {}))
406
+ .filter(([, n]) => n > 0)
407
+ .map(([evaluatorId, n]) => `${evaluatorId}: ${n}`)
408
+ .join(", ");
409
+ // Only when something was skipped, so a clean run keeps its narrow table (#693).
410
+ const showSkipped = aggregates.some((agg) => skippedOf(agg) !== "");
395
411
  const aggTable = new Table({
396
- head: ["Variant", "Items", "Avg Duration", "Avg Tokens", "Total Cost"].map((h) => chalk.cyan(h)),
412
+ head: ["Variant", "Items", "Avg Duration", "Avg Tokens", "Total Cost", ...(showSkipped ? ["Skipped"] : [])].map((h) => chalk.cyan(h)),
397
413
  wordWrap: true,
398
414
  });
399
415
  for (const agg of aggregates) {
@@ -403,6 +419,7 @@ export function makeExperimentsCommand() {
403
419
  agg.avgDurationMs != null ? `${Math.round(agg.avgDurationMs)}ms` : "—",
404
420
  agg.avgTokens != null ? Math.round(agg.avgTokens) : "—",
405
421
  agg.totalCost != null ? `$${agg.totalCost}` : "—",
422
+ ...(showSkipped ? [skippedOf(agg) || "—"] : []),
406
423
  ]);
407
424
  }
408
425
  console.log(chalk.bold("\nAggregates:"));
@@ -1,7 +1,22 @@
1
1
  import { Command } from "commander";
2
2
  import { getClient, runAction } from "../lib/client.js";
3
+ import { CliUsageError } from "../lib/errors.js";
3
4
  import { formatPage, formatDetail, formatList, formatSuccess } from "../lib/output.js";
4
5
  import { addPaginationOptions, paginationParams } from "../lib/pagination.js";
6
+ const MIN_MONTHLY_PERCENT = 0;
7
+ const MAX_MONTHLY_PERCENT = 1000;
8
+ /**
9
+ * Validated inside the action rather than as commander's argument parser, so the refusal
10
+ * travels through `runAction` and exits 2 like every other usage error (see `parsePort` in
11
+ * connectors.ts).
12
+ */
13
+ function parseMonthlyPercent(value) {
14
+ const percent = /^\d+$/.test(value) ? Number(value) : NaN;
15
+ if (!Number.isInteger(percent) || percent < MIN_MONTHLY_PERCENT || percent > MAX_MONTHLY_PERCENT) {
16
+ throw new CliUsageError(`percent must be a whole number between ${MIN_MONTHLY_PERCENT} and ${MAX_MONTHLY_PERCENT}, not '${value}'`);
17
+ }
18
+ return percent;
19
+ }
5
20
  export function makeInstallationsCommand() {
6
21
  const cmd = new Command("installations").description("Manage surface installations");
7
22
  const list = new Command("list").description("List installations");
@@ -172,6 +187,36 @@ export function makeInstallationsCommand() {
172
187
  });
173
188
  });
174
189
  cmd.addCommand(keys);
190
+ const budget = new Command("budget").description("Manage an installation's monthly budget: a share of the organisation's pool");
191
+ budget
192
+ .command("get")
193
+ .description("Read an installation's current budget: percent, period, and usage")
194
+ .argument("<id>", "Installation ID")
195
+ .action(async (id, _opts, command) => {
196
+ await runAction(command, async () => {
197
+ const client = getClient(command);
198
+ const { data } = await client.GET("/v1/installations/{id}/budget", {
199
+ params: { path: { id } },
200
+ });
201
+ formatDetail(data, command);
202
+ });
203
+ });
204
+ budget
205
+ .command("set")
206
+ .description("Set or change an installation's monthly budget share, 0-1000 percent")
207
+ .argument("<id>", "Installation ID")
208
+ .argument("<percent>", "Monthly percent of the organisation's pool, 0-1000")
209
+ .action(async (id, percent, _opts, command) => {
210
+ await runAction(command, async () => {
211
+ const client = getClient(command);
212
+ const { data } = await client.PUT("/v1/installations/{id}/budget", {
213
+ params: { path: { id } },
214
+ body: { monthlyPercent: parseMonthlyPercent(percent) },
215
+ });
216
+ formatDetail(data, command);
217
+ });
218
+ });
219
+ cmd.addCommand(budget);
175
220
  return cmd;
176
221
  }
177
222
  //# sourceMappingURL=installations.js.map
@@ -37,7 +37,7 @@ export function makeKnowledgeCommand() {
37
37
  .description("Create a knowledge base and its first configuration version")
38
38
  .requiredOption("-n, --name <name>", "Knowledge base name")
39
39
  .requiredOption("--slug <slug>", "Unique slug")
40
- .requiredOption("--embedding-provider <id>", "Embedding provider ID")
40
+ .requiredOption("--embedding-provider <id>", "Embedding provider: a provider ID, or builtin for the platform's built-in models (text-embedding-3-small, text-embedding-3-large; charged per input token)")
41
41
  .requiredOption("--embedding-model <model>", "Embedding model")
42
42
  .requiredOption("--embedding-dim <n>", "Embedding dimension", parseInt)
43
43
  .option("-d, --description <text>", "Description")
@@ -47,6 +47,7 @@ export function makeKnowledgeCommand() {
47
47
  .option("--parent-chunk-size <n>", "Parent chunk size (hierarchical chunking)", parseInt)
48
48
  .option("--reranker-provider <id>", "Reranker provider ID")
49
49
  .option("--hybrid-search <bool>", "Enable hybrid search (true/false)")
50
+ .option("--text-search-language <name>", "Text-search language for the lexical leg (default simple)")
50
51
  .action(async (opts, command) => {
51
52
  await runAction(command, async () => {
52
53
  const client = getClient(command);
@@ -64,6 +65,7 @@ export function makeKnowledgeCommand() {
64
65
  parentChunkSize: opts.parentChunkSize,
65
66
  rerankerProviderId: opts.rerankerProvider,
66
67
  hybridSearchEnabled: opts.hybridSearch !== undefined ? opts.hybridSearch === "true" : undefined,
68
+ textSearchLanguage: opts.textSearchLanguage,
67
69
  },
68
70
  });
69
71
  formatDetail(data, command);
@@ -75,7 +77,7 @@ export function makeKnowledgeCommand() {
75
77
  .argument("<id>", "Knowledge base ID")
76
78
  .option("-n, --name <name>", "New name")
77
79
  .option("-d, --description <text>", "New description")
78
- .option("--embedding-provider <id>", "New embedding provider ID")
80
+ .option("--embedding-provider <id>", "New embedding provider ID, or builtin. Re-embeds nothing: stored vectors stay comparable only if the new provider serves the same model at the same width; the safe move is a new knowledge base with the documents uploaded again")
79
81
  .option("--embedding-model <model>", "New embedding model")
80
82
  .option("--chunking-strategy <strategy>", "New chunking strategy (AUTO, FIXED, HIERARCHICAL, CUSTOM)")
81
83
  .option("--chunk-size <n>", "New chunk size", parseInt)
@@ -83,6 +85,7 @@ export function makeKnowledgeCommand() {
83
85
  .option("--parent-chunk-size <n>", "New parent chunk size", parseInt)
84
86
  .option("--reranker-provider <id>", "New reranker provider ID")
85
87
  .option("--hybrid-search <bool>", "Enable hybrid search (true/false)")
88
+ .option("--text-search-language <name>", "Change the text-search language (reindexes in the background); omit to leave it untouched")
86
89
  .action(async (id, opts, command) => {
87
90
  await runAction(command, async () => {
88
91
  const client = getClient(command);
@@ -107,6 +110,8 @@ export function makeKnowledgeCommand() {
107
110
  body.rerankerProviderId = opts.rerankerProvider;
108
111
  if (opts.hybridSearch !== undefined)
109
112
  body.hybridSearchEnabled = opts.hybridSearch === "true";
113
+ if (opts.textSearchLanguage)
114
+ body.textSearchLanguage = opts.textSearchLanguage;
110
115
  const { data } = await client.PATCH("/v1/knowledge-bases/{id}", {
111
116
  params: { path: { id } },
112
117
  body: body,
@@ -173,7 +178,7 @@ export function makeKnowledgeCommand() {
173
178
  cmd
174
179
  .command("embedding-models")
175
180
  .description("List embedding models a provider serves that fit a provisioned dimension")
176
- .argument("<providerId>", "Provider ID")
181
+ .argument("<providerId>", "Provider ID, or builtin for the platform's built-in embedding models")
177
182
  .action(async (providerId, _opts, command) => {
178
183
  await runAction(command, async () => {
179
184
  const client = getClient(command);
@@ -149,18 +149,16 @@ export function makeSkillsCommand() {
149
149
  }
150
150
  ({ blob, filename } = fileToBlob(path));
151
151
  }
152
- const config = resolveConfig(command);
153
- const authHeader = await resolveAuthHeader(config);
154
152
  const formData = new FormData();
155
153
  formData.append("file", blob, filename);
154
+ const client = getClient(command);
156
155
  const data = await withSpinner("Importing skill...", async () => {
157
- const res = await fetch(`${config.baseUrl.replace(/\/+$/, "")}/v1/skills/import`, {
158
- method: "POST",
159
- headers: { Authorization: authHeader },
160
- body: formData,
156
+ // Placeholder body for the generated type; bodySerializer sends the real part.
157
+ const result = await client.POST("/v1/skills/import", {
158
+ body: { file: "" },
159
+ bodySerializer: () => formData,
161
160
  });
162
- await checkResponse(res);
163
- return res.json();
161
+ return result.data;
164
162
  });
165
163
  formatDetail(data, command);
166
164
  });
@@ -77,6 +77,7 @@ export function makeTracingCommand() {
77
77
  const sessions = new Command("sessions").description("Session-level trace views (spans grouped by exporter-stamped session id)");
78
78
  const sessionsList = new Command("list").description("List trace sessions");
79
79
  sessionsList.option("--search <query>", "Free-text search across session id and name");
80
+ sessionsList.option("--session-id <sessionId>", "Exact match on the session.id the exporter stamped (slashes allowed)");
80
81
  sessionsList.option("--from <iso>", "Start of time range (ISO-8601)");
81
82
  sessionsList.option("--to <iso>", "End of time range (ISO-8601)");
82
83
  sessionsList.option("--page <n>", "Page index (0-based)", "0");
@@ -88,6 +89,7 @@ export function makeTracingCommand() {
88
89
  params: {
89
90
  query: {
90
91
  search: opts.search,
92
+ sessionId: opts.sessionId,
91
93
  from: opts.from,
92
94
  to: opts.to,
93
95
  page: parseInt(opts.page, 10),
@@ -96,17 +98,17 @@ export function makeTracingCommand() {
96
98
  },
97
99
  });
98
100
  const page = data;
99
- formatList((page?.content ?? []), command, ["sessionId", "name", "startTime", "durationMs", "turnCount", "errorCount", "costUsd"]);
101
+ formatList((page?.content ?? []), command, ["id", "sessionId", "name", "startTime", "durationMs", "turnCount", "errorCount", "costUsd"]);
100
102
  });
101
103
  });
102
104
  sessions.addCommand(sessionsList);
103
105
  const sessionsGet = new Command("get").description("Get all spans of a session by id, sorted chronologically");
104
- sessionsGet.argument("<sessionId>", "Session id");
105
- sessionsGet.action(async (sessionId, _opts, command) => {
106
+ sessionsGet.argument("<id>", "Session id: the id column of `sessions list` (find one by session.id with --session-id)");
107
+ sessionsGet.action(async (id, _opts, command) => {
106
108
  await runAction(command, async () => {
107
109
  const client = getClient(command);
108
- const { data } = await client.GET("/v1/traces/sessions/{sessionId}", {
109
- params: { path: { sessionId } },
110
+ const { data } = await client.GET("/v1/traces/sessions/{id}", {
111
+ params: { path: { id } },
110
112
  });
111
113
  formatDetail(data, command);
112
114
  });