@brainbase-labs/cli 0.49.0 → 0.50.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.
Files changed (3) hide show
  1. package/README.md +11 -6
  2. package/dist/index.js +272 -185
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -116,18 +116,23 @@ this confirms admission or delivery, not task success.
116
116
 
117
117
  ## Renaming and labelling tasks
118
118
 
119
- Change a task's title, its metadata labels or its model, including while it runs:
119
+ Label a task when you create it, find tasks by label, and change a task's
120
+ title, its labels or its model, including while it runs:
120
121
 
121
122
  ```sh
122
- brainbase task update <task-id> --title "Nightly smoke run" --metadata env=staging
123
+ brainbase task create --message "Nightly smoke run" --metadata env=staging
124
+ brainbase task list --metadata env=staging
125
+ brainbase task update <task-id> --title "Nightly smoke run" --metadata env=prod
123
126
  brainbase task update <task-id> --unset-metadata env --json
124
127
  brainbase task update <task-id> --model claude-sonnet-5-5
128
+ brainbase task update <task-id> --clear-model
125
129
  ```
126
130
 
127
- `--metadata` and `--unset-metadata` keep every label you do not name.
128
- `--clear-metadata` removes them all, and `--clear-title` removes the title.
129
- `--model` switches the model the task's next turn runs on; a turn already
130
- running keeps its model. See
131
+ `--metadata` and `--unset-metadata` keep every label you do not name, and
132
+ edits from several clients at once all survive. `--clear-metadata` removes them
133
+ all, and `--clear-title` removes the title. `--model` switches the model the
134
+ task's next turn runs on; a turn already running keeps its model.
135
+ `--clear-model` returns the task to its agent's default model. See
131
136
  [the command reference](docs/reference/commands.mdx) for every flag.
132
137
 
133
138
  ## Benchmark management
package/dist/index.js CHANGED
@@ -36091,7 +36091,7 @@ function padStart(s, n) {
36091
36091
  // package.json
36092
36092
  var package_default = {
36093
36093
  name: "@brainbase-labs/cli",
36094
- version: "0.49.0",
36094
+ version: "0.50.0",
36095
36095
  description: "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
36096
36096
  type: "module",
36097
36097
  bin: {
@@ -86110,8 +86110,173 @@ async function waitForAccount(agentId, slug, before2, options) {
86110
86110
  var import_picocolors68 = __toESM(require_picocolors(), 1);
86111
86111
 
86112
86112
  // src/cli/task-create.ts
86113
- var import_picocolors63 = __toESM(require_picocolors(), 1);
86113
+ var import_picocolors64 = __toESM(require_picocolors(), 1);
86114
86114
  import { randomUUID as randomUUID4 } from "node:crypto";
86115
+
86116
+ // src/cli/task-update.ts
86117
+ var import_picocolors63 = __toESM(require_picocolors(), 1);
86118
+ var TASK_UPDATE_USAGE = "brainbase task update <task-id> [--title <text> | --clear-title] [--metadata key=value]... [--unset-metadata <key>]... [--clear-metadata] [--model <id> | --clear-model] [--json]";
86119
+ var TITLE_MAX = 200;
86120
+ var MODEL_UNSUPPORTED = "This control plane does not support changing a task's model yet.";
86121
+ var CLEAR_MODEL_UNSUPPORTED = "This control plane does not support resetting a task's model yet.";
86122
+ async function runTaskUpdate(taskId, options) {
86123
+ const title = validTitle(options);
86124
+ const labels = validLabelEdit(options);
86125
+ const model = validModel(options);
86126
+ if (title === undefined && !labels && !options.clearMetadata && model === undefined && !options.clearModel) {
86127
+ throw new Error(`Nothing to update: pass --title, --clear-title, --metadata, --unset-metadata, --clear-metadata, --model or --clear-model.
86128
+ ` + `Usage: ${TASK_UPDATE_USAGE}`);
86129
+ }
86130
+ const input = {};
86131
+ if (title !== undefined)
86132
+ input.title = title;
86133
+ if (options.clearMetadata) {
86134
+ input.metadata = Object.fromEntries(labels?.set ?? []);
86135
+ } else if (labels) {
86136
+ input.metadata_patch = {
86137
+ set: Object.fromEntries(labels.set),
86138
+ unset: [...labels.unset]
86139
+ };
86140
+ }
86141
+ if (model !== undefined)
86142
+ input.model = model;
86143
+ if (options.clearModel)
86144
+ input.clear_model = true;
86145
+ let task;
86146
+ try {
86147
+ task = await masApi.updateTask(taskId, input);
86148
+ } catch (error2) {
86149
+ if (!(error2 instanceof ApiError))
86150
+ throw error2;
86151
+ if (error2.status === 422 && rejectsBodyField(error2.body, "clear_model")) {
86152
+ throw new ApiError(CLEAR_MODEL_UNSUPPORTED, error2.status, error2.body);
86153
+ }
86154
+ if (error2.status === 422 && rejectsBodyField(error2.body, "model")) {
86155
+ throw new ApiError(MODEL_UNSUPPORTED, error2.status, error2.body);
86156
+ }
86157
+ if (labels && input.metadata_patch && mergeUnsupported(error2)) {
86158
+ task = await updateByReplacingLabels(taskId, input, labels);
86159
+ } else {
86160
+ throw error2;
86161
+ }
86162
+ }
86163
+ if (options.json) {
86164
+ console.log(JSON.stringify(task, null, 2));
86165
+ return;
86166
+ }
86167
+ banner("task update — rename or relabel a task");
86168
+ console.log(format(task));
86169
+ }
86170
+ function mergeUnsupported(error2) {
86171
+ return error2.status === 501 || error2.status === 422 && rejectsBodyField(error2.body, "metadata_patch");
86172
+ }
86173
+ async function updateByReplacingLabels(taskId, input, labels) {
86174
+ const { metadata_patch: _merge, ...rest2 } = input;
86175
+ const current = await masApi.getTask(taskId);
86176
+ return masApi.updateTask(taskId, {
86177
+ ...rest2,
86178
+ metadata: applyLabelEdit(current.metadata ?? {}, labels)
86179
+ });
86180
+ }
86181
+ function validTitle(options) {
86182
+ if (options.clearTitle) {
86183
+ if (options.title !== undefined) {
86184
+ throw new Error("--title and --clear-title cannot be combined.");
86185
+ }
86186
+ return "";
86187
+ }
86188
+ if (options.title === undefined)
86189
+ return;
86190
+ const title = options.title.trim();
86191
+ if (!title) {
86192
+ throw new Error("--title must not be blank; use --clear-title to remove the title.");
86193
+ }
86194
+ if ([...title].length > TITLE_MAX) {
86195
+ throw new Error(`--title must be at most ${TITLE_MAX} characters.`);
86196
+ }
86197
+ return title;
86198
+ }
86199
+ function validModel(options) {
86200
+ if (options.model !== undefined && options.clearModel) {
86201
+ throw new Error("--model and --clear-model cannot be combined.");
86202
+ }
86203
+ if (options.model === undefined)
86204
+ return;
86205
+ const model = options.model.trim();
86206
+ if (!model)
86207
+ throw new Error("--model must not be blank.");
86208
+ if (!MODEL_ID_RE.test(model)) {
86209
+ throw new Error(`--model "${sanitizeLabel(model)}" is not a valid model id: use letters, digits and . _ - : / only, at most 128 characters.`);
86210
+ }
86211
+ return model;
86212
+ }
86213
+ function parseLabelArgs(raw, flag = "--metadata") {
86214
+ const labels = new Map;
86215
+ for (const arg of raw ?? []) {
86216
+ const equals = arg.indexOf("=");
86217
+ if (equals < 0) {
86218
+ throw new Error(`${flag} expects key=value, got "${arg}".`);
86219
+ }
86220
+ const key2 = arg.slice(0, equals).trim();
86221
+ if (!key2) {
86222
+ throw new Error(`${flag} needs a non-empty key, got "${arg}".`);
86223
+ }
86224
+ if (labels.has(key2)) {
86225
+ throw new Error(`${flag} sets "${key2}" more than once.`);
86226
+ }
86227
+ labels.set(key2, arg.slice(equals + 1));
86228
+ }
86229
+ return labels;
86230
+ }
86231
+ function validLabelEdit(options) {
86232
+ const set3 = parseLabelArgs(options.metadata);
86233
+ const unset2 = new Set;
86234
+ for (const raw of options.unsetMetadata ?? []) {
86235
+ const key2 = raw.trim();
86236
+ if (!key2)
86237
+ throw new Error("--unset-metadata needs a non-empty key.");
86238
+ if (set3.has(key2)) {
86239
+ throw new Error(`"${key2}" is both set with --metadata and removed with --unset-metadata.`);
86240
+ }
86241
+ unset2.add(key2);
86242
+ }
86243
+ if (unset2.size > 0 && options.clearMetadata) {
86244
+ throw new Error("--unset-metadata and --clear-metadata cannot be combined: --clear-metadata already removes every label.");
86245
+ }
86246
+ return set3.size > 0 || unset2.size > 0 ? { set: set3, unset: unset2 } : undefined;
86247
+ }
86248
+ function applyLabelEdit(current, edit) {
86249
+ const next = new Map(Object.entries(current).filter(([key2]) => !edit.unset.has(key2)));
86250
+ for (const [key2, value2] of edit.set)
86251
+ next.set(key2, value2);
86252
+ return Object.fromEntries(next);
86253
+ }
86254
+ function format(task) {
86255
+ const lines = [""];
86256
+ const title = task.title?.trim() ? sanitizeLabel(task.title) : import_picocolors63.default.dim("(untitled)");
86257
+ lines.push(` ${import_picocolors63.default.green("updated")} ${import_picocolors63.default.bold(title)}`);
86258
+ lines.push("");
86259
+ lines.push(` ${import_picocolors63.default.dim("task")} ${sanitizeLabel(task.id)}`);
86260
+ if (task.model !== undefined) {
86261
+ const model = task.model?.trim() ? sanitizeLabel(task.model) : import_picocolors63.default.dim("agent default");
86262
+ lines.push(` ${import_picocolors63.default.dim("model")} ${model}`);
86263
+ }
86264
+ const metadata = Object.entries(task.metadata ?? {});
86265
+ lines.push("");
86266
+ lines.push(` ${import_picocolors63.default.bold(import_picocolors63.default.dim("METADATA"))}`);
86267
+ if (metadata.length === 0) {
86268
+ lines.push(` ${import_picocolors63.default.dim("none")}`);
86269
+ }
86270
+ for (const [key2, value2] of metadata) {
86271
+ lines.push(` ${import_picocolors63.default.dim(sanitizeLabel(key2))} ${sanitizeLabel(value2)}`);
86272
+ }
86273
+ lines.push("");
86274
+ lines.push(` ${import_picocolors63.default.dim("read it with")} ${import_picocolors63.default.cyan(`brainbase task get ${sanitizeLabel(task.id)}`)}`, "");
86275
+ return lines.join(`
86276
+ `);
86277
+ }
86278
+
86279
+ // src/cli/task-create.ts
86115
86280
  var DEFAULT_POLL_INTERVAL_MS2 = 2000;
86116
86281
  async function createTask(input, options) {
86117
86282
  return await masApi.createTask(input, options);
@@ -86164,6 +86329,7 @@ async function runTaskCreate(cwd2, options, dependencies = {}) {
86164
86329
  const title = optionalValue(options.title, "--title");
86165
86330
  const model = optionalValue(options.model, "--model");
86166
86331
  const versionRef = optionalValue(options.version, "--version");
86332
+ const labels = parseLabelArgs(options.metadata);
86167
86333
  const version2 = versionRef ? (await agentVersionsApi.resolve(agentId, versionRef)).version : undefined;
86168
86334
  const input = {
86169
86335
  agent_id: agentId,
@@ -86172,7 +86338,8 @@ async function runTaskCreate(cwd2, options, dependencies = {}) {
86172
86338
  ...title ? { title } : {},
86173
86339
  ...model ? { default_model: model } : {},
86174
86340
  ...interfaceId ? { interface_id: interfaceId } : {},
86175
- ...version2 && { agent_version_id: version2.id }
86341
+ ...version2 && { agent_version_id: version2.id },
86342
+ ...labels.size > 0 ? { metadata: Object.fromEntries(labels) } : {}
86176
86343
  };
86177
86344
  let created;
86178
86345
  try {
@@ -86242,24 +86409,24 @@ async function waitForCreatedTask(created, options, dependencies, version2) {
86242
86409
  return;
86243
86410
  }
86244
86411
  if (outcome.kind === "timeout") {
86245
- console.log(import_picocolors63.default.yellow(`Timed out after ${options.timeoutSeconds}s with the task still ${status}.`));
86412
+ console.log(import_picocolors64.default.yellow(`Timed out after ${options.timeoutSeconds}s with the task still ${status}.`));
86246
86413
  return;
86247
86414
  }
86248
86415
  if (outcome.kind === "interrupted") {
86249
- console.log(import_picocolors63.default.yellow("Interrupted. The task is still running."));
86416
+ console.log(import_picocolors64.default.yellow("Interrupted. The task is still running."));
86250
86417
  return;
86251
86418
  }
86252
- console.log(code === 0 ? import_picocolors63.default.green("Task completed.") : import_picocolors63.default.red(`Task finished as ${status}.`));
86419
+ console.log(code === 0 ? import_picocolors64.default.green("Task completed.") : import_picocolors64.default.red(`Task finished as ${status}.`));
86253
86420
  } finally {
86254
86421
  process.removeListener("SIGINT", onSigint);
86255
86422
  }
86256
86423
  }
86257
86424
 
86258
86425
  // src/cli/task-get.ts
86259
- var import_picocolors65 = __toESM(require_picocolors(), 1);
86426
+ var import_picocolors66 = __toESM(require_picocolors(), 1);
86260
86427
 
86261
86428
  // src/cli/task-list.ts
86262
- var import_picocolors64 = __toESM(require_picocolors(), 1);
86429
+ var import_picocolors65 = __toESM(require_picocolors(), 1);
86263
86430
 
86264
86431
  // src/core/agent-names.ts
86265
86432
  async function agentNames(ids) {
@@ -86289,41 +86456,63 @@ function agentLabel(id, names) {
86289
86456
  // src/cli/task-list.ts
86290
86457
  var INTERFACE_THREAD_METADATA_KEY = "brainbase_interface_id";
86291
86458
  async function runTaskList(options) {
86459
+ const metadata = labelFilter(options);
86292
86460
  if (!options.json)
86293
86461
  banner("task list — recent tasks");
86294
86462
  const tasks = await masApi.listTasks({
86295
86463
  agentId: options.agentId,
86296
86464
  limit: options.limit,
86297
- ...options.interfaceId ? { metadata: { [INTERFACE_THREAD_METADATA_KEY]: options.interfaceId } } : {}
86465
+ ...Object.keys(metadata).length > 0 ? { metadata } : {}
86298
86466
  });
86299
86467
  if (options.json) {
86300
86468
  console.log(JSON.stringify(tasks, null, 2));
86301
86469
  return;
86302
86470
  }
86303
86471
  const names = await agentNames(tasks.map((task) => task.agent_id));
86304
- console.log(formatTaskList(tasks, names, options.agentId, options.interfaceId));
86472
+ const filtered = (options.metadata?.length ?? 0) > 0;
86473
+ console.log(formatTaskList(tasks, names, options.agentId, options.interfaceId, filtered));
86474
+ }
86475
+ function labelFilter(options) {
86476
+ const labels = parseLabelArgs(options.metadata);
86477
+ for (const key2 of labels.keys()) {
86478
+ if (key2.includes(":")) {
86479
+ throw new Error(`--metadata cannot filter on "${key2}": a key with ":" cannot be matched.`);
86480
+ }
86481
+ }
86482
+ if (options.interfaceId) {
86483
+ if (labels.has(INTERFACE_THREAD_METADATA_KEY)) {
86484
+ throw new Error(`--interface already filters on ${INTERFACE_THREAD_METADATA_KEY}.`);
86485
+ }
86486
+ labels.set(INTERFACE_THREAD_METADATA_KEY, options.interfaceId);
86487
+ }
86488
+ return Object.fromEntries(labels);
86305
86489
  }
86306
- function formatTaskList(tasks, names, agentId, interfaceId) {
86490
+ function formatTaskList(tasks, names, agentId, interfaceId, labelFiltered = false) {
86307
86491
  const lines = [""];
86492
+ if (tasks.length === 0 && labelFiltered) {
86493
+ lines.push(` ${import_picocolors65.default.dim("No tasks carry every label given with --metadata.")}`, "");
86494
+ return lines.join(`
86495
+ `);
86496
+ }
86308
86497
  if (tasks.length === 0 && interfaceId) {
86309
86498
  const id = sanitizeLabel(interfaceId);
86310
- lines.push(` ${import_picocolors64.default.dim(`No build conversations for interface ${id}.`)}`, "", ` ${import_picocolors64.default.dim("start one with")} ${import_picocolors64.default.cyan(`brainbase interface build ${id} "..."`)}`, "");
86499
+ lines.push(` ${import_picocolors65.default.dim(`No build conversations for interface ${id}.`)}`, "", ` ${import_picocolors65.default.dim("start one with")} ${import_picocolors65.default.cyan(`brainbase interface build ${id} "..."`)}`, "");
86311
86500
  return lines.join(`
86312
86501
  `);
86313
86502
  }
86314
86503
  if (tasks.length === 0) {
86315
- lines.push(` ${import_picocolors64.default.dim(agentId ? `No tasks for agent ${agentId}.` : "No tasks yet.")}`, "", ` ${import_picocolors64.default.dim("start one with")} ${import_picocolors64.default.cyan('brainbase task create --message "..."')}`, "");
86504
+ lines.push(` ${import_picocolors65.default.dim(agentId ? `No tasks for agent ${agentId}.` : "No tasks yet.")}`, "", ` ${import_picocolors65.default.dim("start one with")} ${import_picocolors65.default.cyan('brainbase task create --message "..."')}`, "");
86316
86505
  return lines.join(`
86317
86506
  `);
86318
86507
  }
86319
86508
  for (const task of tasks) {
86320
- const title = sanitizeLabel(task.title ?? "").trim() || import_picocolors64.default.dim("(untitled)");
86321
- lines.push(` ${statusLabel(task.status)} ${import_picocolors64.default.bold(title)}`);
86322
- lines.push(` ${import_picocolors64.default.dim(task.id)}`);
86323
- lines.push(` ${import_picocolors64.default.dim(`agent ${agentLabel(task.agent_id, names)} · created ${task.created_at}`)}`);
86509
+ const title = sanitizeLabel(task.title ?? "").trim() || import_picocolors65.default.dim("(untitled)");
86510
+ lines.push(` ${statusLabel(task.status)} ${import_picocolors65.default.bold(title)}`);
86511
+ lines.push(` ${import_picocolors65.default.dim(task.id)}`);
86512
+ lines.push(` ${import_picocolors65.default.dim(`agent ${agentLabel(task.agent_id, names)} · created ${task.created_at}`)}`);
86324
86513
  lines.push("");
86325
86514
  }
86326
- lines.push(` ${import_picocolors64.default.dim("inspect one with")} ${import_picocolors64.default.cyan("brainbase task get <id>")}`, "");
86515
+ lines.push(` ${import_picocolors65.default.dim("inspect one with")} ${import_picocolors65.default.cyan("brainbase task get <id>")}`, "");
86327
86516
  return lines.join(`
86328
86517
  `);
86329
86518
  }
@@ -86331,16 +86520,16 @@ function statusLabel(status) {
86331
86520
  const padded = status.padEnd(14);
86332
86521
  switch (status) {
86333
86522
  case "success":
86334
- return import_picocolors64.default.green(padded);
86523
+ return import_picocolors65.default.green(padded);
86335
86524
  case "fail":
86336
- return import_picocolors64.default.red(padded);
86525
+ return import_picocolors65.default.red(padded);
86337
86526
  case "need_more_info":
86338
- return import_picocolors64.default.yellow(padded);
86527
+ return import_picocolors65.default.yellow(padded);
86339
86528
  case "running":
86340
86529
  case "initializing":
86341
- return import_picocolors64.default.cyan(padded);
86530
+ return import_picocolors65.default.cyan(padded);
86342
86531
  default:
86343
- return import_picocolors64.default.dim(padded);
86532
+ return import_picocolors65.default.dim(padded);
86344
86533
  }
86345
86534
  }
86346
86535
 
@@ -86357,7 +86546,7 @@ async function runTaskGet(taskId, options = {}) {
86357
86546
  console.log(JSON.stringify(payload, null, 2));
86358
86547
  return;
86359
86548
  }
86360
- console.log(format(task, evals, await agentNames([task.agent_id])));
86549
+ console.log(format2(task, evals, await agentNames([task.agent_id])));
86361
86550
  }
86362
86551
  async function readEvalRuns(task) {
86363
86552
  try {
@@ -86372,51 +86561,51 @@ async function readEvalRuns(task) {
86372
86561
  throw error2;
86373
86562
  }
86374
86563
  }
86375
- function format(task, evals, names) {
86564
+ function format2(task, evals, names) {
86376
86565
  const lines = [""];
86377
- const title = task.title?.trim() || import_picocolors65.default.dim("(untitled)");
86378
- lines.push(` ${statusLabel(task.status)} ${import_picocolors65.default.bold(title)}`);
86566
+ const title = task.title?.trim() || import_picocolors66.default.dim("(untitled)");
86567
+ lines.push(` ${statusLabel(task.status)} ${import_picocolors66.default.bold(title)}`);
86379
86568
  lines.push("");
86380
- lines.push(` ${import_picocolors65.default.dim("task")} ${task.id}`);
86381
- lines.push(` ${import_picocolors65.default.dim("agent")} ${agentLabel(task.agent_id, names)}`);
86382
- lines.push(` ${import_picocolors65.default.dim("created")} ${task.created_at}`);
86569
+ lines.push(` ${import_picocolors66.default.dim("task")} ${task.id}`);
86570
+ lines.push(` ${import_picocolors66.default.dim("agent")} ${agentLabel(task.agent_id, names)}`);
86571
+ lines.push(` ${import_picocolors66.default.dim("created")} ${task.created_at}`);
86383
86572
  if (task.parent_task_id) {
86384
- lines.push(` ${import_picocolors65.default.dim("parent")} ${task.parent_task_id}`);
86573
+ lines.push(` ${import_picocolors66.default.dim("parent")} ${task.parent_task_id}`);
86385
86574
  }
86386
86575
  if (task.machine_id) {
86387
- lines.push(` ${import_picocolors65.default.dim("machine")} ${task.machine_id}${task.machine_size ? ` (size ${task.machine_size})` : ""}`);
86576
+ lines.push(` ${import_picocolors66.default.dim("machine")} ${task.machine_id}${task.machine_size ? ` (size ${task.machine_size})` : ""}`);
86388
86577
  }
86389
86578
  if (task.sandbox_id)
86390
- lines.push(` ${import_picocolors65.default.dim("sandbox")} ${task.sandbox_id}`);
86579
+ lines.push(` ${import_picocolors66.default.dim("sandbox")} ${task.sandbox_id}`);
86391
86580
  const failure = failureSummary(task);
86392
86581
  if (failure) {
86393
86582
  lines.push("");
86394
- lines.push(` ${import_picocolors65.default.red("failure")} ${failure}`);
86583
+ lines.push(` ${import_picocolors66.default.red("failure")} ${failure}`);
86395
86584
  }
86396
86585
  const metadata = Object.entries(task.metadata ?? {});
86397
86586
  if (metadata.length > 0) {
86398
86587
  lines.push("");
86399
- lines.push(` ${import_picocolors65.default.bold(import_picocolors65.default.dim("METADATA"))}`);
86588
+ lines.push(` ${import_picocolors66.default.bold(import_picocolors66.default.dim("METADATA"))}`);
86400
86589
  for (const [key2, value2] of metadata) {
86401
- lines.push(` ${import_picocolors65.default.dim(key2)} ${value2}`);
86590
+ lines.push(` ${import_picocolors66.default.dim(key2)} ${value2}`);
86402
86591
  }
86403
86592
  }
86404
86593
  lines.push("");
86405
- lines.push(` ${import_picocolors65.default.bold(import_picocolors65.default.dim("EVAL VERDICTS"))}`);
86594
+ lines.push(` ${import_picocolors66.default.bold(import_picocolors66.default.dim("EVAL VERDICTS"))}`);
86406
86595
  if (evals.unavailable) {
86407
- lines.push(` ${import_picocolors65.default.dim(evals.unavailable)}`);
86596
+ lines.push(` ${import_picocolors66.default.dim(evals.unavailable)}`);
86408
86597
  } else if (evals.runs.length === 0) {
86409
- lines.push(` ${import_picocolors65.default.dim("none")}`);
86598
+ lines.push(` ${import_picocolors66.default.dim("none")}`);
86410
86599
  } else {
86411
86600
  for (const run of evals.runs) {
86412
- const verdict = run.passed === true ? import_picocolors65.default.green("pass") : run.passed === false ? import_picocolors65.default.red("fail") : import_picocolors65.default.dim(run.status);
86601
+ const verdict = run.passed === true ? import_picocolors66.default.green("pass") : run.passed === false ? import_picocolors66.default.red("fail") : import_picocolors66.default.dim(run.status);
86413
86602
  lines.push(` ${verdict} ${run.eval_slug ?? run.eval_id}`);
86414
86603
  if (run.reasoning)
86415
- lines.push(` ${import_picocolors65.default.dim(run.reasoning)}`);
86604
+ lines.push(` ${import_picocolors66.default.dim(run.reasoning)}`);
86416
86605
  }
86417
86606
  }
86418
86607
  lines.push("");
86419
- lines.push(` ${import_picocolors65.default.dim("read its transcript with")} ${import_picocolors65.default.cyan(`brainbase task logs ${task.id}`)}`, "");
86608
+ lines.push(` ${import_picocolors66.default.dim("read its transcript with")} ${import_picocolors66.default.cyan(`brainbase task logs ${task.id}`)}`, "");
86420
86609
  return lines.join(`
86421
86610
  `);
86422
86611
  }
@@ -86433,7 +86622,7 @@ function failureSummary(task) {
86433
86622
  }
86434
86623
 
86435
86624
  // src/cli/task-logs.ts
86436
- var import_picocolors66 = __toESM(require_picocolors(), 1);
86625
+ var import_picocolors67 = __toESM(require_picocolors(), 1);
86437
86626
 
86438
86627
  // src/core/task-events.ts
86439
86628
  var DEFAULT_RETRY_BUDGET_MS = 60000;
@@ -86716,14 +86905,14 @@ async function runTaskLogs(taskId, options = {}, dependencies = {}) {
86716
86905
  console.log(renderEvent(event, messages));
86717
86906
  }
86718
86907
  if (ordered.length === 0) {
86719
- console.log(import_picocolors66.default.dim("No events for this task yet."));
86908
+ console.log(import_picocolors67.default.dim("No events for this task yet."));
86720
86909
  }
86721
86910
  }
86722
86911
  async function followTranscript(taskId, options, dependencies) {
86723
86912
  const backfill = options.limit === undefined ? "" : `?backfill=${options.limit}`;
86724
86913
  const { url: url2, resolveBearer } = await masStreamTarget(`/tasks/${encodeURIComponent(taskId)}/events/stream${backfill}`);
86725
86914
  if (!options.json) {
86726
- console.log(import_picocolors66.default.dim(`Following task ${taskId} — Ctrl-C to stop.`));
86915
+ console.log(import_picocolors67.default.dim(`Following task ${taskId} — Ctrl-C to stop.`));
86727
86916
  }
86728
86917
  const setExitCode = dependencies.setExitCode ?? ((code) => process.exitCode = code);
86729
86918
  const controller = new AbortController;
@@ -86765,13 +86954,13 @@ async function followTranscript(taskId, options, dependencies) {
86765
86954
  } else if (options.json) {
86766
86955
  console.log(JSON.stringify(frame));
86767
86956
  } else {
86768
- console.log(import_picocolors66.default.dim(frame.type));
86957
+ console.log(import_picocolors67.default.dim(frame.type));
86769
86958
  }
86770
86959
  },
86771
86960
  onOpen: () => settle.noteFrame(),
86772
86961
  onReconnect: (reason) => {
86773
86962
  settle.noteDisconnected();
86774
- console.error(import_picocolors66.default.dim(`reconnecting — ${reason}`));
86963
+ console.error(import_picocolors67.default.dim(`reconnecting — ${reason}`));
86775
86964
  }
86776
86965
  });
86777
86966
  if (interruptCode !== undefined)
@@ -87104,137 +87293,6 @@ Omit --generation only for a deliberately new Stop.`;
87104
87293
  `));
87105
87294
  }
87106
87295
 
87107
- // src/cli/task-update.ts
87108
- var import_picocolors67 = __toESM(require_picocolors(), 1);
87109
- var TASK_UPDATE_USAGE = "brainbase task update <task-id> [--title <text> | --clear-title] [--metadata key=value]... [--unset-metadata <key>]... [--clear-metadata] [--model <id>] [--json]";
87110
- var TITLE_MAX = 200;
87111
- var MODEL_UNSUPPORTED = "This control plane does not support changing a task's model yet.";
87112
- async function runTaskUpdate(taskId, options) {
87113
- const title = validTitle(options);
87114
- const labels = validLabelEdit(options);
87115
- const model = validModel(options);
87116
- if (title === undefined && !labels && !options.clearMetadata && model === undefined) {
87117
- throw new Error(`Nothing to update: pass --title, --clear-title, --metadata, --unset-metadata, --clear-metadata or --model.
87118
- ` + `Usage: ${TASK_UPDATE_USAGE}`);
87119
- }
87120
- const input = {};
87121
- if (title !== undefined)
87122
- input.title = title;
87123
- if (options.clearMetadata) {
87124
- input.metadata = Object.fromEntries(labels?.set ?? []);
87125
- } else if (labels) {
87126
- const current = await masApi.getTask(taskId);
87127
- input.metadata = applyLabelEdit(current.metadata ?? {}, labels);
87128
- }
87129
- if (model !== undefined)
87130
- input.model = model;
87131
- let task;
87132
- try {
87133
- task = await masApi.updateTask(taskId, input);
87134
- } catch (error2) {
87135
- if (error2 instanceof ApiError && error2.status === 422 && rejectsBodyField(error2.body, "model")) {
87136
- throw new ApiError(MODEL_UNSUPPORTED, error2.status, error2.body);
87137
- }
87138
- throw error2;
87139
- }
87140
- if (options.json) {
87141
- console.log(JSON.stringify(task, null, 2));
87142
- return;
87143
- }
87144
- banner("task update — rename or relabel a task");
87145
- console.log(format2(task));
87146
- }
87147
- function validTitle(options) {
87148
- if (options.clearTitle) {
87149
- if (options.title !== undefined) {
87150
- throw new Error("--title and --clear-title cannot be combined.");
87151
- }
87152
- return "";
87153
- }
87154
- if (options.title === undefined)
87155
- return;
87156
- const title = options.title.trim();
87157
- if (!title) {
87158
- throw new Error("--title must not be blank; use --clear-title to remove the title.");
87159
- }
87160
- if ([...title].length > TITLE_MAX) {
87161
- throw new Error(`--title must be at most ${TITLE_MAX} characters.`);
87162
- }
87163
- return title;
87164
- }
87165
- function validModel(options) {
87166
- if (options.model === undefined)
87167
- return;
87168
- const model = options.model.trim();
87169
- if (!model)
87170
- throw new Error("--model must not be blank.");
87171
- if (!MODEL_ID_RE.test(model)) {
87172
- throw new Error(`--model "${sanitizeLabel(model)}" is not a valid model id: use letters, digits and . _ - : / only, at most 128 characters.`);
87173
- }
87174
- return model;
87175
- }
87176
- function validLabelEdit(options) {
87177
- const set3 = new Map;
87178
- for (const raw of options.metadata ?? []) {
87179
- const equals = raw.indexOf("=");
87180
- if (equals < 0) {
87181
- throw new Error(`--metadata expects key=value, got "${raw}".`);
87182
- }
87183
- const key2 = raw.slice(0, equals).trim();
87184
- if (!key2) {
87185
- throw new Error(`--metadata needs a non-empty key, got "${raw}".`);
87186
- }
87187
- if (set3.has(key2)) {
87188
- throw new Error(`--metadata sets "${key2}" more than once.`);
87189
- }
87190
- set3.set(key2, raw.slice(equals + 1));
87191
- }
87192
- const unset2 = new Set;
87193
- for (const raw of options.unsetMetadata ?? []) {
87194
- const key2 = raw.trim();
87195
- if (!key2)
87196
- throw new Error("--unset-metadata needs a non-empty key.");
87197
- if (set3.has(key2)) {
87198
- throw new Error(`"${key2}" is both set with --metadata and removed with --unset-metadata.`);
87199
- }
87200
- unset2.add(key2);
87201
- }
87202
- if (unset2.size > 0 && options.clearMetadata) {
87203
- throw new Error("--unset-metadata and --clear-metadata cannot be combined: --clear-metadata already removes every label.");
87204
- }
87205
- return set3.size > 0 || unset2.size > 0 ? { set: set3, unset: unset2 } : undefined;
87206
- }
87207
- function applyLabelEdit(current, edit) {
87208
- const next = new Map(Object.entries(current).filter(([key2]) => !edit.unset.has(key2)));
87209
- for (const [key2, value2] of edit.set)
87210
- next.set(key2, value2);
87211
- return Object.fromEntries(next);
87212
- }
87213
- function format2(task) {
87214
- const lines = [""];
87215
- const title = task.title?.trim() ? sanitizeLabel(task.title) : import_picocolors67.default.dim("(untitled)");
87216
- lines.push(` ${import_picocolors67.default.green("updated")} ${import_picocolors67.default.bold(title)}`);
87217
- lines.push("");
87218
- lines.push(` ${import_picocolors67.default.dim("task")} ${sanitizeLabel(task.id)}`);
87219
- if (task.model !== undefined) {
87220
- const model = task.model?.trim() ? sanitizeLabel(task.model) : import_picocolors67.default.dim("agent default");
87221
- lines.push(` ${import_picocolors67.default.dim("model")} ${model}`);
87222
- }
87223
- const metadata = Object.entries(task.metadata ?? {});
87224
- lines.push("");
87225
- lines.push(` ${import_picocolors67.default.bold(import_picocolors67.default.dim("METADATA"))}`);
87226
- if (metadata.length === 0) {
87227
- lines.push(` ${import_picocolors67.default.dim("none")}`);
87228
- }
87229
- for (const [key2, value2] of metadata) {
87230
- lines.push(` ${import_picocolors67.default.dim(sanitizeLabel(key2))} ${sanitizeLabel(value2)}`);
87231
- }
87232
- lines.push("");
87233
- lines.push(` ${import_picocolors67.default.dim("read it with")} ${import_picocolors67.default.cyan(`brainbase task get ${sanitizeLabel(task.id)}`)}`, "");
87234
- return lines.join(`
87235
- `);
87236
- }
87237
-
87238
87296
  // src/cli/task.ts
87239
87297
  var VALUE_FLAGS = [
87240
87298
  ["--agent", "agentId"],
@@ -87246,9 +87304,11 @@ var VALUE_FLAGS = [
87246
87304
  ["--timeout", "timeout"]
87247
87305
  ];
87248
87306
  var BOOLEAN_FLAGS = ["--json", "--wait"];
87307
+ var METADATA_FLAG = "--metadata";
87249
87308
  var TASK_OPTION_NAMES = new Set([
87250
87309
  ...VALUE_FLAGS.map(([flag]) => flag),
87251
- ...BOOLEAN_FLAGS
87310
+ ...BOOLEAN_FLAGS,
87311
+ METADATA_FLAG
87252
87312
  ]);
87253
87313
  var HELP_FLAGS = new Set(["--help", "-h"]);
87254
87314
  var LIST_LIMIT_MAX = 200;
@@ -87280,6 +87340,25 @@ function parseCreateArgs(args) {
87280
87340
  options.wait = true;
87281
87341
  continue;
87282
87342
  }
87343
+ if (arg === METADATA_FLAG || arg.startsWith(`${METADATA_FLAG}=`)) {
87344
+ let value2;
87345
+ if (arg !== METADATA_FLAG) {
87346
+ value2 = arg.slice(METADATA_FLAG.length + 1);
87347
+ } else {
87348
+ value2 = args[index + 1];
87349
+ if (value2 === "--") {
87350
+ value2 = args[index + 2];
87351
+ index += 1;
87352
+ } else if (value2 !== undefined && TASK_OPTION_NAMES.has(value2)) {
87353
+ throw flagLikeValueError(METADATA_FLAG, value2);
87354
+ }
87355
+ if (value2 === undefined)
87356
+ throw new Error("--metadata expects key=value.");
87357
+ index += 1;
87358
+ }
87359
+ (options.metadata ??= []).push(value2);
87360
+ continue;
87361
+ }
87283
87362
  let matched = false;
87284
87363
  for (const [flag, option] of VALUE_FLAGS) {
87285
87364
  if (arg === flag) {
@@ -87412,16 +87491,18 @@ async function dispatchTask(cwd2, sub, args, setJson) {
87412
87491
  const parsed = parse6([
87413
87492
  { key: "agent", names: ["--agent"], kind: "value" },
87414
87493
  { key: "interface", names: ["--interface"], kind: "value" },
87494
+ { key: "metadata", names: ["--metadata"], kind: "value", multiple: true },
87415
87495
  { key: "limit", names: ["--limit"], kind: "value" },
87416
87496
  { key: "json", names: ["--json"], kind: "boolean" }
87417
87497
  ]);
87418
87498
  if (parsed.positionals.length > 0) {
87419
- throw new Error("Usage: brainbase task list [--agent <id>] [--interface <id>] [--limit <n>] [--json]");
87499
+ throw new Error("Usage: brainbase task list [--agent <id>] [--interface <id>] [--metadata key=value]... [--limit <n>] [--json]");
87420
87500
  }
87421
87501
  const interfaceId = optionalValue(stringOption(parsed, "interface"), "--interface");
87422
87502
  await runTaskList({
87423
87503
  agentId: stringOption(parsed, "agent"),
87424
87504
  interfaceId,
87505
+ metadata: stringOptions(parsed, "metadata"),
87425
87506
  limit: positiveInteger(stringOption(parsed, "limit"), "--limit", LIST_LIMIT_MAX),
87426
87507
  json: booleanOption(parsed, "json")
87427
87508
  });
@@ -87473,6 +87554,7 @@ async function dispatchTask(cwd2, sub, args, setJson) {
87473
87554
  { key: "unsetMetadata", names: ["--unset-metadata"], kind: "value", multiple: true },
87474
87555
  { key: "clearMetadata", names: ["--clear-metadata"], kind: "boolean" },
87475
87556
  { key: "model", names: ["--model"], kind: "value" },
87557
+ { key: "clearModel", names: ["--clear-model"], kind: "boolean" },
87476
87558
  { key: "json", names: ["--json"], kind: "boolean" },
87477
87559
  { key: "help", names: ["--help", "-h"], kind: "boolean" }
87478
87560
  ]);
@@ -87488,6 +87570,7 @@ async function dispatchTask(cwd2, sub, args, setJson) {
87488
87570
  unsetMetadata: stringOptions(parsed, "unsetMetadata"),
87489
87571
  clearMetadata: booleanOption(parsed, "clearMetadata"),
87490
87572
  model: stringOption(parsed, "model"),
87573
+ clearModel: booleanOption(parsed, "clearModel"),
87491
87574
  json: booleanOption(parsed, "json")
87492
87575
  });
87493
87576
  return;
@@ -87563,7 +87646,7 @@ function printHelp5() {
87563
87646
  out.push(` ${import_picocolors68.default.cyan("logs")} ${import_picocolors68.default.dim("<task-id> [--follow]")} ${import_picocolors68.default.dim("one line per event in the task's transcript, or tail it live")}`);
87564
87647
  out.push(` ${import_picocolors68.default.cyan("send")} ${import_picocolors68.default.dim("<task-id> <message>")} ${import_picocolors68.default.dim("send a follow-up, including while the agent is running")}`);
87565
87648
  out.push(` ${import_picocolors68.default.cyan("stop")} ${import_picocolors68.default.dim("<task-id>")} ${import_picocolors68.default.dim("stop execution and cancel pending follow-ups, retaining their text")}`);
87566
- out.push(` ${import_picocolors68.default.cyan("update")} ${import_picocolors68.default.dim("<task-id>")} ${import_picocolors68.default.dim("rename a task, change its metadata labels or switch its model, including while it runs")}`);
87649
+ out.push(` ${import_picocolors68.default.cyan("update")} ${import_picocolors68.default.dim("<task-id>")} ${import_picocolors68.default.dim("rename a task, change its metadata labels, switch its model or reset it to the agent default, including while it runs")}`);
87567
87650
  out.push(` ${import_picocolors68.default.cyan("delete")} ${import_picocolors68.default.dim("<task-id>")} ${import_picocolors68.default.dim("permanently delete a stopped task and tear down its sandbox (--yes to skip the prompt)")}`);
87568
87651
  out.push("");
87569
87652
  out.push(` ${import_picocolors68.default.bold("create flags")}`);
@@ -87573,12 +87656,14 @@ function printHelp5() {
87573
87656
  out.push(` ${import_picocolors68.default.dim("--title <text>")} optional task title`);
87574
87657
  out.push(` ${import_picocolors68.default.dim("--model <id>")} optional model override`);
87575
87658
  out.push(` ${import_picocolors68.default.dim("--version <v>")} run a saved version of the agent: 12, v12 or a version id (\`agent versions list\`)`);
87659
+ out.push(` ${import_picocolors68.default.dim("--metadata key=value")} label the task (repeatable)`);
87576
87660
  out.push(` ${import_picocolors68.default.dim("--wait")} block until the task finishes; exit 1 unless it succeeded`);
87577
87661
  out.push(` ${import_picocolors68.default.dim("--timeout <secs>")} give up waiting after <secs> and exit 1 (needs --wait)`);
87578
87662
  out.push("");
87579
87663
  out.push(` ${import_picocolors68.default.bold("list flags")}`);
87580
87664
  out.push(` ${import_picocolors68.default.dim("--agent <id>")} only this agent's tasks`);
87581
87665
  out.push(` ${import_picocolors68.default.dim("--interface <id>")} only this interface's build conversations (hidden from a plain list)`);
87666
+ out.push(` ${import_picocolors68.default.dim("--metadata key=value")} only tasks carrying this label (repeatable; all must match)`);
87582
87667
  out.push(` ${import_picocolors68.default.dim("--limit <n>")} cap the page (1-200, server default 50)`);
87583
87668
  out.push("");
87584
87669
  out.push(` ${import_picocolors68.default.bold("logs flags")}`);
@@ -87590,9 +87675,10 @@ function printHelp5() {
87590
87675
  out.push(` ${import_picocolors68.default.dim("--clear-title")} remove the title; automatic titling may name the task again on a later turn`);
87591
87676
  out.push(` ${import_picocolors68.default.dim("--metadata key=value")} set one label, keeping the others (repeatable)`);
87592
87677
  out.push(` ${import_picocolors68.default.dim("--unset-metadata <key>")} remove one label (repeatable)`);
87678
+ out.push(` ${import_picocolors68.default.dim(" the server applies label edits in one step, so concurrent edits from other clients are kept")}`);
87593
87679
  out.push(` ${import_picocolors68.default.dim("--clear-metadata")} remove every label; with --metadata, the labels become exactly those given`);
87594
- out.push(` ${import_picocolors68.default.dim(" label edits read the current labels first, so two writers at once can overwrite each other")}`);
87595
87680
  out.push(` ${import_picocolors68.default.dim("--model <id>")} switch the model the task's next turn runs on (the running turn keeps its model)`);
87681
+ out.push(` ${import_picocolors68.default.dim("--clear-model")} go back to the agent's default model from the next turn`);
87596
87682
  out.push("");
87597
87683
  out.push(` ${import_picocolors68.default.bold("send flags")}`);
87598
87684
  out.push(` ${import_picocolors68.default.dim("--input-id <uuid>")} recover the same request using its original ID and identical message`);
@@ -97096,12 +97182,12 @@ function help() {
97096
97182
  out.push(divider("TASKS"));
97097
97183
  out.push("");
97098
97184
  out.push(` ${import_picocolors80.default.cyan("task create")} ${import_picocolors80.default.dim("--message <text>")} ${import_picocolors80.default.dim("create a managed task and start its first run (--wait to block on it; --interface <id> for a build conversation)")}`);
97099
- out.push(` ${import_picocolors80.default.cyan("task list")} ${import_picocolors80.default.dim("recent tasks you can reach (--interface <id> for its build conversations)")}`);
97185
+ out.push(` ${import_picocolors80.default.cyan("task list")} ${import_picocolors80.default.dim("recent tasks you can reach (--metadata k=v to filter by label; --interface <id> for its build conversations)")}`);
97100
97186
  out.push(` ${import_picocolors80.default.cyan("task get")} ${import_picocolors80.default.dim("<task-id>")} ${import_picocolors80.default.dim("one task: status, agent, machine, eval verdicts")}`);
97101
97187
  out.push(` ${import_picocolors80.default.cyan("task logs")} ${import_picocolors80.default.dim("<task-id> [--follow]")} ${import_picocolors80.default.dim("one line per event in the task's transcript, or tail it live")}`);
97102
97188
  out.push(` ${import_picocolors80.default.cyan("task send")} ${import_picocolors80.default.dim("<task-id> <message>")} ${import_picocolors80.default.dim("send a follow-up during execution, or queue it for the next turn")}`);
97103
97189
  out.push(` ${import_picocolors80.default.cyan("task stop")} ${import_picocolors80.default.dim("<task-id>")} ${import_picocolors80.default.dim("stop execution and cancel pending follow-ups, retaining their text")}`);
97104
- out.push(` ${import_picocolors80.default.cyan("task update")} ${import_picocolors80.default.dim("<task-id>")} ${import_picocolors80.default.dim("rename a task, change its metadata labels or switch its model, including while it runs")}`);
97190
+ out.push(` ${import_picocolors80.default.cyan("task update")} ${import_picocolors80.default.dim("<task-id>")} ${import_picocolors80.default.dim("rename a task, change its metadata labels, switch its model or reset it to the agent default, including while it runs")}`);
97105
97191
  out.push(` ${import_picocolors80.default.cyan("task delete")} ${import_picocolors80.default.dim("<task-id>")} ${import_picocolors80.default.dim("permanently delete a stopped task and tear down its sandbox")}`);
97106
97192
  out.push("");
97107
97193
  out.push(divider("INTERFACES"));
@@ -97197,10 +97283,11 @@ function help() {
97197
97283
  out.push(` ${import_picocolors80.default.dim("--message <text>")} for task create: required first user message`);
97198
97284
  out.push(` ${import_picocolors80.default.dim("--title <text>")} for task create: optional task title; for task update: the new title`);
97199
97285
  out.push(` ${import_picocolors80.default.dim("--clear-title")} for task update: remove the title (automatic titling may name it again later)`);
97200
- out.push(` ${import_picocolors80.default.dim("--metadata k=v")} for task update: set one label, keeping the others (repeatable)`);
97286
+ out.push(` ${import_picocolors80.default.dim("--metadata k=v")} for task create: label the task; for task list: only tasks with this label; for task update: set one label, keeping the others (all repeatable)`);
97201
97287
  out.push(` ${import_picocolors80.default.dim("--unset-metadata <k>")} for task update: remove one label (repeatable)`);
97202
97288
  out.push(` ${import_picocolors80.default.dim("--clear-metadata")} for task update: remove every label; with --metadata, keep only those given`);
97203
97289
  out.push(` ${import_picocolors80.default.dim("--model <id>")} for task create: optional model override; for task update: switch the model the task's next turn runs on (the running turn keeps its model)`);
97290
+ out.push(` ${import_picocolors80.default.dim("--clear-model")} for task update: go back to the agent's default model from the next turn`);
97204
97291
  out.push(` ${import_picocolors80.default.dim("--version <v>")} for task create: run a saved version of the agent (12, v12 or a version id)`);
97205
97292
  out.push(` ${import_picocolors80.default.dim("--wait")} for task create: block until the task finishes; exit 1 unless it succeeded`);
97206
97293
  out.push(` ${import_picocolors80.default.dim("--timeout <secs>")} for task create --wait: give up after <secs> and exit 1`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainbase-labs/cli",
3
- "version": "0.49.0",
3
+ "version": "0.50.0",
4
4
  "description": "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
5
5
  "type": "module",
6
6
  "bin": {