@workser/cli 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4485,6 +4485,76 @@ same rule.
4485
4485
  - **Cloud and local are different environments.** \`env set\` configures the
4486
4486
  cloud; the files in the app folder configure this computer. Don't hand-edit
4487
4487
  one to change the other.
4488
+ `
4489
+ },
4490
+ {
4491
+ topic: "goals",
4492
+ title: "Business goals & phases",
4493
+ summary: "The level above a task \u2014 a business requirement too big for one sitting, and the ordered phases that deliver it.",
4494
+ commands: ["goal"],
4495
+ source: "skills/workser/reference/goals.md",
4496
+ body: `# Business goals & phases
4497
+
4498
+ A **goal** is a business requirement \u2014 "customers can buy from my site" \u2014 that
4499
+ does not fit in one task. It carries an ordered list of **phase names**; a task
4500
+ joins one by carrying that name (\`workser task create --goal <id> --phase <name>\`).
4501
+ A phase has no existence apart from the tasks inside it, so it cannot drift from
4502
+ the work it names.
4503
+
4504
+ \`\`\`
4505
+ workser goal list
4506
+ workser goal show <id>
4507
+ workser goal create <title> --phase <name...> [--outcome <text>]
4508
+ [--criteria <json>]
4509
+ workser goal update <id> [--title <text>] [--outcome <text>]
4510
+ [--phase <name...>] [--status <value>]
4511
+ workser goal check <id> <criterionId> --phase <name> (--pass|--fail|--reset) [--note <text>]
4512
+ \`\`\`
4513
+
4514
+ Status is one of: proposed, agreed, working, delivered, abandoned.
4515
+
4516
+ ## A goal has to be argued for
4517
+
4518
+ Most requests are one task and should stay one task. Turning a two-hour job into
4519
+ four milestones buries the owner in ceremony before anything is built \u2014 use
4520
+ \`workser task create\` unless you can genuinely name two-plus slices that each
4521
+ deliver something the owner would notice.
4522
+
4523
+ \`\`\`
4524
+ workser goal create "Launch checkout" \\
4525
+ --phase "Cart" --phase "Payment" --phase "Receipts" \\
4526
+ --outcome "A customer can buy something and get a receipt"
4527
+ \`\`\`
4528
+
4529
+ \`--phase\` needs 2\u20136 names. Propose the shape only \u2014 nothing is created until the
4530
+ owner agrees it; planning phase four now is waste, since it will change once
4531
+ phase one is real.
4532
+
4533
+ ## Acceptance criteria are agreed with the shape
4534
+
4535
+ \`--criteria\` maps each phase name to the owner's own sentences about what "done"
4536
+ means for it, e.g. \`'{"Payment":["A customer can pay by card and gets a receipt"]}'\`.
4537
+ Written afterwards they only describe what got built; written with the shape they
4538
+ can still change the plan.
4539
+
4540
+ Record whether one is met with \`workser goal check\`:
4541
+
4542
+ \`\`\`
4543
+ workser goal check g_123 c1 --phase Payment --pass \\
4544
+ --note "Tested a card payment end to end; receipt emailed."
4545
+ \`\`\`
4546
+
4547
+ \`--note\` is required on \`--pass\` \u2014 a tick the owner cannot verify is worse than no
4548
+ tick.
4549
+
4550
+ ## Joining tasks to a goal
4551
+
4552
+ \`\`\`
4553
+ workser task create "Build the payment form" --goal g_123 --phase Payment
4554
+ \`\`\`
4555
+
4556
+ A goal's apps and progress are derived from the tasks that join it, not declared
4557
+ up front \u2014 most of the apps a goal will touch don't exist when it's proposed.
4488
4558
  `
4489
4559
  },
4490
4560
  {
@@ -4685,28 +4755,27 @@ real scope, not just a repeat of the task.
4685
4755
  },
4686
4756
  {
4687
4757
  topic: "sdlc-entities",
4688
- title: "Board cards, decisions and requirements",
4689
- summary: "Read what this project already tracks and decided, keep the Board honest as you work, and record what a future maintainer will need.",
4758
+ title: "Decisions and requirements",
4759
+ summary: "Read what this project already decided, and record what a future maintainer will need. Phased work itself is subtasks, not board cards \u2014 see `workser help tasks`.",
4690
4760
  commands: ["board", "decision", "requirement"],
4691
4761
  source: "skills/workser/reference/sdlc-entities.md",
4692
- body: `# Board cards, decisions and requirements
4762
+ body: `# Decisions and requirements
4693
4763
 
4694
4764
  These are the project's memory across sessions. They write to the **same tables**
4695
- the Orbit desktop's Board and Project Memory panels use, so anything here appears
4696
- there too \u2014 and, inside an Orbit-spawned run, as an inline card in the
4697
- conversation. Documents have their own guide: \`workser help docs\`.
4765
+ the Orbit desktop's Project Memory panel uses, so anything here appears there too
4766
+ \u2014 and, inside an Orbit-spawned run, as an inline card in the conversation.
4767
+ Documents have their own guide: \`workser help docs\`. Phased work is tracked as
4768
+ subtasks, not here \u2014 see \`workser help tasks\`.
4769
+
4770
+ > **The Board (\`workser board ...\`) is deprecated for agent use.** It used to be
4771
+ > where a multi-phase plan went, one card per phase \u2014 and the phase was ALSO a
4772
+ > subtask the planning turn had just filed for the same piece of work. That gave
4773
+ > a task two competing plans, one of which this task's own page never reads and
4774
+ > nothing kept in sync with the other. Phases are \`project_tasks\` subtasks now,
4775
+ > full stop: \`workser task subtask add\`. Do not run \`workser board create\` for
4776
+ > planned work \u2014 see \`workser help tasks\`.
4698
4777
 
4699
4778
  \`\`\`
4700
- workser board list [--status <value>] [--label <value>] [--limit <n>]
4701
- workser board show <id>
4702
- workser board create <title> [--description <text>] [--status <value>]
4703
- [--priority <value>] [--label <value>]
4704
- [--owner <name>] [--milestone <id>]
4705
- workser board update <id> [--title|--description|--status|--priority
4706
- |--label|--owner|--milestone ...]
4707
- workser board move <id> <backlog|in-progress|in-review|done>
4708
- workser board close <id>
4709
-
4710
4779
  workser decision list [--limit <n>]
4711
4780
  workser decision show <id>
4712
4781
  workser decision create <title> --context <text> --decision <text>
@@ -4724,7 +4793,6 @@ workser requirement update <id> [--title <text>] [--body <text>] [--status <text
4724
4793
  Before starting anything beyond a trivial edit:
4725
4794
 
4726
4795
  \`\`\`
4727
- workser board list --json # what's already tracked (don't re-file it)
4728
4796
  workser decision list --json # what was already decided (don't reverse it)
4729
4797
  \`\`\`
4730
4798
 
@@ -4734,19 +4802,18 @@ purpose \u2014 \`workser decision show <id>\` gives you the context and conseque
4734
4802
  not just the title. Reach for \`workser doc list\` / \`workser requirement list\`
4735
4803
  the same way when the task touches documented behaviour.
4736
4804
 
4737
- ## Work with phases \u2192 cards + a plan doc, before you build
4805
+ ## Work with phases \u2192 subtasks + a plan doc, before you build
4738
4806
 
4739
4807
  The moment you split a task into more than one phase, file it \u2014 not afterwards,
4740
4808
  and not only in your reply, which is gone once the conversation scrolls.
4741
4809
 
4742
4810
  \`\`\`bash
4743
- # one card per phase; only the one you're doing goes to in-progress
4744
- workser board create "Phase 1 \u2014 schema + migration" \\
4745
- --description "Add orders/line_items tables and the migration." \\
4746
- --status in-progress --json
4747
- workser board create "Phase 2 \u2014 checkout API" --description "\u2026" --json
4811
+ # the phases themselves \u2014 this task's own subtask list, not the Board
4812
+ workser task subtask add "Phase 1 \u2014 schema + migration" --role api \\
4813
+ --note "Add orders/line_items tables and the migration."
4814
+ workser task subtask add "Phase 2 \u2014 checkout API" --role api --note "\u2026"
4748
4815
 
4749
- # the plan itself, ONE doc, deliberately NOT linked to a card
4816
+ # the plan's narrative, ONE doc, deliberately NOT linked to a subtask
4750
4817
  workser doc create "Checkout \u2014 implementation plan" --markdown "$(cat plan.md)" --json
4751
4818
 
4752
4819
  # the approach, if the plan settled something with real alternatives
@@ -4757,32 +4824,9 @@ workser decision create "Carts live server-side" --context "\u2026" --decision "
4757
4824
  its card and is *hidden* from the Docs panel; a plan spanning three phases
4758
4825
  belongs to the project, not to phase 1.
4759
4826
 
4760
- The bar: if the user closed this conversation now, the Board should still show
4761
- what's left and the doc should still explain the plan to whoever continues it.
4762
-
4763
- ## Keep the Board honest while you work
4764
-
4765
- A Board still reading \`backlog\` after the feature shipped tells the user the
4766
- opposite of the truth. Moving the card is part of finishing the work:
4767
-
4768
- \`\`\`
4769
- workser board move <id> in-progress # you picked it up
4770
- workser board move <id> in-review # ready for the user to look at
4771
- workser board close <id> # done and verified
4772
- \`\`\`
4773
-
4774
- \`--status\` is one of \`backlog | in-progress | in-review | done\` (default
4775
- \`backlog\`). \`--priority\` is one of \`low | normal | high | urgent\` (default
4776
- \`normal\`). \`--label\` repeats for more than one label:
4777
-
4778
- \`\`\`
4779
- workser board create "Fix the login bug" --status in-progress --priority high \\
4780
- --label bug --label auth
4781
- \`\`\`
4782
-
4783
- \`board update\` replaces the labels you pass rather than merging them, and
4784
- touches only the fields you name. There is no \`board delete\`: \`done\` is the
4785
- terminal state, and removing a card the user filed is theirs to do in Orbit.
4827
+ The bar: if the user closed this conversation now, the subtask list should still
4828
+ show what's left and the doc should still explain the plan to whoever continues
4829
+ it.
4786
4830
 
4787
4831
  ## Decisions are append-only
4788
4832
 
@@ -4894,6 +4938,9 @@ below defaults to it and you rarely pass an id at all.
4894
4938
  \`\`\`
4895
4939
  workser task list [--status <value>] [--label <value>] [--limit <n>]
4896
4940
  workser task show [id] # the task you are in, with its steps
4941
+ workser task create <title> [--note <text>] [--kind <value>]
4942
+ [--label <value...>] [--app <id...>]
4943
+ [--infra <ref...>]
4897
4944
 
4898
4945
  workser task subtask add <title> [--role <value>] [--kind <value>]
4899
4946
  [--note <text>] [--app <id...>]
@@ -4917,6 +4964,16 @@ Team's own table. Filing your plan on the Board puts it somewhere the owner's
4917
4964
  task page never reads: they see "created work item" and an empty plan. Use
4918
4965
  \`task subtask add\`.
4919
4966
 
4967
+ ## Opening work from a project channel
4968
+
4969
+ When a project-channel conversation produces actionable work, the PM may record
4970
+ it with \`workser task create\`. Orbit supplies the channel and source-message IDs;
4971
+ the command records them and posts the new task card as a Project Manager message
4972
+ automatically. Do not invent or ask for those IDs.
4973
+
4974
+ Opening a task does **not** approve it or start implementation. The task remains
4975
+ awaiting the owner. Never approve or dispatch a task you opened yourself.
4976
+
4920
4977
  ## Planning a task
4921
4978
 
4922
4979
  Read the project first, then propose. One \`subtask add\` per step:
@@ -5161,11 +5218,11 @@ function registerHelp(program3) {
5161
5218
  var import_picocolors3 = __toESM(require_picocolors(), 1);
5162
5219
 
5163
5220
  // src/context.ts
5164
- import { resolve } from "path";
5221
+ import { resolve as resolve2 } from "path";
5165
5222
 
5166
5223
  // src/config.ts
5167
5224
  import { homedir } from "os";
5168
- import { join } from "path";
5225
+ import { dirname, join, resolve } from "path";
5169
5226
  import {
5170
5227
  chmodSync,
5171
5228
  existsSync,
@@ -5173,6 +5230,8 @@ import {
5173
5230
  readFileSync,
5174
5231
  writeFileSync
5175
5232
  } from "fs";
5233
+ var PROJECT_MARKER = ".workser-project";
5234
+ var APP_MARKER = ".workser-app";
5176
5235
  var GLOBAL_DIR = join(homedir(), ".workser");
5177
5236
  var SESSION_FILE = join(GLOBAL_DIR, "session.json");
5178
5237
  function readSession() {
@@ -5203,10 +5262,82 @@ function projectLinkPath(cwd) {
5203
5262
  return join(cwd, ".workser", "project.json");
5204
5263
  }
5205
5264
  function readProjectLink(cwd) {
5265
+ const found = readFolderIdentity(cwd);
5266
+ if (!found) return null;
5267
+ return {
5268
+ projectId: found.projectId,
5269
+ name: found.projectName,
5270
+ orgId: found.orgId,
5271
+ root: found.projectRoot
5272
+ };
5273
+ }
5274
+ function readFolderIdentity(cwd) {
5275
+ let app = null;
5276
+ for (const dir of ancestors(cwd)) {
5277
+ if (!app) {
5278
+ const marker2 = readJson(join(dir, APP_MARKER));
5279
+ if (marker2 && typeof marker2.appId === "string") {
5280
+ app = { marker: marker2, root: dir };
5281
+ }
5282
+ }
5283
+ const link = readJson(projectLinkPath(dir));
5284
+ if (link && typeof link.projectId === "string") {
5285
+ return {
5286
+ projectRoot: dir,
5287
+ projectId: link.projectId,
5288
+ projectName: link.name,
5289
+ orgId: link.orgId,
5290
+ ...appFields(app)
5291
+ };
5292
+ }
5293
+ const marker = readJson(join(dir, PROJECT_MARKER));
5294
+ if (marker && typeof marker.projectId === "string") {
5295
+ return {
5296
+ projectRoot: dir,
5297
+ projectId: marker.projectId,
5298
+ projectName: marker.projectName ?? void 0,
5299
+ orgId: marker.orgId ?? void 0,
5300
+ ...appFields(app)
5301
+ };
5302
+ }
5303
+ }
5304
+ if (app && typeof app.marker.projectId === "string") {
5305
+ return {
5306
+ projectRoot: app.root,
5307
+ projectId: app.marker.projectId,
5308
+ projectName: app.marker.projectName ?? void 0,
5309
+ orgId: app.marker.orgId ?? void 0,
5310
+ ...appFields(app)
5311
+ };
5312
+ }
5313
+ return null;
5314
+ }
5315
+ function appFields(app) {
5316
+ if (!app) return {};
5317
+ return {
5318
+ appId: app.marker.appId,
5319
+ appName: app.marker.appName ?? void 0,
5320
+ appRoot: app.root
5321
+ };
5322
+ }
5323
+ function ancestors(cwd) {
5324
+ const out = [];
5325
+ const home = homedir();
5326
+ let dir = resolve(cwd);
5327
+ for (let i = 0; i < 64; i++) {
5328
+ out.push(dir);
5329
+ if (dir === home) break;
5330
+ const parent = dirname(dir);
5331
+ if (parent === dir) break;
5332
+ dir = parent;
5333
+ }
5334
+ return out;
5335
+ }
5336
+ function readJson(file) {
5206
5337
  try {
5207
- const p = projectLinkPath(cwd);
5208
- if (!existsSync(p)) return null;
5209
- return JSON.parse(readFileSync(p, "utf8"));
5338
+ if (!existsSync(file)) return null;
5339
+ const parsed = JSON.parse(readFileSync(file, "utf8"));
5340
+ return parsed && typeof parsed === "object" ? parsed : null;
5210
5341
  } catch {
5211
5342
  return null;
5212
5343
  }
@@ -5245,7 +5376,7 @@ function cloudBaseUrl() {
5245
5376
  // src/context.ts
5246
5377
  function buildContext(opts) {
5247
5378
  const session = readSession();
5248
- const cwd = opts.cwd ? resolve(opts.cwd) : process.cwd();
5379
+ const cwd = opts.cwd ? resolve2(opts.cwd) : process.cwd();
5249
5380
  const token = opts.token || process.env.WORKSER_TOKEN || session.token;
5250
5381
  const overridden = Boolean(
5251
5382
  opts.endpoint || opts.token || process.env.WORKSER_DAEMON_URL || process.env.WORKSER_TOKEN
@@ -5254,11 +5385,18 @@ function buildContext(opts) {
5254
5385
  const endpointRaw = socketPath ? "http://localhost" : opts.endpoint || process.env.WORKSER_DAEMON_URL || session.endpoint || cloudBaseUrl();
5255
5386
  const endpoint = endpointRaw.replace(/\/+$/, "");
5256
5387
  const mode2 = socketPath ? "daemon" : /^https?:\/\/(127\.0\.0\.1|localhost|\[::1\])(:|\/|$)/i.test(endpoint) ? "daemon" : "cloud";
5257
- const link = readProjectLink(cwd);
5258
- const projectId = opts.project || process.env.WORKSER_PROJECT_ID || link?.projectId || session.defaultProjectId;
5388
+ const folder = readFolderIdentity(cwd);
5389
+ const projectId = opts.project || process.env.WORKSER_PROJECT_ID || folder?.projectId || session.defaultProjectId;
5390
+ const inThisProject = folder?.projectId === projectId;
5259
5391
  const runId = process.env.WORKSER_RUN_ID || void 0;
5260
5392
  const conversationId = process.env.WORKSER_CONVERSATION_ID || void 0;
5261
5393
  const projectTaskId = process.env.WORKSER_PROJECT_TASK_ID || void 0;
5394
+ const parentTaskId = process.env.WORKSER_PARENT_TASK_ID || void 0;
5395
+ const projectChannelId = process.env.WORKSER_PROJECT_CHANNEL_ID || void 0;
5396
+ const projectChannelMessageId = process.env.WORKSER_PROJECT_CHANNEL_MESSAGE_ID || void 0;
5397
+ const agentRole = process.env.WORKSER_AGENT_ROLE || void 0;
5398
+ const agentType = process.env.WORKSER_AGENT_TYPE || void 0;
5399
+ const agentModel = process.env.WORKSER_AGENT_MODEL || void 0;
5262
5400
  return {
5263
5401
  endpoint,
5264
5402
  socketPath,
@@ -5266,9 +5404,18 @@ function buildContext(opts) {
5266
5404
  mode: mode2,
5267
5405
  cwd,
5268
5406
  projectId,
5407
+ projectRoot: inThisProject ? folder?.projectRoot : void 0,
5408
+ appId: inThisProject ? folder?.appId : void 0,
5409
+ appName: inThisProject ? folder?.appName : void 0,
5269
5410
  runId,
5270
5411
  conversationId,
5271
- projectTaskId
5412
+ projectTaskId,
5413
+ parentTaskId,
5414
+ projectChannelId,
5415
+ projectChannelMessageId,
5416
+ agentRole,
5417
+ agentType,
5418
+ agentModel
5272
5419
  };
5273
5420
  }
5274
5421
  function runTarget(ctx) {
@@ -5304,7 +5451,7 @@ This shell is talking to ${ctx.endpoint} instead of a local app.
5304
5451
  function requireProject(ctx) {
5305
5452
  if (!ctx.projectId) {
5306
5453
  throw new WorkserError(
5307
- "No project selected. Run `workser project use <id>` or pass --project <id>.",
5454
+ "No project selected.\n\nWorkser keeps each project in its own folder \u2014 `~/workser/<org>/<project>/`,\nwith that project's apps inside it. Running from anywhere in that tree is\nenough; this shell is not in one.\n\n \u2022 cd into the project's folder (Workser Orbit's Files tab shows where it is), or\n \u2022 pass --project <id> for a one-off.",
5308
5455
  { code: "no_project" }
5309
5456
  );
5310
5457
  }
@@ -5329,7 +5476,7 @@ function action(fn) {
5329
5476
  // src/client.ts
5330
5477
  import * as http from "http";
5331
5478
  function requestOverSocket(socketPath, pathWithQuery, init) {
5332
- return new Promise((resolve4, reject) => {
5479
+ return new Promise((resolve5, reject) => {
5333
5480
  const req = http.request(
5334
5481
  { socketPath, path: pathWithQuery, method: init.method, headers: init.headers },
5335
5482
  (res) => {
@@ -5338,7 +5485,7 @@ function requestOverSocket(socketPath, pathWithQuery, init) {
5338
5485
  res.on("data", (c) => text += c);
5339
5486
  res.on(
5340
5487
  "end",
5341
- () => resolve4({
5488
+ () => resolve5({
5342
5489
  ok: (res.statusCode ?? 0) >= 200 && (res.statusCode ?? 0) < 300,
5343
5490
  status: res.statusCode ?? 0,
5344
5491
  statusText: res.statusMessage ?? "",
@@ -5435,13 +5582,28 @@ function registerStatus(program3) {
5435
5582
  program3.command("status").description("Show connection, current workspace/project, and latest deploy").action(
5436
5583
  action(async ({ ctx }) => {
5437
5584
  const data = await api(ctx, "/v1/status", { query: { project: ctx.projectId } });
5438
- ok(data, () => {
5585
+ const merged = {
5586
+ ...data,
5587
+ folder: {
5588
+ cwd: ctx.cwd,
5589
+ projectRoot: ctx.projectRoot ?? null,
5590
+ appId: ctx.appId ?? null,
5591
+ appName: ctx.appName ?? null
5592
+ }
5593
+ };
5594
+ ok(merged, () => {
5439
5595
  line(import_picocolors3.default.bold("Workser") + import_picocolors3.default.dim(` (${ctx.mode} \xB7 ${ctx.endpoint})`));
5440
5596
  line(` user: ${data.user?.email ?? "\u2014"}`);
5441
5597
  line(` workspace: ${data.workspace?.name ?? "\u2014"}`);
5442
5598
  line(
5443
5599
  ` project: ${data.project?.name ?? "\u2014"}` + (data.project?.id ? import_picocolors3.default.dim(` (${data.project.id})`) : "")
5444
5600
  );
5601
+ if (ctx.projectRoot) line(` folder: ${import_picocolors3.default.dim(ctx.projectRoot)}`);
5602
+ if (ctx.appId) {
5603
+ line(
5604
+ ` app: ${ctx.appName ?? "\u2014"}` + import_picocolors3.default.dim(` (${ctx.appId})`)
5605
+ );
5606
+ }
5445
5607
  if (data.latestDeploy) {
5446
5608
  line(
5447
5609
  ` deploy: ${colorStatus(data.latestDeploy.status)}` + (data.latestDeploy.url ? ` ${import_picocolors3.default.cyan(data.latestDeploy.url)}` : "")
@@ -5778,7 +5940,7 @@ function registerAuth(program3) {
5778
5940
  // src/commands/storage.ts
5779
5941
  var import_picocolors7 = __toESM(require_picocolors(), 1);
5780
5942
  import { readFile, writeFile } from "fs/promises";
5781
- import { basename, dirname } from "path";
5943
+ import { basename, dirname as dirname2 } from "path";
5782
5944
  function registerStorage(program3) {
5783
5945
  const storage = program3.command("storage").description("Provision and work with the project's object storage (Cloudflare R2)");
5784
5946
  storage.command("create [name]").description("Provision a storage bucket for the project (idempotent)").action(
@@ -5824,7 +5986,7 @@ function registerStorage(program3) {
5824
5986
  const bytes = await readFile(localPath).catch(() => {
5825
5987
  throw new WorkserError(`Can't read local file: ${localPath}`, { code: "bad_input" });
5826
5988
  });
5827
- const dir = dirname(key);
5989
+ const dir = dirname2(key);
5828
5990
  const res = await api(ctx, `/v1/projects/${projectId}/storage/upload-base64`, {
5829
5991
  body: {
5830
5992
  filename: basename(key),
@@ -6980,7 +7142,10 @@ function registerAgent(program3) {
6980
7142
  ).option("--role <label>", "display label for this run (default: <agent>)").option(
6981
7143
  "--instructions <text>",
6982
7144
  "system prompt for this one run \u2014 this teammate's expertise/scope"
6983
- ).option("--model <model>", "model override for the backing CLI").option("--effort <level>", "reasoning effort, where the backing CLI supports it").action(
7145
+ ).option("--model <model>", "model override for the backing CLI").option("--effort <level>", "reasoning effort, where the backing CLI supports it").option(
7146
+ "--read-only",
7147
+ "inspection only \u2014 the teammate runs in the CLI's read-only sandbox and cannot change anything"
7148
+ ).action(
6984
7149
  action(async ({ ctx, args, opts }) => {
6985
7150
  const spawnAgent = args[0];
6986
7151
  const task = args[1].join(" ");
@@ -6991,7 +7156,10 @@ function registerAgent(program3) {
6991
7156
  role: opts.role,
6992
7157
  instructions: opts.instructions,
6993
7158
  model: opts.model,
6994
- effort: opts.effort
7159
+ effort: opts.effort,
7160
+ // Enforced by the backing CLI's own sandbox, not by asking the
7161
+ // spawned agent to behave — see the daemon's `/agents/run`.
7162
+ readOnly: opts.readOnly === true
6995
7163
  }
6996
7164
  });
6997
7165
  const exitCode = res?.exitCode ?? 0;
@@ -7517,7 +7685,7 @@ function businessPath(projectId, resource, subpath) {
7517
7685
  // src/commands/artifact.ts
7518
7686
  var import_picocolors24 = __toESM(require_picocolors(), 1);
7519
7687
  import { existsSync as existsSync2, statSync } from "fs";
7520
- import { resolve as resolve2, basename as basename3 } from "path";
7688
+ import { resolve as resolve3, basename as basename3 } from "path";
7521
7689
  var KINDS = [
7522
7690
  "file",
7523
7691
  "folder",
@@ -7566,7 +7734,7 @@ function registerArtifact(program3) {
7566
7734
  let absPath;
7567
7735
  let kind = opts.kind;
7568
7736
  if (rawPath) {
7569
- absPath = resolve2(ctx.cwd, rawPath);
7737
+ absPath = resolve3(ctx.cwd, rawPath);
7570
7738
  if (!existsSync2(absPath)) {
7571
7739
  throw new WorkserError(
7572
7740
  `Nothing at ${absPath}. Register the file after you've written it.`,
@@ -7617,6 +7785,30 @@ function registerArtifact(program3) {
7617
7785
  );
7618
7786
  })
7619
7787
  );
7788
+ artifact.command("list").description("What this task's steps have produced so far").option("--task <id>", "a specific task (default: the plan this run is in)").option("--step <id>", "one step's output alone, across every attempt").option("--mine", "only what THIS step has produced").action(
7789
+ action(async ({ ctx, opts }) => {
7790
+ const stepId = opts.step || (opts.mine ? ctx.projectTaskId : void 0);
7791
+ if (opts.mine && !stepId) {
7792
+ throw new WorkserError(
7793
+ "--mine needs a step: this run isn't inside one (WORKSER_PROJECT_TASK_ID is unset)."
7794
+ );
7795
+ }
7796
+ let path;
7797
+ if (stepId) {
7798
+ path = `/v1/project-subtasks/${encodeURIComponent(stepId)}/artifacts`;
7799
+ } else {
7800
+ const taskId = opts.task || ctx.parentTaskId || ctx.projectTaskId;
7801
+ if (!taskId) {
7802
+ throw new WorkserError(
7803
+ "No task. This run isn't inside one, so pass --task <id> or --step <id>."
7804
+ );
7805
+ }
7806
+ path = `/v1/project-tasks/${encodeURIComponent(taskId)}/artifacts?scope=all`;
7807
+ }
7808
+ const rows = await api(ctx, path) ?? [];
7809
+ ok(rows, () => printArtifacts(rows));
7810
+ })
7811
+ );
7620
7812
  artifact.command("run").description("Show the task/conversation this agent run is attached to").action(
7621
7813
  action(async ({ ctx }) => {
7622
7814
  const res = await api(ctx, `/v1/runs/${runTarget(ctx)}`);
@@ -7624,6 +7816,31 @@ function registerArtifact(program3) {
7624
7816
  })
7625
7817
  );
7626
7818
  }
7819
+ function printArtifacts(rows) {
7820
+ if (!rows.length) {
7821
+ line(import_picocolors24.default.dim("Nothing produced yet."));
7822
+ return;
7823
+ }
7824
+ const byStep = /* @__PURE__ */ new Map();
7825
+ for (const r of rows) {
7826
+ const list = byStep.get(r.subtask_id) ?? [];
7827
+ list.push(r);
7828
+ byStep.set(r.subtask_id, list);
7829
+ }
7830
+ for (const [stepId, items] of byStep) {
7831
+ line(import_picocolors24.default.bold(`step ${stepId}`));
7832
+ for (const a of items) {
7833
+ const flag = a.promoted_at ? import_picocolors24.default.green(" *") : " ";
7834
+ const where = a.local_path || a.cloud_url || "";
7835
+ line(
7836
+ `${flag} ${import_picocolors24.default.dim(`[${a.kind}]`)} ${a.title ?? "(untitled)"}` + (where ? import_picocolors24.default.dim(` ${where}`) : "")
7837
+ );
7838
+ if (a.description) line(import_picocolors24.default.dim(` ${a.description}`));
7839
+ }
7840
+ line("");
7841
+ }
7842
+ line(import_picocolors24.default.dim("* = handed over as a deliverable; the rest is working material."));
7843
+ }
7627
7844
  function printRun(run) {
7628
7845
  if (!run) return;
7629
7846
  line(` run ${import_picocolors24.default.bold(run.runId)}`);
@@ -7635,7 +7852,7 @@ function printRun(run) {
7635
7852
 
7636
7853
  // src/commands/image.ts
7637
7854
  import { writeFile as writeFile4, mkdir } from "fs/promises";
7638
- import { dirname as dirname2, resolve as resolve3 } from "path";
7855
+ import { dirname as dirname3, resolve as resolve4 } from "path";
7639
7856
  function registerImage(program3) {
7640
7857
  const image = program3.command("image").description("Generate images from a text prompt");
7641
7858
  image.command("generate <prompt>").alias("gen").description("Generate an image and return its public URL").option(
@@ -7690,7 +7907,7 @@ function registerImage(program3) {
7690
7907
  );
7691
7908
  }
7692
7909
  async function download(url, output) {
7693
- const target = resolve3(output);
7910
+ const target = resolve4(output);
7694
7911
  const res = await fetch(url);
7695
7912
  if (!res.ok) {
7696
7913
  throw new WorkserError(
@@ -7698,7 +7915,7 @@ async function download(url, output) {
7698
7915
  { code: "download_failed" }
7699
7916
  );
7700
7917
  }
7701
- await mkdir(dirname2(target), { recursive: true });
7918
+ await mkdir(dirname3(target), { recursive: true });
7702
7919
  await writeFile4(target, Buffer.from(await res.arrayBuffer()));
7703
7920
  return target;
7704
7921
  }
@@ -7711,7 +7928,22 @@ var TYPES = [
7711
7928
  "approval",
7712
7929
  "confirmation",
7713
7930
  "file_upload",
7714
- "information"
7931
+ "information",
7932
+ /**
7933
+ * "Add an app to this project, and do it for me."
7934
+ *
7935
+ * NOT an approval with a well-worded message. What the user is agreeing to
7936
+ * is real infrastructure — a repository, hosting, a folder on their machine,
7937
+ * sometimes a database — and they are entitled to see WHICH app before they
7938
+ * agree, which a sentence and a Yes button cannot show. Needs `--app-type`;
7939
+ * `--app-name` is what it will be called.
7940
+ *
7941
+ * You cannot create an app yourself, and that is deliberate: an agent must
7942
+ * not be able to provision things on somebody's account by deciding to. This
7943
+ * asks. Their click is what creates it, and the answer tells you the new
7944
+ * app's id so the rest of your plan can be scoped to it.
7945
+ */
7946
+ "create_app"
7715
7947
  ];
7716
7948
  function registerAsk(program3) {
7717
7949
  program3.command("ask <message>").description("Ask the user a question and wait for their answer").option(
@@ -7723,6 +7955,12 @@ function registerAsk(program3) {
7723
7955
  "a choice (repeat for each; implies --type choice)",
7724
7956
  collect,
7725
7957
  []
7958
+ ).option(
7959
+ "--app-type <type>",
7960
+ "create_app only: web | mobile | api | worker | cron | python"
7961
+ ).option(
7962
+ "--app-name <name>",
7963
+ "create_app only: what the app should be called"
7726
7964
  ).option("--priority <level>", "low | normal | high | urgent", "normal").option(
7727
7965
  "--timeout <seconds>",
7728
7966
  "how long to wait before giving up (default: 600, max: 3600)",
@@ -7749,6 +7987,12 @@ function registerAsk(program3) {
7749
7987
  { code: "bad_request" }
7750
7988
  );
7751
7989
  }
7990
+ if (type === "create_app" && !opts.appType) {
7991
+ throw new WorkserError(
7992
+ "Say which kind of app: `--app-type mobile`.",
7993
+ { code: "bad_request" }
7994
+ );
7995
+ }
7752
7996
  const timeoutSeconds = Number(opts.timeout);
7753
7997
  if (!Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0) {
7754
7998
  throw new WorkserError("--timeout must be a positive number.", {
@@ -7762,6 +8006,8 @@ function registerAsk(program3) {
7762
8006
  title: opts.title || deriveTitle(message),
7763
8007
  message,
7764
8008
  options: options.length ? options : void 0,
8009
+ appType: opts.appType,
8010
+ appName: opts.appName,
7765
8011
  priority: opts.priority,
7766
8012
  timeoutSeconds
7767
8013
  }
@@ -8051,18 +8297,98 @@ function registerTask(program3) {
8051
8297
  })
8052
8298
  );
8053
8299
  task.command("show [id]").description(
8054
- "Show one task with its subtasks. Defaults to the task this run is inside."
8300
+ "Show a task with its subtasks, what each one did, and what they produced. Defaults to the plan this run is part of."
8055
8301
  ).action(
8056
8302
  action(async ({ ctx, args }) => {
8057
- const id = resolveTaskId(ctx, args[0]);
8303
+ const id = args[0] || ctx.parentTaskId || resolveTaskId(ctx);
8058
8304
  const row = await api(ctx, `/v1/project-tasks/${encodeURIComponent(id)}`);
8059
- ok(row, () => printTask(row));
8305
+ const goalId = row.goal_id;
8306
+ const goal = goalId ? await api(
8307
+ ctx,
8308
+ `/v1/project-goals/${encodeURIComponent(goalId)}`
8309
+ ).catch(() => null) : null;
8310
+ ok({ ...row, goal }, () => printTask(row, goal));
8060
8311
  })
8061
8312
  );
8062
- const subtask = task.command("subtask").description("The steps a task is broken into");
8313
+ task.command("create <title>").description("Open a new root task for the project team").option("--note <text>", "context, constraints and expected outcome").option("--kind <value>", `what it produces (${KINDS2.join(" | ")})`).option("--label <value...>", "labels to put on the task").option("--app <id...>", "apps the task touches").option("--infra <ref...>", "shared setup it touches").option("--goal <id>", "the business goal this delivers part of").option(
8314
+ "--phase <name>",
8315
+ "which slice of that goal \u2014 must match one of its phase names"
8316
+ ).action(
8317
+ action(async ({ ctx, args, opts }) => {
8318
+ requireProject(ctx);
8319
+ if (opts.kind !== void 0) assertOneOf("--kind", opts.kind, KINDS2);
8320
+ const hasChannelOrigin = Boolean(
8321
+ ctx.projectChannelId && ctx.projectChannelMessageId
8322
+ );
8323
+ const row = await api(ctx, "/v1/project-tasks", {
8324
+ body: {
8325
+ title: args[0],
8326
+ summary: opts.note,
8327
+ category: opts.kind,
8328
+ labels: opts.label,
8329
+ // The level above this task, when it has one. Most tasks do not —
8330
+ // a goal has to be argued for, never assumed.
8331
+ goalId: opts.goal,
8332
+ phase: opts.phase,
8333
+ targets: [
8334
+ ...(opts.app ?? []).map((appId) => ({ kind: "app", appId })),
8335
+ ...(opts.infra ?? []).map((ref) => ({ kind: "infra", ref }))
8336
+ ],
8337
+ ...hasChannelOrigin ? {
8338
+ channelId: ctx.projectChannelId,
8339
+ createdFromMessageId: ctx.projectChannelMessageId,
8340
+ createdByKind: "agent"
8341
+ } : {}
8342
+ }
8343
+ });
8344
+ let channelMessage;
8345
+ let channelMessageError;
8346
+ if (hasChannelOrigin && ctx.projectChannelId && ctx.projectChannelMessageId) {
8347
+ try {
8348
+ channelMessage = await api(
8349
+ ctx,
8350
+ `/v1/project-channels/${encodeURIComponent(ctx.projectChannelId)}/messages`,
8351
+ {
8352
+ body: {
8353
+ content: "",
8354
+ agentRole: ctx.agentRole ?? "pm",
8355
+ agentType: ctx.agentType ?? "workser",
8356
+ agentModel: ctx.agentModel ?? "Agent default",
8357
+ attachments: [{ resourceType: "task", resourceId: row.id }]
8358
+ }
8359
+ }
8360
+ );
8361
+ } catch (error) {
8362
+ const failure = error instanceof WorkserError ? error : new WorkserError(String(error));
8363
+ channelMessageError = {
8364
+ code: failure.code,
8365
+ message: failure.message
8366
+ };
8367
+ }
8368
+ }
8369
+ const result = {
8370
+ ...row,
8371
+ ...channelMessage ? { channelMessage } : {},
8372
+ ...channelMessageError ? { channelMessageError } : {}
8373
+ };
8374
+ ok(result, () => {
8375
+ line(`${import_picocolors28.default.green("opened")} ${import_picocolors28.default.bold(row.title)} ${import_picocolors28.default.dim(row.key ?? row.id)}`);
8376
+ if (channelMessage) line(import_picocolors28.default.dim("posted to the channel as Project Manager"));
8377
+ if (channelMessageError) {
8378
+ warn(
8379
+ `Task opened, but its Project Manager card could not be posted: ${channelMessageError.message}`
8380
+ );
8381
+ }
8382
+ });
8383
+ })
8384
+ );
8385
+ const subtask = task.command("subtask").description("The subtasks a task is broken into");
8063
8386
  subtask.command("add <title>").description(
8064
- 'Add one step, e.g. `workser task subtask add "Build the upload screen" --role web`'
8065
- ).option("--task <id>", "the parent task (defaults to the task this run is inside)").option("--role <value>", `who does it (${ROLES.join(" | ")})`).option("--kind <value>", `what it produces (${KINDS2.join(" | ")})`).option("--note <text>", "one sentence on what this step does").option("--app <id...>", "app ids this step touches").option("--infra <ref...>", "shared setup it touches (database | storage | auth | hosting | jobs)").option("--scope <path...>", "files or folders THIS step owns").option("--depends-on <id...>", "steps that must finish first (key or id)").action(
8387
+ 'Add one subtask, e.g. `workser task subtask add "Build the upload screen" --role web`'
8388
+ ).option("--task <id>", "the parent task (defaults to the task this run is inside)").option("--role <value>", `who does it (${ROLES.join(" | ")})`).option("--kind <value>", `what it produces (${KINDS2.join(" | ")})`).option("--note <text>", "one sentence on what this subtask does").option(
8389
+ "--app <id...>",
8390
+ "the app this subtask is for \u2014 one id runs it inside that app's folder; leave it off and it runs at the project, seeing every app"
8391
+ ).option("--infra <ref...>", "shared setup it touches (database | storage | auth | hosting | jobs)").option("--scope <path...>", "files or folders THIS subtask owns").option("--depends-on <id...>", "subtasks that must finish first (key or id)").action(
8066
8392
  action(async ({ ctx, args, opts }) => {
8067
8393
  const parent = resolveTaskId(ctx, opts.task);
8068
8394
  if (opts.role !== void 0) assertOneOf("--role", opts.role, ROLES);
@@ -8089,21 +8415,21 @@ function registerTask(program3) {
8089
8415
  });
8090
8416
  })
8091
8417
  );
8092
- subtask.command("list [taskId]").description("The steps of a task, in order").action(
8418
+ subtask.command("list [taskId]").description("The subtasks of a task, in order").action(
8093
8419
  action(async ({ ctx, args }) => {
8094
8420
  const id = resolveTaskId(ctx, args[0]);
8095
8421
  const row = await api(ctx, `/v1/project-tasks/${encodeURIComponent(id)}`);
8096
8422
  const rows = row.subtasks ?? [];
8097
8423
  ok(rows, () => {
8098
8424
  if (!rows.length) {
8099
- line(import_picocolors28.default.dim("No steps yet."));
8425
+ line(import_picocolors28.default.dim("No subtasks yet."));
8100
8426
  return;
8101
8427
  }
8102
8428
  rows.forEach((r, i) => line(formatSubtask(r, i + 1)));
8103
8429
  });
8104
8430
  })
8105
8431
  );
8106
- subtask.command("update <id>").description("Change a step \u2014 its title, its role, what it touches").option("--title <text>").option("--note <text>").option("--role <value>", ROLES.join(" | ")).option("--kind <value>", KINDS2.join(" | ")).option("--scope <path...>", "replaces the step's scope entirely").action(
8432
+ subtask.command("update <id>").description("Change a subtask \u2014 its title, its role, what it touches").option("--title <text>").option("--note <text>").option("--role <value>", ROLES.join(" | ")).option("--kind <value>", KINDS2.join(" | ")).option("--scope <path...>", "replaces the subtask's scope entirely").action(
8107
8433
  action(async ({ ctx, args, opts }) => {
8108
8434
  if (opts.role !== void 0) assertOneOf("--role", opts.role, ROLES);
8109
8435
  if (opts.kind !== void 0) assertOneOf("--kind", opts.kind, KINDS2);
@@ -8124,7 +8450,7 @@ function registerTask(program3) {
8124
8450
  ok(row, () => line(`${import_picocolors28.default.green("updated")} ${import_picocolors28.default.bold(row.title)}`));
8125
8451
  })
8126
8452
  );
8127
- subtask.command("remove <id>").description("Drop a step from the plan (only before the work starts)").action(
8453
+ subtask.command("remove <id>").description("Remove a subtask (only before the work starts)").action(
8128
8454
  action(async ({ ctx, args }) => {
8129
8455
  await api(ctx, `/v1/project-tasks/${encodeURIComponent(args[0])}`, {
8130
8456
  method: "DELETE"
@@ -8143,6 +8469,24 @@ function registerTask(program3) {
8143
8469
  ok(row, () => line(`${import_picocolors28.default.green("moved")} ${import_picocolors28.default.bold(row.title)} \u2192 ${args[1]}`));
8144
8470
  })
8145
8471
  );
8472
+ task.command("resume [id]").description(
8473
+ "Put a task the owner stopped back in the queue \u2014 only when they ask you to carry on"
8474
+ ).action(
8475
+ action(async ({ ctx, args }) => {
8476
+ const id = resolveTaskId(ctx, args[0]);
8477
+ const row = await api(
8478
+ ctx,
8479
+ `/v1/project-tasks/${encodeURIComponent(id)}/retry`,
8480
+ { method: "POST" }
8481
+ );
8482
+ ok(
8483
+ row,
8484
+ () => line(
8485
+ row?.queued ? `${import_picocolors28.default.green("resumed")} \u2014 it is back in the queue and will start on the next dispatch` : import_picocolors28.default.yellow("could not resume it \u2014 check the step id")
8486
+ )
8487
+ );
8488
+ })
8489
+ );
8146
8490
  subtask.command("send-back <id>").description("Send a finished step back to be done again, with the reason").requiredOption("--note <text>", "what was wrong with it").action(
8147
8491
  action(async ({ ctx, args, opts }) => {
8148
8492
  const row = await api(
@@ -8209,7 +8553,7 @@ function registerTask(program3) {
8209
8553
  ok(row, () => line(`${import_picocolors28.default.green(row.approval_state)} ${import_picocolors28.default.bold(row.title)}`));
8210
8554
  })
8211
8555
  );
8212
- task.command("done [id]").description("Record what a step produced, and move it to ready").option("--summary <text>", "what changed, in the owner's words").action(
8556
+ task.command("done [id]").description("Record what a subtask produced, and move it to ready").option("--summary <text>", "what changed, in the owner's words").action(
8213
8557
  action(async ({ ctx, args, opts }) => {
8214
8558
  const id = resolveTaskId(ctx, args[0]);
8215
8559
  await api(ctx, `/v1/project-tasks/${encodeURIComponent(id)}`, {
@@ -8250,7 +8594,7 @@ async function resolveDeps(ctx, parentId, given) {
8250
8594
  const id = byKey.get(g);
8251
8595
  if (!id) {
8252
8596
  throw new WorkserError(
8253
- `"${g}" is not a step of this task. Run \`workser task subtask list\` to see them.`,
8597
+ `"${g}" is not a subtask of this task. Run \`workser task subtask list\` to see them.`,
8254
8598
  { code: "bad_request" }
8255
8599
  );
8256
8600
  }
@@ -8275,13 +8619,19 @@ function formatSubtask(r, index) {
8275
8619
  const n = import_picocolors28.default.dim(String(index).padStart(2, "0"));
8276
8620
  const role = r.role ? import_picocolors28.default.dim(` [${r.role}]`) : "";
8277
8621
  const scope = r.scope_paths?.length ? import_picocolors28.default.dim(` owns: ${r.scope_paths.join(", ")}`) : "";
8278
- return `${n} ${statusTag2(r.status)} ${r.title}${role}${scope}`;
8279
- }
8280
- function printTask(row) {
8622
+ const head = `${n} ${statusTag2(r.status)} ${r.title}${role}${scope}`;
8623
+ const summary = (r.result_summary ?? "").trim();
8624
+ if (!summary) return head;
8625
+ const wrapped = summary.split("\n").map((l) => ` ${l}`).join("\n");
8626
+ return `${head}
8627
+ ${import_picocolors28.default.dim(wrapped)}`;
8628
+ }
8629
+ function printTask(row, goal) {
8281
8630
  line(`${import_picocolors28.default.bold(row.title)} ${import_picocolors28.default.dim(row.key ?? row.id)}`);
8282
8631
  line(`${statusTag2(row.status)} approval: ${row.approval_state}`);
8283
8632
  if (row.summary) line(`
8284
8633
  ${row.summary}`);
8634
+ if (goal) printGoalContext(row, goal);
8285
8635
  if (row.targets?.length) {
8286
8636
  line(
8287
8637
  `
@@ -8290,11 +8640,52 @@ touches: ${row.targets.map((t) => t.appName ?? t.ref ?? t.kind).join(", ")}`
8290
8640
  }
8291
8641
  if (row.subtasks?.length) {
8292
8642
  line(`
8293
- ${import_picocolors28.default.bold("steps")}`);
8643
+ ${import_picocolors28.default.bold("subtasks")}`);
8294
8644
  row.subtasks.forEach((s, i) => line(formatSubtask(s, i + 1)));
8645
+ line(
8646
+ import_picocolors28.default.dim(
8647
+ `
8648
+ Run \`workser artifact list\` to see what these subtasks produced,`
8649
+ )
8650
+ );
8651
+ line(
8652
+ import_picocolors28.default.dim(
8653
+ `or \`workser artifact list --step <id>\` for one subtask's output alone.`
8654
+ )
8655
+ );
8295
8656
  } else {
8296
- line(import_picocolors28.default.dim("\nNo steps yet."));
8657
+ line(import_picocolors28.default.dim("\nNo subtasks yet."));
8658
+ }
8659
+ }
8660
+ function printGoalContext(row, goal) {
8661
+ const mine = row.phase ?? null;
8662
+ line(`
8663
+ ${import_picocolors28.default.bold("part of")}: ${goal.title}`);
8664
+ if (goal.outcome) line(import_picocolors28.default.dim(`done means: ${goal.outcome}`));
8665
+ const progress = goal.progress ?? [];
8666
+ for (const p of progress) {
8667
+ const where = p.total === 0 ? "not started" : `${p.done} of ${p.total} done`;
8668
+ const here = mine && p.name === mine ? import_picocolors28.default.cyan(" <- this task") : "";
8669
+ const mark = p.state === "done" ? import_picocolors28.default.green("*") : import_picocolors28.default.dim("-");
8670
+ line(` ${mark} ${p.name} \u2014 ${where}${here}`);
8671
+ }
8672
+ const next = progress.find((p) => p.state !== "done");
8673
+ const running = progress.some((p) => p.state === "working");
8674
+ if (next && !running) {
8675
+ line(
8676
+ import_picocolors28.default.dim(
8677
+ `
8678
+ Nothing is running. The next part waiting is "${next.name}" \u2014 offer it to them in a sentence rather than starting it unasked.`
8679
+ )
8680
+ );
8681
+ } else if (!next) {
8682
+ line(import_picocolors28.default.dim("\nEvery part of this plan is done. Say so, and offer to wrap it up."));
8297
8683
  }
8684
+ line(
8685
+ import_picocolors28.default.dim(
8686
+ "The plan is a reference for what was agreed, not a rule about what may run. Work can start on any part at any time if they ask for it."
8687
+ )
8688
+ );
8298
8689
  }
8299
8690
  function statusTag2(status) {
8300
8691
  switch (status) {
@@ -8313,12 +8704,230 @@ function statusTag2(status) {
8313
8704
  }
8314
8705
  }
8315
8706
 
8707
+ // src/commands/goal.ts
8708
+ var import_picocolors29 = __toESM(require_picocolors(), 1);
8709
+ var STATUSES3 = ["proposed", "agreed", "working", "delivered", "abandoned"];
8710
+ function registerGoal(program3) {
8711
+ const goal = program3.command("goal").description("Business goals and the phases that deliver them");
8712
+ goal.command("list").description("What this project is working towards").action(
8713
+ action(async ({ ctx }) => {
8714
+ requireProject(ctx);
8715
+ const rows = await api(ctx, "/v1/project-goals", {
8716
+ query: { projectId: ctx.projectId }
8717
+ }) ?? [];
8718
+ ok(rows, () => {
8719
+ if (!rows.length) {
8720
+ line(import_picocolors29.default.dim("No goals yet \u2014 every task here stands on its own."));
8721
+ return;
8722
+ }
8723
+ for (const g of rows) line(formatGoal(g));
8724
+ });
8725
+ })
8726
+ );
8727
+ goal.command("show <id>").description("One goal: its phases, and how far each has got").action(
8728
+ action(async ({ ctx, args }) => {
8729
+ const row = await api(
8730
+ ctx,
8731
+ `/v1/project-goals/${encodeURIComponent(String(args[0]))}`
8732
+ );
8733
+ ok(row, () => printGoal(row));
8734
+ })
8735
+ );
8736
+ goal.command("create <title>").description(
8737
+ "Propose a business goal and the phases that deliver it \u2014 the owner agrees the shape before any task exists"
8738
+ ).option(
8739
+ "--outcome <text>",
8740
+ "what 'done' means for the whole thing, in the owner's words"
8741
+ ).option(
8742
+ "--phase <name...>",
8743
+ "ordered phase names \u2014 each must deliver something the owner would notice"
8744
+ ).option(
8745
+ "--criteria <json>",
8746
+ `what "done" means per phase, in the OWNER's words: '{"Checkout":["A customer can pay by card and gets a receipt"]}'`
8747
+ ).action(
8748
+ action(async ({ ctx, args, opts }) => {
8749
+ requireProject(ctx);
8750
+ const phases = opts.phase ?? [];
8751
+ if (phases.length < 2) {
8752
+ throw new WorkserError(
8753
+ "A goal needs at least two phases. If you can't name two slices that each deliver something the owner would notice, this is one task \u2014 use `workser task create`."
8754
+ );
8755
+ }
8756
+ if (phases.length > 6) {
8757
+ throw new WorkserError(
8758
+ "More than six phases is a conversation, not a plan. Propose the first few and agree the rest as you go."
8759
+ );
8760
+ }
8761
+ let criteria = {};
8762
+ if (opts.criteria) {
8763
+ try {
8764
+ criteria = JSON.parse(opts.criteria);
8765
+ } catch {
8766
+ throw new WorkserError(
8767
+ `--criteria must be JSON mapping each phase name to a list of sentences, e.g. '{"Checkout":["A customer can pay by card"]}'`
8768
+ );
8769
+ }
8770
+ const unknown = Object.keys(criteria).filter(
8771
+ (name) => !phases.includes(name)
8772
+ );
8773
+ if (unknown.length) {
8774
+ throw new WorkserError(
8775
+ `--criteria names phases that aren't in this goal: ${unknown.join(", ")}. Phases are: ${phases.join(", ")}.`
8776
+ );
8777
+ }
8778
+ }
8779
+ const row = await api(ctx, "/v1/project-goals", {
8780
+ body: {
8781
+ projectId: ctx.projectId,
8782
+ title: args[0],
8783
+ outcome: opts.outcome,
8784
+ phases: phases.map((name) => ({
8785
+ name,
8786
+ criteria: (criteria[name] ?? []).map((text, i) => ({
8787
+ id: `c${i + 1}`,
8788
+ text,
8789
+ met: null
8790
+ }))
8791
+ })),
8792
+ channelId: ctx.projectChannelId,
8793
+ sourceMessageId: ctx.projectChannelMessageId
8794
+ }
8795
+ });
8796
+ ok(row, () => {
8797
+ success(`Proposed ${import_picocolors29.default.bold(row?.title ?? "goal")}`);
8798
+ printGoal(row);
8799
+ line(
8800
+ import_picocolors29.default.dim(
8801
+ "\nNothing has been created yet. The owner agrees the shape first."
8802
+ )
8803
+ );
8804
+ });
8805
+ })
8806
+ );
8807
+ goal.command("check <id> <criterionId>").description("Record whether an acceptance criterion is met").requiredOption("--phase <name>", "which part of the plan it belongs to").option("--pass", "it is met").option("--fail", "it is not met").option("--reset", "back to unchecked").option("--note <text>", "what you checked, and how").action(
8808
+ action(async ({ ctx, args, opts }) => {
8809
+ const chosen = [opts.pass, opts.fail, opts.reset].filter(Boolean);
8810
+ if (chosen.length !== 1) {
8811
+ throw new WorkserError(
8812
+ "Pass exactly one of --pass, --fail or --reset."
8813
+ );
8814
+ }
8815
+ if (opts.pass && !opts.note) {
8816
+ throw new WorkserError(
8817
+ "--note is required on a pass: say what you checked. A tick the owner cannot verify is worse than no tick."
8818
+ );
8819
+ }
8820
+ const met = opts.pass ? true : opts.fail ? false : null;
8821
+ const row = await api(
8822
+ ctx,
8823
+ `/v1/project-goals/${encodeURIComponent(String(args[0]))}/criteria/${encodeURIComponent(String(args[1]))}`,
8824
+ { method: "POST", body: { phase: opts.phase, met, note: opts.note } }
8825
+ );
8826
+ ok(
8827
+ row,
8828
+ () => line(
8829
+ row?.recorded ? met === true ? import_picocolors29.default.green("recorded \u2014 met") : met === false ? import_picocolors29.default.yellow("recorded \u2014 not met") : import_picocolors29.default.dim("recorded \u2014 back to unchecked") : import_picocolors29.default.yellow(
8830
+ "couldn't record it \u2014 check the goal id, the phase name and the criterion id"
8831
+ )
8832
+ )
8833
+ );
8834
+ })
8835
+ );
8836
+ goal.command("update <id>").description("Change the shape, or move the goal along").option("--title <text>", "the owner's words for what they want").option("--outcome <text>", "what 'done' means").option("--phase <name...>", "replace the ordered phase list").option("--status <value>", `one of: ${STATUSES3.join(" | ")}`).action(
8837
+ action(async ({ ctx, args, opts }) => {
8838
+ if (opts.status && !STATUSES3.includes(opts.status)) {
8839
+ throw new WorkserError(
8840
+ `Unknown status "${opts.status}". Use one of: ${STATUSES3.join(", ")}.`
8841
+ );
8842
+ }
8843
+ const row = await api(
8844
+ ctx,
8845
+ `/v1/project-goals/${encodeURIComponent(String(args[0]))}`,
8846
+ {
8847
+ method: "PATCH",
8848
+ body: {
8849
+ title: opts.title,
8850
+ outcome: opts.outcome,
8851
+ phases: opts.phase,
8852
+ status: opts.status
8853
+ }
8854
+ }
8855
+ );
8856
+ ok(row, () => printGoal(row));
8857
+ })
8858
+ );
8859
+ }
8860
+ function appScope(g) {
8861
+ const n = g.appIds?.length ?? 0;
8862
+ if (!n) return "";
8863
+ return import_picocolors29.default.dim(` ${n} part${n === 1 ? "" : "s"} of the system`);
8864
+ }
8865
+ function formatGoal(g) {
8866
+ const where = g.currentPhase && g.status !== "delivered" ? import_picocolors29.default.dim(` now: ${g.currentPhase}`) : "";
8867
+ const count = g.taskTotal != null ? import_picocolors29.default.dim(` ${g.taskDone}/${g.taskTotal} done`) : "";
8868
+ return `${statusTag3(g.status)} ${g.title}${appScope(g)}${where}${count} ${import_picocolors29.default.dim(g.id)}`;
8869
+ }
8870
+ function printGoal(g) {
8871
+ if (!g) return;
8872
+ line(`${import_picocolors29.default.bold(g.title)} ${import_picocolors29.default.dim(g.id)}`);
8873
+ line(statusTag3(g.status));
8874
+ if (g.outcome) line(`
8875
+ done means: ${g.outcome}`);
8876
+ const progress = g.progress ?? [];
8877
+ if (!progress.length) {
8878
+ line(import_picocolors29.default.dim("\nNo phases agreed yet."));
8879
+ return;
8880
+ }
8881
+ line(`
8882
+ ${import_picocolors29.default.bold("phases")}`);
8883
+ for (const p of progress) {
8884
+ const bar2 = p.total > 0 ? `${p.done}/${p.total} done` : import_picocolors29.default.dim("nothing filed yet");
8885
+ const mark = p.state === "done" ? import_picocolors29.default.green("done") : p.state === "working" ? import_picocolors29.default.blue("working") : import_picocolors29.default.dim("waiting");
8886
+ line(
8887
+ ` ${import_picocolors29.default.dim(String(p.index).padStart(2, "0"))} ${p.name} ${mark} ${import_picocolors29.default.dim(bar2)}`
8888
+ );
8889
+ for (const c of p.criteria ?? []) {
8890
+ const tick = c.met === true ? import_picocolors29.default.green("\u2713") : c.met === false ? import_picocolors29.default.red("\u2717") : import_picocolors29.default.dim("\xB7");
8891
+ line(` ${tick} ${c.text} ${import_picocolors29.default.dim(c.id)}`);
8892
+ if (c.note) line(import_picocolors29.default.dim(` ${c.note}`));
8893
+ }
8894
+ }
8895
+ const current = progress.find((p) => p.name === g.currentPhase);
8896
+ if (current) {
8897
+ line(
8898
+ import_picocolors29.default.dim(
8899
+ `
8900
+ ${current.name} \u2014 ${current.done} of ${current.total} done (phase ${current.index} of ${progress.length}).`
8901
+ )
8902
+ );
8903
+ }
8904
+ }
8905
+ function statusTag3(status) {
8906
+ switch (status) {
8907
+ case "agreed":
8908
+ return import_picocolors29.default.cyan("[agreed]");
8909
+ case "working":
8910
+ return import_picocolors29.default.blue("[working]");
8911
+ case "delivered":
8912
+ return import_picocolors29.default.green("[delivered]");
8913
+ case "abandoned":
8914
+ return import_picocolors29.default.dim("[abandoned]");
8915
+ default:
8916
+ return import_picocolors29.default.yellow("[proposed]");
8917
+ }
8918
+ }
8919
+
8316
8920
  // src/role-guard.ts
8317
8921
  var READS = [
8922
+ // `requirement` sits beside `doc` and `decision` because it is the same kind
8923
+ // of thing and was simply forgotten: the verb shipped, no role could run it,
8924
+ // and on 2026-08-23 a channel PM reported to the owner that "requirements are
8925
+ // not readable by this PM role" — which was exactly true, for every role.
8318
8926
  "task",
8319
8927
  "board",
8320
8928
  "doc",
8321
8929
  "decision",
8930
+ "requirement",
8322
8931
  "memory",
8323
8932
  "search",
8324
8933
  "verify",
@@ -8345,7 +8954,19 @@ var READS = [
8345
8954
  // Reading the plan and what is used against it. An agent proposing "add
8346
8955
  // another project" can only sensibly propose it if it can find out the plan
8347
8956
  // allows two and two already exist.
8348
- "usage"
8957
+ "usage",
8958
+ // RECORDING WHAT YOU PRODUCED IS NOT CHANGING THE PROJECT.
8959
+ // Every dispatched role is told, in its own preamble, to run `workser
8960
+ // artifact list` before non-trivial work and `workser artifact add` when it
8961
+ // finishes something the owner should get. Five roles — pm, qa, security,
8962
+ // analyst, sre — were then refused the verb here, so the instruction and the
8963
+ // guard contradicted each other: the agent kept trying to obey an order this
8964
+ // file would not let it obey, and burned its run doing it. It belongs beside
8965
+ // `doc`, `decision` and `board`, which are already in this list and also
8966
+ // have `create` subcommands — these write the project's RECORD, not the
8967
+ // project. What stops a reviewer editing code is its filesystem mode, and
8968
+ // that is untouched.
8969
+ "artifact"
8349
8970
  ];
8350
8971
  var BUILDS = [
8351
8972
  ...READS,
@@ -8354,7 +8975,6 @@ var BUILDS = [
8354
8975
  "db",
8355
8976
  "storage",
8356
8977
  "checkpoint",
8357
- "artifact",
8358
8978
  "image",
8359
8979
  "design",
8360
8980
  "ask",
@@ -8390,15 +9010,44 @@ var NEVER = {
8390
9010
  // proposed has removed the only gate this product has.
8391
9011
  "task approval": "Only the owner can approve a plan."
8392
9012
  };
9013
+ var READ_PAIRS = /* @__PURE__ */ new Set([
9014
+ // The project's own brand and design notes.
9015
+ "design show",
9016
+ // What exists, and what it is wired to.
9017
+ "app list",
9018
+ "app tools",
9019
+ // Shape, never contents-by-arbitrary-SQL.
9020
+ "db list",
9021
+ "db tables",
9022
+ "db schema",
9023
+ // What is stored, not what is in it.
9024
+ "storage list",
9025
+ "storage ls",
9026
+ "checkpoint list",
9027
+ "workflow list",
9028
+ "workflow get",
9029
+ "workflow runs",
9030
+ "workflow nodes",
9031
+ "tool list",
9032
+ // The business data an analyst exists to read.
9033
+ "business resources",
9034
+ "business list",
9035
+ "business get",
9036
+ // The database service's own inventory.
9037
+ "neon status",
9038
+ "neon list"
9039
+ ]);
8393
9040
  function assertRoleMayRun(argv) {
8394
9041
  const role = (process.env.WORKSER_ROLE ?? "").trim();
8395
9042
  if (!role) return;
8396
- const verb = argv[0];
9043
+ const commandArgv = stripLeadingGlobalOptions(argv);
9044
+ const verb = commandArgv[0];
8397
9045
  if (!verb) return;
8398
- const pair = `${argv[0]} ${argv[1] ?? ""}`.trim();
8399
- if (NEVER[pair] && !(pair === "task approval" && (argv[2] === "request" || !argv[2]))) {
9046
+ const pair = `${commandArgv[0]} ${commandArgv[1] ?? ""}`.trim();
9047
+ if (NEVER[pair] && !(pair === "task approval" && (commandArgv[2] === "request" || !commandArgv[2]))) {
8400
9048
  throw new WorkserError(NEVER[pair], { code: "role_forbidden" });
8401
9049
  }
9050
+ if (READ_PAIRS.has(pair)) return;
8402
9051
  const allowed = ROLE_VERBS[role];
8403
9052
  const list = allowed ?? READS;
8404
9053
  if (!list.includes(verb)) {
@@ -8408,9 +9057,38 @@ function assertRoleMayRun(argv) {
8408
9057
  );
8409
9058
  }
8410
9059
  }
9060
+ function stripLeadingGlobalOptions(argv) {
9061
+ const takesValue = /* @__PURE__ */ new Set([
9062
+ "-p",
9063
+ "--project",
9064
+ "-C",
9065
+ "--cwd",
9066
+ "--endpoint",
9067
+ "--token"
9068
+ ]);
9069
+ const flags = /* @__PURE__ */ new Set(["--json", "-q", "--quiet", "-v", "--version"]);
9070
+ let index = 0;
9071
+ while (index < argv.length) {
9072
+ const token = argv[index];
9073
+ if (flags.has(token)) {
9074
+ index += 1;
9075
+ continue;
9076
+ }
9077
+ if (takesValue.has(token)) {
9078
+ index += 2;
9079
+ continue;
9080
+ }
9081
+ if (token.startsWith("--project=") || token.startsWith("--cwd=") || token.startsWith("--endpoint=") || token.startsWith("--token=")) {
9082
+ index += 1;
9083
+ continue;
9084
+ }
9085
+ break;
9086
+ }
9087
+ return argv.slice(index);
9088
+ }
8411
9089
 
8412
9090
  // src/commands/decision.ts
8413
- var import_picocolors29 = __toESM(require_picocolors(), 1);
9091
+ var import_picocolors30 = __toESM(require_picocolors(), 1);
8414
9092
  function registerDecision(program3) {
8415
9093
  const decision = program3.command("decision").description("Read and record the project's architecture decisions");
8416
9094
  decision.command("list").description("Every decision on record \u2014 read this before changing how something works").option("--limit <n>", "cap the number returned (newest first)").action(
@@ -8423,11 +9101,11 @@ function registerDecision(program3) {
8423
9101
  rows = applyLimit(rows, opts.limit);
8424
9102
  ok(rows, () => {
8425
9103
  if (!rows.length) {
8426
- line(import_picocolors29.default.dim("No decisions recorded yet."));
9104
+ line(import_picocolors30.default.dim("No decisions recorded yet."));
8427
9105
  return;
8428
9106
  }
8429
9107
  for (const r of rows) {
8430
- line(`${import_picocolors29.default.dim(r.id)} ${import_picocolors29.default.dim(shortDate(r.createdAt))} ${r.title}`);
9108
+ line(`${import_picocolors30.default.dim(r.id)} ${import_picocolors30.default.dim(shortDate(r.createdAt))} ${r.title}`);
8431
9109
  line(` ${truncate(r.decision, 100)}`);
8432
9110
  }
8433
9111
  });
@@ -8441,16 +9119,16 @@ function registerDecision(program3) {
8441
9119
  `/v1/projects/${projectId}/architecture-decisions/${args[0]}`
8442
9120
  );
8443
9121
  ok(row, () => {
8444
- line(`${import_picocolors29.default.bold(row.title)} ${import_picocolors29.default.dim(row.id)}`);
8445
- line(import_picocolors29.default.dim(`${row.status} \xB7 ${shortDate(row.createdAt)}`));
9122
+ line(`${import_picocolors30.default.bold(row.title)} ${import_picocolors30.default.dim(row.id)}`);
9123
+ line(import_picocolors30.default.dim(`${row.status} \xB7 ${shortDate(row.createdAt)}`));
8446
9124
  line(`
8447
- ${import_picocolors29.default.bold("Context")}
9125
+ ${import_picocolors30.default.bold("Context")}
8448
9126
  ${row.context}`);
8449
9127
  line(`
8450
- ${import_picocolors29.default.bold("Decision")}
9128
+ ${import_picocolors30.default.bold("Decision")}
8451
9129
  ${row.decision}`);
8452
9130
  if (row.consequences) line(`
8453
- ${import_picocolors29.default.bold("Consequences")}
9131
+ ${import_picocolors30.default.bold("Consequences")}
8454
9132
  ${row.consequences}`);
8455
9133
  });
8456
9134
  })
@@ -8479,7 +9157,7 @@ ${row.consequences}`);
8479
9157
  refId: row?.id,
8480
9158
  output: { decision: row }
8481
9159
  });
8482
- ok(row, () => line(`Recorded decision ${import_picocolors29.default.bold(row?.id ?? "")} \u2014 ${title}`));
9160
+ ok(row, () => line(`Recorded decision ${import_picocolors30.default.bold(row?.id ?? "")} \u2014 ${title}`));
8483
9161
  })
8484
9162
  );
8485
9163
  const requirement = program3.command("requirement").description("Read and record the project's requirements");
@@ -8491,11 +9169,11 @@ ${row.consequences}`);
8491
9169
  rows = applyLimit(rows, opts.limit);
8492
9170
  ok(rows, () => {
8493
9171
  if (!rows.length) {
8494
- line(import_picocolors29.default.dim("No requirements recorded yet."));
9172
+ line(import_picocolors30.default.dim("No requirements recorded yet."));
8495
9173
  return;
8496
9174
  }
8497
9175
  for (const r of rows) {
8498
- line(`${import_picocolors29.default.dim(r.id)} ${r.status.padEnd(9)} ${r.title}`);
9176
+ line(`${import_picocolors30.default.dim(r.id)} ${r.status.padEnd(9)} ${r.title}`);
8499
9177
  }
8500
9178
  });
8501
9179
  })
@@ -8508,8 +9186,8 @@ ${row.consequences}`);
8508
9186
  `/v1/projects/${projectId}/requirements/${args[0]}`
8509
9187
  );
8510
9188
  ok(row, () => {
8511
- line(`${import_picocolors29.default.bold(row.title)} ${import_picocolors29.default.dim(row.id)}`);
8512
- line(import_picocolors29.default.dim(`${row.status} \xB7 ${shortDate(row.createdAt)}`));
9189
+ line(`${import_picocolors30.default.bold(row.title)} ${import_picocolors30.default.dim(row.id)}`);
9190
+ line(import_picocolors30.default.dim(`${row.status} \xB7 ${shortDate(row.createdAt)}`));
8513
9191
  line(`
8514
9192
  ${row.body}`);
8515
9193
  });
@@ -8535,7 +9213,7 @@ ${row.body}`);
8535
9213
  refId: row?.id,
8536
9214
  output: { requirement: row }
8537
9215
  });
8538
- ok(row, () => line(`Recorded requirement ${import_picocolors29.default.bold(row?.id ?? "")} \u2014 ${title}`));
9216
+ ok(row, () => line(`Recorded requirement ${import_picocolors30.default.bold(row?.id ?? "")} \u2014 ${title}`));
8539
9217
  })
8540
9218
  );
8541
9219
  requirement.command("update <id>").description(
@@ -8564,7 +9242,7 @@ ${row.body}`);
8564
9242
  code: "bad_request"
8565
9243
  });
8566
9244
  }
8567
- ok(row, () => line(`Updated requirement ${import_picocolors29.default.bold(row.id)} \u2014 ${row.title} (${row.status})`));
9245
+ ok(row, () => line(`Updated requirement ${import_picocolors30.default.bold(row.id)} \u2014 ${row.title} (${row.status})`));
8568
9246
  })
8569
9247
  );
8570
9248
  }
@@ -8587,7 +9265,7 @@ function shortDate(iso) {
8587
9265
  }
8588
9266
 
8589
9267
  // src/commands/doc.ts
8590
- var import_picocolors30 = __toESM(require_picocolors(), 1);
9268
+ var import_picocolors31 = __toESM(require_picocolors(), 1);
8591
9269
 
8592
9270
  // src/mermaid-fences.ts
8593
9271
  function hasDiagram(code) {
@@ -8632,13 +9310,13 @@ function registerDoc(program3) {
8632
9310
  }) ?? [];
8633
9311
  ok(rows, () => {
8634
9312
  if (!rows.length) {
8635
- line(import_picocolors30.default.dim("No documents yet."));
9313
+ line(import_picocolors31.default.dim("No documents yet."));
8636
9314
  return;
8637
9315
  }
8638
9316
  for (const r of rows) {
8639
- const link = r.workItemId ? import_picocolors30.default.dim(` \u21B3 ${r.workItemId}`) : "";
8640
- const file = r.filePath ? import_picocolors30.default.dim(` ${r.filePath}`) : "";
8641
- line(`${import_picocolors30.default.dim(r.id)} ${r.title}${link}${file}`);
9317
+ const link = r.workItemId ? import_picocolors31.default.dim(` \u21B3 ${r.workItemId}`) : "";
9318
+ const file = r.filePath ? import_picocolors31.default.dim(` ${r.filePath}`) : "";
9319
+ line(`${import_picocolors31.default.dim(r.id)} ${r.title}${link}${file}`);
8642
9320
  }
8643
9321
  });
8644
9322
  })
@@ -8652,17 +9330,17 @@ function registerDoc(program3) {
8652
9330
  );
8653
9331
  if (opts.markdown) {
8654
9332
  ok({ id: row.id, title: row.title, filePath: row.filePath }, () => {
8655
- line(`${import_picocolors30.default.bold(row.title)} ${import_picocolors30.default.dim(row.id)}`);
9333
+ line(`${import_picocolors31.default.bold(row.title)} ${import_picocolors31.default.dim(row.id)}`);
8656
9334
  line(
8657
- row.filePath ? `Read it at ${import_picocolors30.default.bold(row.filePath)} (relative to the project folder).` : import_picocolors30.default.dim("This document has no markdown mirror on disk yet.")
9335
+ row.filePath ? `Read it at ${import_picocolors31.default.bold(row.filePath)} (relative to the project folder).` : import_picocolors31.default.dim("This document has no markdown mirror on disk yet.")
8658
9336
  );
8659
9337
  });
8660
9338
  return;
8661
9339
  }
8662
9340
  ok(row, () => {
8663
- line(`${import_picocolors30.default.bold(row.title)} ${import_picocolors30.default.dim(row.id)}`);
8664
- if (row.workItemId) line(import_picocolors30.default.dim(`linked to work item ${row.workItemId}`));
8665
- if (row.filePath) line(import_picocolors30.default.dim(`markdown mirror: ${row.filePath}`));
9341
+ line(`${import_picocolors31.default.bold(row.title)} ${import_picocolors31.default.dim(row.id)}`);
9342
+ if (row.workItemId) line(import_picocolors31.default.dim(`linked to work item ${row.workItemId}`));
9343
+ if (row.filePath) line(import_picocolors31.default.dim(`markdown mirror: ${row.filePath}`));
8666
9344
  line("");
8667
9345
  line(row.contentJson);
8668
9346
  });
@@ -8691,7 +9369,7 @@ function registerDoc(program3) {
8691
9369
  refId: row?.id,
8692
9370
  output: { document: row }
8693
9371
  });
8694
- ok(row, () => line(`Created document ${import_picocolors30.default.bold(row?.id ?? "")} \u2014 ${title}`));
9372
+ ok(row, () => line(`Created document ${import_picocolors31.default.bold(row?.id ?? "")} \u2014 ${title}`));
8695
9373
  })
8696
9374
  );
8697
9375
  doc.command("diagram <id>").description(
@@ -8717,19 +9395,19 @@ function registerDoc(program3) {
8717
9395
  diagrams: diagrams.map((code) => ({ kind: diagramKind(code), code }))
8718
9396
  };
8719
9397
  ok(payload, () => {
8720
- line(`${import_picocolors30.default.bold(row.title)} ${import_picocolors30.default.dim(row.id)}`);
9398
+ line(`${import_picocolors31.default.bold(row.title)} ${import_picocolors31.default.dim(row.id)}`);
8721
9399
  if (markdown === null) {
8722
9400
  line(
8723
- import_picocolors30.default.dim(
9401
+ import_picocolors31.default.dim(
8724
9402
  row.filePath ? `Could not read ${row.filePath} from this folder.` : "This document has no markdown mirror on disk yet."
8725
9403
  )
8726
9404
  );
8727
9405
  } else if (!diagrams.length) {
8728
- line(import_picocolors30.default.dim("No diagrams in this document."));
9406
+ line(import_picocolors31.default.dim("No diagrams in this document."));
8729
9407
  } else {
8730
9408
  for (const [i, code] of diagrams.entries()) {
8731
9409
  const kind = diagramKind(code) ?? "diagram";
8732
- line(`${import_picocolors30.default.dim(String(i + 1))} ${kind} ${import_picocolors30.default.dim(`${code.split("\n").length} lines`)}`);
9410
+ line(`${import_picocolors31.default.dim(String(i + 1))} ${kind} ${import_picocolors31.default.dim(`${code.split("\n").length} lines`)}`);
8733
9411
  }
8734
9412
  }
8735
9413
  });
@@ -8765,7 +9443,7 @@ function registerDoc(program3) {
8765
9443
  code: "bad_request"
8766
9444
  });
8767
9445
  }
8768
- ok(row, () => line(`Updated document ${import_picocolors30.default.bold(row.id)} \u2014 ${row.title}`));
9446
+ ok(row, () => line(`Updated document ${import_picocolors31.default.bold(row.id)} \u2014 ${row.title}`));
8769
9447
  })
8770
9448
  );
8771
9449
  }
@@ -8779,7 +9457,7 @@ function readMirror(cwd, filePath) {
8779
9457
  }
8780
9458
 
8781
9459
  // src/commands/design.ts
8782
- var import_picocolors31 = __toESM(require_picocolors(), 1);
9460
+ var import_picocolors32 = __toESM(require_picocolors(), 1);
8783
9461
  function registerDesign(program3) {
8784
9462
  const design = program3.command("design").description("Read the project's brand (colours, fonts, logo)");
8785
9463
  design.command("show").description("Show this project's brand \u2014 read it before writing any UI").option("--raw", "print the generated token files verbatim instead of a summary").action(
@@ -8793,11 +9471,11 @@ function registerDesign(program3) {
8793
9471
  if (opts.raw) {
8794
9472
  ok(files, () => {
8795
9473
  if (!files.length) {
8796
- line(import_picocolors31.default.dim("No brand set for this project."));
9474
+ line(import_picocolors32.default.dim("No brand set for this project."));
8797
9475
  return;
8798
9476
  }
8799
9477
  for (const f of files) {
8800
- line(import_picocolors31.default.bold(f.path));
9478
+ line(import_picocolors32.default.bold(f.path));
8801
9479
  line(f.contents);
8802
9480
  line("");
8803
9481
  }
@@ -8814,21 +9492,21 @@ function registerDesign(program3) {
8814
9492
  } : { hasBrand: false, colors: {}, fonts: {}, brand: {}, files: [] };
8815
9493
  ok(summary, () => {
8816
9494
  if (!tokens) {
8817
- line(import_picocolors31.default.dim("No brand set for this project \u2014 choose sensible styling yourself."));
9495
+ line(import_picocolors32.default.dim("No brand set for this project \u2014 choose sensible styling yourself."));
8818
9496
  return;
8819
9497
  }
8820
9498
  for (const [name, value] of Object.entries(tokens.brand)) {
8821
- line(`${import_picocolors31.default.dim(name.padEnd(12))} ${value}`);
9499
+ line(`${import_picocolors32.default.dim(name.padEnd(12))} ${value}`);
8822
9500
  }
8823
9501
  for (const [name, value] of Object.entries(tokens.color)) {
8824
- line(`${import_picocolors31.default.dim(`color.${name}`.padEnd(12))} ${value}`);
9502
+ line(`${import_picocolors32.default.dim(`color.${name}`.padEnd(12))} ${value}`);
8825
9503
  }
8826
9504
  for (const [name, value] of Object.entries(tokens.font)) {
8827
- line(`${import_picocolors31.default.dim(`font.${name}`.padEnd(12))} ${value}`);
9505
+ line(`${import_picocolors32.default.dim(`font.${name}`.padEnd(12))} ${value}`);
8828
9506
  }
8829
9507
  line("");
8830
9508
  line(
8831
- import_picocolors31.default.dim(
9509
+ import_picocolors32.default.dim(
8832
9510
  `Generated into the working tree as ${files.map((f) => f.path).join(", ")} \u2014 wire those in, never edit them.`
8833
9511
  )
8834
9512
  );
@@ -8859,7 +9537,7 @@ function unwrap(group) {
8859
9537
  }
8860
9538
 
8861
9539
  // src/commands/api.ts
8862
- var import_picocolors32 = __toESM(require_picocolors(), 1);
9540
+ var import_picocolors33 = __toESM(require_picocolors(), 1);
8863
9541
  import { readdirSync, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
8864
9542
  import { join as join3, relative, sep } from "path";
8865
9543
 
@@ -8973,10 +9651,10 @@ function registerApi(program3) {
8973
9651
  const appId = requireApp(opts.app);
8974
9652
  const res = await api(ctx, `/v1/apps/${encodeURIComponent(appId)}/api/requests`);
8975
9653
  ok(res, () => {
8976
- for (const note of res?.notes ?? []) line(import_picocolors32.default.dim(note));
9654
+ for (const note of res?.notes ?? []) line(import_picocolors33.default.dim(note));
8977
9655
  for (const r of res?.requests ?? []) {
8978
9656
  line(
8979
- `${import_picocolors32.default.dim(r.method.padEnd(6))}${r.path}${r.note ? import_picocolors32.default.dim(` ${r.note}`) : ""}`
9657
+ `${import_picocolors33.default.dim(r.method.padEnd(6))}${r.path}${r.note ? import_picocolors33.default.dim(` ${r.note}`) : ""}`
8980
9658
  );
8981
9659
  }
8982
9660
  });
@@ -9010,14 +9688,14 @@ function registerApi(program3) {
9010
9688
  );
9011
9689
  ok(res, () => {
9012
9690
  if (!res?.ok) {
9013
- line(import_picocolors32.default.red(res?.error ?? "The service did not answer."));
9691
+ line(import_picocolors33.default.red(res?.error ?? "The service did not answer."));
9014
9692
  return;
9015
9693
  }
9016
9694
  const code = `${res.status}${res.statusText ? ` ${res.statusText}` : ""}`;
9017
- const colour2 = res.status && res.status < 300 ? import_picocolors32.default.green : res.status && res.status < 500 ? import_picocolors32.default.yellow : import_picocolors32.default.red;
9018
- line(`${colour2(code)} ${import_picocolors32.default.dim(`${res.durationMs}ms ${res.url}`)}`);
9695
+ const colour2 = res.status && res.status < 300 ? import_picocolors33.default.green : res.status && res.status < 500 ? import_picocolors33.default.yellow : import_picocolors33.default.red;
9696
+ line(`${colour2(code)} ${import_picocolors33.default.dim(`${res.durationMs}ms ${res.url}`)}`);
9019
9697
  if (res.body) line(res.body);
9020
- if (res.truncated) line(import_picocolors32.default.dim("(answer truncated)"));
9698
+ if (res.truncated) line(import_picocolors33.default.dim("(answer truncated)"));
9021
9699
  });
9022
9700
  if (!res?.ok) process.exitCode = 1;
9023
9701
  })
@@ -9037,15 +9715,15 @@ function registerApi(program3) {
9037
9715
  for (const r of report.routes) {
9038
9716
  const known = report.missing.some((m) => m.path === r.path);
9039
9717
  line(
9040
- `${known ? import_picocolors32.default.yellow("undocumented") : import_picocolors32.default.green("documented ")} ${r.path}${import_picocolors32.default.dim(` ${r.file}`)}`
9718
+ `${known ? import_picocolors33.default.yellow("undocumented") : import_picocolors33.default.green("documented ")} ${r.path}${import_picocolors33.default.dim(` ${r.file}`)}`
9041
9719
  );
9042
9720
  }
9043
9721
  for (const p of report.stale) {
9044
- line(`${import_picocolors32.default.dim("in spec only ")} ${p}`);
9722
+ line(`${import_picocolors33.default.dim("in spec only ")} ${p}`);
9045
9723
  }
9046
9724
  line("");
9047
9725
  if (report.ok && specFile) success(summary);
9048
- else line(import_picocolors32.default.yellow(summary));
9726
+ else line(import_picocolors33.default.yellow(summary));
9049
9727
  });
9050
9728
  if (opts.check && !report.ok) {
9051
9729
  throw new WorkserError(summary, { code: "bad_request" });
@@ -9116,7 +9794,7 @@ function listRepoFiles(root, maxDepth = 8) {
9116
9794
  }
9117
9795
 
9118
9796
  // src/commands/analysis.ts
9119
- var import_picocolors33 = __toESM(require_picocolors(), 1);
9797
+ var import_picocolors34 = __toESM(require_picocolors(), 1);
9120
9798
  import { readFileSync as readFileSync4 } from "fs";
9121
9799
  function registerAnalysis(program3) {
9122
9800
  const cmd = program3.command("analysis").description("Run Python analysis locally, recorded in the task");
@@ -9126,14 +9804,14 @@ function registerAnalysis(program3) {
9126
9804
  const res = await api(ctx, path);
9127
9805
  ok(res, () => {
9128
9806
  line(
9129
- `${res?.available ? import_picocolors33.default.green("python") : import_picocolors33.default.red("python")} ${res?.version ?? "not found"} ${import_picocolors33.default.dim(res?.python ?? "")}`
9807
+ `${res?.available ? import_picocolors34.default.green("python") : import_picocolors34.default.red("python")} ${res?.version ?? "not found"} ${import_picocolors34.default.dim(res?.python ?? "")}`
9130
9808
  );
9131
9809
  for (const lib of res?.libraries ?? []) {
9132
9810
  line(
9133
- `${lib.present ? import_picocolors33.default.green(lib.name) : import_picocolors33.default.yellow(lib.name)}${import_picocolors33.default.dim(lib.present ? "" : " missing")}`
9811
+ `${lib.present ? import_picocolors34.default.green(lib.name) : import_picocolors34.default.yellow(lib.name)}${import_picocolors34.default.dim(lib.present ? "" : " missing")}`
9134
9812
  );
9135
9813
  }
9136
- for (const note of res?.notes ?? []) line(import_picocolors33.default.dim(note));
9814
+ for (const note of res?.notes ?? []) line(import_picocolors34.default.dim(note));
9137
9815
  });
9138
9816
  if (!res?.available) process.exitCode = 1;
9139
9817
  })
@@ -9155,15 +9833,15 @@ function registerAnalysis(program3) {
9155
9833
  );
9156
9834
  ok(res, () => {
9157
9835
  if (res?.stdout) line(res.stdout.replace(/\n$/, ""));
9158
- if (res?.stderr) line(import_picocolors33.default.dim(res.stderr.replace(/\n$/, "")));
9159
- if (res?.truncated) line(import_picocolors33.default.dim("(output truncated)"));
9836
+ if (res?.stderr) line(import_picocolors34.default.dim(res.stderr.replace(/\n$/, "")));
9837
+ if (res?.truncated) line(import_picocolors34.default.dim("(output truncated)"));
9160
9838
  const took = `${Math.round((res?.durationMs ?? 0) / 100) / 10}s`;
9161
9839
  line(
9162
- res?.ok ? import_picocolors33.default.green(`\u2713 ${res.summary ?? "It finished."}`) + import_picocolors33.default.dim(` ${took}`) : import_picocolors33.default.yellow(res?.summary ?? "It did not finish.") + import_picocolors33.default.dim(` ${took}`)
9840
+ res?.ok ? import_picocolors34.default.green(`\u2713 ${res.summary ?? "It finished."}`) + import_picocolors34.default.dim(` ${took}`) : import_picocolors34.default.yellow(res?.summary ?? "It did not finish.") + import_picocolors34.default.dim(` ${took}`)
9163
9841
  );
9164
9842
  if (res && !res.sandboxed) {
9165
9843
  line(
9166
- import_picocolors33.default.dim(
9844
+ import_picocolors34.default.dim(
9167
9845
  "This platform has no OS sandbox, so the script ran with your own file access."
9168
9846
  )
9169
9847
  );
@@ -9191,7 +9869,7 @@ function readCode(file, inline) {
9191
9869
  }
9192
9870
 
9193
9871
  // src/commands/scan.ts
9194
- var import_picocolors34 = __toESM(require_picocolors(), 1);
9872
+ var import_picocolors35 = __toESM(require_picocolors(), 1);
9195
9873
  import { spawnSync as spawnSync2 } from "child_process";
9196
9874
  import { existsSync as existsSync3, readFileSync as readFileSync5, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
9197
9875
  import { join as join4, relative as relative2, sep as sep2 } from "path";
@@ -9475,22 +10153,22 @@ function runPermissions(cwd, findings, checked, skipped) {
9475
10153
  }
9476
10154
  function print2(report, summary) {
9477
10155
  for (const s of report.skipped) {
9478
- line(`${import_picocolors34.default.yellow("not checked")} ${s.check}${import_picocolors34.default.dim(` \u2014 ${s.reason}`)}`);
10156
+ line(`${import_picocolors35.default.yellow("not checked")} ${s.check}${import_picocolors35.default.dim(` \u2014 ${s.reason}`)}`);
9479
10157
  }
9480
10158
  for (const f of report.findings) {
9481
- const where = f.file ? import_picocolors34.default.dim(` ${f.file}${f.line ? `:${f.line}` : ""}`) : "";
10159
+ const where = f.file ? import_picocolors35.default.dim(` ${f.file}${f.line ? `:${f.line}` : ""}`) : "";
9482
10160
  line(`${severityTag(f.severity)} ${f.title}${where}`);
9483
- line(` ${import_picocolors34.default.dim(f.fix)}`);
10161
+ line(` ${import_picocolors35.default.dim(f.fix)}`);
9484
10162
  }
9485
10163
  if (report.findings.length || report.skipped.length) line("");
9486
10164
  if (report.ok && !report.skipped.length) success(summary);
9487
- else if (report.ok) line(import_picocolors34.default.yellow(summary));
9488
- else line(import_picocolors34.default.red(summary));
10165
+ else if (report.ok) line(import_picocolors35.default.yellow(summary));
10166
+ else line(import_picocolors35.default.red(summary));
9489
10167
  }
9490
10168
  function severityTag(severity) {
9491
- if (severity === "high") return import_picocolors34.default.red("serious ");
9492
- if (severity === "medium") return import_picocolors34.default.yellow("worth fixing");
9493
- return import_picocolors34.default.dim("minor ");
10169
+ if (severity === "high") return import_picocolors35.default.red("serious ");
10170
+ if (severity === "medium") return import_picocolors35.default.yellow("worth fixing");
10171
+ return import_picocolors35.default.dim("minor ");
9494
10172
  }
9495
10173
  function git(cwd, args) {
9496
10174
  try {
@@ -9551,7 +10229,7 @@ function listRepoFiles2(root, maxDepth = 8) {
9551
10229
  }
9552
10230
 
9553
10231
  // src/commands/health.ts
9554
- var import_picocolors35 = __toESM(require_picocolors(), 1);
10232
+ var import_picocolors36 = __toESM(require_picocolors(), 1);
9555
10233
  function registerHealth(program3) {
9556
10234
  program3.command("health").description("Check that the published apps in this project are still answering").option("--app <webAppId>", "check one app rather than all of them").action(
9557
10235
  action(async ({ ctx, opts }) => {
@@ -9565,16 +10243,16 @@ function registerHealth(program3) {
9565
10243
  }
9566
10244
  function print3(res) {
9567
10245
  if (!res?.checks?.length) {
9568
- line(import_picocolors35.default.dim(res?.note ?? "Nothing to check."));
10246
+ line(import_picocolors36.default.dim(res?.note ?? "Nothing to check."));
9569
10247
  return;
9570
10248
  }
9571
10249
  for (const c of res.checks) {
9572
- const mark = c.ok ? import_picocolors35.default.green("up ") : import_picocolors35.default.red("down");
9573
- const timing = import_picocolors35.default.dim(`${c.ms}ms`);
9574
- const detail = c.ok ? timing : import_picocolors35.default.dim(`${c.error ?? "no answer"}${c.failures > 1 ? ` \xB7 ${c.failures} in a row` : ""}`);
9575
- line(` ${mark} ${c.appName} ${import_picocolors35.default.dim(`(${c.environment})`)} ${c.url} ${detail}`);
10250
+ const mark = c.ok ? import_picocolors36.default.green("up ") : import_picocolors36.default.red("down");
10251
+ const timing = import_picocolors36.default.dim(`${c.ms}ms`);
10252
+ const detail = c.ok ? timing : import_picocolors36.default.dim(`${c.error ?? "no answer"}${c.failures > 1 ? ` \xB7 ${c.failures} in a row` : ""}`);
10253
+ line(` ${mark} ${c.appName} ${import_picocolors36.default.dim(`(${c.environment})`)} ${c.url} ${detail}`);
9576
10254
  if (c.incidentOpened) {
9577
- line(import_picocolors35.default.yellow(` An incident has been opened on the board for this.`));
10255
+ line(import_picocolors36.default.yellow(` An incident has been opened on the board for this.`));
9578
10256
  }
9579
10257
  }
9580
10258
  const down = res.checks.filter((c) => !c.ok);
@@ -9587,14 +10265,14 @@ function print3(res) {
9587
10265
  }
9588
10266
  const production = down.filter((c) => c.environment === "production").length;
9589
10267
  line(
9590
- import_picocolors35.default.red(
10268
+ import_picocolors36.default.red(
9591
10269
  `${down.length} of ${res.checks.length} not answering` + (production ? ` \u2014 ${production} customer-facing.` : " (preview only).")
9592
10270
  )
9593
10271
  );
9594
10272
  }
9595
10273
 
9596
10274
  // src/commands/urls.ts
9597
- var import_picocolors36 = __toESM(require_picocolors(), 1);
10275
+ var import_picocolors37 = __toESM(require_picocolors(), 1);
9598
10276
  function registerUrls(program3) {
9599
10277
  program3.command("urls").description("The stable preview and production addresses of every app in this project").option("--app <webAppId>", "just one app").action(
9600
10278
  action(async ({ ctx, opts }) => {
@@ -9608,20 +10286,20 @@ function registerUrls(program3) {
9608
10286
  const summary = urlsSummary(rows);
9609
10287
  ok({ rows, summary }, () => {
9610
10288
  for (const row of rows) {
9611
- const label = import_picocolors36.default.dim(row.environment.padEnd(10));
9612
- const value = row.url ? import_picocolors36.default.cyan(row.url) : import_picocolors36.default.dim(row.note ?? "not published");
10289
+ const label = import_picocolors37.default.dim(row.environment.padEnd(10));
10290
+ const value = row.url ? import_picocolors37.default.cyan(row.url) : import_picocolors37.default.dim(row.note ?? "not published");
9613
10291
  line(` ${row.appName.padEnd(22)} ${label} ${value}`);
9614
10292
  }
9615
10293
  line("");
9616
10294
  if (rows.some((r) => r.url)) success(summary);
9617
- else line(import_picocolors36.default.yellow(summary));
10295
+ else line(import_picocolors37.default.yellow(summary));
9618
10296
  });
9619
10297
  })
9620
10298
  );
9621
10299
  }
9622
10300
 
9623
10301
  // src/commands/deployments.ts
9624
- var import_picocolors37 = __toESM(require_picocolors(), 1);
10302
+ var import_picocolors38 = __toESM(require_picocolors(), 1);
9625
10303
  function registerDeployments(program3) {
9626
10304
  const cmd = program3.command("deployments").description("Deployment history, and putting a build in front of customers");
9627
10305
  cmd.command("list").description("What has been built, newest first").option("--app <webAppId>", "just one app (default: every app in the project)").option("--env <environment>", "preview or production").option("--limit <n>", "how many to show", "20").action(
@@ -9639,7 +10317,7 @@ function registerDeployments(program3) {
9639
10317
  ok(res, () => {
9640
10318
  if (!items.length) {
9641
10319
  return line(
9642
- import_picocolors37.default.dim(
10320
+ import_picocolors38.default.dim(
9643
10321
  environment ? `Nothing has been deployed to ${environment} yet.` : "Nothing has been deployed yet. `workser deploy` builds the first one."
9644
10322
  )
9645
10323
  );
@@ -9659,13 +10337,13 @@ function registerDeployments(program3) {
9659
10337
  ).catch(() => null) : null;
9660
10338
  ok({ ...dep, logs }, () => {
9661
10339
  line(formatDeployment(dep));
9662
- if (dep?.error_message) line(import_picocolors37.default.red(` ${dep.error_message}`));
10340
+ if (dep?.error_message) line(import_picocolors38.default.red(` ${dep.error_message}`));
9663
10341
  const events = logs?.events ?? [];
9664
10342
  for (const e of events) {
9665
- line(` ${import_picocolors37.default.dim(String(e.type ?? "log"))} ${e.text ?? ""}`);
10343
+ line(` ${import_picocolors38.default.dim(String(e.type ?? "log"))} ${e.text ?? ""}`);
9666
10344
  }
9667
10345
  if (opts.logs && !events.length) {
9668
- line(import_picocolors37.default.dim(" That build produced no output."));
10346
+ line(import_picocolors38.default.dim(" That build produced no output."));
9669
10347
  }
9670
10348
  });
9671
10349
  })
@@ -9709,16 +10387,16 @@ function printPromoted(res, version) {
9709
10387
  const what = version === null ? "the latest build" : `version ${version}`;
9710
10388
  const url = res.url ?? res.vercel_url;
9711
10389
  success(`Production is being rebuilt from ${what}.`);
9712
- if (url) line(import_picocolors37.default.dim(`It will be at ${url}`));
9713
- line(import_picocolors37.default.dim("`workser deploy status` follows it."));
10390
+ if (url) line(import_picocolors38.default.dim(`It will be at ${url}`));
10391
+ line(import_picocolors38.default.dim("`workser deploy status` follows it."));
9714
10392
  }
9715
10393
  function formatDeployment(d) {
9716
10394
  if (!d) return "";
9717
- const version = d.version !== void 0 ? import_picocolors37.default.yellow(`v${d.version}`) : import_picocolors37.default.dim("v?");
9718
- const env = import_picocolors37.default.dim((d.environment ?? "?").padEnd(10));
10395
+ const version = d.version !== void 0 ? import_picocolors38.default.yellow(`v${d.version}`) : import_picocolors38.default.dim("v?");
10396
+ const env = import_picocolors38.default.dim((d.environment ?? "?").padEnd(10));
9719
10397
  const app = d.webAppName ? `${d.webAppName} ` : "";
9720
- const when = import_picocolors37.default.dim(formatTime2(d.created_at));
9721
- const url = d.url ? " " + import_picocolors37.default.cyan(d.url) : "";
10398
+ const when = import_picocolors38.default.dim(formatTime2(d.created_at));
10399
+ const url = d.url ? " " + import_picocolors38.default.cyan(d.url) : "";
9722
10400
  return `${version} ${env} ${colorStatus(d.status ?? "")} ${app}${when}${url}`;
9723
10401
  }
9724
10402
  function formatTime2(t) {
@@ -9728,7 +10406,7 @@ function formatTime2(t) {
9728
10406
  }
9729
10407
 
9730
10408
  // src/commands/usage.ts
9731
- var import_picocolors38 = __toESM(require_picocolors(), 1);
10409
+ var import_picocolors39 = __toESM(require_picocolors(), 1);
9732
10410
 
9733
10411
  // src/usage.ts
9734
10412
  var NEAR_LIMIT_FRACTION = 0.8;
@@ -9823,7 +10501,7 @@ function registerUsage(program3) {
9823
10501
  function print4(report) {
9824
10502
  const dims = report.dimensions ?? [];
9825
10503
  if (!dims.length) {
9826
- return line(import_picocolors38.default.dim("Nothing to measure for this project yet."));
10504
+ return line(import_picocolors39.default.dim("Nothing to measure for this project yet."));
9827
10505
  }
9828
10506
  const width = Math.max(...dims.map((d) => d.label.length));
9829
10507
  for (const d of dims) {
@@ -9832,23 +10510,23 @@ function print4(report) {
9832
10510
  line("");
9833
10511
  const summary = usageSummary(report);
9834
10512
  const worst = dims.map(usageState);
9835
- if (worst.includes("over")) line(import_picocolors38.default.red(summary));
10513
+ if (worst.includes("over")) line(import_picocolors39.default.red(summary));
9836
10514
  else if (worst.includes("near") || worst.includes("unknown"))
9837
- line(import_picocolors38.default.yellow(summary));
10515
+ line(import_picocolors39.default.yellow(summary));
9838
10516
  else success(summary);
9839
10517
  }
9840
10518
  function gauge(d, _labelWidth) {
9841
10519
  const drawn = bar(d);
9842
- return drawn ? ` ${import_picocolors38.default.dim(drawn)}` : "";
10520
+ return drawn ? ` ${import_picocolors39.default.dim(drawn)}` : "";
9843
10521
  }
9844
10522
  function colour(d) {
9845
10523
  switch (usageState(d)) {
9846
10524
  case "over":
9847
- return d.kind === "hard" ? import_picocolors38.default.red : import_picocolors38.default.yellow;
10525
+ return d.kind === "hard" ? import_picocolors39.default.red : import_picocolors39.default.yellow;
9848
10526
  case "near":
9849
- return import_picocolors38.default.yellow;
10527
+ return import_picocolors39.default.yellow;
9850
10528
  case "unknown":
9851
- return import_picocolors38.default.dim;
10529
+ return import_picocolors39.default.dim;
9852
10530
  default:
9853
10531
  return (s) => s;
9854
10532
  }
@@ -9856,7 +10534,7 @@ function colour(d) {
9856
10534
 
9857
10535
  // src/index.ts
9858
10536
  var pkg = {
9859
- version: true ? "0.6.0" : "0.0.0-dev"
10537
+ version: true ? "0.6.2" : "0.0.0-dev"
9860
10538
  };
9861
10539
  var program2 = new Command();
9862
10540
  program2.name("workser").description(
@@ -9909,9 +10587,14 @@ registerAsk(program2);
9909
10587
  registerSearch(program2);
9910
10588
  registerBoard(program2);
9911
10589
  registerTask(program2);
10590
+ registerGoal(program2);
9912
10591
  registerDecision(program2);
9913
10592
  registerDoc(program2);
9914
10593
  registerDesign(program2);
10594
+ configureOutput({
10595
+ json: process.argv.includes("--json"),
10596
+ quiet: process.argv.includes("--quiet") || process.argv.includes("-q")
10597
+ });
9915
10598
  try {
9916
10599
  assertRoleMayRun(process.argv.slice(2));
9917
10600
  } catch (e) {