@getxflow/cli 0.19.0 → 0.20.0

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/help.js CHANGED
@@ -593,9 +593,10 @@ old copy.`,
593
593
  init: `${(0, ui_1.bold)('xflow init')} [dir]: a new project
594
594
 
595
595
  Lays down the platform template: React on Vite, Tailwind, a set of components
596
- (${(0, ui_1.bold)('src/components/ui')}) and ready-made blocks (${(0, ui_1.bold)('src/components/blocks')}): table,
597
- form, filters, kanban, charts. Projects created in the web get the same template, which
598
- is what makes the applications look alike.
596
+ (${(0, ui_1.bold)('src/components/ui')}) and ready-made blocks (${(0, ui_1.bold)('src/components/blocks')}): app
597
+ shell, page header, section, toolbar, list, empty state, stats, detail card, form rows,
598
+ card grid, timeline, table, charts. Projects created in the web get the same template,
599
+ which is what makes the applications look alike.
599
600
 
600
601
  Building the interface on top of these components and the tokens in ${(0, ui_1.bold)('src/index.css')}
601
602
  is not a formality: your own palette on top of them looks foreign inside the platform.
package/dist/session.js CHANGED
@@ -31,7 +31,11 @@ function connect(config, options) {
31
31
  ...(0, credentials_1.storedKeyInputs)(apiUrl),
32
32
  });
33
33
  if (!picked) {
34
- throw new errors_1.CliError(`No access key for ${apiUrl}`, 'Sign in: xflow login. In CI pass the key in the XFLOW_TOKEN variable');
34
+ throw new errors_1.CliError(`No access key for ${apiUrl}`,
35
+ // Who does it, not where the key comes from: the key is issued by the sign-in
36
+ // itself and by nothing else, so an agent reading this has to hand the step over
37
+ // instead of hunting for a value to paste.
38
+ 'Sign in: xflow login (needs a person with a browser). In CI pass the key in XFLOW_TOKEN');
35
39
  }
36
40
  if ('missing' in picked) {
37
41
  throw new errors_1.CliError(`This folder is bound to the organization ${picked.organizationId}, and there is no stored key for it`, 'Sign in to that organization: xflow login. The stored ones: xflow org');
package/dist/version.js CHANGED
@@ -2,6 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DEFAULT_API_URL = exports.CLI_VERSION = void 0;
4
4
  /** Keep in sync with cli/package.json. */
5
- exports.CLI_VERSION = '0.19.0';
5
+ exports.CLI_VERSION = '0.20.0';
6
6
  /** Overridden by XFLOW_API_URL or the `api` field in xflow.json. */
7
7
  exports.DEFAULT_API_URL = 'https://app.getxflow.com';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getxflow/cli",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "description": "CLI for the XFlow platform: source sync, deployment and publishing of applications",
5
5
  "license": "UNLICENSED",
6
6
  "engines": {
@@ -5,15 +5,13 @@ description: Build, deploy and publish apps on the XFlow platform with the xflow
5
5
 
6
6
  # XFlow
7
7
 
8
- Hosting for web apps. The code lives in an ordinary repository on the developer
9
- machine, `xflow deploy` sends the sources, and the platform builds them in a clean
10
- sandbox and serves the result as static files. A platform project is recognized by
11
- the `xflow.json` file in its root.
8
+ Hosting for web apps. The code lives in an ordinary repository on the developer machine,
9
+ `xflow deploy` sends the sources, and the platform builds them in a clean sandbox and serves
10
+ the result as static files. A project is recognized by `xflow.json` in its root.
12
11
 
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.
12
+ No `xflow.json` means the folder is not bound to a project yet: `xflow projects list` names the
13
+ projects of the organization with their ids, `xflow link <id>` binds the folder to one (an empty
14
+ folder also gets the sources), and `xflow init` starts a new project from the template.
17
15
 
18
16
  ## First rule
19
17
 
@@ -24,21 +22,17 @@ contains the fix.
24
22
 
25
23
  ## Keeping these instructions current
26
24
 
27
- These instructions ship with xflow CLI 0.19.0. 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.
25
+ These instructions ship with xflow CLI 0.20.0, so the copy you are reading can be older than
26
+ the CLI answering your commands.
30
27
 
31
28
  When the CLI says this file does not match it, 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.
29
+ again the file it names**: refreshing only writes to disk, and until you re-read it you are
30
+ still working from the old rules. Do it yourself, no need to ask.
35
31
 
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.
32
+ Updating the CLI is not yours. `xflow update` replaces a program shared by every project on
33
+ the machine, and most installations only print the command for a person: offer the update in
34
+ your summary. The exception is a platform refusing the CLI as too old: nothing runs until it
35
+ is updated, so say what you are doing and run it.
42
36
 
43
37
  ## Hard rules
44
38
 
@@ -56,8 +50,9 @@ the error points away from the cause. The sections below carry the details.
56
50
  schedules, and a function created again later gets a different address.
57
51
  4. An already applied migration is never re-run, so editing its file changes
58
52
  nothing. A schema change is always a new file.
59
- 5. When inserts start failing with `db_write_locked`, the database is over its plan
60
- size. The fix is a migration that deletes data, never a rewrite of the failing SQL.
53
+ 5. When inserts start failing with `db_write_locked`, the database is over its plan size
54
+ and Postgres itself refuses. Reads and deletes keep working, so the fix is a migration
55
+ that deletes data, never a rewrite of the SQL. Write comes back within the hour.
61
56
  6. In file storage, call `confirm` only after the PUT has finished: the platform reads
62
57
  the real size from storage, so an early call answers that the file is not there.
63
58
  7. Heavy media does not travel with the sources. Photos, video and PDFs go up with
@@ -65,41 +60,31 @@ the error points away from the cause. The sections below carry the details.
65
60
 
66
61
  ## Plan limits
67
62
 
68
- The organization runs on a plan with finite limits: projects, databases, cloud functions,
69
- active connector connections, developer and staff seats, database and file storage,
70
- function minutes and builds per month, builds running at once, plus schedules and how
71
- often they may run. `xflow whoami` prints every one of them next to what is already
72
- used, and reading it before a long task is cheaper than hitting a wall mid-way.
73
-
74
- Two of them deserve a word. A database outlives its project (deleting a project only
75
- detaches it), so a refused database on a small plan usually means an orphan: the fix is
76
- deleting the unused database in the "Data" section of the web interface, not renaming
77
- anything. Connector connections are counted while active: switching an unused connection
78
- off in the web interface frees the seat without deleting it.
79
-
80
- The plan also caps how long one call of a cloud function may run. The cap is written
81
- into the function when it is deployed, so a plan change reaches the cloud only with the
82
- next `xflow deploy` of each project, in both directions. `xflow whoami` names the plan's
83
- cap; what a deployed function actually carries is what it was last deployed with.
84
-
85
- The same output names the rights of your own key, which is the other half of the answer:
86
- destroying data in a migration, deleting files from storage and linking a connected
87
- account each need a right that is off by default. Reading that line first turns a refusal
88
- halfway through the work into a sentence said before it starts.
89
-
90
- A limit refusal is not a bad request. The CLI prints a line starting with `Plan limit:`,
91
- the API answers `code: "forbidden"` with a `limit` object (`code`, `used`, `limit`), and
92
- MCP tools carry the same field. Retrying the command, renaming things or rewriting the
93
- code changes nothing: tell the user what ran out and stop. Only the owner or an admin
94
- lifts it, in the web interface, by freeing the resource or moving to a bigger plan.
95
-
96
- A schedule refused for running too often is one of these too, not a bad cron string: the
97
- minimum interval comes from the plan, so widen the interval or say what the plan allows.
98
- The number is in `xflow whoami`, never guess it.
99
-
100
- On `db_write_locked` (hard rule 5) it is Postgres itself refusing, not the platform. Reads
101
- and deletes keep working, which is what makes a deleting migration the way out. Write comes
102
- back within an hour of the data going under the limit.
63
+ The organization runs on a plan with finite limits: projects, databases, functions, active
64
+ connections, seats, database and file storage, function minutes, builds per month and at
65
+ once, schedules and how often they may run. `xflow whoami` prints every one of them next to
66
+ what is used, and the rights of your own key with them: destroying data in a migration,
67
+ deleting files from storage and linking a connected account each need a right that is off by
68
+ default. Reading it before a long task turns a refusal halfway through the work into a
69
+ sentence said before it starts.
70
+
71
+ Three of them behave unlike the rest:
72
+
73
+ - A database outlives its project (deleting a project only detaches it), so a refused
74
+ database usually means an orphan: delete the unused one in the "Data" section of the web
75
+ interface.
76
+ - Connections are counted while active: switching an unused one off frees the seat without
77
+ deleting it.
78
+ - The cap on how long one function call may run is written into the function when it is
79
+ deployed, so a plan change reaches the cloud only with the next `xflow deploy` of each
80
+ project, in both directions.
81
+
82
+ A limit refusal is not a bad request. The CLI prints a line starting with `Plan limit:`, the
83
+ API answers `code: "forbidden"` with a `limit` object (`code`, `used`, `limit`), MCP tools
84
+ carry the same field. Retrying, renaming or rewriting the code changes nothing: tell the user
85
+ what ran out and stop, only the owner or an admin lifts it. A schedule refused for running too
86
+ often is one of these too, not a bad cron string: the minimum interval comes from the plan,
87
+ never guess it.
103
88
 
104
89
  ## Workflow
105
90
 
@@ -116,16 +101,13 @@ back within an hour of the data going under the limit.
116
101
  browser for them.
117
102
  6. `xflow publish` makes that same version visible to visitors.
118
103
 
119
- The split is deliberate: shipping a build and showing it are two separate decisions.
120
- Until `publish` runs, visitors keep seeing the previous pages. The one exception is
121
- the very first version of a project: it publishes automatically, since there is no
122
- live version to protect yet.
104
+ The split is deliberate: until `publish` runs, visitors keep seeing the previous pages. The
105
+ one exception is the very first version of a project, which publishes automatically.
123
106
 
124
- `xflow status` says how the local copy differs from the server revision. When a deploy is
125
- rejected before the build starts, the server holds a version this folder has never seen:
126
- somebody deployed first, or the folder was linked to a different project along the way.
127
- Fetch the server work next to yours (`xflow pull --into ./server-copy`), merge it in git,
128
- deploy again. `--force` destroys that work, a last resort rather than a way around the error.
107
+ `xflow status` says how the local copy differs from the server revision. A deploy rejected
108
+ before the build starts means the server holds a version this folder has never seen: somebody
109
+ deployed first, or the folder was linked elsewhere. Fetch it next to yours (`xflow pull --into
110
+ ./server-copy`), merge in git, deploy again. `--force` destroys that work.
129
111
 
130
112
  **Only pages are versioned.** Cloud functions and the database are one per project: they
131
113
  are not versioned, and dev and live share them. So `xflow deploy` changes the running
@@ -133,35 +115,29 @@ application the moment it finishes, before any `publish`, and `publish` moves th
133
115
  only. Tell the user this when a deploy touches `functions/`: there is no staging step for
134
116
  server code.
135
117
 
136
- Rolling back: `xflow deployments` lists the history, `xflow rollback <id>` points the project
137
- at the pages of an earlier build. Only the pages come back, and they then talk to today's
138
- functions. The code of that build is a separate fetch (`xflow pull --revision N --into
139
- ../old-version`, kept outside the project folder); building it over the current one needs
140
- `--force` and a human typing the project name, so you cannot do it on your own.
118
+ Rolling back: `xflow deployments` lists the history, `xflow rollback <id>` points the project at
119
+ the pages of an earlier build. Only the pages come back and they talk to today's functions. The
120
+ code of that build is a separate fetch (`xflow pull --revision N --into ../old-version`, kept
121
+ outside the project folder); building it over the current one needs a human.
141
122
 
142
- Pages are kept for the **three latest successful builds** plus whatever dev and live show.
143
- Older versions stay in the history with `pages_kept` false: you can read what they were and
144
- which revision they came from, but you cannot open or roll back to them. The history itself
145
- holds the **last 30 builds** and the sources the **last 50 revisions** (a revision a live
146
- version was built from is never deleted). Three builds is the whole rollback depth and on an
147
- active project it is a matter of hours, so it is not a backup: keep anything you must not
148
- lose in your own git repository.
123
+ Pages are kept for the **three latest successful builds** plus whatever dev and live show;
124
+ older versions stay in the history with `pages_kept` false, readable but not openable. The
125
+ history holds the **last 30 builds**, the sources the **last 50 revisions** (a revision a live
126
+ version was built from is never deleted). Three builds is the whole rollback depth, hours on an
127
+ active project: keep anything you must not lose in your own git repository.
149
128
 
150
129
  **The only link you give a person is the project page**, `https://app.getxflow.com/projects/<id>`,
151
- which the CLI prints for you. Refer to builds by their number ("version 481203 is built,
152
- 092399 is what visitors see"), never by address. The platform does not hand out build
153
- addresses and neither should you: a build address has the version number baked into it,
154
- and after the next publish it does not break, it keeps answering with the old copy. Anyone
155
- holding that link then stares at a frozen app and concludes the changes never shipped. The
156
- project page always shows the current state, and every version is reachable from it.
130
+ which the CLI prints for you. Refer to builds by their number ("version 481203 is built, 092399
131
+ is what visitors see"), never by address: a build address has the version baked into it and
132
+ after the next publish keeps answering with the old copy, so whoever holds it stares at a
133
+ frozen app and concludes nothing shipped. The project page always shows the current state.
157
134
 
158
135
  ## Build gate
159
136
 
160
- Before the sandbox starts, the platform checks the sources against the template. Every
161
- rule below blocks the build, and violations come back together with files and line
162
- numbers rather than one per attempt: up to fifty of them, and a count when there are
163
- more. Nothing is charged for a rejected attempt, the sandbox never starts. Write code
164
- that already satisfies these rules instead of learning them from rejections.
137
+ Before the sandbox starts, the platform checks the sources against the template. Every rule
138
+ below blocks the build, and violations come back together with files and line numbers, up to
139
+ fifty at a time. A rejected attempt costs nothing, the sandbox never starts. Write code that
140
+ satisfies these rules instead of learning them from rejections.
165
141
 
166
142
  Build setup:
167
143
 
@@ -198,9 +174,9 @@ Interface rules, checked outside `src/components/ui` and `src/components/blocks`
198
174
  `react-hot-toast`, `react-dnd`) must have it mounted in `App.tsx`. Missing providers
199
175
  build fine and give visitors a white screen.
200
176
 
201
- Template integrity. The app grows out of the platform template, and part of that template
202
- is not yours to change. The reference is a snapshot of the project itself, taken when the
203
- platform first looked at it, so these rules never argue with work that was already there:
177
+ Template integrity. Part of the platform template is not yours to change. The reference is a
178
+ snapshot of the project itself, taken when the platform first looked at it, so these rules
179
+ never argue with work that was already there:
204
180
 
205
181
  - Platform files must stay byte for byte as they arrived: `src/lib/theme-sync.ts`,
206
182
  `src/lib/platform-auth.ts`, `src/contexts/platform-auth-context.tsx`,
@@ -225,14 +201,13 @@ A rejection names the file and the revision to take the original from:
225
201
  ## Cloud functions
226
202
 
227
203
  Server-side code lives in `functions/<name>/index.ts` and exports `handler` (`index.js` and
228
- `index.mjs` are read too). The directory name is the name of the function, and the build gate
229
- is strict about it: lowercase latin letters, digits and hyphens, starting with a letter or a
230
- digit, up to 41 characters. `send-mail` passes, `sendMail` and `send_mail` do not, and the
231
- build stops before the sandbox rather than halfway through it.
204
+ `index.mjs` are read too). The directory name is the name of the function, and the build gate is
205
+ strict about it: lowercase latin letters, digits and hyphens, starting with a letter or a digit,
206
+ up to 41 characters. `send-mail` passes, `sendMail` and `send_mail` do not.
232
207
 
233
208
  There is no separate deploy command: `xflow deploy` ships the functions and then builds the
234
- application, in that order. List what is live with `xflow functions list`. The handler
235
- returns `{ statusCode, body }` where `body` is a JSON string.
209
+ application, in that order. `xflow functions list` shows what is live. The handler returns
210
+ `{ statusCode, body }` where `body` is a JSON string.
236
211
 
237
212
  Keep one shape inside that string across the whole project: `{ success: true, data }`
238
213
  when it worked, `{ success: false, error: { message, code } }` when it did not. Nothing
@@ -240,44 +215,32 @@ enforces this, but a project where every function answers its own way costs an a
240
215
  on every call. Branch the frontend on `error.code`, never on `error.message`: wording
241
216
  gets rewritten on any edit, a code does not.
242
217
 
243
- How a non-2xx answer reaches the page depends on the generation of `src/lib/xflow.ts`
244
- in the project, so look at that file before writing the catch: a copy that declares
245
- `XFlowError` throws it carrying `code` and `status`, and a copy that says nothing about
246
- errors is older and throws a plain `Error` whose message is all there is. In an older
247
- project, a function that wants its error text seen on the page answers `200` with
248
- `{ success: false, error }` in the body.
249
-
250
- The sources are the whole truth about which functions exist. Delete the directory and the
251
- next deploy would delete the function from the cloud, schedules included, and that cannot be
252
- undone: a function created again later gets a different address. So never remove a function
253
- directory to "clean up" unless the user asked for the function to go.
254
-
255
- Such a deploy does not start on its own: the platform names the functions it would remove and
256
- refuses until somebody agrees. Under an agent there is no terminal to ask in, so the refusal
257
- reaches you, and `--allow-removals` is the only way past it. Adding that flag to get the
258
- build running is exactly the wrong move: it means you deleted something the user did not ask
259
- you to delete. Put the directories back instead, and if the removal really is intended, say
260
- which functions are about to go and let the user answer.
261
-
262
- Debugging a deployed function is two commands: `xflow functions invoke <name>` calls it
263
- the way the app does and prints status, timing and body (`--data '{"a":1}'` sends a body,
264
- `--data-file body.json` sends the same from a file, `--json` prints the body alone), and
265
- `xflow functions logs <name>` shows the failures, each with its stack and the console
266
- output of that call. Only failed calls are
267
- logged, so an empty output means the function never crashed, not that logging is broken.
268
-
269
- The body has to be JSON and the CLI checks it before calling. In PowerShell the double
270
- quotes never reach the CLI: `--data '{"a":1}'` arrives as `{a:1}`, so escape them
271
- (`--data '{\"a\":1}'`) or pass a file. This is a shell habit, not a bug in the function, and
272
- it bites `--payload` of a schedule the same way. A command that worked writes nothing to
273
- stderr, so a red line in PowerShell means it really failed; `--json` prints the payload and
274
- nothing else.
218
+ How a non-2xx answer reaches the page depends on the generation of `src/lib/xflow.ts`, so read
219
+ that file before writing the catch: a copy declaring `XFlowError` throws it with `code` and
220
+ `status`, an older copy throws a plain `Error` whose message is all there is. In an older
221
+ project, a function whose error text must be seen answers `200` with `{ success: false, error }`.
222
+
223
+ The sources are the whole truth about which functions exist: a deleted directory takes the
224
+ function and its schedules out of the cloud, undoably, and a function created again later gets
225
+ a different address. Such a deploy refuses until somebody agrees, and `--allow-removals` is the
226
+ only way past it. Reaching for that flag to get a build running means you deleted something the
227
+ user never asked you to delete: put the directories back. If the removal is intended, name the
228
+ functions about to go and let the user answer.
229
+
230
+ Debugging a deployed function is two commands: `xflow functions invoke <name>` calls it the way
231
+ the app does and prints status, timing and body (`--data '{"a":1}'`, `--data-file body.json`,
232
+ `--json` for the body alone), and `xflow functions logs <name>` shows the failures with stacks
233
+ and console output. Only failed calls are logged, so empty output means it never crashed.
234
+
235
+ The body has to be JSON and the CLI checks it before calling. In PowerShell the double quotes
236
+ never reach the CLI (`--data '{"a":1}'` arrives as `{a:1}`): escape them (`--data '{\"a\":1}'`)
237
+ or pass a file. It bites `--payload` of a schedule the same way. A command that worked writes
238
+ nothing to stderr, so a red line there means it really failed.
275
239
 
276
240
  From the app, call a function through `src/lib/xflow.ts`:
277
- `await xflow.functions.invoke('send-mail', { body: { to } })`. It carries the credentials
278
- for you. Addresses are baked into the build, which is why the functions go out first:
279
- by the time the bundle is built they already exist, and a new function is never missing
280
- from the application that calls it.
241
+ `await xflow.functions.invoke('send-mail', { body: { to } })`, which carries the credentials for
242
+ you. Addresses are baked into the build, which is why functions go out first: by the time the
243
+ bundle is built they already exist.
281
244
 
282
245
  ### Who is calling
283
246
 
@@ -318,13 +281,12 @@ and `X-Server-Key` with `process.env.XFLOW_SERVER_KEY`. The token is the ticket
318
281
  project and the key is the identity; the wrapper checks the ticket first, so the key alone
319
282
  answers 401.
320
283
 
321
- An outside service (a webhook from a payment provider, a bot, a CRM) has no person behind it
322
- and needs a key of that one function. Keys are not issued by default and the CLI cannot
323
- create one: a human issues it in the project settings: «Облачные функции» → the function →
324
- «Настройки». Ask the user to do that and to paste the address back to you — never invent
325
- another way in. Send it in the `X-Function-Key` header. A function holds at most two keys,
326
- and the second one exists to replace the first without downtime, not to serve a second
327
- consumer.
284
+ An outside service (a payment webhook, a bot, a CRM) has no person behind it and needs a key of
285
+ that one function. Keys are not issued by default and the CLI cannot create one: a human issues
286
+ it in the project settings, «Облачные функции» → the function → «Настройки». Ask the user to do
287
+ that and to paste the address back — never invent another way in. Send it in the
288
+ `X-Function-Key` header. A function holds at most two keys, the second one to replace the first
289
+ without downtime, not to serve a second consumer.
328
290
 
329
291
  `xflow functions list` shows who can reach each function: `in-app only` (no keys, answers
330
292
  only inside the application) or `external (N keys)` (a human issued external access). Key
@@ -333,33 +295,29 @@ values are never shown there.
333
295
  ## Secrets and connected accounts
334
296
 
335
297
  Keys and passwords live on the platform, not in the repository: `xflow env set SMTP_PASSWORD=…`
336
- writes one, `xflow env` lists the names, `xflow env check` tells you which variables your
337
- functions read but the platform does not have. Values never come back out the only place
338
- they exist is inside the running function.
298
+ writes one, `xflow env` lists the names, `xflow env check` names the variables your functions
299
+ read but the platform does not have. Values never come back out: the only place they exist is
300
+ inside the running function.
339
301
 
340
302
  A variable goes to the whole organization unless you say otherwise: `--scope project` keeps it
341
303
  to this project alone, `--scope organization` is the default, and a third word is refused
342
304
  rather than read as the default. Pick the narrow one for anything that belongs to one
343
305
  application.
344
306
 
345
- These commands see only what this project can see: variables shared across the organization
346
- and the ones bound to this project. A variable bound to another project is invisible here, so
347
- `env rm` reports it as missing even though names are unique within the organization.
348
-
349
- Why hard rules 1 and 2 are hard rules: a function receives only the variables it mentions by
350
- name, and the platform reads those names out of the text of the code, so an opaque read ships
351
- a function whose secret silently never arrives. The build stops on one instead. And a value
352
- written today reaches the running function on the next `xflow deploy`, not before.
353
-
354
- Some variables come from a connected account instead of from you. When someone connects an
355
- advertising cabinet or another external service in the platform settings and links it to the
356
- project, its credentials show up in `xflow env` marked read-only, under a prefix chosen at
357
- link time: `YANDEX_DIRECT_TOKEN`, `YANDEX_DIRECT_CLIENT_LOGIN`. Read them like any other
358
- variable. Do not try to `xflow env set` those names: the platform keeps the values in sync
359
- and refuses. If a call to that service starts failing with an authorisation error, run
360
- `xflow status`: it says whether the token is merely expiring (any build renews it) or the
361
- account was disconnected on the provider's side, which only a human can fix by reconnecting
362
- it in the platform settings.
307
+ These commands see only what this project can see: organization-wide variables and the ones
308
+ bound to this project. One bound to another project is invisible here, so `env rm` reports it
309
+ missing even though names are unique within the organization.
310
+
311
+ Hence hard rules 1 and 2: a function receives only the variables it mentions by name, and the
312
+ platform reads those names out of the text of the code, so an opaque read would ship a function
313
+ whose secret never arrives. A value written today rides the next `xflow deploy`.
314
+
315
+ Some variables come from a connected account instead of from you. Credentials of an account
316
+ linked to the project show up in `xflow env` marked read-only, under a prefix chosen at link
317
+ time (`YANDEX_DIRECT_TOKEN`, `YANDEX_DIRECT_CLIENT_LOGIN`): read them like any other variable,
318
+ `xflow env set` on those names is refused. If calls to that service start failing on
319
+ authorisation, `xflow status` says whether the token is merely expiring (any build renews it)
320
+ or the account was disconnected on the provider's side, which only a human can reconnect.
363
321
 
364
322
  `xflow connections` lists those accounts: the ones already linked to this project, with the
365
323
  alias and the state of the access, and the ones the organization has but this project does
@@ -373,11 +331,10 @@ while a function still reads one of the variables, naming those functions, so re
373
331
  list before reaching for `--force`. Under `--project` there is no folder to read: it
374
332
  prints the variables that go and removes the link.
375
333
 
376
- Linking needs the `connections:link` right on the key, and only accounts granted to the
377
- owner of the key personally can be linked at all; the same right covers calling an unlinked
378
- account. If the right was taken away, say so and
379
- ask the person to turn it back on where the key permissions live in the platform settings: a key cannot
380
- grant it to itself. Connecting a new account and switching one off stay with a person too.
334
+ Linking needs the `connections:link` right on the key, and only accounts granted to the owner
335
+ of the key personally can be linked at all; the same right covers calling an unlinked account.
336
+ A key cannot grant it to itself, so ask the person to turn it back on where the key permissions
337
+ live. Connecting a new account and switching one off stay with a person too.
381
338
 
382
339
  ### Asking an API what it returns
383
340
 
@@ -406,10 +363,9 @@ linked or not: an invented name is left in the text as you typed it and the requ
406
363
  anyway, into somebody's live account. `xflow env` is not that list. Only the hosts of that
407
364
  connector are reachable and a refusal lists them.
408
365
 
409
- **Ask for the shape, not the volume.** One day and `limit=1` is enough to learn what the
410
- fields are called; the answer is printed head first and says when there is more. The whole
411
- dataset belongs in a cloud function, which is where you were going anyway. `--json | jq
412
- '.response.body | keys'` gives the field names without the body entering your context at all.
366
+ **Ask for the shape, not the volume.** One day and `limit=1` is enough to learn what the fields
367
+ are called, and the whole dataset belongs in a cloud function anyway. `--json | jq
368
+ '.response.body | keys'` gives the field names without the body entering your context.
413
369
 
414
370
  The call goes out for real, against a live account: `POST` and `DELETE` change things there,
415
371
  and nothing undoes that. Read the method of the endpoint before calling it, and ask the
@@ -457,23 +413,19 @@ You never write that variable yourself: `xflow env set DATABASE_URL=...` is refu
457
413
  fills those in itself, and your value under one of them would shadow the real one.
458
414
 
459
415
  The platform keeps no database history and no backups, so anything that destroys data
460
- (`DROP TABLE`, `DROP COLUMN`, `TRUNCATE`, `DELETE FROM` without a condition) needs two
461
- things at once: the `--allow-destructive` flag, and the right to destroy data on the key.
462
- That right is off by default and only its owner turns it on, where the key permissions live in
463
- the platform settings. When the refusal is about the right rather than the flag, adding the flag
464
- changes nothing: say what needs deleting and why, and let the person decide. With both in
465
- place the affected tables are dumped first and kept for 7 days. Check with `--dry-run` first.
466
-
467
- One logical database can be shared by several projects, so your migration can break an app
468
- you do not see, and `xflow db status` lists applied migrations that have no file in your
469
- repository: that is somebody else's project. `xflow db list` names the databases of the
470
- organization and the projects on each, which is where you find out who else is on yours.
471
- For the same reason `migrations/` is not the
472
- schema. It says what you did; `xflow db schema [table]` says what is in the database right
473
- now, and `xflow db query "select ..."` reads it inside a READ ONLY transaction (50 rows by
474
- default, `--limit` raises that to 300). The table cuts long values to keep the columns
475
- readable; `--json` prints them whole. Look before
476
- you write a migration against a shared database.
416
+ (`DROP TABLE`, `DROP COLUMN`, `TRUNCATE`, `DELETE FROM` without a condition) needs both the
417
+ `--allow-destructive` flag and the right to destroy data on the key, which is off by default and
418
+ only its owner turns on. When the refusal is about the right, the flag changes nothing: say what
419
+ needs deleting and why, and let the person decide. With both in place the affected tables are
420
+ dumped first and kept for 7 days. Check with `--dry-run`.
421
+
422
+ One logical database can be shared by several projects, so your migration can break an app you
423
+ do not see: `xflow db status` lists applied migrations with no file in your repository (that is
424
+ somebody else's project), and `xflow db list` names who else is on yours. For the same reason
425
+ `migrations/` is not the schema. It says what you did; `xflow db schema [table]` says what is in
426
+ the database right now, and `xflow db query "select ..."` reads it inside a READ ONLY
427
+ transaction (50 rows by default, `--limit` up to 300; `--json` prints long values whole). Look
428
+ before you write a migration against a shared database.
477
429
 
478
430
  ## File storage
479
431
 
@@ -489,29 +441,28 @@ xflow storage ls --json # the same list, machine-readable
489
441
  ```
490
442
 
491
443
  The caps that send them here (10 MB of sources, 15 MB unpacked) are about heavy media, not
492
- about code: icons, fonts and small artwork the build needs stay in the repository as usual.
493
- Everything heavy goes to storage, where it is metered against the organization plan and is
494
- not re-uploaded on every deploy.
444
+ about code: icons, fonts and small artwork stay in the repository as usual. Everything heavy
445
+ goes to storage, metered against the organization plan and not re-uploaded on every deploy.
495
446
 
496
- `push` prints the address of every file and skips what is already stored under the same path
497
- and size, so a repeat run after a broken connection is cheap; `--replace` overwrites a file
498
- whose size differs. An address belongs to the record rather than to the bytes, so a
499
- replacement keeps it and the links in your code and tables keep working. Put those addresses
500
- into the code or into a table: there is no command that fetches files back to a machine.
447
+ `push` prints the address of every file and skips what is already stored under the same path and
448
+ size, so a repeat run after a broken connection is cheap; `--replace` overwrites a file whose
449
+ size differs. An address belongs to the record rather than to the bytes, so a replacement keeps
450
+ it and existing links keep working. Put those addresses into the code or into a table: nothing
451
+ fetches files back to a machine.
501
452
 
502
453
  Deleting needs a right of its own, off by default (**Delete files**, where the key permissions
503
- live in the platform settings), so `xflow storage rm` may answer that the key was not granted it.
504
- Ask the person to switch it on, and do not look for a way around: whatever the users of the
505
- application uploaded lives in the same folders, and there is no undo. A folder first tells
506
- you how many files it holds and deletes them only with `--yes`.
454
+ live), so `xflow storage rm` may answer that the key was not granted it. Ask the person to switch
455
+ it on and do not look for a way around: whatever the users of the application uploaded lives in
456
+ the same folders, and there is no undo. A folder first says how many files it holds and deletes
457
+ them only with `--yes`.
507
458
 
508
459
  **Files that users of the application upload** go through a cloud function of your own. The
509
- browser cannot reach storage directly: those endpoints take only the server key of the
510
- project, and the platform puts it into the environment of your cloud functions as
511
- `XFLOW_SERVER_KEY`. Nothing else holds it: not the bundle, not `.env`, not `xflow env`.
460
+ browser cannot reach storage directly: those endpoints take only the server key of the project,
461
+ which the platform puts into the environment of your functions as `XFLOW_SERVER_KEY` and
462
+ nothing else holds not the bundle, not `.env`, not `xflow env`.
512
463
 
513
- So uploading is a function of your own. It asks the platform for a one-time link, the browser
514
- then sends the bytes straight to storage, and a second call records the file:
464
+ So uploading is a function of your own: it asks the platform for a one-time link, the browser
465
+ sends the bytes straight to storage, and a second call records the file:
515
466
 
516
467
  ```js
517
468
  const link = await fetch(`${process.env.XFLOW_API_URL}/api/storage/project/upload-url`, {
@@ -527,14 +478,12 @@ const link = await fetch(`${process.env.XFLOW_API_URL}/api/storage/project/uploa
527
478
  `confirm` takes the returned `s3Key` and answers with the file and its address; `delete`
528
479
  takes that same `url`. Never pipe the bytes through the function itself.
529
480
 
530
- There is no endpoint that lists files back to a function, so the address that `confirm`
531
- returns is the only copy this door gives you: write it into a table of your own in the same
532
- call, and the application reads its files from there.
481
+ No endpoint lists files back to a function, so the address `confirm` returns is the only copy
482
+ this door gives you: write it into a table of your own in the same call.
533
483
 
534
- That address never expires and is safe to store, but it is not a public link. It opens only
535
- for a visitor who is signed in and has access to this project, the same rule that guards the
536
- application itself, so it works on your pages and does nothing in an email or on a page
537
- anyone can open.
484
+ That address never expires and is safe to store, but it is not public: it opens only for a
485
+ signed-in visitor with access to this project, the same rule that guards the application itself,
486
+ so it works on your pages and does nothing in an email.
538
487
 
539
488
  Four things bite an upload that otherwise looks right:
540
489
 
@@ -559,28 +508,27 @@ A refusal comes back as `{ error, code }`. Branch on `code` (`invalid_name`, `fi
559
508
  `quota_exceeded`, `not_uploaded`, `duplicate_name`, `forbidden_type`, `not_found`, …) and never on the text:
560
509
  the wording is free to change, the code is not.
561
510
 
562
- What the app may do with files is decided inside that function, because the page in the
563
- browser can be edited by whoever opened it. Never write the key into the sources and never
564
- send it to the frontend: the build gate stops on a key found in the application code, and a
565
- key that reached a visitor lets them delete every file of the project.
511
+ What the app may do with files is decided inside that function, because the page can be edited
512
+ by whoever opened it. Never send the key to the frontend: the build gate stops on a key found in
513
+ the sources, and a key that reached a visitor deletes every file of the project.
566
514
 
567
515
  ## Organizations and keys
568
516
 
569
- One key per organization, stored side by side rather than replacing each other:
570
- `xflow org` lists them, `xflow org switch <name|id>` changes the active one, and
571
- `xflow help org` has the precedence rules. Two things are worth knowing without asking.
517
+ One key per organization, stored side by side rather than replacing each other: `xflow org`
518
+ lists them, `xflow org switch <name|id>` changes the active one, `xflow help org` has the
519
+ precedence rules.
572
520
 
573
- Inside a project folder there is nothing to switch: commands follow the organization the
574
- folder is bound to, whatever the active one is. That is what lets two projects of two
575
- organizations work side by side. The exception is `--project <id>`, which every command
576
- that can answer without the working copy takes (`xflow help project` lists them): it ignores
577
- the folder whole, so a second project needs no second checkout. `projects get` and `link`
578
- take no flag and name the project by its id as an argument instead.
521
+ Inside a project folder there is nothing to switch: commands follow the organization the folder
522
+ is bound to, whatever the active one is, which is what lets projects of two organizations work
523
+ side by side. The exception is `--project <id>`, taken by every command that can answer without
524
+ the working copy (`xflow help project` lists them): it ignores the folder whole, so a second
525
+ project needs no second checkout. `projects get` and `link` name the project by id as an
526
+ argument instead.
579
527
 
580
528
  A "not found" on a project you know exists usually means the key belongs to another
581
- organization, not that the project is gone: ids are unique across the platform, so a
582
- wrong key can never reach somebody else's project. Check `xflow org`. Signing in to a
583
- missing organization is `xflow login`, and that needs a person with a browser.
529
+ organization: ids are unique across the platform, so a wrong key can never reach somebody
530
+ else's project. Check `xflow org`; signing in to a missing organization is `xflow login`, and
531
+ that needs a person with a browser.
584
532
 
585
533
  ## Direct access without the terminal
586
534
 
@@ -605,18 +553,28 @@ through tool calls also burns the user's tokens for nothing.
605
553
 
606
554
  ## App design
607
555
 
608
- The platform design system already ships inside the project, and the app is supposed
609
- to look like a part of the platform:
556
+ The platform design system ships inside the project, and the app should look like a part
557
+ of the platform:
558
+
559
+ - `src/components/ui` — primitives: buttons, inputs, dialogs, tables, menus
560
+ - `src/components/blocks` — composed blocks: app shell, page header, section, toolbar with
561
+ filters, list, empty state, stats, detail card, form rows, card grid, timeline, table,
562
+ charts. Props are typed next to each block
563
+ - `src/index.css` — colour tokens
564
+
565
+ Take a block before writing your own layout, and colours from tokens (`bg-card`,
566
+ `text-muted-foreground`, `bg-success-soft`): a custom hex palette makes the app look
567
+ foreign inside the platform.
610
568
 
611
- - `src/components/ui` for primitives: buttons, inputs, dialogs, tables, menus
612
- - `src/components/blocks` for composed blocks: `data-table` and `charts`
613
- - `src/index.css` for color tokens
569
+ Screens that read as a product, not as a form:
614
570
 
615
- Before writing your own component, check whether a block already covers it: props are
616
- typed next to each block, larger ones keep a separate `types.ts`. Take colors from
617
- tokens (`bg-card`, `text-muted-foreground`, `bg-success-soft` and the like). A custom
618
- hex palette makes the app look foreign inside the platform, which is the whole reason
619
- the design system sits in the project.
571
+ - Every screen starts with `Page` and `PageHeader`; header, filters and content in one column.
572
+ - Draw all four states: loading, error, empty, data. An empty screen says what appears here and offers an action.
573
+ - Colour only when it communicates: status, action, error, success. Grey carries the structure.
574
+ - Density over air: people scan and compare. Numbers in a column, `tabular-nums`.
575
+ - Name the shape first: list, list with detail, dashboard, form, board.
576
+ - Seed 5-10 believable rows in the first migration: a screen with zero rows looks unfinished.
577
+ - Squint at the result: if the hierarchy blurs away, it is decoration, not structure.
620
578
 
621
579
  The app runs inside the platform in an iframe and receives the theme and the current
622
580
  user from it. The `usePlatformAuth()` hook gives the name, role, permissions and the