@workser/cli 0.2.4 → 0.2.5
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 +32 -0
- package/dist/index.js +86 -5
- package/package.json +1 -1
- package/skills/workser/SKILL.md +25 -26
- package/skills/workser/reference/sdlc-entities.md +29 -2
package/AGENTS.md
CHANGED
|
@@ -42,6 +42,7 @@ what you *can* do.
|
|
|
42
42
|
workser status | whoami # orient (read)
|
|
43
43
|
workser project show | list # your pinned project + the workspace
|
|
44
44
|
workser env set KEY=VALUE… | list | get KEY # env vars
|
|
45
|
+
workser env pull [--out .env.local] # write cloud env vars into a local file
|
|
45
46
|
workser db create # provision the Neon Postgres database
|
|
46
47
|
workser db url | list # DB connection string + status
|
|
47
48
|
workser db tables # list tables in the database
|
|
@@ -92,6 +93,33 @@ workser ask "<question>" [--type <t>] [--option <o>] # ask the user, WAIT for th
|
|
|
92
93
|
# project create, project use, env rm, domain set
|
|
93
94
|
```
|
|
94
95
|
|
|
96
|
+
## Project tracking (the Board, Docs and Memory the user sees in Orbit)
|
|
97
|
+
```
|
|
98
|
+
workser board list | show <id> # what's already tracked
|
|
99
|
+
workser board create "<title>" [--description <t>] [--status <s>] [--priority <p>]
|
|
100
|
+
workser board move <id> <backlog|in-progress|in-review|done> | close <id>
|
|
101
|
+
workser decision list | show <id> # what was already decided
|
|
102
|
+
workser decision create "<title>" --context <t> --decision <t> [--consequences <t>]
|
|
103
|
+
workser requirement list | show <id> | create "<title>" --body <t> | update <id> --status <s>
|
|
104
|
+
workser doc list | show <id> [--markdown]
|
|
105
|
+
workser doc create "<title>" --markdown <text> | doc update <id> --markdown <text>
|
|
106
|
+
```
|
|
107
|
+
**Read before you plan** — `board list` and `decision list` tell you what someone is
|
|
108
|
+
already doing and what this project chose on purpose, so you don't re-file work or
|
|
109
|
+
quietly reverse a decision.
|
|
110
|
+
|
|
111
|
+
**A plan with phases goes on the Board before you build it.** The moment you split a
|
|
112
|
+
task into more than one phase: one `board create` card per phase (only the one you're
|
|
113
|
+
doing goes to `in-progress`), the plan itself as one `doc create --markdown` — with
|
|
114
|
+
**no `--work-item`**, because a linked doc is shown on its card and hidden from the
|
|
115
|
+
Docs panel — and a `decision create` if the plan settled a real tradeoff. A plan that
|
|
116
|
+
lives only in your reply is gone as soon as the conversation scrolls.
|
|
117
|
+
|
|
118
|
+
**Keep it true as you work.** `board move <id> in-progress` when you pick it up,
|
|
119
|
+
`in-review` when it's ready to look at, `board close <id>` when it's done and
|
|
120
|
+
verified. A Board still reading `backlog` after you shipped is worse than no Board.
|
|
121
|
+
Decisions are append-only: supersede an old one with a new record, never edit it.
|
|
122
|
+
|
|
95
123
|
## Memory (remember across conversations, not just this one)
|
|
96
124
|
Every conversation you run is otherwise a fresh start — no memory of what you or the
|
|
97
125
|
user decided last time. `workser memory add "<content>"` fixes that: it stores durable,
|
|
@@ -205,8 +233,12 @@ keep your own context lean and get a specialized second perspective; a non-zero
|
|
|
205
233
|
## Example
|
|
206
234
|
```bash
|
|
207
235
|
workser status --json # orient: which project, last deploy
|
|
236
|
+
workser board list --json # what's tracked; decision list for what's decided
|
|
237
|
+
workser board create "Phase 1 — …" --status in-progress --json # phased plan -> cards
|
|
238
|
+
workser doc create "Plan" --markdown "…" --json # …plus the plan doc
|
|
208
239
|
workser env set STRIPE_KEY=sk_live_… --json
|
|
209
240
|
# … you write the app code with your normal tools …
|
|
210
241
|
workser deploy --prod --watch --json # -> .data.url is the live URL
|
|
242
|
+
workser board close <id> --json # the Board now matches reality
|
|
211
243
|
```
|
|
212
244
|
Report results to the user in plain language, not raw JSON.
|
package/dist/index.js
CHANGED
|
@@ -4243,6 +4243,33 @@ purpose \u2014 \`workser decision show <id>\` gives you the context and conseque
|
|
|
4243
4243
|
not just the title. Reach for \`workser doc list\` / \`workser requirement list\`
|
|
4244
4244
|
the same way when the task touches documented behaviour.
|
|
4245
4245
|
|
|
4246
|
+
## Work with phases \u2192 cards + a plan doc, before you build
|
|
4247
|
+
|
|
4248
|
+
The moment you split a task into more than one phase, file it \u2014 not afterwards,
|
|
4249
|
+
and not only in your reply, which is gone once the conversation scrolls.
|
|
4250
|
+
|
|
4251
|
+
\`\`\`bash
|
|
4252
|
+
# one card per phase; only the one you're doing goes to in-progress
|
|
4253
|
+
workser board create "Phase 1 \u2014 schema + migration" \\
|
|
4254
|
+
--description "Add orders/line_items tables and the migration." \\
|
|
4255
|
+
--status in-progress --json
|
|
4256
|
+
workser board create "Phase 2 \u2014 checkout API" --description "\u2026" --json
|
|
4257
|
+
workser board create "Phase 3 \u2014 cart UI" --description "\u2026" --json
|
|
4258
|
+
|
|
4259
|
+
# the plan itself, ONE doc, deliberately NOT linked to a card
|
|
4260
|
+
workser doc create "Checkout \u2014 implementation plan" --markdown "$(cat plan.md)" --json
|
|
4261
|
+
|
|
4262
|
+
# the approach, if the plan settled something with real alternatives
|
|
4263
|
+
workser decision create "Carts live server-side" --context "\u2026" --decision "\u2026" --json
|
|
4264
|
+
\`\`\`
|
|
4265
|
+
|
|
4266
|
+
**Don't pass \`--work-item\` for a multi-phase plan.** A linked document renders on
|
|
4267
|
+
its card and is *hidden* from the Docs panel; a plan spanning three phases belongs
|
|
4268
|
+
to the project, not to phase 1.
|
|
4269
|
+
|
|
4270
|
+
The bar: if the user closed this conversation now, the Board should still show
|
|
4271
|
+
what's left and the doc should still explain the plan to whoever continues it.
|
|
4272
|
+
|
|
4246
4273
|
## Keep the Board honest while you work
|
|
4247
4274
|
|
|
4248
4275
|
A Board still reading \`backlog\` after the feature shipped tells the user the
|
|
@@ -4307,8 +4334,8 @@ workser doc update <id> --markdown "$(cat updated.md)"
|
|
|
4307
4334
|
|
|
4308
4335
|
Record what a future maintainer would need: follow-up work you found but didn't
|
|
4309
4336
|
do, a choice between real alternatives, a behaviour worth writing down. Don't
|
|
4310
|
-
narrate every small step \u2014 and never treat filing a card as a substitute for
|
|
4311
|
-
|
|
4337
|
+
narrate every small step \u2014 and never treat filing a card as a substitute for the
|
|
4338
|
+
work. A card saying "fix the bug" is not fixing the bug.
|
|
4312
4339
|
`
|
|
4313
4340
|
},
|
|
4314
4341
|
{
|
|
@@ -5373,6 +5400,7 @@ async function presign(ctx, projectId, bucket, key, operation, expiresInSeconds)
|
|
|
5373
5400
|
|
|
5374
5401
|
// src/commands/env.ts
|
|
5375
5402
|
var import_picocolors9 = __toESM(require_picocolors(), 1);
|
|
5403
|
+
import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
5376
5404
|
function appQuery(opts) {
|
|
5377
5405
|
const app = typeof opts?.app === "string" ? opts.app : "";
|
|
5378
5406
|
return app ? `?webAppId=${encodeURIComponent(app)}` : "";
|
|
@@ -5429,6 +5457,59 @@ function registerEnv(program3) {
|
|
|
5429
5457
|
})
|
|
5430
5458
|
)
|
|
5431
5459
|
);
|
|
5460
|
+
env.command("pull").description("Write this app's cloud env vars into a local file (default .env.local)").option("--app <webAppId>", APP_FLAG_HELP).option("--out <file>", "Local file to write", ".env.local").action(
|
|
5461
|
+
action(async ({ ctx, opts }) => {
|
|
5462
|
+
const projectId = requireProject(ctx);
|
|
5463
|
+
const items = await api(
|
|
5464
|
+
ctx,
|
|
5465
|
+
`/v1/projects/${projectId}/env${appQuery(opts)}`
|
|
5466
|
+
);
|
|
5467
|
+
const outPath = typeof opts?.out === "string" ? opts.out : ".env.local";
|
|
5468
|
+
if (!items?.length) {
|
|
5469
|
+
return ok(
|
|
5470
|
+
{ file: outPath, pulled: [] },
|
|
5471
|
+
() => line(import_picocolors9.default.dim("No cloud variables to pull."))
|
|
5472
|
+
);
|
|
5473
|
+
}
|
|
5474
|
+
const pulled = [];
|
|
5475
|
+
for (const item of items) {
|
|
5476
|
+
const res = await api(
|
|
5477
|
+
ctx,
|
|
5478
|
+
`/v1/projects/${projectId}/env/${encodeURIComponent(item.key)}${appQuery(opts)}`
|
|
5479
|
+
);
|
|
5480
|
+
pulled.push({ key: item.key, value: res?.value ?? "" });
|
|
5481
|
+
}
|
|
5482
|
+
await mergeEnvFile(outPath, pulled);
|
|
5483
|
+
ok(
|
|
5484
|
+
{ file: outPath, pulled: pulled.map((p) => p.key) },
|
|
5485
|
+
() => success(
|
|
5486
|
+
`Pulled ${pulled.length} variable(s) into ${import_picocolors9.default.bold(outPath)}.`
|
|
5487
|
+
)
|
|
5488
|
+
);
|
|
5489
|
+
})
|
|
5490
|
+
);
|
|
5491
|
+
}
|
|
5492
|
+
var ENV_KEY_LINE = /^([A-Za-z_][A-Za-z0-9_]*)=/;
|
|
5493
|
+
async function mergeEnvFile(path, vars) {
|
|
5494
|
+
const existing = await readFile3(path, "utf8").catch(() => "");
|
|
5495
|
+
const lines = existing.length ? existing.split(/\r?\n/) : [];
|
|
5496
|
+
const remaining = new Map(vars.map((v) => [v.key, v.value]));
|
|
5497
|
+
const merged = lines.map((rawLine) => {
|
|
5498
|
+
const match = ENV_KEY_LINE.exec(rawLine);
|
|
5499
|
+
if (!match || !remaining.has(match[1])) return rawLine;
|
|
5500
|
+
const key = match[1];
|
|
5501
|
+
const value = remaining.get(key);
|
|
5502
|
+
remaining.delete(key);
|
|
5503
|
+
return `${key}=${formatEnvValue(value)}`;
|
|
5504
|
+
});
|
|
5505
|
+
while (merged.length && merged[merged.length - 1] === "") merged.pop();
|
|
5506
|
+
for (const [key, value] of remaining) {
|
|
5507
|
+
merged.push(`${key}=${formatEnvValue(value)}`);
|
|
5508
|
+
}
|
|
5509
|
+
await writeFile3(path, merged.join("\n") + "\n", "utf8");
|
|
5510
|
+
}
|
|
5511
|
+
function formatEnvValue(value) {
|
|
5512
|
+
return /[\s"'#]/.test(value) ? JSON.stringify(value) : value;
|
|
5432
5513
|
}
|
|
5433
5514
|
|
|
5434
5515
|
// src/commands/deploy.ts
|
|
@@ -6221,7 +6302,7 @@ function printRun(run) {
|
|
|
6221
6302
|
}
|
|
6222
6303
|
|
|
6223
6304
|
// src/commands/image.ts
|
|
6224
|
-
import { writeFile as
|
|
6305
|
+
import { writeFile as writeFile4, mkdir } from "fs/promises";
|
|
6225
6306
|
import { dirname as dirname2, resolve as resolve3 } from "path";
|
|
6226
6307
|
function registerImage(program3) {
|
|
6227
6308
|
const image = program3.command("image").description("Generate images from a text prompt");
|
|
@@ -6286,7 +6367,7 @@ async function download(url, output) {
|
|
|
6286
6367
|
);
|
|
6287
6368
|
}
|
|
6288
6369
|
await mkdir(dirname2(target), { recursive: true });
|
|
6289
|
-
await
|
|
6370
|
+
await writeFile4(target, Buffer.from(await res.arrayBuffer()));
|
|
6290
6371
|
return target;
|
|
6291
6372
|
}
|
|
6292
6373
|
|
|
@@ -6972,7 +7053,7 @@ function unwrap(group) {
|
|
|
6972
7053
|
|
|
6973
7054
|
// src/index.ts
|
|
6974
7055
|
var pkg = {
|
|
6975
|
-
version: true ? "0.2.
|
|
7056
|
+
version: true ? "0.2.5" : "0.0.0-dev"
|
|
6976
7057
|
};
|
|
6977
7058
|
var program2 = new Command();
|
|
6978
7059
|
program2.name("workser").description(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workser/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
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
|
@@ -35,10 +35,9 @@ command. Don't print them all; you are paying for every line you load.
|
|
|
35
35
|
| Record finished output, or ask the user a question | `artifact …`, `ask` | `workser help deliverables` |
|
|
36
36
|
| Control this machine — files, shell, screen, browser | `tool …` | `workser help computer-use` |
|
|
37
37
|
|
|
38
|
-
`workser help` with no topic lists them. **The CLI is the source of truth
|
|
39
|
-
these guides
|
|
40
|
-
|
|
41
|
-
cannot be out of date.
|
|
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.
|
|
42
41
|
|
|
43
42
|
## Orientation (no guide needed)
|
|
44
43
|
|
|
@@ -61,13 +60,11 @@ SQL, provision the bucket + auth, deploy, set env vars, manage files. Sensitive
|
|
|
61
60
|
actions are **gated** — the daemon may return `error.code = "awaiting_approval"`
|
|
62
61
|
(exit 5) and wait for the user to approve in Orbit; ask them to approve, then retry.
|
|
63
62
|
|
|
64
|
-
What you **cannot** do is administer the project set or destroy config:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
Owner-only, for reference: `project create` · `project use` · `env rm` · `domain set`.
|
|
63
|
+
What you **cannot** do is administer the project set or destroy config:
|
|
64
|
+
`project create` · `project use` · `env rm` · `domain set` return
|
|
65
|
+
`error.code = "owner_only"` (exit 6). Tell the user it's an owner action to do in
|
|
66
|
+
Orbit, then continue with what you can. The project is already selected; you don't
|
|
67
|
+
pick or switch it.
|
|
71
68
|
|
|
72
69
|
## Golden rules
|
|
73
70
|
|
|
@@ -80,19 +77,24 @@ Owner-only, for reference: `project create` · `project use` · `env rm` · `dom
|
|
|
80
77
|
(what was already decided, so you don't quietly reverse it), and
|
|
81
78
|
`workser design show --json` before writing UI. This project outlives your
|
|
82
79
|
session; that context is how you don't start from zero.
|
|
83
|
-
3. **
|
|
80
|
+
3. **A phased plan goes on the Board before you build it.** One `board create`
|
|
81
|
+
card per phase, the plan itself as one `doc create` (no `--work-item`, or it's
|
|
82
|
+
hidden from the Docs panel), and a `decision create` if it settled a real
|
|
83
|
+
tradeoff. A plan that lives only in your reply is gone when the conversation
|
|
84
|
+
scrolls. Details: `workser help sdlc-entities`.
|
|
85
|
+
4. **Stay in your lane.** `error.code = "owner_only"` (exit 6) means the action is
|
|
84
86
|
reserved for the owner in Orbit. Don't retry or look for a workaround — tell the
|
|
85
87
|
user, then continue. Provisioning the *pinned project's own* db / bucket / auth is
|
|
86
88
|
allowed (it may be approval-gated, not owner-only).
|
|
87
|
-
|
|
89
|
+
5. **Approvals are normal.** Some allowed actions may return
|
|
88
90
|
`{"error":{"code":"awaiting_approval"}}` (exit 5) while the user approves in the
|
|
89
91
|
Orbit UI. Tell the user to approve, then retry — do **not** try to bypass it.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
+
6. **Never ask for or store credentials.** Auth is handled by Orbit; you never see keys.
|
|
93
|
+
7. **Verify before "done".** Before telling the user a task is complete, run
|
|
92
94
|
`workser verify --json` (runs the project's typecheck/lint/build). If it
|
|
93
95
|
reports `"ok": false`, fix the errors it lists and re-run until it passes —
|
|
94
96
|
a green build is the bar for "done", not your own judgement.
|
|
95
|
-
|
|
97
|
+
8. **Destructive shell actions are blocked.** Irreversible commands (`rm -rf /`,
|
|
96
98
|
`git reset --hard`, `DROP`/`TRUNCATE`, `curl | sh`, …) are refused by Workser's
|
|
97
99
|
safety policy — don't attempt them; use migrations + scoped changes instead.
|
|
98
100
|
|
|
@@ -102,19 +104,16 @@ Owner-only, for reference: `project create` · `project use` · `env rm` · `dom
|
|
|
102
104
|
workser status --json # 1. orient (project is already pinned)
|
|
103
105
|
workser board list --json # 2. what's already tracked
|
|
104
106
|
workser decision list --json # …and already decided
|
|
105
|
-
workser board
|
|
106
|
-
workser
|
|
107
|
-
workser
|
|
107
|
+
workser board create "Phase 2 — …" --json # 3. phases? file them + the plan doc
|
|
108
|
+
workser board move <id> in-progress --json # 4. claim the card you're doing
|
|
109
|
+
workser db create --json # 5. provision infra the app needs (idempotent)
|
|
110
|
+
workser env set STRIPE_KEY=sk_live_… --json # 6. configure it
|
|
108
111
|
# … you write the app code with your normal tools …
|
|
109
|
-
workser verify --json #
|
|
110
|
-
workser deploy --prod --watch --json #
|
|
111
|
-
workser board close <id> --json #
|
|
112
|
+
workser verify --json # 7. green build is the bar for "done"
|
|
113
|
+
workser deploy --prod --watch --json # 8. ship; returns the live URL
|
|
114
|
+
workser board close <id> --json # 9. the Board now matches reality
|
|
112
115
|
```
|
|
113
116
|
|
|
114
|
-
Provisioning the pinned project's own database / bucket / auth is yours to do
|
|
115
|
-
(`db create`, `storage create`, `auth enable`) — the user may need to approve it in
|
|
116
|
-
Orbit (`awaiting_approval`). Only a **custom domain** is an owner action.
|
|
117
|
-
|
|
118
117
|
## Reading results
|
|
119
118
|
|
|
120
119
|
- Success: use `.data` (e.g. `.data.url` after deploy, `.data` array after `list`).
|
|
@@ -55,6 +55,33 @@ purpose — `workser decision show <id>` gives you the context and consequences,
|
|
|
55
55
|
not just the title. Reach for `workser doc list` / `workser requirement list`
|
|
56
56
|
the same way when the task touches documented behaviour.
|
|
57
57
|
|
|
58
|
+
## Work with phases → cards + a plan doc, before you build
|
|
59
|
+
|
|
60
|
+
The moment you split a task into more than one phase, file it — not afterwards,
|
|
61
|
+
and not only in your reply, which is gone once the conversation scrolls.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# one card per phase; only the one you're doing goes to in-progress
|
|
65
|
+
workser board create "Phase 1 — schema + migration" \
|
|
66
|
+
--description "Add orders/line_items tables and the migration." \
|
|
67
|
+
--status in-progress --json
|
|
68
|
+
workser board create "Phase 2 — checkout API" --description "…" --json
|
|
69
|
+
workser board create "Phase 3 — cart UI" --description "…" --json
|
|
70
|
+
|
|
71
|
+
# the plan itself, ONE doc, deliberately NOT linked to a card
|
|
72
|
+
workser doc create "Checkout — implementation plan" --markdown "$(cat plan.md)" --json
|
|
73
|
+
|
|
74
|
+
# the approach, if the plan settled something with real alternatives
|
|
75
|
+
workser decision create "Carts live server-side" --context "…" --decision "…" --json
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Don't pass `--work-item` for a multi-phase plan.** A linked document renders on
|
|
79
|
+
its card and is *hidden* from the Docs panel; a plan spanning three phases belongs
|
|
80
|
+
to the project, not to phase 1.
|
|
81
|
+
|
|
82
|
+
The bar: if the user closed this conversation now, the Board should still show
|
|
83
|
+
what's left and the doc should still explain the plan to whoever continues it.
|
|
84
|
+
|
|
58
85
|
## Keep the Board honest while you work
|
|
59
86
|
|
|
60
87
|
A Board still reading `backlog` after the feature shipped tells the user the
|
|
@@ -119,5 +146,5 @@ workser doc update <id> --markdown "$(cat updated.md)"
|
|
|
119
146
|
|
|
120
147
|
Record what a future maintainer would need: follow-up work you found but didn't
|
|
121
148
|
do, a choice between real alternatives, a behaviour worth writing down. Don't
|
|
122
|
-
narrate every small step — and never treat filing a card as a substitute for
|
|
123
|
-
|
|
149
|
+
narrate every small step — and never treat filing a card as a substitute for the
|
|
150
|
+
work. A card saying "fix the bug" is not fixing the bug.
|