@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 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 forever; if it does, carry
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
- "create_app only: web | mobile | api | worker | cron | python"
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
- "Say which kind of app: `--app-type mobile`.",
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.20" : "0.0.0-dev"
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.20",
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",
@@ -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 forever; if it does, carry
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.