@workser/cli 0.6.1 → 0.6.3

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
  {
@@ -5316,7 +5386,7 @@ function buildContext(opts) {
5316
5386
  const endpoint = endpointRaw.replace(/\/+$/, "");
5317
5387
  const mode2 = socketPath ? "daemon" : /^https?:\/\/(127\.0\.0\.1|localhost|\[::1\])(:|\/|$)/i.test(endpoint) ? "daemon" : "cloud";
5318
5388
  const folder = readFolderIdentity(cwd);
5319
- const projectId = opts.project || process.env.WORKSER_PROJECT_ID || folder?.projectId || session.defaultProjectId;
5389
+ const projectId = opts.project || folder?.projectId || process.env.WORKSER_PROJECT_ID || session.defaultProjectId;
5320
5390
  const inThisProject = folder?.projectId === projectId;
5321
5391
  const runId = process.env.WORKSER_RUN_ID || void 0;
5322
5392
  const conversationId = process.env.WORKSER_CONVERSATION_ID || void 0;
@@ -5334,6 +5404,15 @@ function buildContext(opts) {
5334
5404
  mode: mode2,
5335
5405
  cwd,
5336
5406
  projectId,
5407
+ /**
5408
+ * The ORG the cwd belongs to, from the same marker.
5409
+ *
5410
+ * Read here rather than from `WORKSER_ORGANIZATION_ID` for the same reason
5411
+ * as the project above: a user belongs to several, and the env is a
5412
+ * snapshot of the run that spawned this process, not of the folder the
5413
+ * command is standing in.
5414
+ */
5415
+ orgId: inThisProject ? folder?.orgId : void 0,
5337
5416
  projectRoot: inThisProject ? folder?.projectRoot : void 0,
5338
5417
  appId: inThisProject ? folder?.appId : void 0,
5339
5418
  appName: inThisProject ? folder?.appName : void 0,
@@ -5686,13 +5765,61 @@ function registerProject(program3) {
5686
5765
  if (!merged.localPath) {
5687
5766
  line(
5688
5767
  import_picocolors4.default.dim(
5689
- " no folder linked on this computer \u2014 link one in Workser Orbit"
5768
+ ` no code on this computer yet \u2014 run \`workser project sync ${merged.id ?? id}\``
5690
5769
  )
5691
5770
  );
5692
5771
  }
5693
5772
  });
5694
5773
  })
5695
5774
  );
5775
+ project.command("sync [id]").description(
5776
+ "Bring an app's code down to this computer \u2014 safe to run again; never overwrites local work"
5777
+ ).action(
5778
+ action(async ({ ctx, args }) => {
5779
+ const projectId = requireProject(ctx);
5780
+ const appId = args[0] ?? ctx.appId;
5781
+ if (!appId) {
5782
+ throw new Error(
5783
+ "Which app? Pass its id, or run this from inside the app's folder."
5784
+ );
5785
+ }
5786
+ const prepared = await api(
5787
+ ctx,
5788
+ `/v1/app-folders/prepare`,
5789
+ {
5790
+ method: "POST",
5791
+ body: { projectId, webAppId: appId, requireSource: true }
5792
+ }
5793
+ );
5794
+ const localPath = prepared?.localPath;
5795
+ if (!localPath) {
5796
+ throw new Error(
5797
+ "Couldn't make a folder for this app on this computer."
5798
+ );
5799
+ }
5800
+ const seeded = await api(
5801
+ ctx,
5802
+ `/v1/app-folders/seed`,
5803
+ {
5804
+ method: "POST",
5805
+ body: {
5806
+ projectId,
5807
+ webAppId: appId,
5808
+ localPath,
5809
+ requireSource: true
5810
+ }
5811
+ }
5812
+ );
5813
+ ok({ appId, localPath, ...seeded }, () => {
5814
+ if (seeded?.ok === false) {
5815
+ line(import_picocolors4.default.yellow(seeded.message ?? "The code did not arrive."));
5816
+ line(import_picocolors4.default.dim(` folder ${localPath}`));
5817
+ return;
5818
+ }
5819
+ line(`${import_picocolors4.default.green("synced")} ${localPath}`);
5820
+ });
5821
+ })
5822
+ );
5696
5823
  project.command("list").description("List the workspace's projects").action(
5697
5824
  action(async ({ ctx }) => {
5698
5825
  const items = await api(ctx, `/v1/projects`);
@@ -7858,7 +7985,22 @@ var TYPES = [
7858
7985
  "approval",
7859
7986
  "confirmation",
7860
7987
  "file_upload",
7861
- "information"
7988
+ "information",
7989
+ /**
7990
+ * "Add an app to this project, and do it for me."
7991
+ *
7992
+ * NOT an approval with a well-worded message. What the user is agreeing to
7993
+ * is real infrastructure — a repository, hosting, a folder on their machine,
7994
+ * sometimes a database — and they are entitled to see WHICH app before they
7995
+ * agree, which a sentence and a Yes button cannot show. Needs `--app-type`;
7996
+ * `--app-name` is what it will be called.
7997
+ *
7998
+ * You cannot create an app yourself, and that is deliberate: an agent must
7999
+ * not be able to provision things on somebody's account by deciding to. This
8000
+ * asks. Their click is what creates it, and the answer tells you the new
8001
+ * app's id so the rest of your plan can be scoped to it.
8002
+ */
8003
+ "create_app"
7862
8004
  ];
7863
8005
  function registerAsk(program3) {
7864
8006
  program3.command("ask <message>").description("Ask the user a question and wait for their answer").option(
@@ -7870,6 +8012,12 @@ function registerAsk(program3) {
7870
8012
  "a choice (repeat for each; implies --type choice)",
7871
8013
  collect,
7872
8014
  []
8015
+ ).option(
8016
+ "--app-type <type>",
8017
+ "create_app only: web | mobile | api | worker | cron | python"
8018
+ ).option(
8019
+ "--app-name <name>",
8020
+ "create_app only: what the app should be called"
7873
8021
  ).option("--priority <level>", "low | normal | high | urgent", "normal").option(
7874
8022
  "--timeout <seconds>",
7875
8023
  "how long to wait before giving up (default: 600, max: 3600)",
@@ -7896,6 +8044,12 @@ function registerAsk(program3) {
7896
8044
  { code: "bad_request" }
7897
8045
  );
7898
8046
  }
8047
+ if (type === "create_app" && !opts.appType) {
8048
+ throw new WorkserError(
8049
+ "Say which kind of app: `--app-type mobile`.",
8050
+ { code: "bad_request" }
8051
+ );
8052
+ }
7899
8053
  const timeoutSeconds = Number(opts.timeout);
7900
8054
  if (!Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0) {
7901
8055
  throw new WorkserError("--timeout must be a positive number.", {
@@ -7909,6 +8063,8 @@ function registerAsk(program3) {
7909
8063
  title: opts.title || deriveTitle(message),
7910
8064
  message,
7911
8065
  options: options.length ? options : void 0,
8066
+ appType: opts.appType,
8067
+ appName: opts.appName,
7912
8068
  priority: opts.priority,
7913
8069
  timeoutSeconds
7914
8070
  }
@@ -8203,10 +8359,18 @@ function registerTask(program3) {
8203
8359
  action(async ({ ctx, args }) => {
8204
8360
  const id = args[0] || ctx.parentTaskId || resolveTaskId(ctx);
8205
8361
  const row = await api(ctx, `/v1/project-tasks/${encodeURIComponent(id)}`);
8206
- ok(row, () => printTask(row));
8362
+ const goalId = row.goal_id;
8363
+ const goal = goalId ? await api(
8364
+ ctx,
8365
+ `/v1/project-goals/${encodeURIComponent(goalId)}`
8366
+ ).catch(() => null) : null;
8367
+ ok({ ...row, goal }, () => printTask(row, goal));
8207
8368
  })
8208
8369
  );
8209
- 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").action(
8370
+ 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(
8371
+ "--phase <name>",
8372
+ "which slice of that goal \u2014 must match one of its phase names"
8373
+ ).action(
8210
8374
  action(async ({ ctx, args, opts }) => {
8211
8375
  requireProject(ctx);
8212
8376
  if (opts.kind !== void 0) assertOneOf("--kind", opts.kind, KINDS2);
@@ -8219,6 +8383,10 @@ function registerTask(program3) {
8219
8383
  summary: opts.note,
8220
8384
  category: opts.kind,
8221
8385
  labels: opts.label,
8386
+ // The level above this task, when it has one. Most tasks do not —
8387
+ // a goal has to be argued for, never assumed.
8388
+ goalId: opts.goal,
8389
+ phase: opts.phase,
8222
8390
  targets: [
8223
8391
  ...(opts.app ?? []).map((appId) => ({ kind: "app", appId })),
8224
8392
  ...(opts.infra ?? []).map((ref) => ({ kind: "infra", ref }))
@@ -8271,13 +8439,13 @@ function registerTask(program3) {
8271
8439
  });
8272
8440
  })
8273
8441
  );
8274
- const subtask = task.command("subtask").description("The steps a task is broken into");
8442
+ const subtask = task.command("subtask").description("The subtasks a task is broken into");
8275
8443
  subtask.command("add <title>").description(
8276
- 'Add one step, e.g. `workser task subtask add "Build the upload screen" --role web`'
8277
- ).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(
8444
+ 'Add one subtask, e.g. `workser task subtask add "Build the upload screen" --role web`'
8445
+ ).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(
8278
8446
  "--app <id...>",
8279
- "the app this step is for \u2014 one id runs the step inside that app's folder; leave it off and the step runs at the project, seeing every app"
8280
- ).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(
8447
+ "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"
8448
+ ).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(
8281
8449
  action(async ({ ctx, args, opts }) => {
8282
8450
  const parent = resolveTaskId(ctx, opts.task);
8283
8451
  if (opts.role !== void 0) assertOneOf("--role", opts.role, ROLES);
@@ -8304,21 +8472,21 @@ function registerTask(program3) {
8304
8472
  });
8305
8473
  })
8306
8474
  );
8307
- subtask.command("list [taskId]").description("The steps of a task, in order").action(
8475
+ subtask.command("list [taskId]").description("The subtasks of a task, in order").action(
8308
8476
  action(async ({ ctx, args }) => {
8309
8477
  const id = resolveTaskId(ctx, args[0]);
8310
8478
  const row = await api(ctx, `/v1/project-tasks/${encodeURIComponent(id)}`);
8311
8479
  const rows = row.subtasks ?? [];
8312
8480
  ok(rows, () => {
8313
8481
  if (!rows.length) {
8314
- line(import_picocolors28.default.dim("No steps yet."));
8482
+ line(import_picocolors28.default.dim("No subtasks yet."));
8315
8483
  return;
8316
8484
  }
8317
8485
  rows.forEach((r, i) => line(formatSubtask(r, i + 1)));
8318
8486
  });
8319
8487
  })
8320
8488
  );
8321
- 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(
8489
+ 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(
8322
8490
  action(async ({ ctx, args, opts }) => {
8323
8491
  if (opts.role !== void 0) assertOneOf("--role", opts.role, ROLES);
8324
8492
  if (opts.kind !== void 0) assertOneOf("--kind", opts.kind, KINDS2);
@@ -8339,7 +8507,7 @@ function registerTask(program3) {
8339
8507
  ok(row, () => line(`${import_picocolors28.default.green("updated")} ${import_picocolors28.default.bold(row.title)}`));
8340
8508
  })
8341
8509
  );
8342
- subtask.command("remove <id>").description("Drop a step from the plan (only before the work starts)").action(
8510
+ subtask.command("remove <id>").description("Remove a subtask (only before the work starts)").action(
8343
8511
  action(async ({ ctx, args }) => {
8344
8512
  await api(ctx, `/v1/project-tasks/${encodeURIComponent(args[0])}`, {
8345
8513
  method: "DELETE"
@@ -8359,7 +8527,7 @@ function registerTask(program3) {
8359
8527
  })
8360
8528
  );
8361
8529
  task.command("resume [id]").description(
8362
- "Put a step the owner stopped back in the queue \u2014 only when they ask you to carry on"
8530
+ "Put a task the owner stopped back in the queue \u2014 only when they ask you to carry on"
8363
8531
  ).action(
8364
8532
  action(async ({ ctx, args }) => {
8365
8533
  const id = resolveTaskId(ctx, args[0]);
@@ -8368,12 +8536,22 @@ function registerTask(program3) {
8368
8536
  `/v1/project-tasks/${encodeURIComponent(id)}/retry`,
8369
8537
  { method: "POST" }
8370
8538
  );
8371
- ok(
8372
- row,
8373
- () => line(
8374
- 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")
8375
- )
8376
- );
8539
+ ok(row, () => {
8540
+ if (!row?.queued) {
8541
+ line(import_picocolors28.default.yellow("could not resume it \u2014 check the step id"));
8542
+ return;
8543
+ }
8544
+ const started = row.started ?? 0;
8545
+ if (started > 0) {
8546
+ line(
8547
+ `${import_picocolors28.default.green("resumed")} \u2014 ${started} step${started === 1 ? "" : "s"} started`
8548
+ );
8549
+ return;
8550
+ }
8551
+ line(
8552
+ `${import_picocolors28.default.green("resumed")} \u2014 it is queued, but nothing started yet. Check the plan is approved and that no step is blocking the rest.`
8553
+ );
8554
+ });
8377
8555
  })
8378
8556
  );
8379
8557
  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(
@@ -8407,6 +8585,30 @@ function registerTask(program3) {
8407
8585
  ok(row, () => line(import_picocolors28.default.green("approved \u2014 you may start")));
8408
8586
  })
8409
8587
  );
8588
+ task.command("start [id]").description(
8589
+ "Start the steps this task is ready to run \u2014 only works once the owner has approved the plan"
8590
+ ).action(
8591
+ action(async ({ ctx, args }) => {
8592
+ const id = resolveTaskId(ctx, args[0]);
8593
+ const row = await api(
8594
+ ctx,
8595
+ `/v1/project-tasks/${encodeURIComponent(id)}/start`,
8596
+ { method: "POST" }
8597
+ );
8598
+ ok(row, () => {
8599
+ const started = row?.started ?? null;
8600
+ if (started && started > 0) {
8601
+ line(
8602
+ import_picocolors28.default.green(
8603
+ `started ${started} step${started === 1 ? "" : "s"} \u2014 they are running now`
8604
+ )
8605
+ );
8606
+ return;
8607
+ }
8608
+ line(import_picocolors28.default.yellow(row?.note ?? "Nothing started."));
8609
+ });
8610
+ })
8611
+ );
8410
8612
  task.command("approval").description("Ask the owner to approve the plan, or record their decision").argument("<request|approve|decline>").option("--task <id>", "defaults to the task this run is inside").option("--note <text>", "why").action(
8411
8613
  action(async ({ ctx, args, opts }) => {
8412
8614
  const id = resolveTaskId(ctx, opts.task);
@@ -8442,7 +8644,7 @@ function registerTask(program3) {
8442
8644
  ok(row, () => line(`${import_picocolors28.default.green(row.approval_state)} ${import_picocolors28.default.bold(row.title)}`));
8443
8645
  })
8444
8646
  );
8445
- 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(
8647
+ 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(
8446
8648
  action(async ({ ctx, args, opts }) => {
8447
8649
  const id = resolveTaskId(ctx, args[0]);
8448
8650
  await api(ctx, `/v1/project-tasks/${encodeURIComponent(id)}`, {
@@ -8483,7 +8685,7 @@ async function resolveDeps(ctx, parentId, given) {
8483
8685
  const id = byKey.get(g);
8484
8686
  if (!id) {
8485
8687
  throw new WorkserError(
8486
- `"${g}" is not a step of this task. Run \`workser task subtask list\` to see them.`,
8688
+ `"${g}" is not a subtask of this task. Run \`workser task subtask list\` to see them.`,
8487
8689
  { code: "bad_request" }
8488
8690
  );
8489
8691
  }
@@ -8515,11 +8717,12 @@ function formatSubtask(r, index) {
8515
8717
  return `${head}
8516
8718
  ${import_picocolors28.default.dim(wrapped)}`;
8517
8719
  }
8518
- function printTask(row) {
8720
+ function printTask(row, goal) {
8519
8721
  line(`${import_picocolors28.default.bold(row.title)} ${import_picocolors28.default.dim(row.key ?? row.id)}`);
8520
8722
  line(`${statusTag2(row.status)} approval: ${row.approval_state}`);
8521
8723
  if (row.summary) line(`
8522
8724
  ${row.summary}`);
8725
+ if (goal) printGoalContext(row, goal);
8523
8726
  if (row.targets?.length) {
8524
8727
  line(
8525
8728
  `
@@ -8528,22 +8731,52 @@ touches: ${row.targets.map((t) => t.appName ?? t.ref ?? t.kind).join(", ")}`
8528
8731
  }
8529
8732
  if (row.subtasks?.length) {
8530
8733
  line(`
8531
- ${import_picocolors28.default.bold("steps")}`);
8734
+ ${import_picocolors28.default.bold("subtasks")}`);
8532
8735
  row.subtasks.forEach((s, i) => line(formatSubtask(s, i + 1)));
8533
8736
  line(
8534
8737
  import_picocolors28.default.dim(
8535
8738
  `
8536
- Run \`workser artifact list\` to see what these steps produced,`
8739
+ Run \`workser artifact list\` to see what these subtasks produced,`
8537
8740
  )
8538
8741
  );
8539
8742
  line(
8540
8743
  import_picocolors28.default.dim(
8541
- `or \`workser artifact list --step <id>\` for one step's output alone.`
8744
+ `or \`workser artifact list --step <id>\` for one subtask's output alone.`
8542
8745
  )
8543
8746
  );
8544
8747
  } else {
8545
- line(import_picocolors28.default.dim("\nNo steps yet."));
8748
+ line(import_picocolors28.default.dim("\nNo subtasks yet."));
8749
+ }
8750
+ }
8751
+ function printGoalContext(row, goal) {
8752
+ const mine = row.phase ?? null;
8753
+ line(`
8754
+ ${import_picocolors28.default.bold("part of")}: ${goal.title}`);
8755
+ if (goal.outcome) line(import_picocolors28.default.dim(`done means: ${goal.outcome}`));
8756
+ const progress = goal.progress ?? [];
8757
+ for (const p of progress) {
8758
+ const where = p.total === 0 ? "not started" : `${p.done} of ${p.total} done`;
8759
+ const here = mine && p.name === mine ? import_picocolors28.default.cyan(" <- this task") : "";
8760
+ const mark = p.state === "done" ? import_picocolors28.default.green("*") : import_picocolors28.default.dim("-");
8761
+ line(` ${mark} ${p.name} \u2014 ${where}${here}`);
8762
+ }
8763
+ const next = progress.find((p) => p.state !== "done");
8764
+ const running = progress.some((p) => p.state === "working");
8765
+ if (next && !running) {
8766
+ line(
8767
+ import_picocolors28.default.dim(
8768
+ `
8769
+ Nothing is running. The next part waiting is "${next.name}" \u2014 offer it to them in a sentence rather than starting it unasked.`
8770
+ )
8771
+ );
8772
+ } else if (!next) {
8773
+ line(import_picocolors28.default.dim("\nEvery part of this plan is done. Say so, and offer to wrap it up."));
8546
8774
  }
8775
+ line(
8776
+ import_picocolors28.default.dim(
8777
+ "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."
8778
+ )
8779
+ );
8547
8780
  }
8548
8781
  function statusTag2(status) {
8549
8782
  switch (status) {
@@ -8562,6 +8795,219 @@ function statusTag2(status) {
8562
8795
  }
8563
8796
  }
8564
8797
 
8798
+ // src/commands/goal.ts
8799
+ var import_picocolors29 = __toESM(require_picocolors(), 1);
8800
+ var STATUSES3 = ["proposed", "agreed", "working", "delivered", "abandoned"];
8801
+ function registerGoal(program3) {
8802
+ const goal = program3.command("goal").description("Business goals and the phases that deliver them");
8803
+ goal.command("list").description("What this project is working towards").action(
8804
+ action(async ({ ctx }) => {
8805
+ requireProject(ctx);
8806
+ const rows = await api(ctx, "/v1/project-goals", {
8807
+ query: { projectId: ctx.projectId }
8808
+ }) ?? [];
8809
+ ok(rows, () => {
8810
+ if (!rows.length) {
8811
+ line(import_picocolors29.default.dim("No goals yet \u2014 every task here stands on its own."));
8812
+ return;
8813
+ }
8814
+ for (const g of rows) line(formatGoal(g));
8815
+ });
8816
+ })
8817
+ );
8818
+ goal.command("show <id>").description("One goal: its phases, and how far each has got").action(
8819
+ action(async ({ ctx, args }) => {
8820
+ const row = await api(
8821
+ ctx,
8822
+ `/v1/project-goals/${encodeURIComponent(String(args[0]))}`
8823
+ );
8824
+ ok(row, () => printGoal(row));
8825
+ })
8826
+ );
8827
+ goal.command("create <title>").description(
8828
+ "Propose a business goal and the phases that deliver it \u2014 the owner agrees the shape before any task exists"
8829
+ ).option(
8830
+ "--outcome <text>",
8831
+ "what 'done' means for the whole thing, in the owner's words"
8832
+ ).option(
8833
+ "--phase <name...>",
8834
+ "ordered phase names \u2014 each must deliver something the owner would notice"
8835
+ ).option(
8836
+ "--criteria <json>",
8837
+ `what "done" means per phase, in the OWNER's words: '{"Checkout":["A customer can pay by card and gets a receipt"]}'`
8838
+ ).action(
8839
+ action(async ({ ctx, args, opts }) => {
8840
+ requireProject(ctx);
8841
+ const phases = opts.phase ?? [];
8842
+ if (phases.length < 2) {
8843
+ throw new WorkserError(
8844
+ "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`."
8845
+ );
8846
+ }
8847
+ if (phases.length > 6) {
8848
+ throw new WorkserError(
8849
+ "More than six phases is a conversation, not a plan. Propose the first few and agree the rest as you go."
8850
+ );
8851
+ }
8852
+ let criteria = {};
8853
+ if (opts.criteria) {
8854
+ try {
8855
+ criteria = JSON.parse(opts.criteria);
8856
+ } catch {
8857
+ throw new WorkserError(
8858
+ `--criteria must be JSON mapping each phase name to a list of sentences, e.g. '{"Checkout":["A customer can pay by card"]}'`
8859
+ );
8860
+ }
8861
+ const unknown = Object.keys(criteria).filter(
8862
+ (name) => !phases.includes(name)
8863
+ );
8864
+ if (unknown.length) {
8865
+ throw new WorkserError(
8866
+ `--criteria names phases that aren't in this goal: ${unknown.join(", ")}. Phases are: ${phases.join(", ")}.`
8867
+ );
8868
+ }
8869
+ }
8870
+ const row = await api(ctx, "/v1/project-goals", {
8871
+ body: {
8872
+ projectId: ctx.projectId,
8873
+ title: args[0],
8874
+ outcome: opts.outcome,
8875
+ phases: phases.map((name) => ({
8876
+ name,
8877
+ criteria: (criteria[name] ?? []).map((text, i) => ({
8878
+ id: `c${i + 1}`,
8879
+ text,
8880
+ met: null
8881
+ }))
8882
+ })),
8883
+ channelId: ctx.projectChannelId,
8884
+ sourceMessageId: ctx.projectChannelMessageId
8885
+ }
8886
+ });
8887
+ ok(row, () => {
8888
+ success(`Proposed ${import_picocolors29.default.bold(row?.title ?? "goal")}`);
8889
+ printGoal(row);
8890
+ line(
8891
+ import_picocolors29.default.dim(
8892
+ "\nNothing has been created yet. The owner agrees the shape first."
8893
+ )
8894
+ );
8895
+ });
8896
+ })
8897
+ );
8898
+ 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(
8899
+ action(async ({ ctx, args, opts }) => {
8900
+ const chosen = [opts.pass, opts.fail, opts.reset].filter(Boolean);
8901
+ if (chosen.length !== 1) {
8902
+ throw new WorkserError(
8903
+ "Pass exactly one of --pass, --fail or --reset."
8904
+ );
8905
+ }
8906
+ if (opts.pass && !opts.note) {
8907
+ throw new WorkserError(
8908
+ "--note is required on a pass: say what you checked. A tick the owner cannot verify is worse than no tick."
8909
+ );
8910
+ }
8911
+ const met = opts.pass ? true : opts.fail ? false : null;
8912
+ const row = await api(
8913
+ ctx,
8914
+ `/v1/project-goals/${encodeURIComponent(String(args[0]))}/criteria/${encodeURIComponent(String(args[1]))}`,
8915
+ { method: "POST", body: { phase: opts.phase, met, note: opts.note } }
8916
+ );
8917
+ ok(
8918
+ row,
8919
+ () => line(
8920
+ 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(
8921
+ "couldn't record it \u2014 check the goal id, the phase name and the criterion id"
8922
+ )
8923
+ )
8924
+ );
8925
+ })
8926
+ );
8927
+ 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(
8928
+ action(async ({ ctx, args, opts }) => {
8929
+ if (opts.status && !STATUSES3.includes(opts.status)) {
8930
+ throw new WorkserError(
8931
+ `Unknown status "${opts.status}". Use one of: ${STATUSES3.join(", ")}.`
8932
+ );
8933
+ }
8934
+ const row = await api(
8935
+ ctx,
8936
+ `/v1/project-goals/${encodeURIComponent(String(args[0]))}`,
8937
+ {
8938
+ method: "PATCH",
8939
+ body: {
8940
+ title: opts.title,
8941
+ outcome: opts.outcome,
8942
+ phases: opts.phase,
8943
+ status: opts.status
8944
+ }
8945
+ }
8946
+ );
8947
+ ok(row, () => printGoal(row));
8948
+ })
8949
+ );
8950
+ }
8951
+ function appScope(g) {
8952
+ const n = g.appIds?.length ?? 0;
8953
+ if (!n) return "";
8954
+ return import_picocolors29.default.dim(` ${n} part${n === 1 ? "" : "s"} of the system`);
8955
+ }
8956
+ function formatGoal(g) {
8957
+ const where = g.currentPhase && g.status !== "delivered" ? import_picocolors29.default.dim(` now: ${g.currentPhase}`) : "";
8958
+ const count = g.taskTotal != null ? import_picocolors29.default.dim(` ${g.taskDone}/${g.taskTotal} done`) : "";
8959
+ return `${statusTag3(g.status)} ${g.title}${appScope(g)}${where}${count} ${import_picocolors29.default.dim(g.id)}`;
8960
+ }
8961
+ function printGoal(g) {
8962
+ if (!g) return;
8963
+ line(`${import_picocolors29.default.bold(g.title)} ${import_picocolors29.default.dim(g.id)}`);
8964
+ line(statusTag3(g.status));
8965
+ if (g.outcome) line(`
8966
+ done means: ${g.outcome}`);
8967
+ const progress = g.progress ?? [];
8968
+ if (!progress.length) {
8969
+ line(import_picocolors29.default.dim("\nNo phases agreed yet."));
8970
+ return;
8971
+ }
8972
+ line(`
8973
+ ${import_picocolors29.default.bold("phases")}`);
8974
+ for (const p of progress) {
8975
+ const bar2 = p.total > 0 ? `${p.done}/${p.total} done` : import_picocolors29.default.dim("nothing filed yet");
8976
+ const mark = p.state === "done" ? import_picocolors29.default.green("done") : p.state === "working" ? import_picocolors29.default.blue("working") : import_picocolors29.default.dim("waiting");
8977
+ line(
8978
+ ` ${import_picocolors29.default.dim(String(p.index).padStart(2, "0"))} ${p.name} ${mark} ${import_picocolors29.default.dim(bar2)}`
8979
+ );
8980
+ for (const c of p.criteria ?? []) {
8981
+ const tick = c.met === true ? import_picocolors29.default.green("\u2713") : c.met === false ? import_picocolors29.default.red("\u2717") : import_picocolors29.default.dim("\xB7");
8982
+ line(` ${tick} ${c.text} ${import_picocolors29.default.dim(c.id)}`);
8983
+ if (c.note) line(import_picocolors29.default.dim(` ${c.note}`));
8984
+ }
8985
+ }
8986
+ const current = progress.find((p) => p.name === g.currentPhase);
8987
+ if (current) {
8988
+ line(
8989
+ import_picocolors29.default.dim(
8990
+ `
8991
+ ${current.name} \u2014 ${current.done} of ${current.total} done (phase ${current.index} of ${progress.length}).`
8992
+ )
8993
+ );
8994
+ }
8995
+ }
8996
+ function statusTag3(status) {
8997
+ switch (status) {
8998
+ case "agreed":
8999
+ return import_picocolors29.default.cyan("[agreed]");
9000
+ case "working":
9001
+ return import_picocolors29.default.blue("[working]");
9002
+ case "delivered":
9003
+ return import_picocolors29.default.green("[delivered]");
9004
+ case "abandoned":
9005
+ return import_picocolors29.default.dim("[abandoned]");
9006
+ default:
9007
+ return import_picocolors29.default.yellow("[proposed]");
9008
+ }
9009
+ }
9010
+
8565
9011
  // src/role-guard.ts
8566
9012
  var READS = [
8567
9013
  // `requirement` sits beside `doc` and `decision` because it is the same kind
@@ -8599,7 +9045,19 @@ var READS = [
8599
9045
  // Reading the plan and what is used against it. An agent proposing "add
8600
9046
  // another project" can only sensibly propose it if it can find out the plan
8601
9047
  // allows two and two already exist.
8602
- "usage"
9048
+ "usage",
9049
+ // RECORDING WHAT YOU PRODUCED IS NOT CHANGING THE PROJECT.
9050
+ // Every dispatched role is told, in its own preamble, to run `workser
9051
+ // artifact list` before non-trivial work and `workser artifact add` when it
9052
+ // finishes something the owner should get. Five roles — pm, qa, security,
9053
+ // analyst, sre — were then refused the verb here, so the instruction and the
9054
+ // guard contradicted each other: the agent kept trying to obey an order this
9055
+ // file would not let it obey, and burned its run doing it. It belongs beside
9056
+ // `doc`, `decision` and `board`, which are already in this list and also
9057
+ // have `create` subcommands — these write the project's RECORD, not the
9058
+ // project. What stops a reviewer editing code is its filesystem mode, and
9059
+ // that is untouched.
9060
+ "artifact"
8603
9061
  ];
8604
9062
  var BUILDS = [
8605
9063
  ...READS,
@@ -8608,7 +9066,6 @@ var BUILDS = [
8608
9066
  "db",
8609
9067
  "storage",
8610
9068
  "checkpoint",
8611
- "artifact",
8612
9069
  "image",
8613
9070
  "design",
8614
9071
  "ask",
@@ -8644,6 +9101,33 @@ var NEVER = {
8644
9101
  // proposed has removed the only gate this product has.
8645
9102
  "task approval": "Only the owner can approve a plan."
8646
9103
  };
9104
+ var READ_PAIRS = /* @__PURE__ */ new Set([
9105
+ // The project's own brand and design notes.
9106
+ "design show",
9107
+ // What exists, and what it is wired to.
9108
+ "app list",
9109
+ "app tools",
9110
+ // Shape, never contents-by-arbitrary-SQL.
9111
+ "db list",
9112
+ "db tables",
9113
+ "db schema",
9114
+ // What is stored, not what is in it.
9115
+ "storage list",
9116
+ "storage ls",
9117
+ "checkpoint list",
9118
+ "workflow list",
9119
+ "workflow get",
9120
+ "workflow runs",
9121
+ "workflow nodes",
9122
+ "tool list",
9123
+ // The business data an analyst exists to read.
9124
+ "business resources",
9125
+ "business list",
9126
+ "business get",
9127
+ // The database service's own inventory.
9128
+ "neon status",
9129
+ "neon list"
9130
+ ]);
8647
9131
  function assertRoleMayRun(argv) {
8648
9132
  const role = (process.env.WORKSER_ROLE ?? "").trim();
8649
9133
  if (!role) return;
@@ -8654,6 +9138,7 @@ function assertRoleMayRun(argv) {
8654
9138
  if (NEVER[pair] && !(pair === "task approval" && (commandArgv[2] === "request" || !commandArgv[2]))) {
8655
9139
  throw new WorkserError(NEVER[pair], { code: "role_forbidden" });
8656
9140
  }
9141
+ if (READ_PAIRS.has(pair)) return;
8657
9142
  const allowed = ROLE_VERBS[role];
8658
9143
  const list = allowed ?? READS;
8659
9144
  if (!list.includes(verb)) {
@@ -8694,7 +9179,7 @@ function stripLeadingGlobalOptions(argv) {
8694
9179
  }
8695
9180
 
8696
9181
  // src/commands/decision.ts
8697
- var import_picocolors29 = __toESM(require_picocolors(), 1);
9182
+ var import_picocolors30 = __toESM(require_picocolors(), 1);
8698
9183
  function registerDecision(program3) {
8699
9184
  const decision = program3.command("decision").description("Read and record the project's architecture decisions");
8700
9185
  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(
@@ -8707,11 +9192,11 @@ function registerDecision(program3) {
8707
9192
  rows = applyLimit(rows, opts.limit);
8708
9193
  ok(rows, () => {
8709
9194
  if (!rows.length) {
8710
- line(import_picocolors29.default.dim("No decisions recorded yet."));
9195
+ line(import_picocolors30.default.dim("No decisions recorded yet."));
8711
9196
  return;
8712
9197
  }
8713
9198
  for (const r of rows) {
8714
- line(`${import_picocolors29.default.dim(r.id)} ${import_picocolors29.default.dim(shortDate(r.createdAt))} ${r.title}`);
9199
+ line(`${import_picocolors30.default.dim(r.id)} ${import_picocolors30.default.dim(shortDate(r.createdAt))} ${r.title}`);
8715
9200
  line(` ${truncate(r.decision, 100)}`);
8716
9201
  }
8717
9202
  });
@@ -8725,16 +9210,16 @@ function registerDecision(program3) {
8725
9210
  `/v1/projects/${projectId}/architecture-decisions/${args[0]}`
8726
9211
  );
8727
9212
  ok(row, () => {
8728
- line(`${import_picocolors29.default.bold(row.title)} ${import_picocolors29.default.dim(row.id)}`);
8729
- line(import_picocolors29.default.dim(`${row.status} \xB7 ${shortDate(row.createdAt)}`));
9213
+ line(`${import_picocolors30.default.bold(row.title)} ${import_picocolors30.default.dim(row.id)}`);
9214
+ line(import_picocolors30.default.dim(`${row.status} \xB7 ${shortDate(row.createdAt)}`));
8730
9215
  line(`
8731
- ${import_picocolors29.default.bold("Context")}
9216
+ ${import_picocolors30.default.bold("Context")}
8732
9217
  ${row.context}`);
8733
9218
  line(`
8734
- ${import_picocolors29.default.bold("Decision")}
9219
+ ${import_picocolors30.default.bold("Decision")}
8735
9220
  ${row.decision}`);
8736
9221
  if (row.consequences) line(`
8737
- ${import_picocolors29.default.bold("Consequences")}
9222
+ ${import_picocolors30.default.bold("Consequences")}
8738
9223
  ${row.consequences}`);
8739
9224
  });
8740
9225
  })
@@ -8763,7 +9248,7 @@ ${row.consequences}`);
8763
9248
  refId: row?.id,
8764
9249
  output: { decision: row }
8765
9250
  });
8766
- ok(row, () => line(`Recorded decision ${import_picocolors29.default.bold(row?.id ?? "")} \u2014 ${title}`));
9251
+ ok(row, () => line(`Recorded decision ${import_picocolors30.default.bold(row?.id ?? "")} \u2014 ${title}`));
8767
9252
  })
8768
9253
  );
8769
9254
  const requirement = program3.command("requirement").description("Read and record the project's requirements");
@@ -8775,11 +9260,11 @@ ${row.consequences}`);
8775
9260
  rows = applyLimit(rows, opts.limit);
8776
9261
  ok(rows, () => {
8777
9262
  if (!rows.length) {
8778
- line(import_picocolors29.default.dim("No requirements recorded yet."));
9263
+ line(import_picocolors30.default.dim("No requirements recorded yet."));
8779
9264
  return;
8780
9265
  }
8781
9266
  for (const r of rows) {
8782
- line(`${import_picocolors29.default.dim(r.id)} ${r.status.padEnd(9)} ${r.title}`);
9267
+ line(`${import_picocolors30.default.dim(r.id)} ${r.status.padEnd(9)} ${r.title}`);
8783
9268
  }
8784
9269
  });
8785
9270
  })
@@ -8792,8 +9277,8 @@ ${row.consequences}`);
8792
9277
  `/v1/projects/${projectId}/requirements/${args[0]}`
8793
9278
  );
8794
9279
  ok(row, () => {
8795
- line(`${import_picocolors29.default.bold(row.title)} ${import_picocolors29.default.dim(row.id)}`);
8796
- line(import_picocolors29.default.dim(`${row.status} \xB7 ${shortDate(row.createdAt)}`));
9280
+ line(`${import_picocolors30.default.bold(row.title)} ${import_picocolors30.default.dim(row.id)}`);
9281
+ line(import_picocolors30.default.dim(`${row.status} \xB7 ${shortDate(row.createdAt)}`));
8797
9282
  line(`
8798
9283
  ${row.body}`);
8799
9284
  });
@@ -8819,7 +9304,7 @@ ${row.body}`);
8819
9304
  refId: row?.id,
8820
9305
  output: { requirement: row }
8821
9306
  });
8822
- ok(row, () => line(`Recorded requirement ${import_picocolors29.default.bold(row?.id ?? "")} \u2014 ${title}`));
9307
+ ok(row, () => line(`Recorded requirement ${import_picocolors30.default.bold(row?.id ?? "")} \u2014 ${title}`));
8823
9308
  })
8824
9309
  );
8825
9310
  requirement.command("update <id>").description(
@@ -8848,7 +9333,7 @@ ${row.body}`);
8848
9333
  code: "bad_request"
8849
9334
  });
8850
9335
  }
8851
- ok(row, () => line(`Updated requirement ${import_picocolors29.default.bold(row.id)} \u2014 ${row.title} (${row.status})`));
9336
+ ok(row, () => line(`Updated requirement ${import_picocolors30.default.bold(row.id)} \u2014 ${row.title} (${row.status})`));
8852
9337
  })
8853
9338
  );
8854
9339
  }
@@ -8871,7 +9356,7 @@ function shortDate(iso) {
8871
9356
  }
8872
9357
 
8873
9358
  // src/commands/doc.ts
8874
- var import_picocolors30 = __toESM(require_picocolors(), 1);
9359
+ var import_picocolors31 = __toESM(require_picocolors(), 1);
8875
9360
 
8876
9361
  // src/mermaid-fences.ts
8877
9362
  function hasDiagram(code) {
@@ -8916,13 +9401,13 @@ function registerDoc(program3) {
8916
9401
  }) ?? [];
8917
9402
  ok(rows, () => {
8918
9403
  if (!rows.length) {
8919
- line(import_picocolors30.default.dim("No documents yet."));
9404
+ line(import_picocolors31.default.dim("No documents yet."));
8920
9405
  return;
8921
9406
  }
8922
9407
  for (const r of rows) {
8923
- const link = r.workItemId ? import_picocolors30.default.dim(` \u21B3 ${r.workItemId}`) : "";
8924
- const file = r.filePath ? import_picocolors30.default.dim(` ${r.filePath}`) : "";
8925
- line(`${import_picocolors30.default.dim(r.id)} ${r.title}${link}${file}`);
9408
+ const link = r.workItemId ? import_picocolors31.default.dim(` \u21B3 ${r.workItemId}`) : "";
9409
+ const file = r.filePath ? import_picocolors31.default.dim(` ${r.filePath}`) : "";
9410
+ line(`${import_picocolors31.default.dim(r.id)} ${r.title}${link}${file}`);
8926
9411
  }
8927
9412
  });
8928
9413
  })
@@ -8936,17 +9421,17 @@ function registerDoc(program3) {
8936
9421
  );
8937
9422
  if (opts.markdown) {
8938
9423
  ok({ id: row.id, title: row.title, filePath: row.filePath }, () => {
8939
- line(`${import_picocolors30.default.bold(row.title)} ${import_picocolors30.default.dim(row.id)}`);
9424
+ line(`${import_picocolors31.default.bold(row.title)} ${import_picocolors31.default.dim(row.id)}`);
8940
9425
  line(
8941
- 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.")
9426
+ 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.")
8942
9427
  );
8943
9428
  });
8944
9429
  return;
8945
9430
  }
8946
9431
  ok(row, () => {
8947
- line(`${import_picocolors30.default.bold(row.title)} ${import_picocolors30.default.dim(row.id)}`);
8948
- if (row.workItemId) line(import_picocolors30.default.dim(`linked to work item ${row.workItemId}`));
8949
- if (row.filePath) line(import_picocolors30.default.dim(`markdown mirror: ${row.filePath}`));
9432
+ line(`${import_picocolors31.default.bold(row.title)} ${import_picocolors31.default.dim(row.id)}`);
9433
+ if (row.workItemId) line(import_picocolors31.default.dim(`linked to work item ${row.workItemId}`));
9434
+ if (row.filePath) line(import_picocolors31.default.dim(`markdown mirror: ${row.filePath}`));
8950
9435
  line("");
8951
9436
  line(row.contentJson);
8952
9437
  });
@@ -8975,7 +9460,7 @@ function registerDoc(program3) {
8975
9460
  refId: row?.id,
8976
9461
  output: { document: row }
8977
9462
  });
8978
- ok(row, () => line(`Created document ${import_picocolors30.default.bold(row?.id ?? "")} \u2014 ${title}`));
9463
+ ok(row, () => line(`Created document ${import_picocolors31.default.bold(row?.id ?? "")} \u2014 ${title}`));
8979
9464
  })
8980
9465
  );
8981
9466
  doc.command("diagram <id>").description(
@@ -9001,19 +9486,19 @@ function registerDoc(program3) {
9001
9486
  diagrams: diagrams.map((code) => ({ kind: diagramKind(code), code }))
9002
9487
  };
9003
9488
  ok(payload, () => {
9004
- line(`${import_picocolors30.default.bold(row.title)} ${import_picocolors30.default.dim(row.id)}`);
9489
+ line(`${import_picocolors31.default.bold(row.title)} ${import_picocolors31.default.dim(row.id)}`);
9005
9490
  if (markdown === null) {
9006
9491
  line(
9007
- import_picocolors30.default.dim(
9492
+ import_picocolors31.default.dim(
9008
9493
  row.filePath ? `Could not read ${row.filePath} from this folder.` : "This document has no markdown mirror on disk yet."
9009
9494
  )
9010
9495
  );
9011
9496
  } else if (!diagrams.length) {
9012
- line(import_picocolors30.default.dim("No diagrams in this document."));
9497
+ line(import_picocolors31.default.dim("No diagrams in this document."));
9013
9498
  } else {
9014
9499
  for (const [i, code] of diagrams.entries()) {
9015
9500
  const kind = diagramKind(code) ?? "diagram";
9016
- line(`${import_picocolors30.default.dim(String(i + 1))} ${kind} ${import_picocolors30.default.dim(`${code.split("\n").length} lines`)}`);
9501
+ line(`${import_picocolors31.default.dim(String(i + 1))} ${kind} ${import_picocolors31.default.dim(`${code.split("\n").length} lines`)}`);
9017
9502
  }
9018
9503
  }
9019
9504
  });
@@ -9049,7 +9534,7 @@ function registerDoc(program3) {
9049
9534
  code: "bad_request"
9050
9535
  });
9051
9536
  }
9052
- ok(row, () => line(`Updated document ${import_picocolors30.default.bold(row.id)} \u2014 ${row.title}`));
9537
+ ok(row, () => line(`Updated document ${import_picocolors31.default.bold(row.id)} \u2014 ${row.title}`));
9053
9538
  })
9054
9539
  );
9055
9540
  }
@@ -9063,7 +9548,7 @@ function readMirror(cwd, filePath) {
9063
9548
  }
9064
9549
 
9065
9550
  // src/commands/design.ts
9066
- var import_picocolors31 = __toESM(require_picocolors(), 1);
9551
+ var import_picocolors32 = __toESM(require_picocolors(), 1);
9067
9552
  function registerDesign(program3) {
9068
9553
  const design = program3.command("design").description("Read the project's brand (colours, fonts, logo)");
9069
9554
  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(
@@ -9077,11 +9562,11 @@ function registerDesign(program3) {
9077
9562
  if (opts.raw) {
9078
9563
  ok(files, () => {
9079
9564
  if (!files.length) {
9080
- line(import_picocolors31.default.dim("No brand set for this project."));
9565
+ line(import_picocolors32.default.dim("No brand set for this project."));
9081
9566
  return;
9082
9567
  }
9083
9568
  for (const f of files) {
9084
- line(import_picocolors31.default.bold(f.path));
9569
+ line(import_picocolors32.default.bold(f.path));
9085
9570
  line(f.contents);
9086
9571
  line("");
9087
9572
  }
@@ -9098,21 +9583,21 @@ function registerDesign(program3) {
9098
9583
  } : { hasBrand: false, colors: {}, fonts: {}, brand: {}, files: [] };
9099
9584
  ok(summary, () => {
9100
9585
  if (!tokens) {
9101
- line(import_picocolors31.default.dim("No brand set for this project \u2014 choose sensible styling yourself."));
9586
+ line(import_picocolors32.default.dim("No brand set for this project \u2014 choose sensible styling yourself."));
9102
9587
  return;
9103
9588
  }
9104
9589
  for (const [name, value] of Object.entries(tokens.brand)) {
9105
- line(`${import_picocolors31.default.dim(name.padEnd(12))} ${value}`);
9590
+ line(`${import_picocolors32.default.dim(name.padEnd(12))} ${value}`);
9106
9591
  }
9107
9592
  for (const [name, value] of Object.entries(tokens.color)) {
9108
- line(`${import_picocolors31.default.dim(`color.${name}`.padEnd(12))} ${value}`);
9593
+ line(`${import_picocolors32.default.dim(`color.${name}`.padEnd(12))} ${value}`);
9109
9594
  }
9110
9595
  for (const [name, value] of Object.entries(tokens.font)) {
9111
- line(`${import_picocolors31.default.dim(`font.${name}`.padEnd(12))} ${value}`);
9596
+ line(`${import_picocolors32.default.dim(`font.${name}`.padEnd(12))} ${value}`);
9112
9597
  }
9113
9598
  line("");
9114
9599
  line(
9115
- import_picocolors31.default.dim(
9600
+ import_picocolors32.default.dim(
9116
9601
  `Generated into the working tree as ${files.map((f) => f.path).join(", ")} \u2014 wire those in, never edit them.`
9117
9602
  )
9118
9603
  );
@@ -9143,7 +9628,7 @@ function unwrap(group) {
9143
9628
  }
9144
9629
 
9145
9630
  // src/commands/api.ts
9146
- var import_picocolors32 = __toESM(require_picocolors(), 1);
9631
+ var import_picocolors33 = __toESM(require_picocolors(), 1);
9147
9632
  import { readdirSync, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
9148
9633
  import { join as join3, relative, sep } from "path";
9149
9634
 
@@ -9257,10 +9742,10 @@ function registerApi(program3) {
9257
9742
  const appId = requireApp(opts.app);
9258
9743
  const res = await api(ctx, `/v1/apps/${encodeURIComponent(appId)}/api/requests`);
9259
9744
  ok(res, () => {
9260
- for (const note of res?.notes ?? []) line(import_picocolors32.default.dim(note));
9745
+ for (const note of res?.notes ?? []) line(import_picocolors33.default.dim(note));
9261
9746
  for (const r of res?.requests ?? []) {
9262
9747
  line(
9263
- `${import_picocolors32.default.dim(r.method.padEnd(6))}${r.path}${r.note ? import_picocolors32.default.dim(` ${r.note}`) : ""}`
9748
+ `${import_picocolors33.default.dim(r.method.padEnd(6))}${r.path}${r.note ? import_picocolors33.default.dim(` ${r.note}`) : ""}`
9264
9749
  );
9265
9750
  }
9266
9751
  });
@@ -9294,14 +9779,14 @@ function registerApi(program3) {
9294
9779
  );
9295
9780
  ok(res, () => {
9296
9781
  if (!res?.ok) {
9297
- line(import_picocolors32.default.red(res?.error ?? "The service did not answer."));
9782
+ line(import_picocolors33.default.red(res?.error ?? "The service did not answer."));
9298
9783
  return;
9299
9784
  }
9300
9785
  const code = `${res.status}${res.statusText ? ` ${res.statusText}` : ""}`;
9301
- const colour2 = res.status && res.status < 300 ? import_picocolors32.default.green : res.status && res.status < 500 ? import_picocolors32.default.yellow : import_picocolors32.default.red;
9302
- line(`${colour2(code)} ${import_picocolors32.default.dim(`${res.durationMs}ms ${res.url}`)}`);
9786
+ const colour2 = res.status && res.status < 300 ? import_picocolors33.default.green : res.status && res.status < 500 ? import_picocolors33.default.yellow : import_picocolors33.default.red;
9787
+ line(`${colour2(code)} ${import_picocolors33.default.dim(`${res.durationMs}ms ${res.url}`)}`);
9303
9788
  if (res.body) line(res.body);
9304
- if (res.truncated) line(import_picocolors32.default.dim("(answer truncated)"));
9789
+ if (res.truncated) line(import_picocolors33.default.dim("(answer truncated)"));
9305
9790
  });
9306
9791
  if (!res?.ok) process.exitCode = 1;
9307
9792
  })
@@ -9321,15 +9806,15 @@ function registerApi(program3) {
9321
9806
  for (const r of report.routes) {
9322
9807
  const known = report.missing.some((m) => m.path === r.path);
9323
9808
  line(
9324
- `${known ? import_picocolors32.default.yellow("undocumented") : import_picocolors32.default.green("documented ")} ${r.path}${import_picocolors32.default.dim(` ${r.file}`)}`
9809
+ `${known ? import_picocolors33.default.yellow("undocumented") : import_picocolors33.default.green("documented ")} ${r.path}${import_picocolors33.default.dim(` ${r.file}`)}`
9325
9810
  );
9326
9811
  }
9327
9812
  for (const p of report.stale) {
9328
- line(`${import_picocolors32.default.dim("in spec only ")} ${p}`);
9813
+ line(`${import_picocolors33.default.dim("in spec only ")} ${p}`);
9329
9814
  }
9330
9815
  line("");
9331
9816
  if (report.ok && specFile) success(summary);
9332
- else line(import_picocolors32.default.yellow(summary));
9817
+ else line(import_picocolors33.default.yellow(summary));
9333
9818
  });
9334
9819
  if (opts.check && !report.ok) {
9335
9820
  throw new WorkserError(summary, { code: "bad_request" });
@@ -9400,7 +9885,7 @@ function listRepoFiles(root, maxDepth = 8) {
9400
9885
  }
9401
9886
 
9402
9887
  // src/commands/analysis.ts
9403
- var import_picocolors33 = __toESM(require_picocolors(), 1);
9888
+ var import_picocolors34 = __toESM(require_picocolors(), 1);
9404
9889
  import { readFileSync as readFileSync4 } from "fs";
9405
9890
  function registerAnalysis(program3) {
9406
9891
  const cmd = program3.command("analysis").description("Run Python analysis locally, recorded in the task");
@@ -9410,14 +9895,14 @@ function registerAnalysis(program3) {
9410
9895
  const res = await api(ctx, path);
9411
9896
  ok(res, () => {
9412
9897
  line(
9413
- `${res?.available ? import_picocolors33.default.green("python") : import_picocolors33.default.red("python")} ${res?.version ?? "not found"} ${import_picocolors33.default.dim(res?.python ?? "")}`
9898
+ `${res?.available ? import_picocolors34.default.green("python") : import_picocolors34.default.red("python")} ${res?.version ?? "not found"} ${import_picocolors34.default.dim(res?.python ?? "")}`
9414
9899
  );
9415
9900
  for (const lib of res?.libraries ?? []) {
9416
9901
  line(
9417
- `${lib.present ? import_picocolors33.default.green(lib.name) : import_picocolors33.default.yellow(lib.name)}${import_picocolors33.default.dim(lib.present ? "" : " missing")}`
9902
+ `${lib.present ? import_picocolors34.default.green(lib.name) : import_picocolors34.default.yellow(lib.name)}${import_picocolors34.default.dim(lib.present ? "" : " missing")}`
9418
9903
  );
9419
9904
  }
9420
- for (const note of res?.notes ?? []) line(import_picocolors33.default.dim(note));
9905
+ for (const note of res?.notes ?? []) line(import_picocolors34.default.dim(note));
9421
9906
  });
9422
9907
  if (!res?.available) process.exitCode = 1;
9423
9908
  })
@@ -9439,15 +9924,15 @@ function registerAnalysis(program3) {
9439
9924
  );
9440
9925
  ok(res, () => {
9441
9926
  if (res?.stdout) line(res.stdout.replace(/\n$/, ""));
9442
- if (res?.stderr) line(import_picocolors33.default.dim(res.stderr.replace(/\n$/, "")));
9443
- if (res?.truncated) line(import_picocolors33.default.dim("(output truncated)"));
9927
+ if (res?.stderr) line(import_picocolors34.default.dim(res.stderr.replace(/\n$/, "")));
9928
+ if (res?.truncated) line(import_picocolors34.default.dim("(output truncated)"));
9444
9929
  const took = `${Math.round((res?.durationMs ?? 0) / 100) / 10}s`;
9445
9930
  line(
9446
- 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}`)
9931
+ 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}`)
9447
9932
  );
9448
9933
  if (res && !res.sandboxed) {
9449
9934
  line(
9450
- import_picocolors33.default.dim(
9935
+ import_picocolors34.default.dim(
9451
9936
  "This platform has no OS sandbox, so the script ran with your own file access."
9452
9937
  )
9453
9938
  );
@@ -9475,7 +9960,7 @@ function readCode(file, inline) {
9475
9960
  }
9476
9961
 
9477
9962
  // src/commands/scan.ts
9478
- var import_picocolors34 = __toESM(require_picocolors(), 1);
9963
+ var import_picocolors35 = __toESM(require_picocolors(), 1);
9479
9964
  import { spawnSync as spawnSync2 } from "child_process";
9480
9965
  import { existsSync as existsSync3, readFileSync as readFileSync5, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
9481
9966
  import { join as join4, relative as relative2, sep as sep2 } from "path";
@@ -9759,22 +10244,22 @@ function runPermissions(cwd, findings, checked, skipped) {
9759
10244
  }
9760
10245
  function print2(report, summary) {
9761
10246
  for (const s of report.skipped) {
9762
- line(`${import_picocolors34.default.yellow("not checked")} ${s.check}${import_picocolors34.default.dim(` \u2014 ${s.reason}`)}`);
10247
+ line(`${import_picocolors35.default.yellow("not checked")} ${s.check}${import_picocolors35.default.dim(` \u2014 ${s.reason}`)}`);
9763
10248
  }
9764
10249
  for (const f of report.findings) {
9765
- const where = f.file ? import_picocolors34.default.dim(` ${f.file}${f.line ? `:${f.line}` : ""}`) : "";
10250
+ const where = f.file ? import_picocolors35.default.dim(` ${f.file}${f.line ? `:${f.line}` : ""}`) : "";
9766
10251
  line(`${severityTag(f.severity)} ${f.title}${where}`);
9767
- line(` ${import_picocolors34.default.dim(f.fix)}`);
10252
+ line(` ${import_picocolors35.default.dim(f.fix)}`);
9768
10253
  }
9769
10254
  if (report.findings.length || report.skipped.length) line("");
9770
10255
  if (report.ok && !report.skipped.length) success(summary);
9771
- else if (report.ok) line(import_picocolors34.default.yellow(summary));
9772
- else line(import_picocolors34.default.red(summary));
10256
+ else if (report.ok) line(import_picocolors35.default.yellow(summary));
10257
+ else line(import_picocolors35.default.red(summary));
9773
10258
  }
9774
10259
  function severityTag(severity) {
9775
- if (severity === "high") return import_picocolors34.default.red("serious ");
9776
- if (severity === "medium") return import_picocolors34.default.yellow("worth fixing");
9777
- return import_picocolors34.default.dim("minor ");
10260
+ if (severity === "high") return import_picocolors35.default.red("serious ");
10261
+ if (severity === "medium") return import_picocolors35.default.yellow("worth fixing");
10262
+ return import_picocolors35.default.dim("minor ");
9778
10263
  }
9779
10264
  function git(cwd, args) {
9780
10265
  try {
@@ -9835,7 +10320,7 @@ function listRepoFiles2(root, maxDepth = 8) {
9835
10320
  }
9836
10321
 
9837
10322
  // src/commands/health.ts
9838
- var import_picocolors35 = __toESM(require_picocolors(), 1);
10323
+ var import_picocolors36 = __toESM(require_picocolors(), 1);
9839
10324
  function registerHealth(program3) {
9840
10325
  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(
9841
10326
  action(async ({ ctx, opts }) => {
@@ -9849,16 +10334,16 @@ function registerHealth(program3) {
9849
10334
  }
9850
10335
  function print3(res) {
9851
10336
  if (!res?.checks?.length) {
9852
- line(import_picocolors35.default.dim(res?.note ?? "Nothing to check."));
10337
+ line(import_picocolors36.default.dim(res?.note ?? "Nothing to check."));
9853
10338
  return;
9854
10339
  }
9855
10340
  for (const c of res.checks) {
9856
- const mark = c.ok ? import_picocolors35.default.green("up ") : import_picocolors35.default.red("down");
9857
- const timing = import_picocolors35.default.dim(`${c.ms}ms`);
9858
- const detail = c.ok ? timing : import_picocolors35.default.dim(`${c.error ?? "no answer"}${c.failures > 1 ? ` \xB7 ${c.failures} in a row` : ""}`);
9859
- line(` ${mark} ${c.appName} ${import_picocolors35.default.dim(`(${c.environment})`)} ${c.url} ${detail}`);
10341
+ const mark = c.ok ? import_picocolors36.default.green("up ") : import_picocolors36.default.red("down");
10342
+ const timing = import_picocolors36.default.dim(`${c.ms}ms`);
10343
+ const detail = c.ok ? timing : import_picocolors36.default.dim(`${c.error ?? "no answer"}${c.failures > 1 ? ` \xB7 ${c.failures} in a row` : ""}`);
10344
+ line(` ${mark} ${c.appName} ${import_picocolors36.default.dim(`(${c.environment})`)} ${c.url} ${detail}`);
9860
10345
  if (c.incidentOpened) {
9861
- line(import_picocolors35.default.yellow(` An incident has been opened on the board for this.`));
10346
+ line(import_picocolors36.default.yellow(` An incident has been opened on the board for this.`));
9862
10347
  }
9863
10348
  }
9864
10349
  const down = res.checks.filter((c) => !c.ok);
@@ -9871,14 +10356,14 @@ function print3(res) {
9871
10356
  }
9872
10357
  const production = down.filter((c) => c.environment === "production").length;
9873
10358
  line(
9874
- import_picocolors35.default.red(
10359
+ import_picocolors36.default.red(
9875
10360
  `${down.length} of ${res.checks.length} not answering` + (production ? ` \u2014 ${production} customer-facing.` : " (preview only).")
9876
10361
  )
9877
10362
  );
9878
10363
  }
9879
10364
 
9880
10365
  // src/commands/urls.ts
9881
- var import_picocolors36 = __toESM(require_picocolors(), 1);
10366
+ var import_picocolors37 = __toESM(require_picocolors(), 1);
9882
10367
  function registerUrls(program3) {
9883
10368
  program3.command("urls").description("The stable preview and production addresses of every app in this project").option("--app <webAppId>", "just one app").action(
9884
10369
  action(async ({ ctx, opts }) => {
@@ -9892,20 +10377,20 @@ function registerUrls(program3) {
9892
10377
  const summary = urlsSummary(rows);
9893
10378
  ok({ rows, summary }, () => {
9894
10379
  for (const row of rows) {
9895
- const label = import_picocolors36.default.dim(row.environment.padEnd(10));
9896
- const value = row.url ? import_picocolors36.default.cyan(row.url) : import_picocolors36.default.dim(row.note ?? "not published");
10380
+ const label = import_picocolors37.default.dim(row.environment.padEnd(10));
10381
+ const value = row.url ? import_picocolors37.default.cyan(row.url) : import_picocolors37.default.dim(row.note ?? "not published");
9897
10382
  line(` ${row.appName.padEnd(22)} ${label} ${value}`);
9898
10383
  }
9899
10384
  line("");
9900
10385
  if (rows.some((r) => r.url)) success(summary);
9901
- else line(import_picocolors36.default.yellow(summary));
10386
+ else line(import_picocolors37.default.yellow(summary));
9902
10387
  });
9903
10388
  })
9904
10389
  );
9905
10390
  }
9906
10391
 
9907
10392
  // src/commands/deployments.ts
9908
- var import_picocolors37 = __toESM(require_picocolors(), 1);
10393
+ var import_picocolors38 = __toESM(require_picocolors(), 1);
9909
10394
  function registerDeployments(program3) {
9910
10395
  const cmd = program3.command("deployments").description("Deployment history, and putting a build in front of customers");
9911
10396
  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(
@@ -9923,7 +10408,7 @@ function registerDeployments(program3) {
9923
10408
  ok(res, () => {
9924
10409
  if (!items.length) {
9925
10410
  return line(
9926
- import_picocolors37.default.dim(
10411
+ import_picocolors38.default.dim(
9927
10412
  environment ? `Nothing has been deployed to ${environment} yet.` : "Nothing has been deployed yet. `workser deploy` builds the first one."
9928
10413
  )
9929
10414
  );
@@ -9943,13 +10428,13 @@ function registerDeployments(program3) {
9943
10428
  ).catch(() => null) : null;
9944
10429
  ok({ ...dep, logs }, () => {
9945
10430
  line(formatDeployment(dep));
9946
- if (dep?.error_message) line(import_picocolors37.default.red(` ${dep.error_message}`));
10431
+ if (dep?.error_message) line(import_picocolors38.default.red(` ${dep.error_message}`));
9947
10432
  const events = logs?.events ?? [];
9948
10433
  for (const e of events) {
9949
- line(` ${import_picocolors37.default.dim(String(e.type ?? "log"))} ${e.text ?? ""}`);
10434
+ line(` ${import_picocolors38.default.dim(String(e.type ?? "log"))} ${e.text ?? ""}`);
9950
10435
  }
9951
10436
  if (opts.logs && !events.length) {
9952
- line(import_picocolors37.default.dim(" That build produced no output."));
10437
+ line(import_picocolors38.default.dim(" That build produced no output."));
9953
10438
  }
9954
10439
  });
9955
10440
  })
@@ -9993,16 +10478,16 @@ function printPromoted(res, version) {
9993
10478
  const what = version === null ? "the latest build" : `version ${version}`;
9994
10479
  const url = res.url ?? res.vercel_url;
9995
10480
  success(`Production is being rebuilt from ${what}.`);
9996
- if (url) line(import_picocolors37.default.dim(`It will be at ${url}`));
9997
- line(import_picocolors37.default.dim("`workser deploy status` follows it."));
10481
+ if (url) line(import_picocolors38.default.dim(`It will be at ${url}`));
10482
+ line(import_picocolors38.default.dim("`workser deploy status` follows it."));
9998
10483
  }
9999
10484
  function formatDeployment(d) {
10000
10485
  if (!d) return "";
10001
- const version = d.version !== void 0 ? import_picocolors37.default.yellow(`v${d.version}`) : import_picocolors37.default.dim("v?");
10002
- const env = import_picocolors37.default.dim((d.environment ?? "?").padEnd(10));
10486
+ const version = d.version !== void 0 ? import_picocolors38.default.yellow(`v${d.version}`) : import_picocolors38.default.dim("v?");
10487
+ const env = import_picocolors38.default.dim((d.environment ?? "?").padEnd(10));
10003
10488
  const app = d.webAppName ? `${d.webAppName} ` : "";
10004
- const when = import_picocolors37.default.dim(formatTime2(d.created_at));
10005
- const url = d.url ? " " + import_picocolors37.default.cyan(d.url) : "";
10489
+ const when = import_picocolors38.default.dim(formatTime2(d.created_at));
10490
+ const url = d.url ? " " + import_picocolors38.default.cyan(d.url) : "";
10006
10491
  return `${version} ${env} ${colorStatus(d.status ?? "")} ${app}${when}${url}`;
10007
10492
  }
10008
10493
  function formatTime2(t) {
@@ -10012,7 +10497,7 @@ function formatTime2(t) {
10012
10497
  }
10013
10498
 
10014
10499
  // src/commands/usage.ts
10015
- var import_picocolors38 = __toESM(require_picocolors(), 1);
10500
+ var import_picocolors39 = __toESM(require_picocolors(), 1);
10016
10501
 
10017
10502
  // src/usage.ts
10018
10503
  var NEAR_LIMIT_FRACTION = 0.8;
@@ -10107,7 +10592,7 @@ function registerUsage(program3) {
10107
10592
  function print4(report) {
10108
10593
  const dims = report.dimensions ?? [];
10109
10594
  if (!dims.length) {
10110
- return line(import_picocolors38.default.dim("Nothing to measure for this project yet."));
10595
+ return line(import_picocolors39.default.dim("Nothing to measure for this project yet."));
10111
10596
  }
10112
10597
  const width = Math.max(...dims.map((d) => d.label.length));
10113
10598
  for (const d of dims) {
@@ -10116,23 +10601,23 @@ function print4(report) {
10116
10601
  line("");
10117
10602
  const summary = usageSummary(report);
10118
10603
  const worst = dims.map(usageState);
10119
- if (worst.includes("over")) line(import_picocolors38.default.red(summary));
10604
+ if (worst.includes("over")) line(import_picocolors39.default.red(summary));
10120
10605
  else if (worst.includes("near") || worst.includes("unknown"))
10121
- line(import_picocolors38.default.yellow(summary));
10606
+ line(import_picocolors39.default.yellow(summary));
10122
10607
  else success(summary);
10123
10608
  }
10124
10609
  function gauge(d, _labelWidth) {
10125
10610
  const drawn = bar(d);
10126
- return drawn ? ` ${import_picocolors38.default.dim(drawn)}` : "";
10611
+ return drawn ? ` ${import_picocolors39.default.dim(drawn)}` : "";
10127
10612
  }
10128
10613
  function colour(d) {
10129
10614
  switch (usageState(d)) {
10130
10615
  case "over":
10131
- return d.kind === "hard" ? import_picocolors38.default.red : import_picocolors38.default.yellow;
10616
+ return d.kind === "hard" ? import_picocolors39.default.red : import_picocolors39.default.yellow;
10132
10617
  case "near":
10133
- return import_picocolors38.default.yellow;
10618
+ return import_picocolors39.default.yellow;
10134
10619
  case "unknown":
10135
- return import_picocolors38.default.dim;
10620
+ return import_picocolors39.default.dim;
10136
10621
  default:
10137
10622
  return (s) => s;
10138
10623
  }
@@ -10140,7 +10625,7 @@ function colour(d) {
10140
10625
 
10141
10626
  // src/index.ts
10142
10627
  var pkg = {
10143
- version: true ? "0.6.1" : "0.0.0-dev"
10628
+ version: true ? "0.6.3" : "0.0.0-dev"
10144
10629
  };
10145
10630
  var program2 = new Command();
10146
10631
  program2.name("workser").description(
@@ -10193,6 +10678,7 @@ registerAsk(program2);
10193
10678
  registerSearch(program2);
10194
10679
  registerBoard(program2);
10195
10680
  registerTask(program2);
10681
+ registerGoal(program2);
10196
10682
  registerDecision(program2);
10197
10683
  registerDoc(program2);
10198
10684
  registerDesign(program2);