@workser/cli 0.6.20 → 0.6.22
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 +261 -5
- package/package.json +1 -1
- package/skills/workser/SKILL.md +2 -0
- package/skills/workser/reference/apps.md +77 -0
- package/skills/workser/reference/deliverables.md +29 -2
- package/skills/workser/reference/desktop.md +98 -0
- package/skills/workser/reference/env.md +7 -0
- package/skills/workser/reference/neon-backend.md +29 -0
package/dist/index.js
CHANGED
|
@@ -4035,6 +4035,84 @@ built next.
|
|
|
4035
4035
|
Run it alongside \`workser verify\` before declaring an API task finished. An API
|
|
4036
4036
|
somebody can call and an API somebody can integrate with are different products,
|
|
4037
4037
|
and the spec is the difference.
|
|
4038
|
+
`
|
|
4039
|
+
},
|
|
4040
|
+
{
|
|
4041
|
+
topic: "apps",
|
|
4042
|
+
title: "The project's other apps \u2014 what they are, and wiring one to another",
|
|
4043
|
+
summary: "List every app in the project with its type, deploy state and URL; read a sibling's settings without exposing them; point a client at the right service.",
|
|
4044
|
+
commands: ["project", "env"],
|
|
4045
|
+
source: "skills/workser/reference/apps.md",
|
|
4046
|
+
body: `# The project's other apps
|
|
4047
|
+
|
|
4048
|
+
A project holds several apps \u2014 a web app, one or more services, maybe a phone
|
|
4049
|
+
or desktop app \u2014 and each has its own variables. Every command here takes
|
|
4050
|
+
\`--app <webAppId>\`; without it you are operating on the app whose folder you
|
|
4051
|
+
are in.
|
|
4052
|
+
|
|
4053
|
+
Start from the inventory rather than guessing:
|
|
4054
|
+
|
|
4055
|
+
\`\`\`bash
|
|
4056
|
+
workser project apps # every app: id, type, status, URL, local folder
|
|
4057
|
+
workser project app <id> # one app: preview + production URLs, local path
|
|
4058
|
+
\`\`\`
|
|
4059
|
+
|
|
4060
|
+
That answers the questions you actually need before wiring anything: which
|
|
4061
|
+
services exist, whether each is **deployed or only local**, and what its
|
|
4062
|
+
address is per environment.
|
|
4063
|
+
|
|
4064
|
+
### Reading another app's config without exposing it
|
|
4065
|
+
|
|
4066
|
+
\`\`\`bash
|
|
4067
|
+
workser env list --app <id> --env production # KEYS only \u2014 values are masked
|
|
4068
|
+
workser env get <key> --app <id> # the value. Sensitive.
|
|
4069
|
+
\`\`\`
|
|
4070
|
+
|
|
4071
|
+
\`list\` is the one to reach for. It answers "does that service already have a
|
|
4072
|
+
\`STRIPE_SECRET_KEY\`?" without printing it, which is almost always the question.
|
|
4073
|
+
Use \`get\` only when you genuinely need the value in hand, and never echo it into
|
|
4074
|
+
a file the user will read, a commit, or a log.
|
|
4075
|
+
|
|
4076
|
+
### Copying a value from one app to another
|
|
4077
|
+
|
|
4078
|
+
\`\`\`bash
|
|
4079
|
+
workser env set KEY="$(workser env get KEY --app <from> --quiet)" --app <to>
|
|
4080
|
+
\`\`\`
|
|
4081
|
+
|
|
4082
|
+
Two rules that are not optional:
|
|
4083
|
+
|
|
4084
|
+
- **Match the environment.** A production key set into preview, or the reverse,
|
|
4085
|
+
produces an app that works in one place and fails in the other with no
|
|
4086
|
+
message. Pass \`--env\` on both sides.
|
|
4087
|
+
- **Never copy a secret into a phone or desktop app.** Those bundles install on
|
|
4088
|
+
someone's device and anything inside can be read by whoever installs it. A
|
|
4089
|
+
client gets public values only (\`EXPO_PUBLIC_*\`, \`NEXT_PUBLIC_*\`); anything
|
|
4090
|
+
privileged stays in a service and the client reaches it over HTTP.
|
|
4091
|
+
|
|
4092
|
+
### Pulling config into a local file
|
|
4093
|
+
|
|
4094
|
+
\`\`\`bash
|
|
4095
|
+
workser env pull --app <id> --env preview --out .env.local
|
|
4096
|
+
\`\`\`
|
|
4097
|
+
|
|
4098
|
+
Writes that app's cloud variables locally so \`npm run dev\` behaves like the
|
|
4099
|
+
deployed app. Keep the file out of git.
|
|
4100
|
+
|
|
4101
|
+
### Pointing a client at a service
|
|
4102
|
+
|
|
4103
|
+
There is no magic wiring. Read the service's URL from \`project app <id>\`, then
|
|
4104
|
+
set it on the client under the key that client reads:
|
|
4105
|
+
|
|
4106
|
+
| Client | Key |
|
|
4107
|
+
| --- | --- |
|
|
4108
|
+
| Phone app (Expo) | \`EXPO_PUBLIC_API_URL\` |
|
|
4109
|
+
| Desktop app | \`NEXT_PUBLIC_API_URL\` |
|
|
4110
|
+
| Web app | \`NEXT_PUBLIC_API_URL\` |
|
|
4111
|
+
|
|
4112
|
+
Workser sets this for you **only** when the project has exactly one service \u2014
|
|
4113
|
+
then it is not a guess. With two or more, the choice is yours to make and to
|
|
4114
|
+
say out loud, because nothing else can know which service that client belongs
|
|
4115
|
+
to.
|
|
4038
4116
|
`
|
|
4039
4117
|
},
|
|
4040
4118
|
{
|
|
@@ -4491,8 +4569,35 @@ turn.
|
|
|
4491
4569
|
|
|
4492
4570
|
Types: \`input\` (default, free text), \`choice\` (with \`--option\`), \`approval\`
|
|
4493
4571
|
(permission), \`confirmation\` (check an assumption), \`information\` (FYI, no answer
|
|
4494
|
-
needed). It times out (default 10 min) rather than hanging
|
|
4495
|
-
on and state clearly what you assumed.
|
|
4572
|
+
needed), \`create_app\` (below). It times out (default 10 min) rather than hanging
|
|
4573
|
+
forever; if it does, carry on and state clearly what you assumed.
|
|
4574
|
+
|
|
4575
|
+
## Asking for an app that does not exist yet
|
|
4576
|
+
|
|
4577
|
+
The project needs a kind of app it does not have \u2014 a backend for the phone app, a
|
|
4578
|
+
desktop build of the website. **You cannot create one yourself**, by design: apps
|
|
4579
|
+
are real infrastructure on the owner's account. Ask, and their click creates it.
|
|
4580
|
+
The answer gives you the new app's id.
|
|
4581
|
+
|
|
4582
|
+
\`\`\`
|
|
4583
|
+
workser ask "The phone app needs an API to hold its data. Add one?" \\
|
|
4584
|
+
--type create_app --app-type api --app-name "Lattice Drive API"
|
|
4585
|
+
\`\`\`
|
|
4586
|
+
|
|
4587
|
+
\`--app-type\` is required and must be one of:
|
|
4588
|
+
|
|
4589
|
+
| | |
|
|
4590
|
+
|---|---|
|
|
4591
|
+
| \`web\` | Next.js site on Workser hosting |
|
|
4592
|
+
| \`mobile\` | Expo / React Native phone app |
|
|
4593
|
+
| \`desktop\` | Next.js + Electron, installs on a Mac or PC |
|
|
4594
|
+
| \`api\` | backend service \u2014 \`api-hono\` (TypeScript) or \`api-python\` (FastAPI) |
|
|
4595
|
+
|
|
4596
|
+
**Name the kind you actually mean.** Asking for \`web\` because you are unsure is
|
|
4597
|
+
how a desktop app gets created as a website: the card shows the owner the kind
|
|
4598
|
+
you named, they approve THAT, and the wrong app is provisioned under your own
|
|
4599
|
+
sentence asking for the right one. An unknown kind is refused with the list
|
|
4600
|
+
above rather than guessed at \u2014 read it and ask again.
|
|
4496
4601
|
|
|
4497
4602
|
**Never ask for a secret value this way** \u2014 the answer is stored and displayed. Ask
|
|
4498
4603
|
*where* a key should go, then have the user set it (\`workser env set\` writes it
|
|
@@ -4569,6 +4674,105 @@ workser artifact add --url https://acme.workser.app --kind app -t "Storefront"
|
|
|
4569
4674
|
\`\`\`
|
|
4570
4675
|
|
|
4571
4676
|
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.
|
|
4572
4776
|
`
|
|
4573
4777
|
},
|
|
4574
4778
|
{
|
|
@@ -4776,6 +4980,13 @@ same rule.
|
|
|
4776
4980
|
- **Cloud and local are different environments.** \`env set\` configures the
|
|
4777
4981
|
cloud; the files in the app folder configure this computer. Don't hand-edit
|
|
4778
4982
|
one to change the other.
|
|
4983
|
+
|
|
4984
|
+
## Another app's variables
|
|
4985
|
+
|
|
4986
|
+
Every command here takes \`--app <webAppId>\`; without it you are operating on
|
|
4987
|
+
the app whose folder you are in. Reading a sibling service's config, copying a
|
|
4988
|
+
value across, and pointing a client at a service are all covered in
|
|
4989
|
+
\`workser help apps\`.
|
|
4779
4990
|
`
|
|
4780
4991
|
},
|
|
4781
4992
|
{
|
|
@@ -5071,6 +5282,35 @@ connect to it** \u2014 useful as a snapshot, useless as somewhere to run tests.
|
|
|
5071
5282
|
is this database costing me while nothing is happening".
|
|
5072
5283
|
- **Most apps never need the storage or functions half.** If the user just wants
|
|
5073
5284
|
to store uploads, the default bucket in \`reference/storage.md\` is the answer.
|
|
5285
|
+
|
|
5286
|
+
## Sign-in for the app you are building
|
|
5287
|
+
|
|
5288
|
+
**WEB APPS ONLY.** Everything in this section is about the \`web\` app type. Neon's
|
|
5289
|
+
managed auth is consumed through a Next.js server route (\`/api/auth/*\`), so it
|
|
5290
|
+
does not reach a mobile app, and it does not reach a desktop app either \u2014 that
|
|
5291
|
+
type is a static export and cannot serve a route at all. For those two, auth
|
|
5292
|
+
belongs in the project's API service, self-hosted, where Google needs the owner's
|
|
5293
|
+
own credentials like anywhere else. Do not tell a phone-app owner that Google
|
|
5294
|
+
sign-in is free.
|
|
5295
|
+
|
|
5296
|
+
**On a web app with Neon-managed auth, Google sign-in is already on.** Neon runs
|
|
5297
|
+
it on its own shared OAuth credentials \u2014 there is no client ID to create, no
|
|
5298
|
+
secret to set, no redirect URI to register. If the owner asks for "login with
|
|
5299
|
+
Google", build the button; do not send them to the Google Cloud Console.
|
|
5300
|
+
|
|
5301
|
+
Check which mode the app is on before answering: \`workser env get AUTH_MODE\`.
|
|
5302
|
+
\`neon_managed\` means the above. Anything else (or absent) is self-hosted, where
|
|
5303
|
+
Google needs the owner's own \`GOOGLE_CLIENT_ID\`/\`GOOGLE_CLIENT_SECRET\` and a
|
|
5304
|
+
redirect URI per domain.
|
|
5305
|
+
|
|
5306
|
+
Two caveats worth saying out loud rather than letting the owner find later:
|
|
5307
|
+
|
|
5308
|
+
- **The consent screen says Neon, not their brand**, and the quota is shared.
|
|
5309
|
+
Correct for a prototype, wrong once real users sign in. Moving to their own
|
|
5310
|
+
Google client is done in the desktop app under **Cloud -> Auth -> Sign-in
|
|
5311
|
+
providers**, which also shows the exact redirect URI to register.
|
|
5312
|
+
- **GitHub and Vercel have no shared credentials.** Those stay off until the
|
|
5313
|
+
owner installs their own client in that same place.
|
|
5074
5314
|
`
|
|
5075
5315
|
},
|
|
5076
5316
|
{
|
|
@@ -9048,6 +9288,16 @@ var TYPES = [
|
|
|
9048
9288
|
*/
|
|
9049
9289
|
"create_app"
|
|
9050
9290
|
];
|
|
9291
|
+
var APP_TYPES = [
|
|
9292
|
+
"web",
|
|
9293
|
+
"mobile",
|
|
9294
|
+
"desktop",
|
|
9295
|
+
// `api` is the shorthand; the two concrete spellings pick the runtime and
|
|
9296
|
+
// are accepted at this boundary for an agent that knows which one it wants.
|
|
9297
|
+
"api",
|
|
9298
|
+
"api-hono",
|
|
9299
|
+
"api-python"
|
|
9300
|
+
];
|
|
9051
9301
|
function registerAsk(program3) {
|
|
9052
9302
|
program3.command("ask <message>").description("Ask the user a question and wait for their answer").option(
|
|
9053
9303
|
"-t, --type <type>",
|
|
@@ -9060,7 +9310,7 @@ function registerAsk(program3) {
|
|
|
9060
9310
|
[]
|
|
9061
9311
|
).option(
|
|
9062
9312
|
"--app-type <type>",
|
|
9063
|
-
|
|
9313
|
+
`create_app only: ${APP_TYPES.join(" | ")}`
|
|
9064
9314
|
).option(
|
|
9065
9315
|
"--app-name <name>",
|
|
9066
9316
|
"create_app only: what the app should be called"
|
|
@@ -9092,7 +9342,13 @@ function registerAsk(program3) {
|
|
|
9092
9342
|
}
|
|
9093
9343
|
if (type === "create_app" && !opts.appType) {
|
|
9094
9344
|
throw new WorkserError(
|
|
9095
|
-
|
|
9345
|
+
`Say which kind of app: \`--app-type desktop\`. One of: ${APP_TYPES.join(", ")}.`,
|
|
9346
|
+
{ code: "bad_request" }
|
|
9347
|
+
);
|
|
9348
|
+
}
|
|
9349
|
+
if (type === "create_app" && opts.appType && !APP_TYPES.includes(opts.appType)) {
|
|
9350
|
+
throw new WorkserError(
|
|
9351
|
+
`Unknown --app-type "${opts.appType}". Use one of: ${APP_TYPES.join(", ")}.`,
|
|
9096
9352
|
{ code: "bad_request" }
|
|
9097
9353
|
);
|
|
9098
9354
|
}
|
|
@@ -12189,7 +12445,7 @@ function colour(d) {
|
|
|
12189
12445
|
|
|
12190
12446
|
// src/index.ts
|
|
12191
12447
|
var pkg = {
|
|
12192
|
-
version: true ? "0.6.
|
|
12448
|
+
version: true ? "0.6.22" : "0.0.0-dev"
|
|
12193
12449
|
};
|
|
12194
12450
|
var program2 = new Command();
|
|
12195
12451
|
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.22",
|
|
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
|
@@ -21,6 +21,8 @@ screen long. Find your row, run that **one** command — every line costs you.
|
|
|
21
21
|
| See what was decided or written down for this project | `decision …`, `requirement …`, `doc …` | `workser help sdlc-entities` |
|
|
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
|
+
| 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` |
|
|
24
26
|
| Deploy, set env vars, read logs, check a domain | `deploy`, `env …`, `logs`, `versions`, `domain`, `open` | `workser help deploy` |
|
|
25
27
|
| Save work before a risky change, undo it, sync this folder | `checkpoint`, `restore`, `sync` | `workser help version-control` |
|
|
26
28
|
| Put files in the project's bucket | `storage …` | `workser help storage` |
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: apps
|
|
3
|
+
title: The project's other apps — what they are, and wiring one to another
|
|
4
|
+
summary: List every app in the project with its type, deploy state and URL; read a sibling's settings without exposing them; point a client at the right service.
|
|
5
|
+
commands: [project, env]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# The project's other apps
|
|
9
|
+
|
|
10
|
+
A project holds several apps — a web app, one or more services, maybe a phone
|
|
11
|
+
or desktop app — and each has its own variables. Every command here takes
|
|
12
|
+
`--app <webAppId>`; without it you are operating on the app whose folder you
|
|
13
|
+
are in.
|
|
14
|
+
|
|
15
|
+
Start from the inventory rather than guessing:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
workser project apps # every app: id, type, status, URL, local folder
|
|
19
|
+
workser project app <id> # one app: preview + production URLs, local path
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
That answers the questions you actually need before wiring anything: which
|
|
23
|
+
services exist, whether each is **deployed or only local**, and what its
|
|
24
|
+
address is per environment.
|
|
25
|
+
|
|
26
|
+
### Reading another app's config without exposing it
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
workser env list --app <id> --env production # KEYS only — values are masked
|
|
30
|
+
workser env get <key> --app <id> # the value. Sensitive.
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`list` is the one to reach for. It answers "does that service already have a
|
|
34
|
+
`STRIPE_SECRET_KEY`?" without printing it, which is almost always the question.
|
|
35
|
+
Use `get` only when you genuinely need the value in hand, and never echo it into
|
|
36
|
+
a file the user will read, a commit, or a log.
|
|
37
|
+
|
|
38
|
+
### Copying a value from one app to another
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
workser env set KEY="$(workser env get KEY --app <from> --quiet)" --app <to>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Two rules that are not optional:
|
|
45
|
+
|
|
46
|
+
- **Match the environment.** A production key set into preview, or the reverse,
|
|
47
|
+
produces an app that works in one place and fails in the other with no
|
|
48
|
+
message. Pass `--env` on both sides.
|
|
49
|
+
- **Never copy a secret into a phone or desktop app.** Those bundles install on
|
|
50
|
+
someone's device and anything inside can be read by whoever installs it. A
|
|
51
|
+
client gets public values only (`EXPO_PUBLIC_*`, `NEXT_PUBLIC_*`); anything
|
|
52
|
+
privileged stays in a service and the client reaches it over HTTP.
|
|
53
|
+
|
|
54
|
+
### Pulling config into a local file
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
workser env pull --app <id> --env preview --out .env.local
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Writes that app's cloud variables locally so `npm run dev` behaves like the
|
|
61
|
+
deployed app. Keep the file out of git.
|
|
62
|
+
|
|
63
|
+
### Pointing a client at a service
|
|
64
|
+
|
|
65
|
+
There is no magic wiring. Read the service's URL from `project app <id>`, then
|
|
66
|
+
set it on the client under the key that client reads:
|
|
67
|
+
|
|
68
|
+
| Client | Key |
|
|
69
|
+
| --- | --- |
|
|
70
|
+
| Phone app (Expo) | `EXPO_PUBLIC_API_URL` |
|
|
71
|
+
| Desktop app | `NEXT_PUBLIC_API_URL` |
|
|
72
|
+
| Web app | `NEXT_PUBLIC_API_URL` |
|
|
73
|
+
|
|
74
|
+
Workser sets this for you **only** when the project has exactly one service —
|
|
75
|
+
then it is not a guess. With two or more, the choice is yours to make and to
|
|
76
|
+
say out loud, because nothing else can know which service that client belongs
|
|
77
|
+
to.
|
|
@@ -95,8 +95,35 @@ turn.
|
|
|
95
95
|
|
|
96
96
|
Types: `input` (default, free text), `choice` (with `--option`), `approval`
|
|
97
97
|
(permission), `confirmation` (check an assumption), `information` (FYI, no answer
|
|
98
|
-
needed). It times out (default 10 min) rather than hanging
|
|
99
|
-
on and state clearly what you assumed.
|
|
98
|
+
needed), `create_app` (below). It times out (default 10 min) rather than hanging
|
|
99
|
+
forever; if it does, carry on and state clearly what you assumed.
|
|
100
|
+
|
|
101
|
+
## Asking for an app that does not exist yet
|
|
102
|
+
|
|
103
|
+
The project needs a kind of app it does not have — a backend for the phone app, a
|
|
104
|
+
desktop build of the website. **You cannot create one yourself**, by design: apps
|
|
105
|
+
are real infrastructure on the owner's account. Ask, and their click creates it.
|
|
106
|
+
The answer gives you the new app's id.
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
workser ask "The phone app needs an API to hold its data. Add one?" \
|
|
110
|
+
--type create_app --app-type api --app-name "Lattice Drive API"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`--app-type` is required and must be one of:
|
|
114
|
+
|
|
115
|
+
| | |
|
|
116
|
+
|---|---|
|
|
117
|
+
| `web` | Next.js site on Workser hosting |
|
|
118
|
+
| `mobile` | Expo / React Native phone app |
|
|
119
|
+
| `desktop` | Next.js + Electron, installs on a Mac or PC |
|
|
120
|
+
| `api` | backend service — `api-hono` (TypeScript) or `api-python` (FastAPI) |
|
|
121
|
+
|
|
122
|
+
**Name the kind you actually mean.** Asking for `web` because you are unsure is
|
|
123
|
+
how a desktop app gets created as a website: the card shows the owner the kind
|
|
124
|
+
you named, they approve THAT, and the wrong app is provisioned under your own
|
|
125
|
+
sentence asking for the right one. An unknown kind is refused with the list
|
|
126
|
+
above rather than guessed at — read it and ask again.
|
|
100
127
|
|
|
101
128
|
**Never ask for a secret value this way** — the answer is stored and displayed. Ask
|
|
102
129
|
*where* a key should go, then have the user set it (`workser env set` writes it
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: desktop
|
|
3
|
+
title: Desktop apps — one codebase, a window, and an installer
|
|
4
|
+
summary: How a Workser desktop app is built, what breaks only after it is installed, and how to produce and publish a signed installer.
|
|
5
|
+
commands: [project, env, deploy]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Desktop apps
|
|
9
|
+
|
|
10
|
+
A Workser desktop app is **one Next.js codebase with two ways to run**: a normal
|
|
11
|
+
web build for development and preview, and an Electron shell that loads the
|
|
12
|
+
static export from disk and gets packaged into an installer.
|
|
13
|
+
|
|
14
|
+
There is no deploy. A desktop app has no Vercel project and no URL — its
|
|
15
|
+
preview is the app running locally, and its deliverable is a file someone
|
|
16
|
+
double-clicks.
|
|
17
|
+
|
|
18
|
+
## The rule that breaks everything, and only after install
|
|
19
|
+
|
|
20
|
+
**The app must stay statically exportable.** A packaged Electron app loads from
|
|
21
|
+
`file://` and has **no Node server**. So none of this exists once it is
|
|
22
|
+
installed:
|
|
23
|
+
|
|
24
|
+
- server components that fetch at request time
|
|
25
|
+
- server actions (`"use server"`)
|
|
26
|
+
- route handlers under `app/api/*`
|
|
27
|
+
- `next/image` optimisation
|
|
28
|
+
|
|
29
|
+
Every one of them works in `npm run dev` **and** in the local preview, and fails
|
|
30
|
+
only on the owner's machine. That is the one direction of error nobody catches,
|
|
31
|
+
so `next.config.mjs` keeps `output: "export"` to make the build fail loudly
|
|
32
|
+
instead. Do not remove it to "fix" an import.
|
|
33
|
+
|
|
34
|
+
Need a server? Use the project's api app — `workser project apps` to find it,
|
|
35
|
+
then read its URL and set `NEXT_PUBLIC_API_URL`. See `workser help apps`.
|
|
36
|
+
|
|
37
|
+
## Secrets: this is a public client
|
|
38
|
+
|
|
39
|
+
Anyone who installs the app can read every string inside it. So:
|
|
40
|
+
|
|
41
|
+
- `NEXT_PUBLIC_*` only, and nothing behind that prefix is private
|
|
42
|
+
- never a database URL, an API key, or a `wsgw_` gateway key
|
|
43
|
+
- anything privileged lives in the api app and is reached over HTTP
|
|
44
|
+
|
|
45
|
+
The manifest deliberately does not declare `AI_GATEWAY_API_KEY` for this type,
|
|
46
|
+
which is what stops Workser seeding one. Do not add it.
|
|
47
|
+
|
|
48
|
+
## The two processes
|
|
49
|
+
|
|
50
|
+
| File | Runs where | May do |
|
|
51
|
+
| --- | --- | --- |
|
|
52
|
+
| `electron/main.js` | Node, on the machine | files, tray, windows, updates |
|
|
53
|
+
| `app/**` | the renderer, sandboxed | ordinary web code, no Node |
|
|
54
|
+
| `electron/preload.js` | the bridge | expose **named functions** only |
|
|
55
|
+
|
|
56
|
+
`contextIsolation: true` and `nodeIntegration: false` are not defaults to tidy
|
|
57
|
+
up — turning either off hands full filesystem and process access to page code.
|
|
58
|
+
Add a capability by exporting one named function from preload, never by handing
|
|
59
|
+
the renderer `ipcRenderer`.
|
|
60
|
+
|
|
61
|
+
## Building and shipping
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm run dev # browser only, fastest loop
|
|
65
|
+
npm run dev:desktop # the same app inside a real Electron window
|
|
66
|
+
npm run build # static export -> out/
|
|
67
|
+
npm run dist:desktop # the installer -> release/
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
In the app, **Publish → Your installer** does the same thing with a target
|
|
71
|
+
picker, progress, and a download link at the end.
|
|
72
|
+
|
|
73
|
+
## Signing — the owner's certificate, never Workser's
|
|
74
|
+
|
|
75
|
+
An unsigned build runs perfectly on the machine that made it and tells every
|
|
76
|
+
other machine the app is damaged (macOS) or trips SmartScreen (Windows). That
|
|
77
|
+
is the failure an owner cannot diagnose, so say it before they send the file.
|
|
78
|
+
|
|
79
|
+
`electron-builder` reads these from the environment; set them with
|
|
80
|
+
`workser env set --app <id>`:
|
|
81
|
+
|
|
82
|
+
| Platform | Keys |
|
|
83
|
+
| --- | --- |
|
|
84
|
+
| macOS | `CSC_LINK`, `CSC_KEY_PASSWORD` |
|
|
85
|
+
| macOS notarize | `APPLE_ID`, `APPLE_APP_SPECIFIC_PASSWORD`, `APPLE_TEAM_ID` |
|
|
86
|
+
| Windows | `WIN_CSC_LINK`, `WIN_CSC_KEY_PASSWORD` |
|
|
87
|
+
|
|
88
|
+
A Mac app can only be built on a Mac — `codesign` and `hdiutil` are Apple's and
|
|
89
|
+
have no substitute.
|
|
90
|
+
|
|
91
|
+
## Auto-update
|
|
92
|
+
|
|
93
|
+
Publishing uploads the installer **and** the update metadata
|
|
94
|
+
(`latest-mac.yml` / `latest.yml`) beside it. `electron-updater` reads only that
|
|
95
|
+
metadata, so an installer published alone gives you a download that works once
|
|
96
|
+
and an app that can never update itself — and nobody notices until release two.
|
|
97
|
+
Keep `publish.url` in `electron-builder.yml` pointed at the prefix the publish
|
|
98
|
+
step reports back.
|
|
@@ -113,3 +113,10 @@ same rule.
|
|
|
113
113
|
- **Cloud and local are different environments.** `env set` configures the
|
|
114
114
|
cloud; the files in the app folder configure this computer. Don't hand-edit
|
|
115
115
|
one to change the other.
|
|
116
|
+
|
|
117
|
+
## Another app's variables
|
|
118
|
+
|
|
119
|
+
Every command here takes `--app <webAppId>`; without it you are operating on
|
|
120
|
+
the app whose folder you are in. Reading a sibling service's config, copying a
|
|
121
|
+
value across, and pointing a client at a service are all covered in
|
|
122
|
+
`workser help apps`.
|
|
@@ -74,3 +74,32 @@ connect to it** — useful as a snapshot, useless as somewhere to run tests.
|
|
|
74
74
|
is this database costing me while nothing is happening".
|
|
75
75
|
- **Most apps never need the storage or functions half.** If the user just wants
|
|
76
76
|
to store uploads, the default bucket in `reference/storage.md` is the answer.
|
|
77
|
+
|
|
78
|
+
## Sign-in for the app you are building
|
|
79
|
+
|
|
80
|
+
**WEB APPS ONLY.** Everything in this section is about the `web` app type. Neon's
|
|
81
|
+
managed auth is consumed through a Next.js server route (`/api/auth/*`), so it
|
|
82
|
+
does not reach a mobile app, and it does not reach a desktop app either — that
|
|
83
|
+
type is a static export and cannot serve a route at all. For those two, auth
|
|
84
|
+
belongs in the project's API service, self-hosted, where Google needs the owner's
|
|
85
|
+
own credentials like anywhere else. Do not tell a phone-app owner that Google
|
|
86
|
+
sign-in is free.
|
|
87
|
+
|
|
88
|
+
**On a web app with Neon-managed auth, Google sign-in is already on.** Neon runs
|
|
89
|
+
it on its own shared OAuth credentials — there is no client ID to create, no
|
|
90
|
+
secret to set, no redirect URI to register. If the owner asks for "login with
|
|
91
|
+
Google", build the button; do not send them to the Google Cloud Console.
|
|
92
|
+
|
|
93
|
+
Check which mode the app is on before answering: `workser env get AUTH_MODE`.
|
|
94
|
+
`neon_managed` means the above. Anything else (or absent) is self-hosted, where
|
|
95
|
+
Google needs the owner's own `GOOGLE_CLIENT_ID`/`GOOGLE_CLIENT_SECRET` and a
|
|
96
|
+
redirect URI per domain.
|
|
97
|
+
|
|
98
|
+
Two caveats worth saying out loud rather than letting the owner find later:
|
|
99
|
+
|
|
100
|
+
- **The consent screen says Neon, not their brand**, and the quota is shared.
|
|
101
|
+
Correct for a prototype, wrong once real users sign in. Moving to their own
|
|
102
|
+
Google client is done in the desktop app under **Cloud -> Auth -> Sign-in
|
|
103
|
+
providers**, which also shows the exact redirect URI to register.
|
|
104
|
+
- **GitHub and Vercel have no shared credentials.** Those stay off until the
|
|
105
|
+
owner installs their own client in that same place.
|