@workser/cli 0.6.27 → 0.6.29
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 +963 -288
- package/package.json +1 -1
- package/skills/workser/SKILL.md +13 -20
- package/skills/workser/reference/agent-cloud.md +23 -14
- package/skills/workser/reference/agent-triggers.md +69 -0
- package/skills/workser/reference/chat-channels.md +106 -0
- package/skills/workser/reference/images.md +12 -1
- package/skills/workser/reference/storage.md +37 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workser/cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.29",
|
|
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
|
@@ -24,7 +24,7 @@ screen long. Find your row, run that **one** command — every line costs you.
|
|
|
24
24
|
| See the project's other apps, and wire one to another | `project …`, `env … --app` | `workser help apps` |
|
|
25
25
|
| Deploy, set env vars, read logs, check a domain | `deploy`, `env …`, `logs`, `versions`, `domain`, `open` | `workser help deploy` |
|
|
26
26
|
| Save work before a risky change, undo it, sync this folder | `checkpoint`, `restore`, `sync` | `workser help version-control` |
|
|
27
|
-
|
|
|
27
|
+
| Store an image, PDF or upload — **never in the app folder** | `storage …` | `workser help storage` |
|
|
28
28
|
| Read or write products, orders, customers, deals | `business …` | `workser help business` |
|
|
29
29
|
| Use the project's own Neon buckets or functions | `neon …` | `workser help neon` |
|
|
30
30
|
| Build an automation, or use Gmail/Slack/Stripe/Sheets | `workflow …`, `app …` | `workser help automation` |
|
|
@@ -48,10 +48,9 @@ model call before you have ruled it out. A hand-written one is invisible to the
|
|
|
48
48
|
Agent Cloud console, unversioned, unmetered, and needs a model credential nothing
|
|
49
49
|
mints — so it answers nobody in production.
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
Python, then hand-built — and you say which rung you took and why.
|
|
51
|
+
Agent Cloud is on every plan. Setup and publish are free; test and live runs need
|
|
52
|
+
prepaid AI credits. On 402, hand over the top-up and stop — do not build a hidden
|
|
53
|
+
substitute. If ruled out for a product reason, follow the ORDER below and say why.
|
|
55
54
|
`workser help agent-cloud` has all of it, including the case that legitimately
|
|
56
55
|
starts lower: a developer wanting their own agent code, knowing this exists.
|
|
57
56
|
|
|
@@ -60,12 +59,8 @@ starts lower: a developer wanting their own agent code, knowing this exists.
|
|
|
60
59
|
```
|
|
61
60
|
workser status # connection + pinned project + latest deploy
|
|
62
61
|
workser whoami # who am I / which workspace
|
|
63
|
-
workser project show # the project pinned here
|
|
64
|
-
workser project list # your organization's projects
|
|
65
62
|
workser verify # typecheck/lint/build — gate "done" on this
|
|
66
63
|
workser doctor # endpoint, mode, token presence, project
|
|
67
|
-
workser login # authenticate outside Orbit (CI)
|
|
68
|
-
workser logout # clear a saved standalone session
|
|
69
64
|
```
|
|
70
65
|
|
|
71
66
|
## Scope
|
|
@@ -107,7 +102,13 @@ move between its projects (`--project <id>`, or `cd`); another org returns
|
|
|
107
102
|
7. **Verify before "done".** Run `workser verify --json` (typecheck/lint/build).
|
|
108
103
|
On `"ok": false`, fix what it lists and re-run until it passes — a green build
|
|
109
104
|
is the bar, not your own judgement.
|
|
110
|
-
8. **
|
|
105
|
+
8. **The project's own infra is the default — db, bucket, auth.** Don't reach
|
|
106
|
+
past it for one you know better; the owner may name another provider and that
|
|
107
|
+
is theirs to decide, so ask rather than drift. Media always goes to storage
|
|
108
|
+
(`workser storage put`, then the URL) and never into `public/`: files there
|
|
109
|
+
are committed, ride in every deploy bundle (25MB cap), and can't change
|
|
110
|
+
without a redeploy. A logo or icon is the exception; content is not.
|
|
111
|
+
9. **Destructive shell actions are blocked.** Irreversible commands (`rm -rf /`,
|
|
111
112
|
`git reset --hard`, `DROP`/`TRUNCATE`, `curl | sh`, …) are refused by Workser's
|
|
112
113
|
safety policy — don't attempt them; use migrations + scoped changes instead.
|
|
113
114
|
|
|
@@ -125,20 +126,12 @@ workser verify --json # 7. green build is the bar
|
|
|
125
126
|
workser deploy --prod --watch --json # 8. ship → stable *.workser.app URL
|
|
126
127
|
```
|
|
127
128
|
|
|
128
|
-
For a larger outcome, propose the shape and stop:
|
|
129
|
-
|
|
130
|
-
```bash
|
|
131
|
-
workser goal create "Launch checkout" \
|
|
132
|
-
--phase "Cart" --phase "Payment" --phase "Receipts" \
|
|
133
|
-
--outcome "A customer can buy something and get a receipt" --json
|
|
134
|
-
# After agreement, link phase tasks with --goal and --phase.
|
|
135
|
-
```
|
|
136
|
-
|
|
137
129
|
## Reading results
|
|
138
130
|
|
|
139
131
|
- Success: use `.data` (`.data.url` after deploy, a `.data` array after `list`).
|
|
140
132
|
- Failure: check `.error.code` — `not_connected` (open Orbit, or `workser login`
|
|
141
|
-
for CI
|
|
133
|
+
for CI; `workser logout` clears it) · `unauthorized` ·
|
|
134
|
+
`no_project` (the user links it in Orbit) ·
|
|
142
135
|
`owner_only`/`awaiting_approval` (rules 4–5) · `needs_local_app` (no Workser
|
|
143
136
|
app here, so folder commands cannot run — say so, don't reach for `git`).
|
|
144
137
|
|
|
@@ -48,6 +48,12 @@ sending it. That matters: the API silently drops unknown fields, so a typo
|
|
|
48
48
|
would otherwise be accepted, dropped, and reported as success — leaving an
|
|
49
49
|
agent that had been told nothing.
|
|
50
50
|
|
|
51
|
+
## Nothing starts it until you give it a trigger
|
|
52
|
+
|
|
53
|
+
A published agent runs when asked and at no other time. A time, a chat message
|
|
54
|
+
or an app event is a **trigger**, and it works the moment it is saved — it is
|
|
55
|
+
not part of the agent's version. `workser help agent-triggers`.
|
|
56
|
+
|
|
51
57
|
## Nothing takes effect until you publish
|
|
52
58
|
|
|
53
59
|
**This is the step to not forget.** The runtime resolves the PUBLISHED version
|
|
@@ -95,26 +101,28 @@ publishes an agent that never answers anybody. Pictures, video, voice and
|
|
|
95
101
|
transcription are abilities, not models you set, and one call from the app's
|
|
96
102
|
own code needs no agent at all: `workser help agent-cloud-media`.
|
|
97
103
|
|
|
98
|
-
##
|
|
104
|
+
## Every plan can use Agent Cloud — runs use credits
|
|
99
105
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
106
|
+
Creating, teaching and publishing an agent are available on free, trial and paid
|
|
107
|
+
plans. A `try` or live `run` spends real money on model usage and hosted runtime,
|
|
108
|
+
so it starts only when the organisation's shared AI-credit wallet clears the
|
|
109
|
+
minimum balance shown in the refusal.
|
|
103
110
|
|
|
104
|
-
**When you hit
|
|
111
|
+
**When you hit a 402, hand the credit top-up over — do not just describe it.** Workser
|
|
105
112
|
files the refusal on the conversation as a clickable step and artifact, so the
|
|
106
|
-
person gets something actionable. Say what you were doing, that
|
|
107
|
-
|
|
113
|
+
person gets something actionable. Say what you were doing, that the wallet needs
|
|
114
|
+
credits, and stop. The same rule applies when the owner supplies their own model
|
|
115
|
+
key because the hosted runtime and fallback path still cost money.
|
|
108
116
|
|
|
109
117
|
Do NOT:
|
|
110
118
|
|
|
111
|
-
- retry the call, or try a different agent — the
|
|
119
|
+
- retry the call, or try a different agent — the wallet is shared
|
|
112
120
|
- try to work around it by running the job yourself as a coding agent; that is
|
|
113
121
|
a different product and it will not be there when the user closes the window
|
|
114
|
-
- guess at prices, discounts or
|
|
122
|
+
- guess at prices, discounts or free usage
|
|
115
123
|
|
|
116
124
|
If the user asks how much: it is per minute of run time plus the model, and the
|
|
117
|
-
|
|
125
|
+
Agent Cloud usage view has the current figures. You do not have them.
|
|
118
126
|
|
|
119
127
|
## When to reach for this
|
|
120
128
|
|
|
@@ -135,7 +143,7 @@ workflow`.
|
|
|
135
143
|
## If Agent Cloud is ruled out, there is an order
|
|
136
144
|
|
|
137
145
|
Propose Agent Cloud first, every time. If it genuinely does not fit — the owner
|
|
138
|
-
|
|
146
|
+
does not want a managed runtime, or wants their own code in their own framework — go
|
|
139
147
|
down this list in order, and say which rung you took and why you skipped the
|
|
140
148
|
ones above it:
|
|
141
149
|
|
|
@@ -190,9 +198,10 @@ watching sees the agent think. See the `workser-sdk` skill, `reference/agents.md
|
|
|
190
198
|
what to do, what to leave alone, when to ask. Vague instructions are the
|
|
191
199
|
single biggest cause of an agent that "doesn't work".
|
|
192
200
|
|
|
193
|
-
3. **
|
|
194
|
-
|
|
195
|
-
|
|
201
|
+
3. **Every plan can run agents once the shared wallet has enough credits.** A
|
|
202
|
+
`402` with `insufficient_credits` needs a top-up; `spend_limit_reached` means
|
|
203
|
+
the owner reached the guard rail they set. Tell them exactly what it says and
|
|
204
|
+
hand over the matching action; never suggest a subscription upgrade.
|
|
196
205
|
|
|
197
206
|
4. **Say who it is for.** An agent acting for one of the app's customers needs
|
|
198
207
|
`referenceUserId`, or its memory and audit trail belong to nobody.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: agent-triggers
|
|
3
|
+
title: What starts an Agent Cloud agent
|
|
4
|
+
summary: Schedules, chat channels and app events — a trigger works the moment it is saved, unlike everything else on an agent.
|
|
5
|
+
commands: []
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# What starts an Agent Cloud agent
|
|
9
|
+
|
|
10
|
+
A published agent runs when somebody asks it to and at no other time. A trigger
|
|
11
|
+
is what makes it start on its own — a time, a chat message, or an event in a
|
|
12
|
+
connected app.
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
workser agent-cloud triggers <id> # what starts it today
|
|
16
|
+
workser agent-cloud trigger-add <id> schedule cron="0 9 * * 1-5" name="Morning report"
|
|
17
|
+
workser agent-cloud trigger-add <id> chat app_type=line
|
|
18
|
+
workser agent-cloud trigger-add <id> app_event event_type=GMAIL_NEW_GMAIL_MESSAGE connected_account_id=<id>
|
|
19
|
+
workser agent-cloud trigger-setup <id> <triggerId> # the webhook URL to paste
|
|
20
|
+
workser agent-cloud trigger-events <id> # what fired, and what it started
|
|
21
|
+
workser agent-cloud trigger-remove <id> <triggerId>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**A trigger works the moment it is saved** — unlike everything above, it is not
|
|
25
|
+
part of the agent's version and does not wait for a publish.
|
|
26
|
+
|
|
27
|
+
`rule=` is optional and is the FIRING rule, not the agent's instructions:
|
|
28
|
+
`rule="only when the message mentions an order"`. Leave it empty on a chat
|
|
29
|
+
channel and every message reaches the agent, which is what a support agent
|
|
30
|
+
wants — the agent decides what to do with each one.
|
|
31
|
+
|
|
32
|
+
For a chat trigger, **`trigger-setup` is not optional**: it returns the webhook
|
|
33
|
+
URL to paste into LINE's or Slack's console, and it says whether anything has
|
|
34
|
+
arrived yet. Without that step the trigger sits there looking finished and
|
|
35
|
+
never receives anything.
|
|
36
|
+
|
|
37
|
+
To let the agent ANSWER on that channel, the project also needs the account
|
|
38
|
+
connected — `workser line connect --token …`. See `workser help chat-channels`.
|
|
39
|
+
|
|
40
|
+
## The two halves of a chat agent
|
|
41
|
+
|
|
42
|
+
Receiving and answering are separate, and each has its own setup:
|
|
43
|
+
|
|
44
|
+
| | What it does | How |
|
|
45
|
+
| --- | --- | --- |
|
|
46
|
+
| The trigger | a message STARTS the agent | `trigger-add <id> chat app_type=line`, then `trigger-setup` |
|
|
47
|
+
| The connection | the agent can ANSWER | `workser line connect --token …` |
|
|
48
|
+
|
|
49
|
+
Do only the first and the agent listens and never speaks. Do only the second and
|
|
50
|
+
nothing ever wakes it up.
|
|
51
|
+
|
|
52
|
+
With both in place, a run started by a message is handed the reply token and the
|
|
53
|
+
sender, and gets every operation of that account as a tool — `line_reply`,
|
|
54
|
+
`slack_send_message` and the rest. Sends go through the agent's approval gate;
|
|
55
|
+
reads do not.
|
|
56
|
+
|
|
57
|
+
## Schedules, in plain words
|
|
58
|
+
|
|
59
|
+
`cron=` takes a standard five-field expression. The four people ask for:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
cron="0 9 * * 1-5" every weekday at 9am
|
|
63
|
+
cron="0 9 * * *" every day at 9am
|
|
64
|
+
cron="0 * * * *" every hour
|
|
65
|
+
cron="0 9 * * 1" every Monday at 9am
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`timezone=` defaults to UTC, which is almost never what somebody means by "9am".
|
|
69
|
+
Set it: `timezone="Asia/Bangkok"`.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: chat-channels
|
|
3
|
+
title: LINE, Telegram, Discord, Slack
|
|
4
|
+
summary: Connect a chat account once, then send, reply and read from the CLI or from an agent — LINE gets its whole Messaging API.
|
|
5
|
+
commands: [line, telegram, discord, slack]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# LINE, Telegram, Discord, Slack
|
|
9
|
+
|
|
10
|
+
Connect the business's chat account to this project **once**. After that it is
|
|
11
|
+
reachable from here, from Workser Code, and from any Agent Cloud agent in this
|
|
12
|
+
project — including the one a chat message starts.
|
|
13
|
+
|
|
14
|
+
Every provider takes the same six verbs:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
workser <provider> status # is anything connected?
|
|
18
|
+
workser <provider> connect --token <token>
|
|
19
|
+
workser <provider> verify # does the credential still work?
|
|
20
|
+
workser <provider> disconnect
|
|
21
|
+
workser <provider> ops [--group <name>] # everything it can do
|
|
22
|
+
workser <provider> call <operation> --params '<json>'
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
where `<provider>` is `line`, `telegram`, `discord` or `slack`.
|
|
26
|
+
|
|
27
|
+
## Getting the token
|
|
28
|
+
|
|
29
|
+
| Provider | Where | Flag |
|
|
30
|
+
| --- | --- | --- |
|
|
31
|
+
| LINE | Developers console → Messaging API → Channel access token | `--token`, plus `--secret` for the channel secret |
|
|
32
|
+
| Telegram | @BotFather → `/newbot` or `/token` | `--token` |
|
|
33
|
+
| Discord | Developer Portal → your app → Bot → Reset Token | `--token` (the bot must also be invited to the server) |
|
|
34
|
+
| Slack | Your app → OAuth & Permissions → Bot User OAuth Token (`xoxb-`) | `--token`, plus `--signing-secret` |
|
|
35
|
+
|
|
36
|
+
`connect` calls the provider immediately to prove the credential works, so a
|
|
37
|
+
mistyped token fails right there instead of an hour later as a customer message
|
|
38
|
+
nobody answered.
|
|
39
|
+
|
|
40
|
+
## `call` is the whole API
|
|
41
|
+
|
|
42
|
+
`ops` prints every operation with a one-line summary. The `●` marks the ones
|
|
43
|
+
that send something, spend quota, or change the account.
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
workser line call push --params '{"to":"U4af…","messages":[{"type":"text","text":"Your order shipped"}]}'
|
|
47
|
+
workser line call richmenu_list
|
|
48
|
+
workser telegram call send_chat_action --params '{"chat_id":123,"action":"typing"}'
|
|
49
|
+
workser slack call list_conversations --params '{"types":"public_channel"}'
|
|
50
|
+
workser discord call create_thread --params '{"channel_id":"…","message_id":"…","name":"Order #4021"}'
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Parameters are flat — path, query and body fields all go in `--params` together
|
|
54
|
+
and the server sorts them. Fields it has never heard of are **forwarded
|
|
55
|
+
untouched**, so a flex component or a Slack block the vendor shipped last week
|
|
56
|
+
works today.
|
|
57
|
+
|
|
58
|
+
## Shortcuts
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
workser line send <to> "<text>" | reply <replyToken> "<text>" | broadcast "<text>"
|
|
62
|
+
workser line quota | profile <userId>
|
|
63
|
+
workser telegram send <chatId> "<text>"
|
|
64
|
+
workser discord send <channelId> "<text>"
|
|
65
|
+
workser slack send <channel> "<text>"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Reply beats push, on LINE
|
|
69
|
+
|
|
70
|
+
`reply` uses the token that came with the incoming message. It is **free** and
|
|
71
|
+
does not touch the monthly quota; `push` costs one message per recipient. The
|
|
72
|
+
token is single-use and expires within a minute or so, so reply first and fall
|
|
73
|
+
back to push.
|
|
74
|
+
|
|
75
|
+
`broadcast` sends to every follower and spends one message each. On a large
|
|
76
|
+
account that is the most expensive call available — say what it will cost
|
|
77
|
+
before running it.
|
|
78
|
+
|
|
79
|
+
## How complete each one is
|
|
80
|
+
|
|
81
|
+
**LINE is the deep one** — its entire Messaging API, about seventy operations:
|
|
82
|
+
messaging, rich menus, audiences and narrowcast, insight, quota, content,
|
|
83
|
+
groups and rooms, and the webhook endpoint itself.
|
|
84
|
+
|
|
85
|
+
**Telegram, Discord and Slack carry what a chat agent needs** — send, reply,
|
|
86
|
+
edit, delete, react, typing indicator, threads and DMs, plus reading people and
|
|
87
|
+
conversations. Not their whole APIs. `ops` is the truth; ask it rather than
|
|
88
|
+
assuming an operation exists.
|
|
89
|
+
|
|
90
|
+
## Rich menus, in order (LINE)
|
|
91
|
+
|
|
92
|
+
A rich menu does nothing until it has both a picture and a place to appear:
|
|
93
|
+
|
|
94
|
+
1. `richmenu_create` — the layout and tappable areas. Returns an id.
|
|
95
|
+
2. `richmenu_upload_image` — `content` is the file **base64-encoded**, with
|
|
96
|
+
`content_type`. The image must match the declared size exactly.
|
|
97
|
+
3. `richmenu_set_default` (everybody) or `richmenu_link_user` (one person).
|
|
98
|
+
|
|
99
|
+
`richmenu_validate` checks a layout without creating it.
|
|
100
|
+
|
|
101
|
+
## When it is an agent doing this
|
|
102
|
+
|
|
103
|
+
An Agent Cloud agent in a project with a connected account gets these as tools
|
|
104
|
+
automatically — there is nothing to bind, and no capability to switch on. Sends
|
|
105
|
+
go through the agent's approval gate; reads do not. See
|
|
106
|
+
`workser help agent-cloud`.
|
|
@@ -19,7 +19,8 @@ that URL directly in the app.
|
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
21
|
workser image generate "flat illustration of a farm delivery van, brand colors" --json
|
|
22
|
-
workser image gen "same van, from the side" -r https://…
|
|
22
|
+
workser image gen "same van, from the side" -r https://… --json # use .data.url
|
|
23
|
+
workser image gen "van, rear view" -o /tmp/van.png --json # only if you must
|
|
23
24
|
```
|
|
24
25
|
|
|
25
26
|
## Notes that matter (generation)
|
|
@@ -30,6 +31,16 @@ workser image gen "same van, from the side" -r https://… -o ./public/van.png -
|
|
|
30
31
|
question comes back as text rather than an image. Check that you actually got an
|
|
31
32
|
image before wiring the URL into a page; an empty result is not a transport error
|
|
32
33
|
to retry.
|
|
34
|
+
- **The URL is the deliverable — don't download it into the app folder.** This
|
|
35
|
+
example used to write to `./public/van.png`, which is the single most common way
|
|
36
|
+
generated art ends up committed to the user's repository: in every deploy bundle
|
|
37
|
+
for ever, unreplaceable without a redeploy, against a 25MB publish cap. The
|
|
38
|
+
returned URL is already public and already served. Reference it.
|
|
39
|
+
|
|
40
|
+
When a file genuinely has to exist — an asset the build reads, something to hand
|
|
41
|
+
the user — `-o` into a temp path and `workser storage put` it into the bucket
|
|
42
|
+
(or whatever store the owner chose; the repo is never it). See
|
|
43
|
+
`reference/storage.md`.
|
|
33
44
|
- **`--output` writes only the first image.** If you asked for several, the rest
|
|
34
45
|
exist only as URLs.
|
|
35
46
|
- **Placeholder art is not a deliverable.** Generating a hero image to unblock a
|
|
@@ -18,6 +18,40 @@ workser storage put <local> <key> # upload a file into the bucket
|
|
|
18
18
|
workser storage get <key> [dest] # download an object (or print its URL)
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
## This is where media goes — the folder is for code
|
|
22
|
+
|
|
23
|
+
Every project has this bucket, provisioned and paid for, served over a CDN, and
|
|
24
|
+
visible to the owner on the Files screen. It is the **default** home for generated
|
|
25
|
+
art, product photography, PDFs, exports, avatars and anything a user uploads.
|
|
26
|
+
|
|
27
|
+
**Default, not mandate.** An owner who wants Cloudinary, their own S3, or anything
|
|
28
|
+
else is entitled to it — build it and don't argue. What is not yours to do is pick
|
|
29
|
+
the alternative for them, or drift into one because it was quicker. If you think
|
|
30
|
+
there is a real reason to go outside, say so and let them answer, then
|
|
31
|
+
`workser decision create` it so the next agent doesn't quietly reverse it.
|
|
32
|
+
|
|
33
|
+
**The repo is not one of the options.** Whichever provider the owner chose, files
|
|
34
|
+
under `public/`, `assets/` or `static/` cost them four things, none recoverable
|
|
35
|
+
later:
|
|
36
|
+
|
|
37
|
+
- **They are committed.** Publishing runs `git add -A` over the app folder, so
|
|
38
|
+
every image enters the repository's history. Deleting it afterwards does not
|
|
39
|
+
remove it.
|
|
40
|
+
- **They ride in every deploy.** The source bundle has a 25MB cap; enough media
|
|
41
|
+
and publishing stops working outright, with an error about the bundle rather
|
|
42
|
+
than about the images.
|
|
43
|
+
- **They cannot change without a redeploy.** A photo the owner wants swapped
|
|
44
|
+
becomes a code change and a build. From the bucket it is one `storage put`.
|
|
45
|
+
- **They are invisible.** The Files screen lists the bucket. Nothing there shows
|
|
46
|
+
what is sitting in the repo, so the owner cannot find, replace or delete it.
|
|
47
|
+
|
|
48
|
+
Small build-time assets — a logo, a favicon, an icon, an SVG the bundler inlines —
|
|
49
|
+
are the exception. Anything that is content, or that a user produced, is not.
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
workser storage put ./out/hero.png products/hero.png --json # → .data.url
|
|
53
|
+
```
|
|
54
|
+
|
|
21
55
|
## Notes that matter
|
|
22
56
|
|
|
23
57
|
- **One bucket per project, shared by its apps.** Namespace your keys by app or
|
|
@@ -27,7 +61,9 @@ workser storage get <key> [dest] # download an object (or print its URL)
|
|
|
27
61
|
- **This is not where app uploads should go through you.** At runtime the app uses
|
|
28
62
|
`workser.storage` from `@workser/app`, and for anything large it should request a
|
|
29
63
|
presigned upload URL so the bytes never pass through Workser. See the
|
|
30
|
-
`workser-sdk` skill.
|
|
64
|
+
`workser-sdk` skill. Writing an upload handler that saves into the app's own
|
|
65
|
+
filesystem is the same mistake as above, plus one more: on a serverless host the
|
|
66
|
+
file is gone at the end of the request.
|
|
31
67
|
|
|
32
68
|
## Not the same as `workser neon storage`
|
|
33
69
|
|