@getxflow/cli 0.34.1 → 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,9 +92,9 @@ ${(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
- xflow db query "select ..." read data, in a read-only transaction
97
+ xflow db query 'select ...' read data, in a read-only transaction
89
98
  xflow db migrate [--dry-run] [--allow-destructive]
90
99
  apply the migrations from migrations/*.sql
91
100
  xflow db move --to <server> --confirm <database>
@@ -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)
@@ -161,26 +172,30 @@ check ${(0, ui_1.bold)('xflow org')}.`,
161
172
  Every command that only talks to the platform takes it, so a service project needs
162
173
  no working copy of its own:
163
174
 
164
- xflow db query --project 3f2a… "select id, title from tasks where done = false"
175
+ xflow db query --project 3f2a… 'select id, title from tasks where done = false'
165
176
  xflow functions invoke kanban --project 3f2a… --data-file card.json
166
177
 
167
178
  Which commands: ${(0, ui_1.bold)('db status')}, ${(0, ui_1.bold)('db query')}, ${(0, ui_1.bold)('db schema')}, ${(0, ui_1.bold)('db move')}, ${(0, ui_1.bold)('functions list')},
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,51 +333,50 @@ 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
- xflow db query "select ..." read data (--limit N: 50 by default, 300 at most;
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.
328
-
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
332
- writes are rejected by the database itself, not by us reading your SQL. A query with
333
- double quotes goes in a .sql file outside the project: Windows PowerShell strips them from
334
- arguments, and a file in migrations/ would be taken for a migration. Its table cuts
361
+ that fails is rolled back. Buying, paying for and deleting servers stay with the person.
362
+
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
365
+ writes are rejected by the database itself, not by us reading your SQL. Single quotes around
366
+ the query hold in PowerShell, bash and zsh, while cmd.exe wants double ones; a query that
367
+ needs quotes of its own goes in a .sql file outside the project, because Windows PowerShell
368
+ strips double quotes from arguments and a file in migrations/ would be taken for a
369
+ migration. Its table cuts
335
370
  long values to keep the columns readable; ${(0, ui_1.bold)('--json')} prints them whole, together with
336
371
  the applied limit and its ceiling. ${(0, ui_1.bold)('status')} named by id (${(0, ui_1.bold)('--project')}) answers with
337
372
  the applied history alone: comparing against ${(0, ui_1.bold)('migrations/')} needs the folder.
338
373
 
339
- ${(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')},
340
375
  ${(0, ui_1.bold)('migrations/0002_orders.sql')} and so on). The order comes from the file name, which
341
- is why the leading number is required. Every migration runs in its own transaction, the
342
- 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.
343
380
 
344
381
  --dry-run report what would be applied without touching the database
345
382
  --allow-destructive allow operations that destroy data
@@ -351,21 +388,17 @@ access key. That right is off by default, and only its owner turns it on, in the
351
388
  platform settings, where the key permissions live: a flag is something an agent adds by itself, a
352
389
  right is not. With both in place, the contents of the affected tables are dumped and
353
390
  kept for 7 days: that is a "caught it right away" safety net, not a backup.
354
- ${(0, ui_1.bold)('DROP DATABASE')} is never allowed, because the database is shared across the
355
- organization.
391
+ ${(0, ui_1.bold)('DROP DATABASE')} is never allowed.
356
392
 
357
393
  Editing an already applied file achieves nothing: ${(0, ui_1.bold)('migrate')} refuses a name that is
358
394
  already applied with different content, and ${(0, ui_1.bold)('xflow db status')} lists such a file
359
- separately. Write a new migration instead.
360
-
361
- One logical database can be attached to several projects, so the history may hold
362
- migrations that are not in your repository. That is normal, but it also means your
363
- migration can break somebody else's application, and that a name already in that history
364
- 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.`,
365
396
  logs: `${(0, ui_1.bold)('xflow logs')} and ${(0, ui_1.bold)('xflow functions logs')}: project errors
366
397
 
367
- One stream per project: crashes of the released application in the browser and
368
- 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.
369
402
 
370
403
  --limit N how many to show (10 by default)
371
404
 
@@ -380,7 +413,7 @@ never gets as far as the wrapper), going over the execution time cap of the plan
380
413
  Those show up in the answer to ${(0, ui_1.bold)('xflow functions invoke')}.
381
414
 
382
415
  Browser errors are collected by ${(0, ui_1.bold)('src/utils/error-logger.ts')} of the template and
383
- 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.`,
384
417
  storage: `${(0, ui_1.bold)('xflow storage')}: files of the application
385
418
 
386
419
  The sources archive is capped at 10 MB and is rebuilt and re-uploaded on every
@@ -447,9 +480,10 @@ variables literally. ${(0, ui_1.bold)('check')} names those reads and the build
447
480
  that quietly fails to arrive is found by the function falling over weeks later.
448
481
 
449
482
  The value reaches the function on deploy, not at the moment it is stored: after
450
- ${(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
451
484
  change but whose variables did, so nothing is left holding an old value. The same after a
452
- 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.`,
453
487
  connections: `${(0, ui_1.bold)('xflow connections')}: accounts connected to the organization
454
488
 
455
489
  Somebody signs in to Yandex Metrika, Bitrix or a mail service once, in the platform,
@@ -485,8 +519,8 @@ linked to a service project without a folder has to be removable the same way.
485
519
  Variables are named after the alias: an OAuth connection called ${(0, ui_1.bold)('YANDEX_METRIKA')}
486
520
  gives ${(0, ui_1.bold)('YANDEX_METRIKA_TOKEN')}, a key-based one gives a variable per field. They
487
521
  arrive at a function on its next deploy, like every other variable, so after a change
488
- run ${(0, ui_1.bold)('xflow deploy')}. A token close to expiry is renewed by any build on the way;
489
- 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.
490
524
 
491
525
  Linking needs a right on the key, ${(0, ui_1.bold)('connections:link')}. Keys are issued with it,
492
526
  and a person can take it away in the platform settings, where the key permissions live; a key cannot give
@@ -556,41 +590,38 @@ how Yandex works. ${(0, ui_1.bold)('The time is always UTC')}, local time is not
556
590
  ${(0, ui_1.bold)('list')} prints that body next to the schedule, so what a timer sends can be read
557
591
  back without setting it again.
558
592
 
559
- A scheduled run arrives at the handler as a POST with no headers, and the project
560
- token is not checked on it: such an event cannot be forged from outside. The function
561
- 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.
562
598
 
563
599
  A failed run shows up in ${(0, ui_1.bold)('xflow functions logs')}: nobody is watching a scheduled
564
600
  function, so the wrapper reports a crash the same way it does on an ordinary call.`,
565
601
  functions: `${(0, ui_1.bold)('xflow functions')}: what is deployed, and calling it
566
602
 
567
- xflow functions list name, state, who may call it, age, address
568
- 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
569
605
  xflow functions logs [name] crashes: stack and console output
570
606
 
571
- There is no deploy command of their own: ${(0, ui_1.bold)('xflow deploy')} ships everything in
572
- ${(0, ui_1.bold)('functions/<name>/index.ts')} and then builds the application.
573
-
574
- ${(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.
575
610
 
576
- in-app only a visitor of the application, with a pass the platform issues
577
- external (N keys) plus an outside service holding a key a human issued in the interface
578
- 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:
579
612
 
580
- An open function is how a page on xflowpage.ru gets a backend: it answers without a pass
581
- and without a key, so the page calls the printed address directly and bakes no key into
582
- its files. Remove the line and deploy again to close it. The function that stays inside
583
- the application declares nothing, and ${(0, ui_1.bold)("exports.minRole = 'developer'")} raises the
584
- role the visitor needs. The address is printed for every deployed function; it is not a
585
- secret, the application bundle carries it.
613
+ { "projectId": "…", "functions": { "book-slot": { "callers": ["zapis", "clinic-landing"] } } }
586
614
 
587
- Under the table, when there are any: the standalone functions of the organization this
588
- project calls by the same name. Their code lives elsewhere, so reaching them from here
589
- 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.
590
621
 
591
- ${(0, ui_1.bold)('invoke')} calls it exactly the way the application does: the project token plus a
592
- visitor pass for the person who owns the key, so the function sees a real caller and its
593
- 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')}.
594
625
 
595
626
  --data '{"a":1}' request body (the method becomes POST by default)
596
627
  --data-file body.json the same body from a file
@@ -602,16 +633,17 @@ strips double quotes from a native call, so ${(0, ui_1.bold)('--data')} needs th
602
633
  (${(0, ui_1.bold)('--data \'{\\"a\\":1}\'')}) and a file needs nothing.
603
634
 
604
635
  Prints the status, the response time and the body; under ${(0, ui_1.bold)('--json')} the body
605
- alone. A non-zero exit code on 4xx and 5xx: in CI such a call has to fail the step.
606
- 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>')}.`,
607
639
  mcp: `${(0, ui_1.bold)('xflow mcp install')}: connect the agent to the platform directly
608
640
 
609
641
  Writes the MCP server into the configuration of a client that has its own connect
610
642
  command: today that is Claude Code, and for the rest the command prints what to enter
611
643
  by hand. After that the agent runs the platform without a terminal: it inspects the
612
- database, applies migrations, ships and calls functions, sets schedules, reads logs.
613
- The commands stay: code, builds and releases are still done by the CLI, because
614
- 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.
615
647
 
616
648
  The command takes the access key from your own login and does not print it: usually
617
649
  the agent runs this command itself, and everything printed lands in its context and in
@@ -651,53 +683,57 @@ Plus a few pointer lines in ${(0, ui_1.bold)('AGENTS.md')}. A skill is picked up
651
683
  description matches the task, and "add a customers table" will not trigger it. AGENTS.md
652
684
  is always read by the agent, which is why the pointer is appended there, at the end of
653
685
  the file and once. ${(0, ui_1.bold)('CLAUDE.md')} is left alone: Claude Code reads .claude/skills anyway.`,
654
- 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
655
687
 
656
- Runs ${(0, ui_1.bold)('npm run dev')} in the project folder (127.0.0.1:3000) with the access key of the
657
- organization that holds the project. The dev server trades that key for a visitor pass,
658
- so the cloud functions answer here the way they do on the platform. The key is picked
659
- as for every other command (${(0, ui_1.bold)('xflow help org')}): with several organizations signed
660
- 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.
661
693
 
662
- Use it instead of a bare ${(0, ui_1.bold)('npm run dev')}: that one is handed no key, so its calls to
663
- cloud functions fail. In a project made before this command the failure reads "No access
664
- key for the platform: run xflow login", and signing in will not help, because the key is
665
- no longer left in a file for that dev server to pick up.
666
-
667
- The platform address comes from the CLI too, not from .env, so the key goes to the
668
- platform that issued it and nowhere else. It never reaches the browser: the page gets
669
- only the ${(0, ui_1.bold)('VITE_')} variables. The project token and the function addresses are
670
- fetched on every start as well: a function someone else shipped answers here without a
671
- 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.
672
697
 
673
698
  The server listens on 127.0.0.1 and nowhere else, because it holds that key: on a
674
699
  shared network, anything else would let a neighbour call the functions of the project
675
700
  as you. A phone on the same Wi-Fi therefore cannot open it either.`,
676
- pull: `${(0, ui_1.bold)('xflow pull')}: fetch the sources
701
+ pull: `${(0, ui_1.bold)('xflow pull')} [functions|apps/<folder>]: fetch the sources
677
702
 
678
- By default it fetches the latest revision into the project folder and refuses to write
679
- 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.
680
707
 
681
- --into <dir> unpack alongside, to compare
682
- --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)
683
711
  --force overwrite the folder completely`,
684
- 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
685
721
 
686
- Three steps: sending the sources, shipping the cloud functions, building the application.
687
- The build command and the output directory come from xflow.json (npm run build and dist
688
- 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.
689
724
 
690
725
  -m <text> the version comment, required: one short line in the language the
691
- user speaks, saying what changed. It names the version in the
692
- history (--message is the long form)
726
+ user speaks, saying what changed (--message is the long form)
693
727
  --no-push do not send sources, build from the latest server revision
694
- --revision <N> build a revision the platform already holds: this is how a
695
- version comes back (the list: xflow deployments)
696
- --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
697
732
  --force allow overwriting the server revision while sending
698
733
  --allow-removals agree in advance to remove the functions gone from the sources
699
734
 
700
- 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,
701
737
  .git, dist, build, .next, any .env, plus everything listed in .xflowignore and in the
702
738
  ignore field of xflow.json.
703
739
 
@@ -711,20 +747,21 @@ CLI shows whose work you are about to destroy and asks for confirmation by typin
711
747
  project name. In a non-interactive run (CI, an agent) there is no way to confirm: a
712
748
  version conflict has to fail the build rather than silently destroy somebody else's work.
713
749
 
714
- The platform builds, in a clean sandbox on one Node version for everybody, so "it
715
- worked on my machine" no longer depends on your machine. Before the build the project
716
- is checked against the template: mismatches are printed as a list and the build does
717
- not start at all.
718
-
719
- Everything in ${(0, ui_1.bold)('functions/<name>/index.ts')} is bundled and shipped by the same run,
720
- before the application is built. That order is not a convention but the only one that
721
- works: the addresses of the functions are baked into the bundle, so they have to exist
722
- first. A function whose code and variables did not change is left alone, and a function
723
- that fails to ship fails the whole build.
724
-
725
- A function gone from the sources would be deleted from the cloud along with its
726
- schedules, and that is final: one created again later gets a different address. So the
727
- 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
728
765
  and asks; without one (CI, an agent) it stops and ${(0, ui_1.bold)('--allow-removals')} is the only way
729
766
  to agree. If the sources hold no functions at all while the cloud holds several, nothing
730
767
  is deleted and nothing is asked: that looks like a directory which never made it (a
@@ -733,60 +770,26 @@ ${(0, ui_1.bold)('functions/')} line in .xflowignore) rather than a decision.
733
770
  The database is not part of this: migrations change data in ways nothing can undo, so
734
771
  they stay their own command (${(0, ui_1.bold)('xflow db migrate')}).
735
772
 
736
- The built version is visible on the project page, and the CLI prints the link. Visitors
737
- see it after ${(0, ui_1.bold)('xflow publish')}. The address of the build itself is not printed:
738
- it carries the version number, and after the next publish such a link quietly serves an
739
- old copy.
740
-
741
- ${(0, ui_1.bold)('--revision <N>')} brings a version back. The sources of that revision are already on
742
- the server, so nothing goes up, no copy of them is needed next to you and the newest
743
- revision stays the newest: this build simply runs an older one. Since there is no folder
744
- to stand in, the project can be named by id (${(0, ui_1.bold)('--project')}), and for a standalone
745
- function that is the only way back: it has no pages, so ${(0, ui_1.bold)('xflow rollback')} has nothing
746
- to return. Functions that exist now but not in that revision would be removed, and the
747
- same question is asked as in any other deploy.`,
748
- init: `${(0, ui_1.bold)('xflow init')} [dir]: a new project
749
-
750
- Lays down the platform template: React on Vite, Tailwind, a set of components
751
- (${(0, ui_1.bold)('src/components/ui')}) and ready-made blocks (${(0, ui_1.bold)('src/components/blocks')}): app
752
- shell, page header, section, toolbar, list, empty state, stats, detail card, form rows,
753
- card grid, timeline, table, charts. Projects created in the web get the same template,
754
- which is what makes the applications look alike.
755
-
756
- Building the interface on top of these components and the tokens in ${(0, ui_1.bold)('src/index.css')}
757
- 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.
758
786
 
759
787
  --name <name> project name (the folder name by default)
760
- --function a standalone function instead of an application
761
- --template <id> a different template (the list: xflow templates)
762
- --database <id> attach a logical database of the organization (the list: xflow db list)
763
- --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:
764
791
  platform, or an own server of the organization (xflow db list)
765
792
 
766
- ${(0, ui_1.bold)('--function')} makes a shared backend of the organization: a project without a site,
767
- with one handler in ${(0, ui_1.bold)('functions/<name>/index.ts')} and no template to fetch. Any
768
- application of the organization calls it; who exactly is allowed in is decided by
769
- ${(0, ui_1.bold)('minRole')} inside the function. It builds and releases with the same deploy, but
770
- there are no pages to publish, so publish and rollback do not apply to it.
771
-
772
- Without either flag the project gets its database with the first migration, on the
773
- platform server. ${(0, ui_1.bold)('--server')} decides it up front: the server has to be running, and
774
- the database counts against the plan at once.
775
-
776
- The order: the project is created on the platform first, because without it there is
777
- nowhere to get the token for .env. If writing the files fails, the project stays empty
778
- and the CLI explains how to pick it up with the link command.`,
779
- rollback: `${(0, ui_1.bold)('xflow rollback')} <version number>: serve the pages of an earlier build
780
-
781
- Points the project at the pages of that build. ${(0, ui_1.bold)('Only the pages come back.')} Cloud
782
- functions and the database are one per project: they are not versioned, they are shared
783
- with the published application, and they stay exactly as they are now. A page from August
784
- will be talking to today's functions.
785
-
786
- The sources stay at their own revision too. Fetching the code of that build is a separate
787
- command (${(0, ui_1.bold)('xflow pull --revision N --into ../old-version')}, keep the copy outside the
788
- project folder). Building that code over the current one is a further, deliberate step: it
789
- replaces the server revision, and whatever is newer on the platform goes away.
790
-
791
- 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.`,
792
795
  };