@getxflow/cli 0.9.1 → 0.10.1

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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: xflow
3
- description: Build, deploy and publish apps on the XFlow platform with the xflow CLI. Use whenever the project root has xflow.json or VITE_XFLOW_* variables, and for any task that touches deployment, publishing, rollback, source sync, the project database or SQL migrations, cloud functions, schedules, environment variables and secrets, production errors and logs, or UI built on the platform design system.
3
+ description: Build, deploy and publish apps on the XFlow platform with the xflow CLI. Use whenever the project root has xflow.json or VITE_XFLOW_* variables, and for any task that touches deployment, publishing, rollback, source sync, the project database or SQL migrations, cloud functions, schedules, environment variables and secrets, file storage and uploads of photos, video or PDFs, connected accounts of external services, production errors and logs, or UI built on the platform design system.
4
4
  ---
5
5
 
6
6
  # XFlow
@@ -10,6 +10,11 @@ machine, `xflow deploy` sends the sources, and the platform builds them in a cle
10
10
  sandbox and serves the result as static files. A platform project is recognized by
11
11
  the `xflow.json` file in its root.
12
12
 
13
+ No `xflow.json` means the folder is not bound to a project yet. `xflow projects list`
14
+ names the projects of the organization with their ids, `xflow link <id>` binds the
15
+ folder to one of them (an empty folder also gets the sources), and `xflow init` starts
16
+ a new project from the platform template.
17
+
13
18
  ## First rule
14
19
 
15
20
  Check commands and flags against `xflow help` and `xflow help <command>`, not against
@@ -17,9 +22,23 @@ memory. If a command is not in the help output, it does not exist: guessing flag
17
22
  pointless. The CLI prints a hint with almost every error, read it in full, it usually
18
23
  contains the fix.
19
24
 
20
- When the CLI says a newer version is out, or refuses to work because the platform wants
21
- a newer one, run `xflow update`. It also rewrites these instructions, which ship inside
22
- the package: what you are reading may be older than the platform you are working on.
25
+ ## Keeping these instructions current
26
+
27
+ These instructions ship with xflow CLI 0.10.1. They travel inside the package, so the copy
28
+ you are reading can be older than the CLI answering your commands, and nothing about that
29
+ is visible in the text itself.
30
+
31
+ When the CLI says this file is out of date, run `xflow skills --refresh` and then **read
32
+ again the file it names**. Refreshing only writes new text to disk; until you re-read it you
33
+ are still working from the old rules, and the command will look like it did nothing. This
34
+ repair is yours to make, no need to ask anyone.
35
+
36
+ Updating the CLI itself is a different matter and not yours by default. `xflow update`
37
+ replaces a program shared by every project on the machine, and in most installations it
38
+ cannot even do that: it prints the command for a person instead. So when a newer version is
39
+ merely available, finish the task and offer the update in your summary. The one exception is
40
+ a platform refusing to work with the CLI as too old: nothing runs until it is updated, so say
41
+ what you are doing and run it.
23
42
 
24
43
  ## Hard rules
25
44
 
@@ -39,15 +58,22 @@ the error points away from the cause. The sections below carry the details.
39
58
  nothing. A schema change is always a new file.
40
59
  5. When inserts start failing with `db_write_locked`, the database is over its plan
41
60
  size. The fix is a migration that deletes data, never a rewrite of the failing SQL.
42
- 6. In file storage, call `confirm` only after the PUT has finished, and send the same
43
- `Content-Type` in both calls. Both mistakes answer 200 and break later.
61
+ 6. In file storage, call `confirm` only after the PUT has finished: the platform reads
62
+ the real size from storage, so an early call answers that the file is not there.
63
+ 7. Heavy media does not travel with the sources. Photos, video and PDFs go up with
64
+ `xflow storage push`, not into the repository: the archive is capped at 10 MB.
44
65
 
45
66
  ## Plan limits
46
67
 
47
68
  The organization runs on a plan with finite limits: projects, cloud functions, developer
48
- and staff seats, database and file storage, function minutes per month, plus the right to
49
- use schedules. `xflow whoami` prints every one of them next to what is already used, and
50
- reading it before a long task is cheaper than hitting a wall mid-way.
69
+ and staff seats, database and file storage, function minutes per month, plus schedules and
70
+ how often they may run. `xflow whoami` prints every one of them next to what is already
71
+ used, and reading it before a long task is cheaper than hitting a wall mid-way.
72
+
73
+ The same output names the rights of your own key, which is the other half of the answer:
74
+ destroying data in a migration, deleting files from storage and linking a connected
75
+ account each need a right that is off by default. Reading that line first turns a refusal
76
+ halfway through the work into a sentence said before it starts.
51
77
 
52
78
  A limit refusal is not a bad request. The CLI prints a line starting with `Plan limit:`,
53
79
  the API answers `code: "forbidden"` with a `limit` object (`code`, `used`, `limit`), and
@@ -55,17 +81,21 @@ MCP tools carry the same field. Retrying the command, renaming things or rewriti
55
81
  code changes nothing: tell the user what ran out and stop. Only the owner or an admin
56
82
  lifts it, in the web interface, by freeing the resource or moving to a bigger plan.
57
83
 
58
- One refusal looks like a code error but is not: when the database is over its plan size,
59
- Postgres itself rejects inserts (`db_write_locked`). Reads and deletes still work, so the
60
- fix is a migration that deletes data, never a rewrite of the failing SQL. Write is
61
- restored within an hour of the data going back under the limit.
84
+ A schedule refused for running too often is one of these too, not a bad cron string: the
85
+ minimum interval comes from the plan, so widen the interval or say what the plan allows.
86
+ The number is in `xflow whoami`, never guess it.
87
+
88
+ On `db_write_locked` (hard rule 5) it is Postgres itself refusing, not the platform. Reads
89
+ and deletes keep working, which is what makes a deleting migration the way out. Write comes
90
+ back within an hour of the data going under the limit.
62
91
 
63
92
  ## Workflow
64
93
 
65
94
  1. Change the code.
66
95
  2. `npm run typecheck` for a two-second type check (older projects may not have the
67
96
  script, then `npx tsc --noEmit`).
68
- 3. `npm run build` if the change is substantial, before deploying.
97
+ 3. `npm run build` if the change is substantial. The platform builds again in its own
98
+ sandbox, on one Node version for everyone, so this is only a fast way to see errors early.
69
99
  4. `xflow deploy` sends the sources, ships the cloud functions and builds the application
70
100
  on the platform, printing each phase and the six-digit number of the version it built.
71
101
  5. Give the user the project link the CLI printed and let them look. Do not open a
@@ -77,25 +107,26 @@ Until `publish` runs, visitors keep seeing the previous pages. The one exception
77
107
  the very first version of a project: it publishes automatically, since there is no
78
108
  live version to protect yet.
79
109
 
110
+ `xflow status` says how the local copy differs from the server revision. When a deploy is
111
+ rejected before the build starts, the server revision is newer: somebody deployed first.
112
+ Fetch their work next to yours (`xflow pull --into ./server-copy`), merge it in git, deploy
113
+ again. `--force` destroys their work, a last resort rather than a way around the error.
114
+
80
115
  **Only pages are versioned.** Cloud functions and the database are one per project: they
81
116
  are not versioned, and dev and live share them. So `xflow deploy` changes the running
82
117
  application the moment it finishes, before any `publish`, and `publish` moves the pages
83
118
  only. Tell the user this when a deploy touches `functions/`: there is no staging step for
84
119
  server code.
85
120
 
86
- Rolling back: `xflow deployments` lists the version history, `xflow rollback <id>` points
87
- the project at the pages of an earlier build. Only the pages come back those pages then
88
- talk to today's functions. The sources stay on their own revision; fetching the code of
89
- that build is a separate command (`xflow pull --revision N --into ../old-version`, keep the
90
- copy outside the project folder). Building that code over the current one is a further,
91
- deliberate step: it replaces the server revision, needs `--force`, and asks a human to type
92
- the project name, so you cannot do it on your own.
121
+ Rolling back: `xflow deployments` lists the history, `xflow rollback <id>` points the project
122
+ at the pages of an earlier build. Only the pages come back, and they then talk to today's
123
+ functions. The code of that build is a separate fetch (`xflow pull --revision N --into
124
+ ../old-version`, kept outside the project folder); building it over the current one needs
125
+ `--force` and a human typing the project name, so you cannot do it on your own.
93
126
 
94
- The platform keeps the **last 30 successful builds**. Older ones are deleted, files and
95
- history row alike, and rolling back to them stops working. That is rollback depth, not a
96
- backup of the code: the code lives in revisions (the last 100, and a revision a live
97
- version was built from is never deleted), and a build is made from a revision again.
98
- Keep anything you must not lose in your own git repository, not in the version history.
127
+ The platform keeps the **last 30 successful builds** and the **last 100 revisions** (a
128
+ revision a live version was built from is never deleted). That is rollback depth, not a
129
+ backup: keep anything you must not lose in your own git repository.
99
130
 
100
131
  **The only link you give a person is the project page**, `https://app.getxflow.com/projects/<id>`,
101
132
  which the CLI prints for you. Refer to builds by their number ("version 481203 is built,
@@ -105,25 +136,25 @@ and after the next publish it does not break, it keeps answering with the old co
105
136
  holding that link then stares at a frozen app and concludes the changes never shipped. The
106
137
  project page always shows the current state, and every version is reachable from it.
107
138
 
108
- The platform builds the project itself, in a clean sandbox with one Node version for
109
- everyone, and serves the result as static files. Nothing is built on your machine for
110
- deployment, so a local `npm run build` is only a fast way to see errors early.
111
-
112
139
  ## Build gate
113
140
 
114
141
  Before the sandbox starts, the platform checks the sources against the template. Every
115
- rule below blocks the build, and the whole list of violations comes back at once, with
116
- files and line numbers. Nothing is charged for a rejected attempt: the sandbox never
117
- starts. Write code that already satisfies these rules instead of learning them from
118
- rejections.
142
+ rule below blocks the build, and violations come back together with files and line
143
+ numbers rather than one per attempt: up to fifty of them, and a count when there are
144
+ more. Nothing is charged for a rejected attempt, the sandbox never starts. Write code
145
+ that already satisfies these rules instead of learning them from rejections.
119
146
 
120
147
  Build setup:
121
148
 
122
149
  - `package.json` with the build script named in `xflow.json` (`npm run build` by default).
123
150
  - Vite: a `vite.config.*` and `vite` in dependencies.
124
151
  - No server frameworks: `next`, `nuxt`, `remix`, `@sveltejs/kit`, `astro`.
125
- - `index.html` in the root, `src/main.tsx` as the entry point.
152
+ - `index.html` in the root and an entry point at `src/main.tsx` (`.ts`, `.jsx` and `.js`
153
+ count too).
126
154
  - Application code under `src/`. Root `app/`, `pages/`, `next/` are rejected.
155
+ - Heavy media does not belong in the sources at all: the archive is capped at 10 MB and
156
+ the built application at 15 MB unpacked. Upload it with `xflow storage push` instead
157
+ and refer to the addresses it prints, see **File storage**.
127
158
 
128
159
  Platform contract, checked across all of `src/`:
129
160
 
@@ -174,10 +205,15 @@ A rejection names the file and the revision to take the original from:
174
205
 
175
206
  ## Cloud functions
176
207
 
177
- Server-side code lives in `functions/<name>/index.ts` and exports `handler`. There is no
178
- separate deploy command: `xflow deploy` ships the functions and then builds the application,
179
- in that order. List what is live with `xflow functions list`. The handler returns
180
- `{ statusCode, body }` where `body` is a JSON string.
208
+ Server-side code lives in `functions/<name>/index.ts` and exports `handler` (`index.js` and
209
+ `index.mjs` are read too). The directory name is the name of the function, and the build gate
210
+ is strict about it: lowercase latin letters, digits and hyphens, starting with a letter or a
211
+ digit, up to 41 characters. `send-mail` passes, `sendMail` and `send_mail` do not, and the
212
+ build stops before the sandbox rather than halfway through it.
213
+
214
+ There is no separate deploy command: `xflow deploy` ships the functions and then builds the
215
+ application, in that order. List what is live with `xflow functions list`. The handler
216
+ returns `{ statusCode, body }` where `body` is a JSON string.
181
217
 
182
218
  Keep one shape inside that string across the whole project: `{ success: true, data }`
183
219
  when it worked, `{ success: false, error: { message, code } }` when it did not. Nothing
@@ -252,24 +288,29 @@ An outside service (a webhook from a payment provider, a bot, a CRM) has no pers
252
288
  and needs a key of that one function. Keys are not issued by default and the CLI cannot
253
289
  create one: a human issues it in the project settings: «Облачные функции» → the function →
254
290
  «Настройки». Ask the user to do that and to paste the address back to you — never invent
255
- another way in. A function holds at most two keys, and the second one exists to replace the
256
- first without downtime, not to serve a second consumer.
291
+ another way in. Send it in the `X-Function-Key` header. A function holds at most two keys,
292
+ and the second one exists to replace the first without downtime, not to serve a second
293
+ consumer.
257
294
 
258
295
  `xflow functions list` shows who can reach each function: `in-app only` (no keys, answers
259
296
  only inside the application) or `external (N keys)` (a human issued external access). Key
260
297
  values are never shown there.
261
298
 
299
+ ## Secrets and connected accounts
300
+
262
301
  Keys and passwords live on the platform, not in the repository: `xflow env set SMTP_PASSWORD=…`
263
302
  writes one, `xflow env` lists the names, `xflow env check` tells you which variables your
264
303
  functions read but the platform does not have. Values never come back out — the only place
265
304
  they exist is inside the running function.
266
305
 
267
- A function receives only the variables it mentions by name via `process.env.NAME`, so never
268
- assemble a variable name from an expression, never reach the environment through `?.`, and
269
- never destructure or spread it (`const { API_KEY } = process.env` reads as no mention at all,
270
- and the variable arrives empty). The build stops on such a read rather than shipping a
271
- function whose secret silently never arrives. New values arrive on the next `xflow deploy`,
272
- not at the moment they are written.
306
+ These commands see only what this project can see: variables shared across the organization
307
+ and the ones bound to this project. A variable bound to another project is invisible here, so
308
+ `env rm` reports it as missing even though names are unique within the organization.
309
+
310
+ Why hard rules 1 and 2 are hard rules: a function receives only the variables it mentions by
311
+ name, and the platform reads those names out of the text of the code, so an opaque read ships
312
+ a function whose secret silently never arrives. The build stops on one instead. And a value
313
+ written today reaches the running function on the next `xflow deploy`, not before.
273
314
 
274
315
  Some variables come from a connected account instead of from you. When someone connects an
275
316
  advertising cabinet or another external service in the platform settings and links it to the
@@ -286,23 +327,22 @@ alias and the state of the access, and the ones the organization has but this pr
286
327
  not use yet, marked `available, not linked`. Check it before telling anyone a service is
287
328
  unavailable: the account you need is often connected already, one link away.
288
329
 
289
- `xflow connections link "Яндекс Метрика" --as YANDEX_METRIKA` is that link, and
290
- `xflow connections unlink YANDEX_METRIKA` undoes it. Name the connection the way the list
291
- does, in its first column, or by its identifier; unlink also takes the alias, which your
292
- own code already knows. If two accounts of the same service share a name, the command
293
- prints their identifiers instead of guessing. Unlink refuses while a function still reads
294
- one of the variables and names those functions, so read that list before reaching for
295
- `--force`.
330
+ `xflow connections link "Яндекс Метрика" --as YANDEX_METRIKA` is that link and
331
+ `xflow connections unlink YANDEX_METRIKA` undoes it; `xflow help connections` has the
332
+ naming rules and the flags. Unlink refuses while a function still reads one of the
333
+ variables and names those functions, so read that list before reaching for `--force`.
334
+
335
+ Linking needs the `connections:link` right on the key, and only accounts granted to the
336
+ owner of the key personally can be linked at all. If the right was taken away, say so and
337
+ ask the person to turn it back on in the platform settings under Developers: a key cannot
338
+ grant it to itself. Connecting a new account and switching one off stay with a person too.
296
339
 
297
- Linking needs the `connections:link` right, which keys are issued with. If it was taken
298
- away, say so and ask the person to turn it back on in the platform settings under
299
- Developers: a key cannot grant it to itself. Only accounts granted to the owner of the key
300
- personally can be linked at all. Connecting a new account and switching one off stay with a
301
- person too.
340
+ ## Schedules
302
341
 
303
- To run a function on a timer: `xflow schedules set report "0 3 ? * * *"` (daily at 03:00).
304
- Six fields, UTC, and exactly one of day-of-month / day-of-week must be `?` — that is
305
- how Yandex wants it. A scheduled run reaches the handler as a POST with no headers.
342
+ `xflow schedules set report "0 3 ? * * *"` runs a function daily at 03:00. Six fields, UTC,
343
+ and exactly one of day-of-month / day-of-week must be `?` — that is how Yandex wants it.
344
+ A scheduled run reaches the handler as a POST with no headers, and `--payload '{"mode":"full"}'`
345
+ is how it gets a body. How often a schedule may run is a plan limit, see **Plan limits**.
306
346
 
307
347
  The pieces line up in one pass. From a new function to a verified schedule:
308
348
 
@@ -310,7 +350,7 @@ The pieces line up in one pass. From a new function to a verified schedule:
310
350
  xflow env set SMTP_PASSWORD=... # secrets first: values ride the next deploy
311
351
  # write functions/report/index.ts, reading process.env.SMTP_PASSWORD literally
312
352
  xflow deploy # ships the function, then builds the app
313
- xflow schedules set report "0 3 ? * * *" # after the deploy: a schedule needs a deployed function
353
+ xflow schedules set report "0 3 ? * * *" --payload '{"mode":"full"}' # needs a deployed function
314
354
  xflow functions invoke report # run it once, the way the app would
315
355
  xflow functions logs report # empty output means it never crashed
316
356
  ```
@@ -336,41 +376,55 @@ You never write that variable yourself: `xflow env set DATABASE_URL=...` is refu
336
376
  `xflow env rm DATABASE_URL`. The same goes for every name starting with `XFLOW`: the platform
337
377
  fills those in itself, and your value under one of them would shadow the real one.
338
378
 
339
- `env` commands reach only what this project can see: variables shared across the organization
340
- and the ones bound to this project. A variable bound to a different project is invisible here,
341
- so `env rm` reports it as missing even though names are unique within the organization.
342
-
343
- The platform keeps no database history and no backups. Anything that destroys data
344
- (`DROP TABLE`, `DROP COLUMN`, `TRUNCATE`, `DELETE FROM` without a condition) is refused
345
- unless two things hold at once: you pass `--allow-destructive`, and the access key carries
346
- the right to destroy data. That right is off by default and only its owner can turn it on,
347
- in the platform settings, under Developers. So when a destructive migration is refused for
348
- the right rather than the flag, adding the flag changes nothing: say what needs deleting and
349
- why, and let the person decide. With both in place the affected tables are dumped first and
350
- kept for 7 days. Check with `--dry-run` before applying.
379
+ The platform keeps no database history and no backups, so anything that destroys data
380
+ (`DROP TABLE`, `DROP COLUMN`, `TRUNCATE`, `DELETE FROM` without a condition) needs two
381
+ things at once: the `--allow-destructive` flag, and the right to destroy data on the key.
382
+ That right is off by default and only its owner turns it on, under Developers in the
383
+ platform settings. When the refusal is about the right rather than the flag, adding the flag
384
+ changes nothing: say what needs deleting and why, and let the person decide. With both in
385
+ place the affected tables are dumped first and kept for 7 days. Check with `--dry-run` first.
351
386
 
352
387
  One logical database can be shared by several projects, so your migration can break an app
353
- you do not see. `xflow db status` lists applied migrations that have no file in your
354
- repository: that is what someone else's project did.
355
-
356
- For the same reason the `migrations/` directory is not the schema. It says what you did;
357
- `xflow db schema` says what is in the database right now, and `xflow db schema <table>` gives
358
- the columns of one table. `xflow db query "select ..."` reads data, inside a READ ONLY
359
- transaction, so a write there fails by design rather than by accident. Look before you write
360
- a migration against a shared database.
388
+ you do not see, and `xflow db status` lists applied migrations that have no file in your
389
+ repository: that is somebody else's project. For the same reason `migrations/` is not the
390
+ schema. It says what you did; `xflow db schema [table]` says what is in the database right
391
+ now, and `xflow db query "select ..."` reads it inside a READ ONLY transaction. Look before
392
+ you write a migration against a shared database.
361
393
 
362
394
  ## File storage
363
395
 
364
- Sources go up as one archive, capped at 10 MB. That cap is about heavy media, not about
365
- code: an app with hundreds of files is nowhere near it. Icons, fonts and small artwork the
366
- build needs belong in the repository as usual. Photos, video, PDFs and anything a user
367
- uploads belong in file storage, which is metered against the organization plan and is not
368
- rebuilt and re-uploaded on every deploy.
396
+ Two kinds of files live here, and they arrive through different doors.
397
+
398
+ **Static assets of the application you are building** — product photos, video, PDFs, big
399
+ artwork you upload yourself, from the terminal:
400
+
401
+ ```bash
402
+ xflow storage push ./media --to media # a folder, its structure kept
403
+ xflow storage ls # what is there, with addresses and sizes
404
+ xflow storage ls --json # the same list, machine-readable
405
+ ```
406
+
407
+ The caps that send them here (10 MB of sources, 15 MB unpacked) are about heavy media, not
408
+ about code: icons, fonts and small artwork the build needs stay in the repository as usual.
409
+ Everything heavy goes to storage, where it is metered against the organization plan and is
410
+ not re-uploaded on every deploy.
369
411
 
370
- The project has file storage, and the browser cannot reach it. Those endpoints take only the
371
- server key of the project, and the platform puts it into the environment of your cloud
372
- functions as `XFLOW_SERVER_KEY`. Nothing else holds it: not the bundle, not `.env`, not
373
- `xflow env`.
412
+ `push` prints the address of every file and skips what is already stored under the same path
413
+ and size, so a repeat run after a broken connection is cheap; `--replace` overwrites a file
414
+ whose size differs. An address belongs to the record rather than to the bytes, so a
415
+ replacement keeps it and the links in your code and tables keep working. Put those addresses
416
+ into the code or into a table: there is no command that fetches files back to a machine.
417
+
418
+ Deleting needs a right of its own, off by default (**Delete files** in the Developers
419
+ section of the platform), so `xflow storage rm` may answer that the key was not granted it.
420
+ Ask the person to switch it on, and do not look for a way around: whatever the users of the
421
+ application uploaded lives in the same folders, and there is no undo. A folder first tells
422
+ you how many files it holds and deletes them only with `--yes`.
423
+
424
+ **Files that users of the application upload** go through a cloud function of your own. The
425
+ browser cannot reach storage directly: those endpoints take only the server key of the
426
+ project, and the platform puts it into the environment of your cloud functions as
427
+ `XFLOW_SERVER_KEY`. Nothing else holds it: not the bundle, not `.env`, not `xflow env`.
374
428
 
375
429
  So uploading is a function of your own. It asks the platform for a one-time link, the browser
376
430
  then sends the bytes straight to storage, and a second call records the file:
@@ -386,35 +440,30 @@ const link = await fetch(`${process.env.XFLOW_API_URL}/api/storage/project/uploa
386
440
  }).then((r) => r.json())
387
441
  ```
388
442
 
389
- `confirm` takes the same fields plus the returned `s3Key` and answers with the file and its
390
- address; `delete` takes that same `url`. Never pipe the bytes through the function itself.
443
+ `confirm` takes the returned `s3Key` and answers with the file and its address; `delete`
444
+ takes that same `url`. Never pipe the bytes through the function itself.
391
445
 
392
- There is no endpoint that lists the files back, so the address that `confirm` returns is the
393
- only copy you get: write it into a table of your own in the same call, and the application
394
- reads its files from there.
446
+ There is no endpoint that lists files back to a function, so the address that `confirm`
447
+ returns is the only copy this door gives you: write it into a table of your own in the same
448
+ call, and the application reads its files from there.
395
449
 
396
450
  That address never expires and is safe to store, but it is not a public link. It opens only
397
451
  for a visitor who is signed in and has access to this project, the same rule that guards the
398
452
  application itself, so it works on your pages and does nothing in an email or on a page
399
453
  anyone can open.
400
454
 
401
- Four things bite an upload that otherwise looks right, and none of them is obvious from the
402
- answers you get:
403
-
404
- - **The content type can quietly split in two.** Nothing checks that the `Content-Type` the
405
- browser sends on the PUT matches the one you named in `upload-url`: both calls answer 200.
406
- But storage keeps the header the browser sent and serves the file under it, while the
407
- record keeps the one you named, so a card can say `image/png` about a file every browser
408
- treats as HTML. Send the same string in both calls and they cannot drift.
409
- - **The same name in the same folder is refused.** Names are unique per folder, so a second
410
- `avatar.png` fails instead of replacing the first. Give the name a suffix of your own, or
411
- delete the old file before confirming the new one.
455
+ Three things bite an upload that otherwise looks right:
456
+
457
+ - **A name already taken in that folder is refused before the link is issued.** Pass
458
+ `overwrite: true` to replace the file: the bytes change and the address stays, which is
459
+ what the pages of the application expect. Without it, give the name a suffix of your own.
412
460
  - **The limits are 200 MB per file and the storage quota of the organization.** The quota is
413
461
  checked again on `confirm`, by the real size, which means a refusal can land after the
414
462
  bytes are already up; the platform then removes the object and your table stays clean.
415
463
  - **Confirm only after the PUT has finished.** The platform looks the object up in storage
416
- and takes its real size from there, not from what you declared, so an early `confirm`
417
- answers that the file is not there.
464
+ and takes its real size and content type from there, not from what you declared, so an
465
+ early `confirm` answers that the file is not there. Send on the PUT the `Content-Type` you
466
+ named when asking for the link: storage serves the file under the header it received.
418
467
 
419
468
  A refusal comes back as `{ error, code }`. Branch on `code` (`invalid_name`, `file_too_large`,
420
469
  `quota_exceeded`, `not_uploaded`, `duplicate_name`, `not_found`, …) and never on the text:
@@ -425,33 +474,20 @@ browser can be edited by whoever opened it. Never write the key into the sources
425
474
  send it to the frontend: the build gate stops on a key found in the application code, and a
426
475
  key that reached a visitor lets them delete every file of the project.
427
476
 
428
- ## Syncing code
429
-
430
- `xflow status` shows how the local copy differs from the server revision.
431
- `xflow deploy` sends sources, `xflow pull` fetches them.
477
+ ## Organizations and keys
432
478
 
433
- If a deploy is rejected before the build starts, the server revision is newer, meaning
434
- someone deployed first. Fetch their work next to yours (`xflow pull --into ./server-copy`),
435
- merge it locally, then deploy again. `--force` destroys their work: a last resort, not a
436
- way around the error.
479
+ One key per organization, stored side by side rather than replacing each other:
480
+ `xflow org` lists them, `xflow org switch <name|id>` changes the active one, and
481
+ `xflow help org` has the precedence rules. Two things are worth knowing without asking.
437
482
 
438
- ## Organizations and keys
483
+ Inside a project folder there is nothing to switch: commands follow the organization the
484
+ folder is bound to, whatever the active one is. That is what lets two projects of two
485
+ organizations work side by side.
439
486
 
440
- A key belongs to one organization, and `xflow login` stores it next to the ones already
441
- stored instead of replacing them. Which key a command uses, in order: `XFLOW_TOKEN` when
442
- set (default platform address only), then the organization the project folder is bound
443
- to (`.xflow/state.json`, written by init, link and the first successful deploy or pull),
444
- then the active organization. `xflow org` lists the stored organizations with the active
445
- one marked, `xflow org switch <name|id>` makes another one active without a browser, and
446
- `xflow whoami` names the organization behind the current key.
447
-
448
- Inside a project folder there is nothing to switch: commands follow the folder's own
449
- organization whatever the active one is, which is what lets two projects of two
450
- organizations work side by side. Project ids are unique across the platform, so a key of
451
- the wrong organization can never touch another organization's project: the command fails
452
- with "not found" instead. When that error names a project you know exists, check
453
- `xflow org`; signing in to a missing organization is `xflow login`, and that needs a
454
- person with a browser.
487
+ A "not found" on a project you know exists usually means the key belongs to another
488
+ organization, not that the project is gone: ids are unique across the platform, so a
489
+ wrong key can never reach somebody else's project. Check `xflow org`. Signing in to a
490
+ missing organization is `xflow login`, and that needs a person with a browser.
455
491
 
456
492
  ## Direct access without the terminal
457
493