@getxflow/cli 0.34.2 → 1.0.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
@@ -12,7 +12,10 @@ const version_1 = require("./version");
12
12
  const ALIASES = {
13
13
  invoke: 'functions',
14
14
  publish: 'deploy',
15
- deployments: 'rollback',
15
+ rollback: 'deploy',
16
+ deployments: 'deploy',
17
+ apps: 'projects',
18
+ status: 'projects',
16
19
  };
17
20
  /** Help text. For agents this is the reference documentation. */
18
21
  /** Whether `xflow help <topic>` has a page: a hint that sends nowhere is a second dead end. */
@@ -35,19 +38,22 @@ Every command with a line of its own: xflow help`);
35
38
  }
36
39
  (0, ui_1.out)(`${(0, ui_1.bold)('xflow')} ${(0, ui_1.dim)(version_1.CLI_VERSION)} XFlow application hosting
37
40
 
41
+ A project is a box: one database, cloud functions, secrets. Its apps live in apps/<folder>,
42
+ each with its own versions and address. Commands follow the folder you stand in.
43
+
38
44
  ${(0, ui_1.bold)('Getting started')}
39
45
  xflow login sign in through the browser
40
- xflow init [dir] new project from the platform template
41
- xflow templates which templates are available
42
- xflow link <id> link this folder to a project, an empty one also gets the sources
43
- xflow dev run the app on 127.0.0.1:3000, cloud functions included
46
+ xflow init [dir] new project with its first app from the platform template
47
+ xflow apps add <folder> another app of this project, in apps/<folder>
48
+ xflow link <id> link this folder to a project, an empty one gets the whole project
49
+ xflow dev in an app folder: run it on 127.0.0.1:3000, functions included
44
50
  xflow skills pick the agents that get the platform instructions
45
51
  xflow mcp install give the agent platform access without a terminal
46
52
 
47
53
  ${(0, ui_1.bold)('Sources')}
48
- xflow status what is on the server and how the local copy differs
49
- xflow pull [--into dir] [--revision N]
50
- fetch the sources (the latest revision by default)
54
+ xflow status what is on the server and how the local copies differ
55
+ xflow pull [functions|apps/<folder>] [--into dir] [--revision N]
56
+ fetch the sources of the root or of one app
51
57
 
52
58
  ${(0, ui_1.bold)('Files')}
53
59
  xflow storage ls [folder] what the file storage of the project holds
@@ -58,13 +64,16 @@ ${(0, ui_1.bold)('Files')}
58
64
  delete a folder with everything in it
59
65
 
60
66
  ${(0, ui_1.bold)('Releasing')}
61
- xflow deploy -m "what changed" send the code, ship the functions, build on the platform
62
- xflow publish show the dev version to visitors
63
- xflow rollback <version number> serve the pages of an earlier build
64
- xflow deployments version history
67
+ xflow deploy functions -m "what changed"
68
+ send the project root, ship the functions and their callers
69
+ xflow deploy apps/<folder> -m "what changed"
70
+ send one app and build it (a bare deploy inside its folder)
71
+ xflow publish [build] point the app address at the newest build, or a named one
72
+ xflow rollback <build> the same with an older build
73
+ xflow deployments [functions] builds of the app, or deploys of the functions
65
74
 
66
75
  ${(0, ui_1.bold)('Functions')}
67
- xflow functions list what is deployed, who may call it, the address
76
+ xflow functions list what is deployed, its callers, open or not, last call
68
77
  xflow functions invoke <name> [--data '{"a":1}']
69
78
  call a function and print the answer
70
79
  xflow functions logs [name] function crashes: stack and console output
@@ -83,7 +92,7 @@ ${(0, ui_1.bold)('Functions')}
83
92
 
84
93
  ${(0, ui_1.bold)('Database')}
85
94
  xflow db status which migrations are applied and which are waiting
86
- xflow db list databases of the organization, servers, projects
95
+ xflow db list databases of the projects and the servers of the organization
87
96
  xflow db schema [table] tables of the project, or the columns of one
88
97
  xflow db query 'select ...' read data, in a read-only transaction
89
98
  xflow db migrate [--dry-run] [--allow-destructive]
@@ -101,9 +110,10 @@ ${(0, ui_1.bold)('Pages')}
101
110
  xflow pages rollback <page> <N> put an earlier version back on the address
102
111
 
103
112
  ${(0, ui_1.bold)('Reference')}
104
- xflow logs [--limit N] browser errors from the released application
113
+ xflow logs [--limit N] browser errors of the published apps
105
114
  xflow projects list projects of the organization
106
- xflow projects get [id] project card
115
+ xflow projects get [id] project card: apps, database, functions
116
+ xflow apps the apps of this project and their folders
107
117
  xflow org organizations with a stored key, the active one marked
108
118
  xflow org members who is in the organization, and with which role
109
119
  xflow org switch <name|id> make another organization the active one
@@ -115,6 +125,7 @@ ${(0, ui_1.bold)('Reference')}
115
125
  ${(0, ui_1.bold)('Outside a project folder')}
116
126
  --project <id> name the project by id, for every command that can
117
127
  answer without the local files (xflow help project)
128
+ --app <id> name an app by id: deploy --revision, publish, deployments, logs
118
129
 
119
130
  ${(0, ui_1.bold)('Environment')}
120
131
  XFLOW_TOKEN access key (for CI, instead of xflow login)
@@ -168,19 +179,23 @@ Which commands: ${(0, ui_1.bold)('db status')}, ${(0, ui_1.bold)('db query')}, $
168
179
  ${(0, ui_1.bold)('functions invoke')}, ${(0, ui_1.bold)('functions logs')}, ${(0, ui_1.bold)('logs')}, ${(0, ui_1.bold)('env')}, ${(0, ui_1.bold)('env rm')},
169
180
  ${(0, ui_1.bold)('connections')}, ${(0, ui_1.bold)('connections link')}, ${(0, ui_1.bold)('connections unlink')}, ${(0, ui_1.bold)('schedules')},
170
181
  ${(0, ui_1.bold)('schedules set')}, ${(0, ui_1.bold)('schedules rm')}, ${(0, ui_1.bold)('storage ls')}, ${(0, ui_1.bold)('storage push')},
171
- ${(0, ui_1.bold)('storage rm')}, ${(0, ui_1.bold)('deployments')}, ${(0, ui_1.bold)('publish')}, ${(0, ui_1.bold)('rollback')}, ${(0, ui_1.bold)('deploy')}. Three of
182
+ ${(0, ui_1.bold)('storage rm')}, ${(0, ui_1.bold)('apps')}, ${(0, ui_1.bold)('deployments')} (the functions deploys) and ${(0, ui_1.bold)('deploy')}. Three of
172
183
  them answer with less under the flag: ${(0, ui_1.bold)('db status')} gives the applied history alone,
173
184
  because comparing it against the local ${(0, ui_1.bold)('migrations/')} directory needs the folder,
174
185
  ${(0, ui_1.bold)('connections unlink')} removes the link without naming the functions that read
175
186
  its variables, for the same reason, and ${(0, ui_1.bold)('deploy')} takes the flag only together with
176
- ${(0, ui_1.bold)('--revision')}: that build runs a revision the platform already holds, so there is
177
- nothing to send and no folder to send it from. Without ${(0, ui_1.bold)('--revision')} it refuses and
178
- says so.
187
+ ${(0, ui_1.bold)('--revision')}: that deploy runs a revision of the functions the platform already holds,
188
+ so there is nothing to send and no folder to send it from.
189
+
190
+ ${(0, ui_1.bold)('--app <id>')} does the same for one app: ${(0, ui_1.bold)('deploy --app <id> --revision N')}, ${(0, ui_1.bold)('deployments')},
191
+ ${(0, ui_1.bold)('logs')}, ${(0, ui_1.bold)('publish')} and ${(0, ui_1.bold)('rollback')}. Its project comes from the platform, so the two
192
+ flags never go together.
179
193
 
180
194
  The rest cannot answer without the working copy and have nothing to do with an id
181
- instead: ${(0, ui_1.bold)('pull')}, ${(0, ui_1.bold)('status')}, ${(0, ui_1.bold)('dev')}, ${(0, ui_1.bold)('db migrate')},
195
+ instead: ${(0, ui_1.bold)('pull')}, ${(0, ui_1.bold)('status')}, ${(0, ui_1.bold)('dev')}, ${(0, ui_1.bold)('apps add')}, ${(0, ui_1.bold)('db migrate')},
182
196
  ${(0, ui_1.bold)('env check')}, ${(0, ui_1.bold)('env set')}. They answer about the files next to them, so
183
- they keep asking for a folder. ${(0, ui_1.bold)('projects get')} and ${(0, ui_1.bold)('link')} take no flag either:
197
+ they keep asking for a folder. ${(0, ui_1.bold)('publish')} and ${(0, ui_1.bold)('rollback')} take ${(0, ui_1.bold)('--app')} rather than
198
+ ${(0, ui_1.bold)('--project')}: they move one app. ${(0, ui_1.bold)('projects get')} and ${(0, ui_1.bold)('link')} take no flag either:
184
199
  they name the project by its id as an argument.
185
200
 
186
201
  The folder is then ignored whole, and that includes its ${(0, ui_1.bold)('xflow.json')}: the address
@@ -198,13 +213,35 @@ With several organizations signed in, the one holding the project is looked for
198
213
  every call: there is no folder to write the answer into, and a second place to
199
214
  remember it would be a second thing to keep true. When most calls go to one project,
200
215
  ${(0, ui_1.bold)('xflow org switch')} to its organization skips that search.`,
201
- projects: `${(0, ui_1.bold)('xflow projects')}: the projects of the organization
202
-
203
- xflow projects list every project, its versions state and age
204
- xflow projects get [id] one card: versions, database, functions, schedules
205
-
206
- Both name the project positionally: the id is an argument, not a --project flag.
207
- Without one, ${(0, ui_1.bold)('get')} answers about the project of the folder it stands in.
216
+ projects: `${(0, ui_1.bold)('xflow projects')} and ${(0, ui_1.bold)('xflow apps')}: projects and their apps
217
+
218
+ A project is a box: one database with its migrations, the cloud functions, secrets and
219
+ members, and no code of its own. An app is code the platform builds and serves at
220
+ https://<id>--<org>.xflowpage.ru, with its own versions and access level. One folder per
221
+ project, apps in subfolders:
222
+
223
+ xflow.json projectId, and who may call each function
224
+ package.json dependencies of the functions
225
+ functions/<name>/index.ts cloud functions, shared by every app
226
+ migrations/0001_init.sql the one database of the project
227
+ apps/<folder>/ an app: a Vite project with its own xflow.json (appId)
228
+
229
+ xflow projects list every project, its apps count and age
230
+ xflow projects get [id] one card: apps, database, functions and callers, schedules
231
+ xflow apps the apps of this project, their folders and addresses
232
+ --json as JSON, with the app ids
233
+ xflow apps add <folder> a new app, laid into apps/<folder> from the template
234
+ --name <name> its name (the folder by default)
235
+ --color <color> its tile color (the platform picks one by default)
236
+ xflow status in the root: the root and every app; in an app: that app
237
+
238
+ The root and every app are separate histories: apps/** never travels with the root, the
239
+ root never travels with an app. The app is its appId, so a folder can be renamed freely.
240
+ Who opens an app (members of the project, a list, anyone with the link) is set by a person
241
+ in the web interface, not here.
242
+
243
+ Both ${(0, ui_1.bold)('projects')} commands name the project positionally: the id is an argument, not a
244
+ --project flag. Without one, ${(0, ui_1.bold)('get')} answers about the project of the folder it stands in.
208
245
 
209
246
  An id typed by hand is looked for across every signed-in organization, and the
210
247
  folder around the command is ignored whole, the way the ${(0, ui_1.bold)('--project')} flag does it
@@ -218,14 +255,14 @@ projects these are.
218
255
  ${(0, ui_1.bold)('xflow link <id>')} binds the current folder to a project; ${(0, ui_1.bold)('xflow init')} makes a new one.`,
219
256
  link: `${(0, ui_1.bold)('xflow link')} <id>: bind this folder to a project
220
257
 
221
- Writes xflow.json and .xflow/state.json, points .env at the project and lays down the
222
- instructions for the agents. The id comes from ${(0, ui_1.bold)('xflow projects list')}; a project of
223
- another signed-in organization is found too, and the folder is bound to that organization.
258
+ Writes the root xflow.json and .xflow/state.json and lays down the instructions for the
259
+ agents. The id comes from ${(0, ui_1.bold)('xflow projects list')}; a project of another signed-in
260
+ organization is found too, and the folder is bound to that organization.
224
261
 
225
- An empty folder (.git alone counts as empty) also gets the latest sources, so there is no
226
- ${(0, ui_1.bold)('pull')} after it: pull would refuse the folder anyway, it is not empty any more. A
227
- folder with files keeps them as they are: ${(0, ui_1.bold)('xflow status')} says how they differ from
228
- the server revision, ${(0, ui_1.bold)('xflow pull --into ./server-copy')} fetches it next to them.
262
+ An empty folder (.git alone counts as empty) gets the whole project: the latest revision
263
+ of the root and of every app, each app in apps/<folder> under the folder name the project
264
+ revision records. A folder with files keeps them as they are: ${(0, ui_1.bold)('xflow status')} says
265
+ how they differ, ${(0, ui_1.bold)('xflow pull --into ./server-copy')} fetches the whole project next to them.
229
266
 
230
267
  In a folder that is already linked, the command points xflow.json at the other project and
231
268
  keeps the rest of the file. A new project instead of an existing one: ${(0, ui_1.bold)('xflow init')}.`,
@@ -233,8 +270,8 @@ keeps the rest of the file. A new project instead of an existing one: ${(0, ui_1
233
270
 
234
271
  A page is a static site at https://<name>.xflowpage.ru: a landing page, a presentation, an
235
272
  invoice for people outside the organization. It opens by its address for anyone, without
236
- signing in and outside the platform. A project is the opposite: it opens only inside the
237
- platform, for the members of the organization.
273
+ signing in, and has no build, no publish step and no database. An app is the opposite: code
274
+ the platform builds, with versions, access levels and a database through functions.
238
275
 
239
276
  xflow pages the pages, their state and address
240
277
  xflow pages create <title> <path> a new page with its first version: there are no
@@ -261,10 +298,11 @@ build it and upload what the build writes. Dot entries are skipped. At most 5 MB
261
298
  unpacked and 1000 files.
262
299
 
263
300
  The page is public, so a file holding a secret key is refused whole: every visitor would read
264
- it. A page that needs a backend (a form, a lookup) calls a cloud function that declares
265
- ${(0, ui_1.bold)("exports.access = 'public'")}: such a function answers without a pass and without a
266
- key, and the page calls its address directly (${(0, ui_1.bold)('xflow functions list')} prints it). No
267
- key is baked into the page. Search engines are asked not to index pages, so do not promise SEO.
301
+ it. A page that needs a backend (a form, a lookup) calls ${(0, ui_1.bold)('/_fn/<name>')} on its own address,
302
+ and only a function whose callers in the root xflow.json of its project name the page answers:
303
+ add the page name there and run ${(0, ui_1.bold)('xflow deploy functions')}. The page has to exist first.
304
+ Such a function is open to the whole internet. Search engines are asked not to index pages,
305
+ so do not promise SEO.
268
306
 
269
307
  A page takes the key of the whole organization, the one ${(0, ui_1.bold)('xflow login')} issues: a key
270
308
  scoped to projects is refused. Switching a page off and deleting it are for a person, in the
@@ -295,40 +333,35 @@ will work.`,
295
333
  db: `${(0, ui_1.bold)('xflow db')}: schema, data, migrations
296
334
 
297
335
  xflow db status what is applied, what is waiting, the server
298
- xflow db list databases of the organization: servers, projects
336
+ xflow db list databases of the projects, the servers of the organization
299
337
  xflow db schema [table] tables of the project, or the columns of one
300
338
  xflow db query 'select ...' read data (--limit N: 50 by default, 300 at most;
301
339
  --json prints the values whole)
302
340
  xflow db query <file>.sql the same, the query read from a file
303
- xflow db migrate apply migrations/*.sql
304
- xflow db move --to <server> --confirm <database>
341
+ xflow db migrate store the project root, then apply migrations/*.sql
342
+ xflow db move --to <server> --confirm <project name>
305
343
  move the project database to another server
306
344
 
307
- ${(0, ui_1.bold)('list')} is about the organization, not about this project: it names the logical
308
- databases, the identifier ${(0, ui_1.bold)('xflow init --database <id>')} asks for, and the projects
309
- attached to each one. That last column is the one to read before a migration. The server
310
- column says where a database lives: ${(0, ui_1.bold)('platform')} is the common one, ${(0, ui_1.bold)('own "<name>"')} the
311
- organization's own server. While an own server is not running, its databases do not
312
- answer, and that is not fixable from the code: its state is in the web interface. An
313
- organization with own servers also gets them listed, with the room used on each: that is
314
- the choice for ${(0, ui_1.bold)('xflow init --server')} and ${(0, ui_1.bold)('db move')}. Without that flag a new
315
- database goes to ${(0, ui_1.bold)('platform')}.
345
+ Every project has exactly one database of its own, shared by all of its apps and functions,
346
+ and every command here works from any folder of the project. ${(0, ui_1.bold)('list')} is about the
347
+ organization: one database per project, and where each lives: ${(0, ui_1.bold)('platform')} is the common
348
+ server, ${(0, ui_1.bold)('own "<name>"')} an own server of the organization. While an own server is not
349
+ running, its databases do not answer, and that is not fixable from the code. An organization
350
+ with own servers also gets them listed, with the room used on each: that is the choice for
351
+ ${(0, ui_1.bold)('xflow init --server')} and ${(0, ui_1.bold)('db move')}.
316
352
 
317
353
  ${(0, ui_1.bold)('move')} takes the database of this project to another server of the organization:
318
354
  ${(0, ui_1.bold)('platform')} or an own server by name (or id, when two share a name). The platform copies
319
- the tables, switches the functions of every project on the database and removes the old
320
- copy; nothing needs a deploy. While it copies, the applications read data but cannot save
321
- anything new, usually for a couple of minutes, and that goes for every project on the
322
- database, which is why ${(0, ui_1.bold)('--confirm')} takes the database name. It also needs the right
323
- to move databases on the key: off by default, only its owner turns it on, in the platform
324
- settings where the key permissions live. The command follows the move to the end;
355
+ the tables, switches the functions of the project and removes the old copy; nothing needs a
356
+ deploy. While it copies, the apps read data but cannot save anything new, usually for a
357
+ couple of minutes, which is why ${(0, ui_1.bold)('--confirm')} takes the project name. It also needs the
358
+ right to move databases on the key: off by default, only its owner turns it on, in the
359
+ platform settings where the key permissions live. The command follows the move to the end;
325
360
  stopping it does not stop the move, and ${(0, ui_1.bold)('xflow db status')} shows where it stands. A move
326
- that fails is rolled back: the database stays where it was and works as before. Buying,
327
- paying for and deleting servers stay with the person, in the web interface.
361
+ that fails is rolled back. Buying, paying for and deleting servers stay with the person.
328
362
 
329
- ${(0, ui_1.bold)('schema')} answers a different question than the ${(0, ui_1.bold)('migrations/')} directory: one logical
330
- database is shared by several projects, so the files say what you did, and the schema
331
- says what is actually in there. ${(0, ui_1.bold)('query')} runs inside a READ ONLY transaction, so
363
+ ${(0, ui_1.bold)('schema')} answers a different question than the ${(0, ui_1.bold)('migrations/')} directory: the files say
364
+ what you did, the schema says what is actually in there. ${(0, ui_1.bold)('query')} runs inside a READ ONLY transaction, so
332
365
  writes are rejected by the database itself, not by us reading your SQL. Single quotes around
333
366
  the query hold in PowerShell, bash and zsh, while cmd.exe wants double ones; a query that
334
367
  needs quotes of its own goes in a .sql file outside the project, because Windows PowerShell
@@ -338,10 +371,12 @@ long values to keep the columns readable; ${(0, ui_1.bold)('--json')} prints the
338
371
  the applied limit and its ceiling. ${(0, ui_1.bold)('status')} named by id (${(0, ui_1.bold)('--project')}) answers with
339
372
  the applied history alone: comparing against ${(0, ui_1.bold)('migrations/')} needs the folder.
340
373
 
341
- ${(0, ui_1.bold)('migrate')}: the files live in the repository (${(0, ui_1.bold)('migrations/0001_init.sql')},
374
+ ${(0, ui_1.bold)('migrate')}: the files live in the project root (${(0, ui_1.bold)('migrations/0001_init.sql')},
342
375
  ${(0, ui_1.bold)('migrations/0002_orders.sql')} and so on). The order comes from the file name, which
343
- is why the leading number is required. Every migration runs in its own transaction, the
344
- history is kept in the database itself, and anything already applied is not run again.
376
+ is why the leading number is required. The command first stores the project root as a
377
+ revision, so the migrations stay in its history and a clone gets them, then applies what is
378
+ new. Every migration runs in its own transaction, the history is kept in the database
379
+ itself, and anything already applied is not run again.
345
380
 
346
381
  --dry-run report what would be applied without touching the database
347
382
  --allow-destructive allow operations that destroy data
@@ -353,21 +388,17 @@ access key. That right is off by default, and only its owner turns it on, in the
353
388
  platform settings, where the key permissions live: a flag is something an agent adds by itself, a
354
389
  right is not. With both in place, the contents of the affected tables are dumped and
355
390
  kept for 7 days: that is a "caught it right away" safety net, not a backup.
356
- ${(0, ui_1.bold)('DROP DATABASE')} is never allowed, because the database is shared across the
357
- organization.
391
+ ${(0, ui_1.bold)('DROP DATABASE')} is never allowed.
358
392
 
359
393
  Editing an already applied file achieves nothing: ${(0, ui_1.bold)('migrate')} refuses a name that is
360
394
  already applied with different content, and ${(0, ui_1.bold)('xflow db status')} lists such a file
361
- separately. Write a new migration instead.
362
-
363
- One logical database can be attached to several projects, so the history may hold
364
- migrations that are not in your repository. That is normal, but it also means your
365
- migration can break somebody else's application, and that a name already in that history
366
- is taken for you too: ${(0, ui_1.bold)('0001_init')} of a neighbour leaves yours no room.`,
395
+ separately. Write a new migration instead.`,
367
396
  logs: `${(0, ui_1.bold)('xflow logs')} and ${(0, ui_1.bold)('xflow functions logs')}: project errors
368
397
 
369
- One stream per project: crashes of the released application in the browser and
370
- crashes of the cloud functions. The last 200 entries are kept, older ones fall off.
398
+ Two streams: crashes of the cloud functions of the project, and browser errors of each
399
+ app. ${(0, ui_1.bold)('xflow logs')} in an app folder (or with ${(0, ui_1.bold)('--app <id>')}) shows that app, in the
400
+ project root every app, each row named by its folder. The last 200 entries of each stream
401
+ are kept, older ones fall off.
371
402
 
372
403
  --limit N how many to show (10 by default)
373
404
 
@@ -382,7 +413,7 @@ never gets as far as the wrapper), going over the execution time cap of the plan
382
413
  Those show up in the answer to ${(0, ui_1.bold)('xflow functions invoke')}.
383
414
 
384
415
  Browser errors are collected by ${(0, ui_1.bold)('src/utils/error-logger.ts')} of the template and
385
- only from released addresses: a local ${(0, ui_1.bold)('npm run dev')} writes nothing here.`,
416
+ only from the published addresses: a local ${(0, ui_1.bold)('xflow dev')} writes nothing here.`,
386
417
  storage: `${(0, ui_1.bold)('xflow storage')}: files of the application
387
418
 
388
419
  The sources archive is capped at 10 MB and is rebuilt and re-uploaded on every
@@ -449,9 +480,10 @@ variables literally. ${(0, ui_1.bold)('check')} names those reads and the build
449
480
  that quietly fails to arrive is found by the function falling over weeks later.
450
481
 
451
482
  The value reaches the function on deploy, not at the moment it is stored: after
452
- ${(0, ui_1.bold)('env set')} run ${(0, ui_1.bold)('xflow deploy')}. The build ships a function whose code did not
483
+ ${(0, ui_1.bold)('env set')} run ${(0, ui_1.bold)('xflow deploy functions')}. It ships a function whose code did not
453
484
  change but whose variables did, so nothing is left holding an old value. The same after a
454
- delete: a function already deployed keeps the old value until its next deploy.`,
485
+ delete: a function already deployed keeps the old value until its next deploy. Apps get no
486
+ variables at all: whatever a secret protects is reached through a function.`,
455
487
  connections: `${(0, ui_1.bold)('xflow connections')}: accounts connected to the organization
456
488
 
457
489
  Somebody signs in to Yandex Metrika, Bitrix or a mail service once, in the platform,
@@ -487,8 +519,8 @@ linked to a service project without a folder has to be removable the same way.
487
519
  Variables are named after the alias: an OAuth connection called ${(0, ui_1.bold)('YANDEX_METRIKA')}
488
520
  gives ${(0, ui_1.bold)('YANDEX_METRIKA_TOKEN')}, a key-based one gives a variable per field. They
489
521
  arrive at a function on its next deploy, like every other variable, so after a change
490
- run ${(0, ui_1.bold)('xflow deploy')}. A token close to expiry is renewed by any build on the way;
491
- a revoked one is not renewed by anything until a human reconnects the account.
522
+ run ${(0, ui_1.bold)('xflow deploy functions')}. A token close to expiry is renewed by that deploy on the
523
+ way; a revoked one is not renewed by anything until a human reconnects the account.
492
524
 
493
525
  Linking needs a right on the key, ${(0, ui_1.bold)('connections:link')}. Keys are issued with it,
494
526
  and a person can take it away in the platform settings, where the key permissions live; a key cannot give
@@ -558,41 +590,38 @@ how Yandex works. ${(0, ui_1.bold)('The time is always UTC')}, local time is not
558
590
  ${(0, ui_1.bold)('list')} prints that body next to the schedule, so what a timer sends can be read
559
591
  back without setting it again.
560
592
 
561
- A scheduled run arrives at the handler as a POST with no headers, and the project
562
- token is not checked on it: such an event cannot be forged from outside. The function
563
- has to be deployed already, since the schedule points at it and not the other way round.
593
+ A scheduled run arrives at the handler as a POST with ${(0, ui_1.bold)("event.xflow.caller === 'schedule'")}:
594
+ the envelope is signed by the platform, so such an event cannot be forged from outside. A
595
+ function without callers that only a schedule wakes is a workflow, and a project without
596
+ apps is made of those. The function has to be deployed already, since the schedule points
597
+ at it and not the other way round.
564
598
 
565
599
  A failed run shows up in ${(0, ui_1.bold)('xflow functions logs')}: nobody is watching a scheduled
566
600
  function, so the wrapper reports a crash the same way it does on an ordinary call.`,
567
601
  functions: `${(0, ui_1.bold)('xflow functions')}: what is deployed, and calling it
568
602
 
569
- xflow functions list name, state, who may call it, age, address
570
- xflow functions invoke <name> call one the way the application does
603
+ xflow functions list name, state, callers, open, keys, last call and error
604
+ xflow functions invoke <name> call one as yourself, through the platform
571
605
  xflow functions logs [name] crashes: stack and console output
572
606
 
573
- There is no deploy command of their own: ${(0, ui_1.bold)('xflow deploy')} ships everything in
574
- ${(0, ui_1.bold)('functions/<name>/index.ts')} and then builds the application.
575
-
576
- ${(0, ui_1.bold)('list')} answers who may reach each function, and the code decides it:
607
+ Functions belong to the project and ship on their own step: ${(0, ui_1.bold)('xflow deploy functions')}
608
+ sends the project root and deploys everything in ${(0, ui_1.bold)('functions/<name>/index.ts')}. Every app
609
+ of the project runs the new code the moment it finishes; there is no publish step for them.
577
610
 
578
- in-app only a visitor of the application, with a pass the platform issues
579
- external (N keys) plus an outside service holding a key a human issued in the interface
580
- public plus anyone at all: the code says ${(0, ui_1.bold)("exports.access = 'public'")}
611
+ Who may call a function is its callers, in the root xflow.json:
581
612
 
582
- An open function is how a page on xflowpage.ru gets a backend: it answers without a pass
583
- and without a key, so the page calls the printed address directly and bakes no key into
584
- its files. Remove the line and deploy again to close it. The function that stays inside
585
- the application declares nothing, and ${(0, ui_1.bold)("exports.minRole = 'developer'")} raises the
586
- role the visitor needs. The address is printed for every deployed function; it is not a
587
- secret, the application bundle carries it.
613
+ { "projectId": "…", "functions": { "book-slot": { "callers": ["zapis", "clinic-landing"] } } }
588
614
 
589
- Under the table, when there are any: the standalone functions of the organization this
590
- project calls by the same name. Their code lives elsewhere, so reaching them from here
591
- takes ${(0, ui_1.bold)('--project <id>')}, which is printed next to each.
615
+ A caller is an app folder of this project (apps/zapis is "zapis") or the name of a page of
616
+ the organization. A function nobody lists has no callers: a workflow, woken by a schedule,
617
+ by a service holding a key a person issued, or by ${(0, ui_1.bold)('invoke')}. ${(0, ui_1.bold)('open')} in the list means a
618
+ caller is public (a page, or an app anyone with the link opens), so anyone on the internet
619
+ can call it. The address in the note under the table is for webhooks and other functions,
620
+ with a key; apps and pages call ${(0, ui_1.bold)('/_fn/<name>')} on their own address.
592
621
 
593
- ${(0, ui_1.bold)('invoke')} calls it exactly the way the application does: the project token plus a
594
- visitor pass for the person who owns the key, so the function sees a real caller and its
595
- role. Both are taken from the platform, no local .env is needed.
622
+ ${(0, ui_1.bold)('invoke')} calls it as the person who owns the key: the platform checks the role, gives
623
+ the function a one-minute ticket and calls it itself, so the function sees
624
+ ${(0, ui_1.bold)("event.xflow.caller === 'platform'")} and the person in ${(0, ui_1.bold)('event.xflow.user')}.
596
625
 
597
626
  --data '{"a":1}' request body (the method becomes POST by default)
598
627
  --data-file body.json the same body from a file
@@ -604,16 +633,17 @@ strips double quotes from a native call, so ${(0, ui_1.bold)('--data')} needs th
604
633
  (${(0, ui_1.bold)('--data \'{\\"a\\":1}\'')}) and a file needs nothing.
605
634
 
606
635
  Prints the status, the response time and the body; under ${(0, ui_1.bold)('--json')} the body
607
- alone. A non-zero exit code on 4xx and 5xx: in CI such a call has to fail the step.
608
- The cause of a crash is shown by ${(0, ui_1.bold)('xflow functions logs <name>')}.`,
636
+ alone. A non-zero exit code on 4xx and 5xx: in CI such a call has to fail the step. A
637
+ refusal of the guard names its reason in the body (${(0, ui_1.bold)('not_bound')}, ${(0, ui_1.bold)('no_access')},
638
+ ${(0, ui_1.bold)('functions_stopped')}); for a crash the hint points at ${(0, ui_1.bold)('xflow functions logs <name>')}.`,
609
639
  mcp: `${(0, ui_1.bold)('xflow mcp install')}: connect the agent to the platform directly
610
640
 
611
641
  Writes the MCP server into the configuration of a client that has its own connect
612
642
  command: today that is Claude Code, and for the rest the command prints what to enter
613
643
  by hand. After that the agent runs the platform without a terminal: it inspects the
614
- database, applies migrations, ships and calls functions, sets schedules, reads logs.
615
- The commands stay: code, builds and releases are still done by the CLI, because
616
- pushing sources through MCP means filling the agent context and spending your money.
644
+ database, applies migrations, calls functions, sets schedules, publishes app builds, reads
645
+ logs. The commands stay: code, builds and functions deploys are still done by the CLI,
646
+ because pushing sources through MCP means filling the agent context and spending your money.
617
647
 
618
648
  The command takes the access key from your own login and does not print it: usually
619
649
  the agent runs this command itself, and everything printed lands in its context and in
@@ -653,53 +683,57 @@ Plus a few pointer lines in ${(0, ui_1.bold)('AGENTS.md')}. A skill is picked up
653
683
  description matches the task, and "add a customers table" will not trigger it. AGENTS.md
654
684
  is always read by the agent, which is why the pointer is appended there, at the end of
655
685
  the file and once. ${(0, ui_1.bold)('CLAUDE.md')} is left alone: Claude Code reads .claude/skills anyway.`,
656
- dev: `${(0, ui_1.bold)('xflow dev')}: run the application on this machine
686
+ dev: `${(0, ui_1.bold)('xflow dev')}: run an app on this machine
657
687
 
658
- Runs ${(0, ui_1.bold)('npm run dev')} in the project folder (127.0.0.1:3000) with the access key of the
659
- organization that holds the project. The dev server trades that key for a visitor pass,
660
- so the cloud functions answer here the way they do on the platform. The key is picked
661
- as for every other command (${(0, ui_1.bold)('xflow help org')}): with several organizations signed
662
- in, the right one is found and the folder is bound to it.
688
+ Runs ${(0, ui_1.bold)('npm run dev')} in the app folder it stands in (127.0.0.1:3000) with the access key
689
+ of the organization that holds the project. Every ${(0, ui_1.bold)('/_fn/<name>')} call of the app goes to
690
+ the platform as this app, so the callers, the access and the person calling are the same
691
+ as on its address. Nothing about functions is kept on this machine: a function someone
692
+ else shipped answers here at once.
663
693
 
664
- Use it instead of a bare ${(0, ui_1.bold)('npm run dev')}: that one is handed no key, so its calls to
665
- cloud functions fail. In a project made before this command the failure reads "No access
666
- key for the platform: run xflow login", and signing in will not help, because the key is
667
- no longer left in a file for that dev server to pick up.
668
-
669
- The platform address comes from the CLI too, not from .env, so the key goes to the
670
- platform that issued it and nowhere else. It never reaches the browser: the page gets
671
- only the ${(0, ui_1.bold)('VITE_')} variables. The project token and the function addresses are
672
- fetched on every start as well: a function someone else shipped answers here without a
673
- deploy of yours.
694
+ Use it instead of a bare ${(0, ui_1.bold)('npm run dev')}: that one is handed no key and no app id, so
695
+ its calls to functions fail. The platform address comes from the CLI too, so the key goes
696
+ to the platform that issued it and nowhere else. It never reaches the browser.
674
697
 
675
698
  The server listens on 127.0.0.1 and nowhere else, because it holds that key: on a
676
699
  shared network, anything else would let a neighbour call the functions of the project
677
700
  as you. A phone on the same Wi-Fi therefore cannot open it either.`,
678
- pull: `${(0, ui_1.bold)('xflow pull')}: fetch the sources
701
+ pull: `${(0, ui_1.bold)('xflow pull')} [functions|apps/<folder>]: fetch the sources
679
702
 
680
- By default it fetches the latest revision into the project folder and refuses to write
681
- into a non-empty one: the platform cannot merge changes, that is git's job.
703
+ Every app and the project root are separate histories, and pull fetches one of them: in an
704
+ app folder that app, in the project root the root (apps/ stays as it is), or the one named.
705
+ It refuses to write into a non-empty folder: the platform cannot merge changes, that is
706
+ git's job.
682
707
 
683
- --into <dir> unpack alongside, to compare
684
- --revision <N> a specific revision (the list: xflow deployments)
708
+ --into <dir> unpack alongside, to compare; in the project root with nothing named
709
+ it fetches the whole project there
710
+ --revision <N> a specific revision of that history (xflow deployments lists them)
685
711
  --force overwrite the folder completely`,
686
- deploy: `${(0, ui_1.bold)('xflow deploy')}: build and release
712
+ deploy: `${(0, ui_1.bold)('xflow deploy')}: deploy the functions or build an app, then publish
713
+
714
+ xflow deploy functions -m "…" send the project root, check it, ship the functions
715
+ and their callers
716
+ xflow deploy apps/<folder> -m "…" send one app and build it; inside its folder a plain
717
+ xflow deploy does the same
718
+ xflow publish [build] point the app address at the newest build, or a named one
719
+ xflow rollback <build> the same, the number required
720
+ xflow deployments [functions] the builds of the app, or the deploys of the functions
687
721
 
688
- Three steps: sending the sources, shipping the cloud functions, building the application.
689
- The build command and the output directory come from xflow.json (npm run build and dist
690
- by default).
722
+ In the project root a plain ${(0, ui_1.bold)('xflow deploy')} refuses and names the targets: functions and
723
+ apps never go in one run, because a change of functions reaches every app at once.
691
724
 
692
725
  -m <text> the version comment, required: one short line in the language the
693
- user speaks, saying what changed. It names the version in the
694
- history (--message is the long form)
726
+ user speaks, saying what changed (--message is the long form)
695
727
  --no-push do not send sources, build from the latest server revision
696
- --revision <N> build a revision the platform already holds: this is how a
697
- version comes back (the list: xflow deployments)
698
- --project <id> with --revision only: the project to rebuild, no folder needed
728
+ --revision <N> run a revision the platform already holds: that is how the functions
729
+ come back (xflow deployments functions lists them)
730
+ --project <id> with --revision only: the functions of that project, no folder needed
731
+ --app <id> with --revision only: that app, no folder needed
699
732
  --force allow overwriting the server revision while sending
700
733
  --allow-removals agree in advance to remove the functions gone from the sources
701
734
 
702
- The whole working copy goes up at once, as one revision. What is not sent: node_modules,
735
+ What goes up: an app sends its own folder; the project root sends everything but apps/,
736
+ plus the apps/<folder>/xflow.json files its callers are read from. Never sent: node_modules,
703
737
  .git, dist, build, .next, any .env, plus everything listed in .xflowignore and in the
704
738
  ignore field of xflow.json.
705
739
 
@@ -713,20 +747,21 @@ CLI shows whose work you are about to destroy and asks for confirmation by typin
713
747
  project name. In a non-interactive run (CI, an agent) there is no way to confirm: a
714
748
  version conflict has to fail the build rather than silently destroy somebody else's work.
715
749
 
716
- The platform builds, in a clean sandbox on one Node version for everybody, so "it
717
- worked on my machine" no longer depends on your machine. Before the build the project
718
- is checked against the template: mismatches are printed as a list and the build does
719
- not start at all.
720
-
721
- Everything in ${(0, ui_1.bold)('functions/<name>/index.ts')} is bundled and shipped by the same run,
722
- before the application is built. That order is not a convention but the only one that
723
- works: the addresses of the functions are baked into the bundle, so they have to exist
724
- first. A function whose code and variables did not change is left alone, and a function
725
- that fails to ship fails the whole build.
726
-
727
- A function gone from the sources would be deleted from the cloud along with its
728
- schedules, and that is final: one created again later gets a different address. So the
729
- build does not start at all until you say yes. In a terminal the CLI names the functions
750
+ The platform builds, in a clean sandbox on one Node version for everybody. Before that the
751
+ sources are checked: what breaks silently (the platform files, the calls in the entry point,
752
+ keys in the code, alert instead of a dialog) is printed as a list and nothing starts. What is
753
+ merely unfinished (the template page title, no icon) goes through and prints a line
754
+ ${(0, ui_1.bold)('Unfinished:')}. Fix it before telling the person the app is done.
755
+
756
+ ${(0, ui_1.bold)('deploy functions')} bundles everything in ${(0, ui_1.bold)('functions/<name>/index.ts')}. A function whose
757
+ code and variables did not change is left alone, and one that fails to ship fails the whole
758
+ deploy. It then records the callers and prints which functions are open to the whole
759
+ internet now. A change that touches data and screens goes in this order: the migration,
760
+ the functions, then every app that shows it; an app deploy warns when the root holds
761
+ changes nobody deployed.
762
+
763
+ A function gone from the sources would be deleted from the cloud along with its keys and
764
+ schedules, and that is final. So the deploy does not start at all until you say yes. In a terminal the CLI names the functions
730
765
  and asks; without one (CI, an agent) it stops and ${(0, ui_1.bold)('--allow-removals')} is the only way
731
766
  to agree. If the sources hold no functions at all while the cloud holds several, nothing
732
767
  is deleted and nothing is asked: that looks like a directory which never made it (a
@@ -735,60 +770,26 @@ ${(0, ui_1.bold)('functions/')} line in .xflowignore) rather than a decision.
735
770
  The database is not part of this: migrations change data in ways nothing can undo, so
736
771
  they stay their own command (${(0, ui_1.bold)('xflow db migrate')}).
737
772
 
738
- The built version is visible on the project page, and the CLI prints the link. Visitors
739
- see it after ${(0, ui_1.bold)('xflow publish')}. The address of the build itself is not printed:
740
- it carries the version number, and after the next publish such a link quietly serves an
741
- old copy.
742
-
743
- ${(0, ui_1.bold)('--revision <N>')} brings a version back. The sources of that revision are already on
744
- the server, so nothing goes up, no copy of them is needed next to you and the newest
745
- revision stays the newest: this build simply runs an older one. Since there is no folder
746
- to stand in, the project can be named by id (${(0, ui_1.bold)('--project')}), and for a standalone
747
- function that is the only way back: it has no pages, so ${(0, ui_1.bold)('xflow rollback')} has nothing
748
- to return. Functions that exist now but not in that revision would be removed, and the
749
- same question is asked as in any other deploy.`,
750
- init: `${(0, ui_1.bold)('xflow init')} [dir]: a new project
751
-
752
- Lays down the platform template: React on Vite, Tailwind, a set of components
753
- (${(0, ui_1.bold)('src/components/ui')}) and ready-made blocks (${(0, ui_1.bold)('src/components/blocks')}): app
754
- shell, page header, section, toolbar, list, empty state, stats, detail card, form rows,
755
- card grid, timeline, table, charts. Projects created in the web get the same template,
756
- which is what makes the applications look alike.
757
-
758
- Building the interface on top of these components and the tokens in ${(0, ui_1.bold)('src/index.css')}
759
- is not a formality: your own palette on top of them looks foreign inside the platform.
773
+ A build of an app is not shown to visitors until ${(0, ui_1.bold)('xflow publish')}: publishing points the
774
+ app address at a build and takes a second, and an older kept build is the rollback. Only
775
+ the app moves: the functions and the database are one per project and stay as they are.
776
+ The very first build of a new app publishes itself. The files of the three latest builds
777
+ are kept; ${(0, ui_1.bold)('xflow deployments')} marks the rest "no pages". Give a person the app address
778
+ the CLI prints, and name builds by their number.`,
779
+ init: `${(0, ui_1.bold)('xflow init')} [dir]: a new project with its first app
780
+
781
+ Creates the project on the platform and lays out its folder: the root xflow.json, a
782
+ package.json for the functions, empty ${(0, ui_1.bold)('functions/')} and ${(0, ui_1.bold)('migrations/')}, and the first app in
783
+ apps/<folder> from the platform template: React on Vite, Tailwind, components in
784
+ ${(0, ui_1.bold)('src/components/ui')} and blocks in ${(0, ui_1.bold)('src/components/blocks')}. Those are a starting set:
785
+ change them, restyle them or delete what you do not use.
760
786
 
761
787
  --name <name> project name (the folder name by default)
762
- --function a standalone function instead of an application
763
- --template <id> a different template (the list: xflow templates)
764
- --database <id> attach a logical database of the organization (the list: xflow db list)
765
- --server <name> a new database for the project, right away on that server:
788
+ --app <folder> the folder of the first app (made from the name by default)
789
+ --no-app a project without apps: its functions are workflows
790
+ --server <name> the database of the project, right away on that server:
766
791
  platform, or an own server of the organization (xflow db list)
767
792
 
768
- ${(0, ui_1.bold)('--function')} makes a shared backend of the organization: a project without a site,
769
- with one handler in ${(0, ui_1.bold)('functions/<name>/index.ts')} and no template to fetch. Any
770
- application of the organization calls it; who exactly is allowed in is decided by
771
- ${(0, ui_1.bold)('minRole')} inside the function. It builds and releases with the same deploy, but
772
- there are no pages to publish, so publish and rollback do not apply to it.
773
-
774
- Without either flag the project gets its database with the first migration, on the
775
- platform server. ${(0, ui_1.bold)('--server')} decides it up front: the server has to be running, and
776
- the database counts against the plan at once.
777
-
778
- The order: the project is created on the platform first, because without it there is
779
- nowhere to get the token for .env. If writing the files fails, the project stays empty
780
- and the CLI explains how to pick it up with the link command.`,
781
- rollback: `${(0, ui_1.bold)('xflow rollback')} <version number>: serve the pages of an earlier build
782
-
783
- Points the project at the pages of that build. ${(0, ui_1.bold)('Only the pages come back.')} Cloud
784
- functions and the database are one per project: they are not versioned, they are shared
785
- with the published application, and they stay exactly as they are now. A page from August
786
- will be talking to today's functions.
787
-
788
- The sources stay at their own revision too. Fetching the code of that build is a separate
789
- command (${(0, ui_1.bold)('xflow pull --revision N --into ../old-version')}, keep the copy outside the
790
- project folder). Building that code over the current one is a further, deliberate step: it
791
- replaces the server revision, and whatever is newer on the platform goes away.
792
-
793
- Visitors keep seeing the published pages until ${(0, ui_1.bold)('xflow publish')} is run.`,
793
+ More apps later: ${(0, ui_1.bold)('xflow apps add <folder>')}. If writing the files fails, the project stays
794
+ on the platform and the CLI says how to pick it up with the link command.`,
794
795
  };