@workser/cli 0.2.6 → 0.2.7

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
@@ -4404,6 +4404,73 @@ workser storage get <key> [dest] # download an object (or print its URL)
4404
4404
  infrastructure on the project's own Neon branch, available only on dedicated tenancy
4405
4405
  in a supported region. They are different stores \u2014 a file put in one is not visible
4406
4406
  in the other. See \`reference/neon-backend.md\`.
4407
+ `
4408
+ },
4409
+ {
4410
+ topic: "version-control",
4411
+ title: "Saving, undoing & syncing your work",
4412
+ summary: "Checkpoint before risky edits, restore when they go wrong, sync with Workser.",
4413
+ commands: ["checkpoint", "restore", "sync"],
4414
+ source: "skills/workser/reference/version-control.md",
4415
+ body: `# Saving, undoing & syncing your work
4416
+
4417
+ \`\`\`
4418
+ workser checkpoint ["what you're about to try"] # save the folder as it stands now
4419
+ workser restore # go back to the newest checkpoint
4420
+ workser restore --list # see the checkpoints you can go back to
4421
+ workser restore <ref> # go back to a specific one
4422
+ workser sync [--branch dev] [--app <id>] # reconcile this folder with Workser
4423
+ \`\`\`
4424
+
4425
+ ## Why these exist instead of git
4426
+
4427
+ This folder is a git repository, but **the history is Workser's**. There is no
4428
+ remote: code moves to and from Workser over its API as git bundles, using the
4429
+ same session everything else here uses. No credential is read, and the user's
4430
+ own git keys and identity are never touched.
4431
+
4432
+ That is what makes any of this work on a computer with no access to the
4433
+ Workser-managed repository \u2014 which is every computer. Nobody has repo access;
4434
+ the API does.
4435
+
4436
+ So the ordinary git verbs are wrong here, and these three replace them:
4437
+
4438
+ | Instead of | Run |
4439
+ |---|---|
4440
+ | \`git stash\` | \`workser checkpoint\` |
4441
+ | \`git commit\` | \`workser checkpoint\` |
4442
+ | \`git reset --hard\` / \`git checkout .\` | \`workser restore\` |
4443
+ | \`git pull\` / \`git push\` | \`workser sync\` |
4444
+
4445
+ Reading is always fine: \`git log\`, \`git diff\`, \`git show\`, \`git status\`.
4446
+
4447
+ ## Notes that matter
4448
+
4449
+ - **Checkpoint before anything you might want to take back** \u2014 a refactor across
4450
+ many files, a dependency upgrade, deleting something large. It costs a second.
4451
+ - **\`git stash\` is the one that actually loses work.** Workser publishes what is
4452
+ on disk, so a deploy while your changes are stashed ships the version *without
4453
+ them* \u2014 and if the run is stopped between \`stash\` and \`stash pop\`, the user
4454
+ opens their folder and their work is gone. \`workser checkpoint\` has neither
4455
+ failure mode.
4456
+ - **Restoring never discards anything.** The current state is saved first, then
4457
+ the older state is restored on top. If you restore the wrong thing, restore
4458
+ again. Say this to the user \u2014 it is the reason they can afford to say yes.
4459
+ - **These are the same checkpoints as the app's Undo button.** One history, not
4460
+ two: a checkpoint you take here appears in Workser, and vice versa.
4461
+ - **\`sync\` pulls before it pushes.** If it reports \`diverged\`, both sides changed
4462
+ and it stops rather than picking a winner \u2014 tell the user to resolve it in
4463
+ Workser instead of forcing it.
4464
+ - **These three need the Workser app running on this computer**, because they
4465
+ work with files. If you get \`needs_local_app\`, you are on a machine that only
4466
+ has the CLI and a token \u2014 say so rather than looking for a workaround. Every
4467
+ read-only command (\`projects\`, \`env\`, \`db\`, \`logs\`, \`status\`) still works.
4468
+
4469
+ ## Shipping is separate
4470
+
4471
+ \`workser sync\` reconciles the folder. \`workser deploy\` puts it in front of
4472
+ users. Syncing does not deploy, and deploying does not require you to sync
4473
+ first \u2014 deploy publishes what is on disk. See \`reference/deploy.md\`.
4407
4474
  `
4408
4475
  }
4409
4476
  ];
@@ -4570,6 +4637,21 @@ function buildContext(opts) {
4570
4637
  function runTarget(ctx) {
4571
4638
  return ctx.runId || "current";
4572
4639
  }
4640
+ function requireLocalApp(ctx, what) {
4641
+ if (ctx.mode === "daemon") return;
4642
+ throw new WorkserError(
4643
+ `\`workser ${what}\` works with the code in this folder, so it needs the Workser app running on this computer.
4644
+
4645
+ This shell is talking to ${ctx.endpoint} instead of a local app.
4646
+
4647
+ \u2022 On your own computer: open Workser and try again.
4648
+ \u2022 On a computer without Workser: install it from https://workser.ai/download,
4649
+ sign in, and open this project \u2014 that is what puts the code here.
4650
+
4651
+ Commands that only read your account (projects, env, db, logs, status) work as normal.`,
4652
+ { code: "needs_local_app" }
4653
+ );
4654
+ }
4573
4655
  function requireProject(ctx) {
4574
4656
  if (!ctx.projectId) {
4575
4657
  throw new WorkserError(
@@ -5521,6 +5603,7 @@ function registerDeploy(program3) {
5521
5603
  "which app to publish (default: the app this folder is linked to)"
5522
5604
  ).action(
5523
5605
  action(async ({ ctx, opts }) => {
5606
+ requireLocalApp(ctx, "deploy");
5524
5607
  const projectId = requireProject(ctx);
5525
5608
  const dep = await api(ctx, `/v1/projects/${projectId}/deploy`, {
5526
5609
  body: {
@@ -5675,6 +5758,7 @@ function openUrl(url) {
5675
5758
 
5676
5759
  // src/commands/doctor.ts
5677
5760
  var import_picocolors13 = __toESM(require_picocolors(), 1);
5761
+ import { spawnSync } from "child_process";
5678
5762
  function registerDoctor(program3) {
5679
5763
  program3.command("doctor").description("Print the resolved endpoint, mode, token presence (masked), and current project").action(
5680
5764
  action(({ ctx, opts }) => {
@@ -5685,12 +5769,14 @@ function registerDoctor(program3) {
5685
5769
  const tokenSource = opts.token ? "--token" : process.env.WORKSER_TOKEN ? "$WORKSER_TOKEN" : session.token ? "session" : void 0;
5686
5770
  const endpointSource = opts.endpoint ? "--endpoint" : process.env.WORKSER_DAEMON_URL ? "$WORKSER_DAEMON_URL" : session.endpoint ? "session" : process.env.WORKSER_API_URL ? "$WORKSER_API_URL" : `cloud-default: ${env}`;
5687
5771
  const projectSource = opts.project ? "--project" : link?.projectId ? ".workser link" : session.defaultProjectId ? "session" : void 0;
5772
+ const git = gitVersion();
5688
5773
  const report = {
5689
5774
  endpoint: ctx.endpoint,
5690
5775
  endpointSource,
5691
5776
  env,
5692
5777
  envIgnored,
5693
5778
  mode: ctx.mode,
5779
+ git: { present: git !== null, version: git },
5694
5780
  token: {
5695
5781
  present: Boolean(ctx.token),
5696
5782
  masked: ctx.token ? maskToken(ctx.token) : null,
@@ -5718,6 +5804,20 @@ function registerDoctor(program3) {
5718
5804
  ` project: ${ctx.projectId ?? import_picocolors13.default.dim("none")}` + (link?.name ? ` ${import_picocolors13.default.dim(`(${link.name})`)}` : "") + (projectSource ? import_picocolors13.default.dim(` [${projectSource}]`) : "")
5719
5805
  );
5720
5806
  line(` cwd: ${ctx.cwd}`);
5807
+ line(` git: ${git ?? import_picocolors13.default.dim("not on this shell's PATH")}`);
5808
+ if (!git) {
5809
+ line("");
5810
+ line(
5811
+ import_picocolors13.default.dim(
5812
+ " Workser brings its own git, so syncing and publishing still work."
5813
+ )
5814
+ );
5815
+ line(
5816
+ import_picocolors13.default.dim(
5817
+ ` Only needed if you want to run git yourself here. ${GIT_INSTALL_HINT}`
5818
+ )
5819
+ );
5820
+ }
5721
5821
  if (envIgnored) {
5722
5822
  line("");
5723
5823
  line(
@@ -5736,6 +5836,24 @@ function registerDoctor(program3) {
5736
5836
  })
5737
5837
  );
5738
5838
  }
5839
+ function gitVersion() {
5840
+ try {
5841
+ const res = spawnSync("git", ["--version"], {
5842
+ encoding: "utf8",
5843
+ timeout: 5e3,
5844
+ // A macOS box with no Command Line Tools has a `git` SHIM that pops a GUI
5845
+ // installer when run. Inheriting stdio would surface that dialog from a
5846
+ // command the user expects to just print a report.
5847
+ stdio: ["ignore", "pipe", "ignore"]
5848
+ });
5849
+ if (res.error || res.status !== 0) return null;
5850
+ const out = (res.stdout || "").toString().trim();
5851
+ return out || null;
5852
+ } catch {
5853
+ return null;
5854
+ }
5855
+ }
5856
+ var GIT_INSTALL_HINT = process.platform === "darwin" ? "Install it with: xcode-select --install" : process.platform === "win32" ? "Install it from: https://git-scm.com/download/win" : "Install it with your package manager, e.g. apt install git";
5739
5857
  function maskToken(token) {
5740
5858
  if (token.length <= 8) return "\u2022".repeat(token.length);
5741
5859
  return `${token.slice(0, 3)}${"\u2022".repeat(Math.min(token.length - 6, 12))}${token.slice(-3)}`;
@@ -5863,6 +5981,7 @@ function registerVerify(program3) {
5863
5981
  "comma-separated subset, e.g. --only typecheck,build"
5864
5982
  ).action(
5865
5983
  action(async ({ ctx, opts }) => {
5984
+ requireLocalApp(ctx, "verify");
5866
5985
  const only = opts.only ? String(opts.only).split(",").map((s) => s.trim()).filter(Boolean) : void 0;
5867
5986
  const res = await api(ctx, `/v1/verify`, {
5868
5987
  body: { cwd: ctx.cwd, ...only ? { only } : {} }
@@ -5890,8 +6009,139 @@ function printVerify(res) {
5890
6009
  );
5891
6010
  }
5892
6011
 
5893
- // src/commands/workflow.ts
6012
+ // src/commands/checkpoint.ts
5894
6013
  var import_picocolors16 = __toESM(require_picocolors(), 1);
6014
+ function registerCheckpoint(program3) {
6015
+ program3.command("checkpoint [label]").description(
6016
+ "Save the current state of this folder so you can come back to it"
6017
+ ).addHelpText(
6018
+ "after",
6019
+ "\nUse this before a risky change. To come back: `workser restore`.\nNever use `git stash` or `git commit` here \u2014 Workser owns this history.\n"
6020
+ ).action(
6021
+ action(async ({ ctx, args }) => {
6022
+ requireLocalApp(ctx, "checkpoint");
6023
+ const label = typeof args[0] === "string" ? args[0] : "";
6024
+ const res = await api(ctx, `/v1/checkpoints`, {
6025
+ body: { cwd: ctx.cwd, ...label ? { label } : {} }
6026
+ });
6027
+ ok(res, () => {
6028
+ const p = res?.point;
6029
+ success(`Saved a checkpoint${p?.label ? `: ${p.label}` : ""}`);
6030
+ if (p?.ref) line(import_picocolors16.default.dim(` ${p.ref.slice(0, 7)}`));
6031
+ line(import_picocolors16.default.dim(" Come back to it with `workser restore`."));
6032
+ });
6033
+ })
6034
+ );
6035
+ const restore = program3.command("restore [ref]").description(
6036
+ "Put this folder back to a saved checkpoint (default: the most recent)"
6037
+ ).option("--list", "show the checkpoints you can go back to", false).action(
6038
+ action(async ({ ctx, args, opts }) => {
6039
+ requireLocalApp(ctx, "restore");
6040
+ if (opts.list) {
6041
+ const res2 = await api(ctx, `/v1/checkpoints`, {
6042
+ query: { cwd: ctx.cwd }
6043
+ });
6044
+ const points = res2?.points ?? [];
6045
+ return ok(res2, () => printPoints(points));
6046
+ }
6047
+ const ref = typeof args[0] === "string" ? args[0].trim() : "";
6048
+ const res = await api(ctx, `/v1/undo`, {
6049
+ body: { cwd: ctx.cwd, ...ref ? { ref } : {} }
6050
+ });
6051
+ ok(res, () => {
6052
+ success(
6053
+ `Put the folder back${res?.restoredTo ? ` to ${String(res.restoredTo).slice(0, 7)}` : ""}`
6054
+ );
6055
+ if (res?.filesChanged) {
6056
+ line(
6057
+ import_picocolors16.default.dim(
6058
+ ` ${res.filesChanged} file${res.filesChanged === 1 ? "" : "s"} changed`
6059
+ )
6060
+ );
6061
+ }
6062
+ line(import_picocolors16.default.dim(" This is reversible: `workser restore` again."));
6063
+ });
6064
+ })
6065
+ );
6066
+ restore.command("list").description("Show the checkpoints you can go back to").action(
6067
+ action(async ({ ctx }) => {
6068
+ requireLocalApp(ctx, "restore list");
6069
+ const res = await api(ctx, `/v1/checkpoints`, {
6070
+ query: { cwd: ctx.cwd }
6071
+ });
6072
+ ok(res, () => printPoints(res?.points ?? []));
6073
+ })
6074
+ );
6075
+ }
6076
+ function printPoints(points) {
6077
+ if (!points.length) {
6078
+ info("No checkpoints yet for this folder.");
6079
+ line(import_picocolors16.default.dim(" Take one with `workser checkpoint`."));
6080
+ return;
6081
+ }
6082
+ line(import_picocolors16.default.bold("Checkpoints"));
6083
+ for (const p of points) {
6084
+ const when = p.at ? new Date(p.at).toLocaleString() : "";
6085
+ line(
6086
+ ` ${import_picocolors16.default.dim(p.ref.slice(0, 7))} ${p.label}${when ? import_picocolors16.default.dim(` ${when}`) : ""}`
6087
+ );
6088
+ }
6089
+ line(
6090
+ import_picocolors16.default.dim(
6091
+ "\nGo back with `workser restore <ref>`, or just `workser restore` for the newest."
6092
+ )
6093
+ );
6094
+ }
6095
+
6096
+ // src/commands/sync.ts
6097
+ var import_picocolors17 = __toESM(require_picocolors(), 1);
6098
+ function registerSync(program3) {
6099
+ program3.command("sync").description(
6100
+ "Reconcile this folder with the copy Workser holds (pull, then push)"
6101
+ ).option("--branch <name>", "which branch to sync (default: this folder's)").option(
6102
+ "--app <webAppId>",
6103
+ "which app's code this folder holds (default: resolved from the folder)"
6104
+ ).addHelpText(
6105
+ "after",
6106
+ "\nUse this instead of `git pull` / `git push` \u2014 this folder has no remote.\n"
6107
+ ).action(
6108
+ action(async ({ ctx, opts }) => {
6109
+ requireLocalApp(ctx, "sync");
6110
+ const projectId = requireProject(ctx);
6111
+ const res = await api(ctx, `/v1/projects/${projectId}/git/sync`, {
6112
+ body: {
6113
+ cwd: ctx.cwd,
6114
+ ...opts.branch ? { branch: opts.branch } : {},
6115
+ ...opts.app ? { webAppId: opts.app } : {}
6116
+ }
6117
+ });
6118
+ const refused = res?.ok === false || res?.state === "diverged";
6119
+ ok(res, () => {
6120
+ if (refused) {
6121
+ warn(res?.message ?? "Couldn't sync this folder.");
6122
+ if (res?.state === "diverged") {
6123
+ line(
6124
+ import_picocolors17.default.dim(
6125
+ " This folder and Workser's copy have both changed. Open Workser to resolve it."
6126
+ )
6127
+ );
6128
+ }
6129
+ return;
6130
+ }
6131
+ if (res?.state === "empty") {
6132
+ line("Nothing to sync yet \u2014 there's no code here or on Workser.");
6133
+ return;
6134
+ }
6135
+ success("Synced");
6136
+ if (res?.ref) line(import_picocolors17.default.dim(` ${String(res.ref).slice(0, 7)}`));
6137
+ });
6138
+ if (refused) process.exitCode = 1;
6139
+ })
6140
+ );
6141
+ }
6142
+
6143
+ // src/commands/workflow.ts
6144
+ var import_picocolors18 = __toESM(require_picocolors(), 1);
5895
6145
  function registerWorkflow(program3) {
5896
6146
  const wf = program3.command("workflow").description("Create, run, and inspect workflow automations for the project");
5897
6147
  wf.command("list").description("List the project's workflows").action(
@@ -5899,10 +6149,10 @@ function registerWorkflow(program3) {
5899
6149
  const projectId = requireProject(ctx);
5900
6150
  const items = await api(ctx, `/v1/projects/${projectId}/workflows`);
5901
6151
  ok(items, () => {
5902
- if (!items?.length) return line(import_picocolors16.default.dim("No workflows yet. `workser workflow create`."));
6152
+ if (!items?.length) return line(import_picocolors18.default.dim("No workflows yet. `workser workflow create`."));
5903
6153
  for (const w of items) {
5904
- const status = w.is_active ? import_picocolors16.default.green("active") : import_picocolors16.default.dim("inactive");
5905
- line(`${w.id} ${import_picocolors16.default.bold(w.name ?? "Untitled")} ${status}`);
6154
+ const status = w.is_active ? import_picocolors18.default.green("active") : import_picocolors18.default.dim("inactive");
6155
+ line(`${w.id} ${import_picocolors18.default.bold(w.name ?? "Untitled")} ${status}`);
5906
6156
  }
5907
6157
  });
5908
6158
  })
@@ -5914,7 +6164,7 @@ function registerWorkflow(program3) {
5914
6164
  const res = await api(ctx, `/v1/projects/${projectId}/workflows`, {
5915
6165
  body: { name: args[0], ...extra }
5916
6166
  });
5917
- ok(res, () => line(`Created workflow ${import_picocolors16.default.bold(res.id)}.`));
6167
+ ok(res, () => line(`Created workflow ${import_picocolors18.default.bold(res.id)}.`));
5918
6168
  })
5919
6169
  );
5920
6170
  wf.command("get <id>").description("Show a workflow's full definition").action(
@@ -5949,8 +6199,8 @@ function registerWorkflow(program3) {
5949
6199
  action(async ({ ctx, args }) => {
5950
6200
  const items = await api(ctx, `/v1/workflows/${args[0]}/executions`);
5951
6201
  ok(items, () => {
5952
- if (!items?.length) return line(import_picocolors16.default.dim("No runs yet."));
5953
- for (const e of items) line(`${e.id} ${e.status ?? ""} ${import_picocolors16.default.dim(e.started_at ?? "")}`);
6202
+ if (!items?.length) return line(import_picocolors18.default.dim("No runs yet."));
6203
+ for (const e of items) line(`${e.id} ${e.status ?? ""} ${import_picocolors18.default.dim(e.started_at ?? "")}`);
5954
6204
  });
5955
6205
  })
5956
6206
  );
@@ -5958,15 +6208,15 @@ function registerWorkflow(program3) {
5958
6208
  action(async ({ ctx, args }) => {
5959
6209
  const items = await api(ctx, `/v1/node-types`, { query: { q: args[0] } });
5960
6210
  ok(items, () => {
5961
- if (!items?.length) return line(import_picocolors16.default.dim("No matching node types."));
5962
- for (const n of items) line(`${n.name ?? n.type} ${import_picocolors16.default.dim(n.category ?? "")}`);
6211
+ if (!items?.length) return line(import_picocolors18.default.dim("No matching node types."));
6212
+ for (const n of items) line(`${n.name ?? n.type} ${import_picocolors18.default.dim(n.category ?? "")}`);
5963
6213
  });
5964
6214
  })
5965
6215
  );
5966
6216
  }
5967
6217
 
5968
6218
  // src/commands/app.ts
5969
- var import_picocolors17 = __toESM(require_picocolors(), 1);
6219
+ var import_picocolors19 = __toESM(require_picocolors(), 1);
5970
6220
  function registerApp(program3) {
5971
6221
  const appCmd = program3.command("app").description("Connect and use third-party app integrations (Gmail, Slack, Stripe, ...)");
5972
6222
  appCmd.command("list").description("List connectable toolkits and this project's existing connections").option("--toolkit <slug>", "filter connections to one toolkit").action(
@@ -5979,8 +6229,8 @@ function registerApp(program3) {
5979
6229
  ok({ catalog, connections }, () => {
5980
6230
  const connected = new Set((connections ?? []).map((c) => c.toolkit ?? c.composio_app));
5981
6231
  for (const t of catalog ?? []) {
5982
- const status = connected.has(t.slug) ? import_picocolors17.default.green("connected") : import_picocolors17.default.dim("not connected");
5983
- line(`${t.slug} ${import_picocolors17.default.bold(t.name ?? t.slug)} ${status}`);
6232
+ const status = connected.has(t.slug) ? import_picocolors19.default.green("connected") : import_picocolors19.default.dim("not connected");
6233
+ line(`${t.slug} ${import_picocolors19.default.bold(t.name ?? t.slug)} ${status}`);
5984
6234
  }
5985
6235
  });
5986
6236
  })
@@ -5997,7 +6247,7 @@ function registerApp(program3) {
5997
6247
  });
5998
6248
  ok(
5999
6249
  res,
6000
- () => res.oauth_url ? line(`Open this URL to finish connecting: ${import_picocolors17.default.underline(res.oauth_url)}`) : line(`Connection ${res.connection_id} is ${res.status}.`)
6250
+ () => res.oauth_url ? line(`Open this URL to finish connecting: ${import_picocolors19.default.underline(res.oauth_url)}`) : line(`Connection ${res.connection_id} is ${res.status}.`)
6001
6251
  );
6002
6252
  })
6003
6253
  );
@@ -6015,8 +6265,8 @@ function registerApp(program3) {
6015
6265
  const projectId = requireProject(ctx);
6016
6266
  const items = await api(ctx, `/v1/projects/${projectId}/integrations/${args[0]}/tools`);
6017
6267
  ok(items, () => {
6018
- if (!items?.length) return line(import_picocolors17.default.dim("No tools found."));
6019
- for (const t of items) line(`${t.slug} ${import_picocolors17.default.dim(t.description ?? "")}`);
6268
+ if (!items?.length) return line(import_picocolors19.default.dim("No tools found."));
6269
+ for (const t of items) line(`${t.slug} ${import_picocolors19.default.dim(t.description ?? "")}`);
6020
6270
  });
6021
6271
  })
6022
6272
  );
@@ -6032,7 +6282,7 @@ function registerApp(program3) {
6032
6282
  }
6033
6283
 
6034
6284
  // src/commands/tool.ts
6035
- var import_picocolors18 = __toESM(require_picocolors(), 1);
6285
+ var import_picocolors20 = __toESM(require_picocolors(), 1);
6036
6286
  function registerTool(program3) {
6037
6287
  const tool = program3.command("tool").description(
6038
6288
  "Computer-use tools: filesystem, shell, screenshot, input control, clipboard, notifications, basic browser"
@@ -6041,7 +6291,7 @@ function registerTool(program3) {
6041
6291
  action(async ({ ctx }) => {
6042
6292
  const tools = await api(ctx, "/v1/tool/list");
6043
6293
  ok(tools, () => {
6044
- if (!tools?.length) return line(import_picocolors18.default.dim("No tools available."));
6294
+ if (!tools?.length) return line(import_picocolors20.default.dim("No tools available."));
6045
6295
  const byCategory = /* @__PURE__ */ new Map();
6046
6296
  for (const t of tools) {
6047
6297
  const list = byCategory.get(t.category) ?? [];
@@ -6049,9 +6299,9 @@ function registerTool(program3) {
6049
6299
  byCategory.set(t.category, list);
6050
6300
  }
6051
6301
  for (const [category, items] of byCategory) {
6052
- line(import_picocolors18.default.bold(category) + ":");
6302
+ line(import_picocolors20.default.bold(category) + ":");
6053
6303
  for (const t of items) {
6054
- line(` ${t.name} ${import_picocolors18.default.dim(t.description ?? "")}`);
6304
+ line(` ${t.name} ${import_picocolors20.default.dim(t.description ?? "")}`);
6055
6305
  }
6056
6306
  }
6057
6307
  });
@@ -6069,7 +6319,7 @@ function registerTool(program3) {
6069
6319
  }
6070
6320
 
6071
6321
  // src/commands/memory.ts
6072
- var import_picocolors19 = __toESM(require_picocolors(), 1);
6322
+ var import_picocolors21 = __toESM(require_picocolors(), 1);
6073
6323
  function registerMemory(program3) {
6074
6324
  const memory = program3.command("memory").description("Durable, cross-conversation project memory (shared with cloud agents on the same project)");
6075
6325
  memory.command("add <content>").description("Store something worth remembering across future conversations").option("--metadata <json>", "extra metadata for filtering, as a JSON string").option("--id <customId>", "custom id for dedup/updates").action(
@@ -6093,9 +6343,9 @@ function registerMemory(program3) {
6093
6343
  });
6094
6344
  ok(res, () => {
6095
6345
  const results = res?.results ?? res ?? [];
6096
- if (!results?.length) return line(import_picocolors19.default.dim("No matching memories."));
6346
+ if (!results?.length) return line(import_picocolors21.default.dim("No matching memories."));
6097
6347
  for (const r of results) {
6098
- line(`${import_picocolors19.default.dim(r.id ?? "?")} ${r.memory ?? r.content ?? ""}`);
6348
+ line(`${import_picocolors21.default.dim(r.id ?? "?")} ${r.memory ?? r.content ?? ""}`);
6099
6349
  }
6100
6350
  });
6101
6351
  })
@@ -6112,7 +6362,7 @@ function registerMemory(program3) {
6112
6362
  }
6113
6363
 
6114
6364
  // src/commands/business.ts
6115
- var import_picocolors20 = __toESM(require_picocolors(), 1);
6365
+ var import_picocolors22 = __toESM(require_picocolors(), 1);
6116
6366
  var RESOURCE_PATHS = {
6117
6367
  "business-config": "business-config",
6118
6368
  "business-settings": "business-settings",
@@ -6172,7 +6422,7 @@ function registerBusiness(program3) {
6172
6422
  const projectId = requireProject(ctx);
6173
6423
  const [resource] = args;
6174
6424
  const res = await api(ctx, businessPath(projectId, resource), { body: JSON.parse(opts.body) });
6175
- ok(res, () => line(`Created ${resource} ${import_picocolors20.default.bold(res?.id ?? "")}.`));
6425
+ ok(res, () => line(`Created ${resource} ${import_picocolors22.default.bold(res?.id ?? "")}.`));
6176
6426
  })
6177
6427
  );
6178
6428
  biz.command("update <resource> <id>").description("Update a record by id (PATCH/PUT \u2014 matches the underlying route)").option("--body <json>", "changed fields as a JSON object string", "{}").action(
@@ -6214,7 +6464,7 @@ function businessPath(projectId, resource, subpath) {
6214
6464
  }
6215
6465
 
6216
6466
  // src/commands/artifact.ts
6217
- var import_picocolors21 = __toESM(require_picocolors(), 1);
6467
+ var import_picocolors23 = __toESM(require_picocolors(), 1);
6218
6468
  import { existsSync as existsSync2, statSync } from "fs";
6219
6469
  import { resolve as resolve2, basename as basename3 } from "path";
6220
6470
  var KINDS = [
@@ -6280,7 +6530,7 @@ function registerArtifact(program3) {
6280
6530
  ok(
6281
6531
  res,
6282
6532
  () => success(
6283
- `Recorded ${import_picocolors21.default.bold(res?.title ?? "artifact")}${res?.kind ? import_picocolors21.default.dim(` (${res.kind})`) : ""}`
6533
+ `Recorded ${import_picocolors23.default.bold(res?.title ?? "artifact")}${res?.kind ? import_picocolors23.default.dim(` (${res.kind})`) : ""}`
6284
6534
  )
6285
6535
  );
6286
6536
  })
@@ -6294,11 +6544,11 @@ function registerArtifact(program3) {
6294
6544
  }
6295
6545
  function printRun(run) {
6296
6546
  if (!run) return;
6297
- line(` run ${import_picocolors21.default.bold(run.runId)}`);
6547
+ line(` run ${import_picocolors23.default.bold(run.runId)}`);
6298
6548
  if (run.taskId) line(` task ${run.taskId}`);
6299
6549
  if (run.conversationId) line(` chat ${run.conversationId}`);
6300
6550
  if (run.projectId) line(` project ${run.projectId}`);
6301
- if (run.cwd) line(` folder ${import_picocolors21.default.dim(run.cwd)}`);
6551
+ if (run.cwd) line(` folder ${import_picocolors23.default.dim(run.cwd)}`);
6302
6552
  }
6303
6553
 
6304
6554
  // src/commands/image.ts
@@ -6372,7 +6622,7 @@ async function download(url, output) {
6372
6622
  }
6373
6623
 
6374
6624
  // src/commands/ask.ts
6375
- var import_picocolors22 = __toESM(require_picocolors(), 1);
6625
+ var import_picocolors24 = __toESM(require_picocolors(), 1);
6376
6626
  var TYPES = [
6377
6627
  "input",
6378
6628
  "choice",
@@ -6423,7 +6673,7 @@ function registerAsk(program3) {
6423
6673
  code: "bad_request"
6424
6674
  });
6425
6675
  }
6426
- info(import_picocolors22.default.dim("Waiting for the user to answer\u2026"));
6676
+ info(import_picocolors24.default.dim("Waiting for the user to answer\u2026"));
6427
6677
  const res = await api(ctx, `/v1/runs/${runTarget(ctx)}/ask`, {
6428
6678
  body: {
6429
6679
  type,
@@ -6449,12 +6699,12 @@ function deriveTitle(message) {
6449
6699
  function printAnswer(res) {
6450
6700
  if (!res) return;
6451
6701
  if (res.status === "answered") {
6452
- line(` ${import_picocolors22.default.green("answered")}`);
6702
+ line(` ${import_picocolors24.default.green("answered")}`);
6453
6703
  const value = extract(res.response);
6454
6704
  if (value) line(` ${value}`);
6455
6705
  return;
6456
6706
  }
6457
- line(` ${import_picocolors22.default.yellow(res.status)} ${import_picocolors22.default.dim(res.reason ?? "")}`);
6707
+ line(` ${import_picocolors24.default.yellow(res.status)} ${import_picocolors24.default.dim(res.reason ?? "")}`);
6458
6708
  }
6459
6709
  function extract(response) {
6460
6710
  if (response == null) return "";
@@ -6473,7 +6723,7 @@ function extract(response) {
6473
6723
  }
6474
6724
 
6475
6725
  // src/commands/search.ts
6476
- var import_picocolors23 = __toESM(require_picocolors(), 1);
6726
+ var import_picocolors25 = __toESM(require_picocolors(), 1);
6477
6727
  function registerSearch(program3) {
6478
6728
  program3.command("search <query>").description("Search the web (Google-grounded, server-side)").option("-n, --max-results <n>", "max results", "5").action(
6479
6729
  action(async ({ ctx, args, opts }) => {
@@ -6486,9 +6736,9 @@ function registerSearch(program3) {
6486
6736
  line("");
6487
6737
  }
6488
6738
  const results = res?.results ?? [];
6489
- if (!results.length) return line(import_picocolors23.default.dim("No results."));
6739
+ if (!results.length) return line(import_picocolors25.default.dim("No results."));
6490
6740
  for (const r of results) {
6491
- line(`${r.title || import_picocolors23.default.dim("(untitled)")} ${import_picocolors23.default.dim(r.url)}`);
6741
+ line(`${r.title || import_picocolors25.default.dim("(untitled)")} ${import_picocolors25.default.dim(r.url)}`);
6492
6742
  }
6493
6743
  });
6494
6744
  })
@@ -6496,7 +6746,7 @@ function registerSearch(program3) {
6496
6746
  }
6497
6747
 
6498
6748
  // src/commands/board.ts
6499
- var import_picocolors24 = __toESM(require_picocolors(), 1);
6749
+ var import_picocolors26 = __toESM(require_picocolors(), 1);
6500
6750
 
6501
6751
  // src/commands/record-step.ts
6502
6752
  async function recordEntityStep(ctx, opts) {
@@ -6535,7 +6785,7 @@ function registerBoard(program3) {
6535
6785
  }
6536
6786
  ok(rows, () => {
6537
6787
  if (!rows.length) {
6538
- line(import_picocolors24.default.dim("No cards on the Board yet."));
6788
+ line(import_picocolors26.default.dim("No cards on the Board yet."));
6539
6789
  return;
6540
6790
  }
6541
6791
  for (const r of rows) line(formatRow(r));
@@ -6550,7 +6800,7 @@ function registerBoard(program3) {
6550
6800
  `/v1/projects/${projectId}/work-items/${args[0]}`
6551
6801
  );
6552
6802
  ok(row, () => {
6553
- line(`${import_picocolors24.default.bold(row.title)} ${import_picocolors24.default.dim(row.id)}`);
6803
+ line(`${import_picocolors26.default.bold(row.title)} ${import_picocolors26.default.dim(row.id)}`);
6554
6804
  line(`${statusTag(row.status)} priority ${row.priority}`);
6555
6805
  if (row.ownerHuman) line(`owner: ${row.ownerHuman}`);
6556
6806
  if (row.labels?.length) line(`labels: ${row.labels.join(", ")}`);
@@ -6591,7 +6841,7 @@ ${row.description}`);
6591
6841
  refId: row?.id,
6592
6842
  output: { workItem: row }
6593
6843
  });
6594
- ok(row, () => line(`Created work item ${import_picocolors24.default.bold(row?.id ?? "")} \u2014 ${title}`));
6844
+ ok(row, () => line(`Created work item ${import_picocolors26.default.bold(row?.id ?? "")} \u2014 ${title}`));
6595
6845
  })
6596
6846
  );
6597
6847
  board.command("update <id>").description("Change fields on a card \u2014 pass only what changes").option("--title <text>", "new title").option("--description <text>", "new description").option("--status <value>", STATUSES.join(" | ")).option("--priority <value>", PRIORITIES.join(" | ")).option(
@@ -6625,7 +6875,7 @@ ${row.description}`);
6625
6875
  );
6626
6876
  }
6627
6877
  const row = await patchItem(ctx, projectId, String(args[0]), body);
6628
- ok(row, () => line(`Updated ${import_picocolors24.default.bold(row.id)} \u2014 ${row.title} ${statusTag(row.status)}`));
6878
+ ok(row, () => line(`Updated ${import_picocolors26.default.bold(row.id)} \u2014 ${row.title} ${statusTag(row.status)}`));
6629
6879
  })
6630
6880
  );
6631
6881
  board.command("move <id> <status>").description(
@@ -6636,14 +6886,14 @@ ${row.description}`);
6636
6886
  const status = String(args[1]);
6637
6887
  assertStatus(status);
6638
6888
  const row = await patchItem(ctx, projectId, String(args[0]), { status });
6639
- ok(row, () => line(`Moved ${import_picocolors24.default.bold(row.title)} \u2192 ${statusTag(row.status)}`));
6889
+ ok(row, () => line(`Moved ${import_picocolors26.default.bold(row.title)} \u2192 ${statusTag(row.status)}`));
6640
6890
  })
6641
6891
  );
6642
6892
  board.command("close <id>").description("Shorthand for `board move <id> done`").action(
6643
6893
  action(async ({ ctx, args }) => {
6644
6894
  const projectId = requireProject(ctx);
6645
6895
  const row = await patchItem(ctx, projectId, String(args[0]), { status: "done" });
6646
- ok(row, () => line(`Closed ${import_picocolors24.default.bold(row.title)} ${statusTag(row.status)}`));
6896
+ ok(row, () => line(`Closed ${import_picocolors26.default.bold(row.title)} ${statusTag(row.status)}`));
6647
6897
  })
6648
6898
  );
6649
6899
  }
@@ -6676,23 +6926,23 @@ function assertPriority(value) {
6676
6926
  }
6677
6927
  }
6678
6928
  function formatRow(r) {
6679
- const labels = r.labels?.length ? import_picocolors24.default.dim(` [${r.labels.join(", ")}]`) : "";
6680
- const owner = r.ownerHuman ? import_picocolors24.default.dim(` @${r.ownerHuman}`) : "";
6681
- return `${import_picocolors24.default.dim(r.id)} ${statusTag(r.status)} ${r.title}${labels}${owner}`;
6929
+ const labels = r.labels?.length ? import_picocolors26.default.dim(` [${r.labels.join(", ")}]`) : "";
6930
+ const owner = r.ownerHuman ? import_picocolors26.default.dim(` @${r.ownerHuman}`) : "";
6931
+ return `${import_picocolors26.default.dim(r.id)} ${statusTag(r.status)} ${r.title}${labels}${owner}`;
6682
6932
  }
6683
6933
  function statusTag(status) {
6684
6934
  const label = status.padEnd(11);
6685
- if (status === "done") return import_picocolors24.default.green(label);
6686
- if (status === "in-progress") return import_picocolors24.default.yellow(label);
6687
- if (status === "in-review") return import_picocolors24.default.cyan(label);
6688
- return import_picocolors24.default.dim(label);
6935
+ if (status === "done") return import_picocolors26.default.green(label);
6936
+ if (status === "in-progress") return import_picocolors26.default.yellow(label);
6937
+ if (status === "in-review") return import_picocolors26.default.cyan(label);
6938
+ return import_picocolors26.default.dim(label);
6689
6939
  }
6690
6940
  function collect2(value, previous) {
6691
6941
  return [...previous, value];
6692
6942
  }
6693
6943
 
6694
6944
  // src/commands/decision.ts
6695
- var import_picocolors25 = __toESM(require_picocolors(), 1);
6945
+ var import_picocolors27 = __toESM(require_picocolors(), 1);
6696
6946
  function registerDecision(program3) {
6697
6947
  const decision = program3.command("decision").description("Read and record the project's architecture decisions");
6698
6948
  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(
@@ -6705,11 +6955,11 @@ function registerDecision(program3) {
6705
6955
  rows = applyLimit(rows, opts.limit);
6706
6956
  ok(rows, () => {
6707
6957
  if (!rows.length) {
6708
- line(import_picocolors25.default.dim("No decisions recorded yet."));
6958
+ line(import_picocolors27.default.dim("No decisions recorded yet."));
6709
6959
  return;
6710
6960
  }
6711
6961
  for (const r of rows) {
6712
- line(`${import_picocolors25.default.dim(r.id)} ${import_picocolors25.default.dim(shortDate(r.createdAt))} ${r.title}`);
6962
+ line(`${import_picocolors27.default.dim(r.id)} ${import_picocolors27.default.dim(shortDate(r.createdAt))} ${r.title}`);
6713
6963
  line(` ${truncate(r.decision, 100)}`);
6714
6964
  }
6715
6965
  });
@@ -6723,16 +6973,16 @@ function registerDecision(program3) {
6723
6973
  `/v1/projects/${projectId}/architecture-decisions/${args[0]}`
6724
6974
  );
6725
6975
  ok(row, () => {
6726
- line(`${import_picocolors25.default.bold(row.title)} ${import_picocolors25.default.dim(row.id)}`);
6727
- line(import_picocolors25.default.dim(`${row.status} \xB7 ${shortDate(row.createdAt)}`));
6976
+ line(`${import_picocolors27.default.bold(row.title)} ${import_picocolors27.default.dim(row.id)}`);
6977
+ line(import_picocolors27.default.dim(`${row.status} \xB7 ${shortDate(row.createdAt)}`));
6728
6978
  line(`
6729
- ${import_picocolors25.default.bold("Context")}
6979
+ ${import_picocolors27.default.bold("Context")}
6730
6980
  ${row.context}`);
6731
6981
  line(`
6732
- ${import_picocolors25.default.bold("Decision")}
6982
+ ${import_picocolors27.default.bold("Decision")}
6733
6983
  ${row.decision}`);
6734
6984
  if (row.consequences) line(`
6735
- ${import_picocolors25.default.bold("Consequences")}
6985
+ ${import_picocolors27.default.bold("Consequences")}
6736
6986
  ${row.consequences}`);
6737
6987
  });
6738
6988
  })
@@ -6761,7 +7011,7 @@ ${row.consequences}`);
6761
7011
  refId: row?.id,
6762
7012
  output: { decision: row }
6763
7013
  });
6764
- ok(row, () => line(`Recorded decision ${import_picocolors25.default.bold(row?.id ?? "")} \u2014 ${title}`));
7014
+ ok(row, () => line(`Recorded decision ${import_picocolors27.default.bold(row?.id ?? "")} \u2014 ${title}`));
6765
7015
  })
6766
7016
  );
6767
7017
  const requirement = program3.command("requirement").description("Read and record the project's requirements");
@@ -6773,11 +7023,11 @@ ${row.consequences}`);
6773
7023
  rows = applyLimit(rows, opts.limit);
6774
7024
  ok(rows, () => {
6775
7025
  if (!rows.length) {
6776
- line(import_picocolors25.default.dim("No requirements recorded yet."));
7026
+ line(import_picocolors27.default.dim("No requirements recorded yet."));
6777
7027
  return;
6778
7028
  }
6779
7029
  for (const r of rows) {
6780
- line(`${import_picocolors25.default.dim(r.id)} ${r.status.padEnd(9)} ${r.title}`);
7030
+ line(`${import_picocolors27.default.dim(r.id)} ${r.status.padEnd(9)} ${r.title}`);
6781
7031
  }
6782
7032
  });
6783
7033
  })
@@ -6790,8 +7040,8 @@ ${row.consequences}`);
6790
7040
  `/v1/projects/${projectId}/requirements/${args[0]}`
6791
7041
  );
6792
7042
  ok(row, () => {
6793
- line(`${import_picocolors25.default.bold(row.title)} ${import_picocolors25.default.dim(row.id)}`);
6794
- line(import_picocolors25.default.dim(`${row.status} \xB7 ${shortDate(row.createdAt)}`));
7043
+ line(`${import_picocolors27.default.bold(row.title)} ${import_picocolors27.default.dim(row.id)}`);
7044
+ line(import_picocolors27.default.dim(`${row.status} \xB7 ${shortDate(row.createdAt)}`));
6795
7045
  line(`
6796
7046
  ${row.body}`);
6797
7047
  });
@@ -6817,7 +7067,7 @@ ${row.body}`);
6817
7067
  refId: row?.id,
6818
7068
  output: { requirement: row }
6819
7069
  });
6820
- ok(row, () => line(`Recorded requirement ${import_picocolors25.default.bold(row?.id ?? "")} \u2014 ${title}`));
7070
+ ok(row, () => line(`Recorded requirement ${import_picocolors27.default.bold(row?.id ?? "")} \u2014 ${title}`));
6821
7071
  })
6822
7072
  );
6823
7073
  requirement.command("update <id>").description(
@@ -6846,7 +7096,7 @@ ${row.body}`);
6846
7096
  code: "bad_request"
6847
7097
  });
6848
7098
  }
6849
- ok(row, () => line(`Updated requirement ${import_picocolors25.default.bold(row.id)} \u2014 ${row.title} (${row.status})`));
7099
+ ok(row, () => line(`Updated requirement ${import_picocolors27.default.bold(row.id)} \u2014 ${row.title} (${row.status})`));
6850
7100
  })
6851
7101
  );
6852
7102
  }
@@ -6869,7 +7119,7 @@ function shortDate(iso) {
6869
7119
  }
6870
7120
 
6871
7121
  // src/commands/doc.ts
6872
- var import_picocolors26 = __toESM(require_picocolors(), 1);
7122
+ var import_picocolors28 = __toESM(require_picocolors(), 1);
6873
7123
  function registerDoc(program3) {
6874
7124
  const doc = program3.command("doc").description("Read and write project documents");
6875
7125
  doc.command("list").description("List the project's documents \u2014 check here before writing a new one").option("--work-item <id>", "the document linked to this card, if there is one").action(
@@ -6880,13 +7130,13 @@ function registerDoc(program3) {
6880
7130
  }) ?? [];
6881
7131
  ok(rows, () => {
6882
7132
  if (!rows.length) {
6883
- line(import_picocolors26.default.dim("No documents yet."));
7133
+ line(import_picocolors28.default.dim("No documents yet."));
6884
7134
  return;
6885
7135
  }
6886
7136
  for (const r of rows) {
6887
- const link = r.workItemId ? import_picocolors26.default.dim(` \u21B3 ${r.workItemId}`) : "";
6888
- const file = r.filePath ? import_picocolors26.default.dim(` ${r.filePath}`) : "";
6889
- line(`${import_picocolors26.default.dim(r.id)} ${r.title}${link}${file}`);
7137
+ const link = r.workItemId ? import_picocolors28.default.dim(` \u21B3 ${r.workItemId}`) : "";
7138
+ const file = r.filePath ? import_picocolors28.default.dim(` ${r.filePath}`) : "";
7139
+ line(`${import_picocolors28.default.dim(r.id)} ${r.title}${link}${file}`);
6890
7140
  }
6891
7141
  });
6892
7142
  })
@@ -6900,17 +7150,17 @@ function registerDoc(program3) {
6900
7150
  );
6901
7151
  if (opts.markdown) {
6902
7152
  ok({ id: row.id, title: row.title, filePath: row.filePath }, () => {
6903
- line(`${import_picocolors26.default.bold(row.title)} ${import_picocolors26.default.dim(row.id)}`);
7153
+ line(`${import_picocolors28.default.bold(row.title)} ${import_picocolors28.default.dim(row.id)}`);
6904
7154
  line(
6905
- row.filePath ? `Read it at ${import_picocolors26.default.bold(row.filePath)} (relative to the project folder).` : import_picocolors26.default.dim("This document has no markdown mirror on disk yet.")
7155
+ row.filePath ? `Read it at ${import_picocolors28.default.bold(row.filePath)} (relative to the project folder).` : import_picocolors28.default.dim("This document has no markdown mirror on disk yet.")
6906
7156
  );
6907
7157
  });
6908
7158
  return;
6909
7159
  }
6910
7160
  ok(row, () => {
6911
- line(`${import_picocolors26.default.bold(row.title)} ${import_picocolors26.default.dim(row.id)}`);
6912
- if (row.workItemId) line(import_picocolors26.default.dim(`linked to work item ${row.workItemId}`));
6913
- if (row.filePath) line(import_picocolors26.default.dim(`markdown mirror: ${row.filePath}`));
7161
+ line(`${import_picocolors28.default.bold(row.title)} ${import_picocolors28.default.dim(row.id)}`);
7162
+ if (row.workItemId) line(import_picocolors28.default.dim(`linked to work item ${row.workItemId}`));
7163
+ if (row.filePath) line(import_picocolors28.default.dim(`markdown mirror: ${row.filePath}`));
6914
7164
  line("");
6915
7165
  line(row.contentJson);
6916
7166
  });
@@ -6939,7 +7189,7 @@ function registerDoc(program3) {
6939
7189
  refId: row?.id,
6940
7190
  output: { document: row }
6941
7191
  });
6942
- ok(row, () => line(`Created document ${import_picocolors26.default.bold(row?.id ?? "")} \u2014 ${title}`));
7192
+ ok(row, () => line(`Created document ${import_picocolors28.default.bold(row?.id ?? "")} \u2014 ${title}`));
6943
7193
  })
6944
7194
  );
6945
7195
  doc.command("update <id>").description("Revise an existing document rather than creating a second copy of it").option("--title <text>", "new title").option("--markdown <text>", "replace the body with this markdown").option("--content-json <json>", "replace the body with this rich-text content JSON").action(
@@ -6966,13 +7216,13 @@ function registerDoc(program3) {
6966
7216
  code: "bad_request"
6967
7217
  });
6968
7218
  }
6969
- ok(row, () => line(`Updated document ${import_picocolors26.default.bold(row.id)} \u2014 ${row.title}`));
7219
+ ok(row, () => line(`Updated document ${import_picocolors28.default.bold(row.id)} \u2014 ${row.title}`));
6970
7220
  })
6971
7221
  );
6972
7222
  }
6973
7223
 
6974
7224
  // src/commands/design.ts
6975
- var import_picocolors27 = __toESM(require_picocolors(), 1);
7225
+ var import_picocolors29 = __toESM(require_picocolors(), 1);
6976
7226
  function registerDesign(program3) {
6977
7227
  const design = program3.command("design").description("Read the project's brand (colours, fonts, logo)");
6978
7228
  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(
@@ -6986,11 +7236,11 @@ function registerDesign(program3) {
6986
7236
  if (opts.raw) {
6987
7237
  ok(files, () => {
6988
7238
  if (!files.length) {
6989
- line(import_picocolors27.default.dim("No brand set for this project."));
7239
+ line(import_picocolors29.default.dim("No brand set for this project."));
6990
7240
  return;
6991
7241
  }
6992
7242
  for (const f of files) {
6993
- line(import_picocolors27.default.bold(f.path));
7243
+ line(import_picocolors29.default.bold(f.path));
6994
7244
  line(f.contents);
6995
7245
  line("");
6996
7246
  }
@@ -7007,21 +7257,21 @@ function registerDesign(program3) {
7007
7257
  } : { hasBrand: false, colors: {}, fonts: {}, brand: {}, files: [] };
7008
7258
  ok(summary, () => {
7009
7259
  if (!tokens) {
7010
- line(import_picocolors27.default.dim("No brand set for this project \u2014 choose sensible styling yourself."));
7260
+ line(import_picocolors29.default.dim("No brand set for this project \u2014 choose sensible styling yourself."));
7011
7261
  return;
7012
7262
  }
7013
7263
  for (const [name, value] of Object.entries(tokens.brand)) {
7014
- line(`${import_picocolors27.default.dim(name.padEnd(12))} ${value}`);
7264
+ line(`${import_picocolors29.default.dim(name.padEnd(12))} ${value}`);
7015
7265
  }
7016
7266
  for (const [name, value] of Object.entries(tokens.color)) {
7017
- line(`${import_picocolors27.default.dim(`color.${name}`.padEnd(12))} ${value}`);
7267
+ line(`${import_picocolors29.default.dim(`color.${name}`.padEnd(12))} ${value}`);
7018
7268
  }
7019
7269
  for (const [name, value] of Object.entries(tokens.font)) {
7020
- line(`${import_picocolors27.default.dim(`font.${name}`.padEnd(12))} ${value}`);
7270
+ line(`${import_picocolors29.default.dim(`font.${name}`.padEnd(12))} ${value}`);
7021
7271
  }
7022
7272
  line("");
7023
7273
  line(
7024
- import_picocolors27.default.dim(
7274
+ import_picocolors29.default.dim(
7025
7275
  `Generated into the working tree as ${files.map((f) => f.path).join(", ")} \u2014 wire those in, never edit them.`
7026
7276
  )
7027
7277
  );
@@ -7053,7 +7303,7 @@ function unwrap(group) {
7053
7303
 
7054
7304
  // src/index.ts
7055
7305
  var pkg = {
7056
- version: true ? "0.2.6" : "0.0.0-dev"
7306
+ version: true ? "0.2.7" : "0.0.0-dev"
7057
7307
  };
7058
7308
  var program2 = new Command();
7059
7309
  program2.name("workser").description(
@@ -7086,6 +7336,8 @@ registerOpen(program2);
7086
7336
  registerDoctor(program2);
7087
7337
  registerAgent(program2);
7088
7338
  registerVerify(program2);
7339
+ registerCheckpoint(program2);
7340
+ registerSync(program2);
7089
7341
  registerWorkflow(program2);
7090
7342
  registerApp(program2);
7091
7343
  registerTool(program2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workser/cli",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "Workser CLI — give your local AI agent native DevOps & infrastructure on Workser. The agent runs `workser …` to provision, deploy, and manage real apps.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -5,19 +5,16 @@ description: Provision, deploy, and operate the current Workser app. Use when th
5
5
 
6
6
  # Workser — ship & run the current app from the terminal
7
7
 
8
- You have the `workser` CLI. It lets you act as the user's DevOps engineer for the
9
- **one project** Workser Orbit has linked to this directory: provision and browse its
10
- Neon Postgres database, deploy it, manage its env vars and bucket, provision auth,
11
- and read its logs **on the user's own account**, through the Workser Orbit app
12
- (which handles auth and shows the user what you're doing).
13
-
14
- For writing code, keep using your normal tools. Use `workser` for the last mile.
8
+ You have the `workser` CLI. Use it to operate the **one project** Workser Orbit has
9
+ linked to this directory infrastructure, config, shipping **on the user's own
10
+ account**, through the Orbit app, which handles auth and shows them what you're
11
+ doing. For writing code, keep using your normal tools.
15
12
 
16
13
  ## Read one guide, not all of them
17
14
 
18
- This page is the index. The detail lives in the CLI itself `workser help <topic>`
19
- prints one focused guide, about a screen long. Find your row, run that **one**
20
- command. Don't print them all; you are paying for every line you load.
15
+ This page is the index. `workser help <topic>` prints one focused guide, about a
16
+ screen long. Find your row, run that **one** command — you pay for every line you
17
+ load.
21
18
 
22
19
  | You need to… | Commands | Run |
23
20
  | --- | --- | --- |
@@ -25,6 +22,7 @@ command. Don't print them all; you are paying for every line you load.
25
22
  | Follow the project's brand — colours, fonts, logo | `design …` | `workser help brand` |
26
23
  | Provision or query Postgres; list end users | `db …`, `auth …` | `workser help database` |
27
24
  | Deploy, set env vars, read logs, check a domain | `deploy`, `env …`, `logs`, `versions`, `domain`, `open` | `workser help deploy` |
25
+ | Save work before a risky change, undo it, sync this folder | `checkpoint`, `restore`, `sync` | `workser help version-control` |
28
26
  | Put files in the project's bucket | `storage …` | `workser help storage` |
29
27
  | Read or write products, orders, customers, deals | `business …` | `workser help business` |
30
28
  | Use the project's own Neon buckets or functions | `neon …` | `workser help neon` |
@@ -35,9 +33,8 @@ command. Don't print them all; you are paying for every line you load.
35
33
  | Record finished output, or ask the user a question | `artifact …`, `ask` | `workser help deliverables` |
36
34
  | Control this machine — files, shell, screen, browser | `tool …` | `workser help computer-use` |
37
35
 
38
- `workser help` with no topic lists them. **The CLI is the source of truth** — it
39
- ships these guides itself, so they match the version you are running, and
40
- `workser <command> --help` is generated from the implementation.
36
+ `workser help` lists them all; `workser <command> --help` gives exact flags. Both
37
+ come from the CLI itself, so they match the version you are running.
41
38
 
42
39
  ## Orientation (no guide needed)
43
40
 
@@ -123,6 +120,8 @@ workser board close <id> --json # 9. the Board now matches reality
123
120
  - `no_project` → no project is linked here; the user links it in Orbit.
124
121
  - `owner_only` → an owner action; tell the user to do it in Orbit, then continue.
125
122
  - `awaiting_approval` → user must approve in Orbit; then retry.
123
+ - `needs_local_app` → this machine has no Workser app, so folder commands
124
+ can't run. Say so; don't reach for `git` instead.
126
125
 
127
126
  Keep the user informed in plain language ("Provisioned a database and deployed —
128
127
  it's live at <url>"), not raw JSON.
@@ -0,0 +1,66 @@
1
+ ---
2
+ topic: version-control
3
+ title: Saving, undoing & syncing your work
4
+ summary: Checkpoint before risky edits, restore when they go wrong, sync with Workser.
5
+ commands: [checkpoint, restore, sync]
6
+ ---
7
+
8
+ # Saving, undoing & syncing your work
9
+
10
+ ```
11
+ workser checkpoint ["what you're about to try"] # save the folder as it stands now
12
+ workser restore # go back to the newest checkpoint
13
+ workser restore --list # see the checkpoints you can go back to
14
+ workser restore <ref> # go back to a specific one
15
+ workser sync [--branch dev] [--app <id>] # reconcile this folder with Workser
16
+ ```
17
+
18
+ ## Why these exist instead of git
19
+
20
+ This folder is a git repository, but **the history is Workser's**. There is no
21
+ remote: code moves to and from Workser over its API as git bundles, using the
22
+ same session everything else here uses. No credential is read, and the user's
23
+ own git keys and identity are never touched.
24
+
25
+ That is what makes any of this work on a computer with no access to the
26
+ Workser-managed repository — which is every computer. Nobody has repo access;
27
+ the API does.
28
+
29
+ So the ordinary git verbs are wrong here, and these three replace them:
30
+
31
+ | Instead of | Run |
32
+ |---|---|
33
+ | `git stash` | `workser checkpoint` |
34
+ | `git commit` | `workser checkpoint` |
35
+ | `git reset --hard` / `git checkout .` | `workser restore` |
36
+ | `git pull` / `git push` | `workser sync` |
37
+
38
+ Reading is always fine: `git log`, `git diff`, `git show`, `git status`.
39
+
40
+ ## Notes that matter
41
+
42
+ - **Checkpoint before anything you might want to take back** — a refactor across
43
+ many files, a dependency upgrade, deleting something large. It costs a second.
44
+ - **`git stash` is the one that actually loses work.** Workser publishes what is
45
+ on disk, so a deploy while your changes are stashed ships the version *without
46
+ them* — and if the run is stopped between `stash` and `stash pop`, the user
47
+ opens their folder and their work is gone. `workser checkpoint` has neither
48
+ failure mode.
49
+ - **Restoring never discards anything.** The current state is saved first, then
50
+ the older state is restored on top. If you restore the wrong thing, restore
51
+ again. Say this to the user — it is the reason they can afford to say yes.
52
+ - **These are the same checkpoints as the app's Undo button.** One history, not
53
+ two: a checkpoint you take here appears in Workser, and vice versa.
54
+ - **`sync` pulls before it pushes.** If it reports `diverged`, both sides changed
55
+ and it stops rather than picking a winner — tell the user to resolve it in
56
+ Workser instead of forcing it.
57
+ - **These three need the Workser app running on this computer**, because they
58
+ work with files. If you get `needs_local_app`, you are on a machine that only
59
+ has the CLI and a token — say so rather than looking for a workaround. Every
60
+ read-only command (`projects`, `env`, `db`, `logs`, `status`) still works.
61
+
62
+ ## Shipping is separate
63
+
64
+ `workser sync` reconciles the folder. `workser deploy` puts it in front of
65
+ users. Syncing does not deploy, and deploying does not require you to sync
66
+ first — deploy publishes what is on disk. See `reference/deploy.md`.