@workser/cli 0.6.22 → 0.6.26
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/AGENTS.md +17 -14
- package/dist/index.js +93 -248
- package/package.json +1 -1
- package/skills/workser/SKILL.md +15 -8
- package/skills/workser/reference/deliverables.md +7 -3
- package/skills/workser/reference/images.md +27 -0
- package/skills/workser/reference/sdlc-entities.md +11 -8
- /package/skills/workser/reference/{desktop.md → desktop.md.off} +0 -0
package/AGENTS.md
CHANGED
|
@@ -133,16 +133,18 @@ workser requirement list | show <id> | create "<title>" --body <t> | update <id>
|
|
|
133
133
|
workser doc list | show <id> [--markdown]
|
|
134
134
|
workser doc create "<title>" --markdown <text> | doc update <id> --markdown <text>
|
|
135
135
|
```
|
|
136
|
-
**Read before you plan** — `
|
|
137
|
-
|
|
138
|
-
quietly reverse a decision.
|
|
139
|
-
|
|
140
|
-
**
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
`
|
|
136
|
+
**Read before you plan** — `goal list`, `task list`, and `decision list` tell you
|
|
137
|
+
what the team is delivering, what someone is already doing, and what this project
|
|
138
|
+
chose on purpose, so you don't re-file work or quietly reverse a decision.
|
|
139
|
+
|
|
140
|
+
**Choose the planning level before creating work.** A business outcome with two or
|
|
141
|
+
more owner-visible deliveries is a goal: `goal create` proposes its ordered phases,
|
|
142
|
+
then you stop until the owner agrees the shape. A task is one delivery; `task subtask
|
|
143
|
+
add` records the specialist steps inside it. Do not call subtasks phases, and do not
|
|
144
|
+
create the goal's tasks in the proposal turn. If you spot a bad title, role, note, or
|
|
145
|
+
file scope after creation, correct that existing row with `task subtask update <id>`;
|
|
146
|
+
do not create a replacement or duplicate the plan. The legacy Board is not a planning
|
|
147
|
+
surface for agents.
|
|
146
148
|
|
|
147
149
|
**Keep it true as you work.** `board move <id> in-progress` when you pick it up,
|
|
148
150
|
`in-review` when it's ready to look at, `board close <id>` when it's done and
|
|
@@ -265,12 +267,13 @@ keep your own context lean and get a specialized second perspective; a non-zero
|
|
|
265
267
|
## Example
|
|
266
268
|
```bash
|
|
267
269
|
workser status --json # orient: which project, last deploy
|
|
268
|
-
workser
|
|
269
|
-
workser
|
|
270
|
-
workser
|
|
270
|
+
workser goal list --json # long-running outcomes and their phases
|
|
271
|
+
workser task list --json # the work already filed
|
|
272
|
+
workser decision list --json # what the project chose on purpose
|
|
273
|
+
# For a big outcome: goal create ... --phase ... --json, then STOP for agreement.
|
|
274
|
+
# For one delivery: task create ..., then task subtask add ... for its steps.
|
|
271
275
|
workser env set STRIPE_KEY=sk_live_… --json
|
|
272
276
|
# … you write the app code with your normal tools …
|
|
273
277
|
workser deploy --prod --watch --json # -> .data.url is the live URL
|
|
274
|
-
workser board close <id> --json # the Board now matches reality
|
|
275
278
|
```
|
|
276
279
|
Report results to the user in plain language, not raw JSON.
|
package/dist/index.js
CHANGED
|
@@ -3584,6 +3584,7 @@ function ownerOnly(opts) {
|
|
|
3584
3584
|
|
|
3585
3585
|
// src/output.ts
|
|
3586
3586
|
var OUT_OF_SCOPE_EXIT = 7;
|
|
3587
|
+
var BILLING_EXIT = 8;
|
|
3587
3588
|
var mode = "text";
|
|
3588
3589
|
var quiet = false;
|
|
3589
3590
|
function configureOutput(opts) {
|
|
@@ -3645,6 +3646,18 @@ function fail(err) {
|
|
|
3645
3646
|
process.stderr.write(import_picocolors.default.dim(" Approve the action in Workser Orbit, then retry.\n"));
|
|
3646
3647
|
} else if (e.code === "owner_only") {
|
|
3647
3648
|
process.stderr.write(import_picocolors.default.dim(" This is an owner action \u2014 do it in the Workser Orbit app.\n"));
|
|
3649
|
+
} else if (e.code === "insufficient_credits") {
|
|
3650
|
+
process.stderr.write(
|
|
3651
|
+
import_picocolors.default.dim(
|
|
3652
|
+
" The organization is out of AI credits. Top up in Workser Orbit (Billing) \u2014 retrying will not help.\n"
|
|
3653
|
+
)
|
|
3654
|
+
);
|
|
3655
|
+
} else if (e.code === "image_quota_reached") {
|
|
3656
|
+
process.stderr.write(
|
|
3657
|
+
import_picocolors.default.dim(
|
|
3658
|
+
" This month's plan allowance is spent. Upgrade the plan, or wait for the month to roll over \u2014 retrying will not help.\n"
|
|
3659
|
+
)
|
|
3660
|
+
);
|
|
3648
3661
|
}
|
|
3649
3662
|
}
|
|
3650
3663
|
process.exit(exitCodeFor(e));
|
|
@@ -3655,6 +3668,9 @@ function exitCodeFor(e) {
|
|
|
3655
3668
|
if (e.code === "awaiting_approval") return 5;
|
|
3656
3669
|
if (e.code === "owner_only") return OWNER_ONLY_EXIT;
|
|
3657
3670
|
if (e.code === "out_of_scope") return OUT_OF_SCOPE_EXIT;
|
|
3671
|
+
if (e.code === "insufficient_credits" || e.code === "image_quota_reached") {
|
|
3672
|
+
return BILLING_EXIT;
|
|
3673
|
+
}
|
|
3658
3674
|
return 1;
|
|
3659
3675
|
}
|
|
3660
3676
|
|
|
@@ -4575,7 +4591,7 @@ forever; if it does, carry on and state clearly what you assumed.
|
|
|
4575
4591
|
## Asking for an app that does not exist yet
|
|
4576
4592
|
|
|
4577
4593
|
The project needs a kind of app it does not have \u2014 a backend for the phone app, a
|
|
4578
|
-
|
|
4594
|
+
phone version of the website. **You cannot create one yourself**, by design: apps
|
|
4579
4595
|
are real infrastructure on the owner's account. Ask, and their click creates it.
|
|
4580
4596
|
The answer gives you the new app's id.
|
|
4581
4597
|
|
|
@@ -4590,15 +4606,19 @@ workser ask "The phone app needs an API to hold its data. Add one?" \\
|
|
|
4590
4606
|
|---|---|
|
|
4591
4607
|
| \`web\` | Next.js site on Workser hosting |
|
|
4592
4608
|
| \`mobile\` | Expo / React Native phone app |
|
|
4593
|
-
| \`desktop\` | Next.js + Electron, installs on a Mac or PC |
|
|
4594
4609
|
| \`api\` | backend service \u2014 \`api-hono\` (TypeScript) or \`api-python\` (FastAPI) |
|
|
4595
4610
|
|
|
4596
4611
|
**Name the kind you actually mean.** Asking for \`web\` because you are unsure is
|
|
4597
|
-
how a
|
|
4612
|
+
how a phone app gets created as a website: the card shows the owner the kind
|
|
4598
4613
|
you named, they approve THAT, and the wrong app is provisioned under your own
|
|
4599
4614
|
sentence asking for the right one. An unknown kind is refused with the list
|
|
4600
4615
|
above rather than guessed at \u2014 read it and ask again.
|
|
4601
4616
|
|
|
4617
|
+
**There is no desktop app type.** Workser builds websites, phone apps and
|
|
4618
|
+
backend services. If the owner asks for a Mac or Windows app, say plainly that
|
|
4619
|
+
Workser cannot make one yet \u2014 do not offer a web app dressed as a window, and
|
|
4620
|
+
do not build a "desktop-style" page instead. That is not what they asked for.
|
|
4621
|
+
|
|
4602
4622
|
**Never ask for a secret value this way** \u2014 the answer is stored and displayed. Ask
|
|
4603
4623
|
*where* a key should go, then have the user set it (\`workser env set\` writes it
|
|
4604
4624
|
without you ever seeing it).
|
|
@@ -4674,105 +4694,6 @@ workser artifact add --url https://acme.workser.app --kind app -t "Storefront"
|
|
|
4674
4694
|
\`\`\`
|
|
4675
4695
|
|
|
4676
4696
|
See \`reference/deliverables.md\`.
|
|
4677
|
-
`
|
|
4678
|
-
},
|
|
4679
|
-
{
|
|
4680
|
-
topic: "desktop",
|
|
4681
|
-
title: "Desktop apps \u2014 one codebase, a window, and an installer",
|
|
4682
|
-
summary: "How a Workser desktop app is built, what breaks only after it is installed, and how to produce and publish a signed installer.",
|
|
4683
|
-
commands: ["project", "env", "deploy"],
|
|
4684
|
-
source: "skills/workser/reference/desktop.md",
|
|
4685
|
-
body: `# Desktop apps
|
|
4686
|
-
|
|
4687
|
-
A Workser desktop app is **one Next.js codebase with two ways to run**: a normal
|
|
4688
|
-
web build for development and preview, and an Electron shell that loads the
|
|
4689
|
-
static export from disk and gets packaged into an installer.
|
|
4690
|
-
|
|
4691
|
-
There is no deploy. A desktop app has no Vercel project and no URL \u2014 its
|
|
4692
|
-
preview is the app running locally, and its deliverable is a file someone
|
|
4693
|
-
double-clicks.
|
|
4694
|
-
|
|
4695
|
-
## The rule that breaks everything, and only after install
|
|
4696
|
-
|
|
4697
|
-
**The app must stay statically exportable.** A packaged Electron app loads from
|
|
4698
|
-
\`file://\` and has **no Node server**. So none of this exists once it is
|
|
4699
|
-
installed:
|
|
4700
|
-
|
|
4701
|
-
- server components that fetch at request time
|
|
4702
|
-
- server actions (\`"use server"\`)
|
|
4703
|
-
- route handlers under \`app/api/*\`
|
|
4704
|
-
- \`next/image\` optimisation
|
|
4705
|
-
|
|
4706
|
-
Every one of them works in \`npm run dev\` **and** in the local preview, and fails
|
|
4707
|
-
only on the owner's machine. That is the one direction of error nobody catches,
|
|
4708
|
-
so \`next.config.mjs\` keeps \`output: "export"\` to make the build fail loudly
|
|
4709
|
-
instead. Do not remove it to "fix" an import.
|
|
4710
|
-
|
|
4711
|
-
Need a server? Use the project's api app \u2014 \`workser project apps\` to find it,
|
|
4712
|
-
then read its URL and set \`NEXT_PUBLIC_API_URL\`. See \`workser help apps\`.
|
|
4713
|
-
|
|
4714
|
-
## Secrets: this is a public client
|
|
4715
|
-
|
|
4716
|
-
Anyone who installs the app can read every string inside it. So:
|
|
4717
|
-
|
|
4718
|
-
- \`NEXT_PUBLIC_*\` only, and nothing behind that prefix is private
|
|
4719
|
-
- never a database URL, an API key, or a \`wsgw_\` gateway key
|
|
4720
|
-
- anything privileged lives in the api app and is reached over HTTP
|
|
4721
|
-
|
|
4722
|
-
The manifest deliberately does not declare \`AI_GATEWAY_API_KEY\` for this type,
|
|
4723
|
-
which is what stops Workser seeding one. Do not add it.
|
|
4724
|
-
|
|
4725
|
-
## The two processes
|
|
4726
|
-
|
|
4727
|
-
| File | Runs where | May do |
|
|
4728
|
-
| --- | --- | --- |
|
|
4729
|
-
| \`electron/main.js\` | Node, on the machine | files, tray, windows, updates |
|
|
4730
|
-
| \`app/**\` | the renderer, sandboxed | ordinary web code, no Node |
|
|
4731
|
-
| \`electron/preload.js\` | the bridge | expose **named functions** only |
|
|
4732
|
-
|
|
4733
|
-
\`contextIsolation: true\` and \`nodeIntegration: false\` are not defaults to tidy
|
|
4734
|
-
up \u2014 turning either off hands full filesystem and process access to page code.
|
|
4735
|
-
Add a capability by exporting one named function from preload, never by handing
|
|
4736
|
-
the renderer \`ipcRenderer\`.
|
|
4737
|
-
|
|
4738
|
-
## Building and shipping
|
|
4739
|
-
|
|
4740
|
-
\`\`\`bash
|
|
4741
|
-
npm run dev # browser only, fastest loop
|
|
4742
|
-
npm run dev:desktop # the same app inside a real Electron window
|
|
4743
|
-
npm run build # static export -> out/
|
|
4744
|
-
npm run dist:desktop # the installer -> release/
|
|
4745
|
-
\`\`\`
|
|
4746
|
-
|
|
4747
|
-
In the app, **Publish \u2192 Your installer** does the same thing with a target
|
|
4748
|
-
picker, progress, and a download link at the end.
|
|
4749
|
-
|
|
4750
|
-
## Signing \u2014 the owner's certificate, never Workser's
|
|
4751
|
-
|
|
4752
|
-
An unsigned build runs perfectly on the machine that made it and tells every
|
|
4753
|
-
other machine the app is damaged (macOS) or trips SmartScreen (Windows). That
|
|
4754
|
-
is the failure an owner cannot diagnose, so say it before they send the file.
|
|
4755
|
-
|
|
4756
|
-
\`electron-builder\` reads these from the environment; set them with
|
|
4757
|
-
\`workser env set --app <id>\`:
|
|
4758
|
-
|
|
4759
|
-
| Platform | Keys |
|
|
4760
|
-
| --- | --- |
|
|
4761
|
-
| macOS | \`CSC_LINK\`, \`CSC_KEY_PASSWORD\` |
|
|
4762
|
-
| macOS notarize | \`APPLE_ID\`, \`APPLE_APP_SPECIFIC_PASSWORD\`, \`APPLE_TEAM_ID\` |
|
|
4763
|
-
| Windows | \`WIN_CSC_LINK\`, \`WIN_CSC_KEY_PASSWORD\` |
|
|
4764
|
-
|
|
4765
|
-
A Mac app can only be built on a Mac \u2014 \`codesign\` and \`hdiutil\` are Apple's and
|
|
4766
|
-
have no substitute.
|
|
4767
|
-
|
|
4768
|
-
## Auto-update
|
|
4769
|
-
|
|
4770
|
-
Publishing uploads the installer **and** the update metadata
|
|
4771
|
-
(\`latest-mac.yml\` / \`latest.yml\`) beside it. \`electron-updater\` reads only that
|
|
4772
|
-
metadata, so an installer published alone gives you a download that works once
|
|
4773
|
-
and an app that can never update itself \u2014 and nobody notices until release two.
|
|
4774
|
-
Keep \`publish.url\` in \`electron-builder.yml\` pointed at the prefix the publish
|
|
4775
|
-
step reports back.
|
|
4776
4697
|
`
|
|
4777
4698
|
},
|
|
4778
4699
|
{
|
|
@@ -5071,6 +4992,7 @@ up front \u2014 most of the apps a goal will touch don't exist when it's propose
|
|
|
5071
4992
|
workser image generate "<prompt>" # alias: workser image gen
|
|
5072
4993
|
-r, --reference <url...> # condition on existing images (up to 4)
|
|
5073
4994
|
-o, --output <path> # also download the first image locally
|
|
4995
|
+
workser image usage # can I generate right now, and what would stop me
|
|
5074
4996
|
\`\`\`
|
|
5075
4997
|
|
|
5076
4998
|
Returns the generated image's public URL, so the usual move is to generate, then use
|
|
@@ -5094,6 +5016,32 @@ workser image gen "same van, from the side" -r https://\u2026 -o ./public/van.pn
|
|
|
5094
5016
|
- **Placeholder art is not a deliverable.** Generating a hero image to unblock a
|
|
5095
5017
|
layout is fine; shipping it as the user's brand asset without asking is not.
|
|
5096
5018
|
|
|
5019
|
+
## This is metered, and it can be refused
|
|
5020
|
+
|
|
5021
|
+
Every image is billed to the organization's credit wallet, and each plan includes
|
|
5022
|
+
a monthly number of them. Three gates run server-side before anything is drawn \u2014
|
|
5023
|
+
the plan tier, the monthly allowance, then the wallet \u2014 so a call can come back
|
|
5024
|
+
refused having spent nothing.
|
|
5025
|
+
|
|
5026
|
+
\`\`\`bash
|
|
5027
|
+
workser image usage --json # {"limit":10,"used":3,"remaining":7,
|
|
5028
|
+
# "credits":{"available":41.2,"requiredPerImage":4.12,"sufficient":true},
|
|
5029
|
+
# "canGenerate":true,"blockedBy":null}
|
|
5030
|
+
\`\`\`
|
|
5031
|
+
|
|
5032
|
+
- **Check before a batch, not after.** Planning six images is a plan that needs
|
|
5033
|
+
six times \`requiredPerImage\` in the wallet. \`workser image usage\` answers that
|
|
5034
|
+
before any of them cost anything; \`blockedBy\` names which gate would stop you
|
|
5035
|
+
(\`plan\`, \`quota\`, \`credits\`), and it exits non-zero when generation is blocked.
|
|
5036
|
+
- **A refusal is final, not a hiccup \u2014 do NOT retry it.** \`insufficient_credits\`
|
|
5037
|
+
(HTTP 402) and \`image_quota_reached\` (403) come back as those exact codes in
|
|
5038
|
+
\`--json\`, and the CLI exits 8 for both. Retrying cannot succeed and every
|
|
5039
|
+
attempt is another paid call the owner did not ask for. Stop, and tell the
|
|
5040
|
+
owner what to do: top up credits, or upgrade the plan.
|
|
5041
|
+
- **You cannot see their balance any other way**, so do not guess at it from how
|
|
5042
|
+
many images you have already made \u2014 a refusal at image four of six leaves the
|
|
5043
|
+
work half-done and looks like a bug.
|
|
5044
|
+
|
|
5097
5045
|
## Understanding media you can't natively see or hear
|
|
5098
5046
|
|
|
5099
5047
|
The fallback for a text-only model, or media you have no other way to reach: describe
|
|
@@ -5420,14 +5368,17 @@ the same way when the task touches documented behaviour.
|
|
|
5420
5368
|
\`--infra\` match on the server, so the answer covers the whole project rather
|
|
5421
5369
|
than the first page of it. \`decision tag <id>\` files one that already exists.
|
|
5422
5370
|
|
|
5423
|
-
## Work with
|
|
5371
|
+
## Work with one task's steps \u2192 subtasks + a plan doc, before you build
|
|
5424
5372
|
|
|
5425
|
-
|
|
5426
|
-
|
|
5373
|
+
Do not confuse two levels of planning. A **goal phase** is an owner-visible
|
|
5374
|
+
delivery across tasks; create it with \`workser goal create\` and wait for agreement
|
|
5375
|
+
before filing work. A **subtask** is one teammate's implementation step inside a
|
|
5376
|
+
single approved task. File those steps before building \u2014 not afterwards, and not
|
|
5377
|
+
only in your reply, which is gone once the conversation scrolls.
|
|
5427
5378
|
|
|
5428
5379
|
\`\`\`bash
|
|
5429
|
-
# the
|
|
5430
|
-
workser task subtask add "
|
|
5380
|
+
# the implementation steps \u2014 this task's own subtask list, not the Board
|
|
5381
|
+
workser task subtask add "Create schema + migration" --role api --note "\u2026"
|
|
5431
5382
|
|
|
5432
5383
|
# the plan's narrative, ONE doc, deliberately NOT linked to a subtask
|
|
5433
5384
|
workser doc create "Checkout \u2014 plan" --kind plan --markdown "$(cat plan.md)" --json
|
|
@@ -5447,9 +5398,9 @@ Never tell the user a subtask is locked, and never file a second one alongside
|
|
|
5447
5398
|
the wrong one \u2014 a plan with a duplicate phase in it is a plan nobody can read
|
|
5448
5399
|
the progress of.
|
|
5449
5400
|
|
|
5450
|
-
**Don't pass \`--work-item\` for a
|
|
5451
|
-
its card and is *hidden* from the Docs panel; a plan spanning
|
|
5452
|
-
belongs to the project, not to
|
|
5401
|
+
**Don't pass \`--work-item\` for a goal-wide plan.** A linked document renders on
|
|
5402
|
+
its card and is *hidden* from the Docs panel; a plan spanning several goal phases
|
|
5403
|
+
belongs to the project, not to the first task.
|
|
5453
5404
|
|
|
5454
5405
|
The bar: if the user closed this conversation now, the subtask list should still
|
|
5455
5406
|
show what's left and the doc should still explain the plan to whoever continues
|
|
@@ -9208,6 +9159,32 @@ function registerImage(program3) {
|
|
|
9208
9159
|
ok(res, () => line(res?.answer ?? ""));
|
|
9209
9160
|
})
|
|
9210
9161
|
);
|
|
9162
|
+
image.command("usage").description(
|
|
9163
|
+
"Whether an image can be generated right now \u2014 monthly allowance, credit balance, and what would block it"
|
|
9164
|
+
).action(
|
|
9165
|
+
action(async ({ ctx }) => {
|
|
9166
|
+
const projectId = requireProject(ctx);
|
|
9167
|
+
const status = await api(
|
|
9168
|
+
ctx,
|
|
9169
|
+
`/v1/projects/${projectId}/images/usage`
|
|
9170
|
+
);
|
|
9171
|
+
ok(status, () => {
|
|
9172
|
+
const monthly = status.limit === null ? "no monthly limit on this plan" : `${status.used} of ${status.limit} used this month, ${status.remaining} left`;
|
|
9173
|
+
line(`This month: ${monthly}`);
|
|
9174
|
+
line(
|
|
9175
|
+
`Credits: ${status.credits.available.toFixed(2)} available, about ${status.credits.requiredPerImage.toFixed(2)} per image`
|
|
9176
|
+
);
|
|
9177
|
+
if (status.canGenerate) {
|
|
9178
|
+
success("Can generate now");
|
|
9179
|
+
} else {
|
|
9180
|
+
warn(
|
|
9181
|
+
status.blockedBy === "credits" ? "Blocked: not enough organization credits. Top up in Workser Orbit (Billing)." : status.blockedBy === "quota" ? "Blocked: this month's plan allowance is spent. Upgrade, or wait for the month to roll over." : "Blocked: this plan cannot generate images. Upgrade to Spark or higher."
|
|
9182
|
+
);
|
|
9183
|
+
}
|
|
9184
|
+
});
|
|
9185
|
+
if (!status.canGenerate) process.exitCode = BILLING_EXIT;
|
|
9186
|
+
})
|
|
9187
|
+
);
|
|
9211
9188
|
}
|
|
9212
9189
|
async function download(url, output) {
|
|
9213
9190
|
const target = resolve4(output);
|
|
@@ -9291,7 +9268,10 @@ var TYPES = [
|
|
|
9291
9268
|
var APP_TYPES = [
|
|
9292
9269
|
"web",
|
|
9293
9270
|
"mobile",
|
|
9294
|
-
|
|
9271
|
+
// NOT OFFERED AT FIRST LAUNCH (2026-09-05) — `desktop` is `creatable: false`
|
|
9272
|
+
// in core-api, so the card would error on the owner's click. Re-enable
|
|
9273
|
+
// together with that flag; grep the marker.
|
|
9274
|
+
// "desktop",
|
|
9295
9275
|
// `api` is the shorthand; the two concrete spellings pick the runtime and
|
|
9296
9276
|
// are accepted at this boundary for an agent that knows which one it wants.
|
|
9297
9277
|
"api",
|
|
@@ -10594,153 +10574,18 @@ function paceLine(pace) {
|
|
|
10594
10574
|
}
|
|
10595
10575
|
|
|
10596
10576
|
// src/role-guard.ts
|
|
10597
|
-
var READS = [
|
|
10598
|
-
// `requirement` sits beside `doc` and `decision` because it is the same kind
|
|
10599
|
-
// of thing and was simply forgotten: the verb shipped, no role could run it,
|
|
10600
|
-
// and on 2026-08-23 a channel PM reported to the owner that "requirements are
|
|
10601
|
-
// not readable by this PM role" — which was exactly true, for every role.
|
|
10602
|
-
"task",
|
|
10603
|
-
"board",
|
|
10604
|
-
"doc",
|
|
10605
|
-
"decision",
|
|
10606
|
-
"requirement",
|
|
10607
|
-
"memory",
|
|
10608
|
-
"search",
|
|
10609
|
-
"verify",
|
|
10610
|
-
"logs",
|
|
10611
|
-
"status",
|
|
10612
|
-
"help",
|
|
10613
|
-
"whoami",
|
|
10614
|
-
"login",
|
|
10615
|
-
"auth",
|
|
10616
|
-
"project",
|
|
10617
|
-
"open",
|
|
10618
|
-
"doctor",
|
|
10619
|
-
// Both READ and report. `scan` reads files and shells out to npm; `health`
|
|
10620
|
-
// makes a GET request to an address that is already public. Neither can
|
|
10621
|
-
// change anything, which is why the roles that exist to look — qa, security,
|
|
10622
|
-
// sre, analyst — get them without getting anything else.
|
|
10623
|
-
"scan",
|
|
10624
|
-
"health",
|
|
10625
|
-
// Read-only views of what is running. Added with Phase 6a: an SRE that can
|
|
10626
|
-
// read logs but cannot list deployments or read the app's address is being
|
|
10627
|
-
// asked to diagnose an outage with one eye shut.
|
|
10628
|
-
"urls",
|
|
10629
|
-
"deployments",
|
|
10630
|
-
// Reading the plan and what is used against it. An agent proposing "add
|
|
10631
|
-
// another project" can only sensibly propose it if it can find out the plan
|
|
10632
|
-
// allows two and two already exist.
|
|
10633
|
-
"usage",
|
|
10634
|
-
// RECORDING WHAT YOU PRODUCED IS NOT CHANGING THE PROJECT.
|
|
10635
|
-
// Every dispatched role is told, in its own preamble, to run `workser
|
|
10636
|
-
// artifact list` before non-trivial work and `workser artifact add` when it
|
|
10637
|
-
// finishes something the owner should get. Five roles — pm, qa, security,
|
|
10638
|
-
// analyst, sre — were then refused the verb here, so the instruction and the
|
|
10639
|
-
// guard contradicted each other: the agent kept trying to obey an order this
|
|
10640
|
-
// file would not let it obey, and burned its run doing it. It belongs beside
|
|
10641
|
-
// `doc`, `decision` and `board`, which are already in this list and also
|
|
10642
|
-
// have `create` subcommands — these write the project's RECORD, not the
|
|
10643
|
-
// project. What stops a reviewer editing code is its filesystem mode, and
|
|
10644
|
-
// that is untouched.
|
|
10645
|
-
"artifact",
|
|
10646
|
-
// LEAVING A FACT FOR THE TEAM IS NOT CHANGING THE PROJECT — the same
|
|
10647
|
-
// argument as `artifact` directly above, and it belongs in READS rather than
|
|
10648
|
-
// BUILDS on purpose. The roles that DISCOVER things are the reading ones: a
|
|
10649
|
-
// tester that found the real cause, an analyst that found the actual column
|
|
10650
|
-
// name, a security engineer that found where a key is read from. A shared
|
|
10651
|
-
// memory only builders could write to would be missing most of what is worth
|
|
10652
|
-
// sharing. See the daemon's `team-memory.ts`.
|
|
10653
|
-
"note"
|
|
10654
|
-
];
|
|
10655
|
-
var BUILDS = [
|
|
10656
|
-
...READS,
|
|
10657
|
-
"app",
|
|
10658
|
-
"env",
|
|
10659
|
-
"db",
|
|
10660
|
-
"storage",
|
|
10661
|
-
"checkpoint",
|
|
10662
|
-
"image",
|
|
10663
|
-
"design",
|
|
10664
|
-
"ask",
|
|
10665
|
-
"sync",
|
|
10666
|
-
"tool",
|
|
10667
|
-
"workflow",
|
|
10668
|
-
"neon",
|
|
10669
|
-
"business"
|
|
10670
|
-
];
|
|
10671
|
-
var ROLE_VERBS = {
|
|
10672
|
-
pm: [...READS, "ask", "app"],
|
|
10673
|
-
// `note` reaches this via READS.
|
|
10674
|
-
architect: [...BUILDS, "versions"],
|
|
10675
|
-
web: BUILDS,
|
|
10676
|
-
api: BUILDS,
|
|
10677
|
-
mobile: BUILDS,
|
|
10678
|
-
python: BUILDS,
|
|
10679
|
-
automation: BUILDS,
|
|
10680
|
-
designer: BUILDS,
|
|
10681
|
-
qa: READS,
|
|
10682
|
-
security: READS,
|
|
10683
|
-
analyst: READS,
|
|
10684
|
-
sre: [...READS, "deploy", "domain", "versions"],
|
|
10685
|
-
devops: [...BUILDS, "deploy", "domain", "versions"]
|
|
10686
|
-
// NOTE: `deployments` reaches READS above, so every role can LIST and
|
|
10687
|
-
// INSPECT. That is correct — history is a read. The two verbs that change
|
|
10688
|
-
// production (`promote`, `rollback`) are not gated here at all, and must not
|
|
10689
|
-
// be: they are gated in the DAEMON, as `deploy.prod`, which is a door "just
|
|
10690
|
-
// do it" cannot open. A second, verb-name-based rule here would be a weaker
|
|
10691
|
-
// copy of a control that already works.
|
|
10692
|
-
};
|
|
10693
10577
|
var NEVER = {
|
|
10694
|
-
// Approving is the owner's, full stop. An agent that can approve the plan it
|
|
10695
|
-
// proposed has removed the only gate this product has.
|
|
10696
10578
|
"task approval": "Only the owner can approve a plan."
|
|
10697
10579
|
};
|
|
10698
|
-
var READ_PAIRS = /* @__PURE__ */ new Set([
|
|
10699
|
-
// The project's own brand and design notes.
|
|
10700
|
-
"design show",
|
|
10701
|
-
// What exists, and what it is wired to.
|
|
10702
|
-
"app list",
|
|
10703
|
-
"app tools",
|
|
10704
|
-
// Shape, never contents-by-arbitrary-SQL.
|
|
10705
|
-
"db list",
|
|
10706
|
-
"db tables",
|
|
10707
|
-
"db schema",
|
|
10708
|
-
// What is stored, not what is in it.
|
|
10709
|
-
"storage list",
|
|
10710
|
-
"storage ls",
|
|
10711
|
-
"checkpoint list",
|
|
10712
|
-
"workflow list",
|
|
10713
|
-
"workflow get",
|
|
10714
|
-
"workflow runs",
|
|
10715
|
-
"workflow nodes",
|
|
10716
|
-
"tool list",
|
|
10717
|
-
// The business data an analyst exists to read.
|
|
10718
|
-
"business resources",
|
|
10719
|
-
"business list",
|
|
10720
|
-
"business get",
|
|
10721
|
-
// The database service's own inventory.
|
|
10722
|
-
"neon status",
|
|
10723
|
-
"neon list"
|
|
10724
|
-
]);
|
|
10725
10580
|
function assertRoleMayRun(argv) {
|
|
10726
10581
|
const role = (process.env.WORKSER_ROLE ?? "").trim();
|
|
10727
10582
|
if (!role) return;
|
|
10728
10583
|
const commandArgv = stripLeadingGlobalOptions(argv);
|
|
10729
|
-
|
|
10730
|
-
if (!verb) return;
|
|
10584
|
+
if (!commandArgv[0]) return;
|
|
10731
10585
|
const pair = `${commandArgv[0]} ${commandArgv[1] ?? ""}`.trim();
|
|
10732
10586
|
if (NEVER[pair] && !(pair === "task approval" && (commandArgv[2] === "request" || !commandArgv[2]))) {
|
|
10733
10587
|
throw new WorkserError(NEVER[pair], { code: "role_forbidden" });
|
|
10734
10588
|
}
|
|
10735
|
-
if (READ_PAIRS.has(pair)) return;
|
|
10736
|
-
const allowed = ROLE_VERBS[role];
|
|
10737
|
-
const list = allowed ?? READS;
|
|
10738
|
-
if (!list.includes(verb)) {
|
|
10739
|
-
throw new WorkserError(
|
|
10740
|
-
`The ${role} role can't run \`workser ${verb}\`. Report what you found instead, and the step that owns this will do it.`,
|
|
10741
|
-
{ code: "role_forbidden" }
|
|
10742
|
-
);
|
|
10743
|
-
}
|
|
10744
10589
|
}
|
|
10745
10590
|
function stripLeadingGlobalOptions(argv) {
|
|
10746
10591
|
const takesValue = /* @__PURE__ */ new Set([
|
|
@@ -12445,7 +12290,7 @@ function colour(d) {
|
|
|
12445
12290
|
|
|
12446
12291
|
// src/index.ts
|
|
12447
12292
|
var pkg = {
|
|
12448
|
-
version: true ? "0.6.
|
|
12293
|
+
version: true ? "0.6.26" : "0.0.0-dev"
|
|
12449
12294
|
};
|
|
12450
12295
|
var program2 = new Command();
|
|
12451
12296
|
program2.name("workser").description(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workser/cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.26",
|
|
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",
|
package/skills/workser/SKILL.md
CHANGED
|
@@ -22,7 +22,6 @@ screen long. Find your row, run that **one** command — every line costs you.
|
|
|
22
22
|
| Follow the project's brand — colours, fonts, logo | `design …` | `workser help brand` |
|
|
23
23
|
| Provision or query Postgres; list end users | `db …`, `auth …` | `workser help database` |
|
|
24
24
|
| See the project's other apps, and wire one to another | `project …`, `env … --app` | `workser help apps` |
|
|
25
|
-
| Build a desktop app, sign it, ship an installer | `deploy`, `env …` | `workser help desktop` |
|
|
26
25
|
| Deploy, set env vars, read logs, check a domain | `deploy`, `env …`, `logs`, `versions`, `domain`, `open` | `workser help deploy` |
|
|
27
26
|
| Save work before a risky change, undo it, sync this folder | `checkpoint`, `restore`, `sync` | `workser help version-control` |
|
|
28
27
|
| Put files in the project's bucket | `storage …` | `workser help storage` |
|
|
@@ -90,11 +89,10 @@ move between its projects (`--project <id>`, or `cd`); another org returns
|
|
|
90
89
|
`workser decision list --json` (so you don't quietly reverse a decision) and
|
|
91
90
|
`workser design show --json` before writing UI. This project outlives your
|
|
92
91
|
session; that context is how you don't start from zero.
|
|
93
|
-
3. **
|
|
94
|
-
`workser
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
alone is gone when the conversation scrolls. `workser help sdlc-entities`.
|
|
92
|
+
3. **Choose the planning level first.** Two-plus owner-visible deliveries are a
|
|
93
|
+
**goal**: read `workser help goals`, run `goal create`, then stop for agreement.
|
|
94
|
+
One delivery is a task; `task subtask add` records its implementation steps.
|
|
95
|
+
Goal phases are owner checkpoints across tasks; subtasks are not phases.
|
|
98
96
|
4. **Stay in your lane.** On `owner_only` (exit 6) or `out_of_scope` (exit 7),
|
|
99
97
|
don't retry or look for a workaround — tell the user, then continue.
|
|
100
98
|
Provisioning the *pinned project's own* db/bucket/auth is allowed (it may be
|
|
@@ -110,12 +108,12 @@ move between its projects (`--project <id>`, or `cd`); another org returns
|
|
|
110
108
|
`git reset --hard`, `DROP`/`TRUNCATE`, `curl | sh`, …) are refused by Workser's
|
|
111
109
|
safety policy — don't attempt them; use migrations + scoped changes instead.
|
|
112
110
|
|
|
113
|
-
## Typical flow: build → ship
|
|
111
|
+
## Typical flow: one task → build → ship
|
|
114
112
|
|
|
115
113
|
```bash
|
|
116
114
|
workser status --json # 1. orient
|
|
117
115
|
workser decision list --json # 2. what's already decided
|
|
118
|
-
workser task subtask add "
|
|
116
|
+
workser task subtask add "Implement …" --json # 3. steps inside this task
|
|
119
117
|
workser doc create "Plan" --markdown "…" --json # 4. the narrative, once
|
|
120
118
|
workser db create --json # 5. provision infra (idempotent)
|
|
121
119
|
workser env set STRIPE_KEY=sk_live_… --json # 6. configure it
|
|
@@ -124,6 +122,15 @@ workser verify --json # 7. green build is the bar
|
|
|
124
122
|
workser deploy --prod --watch --json # 8. ship → stable *.workser.app URL
|
|
125
123
|
```
|
|
126
124
|
|
|
125
|
+
For a larger outcome, propose the shape and stop:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
workser goal create "Launch checkout" \
|
|
129
|
+
--phase "Cart" --phase "Payment" --phase "Receipts" \
|
|
130
|
+
--outcome "A customer can buy something and get a receipt" --json
|
|
131
|
+
# After agreement, link phase tasks with --goal and --phase.
|
|
132
|
+
```
|
|
133
|
+
|
|
127
134
|
## Reading results
|
|
128
135
|
|
|
129
136
|
- Success: use `.data` (`.data.url` after deploy, a `.data` array after `list`).
|
|
@@ -101,7 +101,7 @@ forever; if it does, carry on and state clearly what you assumed.
|
|
|
101
101
|
## Asking for an app that does not exist yet
|
|
102
102
|
|
|
103
103
|
The project needs a kind of app it does not have — a backend for the phone app, a
|
|
104
|
-
|
|
104
|
+
phone version of the website. **You cannot create one yourself**, by design: apps
|
|
105
105
|
are real infrastructure on the owner's account. Ask, and their click creates it.
|
|
106
106
|
The answer gives you the new app's id.
|
|
107
107
|
|
|
@@ -116,15 +116,19 @@ workser ask "The phone app needs an API to hold its data. Add one?" \
|
|
|
116
116
|
|---|---|
|
|
117
117
|
| `web` | Next.js site on Workser hosting |
|
|
118
118
|
| `mobile` | Expo / React Native phone app |
|
|
119
|
-
| `desktop` | Next.js + Electron, installs on a Mac or PC |
|
|
120
119
|
| `api` | backend service — `api-hono` (TypeScript) or `api-python` (FastAPI) |
|
|
121
120
|
|
|
122
121
|
**Name the kind you actually mean.** Asking for `web` because you are unsure is
|
|
123
|
-
how a
|
|
122
|
+
how a phone app gets created as a website: the card shows the owner the kind
|
|
124
123
|
you named, they approve THAT, and the wrong app is provisioned under your own
|
|
125
124
|
sentence asking for the right one. An unknown kind is refused with the list
|
|
126
125
|
above rather than guessed at — read it and ask again.
|
|
127
126
|
|
|
127
|
+
**There is no desktop app type.** Workser builds websites, phone apps and
|
|
128
|
+
backend services. If the owner asks for a Mac or Windows app, say plainly that
|
|
129
|
+
Workser cannot make one yet — do not offer a web app dressed as a window, and
|
|
130
|
+
do not build a "desktop-style" page instead. That is not what they asked for.
|
|
131
|
+
|
|
128
132
|
**Never ask for a secret value this way** — the answer is stored and displayed. Ask
|
|
129
133
|
*where* a key should go, then have the user set it (`workser env set` writes it
|
|
130
134
|
without you ever seeing it).
|
|
@@ -11,6 +11,7 @@ commands: [image, video, audio]
|
|
|
11
11
|
workser image generate "<prompt>" # alias: workser image gen
|
|
12
12
|
-r, --reference <url...> # condition on existing images (up to 4)
|
|
13
13
|
-o, --output <path> # also download the first image locally
|
|
14
|
+
workser image usage # can I generate right now, and what would stop me
|
|
14
15
|
```
|
|
15
16
|
|
|
16
17
|
Returns the generated image's public URL, so the usual move is to generate, then use
|
|
@@ -34,6 +35,32 @@ workser image gen "same van, from the side" -r https://… -o ./public/van.png -
|
|
|
34
35
|
- **Placeholder art is not a deliverable.** Generating a hero image to unblock a
|
|
35
36
|
layout is fine; shipping it as the user's brand asset without asking is not.
|
|
36
37
|
|
|
38
|
+
## This is metered, and it can be refused
|
|
39
|
+
|
|
40
|
+
Every image is billed to the organization's credit wallet, and each plan includes
|
|
41
|
+
a monthly number of them. Three gates run server-side before anything is drawn —
|
|
42
|
+
the plan tier, the monthly allowance, then the wallet — so a call can come back
|
|
43
|
+
refused having spent nothing.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
workser image usage --json # {"limit":10,"used":3,"remaining":7,
|
|
47
|
+
# "credits":{"available":41.2,"requiredPerImage":4.12,"sufficient":true},
|
|
48
|
+
# "canGenerate":true,"blockedBy":null}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- **Check before a batch, not after.** Planning six images is a plan that needs
|
|
52
|
+
six times `requiredPerImage` in the wallet. `workser image usage` answers that
|
|
53
|
+
before any of them cost anything; `blockedBy` names which gate would stop you
|
|
54
|
+
(`plan`, `quota`, `credits`), and it exits non-zero when generation is blocked.
|
|
55
|
+
- **A refusal is final, not a hiccup — do NOT retry it.** `insufficient_credits`
|
|
56
|
+
(HTTP 402) and `image_quota_reached` (403) come back as those exact codes in
|
|
57
|
+
`--json`, and the CLI exits 8 for both. Retrying cannot succeed and every
|
|
58
|
+
attempt is another paid call the owner did not ask for. Stop, and tell the
|
|
59
|
+
owner what to do: top up credits, or upgrade the plan.
|
|
60
|
+
- **You cannot see their balance any other way**, so do not guess at it from how
|
|
61
|
+
many images you have already made — a refusal at image four of six leaves the
|
|
62
|
+
work half-done and looks like a bug.
|
|
63
|
+
|
|
37
64
|
## Understanding media you can't natively see or hear
|
|
38
65
|
|
|
39
66
|
The fallback for a text-only model, or media you have no other way to reach: describe
|
|
@@ -58,14 +58,17 @@ the same way when the task touches documented behaviour.
|
|
|
58
58
|
`--infra` match on the server, so the answer covers the whole project rather
|
|
59
59
|
than the first page of it. `decision tag <id>` files one that already exists.
|
|
60
60
|
|
|
61
|
-
## Work with
|
|
61
|
+
## Work with one task's steps → subtasks + a plan doc, before you build
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
Do not confuse two levels of planning. A **goal phase** is an owner-visible
|
|
64
|
+
delivery across tasks; create it with `workser goal create` and wait for agreement
|
|
65
|
+
before filing work. A **subtask** is one teammate's implementation step inside a
|
|
66
|
+
single approved task. File those steps before building — not afterwards, and not
|
|
67
|
+
only in your reply, which is gone once the conversation scrolls.
|
|
65
68
|
|
|
66
69
|
```bash
|
|
67
|
-
# the
|
|
68
|
-
workser task subtask add "
|
|
70
|
+
# the implementation steps — this task's own subtask list, not the Board
|
|
71
|
+
workser task subtask add "Create schema + migration" --role api --note "…"
|
|
69
72
|
|
|
70
73
|
# the plan's narrative, ONE doc, deliberately NOT linked to a subtask
|
|
71
74
|
workser doc create "Checkout — plan" --kind plan --markdown "$(cat plan.md)" --json
|
|
@@ -85,9 +88,9 @@ Never tell the user a subtask is locked, and never file a second one alongside
|
|
|
85
88
|
the wrong one — a plan with a duplicate phase in it is a plan nobody can read
|
|
86
89
|
the progress of.
|
|
87
90
|
|
|
88
|
-
**Don't pass `--work-item` for a
|
|
89
|
-
its card and is *hidden* from the Docs panel; a plan spanning
|
|
90
|
-
belongs to the project, not to
|
|
91
|
+
**Don't pass `--work-item` for a goal-wide plan.** A linked document renders on
|
|
92
|
+
its card and is *hidden* from the Docs panel; a plan spanning several goal phases
|
|
93
|
+
belongs to the project, not to the first task.
|
|
91
94
|
|
|
92
95
|
The bar: if the user closed this conversation now, the subtask list should still
|
|
93
96
|
show what's left and the doc should still explain the plan to whoever continues
|
|
File without changes
|