@brainbase-labs/cli 0.49.0 → 0.51.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 +320 -191
  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.51.0",
36095
36095
  description: "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
36096
36096
  type: "module",
36097
36097
  bin: {
@@ -68334,6 +68334,9 @@ var import_picocolors30 = __toESM(require_picocolors(), 1);
68334
68334
  function sanitizeLabel(s3) {
68335
68335
  return s3.replace(/\u001b\][\s\S]*?(?:\u0007|\u001b\\|$)/g, "").replace(/\u001b\[[\u0030-\u003f]*[\u0020-\u002f]*[\u0040-\u007e]?/g, "").replace(/\u001b[\s\S]?/g, "").replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/g, "");
68336
68336
  }
68337
+ function sanitizeLines(s3) {
68338
+ return s3.split(/\r\n|[\n\r\u2028\u2029]/).map(sanitizeLabel);
68339
+ }
68337
68340
 
68338
68341
  // src/cli/agent-status.ts
68339
68342
  function capabilityDrifted(field) {
@@ -86110,8 +86113,173 @@ async function waitForAccount(agentId, slug, before2, options) {
86110
86113
  var import_picocolors68 = __toESM(require_picocolors(), 1);
86111
86114
 
86112
86115
  // src/cli/task-create.ts
86113
- var import_picocolors63 = __toESM(require_picocolors(), 1);
86116
+ var import_picocolors64 = __toESM(require_picocolors(), 1);
86114
86117
  import { randomUUID as randomUUID4 } from "node:crypto";
86118
+
86119
+ // src/cli/task-update.ts
86120
+ var import_picocolors63 = __toESM(require_picocolors(), 1);
86121
+ 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]";
86122
+ var TITLE_MAX = 200;
86123
+ var MODEL_UNSUPPORTED = "This control plane does not support changing a task's model yet.";
86124
+ var CLEAR_MODEL_UNSUPPORTED = "This control plane does not support resetting a task's model yet.";
86125
+ async function runTaskUpdate(taskId, options) {
86126
+ const title = validTitle(options);
86127
+ const labels = validLabelEdit(options);
86128
+ const model = validModel(options);
86129
+ if (title === undefined && !labels && !options.clearMetadata && model === undefined && !options.clearModel) {
86130
+ throw new Error(`Nothing to update: pass --title, --clear-title, --metadata, --unset-metadata, --clear-metadata, --model or --clear-model.
86131
+ ` + `Usage: ${TASK_UPDATE_USAGE}`);
86132
+ }
86133
+ const input = {};
86134
+ if (title !== undefined)
86135
+ input.title = title;
86136
+ if (options.clearMetadata) {
86137
+ input.metadata = Object.fromEntries(labels?.set ?? []);
86138
+ } else if (labels) {
86139
+ input.metadata_patch = {
86140
+ set: Object.fromEntries(labels.set),
86141
+ unset: [...labels.unset]
86142
+ };
86143
+ }
86144
+ if (model !== undefined)
86145
+ input.model = model;
86146
+ if (options.clearModel)
86147
+ input.clear_model = true;
86148
+ let task;
86149
+ try {
86150
+ task = await masApi.updateTask(taskId, input);
86151
+ } catch (error2) {
86152
+ if (!(error2 instanceof ApiError))
86153
+ throw error2;
86154
+ if (error2.status === 422 && rejectsBodyField(error2.body, "clear_model")) {
86155
+ throw new ApiError(CLEAR_MODEL_UNSUPPORTED, error2.status, error2.body);
86156
+ }
86157
+ if (error2.status === 422 && rejectsBodyField(error2.body, "model")) {
86158
+ throw new ApiError(MODEL_UNSUPPORTED, error2.status, error2.body);
86159
+ }
86160
+ if (labels && input.metadata_patch && mergeUnsupported(error2)) {
86161
+ task = await updateByReplacingLabels(taskId, input, labels);
86162
+ } else {
86163
+ throw error2;
86164
+ }
86165
+ }
86166
+ if (options.json) {
86167
+ console.log(JSON.stringify(task, null, 2));
86168
+ return;
86169
+ }
86170
+ banner("task update — rename or relabel a task");
86171
+ console.log(format(task));
86172
+ }
86173
+ function mergeUnsupported(error2) {
86174
+ return error2.status === 501 || error2.status === 422 && rejectsBodyField(error2.body, "metadata_patch");
86175
+ }
86176
+ async function updateByReplacingLabels(taskId, input, labels) {
86177
+ const { metadata_patch: _merge, ...rest2 } = input;
86178
+ const current = await masApi.getTask(taskId);
86179
+ return masApi.updateTask(taskId, {
86180
+ ...rest2,
86181
+ metadata: applyLabelEdit(current.metadata ?? {}, labels)
86182
+ });
86183
+ }
86184
+ function validTitle(options) {
86185
+ if (options.clearTitle) {
86186
+ if (options.title !== undefined) {
86187
+ throw new Error("--title and --clear-title cannot be combined.");
86188
+ }
86189
+ return "";
86190
+ }
86191
+ if (options.title === undefined)
86192
+ return;
86193
+ const title = options.title.trim();
86194
+ if (!title) {
86195
+ throw new Error("--title must not be blank; use --clear-title to remove the title.");
86196
+ }
86197
+ if ([...title].length > TITLE_MAX) {
86198
+ throw new Error(`--title must be at most ${TITLE_MAX} characters.`);
86199
+ }
86200
+ return title;
86201
+ }
86202
+ function validModel(options) {
86203
+ if (options.model !== undefined && options.clearModel) {
86204
+ throw new Error("--model and --clear-model cannot be combined.");
86205
+ }
86206
+ if (options.model === undefined)
86207
+ return;
86208
+ const model = options.model.trim();
86209
+ if (!model)
86210
+ throw new Error("--model must not be blank.");
86211
+ if (!MODEL_ID_RE.test(model)) {
86212
+ throw new Error(`--model "${sanitizeLabel(model)}" is not a valid model id: use letters, digits and . _ - : / only, at most 128 characters.`);
86213
+ }
86214
+ return model;
86215
+ }
86216
+ function parseLabelArgs(raw, flag = "--metadata") {
86217
+ const labels = new Map;
86218
+ for (const arg of raw ?? []) {
86219
+ const equals = arg.indexOf("=");
86220
+ if (equals < 0) {
86221
+ throw new Error(`${flag} expects key=value, got "${arg}".`);
86222
+ }
86223
+ const key2 = arg.slice(0, equals).trim();
86224
+ if (!key2) {
86225
+ throw new Error(`${flag} needs a non-empty key, got "${arg}".`);
86226
+ }
86227
+ if (labels.has(key2)) {
86228
+ throw new Error(`${flag} sets "${key2}" more than once.`);
86229
+ }
86230
+ labels.set(key2, arg.slice(equals + 1));
86231
+ }
86232
+ return labels;
86233
+ }
86234
+ function validLabelEdit(options) {
86235
+ const set3 = parseLabelArgs(options.metadata);
86236
+ const unset2 = new Set;
86237
+ for (const raw of options.unsetMetadata ?? []) {
86238
+ const key2 = raw.trim();
86239
+ if (!key2)
86240
+ throw new Error("--unset-metadata needs a non-empty key.");
86241
+ if (set3.has(key2)) {
86242
+ throw new Error(`"${key2}" is both set with --metadata and removed with --unset-metadata.`);
86243
+ }
86244
+ unset2.add(key2);
86245
+ }
86246
+ if (unset2.size > 0 && options.clearMetadata) {
86247
+ throw new Error("--unset-metadata and --clear-metadata cannot be combined: --clear-metadata already removes every label.");
86248
+ }
86249
+ return set3.size > 0 || unset2.size > 0 ? { set: set3, unset: unset2 } : undefined;
86250
+ }
86251
+ function applyLabelEdit(current, edit) {
86252
+ const next = new Map(Object.entries(current).filter(([key2]) => !edit.unset.has(key2)));
86253
+ for (const [key2, value2] of edit.set)
86254
+ next.set(key2, value2);
86255
+ return Object.fromEntries(next);
86256
+ }
86257
+ function format(task) {
86258
+ const lines = [""];
86259
+ const title = task.title?.trim() ? sanitizeLabel(task.title) : import_picocolors63.default.dim("(untitled)");
86260
+ lines.push(` ${import_picocolors63.default.green("updated")} ${import_picocolors63.default.bold(title)}`);
86261
+ lines.push("");
86262
+ lines.push(` ${import_picocolors63.default.dim("task")} ${sanitizeLabel(task.id)}`);
86263
+ if (task.model !== undefined) {
86264
+ const model = task.model?.trim() ? sanitizeLabel(task.model) : import_picocolors63.default.dim("agent default");
86265
+ lines.push(` ${import_picocolors63.default.dim("model")} ${model}`);
86266
+ }
86267
+ const metadata = Object.entries(task.metadata ?? {});
86268
+ lines.push("");
86269
+ lines.push(` ${import_picocolors63.default.bold(import_picocolors63.default.dim("METADATA"))}`);
86270
+ if (metadata.length === 0) {
86271
+ lines.push(` ${import_picocolors63.default.dim("none")}`);
86272
+ }
86273
+ for (const [key2, value2] of metadata) {
86274
+ lines.push(` ${import_picocolors63.default.dim(sanitizeLabel(key2))} ${sanitizeLabel(value2)}`);
86275
+ }
86276
+ lines.push("");
86277
+ lines.push(` ${import_picocolors63.default.dim("read it with")} ${import_picocolors63.default.cyan(`brainbase task get ${sanitizeLabel(task.id)}`)}`, "");
86278
+ return lines.join(`
86279
+ `);
86280
+ }
86281
+
86282
+ // src/cli/task-create.ts
86115
86283
  var DEFAULT_POLL_INTERVAL_MS2 = 2000;
86116
86284
  async function createTask(input, options) {
86117
86285
  return await masApi.createTask(input, options);
@@ -86164,6 +86332,7 @@ async function runTaskCreate(cwd2, options, dependencies = {}) {
86164
86332
  const title = optionalValue(options.title, "--title");
86165
86333
  const model = optionalValue(options.model, "--model");
86166
86334
  const versionRef = optionalValue(options.version, "--version");
86335
+ const labels = parseLabelArgs(options.metadata);
86167
86336
  const version2 = versionRef ? (await agentVersionsApi.resolve(agentId, versionRef)).version : undefined;
86168
86337
  const input = {
86169
86338
  agent_id: agentId,
@@ -86172,7 +86341,8 @@ async function runTaskCreate(cwd2, options, dependencies = {}) {
86172
86341
  ...title ? { title } : {},
86173
86342
  ...model ? { default_model: model } : {},
86174
86343
  ...interfaceId ? { interface_id: interfaceId } : {},
86175
- ...version2 && { agent_version_id: version2.id }
86344
+ ...version2 && { agent_version_id: version2.id },
86345
+ ...labels.size > 0 ? { metadata: Object.fromEntries(labels) } : {}
86176
86346
  };
86177
86347
  let created;
86178
86348
  try {
@@ -86242,24 +86412,24 @@ async function waitForCreatedTask(created, options, dependencies, version2) {
86242
86412
  return;
86243
86413
  }
86244
86414
  if (outcome.kind === "timeout") {
86245
- console.log(import_picocolors63.default.yellow(`Timed out after ${options.timeoutSeconds}s with the task still ${status}.`));
86415
+ console.log(import_picocolors64.default.yellow(`Timed out after ${options.timeoutSeconds}s with the task still ${status}.`));
86246
86416
  return;
86247
86417
  }
86248
86418
  if (outcome.kind === "interrupted") {
86249
- console.log(import_picocolors63.default.yellow("Interrupted. The task is still running."));
86419
+ console.log(import_picocolors64.default.yellow("Interrupted. The task is still running."));
86250
86420
  return;
86251
86421
  }
86252
- console.log(code === 0 ? import_picocolors63.default.green("Task completed.") : import_picocolors63.default.red(`Task finished as ${status}.`));
86422
+ console.log(code === 0 ? import_picocolors64.default.green("Task completed.") : import_picocolors64.default.red(`Task finished as ${status}.`));
86253
86423
  } finally {
86254
86424
  process.removeListener("SIGINT", onSigint);
86255
86425
  }
86256
86426
  }
86257
86427
 
86258
86428
  // src/cli/task-get.ts
86259
- var import_picocolors65 = __toESM(require_picocolors(), 1);
86429
+ var import_picocolors66 = __toESM(require_picocolors(), 1);
86260
86430
 
86261
86431
  // src/cli/task-list.ts
86262
- var import_picocolors64 = __toESM(require_picocolors(), 1);
86432
+ var import_picocolors65 = __toESM(require_picocolors(), 1);
86263
86433
 
86264
86434
  // src/core/agent-names.ts
86265
86435
  async function agentNames(ids) {
@@ -86283,64 +86453,87 @@ async function agentNames(ids) {
86283
86453
  }
86284
86454
  function agentLabel(id, names) {
86285
86455
  const name = names.get(id.toLowerCase());
86286
- return name ? `${name} (${id})` : id;
86456
+ const safeId = sanitizeLabel(id);
86457
+ return name ? `${name} (${safeId})` : safeId;
86287
86458
  }
86288
86459
 
86289
86460
  // src/cli/task-list.ts
86290
86461
  var INTERFACE_THREAD_METADATA_KEY = "brainbase_interface_id";
86291
86462
  async function runTaskList(options) {
86463
+ const metadata = labelFilter(options);
86292
86464
  if (!options.json)
86293
86465
  banner("task list — recent tasks");
86294
86466
  const tasks = await masApi.listTasks({
86295
86467
  agentId: options.agentId,
86296
86468
  limit: options.limit,
86297
- ...options.interfaceId ? { metadata: { [INTERFACE_THREAD_METADATA_KEY]: options.interfaceId } } : {}
86469
+ ...Object.keys(metadata).length > 0 ? { metadata } : {}
86298
86470
  });
86299
86471
  if (options.json) {
86300
86472
  console.log(JSON.stringify(tasks, null, 2));
86301
86473
  return;
86302
86474
  }
86303
86475
  const names = await agentNames(tasks.map((task) => task.agent_id));
86304
- console.log(formatTaskList(tasks, names, options.agentId, options.interfaceId));
86476
+ const filtered = (options.metadata?.length ?? 0) > 0;
86477
+ console.log(formatTaskList(tasks, names, options.agentId, options.interfaceId, filtered));
86305
86478
  }
86306
- function formatTaskList(tasks, names, agentId, interfaceId) {
86479
+ function labelFilter(options) {
86480
+ const labels = parseLabelArgs(options.metadata);
86481
+ for (const key2 of labels.keys()) {
86482
+ if (key2.includes(":")) {
86483
+ throw new Error(`--metadata cannot filter on "${key2}": a key with ":" cannot be matched.`);
86484
+ }
86485
+ }
86486
+ if (options.interfaceId) {
86487
+ if (labels.has(INTERFACE_THREAD_METADATA_KEY)) {
86488
+ throw new Error(`--interface already filters on ${INTERFACE_THREAD_METADATA_KEY}.`);
86489
+ }
86490
+ labels.set(INTERFACE_THREAD_METADATA_KEY, options.interfaceId);
86491
+ }
86492
+ return Object.fromEntries(labels);
86493
+ }
86494
+ function formatTaskList(tasks, names, agentId, interfaceId, labelFiltered = false) {
86307
86495
  const lines = [""];
86496
+ if (tasks.length === 0 && labelFiltered) {
86497
+ lines.push(` ${import_picocolors65.default.dim("No tasks carry every label given with --metadata.")}`, "");
86498
+ return lines.join(`
86499
+ `);
86500
+ }
86308
86501
  if (tasks.length === 0 && interfaceId) {
86309
86502
  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} "..."`)}`, "");
86503
+ 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
86504
  return lines.join(`
86312
86505
  `);
86313
86506
  }
86314
86507
  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 "..."')}`, "");
86508
+ 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
86509
  return lines.join(`
86317
86510
  `);
86318
86511
  }
86319
86512
  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}`)}`);
86513
+ const title = sanitizeLabel(task.title ?? "").trim() || import_picocolors65.default.dim("(untitled)");
86514
+ lines.push(` ${statusLabel(task.status)} ${import_picocolors65.default.bold(title)}`);
86515
+ lines.push(` ${import_picocolors65.default.dim(sanitizeLabel(task.id))}`);
86516
+ lines.push(` ${import_picocolors65.default.dim(`agent ${agentLabel(task.agent_id, names)} · created ${sanitizeLabel(task.created_at)}`)}`);
86324
86517
  lines.push("");
86325
86518
  }
86326
- lines.push(` ${import_picocolors64.default.dim("inspect one with")} ${import_picocolors64.default.cyan("brainbase task get <id>")}`, "");
86519
+ lines.push(` ${import_picocolors65.default.dim("inspect one with")} ${import_picocolors65.default.cyan("brainbase task get <id>")}`, "");
86327
86520
  return lines.join(`
86328
86521
  `);
86329
86522
  }
86330
86523
  function statusLabel(status) {
86331
- const padded = status.padEnd(14);
86524
+ const padded = sanitizeLabel(status).padEnd(14);
86332
86525
  switch (status) {
86333
86526
  case "success":
86334
- return import_picocolors64.default.green(padded);
86527
+ return import_picocolors65.default.green(padded);
86335
86528
  case "fail":
86336
- return import_picocolors64.default.red(padded);
86529
+ return import_picocolors65.default.red(padded);
86337
86530
  case "need_more_info":
86338
- return import_picocolors64.default.yellow(padded);
86531
+ return import_picocolors65.default.yellow(padded);
86339
86532
  case "running":
86340
86533
  case "initializing":
86341
- return import_picocolors64.default.cyan(padded);
86534
+ return import_picocolors65.default.cyan(padded);
86342
86535
  default:
86343
- return import_picocolors64.default.dim(padded);
86536
+ return import_picocolors65.default.dim(padded);
86344
86537
  }
86345
86538
  }
86346
86539
 
@@ -86357,7 +86550,7 @@ async function runTaskGet(taskId, options = {}) {
86357
86550
  console.log(JSON.stringify(payload, null, 2));
86358
86551
  return;
86359
86552
  }
86360
- console.log(format(task, evals, await agentNames([task.agent_id])));
86553
+ console.log(format2(task, evals, await agentNames([task.agent_id])));
86361
86554
  }
86362
86555
  async function readEvalRuns(task) {
86363
86556
  try {
@@ -86372,51 +86565,62 @@ async function readEvalRuns(task) {
86372
86565
  throw error2;
86373
86566
  }
86374
86567
  }
86375
- function format(task, evals, names) {
86568
+ function format2(task, evals, names) {
86376
86569
  const lines = [""];
86377
- const title = task.title?.trim() || import_picocolors65.default.dim("(untitled)");
86378
- lines.push(` ${statusLabel(task.status)} ${import_picocolors65.default.bold(title)}`);
86570
+ const title = sanitizeLabel(task.title ?? "").trim() || import_picocolors66.default.dim("(untitled)");
86571
+ const id = sanitizeLabel(task.id);
86572
+ lines.push(` ${statusLabel(task.status)} ${import_picocolors66.default.bold(title)}`);
86379
86573
  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}`);
86574
+ lines.push(` ${import_picocolors66.default.dim("task")} ${id}`);
86575
+ lines.push(` ${import_picocolors66.default.dim("agent")} ${agentLabel(task.agent_id, names)}`);
86576
+ if (task.model !== undefined) {
86577
+ const model = task.model?.trim() ? sanitizeLabel(task.model) : import_picocolors66.default.dim("agent default");
86578
+ lines.push(` ${import_picocolors66.default.dim("model")} ${model}`);
86579
+ }
86580
+ lines.push(` ${import_picocolors66.default.dim("created")} ${sanitizeLabel(task.created_at)}`);
86383
86581
  if (task.parent_task_id) {
86384
- lines.push(` ${import_picocolors65.default.dim("parent")} ${task.parent_task_id}`);
86582
+ lines.push(` ${import_picocolors66.default.dim("parent")} ${sanitizeLabel(task.parent_task_id)}`);
86385
86583
  }
86386
86584
  if (task.machine_id) {
86387
- lines.push(` ${import_picocolors65.default.dim("machine")} ${task.machine_id}${task.machine_size ? ` (size ${task.machine_size})` : ""}`);
86585
+ const size2 = task.machine_size ? ` (size ${sanitizeLabel(task.machine_size)})` : "";
86586
+ lines.push(` ${import_picocolors66.default.dim("machine")} ${sanitizeLabel(task.machine_id)}${size2}`);
86388
86587
  }
86389
86588
  if (task.sandbox_id)
86390
- lines.push(` ${import_picocolors65.default.dim("sandbox")} ${task.sandbox_id}`);
86391
- const failure = failureSummary(task);
86589
+ lines.push(` ${import_picocolors66.default.dim("sandbox")} ${sanitizeLabel(task.sandbox_id)}`);
86590
+ const [failure, ...failureRest] = sanitizeLines(failureSummary(task) ?? "").filter((line2) => line2.trim());
86392
86591
  if (failure) {
86592
+ const label3 = " failure ";
86393
86593
  lines.push("");
86394
- lines.push(` ${import_picocolors65.default.red("failure")} ${failure}`);
86594
+ lines.push(` ${import_picocolors66.default.red("failure")} ${failure}`);
86595
+ for (const line2 of failureRest)
86596
+ lines.push(`${" ".repeat(label3.length)}${line2}`);
86395
86597
  }
86396
86598
  const metadata = Object.entries(task.metadata ?? {});
86397
86599
  if (metadata.length > 0) {
86398
86600
  lines.push("");
86399
- lines.push(` ${import_picocolors65.default.bold(import_picocolors65.default.dim("METADATA"))}`);
86601
+ lines.push(` ${import_picocolors66.default.bold(import_picocolors66.default.dim("METADATA"))}`);
86400
86602
  for (const [key2, value2] of metadata) {
86401
- lines.push(` ${import_picocolors65.default.dim(key2)} ${value2}`);
86603
+ lines.push(` ${import_picocolors66.default.dim(sanitizeLabel(key2))} ${sanitizeLabel(String(value2))}`);
86402
86604
  }
86403
86605
  }
86404
86606
  lines.push("");
86405
- lines.push(` ${import_picocolors65.default.bold(import_picocolors65.default.dim("EVAL VERDICTS"))}`);
86607
+ lines.push(` ${import_picocolors66.default.bold(import_picocolors66.default.dim("EVAL VERDICTS"))}`);
86406
86608
  if (evals.unavailable) {
86407
- lines.push(` ${import_picocolors65.default.dim(evals.unavailable)}`);
86609
+ lines.push(` ${import_picocolors66.default.dim(sanitizeLabel(evals.unavailable))}`);
86408
86610
  } else if (evals.runs.length === 0) {
86409
- lines.push(` ${import_picocolors65.default.dim("none")}`);
86611
+ lines.push(` ${import_picocolors66.default.dim("none")}`);
86410
86612
  } else {
86411
86613
  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);
86413
- lines.push(` ${verdict} ${run.eval_slug ?? run.eval_id}`);
86414
- if (run.reasoning)
86415
- lines.push(` ${import_picocolors65.default.dim(run.reasoning)}`);
86614
+ const verdict = run.passed === true ? import_picocolors66.default.green("pass") : run.passed === false ? import_picocolors66.default.red("fail") : import_picocolors66.default.dim(sanitizeLabel(run.status));
86615
+ lines.push(` ${verdict} ${sanitizeLabel(run.eval_slug ?? run.eval_id)}`);
86616
+ for (const line2 of sanitizeLines(run.reasoning ?? "")) {
86617
+ if (line2.trim())
86618
+ lines.push(` ${import_picocolors66.default.dim(line2)}`);
86619
+ }
86416
86620
  }
86417
86621
  }
86418
86622
  lines.push("");
86419
- lines.push(` ${import_picocolors65.default.dim("read its transcript with")} ${import_picocolors65.default.cyan(`brainbase task logs ${task.id}`)}`, "");
86623
+ lines.push(` ${import_picocolors66.default.dim("read its transcript with")} ${import_picocolors66.default.cyan(`brainbase task logs ${id}`)}`, "");
86420
86624
  return lines.join(`
86421
86625
  `);
86422
86626
  }
@@ -86433,7 +86637,7 @@ function failureSummary(task) {
86433
86637
  }
86434
86638
 
86435
86639
  // src/cli/task-logs.ts
86436
- var import_picocolors66 = __toESM(require_picocolors(), 1);
86640
+ var import_picocolors67 = __toESM(require_picocolors(), 1);
86437
86641
 
86438
86642
  // src/core/task-events.ts
86439
86643
  var DEFAULT_RETRY_BUDGET_MS = 60000;
@@ -86716,14 +86920,14 @@ async function runTaskLogs(taskId, options = {}, dependencies = {}) {
86716
86920
  console.log(renderEvent(event, messages));
86717
86921
  }
86718
86922
  if (ordered.length === 0) {
86719
- console.log(import_picocolors66.default.dim("No events for this task yet."));
86923
+ console.log(import_picocolors67.default.dim("No events for this task yet."));
86720
86924
  }
86721
86925
  }
86722
86926
  async function followTranscript(taskId, options, dependencies) {
86723
86927
  const backfill = options.limit === undefined ? "" : `?backfill=${options.limit}`;
86724
86928
  const { url: url2, resolveBearer } = await masStreamTarget(`/tasks/${encodeURIComponent(taskId)}/events/stream${backfill}`);
86725
86929
  if (!options.json) {
86726
- console.log(import_picocolors66.default.dim(`Following task ${taskId} — Ctrl-C to stop.`));
86930
+ console.log(import_picocolors67.default.dim(`Following task ${taskId} — Ctrl-C to stop.`));
86727
86931
  }
86728
86932
  const setExitCode = dependencies.setExitCode ?? ((code) => process.exitCode = code);
86729
86933
  const controller = new AbortController;
@@ -86765,13 +86969,13 @@ async function followTranscript(taskId, options, dependencies) {
86765
86969
  } else if (options.json) {
86766
86970
  console.log(JSON.stringify(frame));
86767
86971
  } else {
86768
- console.log(import_picocolors66.default.dim(frame.type));
86972
+ console.log(import_picocolors67.default.dim(frame.type));
86769
86973
  }
86770
86974
  },
86771
86975
  onOpen: () => settle.noteFrame(),
86772
86976
  onReconnect: (reason) => {
86773
86977
  settle.noteDisconnected();
86774
- console.error(import_picocolors66.default.dim(`reconnecting — ${reason}`));
86978
+ console.error(import_picocolors67.default.dim(`reconnecting — ${reason}`));
86775
86979
  }
86776
86980
  });
86777
86981
  if (interruptCode !== undefined)
@@ -87104,137 +87308,6 @@ Omit --generation only for a deliberately new Stop.`;
87104
87308
  `));
87105
87309
  }
87106
87310
 
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
87311
  // src/cli/task.ts
87239
87312
  var VALUE_FLAGS = [
87240
87313
  ["--agent", "agentId"],
@@ -87246,9 +87319,11 @@ var VALUE_FLAGS = [
87246
87319
  ["--timeout", "timeout"]
87247
87320
  ];
87248
87321
  var BOOLEAN_FLAGS = ["--json", "--wait"];
87322
+ var METADATA_FLAG = "--metadata";
87249
87323
  var TASK_OPTION_NAMES = new Set([
87250
87324
  ...VALUE_FLAGS.map(([flag]) => flag),
87251
- ...BOOLEAN_FLAGS
87325
+ ...BOOLEAN_FLAGS,
87326
+ METADATA_FLAG
87252
87327
  ]);
87253
87328
  var HELP_FLAGS = new Set(["--help", "-h"]);
87254
87329
  var LIST_LIMIT_MAX = 200;
@@ -87280,6 +87355,25 @@ function parseCreateArgs(args) {
87280
87355
  options.wait = true;
87281
87356
  continue;
87282
87357
  }
87358
+ if (arg === METADATA_FLAG || arg.startsWith(`${METADATA_FLAG}=`)) {
87359
+ let value2;
87360
+ if (arg !== METADATA_FLAG) {
87361
+ value2 = arg.slice(METADATA_FLAG.length + 1);
87362
+ } else {
87363
+ value2 = args[index + 1];
87364
+ if (value2 === "--") {
87365
+ value2 = args[index + 2];
87366
+ index += 1;
87367
+ } else if (value2 !== undefined && TASK_OPTION_NAMES.has(value2)) {
87368
+ throw flagLikeValueError(METADATA_FLAG, value2);
87369
+ }
87370
+ if (value2 === undefined)
87371
+ throw new Error("--metadata expects key=value.");
87372
+ index += 1;
87373
+ }
87374
+ (options.metadata ??= []).push(value2);
87375
+ continue;
87376
+ }
87283
87377
  let matched = false;
87284
87378
  for (const [flag, option] of VALUE_FLAGS) {
87285
87379
  if (arg === flag) {
@@ -87412,16 +87506,18 @@ async function dispatchTask(cwd2, sub, args, setJson) {
87412
87506
  const parsed = parse6([
87413
87507
  { key: "agent", names: ["--agent"], kind: "value" },
87414
87508
  { key: "interface", names: ["--interface"], kind: "value" },
87509
+ { key: "metadata", names: ["--metadata"], kind: "value", multiple: true },
87415
87510
  { key: "limit", names: ["--limit"], kind: "value" },
87416
87511
  { key: "json", names: ["--json"], kind: "boolean" }
87417
87512
  ]);
87418
87513
  if (parsed.positionals.length > 0) {
87419
- throw new Error("Usage: brainbase task list [--agent <id>] [--interface <id>] [--limit <n>] [--json]");
87514
+ throw new Error("Usage: brainbase task list [--agent <id>] [--interface <id>] [--metadata key=value]... [--limit <n>] [--json]");
87420
87515
  }
87421
87516
  const interfaceId = optionalValue(stringOption(parsed, "interface"), "--interface");
87422
87517
  await runTaskList({
87423
87518
  agentId: stringOption(parsed, "agent"),
87424
87519
  interfaceId,
87520
+ metadata: stringOptions(parsed, "metadata"),
87425
87521
  limit: positiveInteger(stringOption(parsed, "limit"), "--limit", LIST_LIMIT_MAX),
87426
87522
  json: booleanOption(parsed, "json")
87427
87523
  });
@@ -87473,6 +87569,7 @@ async function dispatchTask(cwd2, sub, args, setJson) {
87473
87569
  { key: "unsetMetadata", names: ["--unset-metadata"], kind: "value", multiple: true },
87474
87570
  { key: "clearMetadata", names: ["--clear-metadata"], kind: "boolean" },
87475
87571
  { key: "model", names: ["--model"], kind: "value" },
87572
+ { key: "clearModel", names: ["--clear-model"], kind: "boolean" },
87476
87573
  { key: "json", names: ["--json"], kind: "boolean" },
87477
87574
  { key: "help", names: ["--help", "-h"], kind: "boolean" }
87478
87575
  ]);
@@ -87488,6 +87585,7 @@ async function dispatchTask(cwd2, sub, args, setJson) {
87488
87585
  unsetMetadata: stringOptions(parsed, "unsetMetadata"),
87489
87586
  clearMetadata: booleanOption(parsed, "clearMetadata"),
87490
87587
  model: stringOption(parsed, "model"),
87588
+ clearModel: booleanOption(parsed, "clearModel"),
87491
87589
  json: booleanOption(parsed, "json")
87492
87590
  });
87493
87591
  return;
@@ -87563,7 +87661,7 @@ function printHelp5() {
87563
87661
  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
87662
  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
87663
  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")}`);
87664
+ 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
87665
  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
87666
  out.push("");
87569
87667
  out.push(` ${import_picocolors68.default.bold("create flags")}`);
@@ -87573,12 +87671,14 @@ function printHelp5() {
87573
87671
  out.push(` ${import_picocolors68.default.dim("--title <text>")} optional task title`);
87574
87672
  out.push(` ${import_picocolors68.default.dim("--model <id>")} optional model override`);
87575
87673
  out.push(` ${import_picocolors68.default.dim("--version <v>")} run a saved version of the agent: 12, v12 or a version id (\`agent versions list\`)`);
87674
+ out.push(` ${import_picocolors68.default.dim("--metadata key=value")} label the task (repeatable)`);
87576
87675
  out.push(` ${import_picocolors68.default.dim("--wait")} block until the task finishes; exit 1 unless it succeeded`);
87577
87676
  out.push(` ${import_picocolors68.default.dim("--timeout <secs>")} give up waiting after <secs> and exit 1 (needs --wait)`);
87578
87677
  out.push("");
87579
87678
  out.push(` ${import_picocolors68.default.bold("list flags")}`);
87580
87679
  out.push(` ${import_picocolors68.default.dim("--agent <id>")} only this agent's tasks`);
87581
87680
  out.push(` ${import_picocolors68.default.dim("--interface <id>")} only this interface's build conversations (hidden from a plain list)`);
87681
+ out.push(` ${import_picocolors68.default.dim("--metadata key=value")} only tasks carrying this label (repeatable; all must match)`);
87582
87682
  out.push(` ${import_picocolors68.default.dim("--limit <n>")} cap the page (1-200, server default 50)`);
87583
87683
  out.push("");
87584
87684
  out.push(` ${import_picocolors68.default.bold("logs flags")}`);
@@ -87590,9 +87690,10 @@ function printHelp5() {
87590
87690
  out.push(` ${import_picocolors68.default.dim("--clear-title")} remove the title; automatic titling may name the task again on a later turn`);
87591
87691
  out.push(` ${import_picocolors68.default.dim("--metadata key=value")} set one label, keeping the others (repeatable)`);
87592
87692
  out.push(` ${import_picocolors68.default.dim("--unset-metadata <key>")} remove one label (repeatable)`);
87693
+ out.push(` ${import_picocolors68.default.dim(" the server applies label edits in one step, so concurrent edits from other clients are kept")}`);
87593
87694
  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
87695
  out.push(` ${import_picocolors68.default.dim("--model <id>")} switch the model the task's next turn runs on (the running turn keeps its model)`);
87696
+ out.push(` ${import_picocolors68.default.dim("--clear-model")} go back to the agent's default model from the next turn`);
87596
87697
  out.push("");
87597
87698
  out.push(` ${import_picocolors68.default.bold("send flags")}`);
87598
87699
  out.push(` ${import_picocolors68.default.dim("--input-id <uuid>")} recover the same request using its original ID and identical message`);
@@ -97011,7 +97112,32 @@ async function runBenchmark(sub, args, write3 = (value2) => process.stdout.write
97011
97112
  }
97012
97113
  }
97013
97114
 
97115
+ // src/ui/console-streams.ts
97116
+ import { format as format3 } from "node:util";
97117
+ function routeConsoleThroughStreams() {
97118
+ if (typeof globalThis.Bun === "undefined")
97119
+ return;
97120
+ const writer = (stream) => (...args) => {
97121
+ if (!stream.writable)
97122
+ return;
97123
+ stream.write(`${format3(...args)}
97124
+ `);
97125
+ };
97126
+ console.log = writer(process.stdout);
97127
+ console.info = writer(process.stdout);
97128
+ console.debug = writer(process.stdout);
97129
+ console.warn = writer(process.stderr);
97130
+ console.error = writer(process.stderr);
97131
+ const ignoreClosedReader = (error2) => {
97132
+ if (error2.code !== "EPIPE")
97133
+ throw error2;
97134
+ };
97135
+ process.stdout.on("error", ignoreClosedReader);
97136
+ process.stderr.on("error", ignoreClosedReader);
97137
+ }
97138
+
97014
97139
  // src/index.ts
97140
+ routeConsoleThroughStreams();
97015
97141
  var PROTECTED = new Set([
97016
97142
  "template",
97017
97143
  "skill",
@@ -97096,12 +97222,12 @@ function help() {
97096
97222
  out.push(divider("TASKS"));
97097
97223
  out.push("");
97098
97224
  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)")}`);
97225
+ 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
97226
  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
97227
  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
97228
  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
97229
  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")}`);
97230
+ 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
97231
  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
97232
  out.push("");
97107
97233
  out.push(divider("INTERFACES"));
@@ -97197,10 +97323,11 @@ function help() {
97197
97323
  out.push(` ${import_picocolors80.default.dim("--message <text>")} for task create: required first user message`);
97198
97324
  out.push(` ${import_picocolors80.default.dim("--title <text>")} for task create: optional task title; for task update: the new title`);
97199
97325
  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)`);
97326
+ 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
97327
  out.push(` ${import_picocolors80.default.dim("--unset-metadata <k>")} for task update: remove one label (repeatable)`);
97202
97328
  out.push(` ${import_picocolors80.default.dim("--clear-metadata")} for task update: remove every label; with --metadata, keep only those given`);
97203
97329
  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)`);
97330
+ out.push(` ${import_picocolors80.default.dim("--clear-model")} for task update: go back to the agent's default model from the next turn`);
97204
97331
  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
97332
  out.push(` ${import_picocolors80.default.dim("--wait")} for task create: block until the task finishes; exit 1 unless it succeeded`);
97206
97333
  out.push(` ${import_picocolors80.default.dim("--timeout <secs>")} for task create --wait: give up after <secs> and exit 1`);
@@ -97695,8 +97822,10 @@ ${didYouMean(cmd, COMMANDS3, "brainbase")}`);
97695
97822
  process14.exit(1);
97696
97823
  }
97697
97824
  } catch (err) {
97825
+ const message2 = sanitizeLines(err.message).join(`
97826
+ `);
97698
97827
  console.error(import_picocolors80.default.red(`
97699
- ${err.message}`));
97828
+ ${message2}`));
97700
97829
  if (err instanceof ApiError && err.status === 401 && !err.message.includes("brainbase login")) {
97701
97830
  console.error(` Run ${import_picocolors80.default.cyan("brainbase login")} to connect this device.`);
97702
97831
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainbase-labs/cli",
3
- "version": "0.49.0",
3
+ "version": "0.51.0",
4
4
  "description": "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
5
5
  "type": "module",
6
6
  "bin": {