@getxflow/cli 0.34.2 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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]
@@ -99,11 +108,13 @@ ${(0, ui_1.bold)('Pages')}
99
108
  upload the site: live at once, there is no publish step
100
109
  xflow pages status <page> the page and its versions
101
110
  xflow pages rollback <page> <N> put an earlier version back on the address
111
+ xflow pages rm <page> --yes delete the page for good: the address empties
102
112
 
103
113
  ${(0, ui_1.bold)('Reference')}
104
- xflow logs [--limit N] browser errors from the released application
114
+ xflow logs [--limit N] browser errors of the published apps
105
115
  xflow projects list projects of the organization
106
- xflow projects get [id] project card
116
+ xflow projects get [id] project card: apps, database, functions
117
+ xflow apps the apps of this project and their folders
107
118
  xflow org organizations with a stored key, the active one marked
108
119
  xflow org members who is in the organization, and with which role
109
120
  xflow org switch <name|id> make another organization the active one
@@ -115,6 +126,7 @@ ${(0, ui_1.bold)('Reference')}
115
126
  ${(0, ui_1.bold)('Outside a project folder')}
116
127
  --project <id> name the project by id, for every command that can
117
128
  answer without the local files (xflow help project)
129
+ --app <id> name an app by id: deploy --revision, publish, deployments, logs
118
130
 
119
131
  ${(0, ui_1.bold)('Environment')}
120
132
  XFLOW_TOKEN access key (for CI, instead of xflow login)
@@ -168,19 +180,23 @@ Which commands: ${(0, ui_1.bold)('db status')}, ${(0, ui_1.bold)('db query')}, $
168
180
  ${(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
181
  ${(0, ui_1.bold)('connections')}, ${(0, ui_1.bold)('connections link')}, ${(0, ui_1.bold)('connections unlink')}, ${(0, ui_1.bold)('schedules')},
170
182
  ${(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
183
+ ${(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
184
  them answer with less under the flag: ${(0, ui_1.bold)('db status')} gives the applied history alone,
173
185
  because comparing it against the local ${(0, ui_1.bold)('migrations/')} directory needs the folder,
174
186
  ${(0, ui_1.bold)('connections unlink')} removes the link without naming the functions that read
175
187
  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.
188
+ ${(0, ui_1.bold)('--revision')}: that deploy runs a revision of the functions the platform already holds,
189
+ so there is nothing to send and no folder to send it from.
190
+
191
+ ${(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')},
192
+ ${(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
193
+ flags never go together.
179
194
 
180
195
  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')},
196
+ 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
197
  ${(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:
198
+ 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
199
+ ${(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
200
  they name the project by its id as an argument.
185
201
 
186
202
  The folder is then ignored whole, and that includes its ${(0, ui_1.bold)('xflow.json')}: the address
@@ -198,13 +214,35 @@ With several organizations signed in, the one holding the project is looked for
198
214
  every call: there is no folder to write the answer into, and a second place to
199
215
  remember it would be a second thing to keep true. When most calls go to one project,
200
216
  ${(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.
217
+ projects: `${(0, ui_1.bold)('xflow projects')} and ${(0, ui_1.bold)('xflow apps')}: projects and their apps
218
+
219
+ A project is a box: one database with its migrations, the cloud functions, secrets and
220
+ members, and no code of its own. An app is code the platform builds and serves at
221
+ https://<id>--<org>.xflowpage.ru, with its own versions and access level. One folder per
222
+ project, apps in subfolders:
223
+
224
+ xflow.json projectId, and who may call each function
225
+ package.json dependencies of the functions
226
+ functions/<name>/index.ts cloud functions, shared by every app
227
+ migrations/0001_init.sql the one database of the project
228
+ apps/<folder>/ an app: a Vite project with its own xflow.json (appId)
229
+
230
+ xflow projects list every project, its apps count and age
231
+ xflow projects get [id] one card: apps, database, functions and callers, schedules
232
+ xflow apps the apps of this project, their folders and addresses
233
+ --json as JSON, with the app ids
234
+ xflow apps add <folder> a new app, laid into apps/<folder> from the template
235
+ --name <name> its name (the folder by default)
236
+ --color <color> its tile color (the platform picks one by default)
237
+ xflow status in the root: the root and every app; in an app: that app
238
+
239
+ The root and every app are separate histories: apps/** never travels with the root, the
240
+ root never travels with an app. The app is its appId, so a folder can be renamed freely.
241
+ Who opens an app (members of the project, a list, anyone with the link) is set by a person
242
+ in the web interface, not here.
243
+
244
+ Both ${(0, ui_1.bold)('projects')} commands name the project positionally: the id is an argument, not a
245
+ --project flag. Without one, ${(0, ui_1.bold)('get')} answers about the project of the folder it stands in.
208
246
 
209
247
  An id typed by hand is looked for across every signed-in organization, and the
210
248
  folder around the command is ignored whole, the way the ${(0, ui_1.bold)('--project')} flag does it
@@ -218,14 +256,14 @@ projects these are.
218
256
  ${(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
257
  link: `${(0, ui_1.bold)('xflow link')} <id>: bind this folder to a project
220
258
 
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.
259
+ Writes the root xflow.json and .xflow/state.json and lays down the instructions for the
260
+ agents. The id comes from ${(0, ui_1.bold)('xflow projects list')}; a project of another signed-in
261
+ organization is found too, and the folder is bound to that organization.
224
262
 
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.
263
+ An empty folder (.git alone counts as empty) gets the whole project: the latest revision
264
+ of the root and of every app, each app in apps/<folder> under the folder name the project
265
+ revision records. A folder with files keeps them as they are: ${(0, ui_1.bold)('xflow status')} says
266
+ how they differ, ${(0, ui_1.bold)('xflow pull --into ./server-copy')} fetches the whole project next to them.
229
267
 
230
268
  In a folder that is already linked, the command points xflow.json at the other project and
231
269
  keeps the rest of the file. A new project instead of an existing one: ${(0, ui_1.bold)('xflow init')}.`,
@@ -233,8 +271,8 @@ keeps the rest of the file. A new project instead of an existing one: ${(0, ui_1
233
271
 
234
272
  A page is a static site at https://<name>.xflowpage.ru: a landing page, a presentation, an
235
273
  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.
274
+ signing in, and has no build, no publish step and no database. An app is the opposite: code
275
+ the platform builds, with versions, access levels and a database through functions.
238
276
 
239
277
  xflow pages the pages, their state and address
240
278
  xflow pages create <title> <path> a new page with its first version: there are no
@@ -245,6 +283,8 @@ platform, for the members of the organization.
245
283
  -m <text> what changed, one short line: it names the version
246
284
  xflow pages status <page> the page and its versions
247
285
  xflow pages rollback <page> <N> put version N back on the address
286
+ xflow pages rm <page> delete the page
287
+ --yes without it the command names what goes and stops
248
288
 
249
289
  ${(0, ui_1.bold)('<path>')} is a folder with index.html at the top, its .zip, or one .html file.
250
290
  ${(0, ui_1.bold)('<page>')} is the name from the address or the id. ${(0, ui_1.bold)('--json')} on list and status
@@ -261,14 +301,20 @@ build it and upload what the build writes. Dot entries are skipped. At most 5 MB
261
301
  unpacked and 1000 files.
262
302
 
263
303
  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.
304
+ it. A page that needs a backend (a form, a lookup) calls ${(0, ui_1.bold)('/_fn/<name>')} on its own address,
305
+ and only a function whose callers in the root xflow.json of its project name the page answers:
306
+ add the page name there and run ${(0, ui_1.bold)('xflow deploy functions')}. The page has to exist first.
307
+ Such a function is open to the whole internet. Search engines are asked not to index pages,
308
+ so do not promise SEO.
309
+
310
+ ${(0, ui_1.bold)('Deleting is final.')} The address goes empty and every version of the page is gone: there
311
+ is no undo and no copy on the platform side. The name stays reserved for the organization, so
312
+ the same ${(0, ui_1.bold)('--slug')} is free for the next page of yours and taken for everyone else. A
313
+ throwaway page put up to try something is worth deleting the same way it was made.
268
314
 
269
315
  A page takes the key of the whole organization, the one ${(0, ui_1.bold)('xflow login')} issues: a key
270
- scoped to projects is refused. Switching a page off and deleting it are for a person, in the
271
- platform interface.`,
316
+ scoped to projects is refused. A page is deleted by whoever made it, or by the owner of the
317
+ organization. Switching a page off for a while is for a person, in the platform interface.`,
272
318
  update: `${(0, ui_1.bold)('xflow update')}: bring the CLI up to date
273
319
 
274
320
  Installs the published version and, if anything changed, rewrites the platform
@@ -295,40 +341,35 @@ will work.`,
295
341
  db: `${(0, ui_1.bold)('xflow db')}: schema, data, migrations
296
342
 
297
343
  xflow db status what is applied, what is waiting, the server
298
- xflow db list databases of the organization: servers, projects
344
+ xflow db list databases of the projects, the servers of the organization
299
345
  xflow db schema [table] tables of the project, or the columns of one
300
346
  xflow db query 'select ...' read data (--limit N: 50 by default, 300 at most;
301
347
  --json prints the values whole)
302
348
  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>
349
+ xflow db migrate store the project root, then apply migrations/*.sql
350
+ xflow db move --to <server> --confirm <project name>
305
351
  move the project database to another server
306
352
 
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')}.
353
+ Every project has exactly one database of its own, shared by all of its apps and functions,
354
+ and every command here works from any folder of the project. ${(0, ui_1.bold)('list')} is about the
355
+ organization: one database per project, and where each lives: ${(0, ui_1.bold)('platform')} is the common
356
+ server, ${(0, ui_1.bold)('own "<name>"')} an own server of the organization. While an own server is not
357
+ running, its databases do not answer, and that is not fixable from the code. An organization
358
+ with own servers also gets them listed, with the room used on each: that is the choice for
359
+ ${(0, ui_1.bold)('xflow init --server')} and ${(0, ui_1.bold)('db move')}.
316
360
 
317
361
  ${(0, ui_1.bold)('move')} takes the database of this project to another server of the organization:
318
362
  ${(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;
363
+ the tables, switches the functions of the project and removes the old copy; nothing needs a
364
+ deploy. While it copies, the apps read data but cannot save anything new, usually for a
365
+ couple of minutes, which is why ${(0, ui_1.bold)('--confirm')} takes the project name. It also needs the
366
+ right to move databases on the key: off by default, only its owner turns it on, in the
367
+ platform settings where the key permissions live. The command follows the move to the end;
325
368
  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.
369
+ that fails is rolled back. Buying, paying for and deleting servers stay with the person.
328
370
 
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
371
+ ${(0, ui_1.bold)('schema')} answers a different question than the ${(0, ui_1.bold)('migrations/')} directory: the files say
372
+ what you did, the schema says what is actually in there. ${(0, ui_1.bold)('query')} runs inside a READ ONLY transaction, so
332
373
  writes are rejected by the database itself, not by us reading your SQL. Single quotes around
333
374
  the query hold in PowerShell, bash and zsh, while cmd.exe wants double ones; a query that
334
375
  needs quotes of its own goes in a .sql file outside the project, because Windows PowerShell
@@ -338,10 +379,12 @@ long values to keep the columns readable; ${(0, ui_1.bold)('--json')} prints the
338
379
  the applied limit and its ceiling. ${(0, ui_1.bold)('status')} named by id (${(0, ui_1.bold)('--project')}) answers with
339
380
  the applied history alone: comparing against ${(0, ui_1.bold)('migrations/')} needs the folder.
340
381
 
341
- ${(0, ui_1.bold)('migrate')}: the files live in the repository (${(0, ui_1.bold)('migrations/0001_init.sql')},
382
+ ${(0, ui_1.bold)('migrate')}: the files live in the project root (${(0, ui_1.bold)('migrations/0001_init.sql')},
342
383
  ${(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.
384
+ is why the leading number is required. The command first stores the project root as a
385
+ revision, so the migrations stay in its history and a clone gets them, then applies what is
386
+ new. Every migration runs in its own transaction, the history is kept in the database
387
+ itself, and anything already applied is not run again.
345
388
 
346
389
  --dry-run report what would be applied without touching the database
347
390
  --allow-destructive allow operations that destroy data
@@ -353,21 +396,17 @@ access key. That right is off by default, and only its owner turns it on, in the
353
396
  platform settings, where the key permissions live: a flag is something an agent adds by itself, a
354
397
  right is not. With both in place, the contents of the affected tables are dumped and
355
398
  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.
399
+ ${(0, ui_1.bold)('DROP DATABASE')} is never allowed.
358
400
 
359
401
  Editing an already applied file achieves nothing: ${(0, ui_1.bold)('migrate')} refuses a name that is
360
402
  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.`,
403
+ separately. Write a new migration instead.`,
367
404
  logs: `${(0, ui_1.bold)('xflow logs')} and ${(0, ui_1.bold)('xflow functions logs')}: project errors
368
405
 
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.
406
+ Two streams: crashes of the cloud functions of the project, and browser errors of each
407
+ app. ${(0, ui_1.bold)('xflow logs')} in an app folder (or with ${(0, ui_1.bold)('--app <id>')}) shows that app, in the
408
+ project root every app, each row named by its folder. The last 200 entries of each stream
409
+ are kept, older ones fall off.
371
410
 
372
411
  --limit N how many to show (10 by default)
373
412
 
@@ -382,7 +421,7 @@ never gets as far as the wrapper), going over the execution time cap of the plan
382
421
  Those show up in the answer to ${(0, ui_1.bold)('xflow functions invoke')}.
383
422
 
384
423
  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.`,
424
+ only from the published addresses: a local ${(0, ui_1.bold)('xflow dev')} writes nothing here.`,
386
425
  storage: `${(0, ui_1.bold)('xflow storage')}: files of the application
387
426
 
388
427
  The sources archive is capped at 10 MB and is rebuilt and re-uploaded on every
@@ -449,9 +488,10 @@ variables literally. ${(0, ui_1.bold)('check')} names those reads and the build
449
488
  that quietly fails to arrive is found by the function falling over weeks later.
450
489
 
451
490
  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
491
+ ${(0, ui_1.bold)('env set')} run ${(0, ui_1.bold)('xflow deploy functions')}. It ships a function whose code did not
453
492
  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.`,
493
+ delete: a function already deployed keeps the old value until its next deploy. Apps get no
494
+ variables at all: whatever a secret protects is reached through a function.`,
455
495
  connections: `${(0, ui_1.bold)('xflow connections')}: accounts connected to the organization
456
496
 
457
497
  Somebody signs in to Yandex Metrika, Bitrix or a mail service once, in the platform,
@@ -487,8 +527,8 @@ linked to a service project without a folder has to be removable the same way.
487
527
  Variables are named after the alias: an OAuth connection called ${(0, ui_1.bold)('YANDEX_METRIKA')}
488
528
  gives ${(0, ui_1.bold)('YANDEX_METRIKA_TOKEN')}, a key-based one gives a variable per field. They
489
529
  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.
530
+ run ${(0, ui_1.bold)('xflow deploy functions')}. A token close to expiry is renewed by that deploy on the
531
+ way; a revoked one is not renewed by anything until a human reconnects the account.
492
532
 
493
533
  Linking needs a right on the key, ${(0, ui_1.bold)('connections:link')}. Keys are issued with it,
494
534
  and a person can take it away in the platform settings, where the key permissions live; a key cannot give
@@ -558,41 +598,38 @@ how Yandex works. ${(0, ui_1.bold)('The time is always UTC')}, local time is not
558
598
  ${(0, ui_1.bold)('list')} prints that body next to the schedule, so what a timer sends can be read
559
599
  back without setting it again.
560
600
 
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.
601
+ A scheduled run arrives at the handler as a POST with ${(0, ui_1.bold)("event.xflow.caller === 'schedule'")}:
602
+ the envelope is signed by the platform, so such an event cannot be forged from outside. A
603
+ function without callers that only a schedule wakes is a workflow, and a project without
604
+ apps is made of those. The function has to be deployed already, since the schedule points
605
+ at it and not the other way round.
564
606
 
565
607
  A failed run shows up in ${(0, ui_1.bold)('xflow functions logs')}: nobody is watching a scheduled
566
608
  function, so the wrapper reports a crash the same way it does on an ordinary call.`,
567
609
  functions: `${(0, ui_1.bold)('xflow functions')}: what is deployed, and calling it
568
610
 
569
- xflow functions list name, state, who may call it, age, address
570
- xflow functions invoke <name> call one the way the application does
611
+ xflow functions list name, state, callers, open, keys, last call and error
612
+ xflow functions invoke <name> call one as yourself, through the platform
571
613
  xflow functions logs [name] crashes: stack and console output
572
614
 
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.
615
+ Functions belong to the project and ship on their own step: ${(0, ui_1.bold)('xflow deploy functions')}
616
+ sends the project root and deploys everything in ${(0, ui_1.bold)('functions/<name>/index.ts')}. Every app
617
+ of the project runs the new code the moment it finishes; there is no publish step for them.
575
618
 
576
- ${(0, ui_1.bold)('list')} answers who may reach each function, and the code decides it:
619
+ Who may call a function is its callers, in the root xflow.json:
577
620
 
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'")}
621
+ { "projectId": "…", "functions": { "book-slot": { "callers": ["zapis", "clinic-landing"] } } }
581
622
 
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.
623
+ A caller is an app folder of this project (apps/zapis is "zapis") or the name of a page of
624
+ the organization. A function nobody lists has no callers: a workflow, woken by a schedule,
625
+ 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
626
+ caller is public (a page, or an app anyone with the link opens), so anyone on the internet
627
+ can call it. The address in the note under the table is for webhooks and other functions,
628
+ with a key; apps and pages call ${(0, ui_1.bold)('/_fn/<name>')} on their own address.
588
629
 
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.
592
-
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.
630
+ ${(0, ui_1.bold)('invoke')} calls it as the person who owns the key: the platform checks the role, gives
631
+ the function a one-minute ticket and calls it itself, so the function sees
632
+ ${(0, ui_1.bold)("event.xflow.caller === 'platform'")} and the person in ${(0, ui_1.bold)('event.xflow.user')}.
596
633
 
597
634
  --data '{"a":1}' request body (the method becomes POST by default)
598
635
  --data-file body.json the same body from a file
@@ -604,16 +641,17 @@ strips double quotes from a native call, so ${(0, ui_1.bold)('--data')} needs th
604
641
  (${(0, ui_1.bold)('--data \'{\\"a\\":1}\'')}) and a file needs nothing.
605
642
 
606
643
  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>')}.`,
644
+ alone. A non-zero exit code on 4xx and 5xx: in CI such a call has to fail the step. A
645
+ refusal of the guard names its reason in the body (${(0, ui_1.bold)('not_bound')}, ${(0, ui_1.bold)('no_access')},
646
+ ${(0, ui_1.bold)('functions_stopped')}); for a crash the hint points at ${(0, ui_1.bold)('xflow functions logs <name>')}.`,
609
647
  mcp: `${(0, ui_1.bold)('xflow mcp install')}: connect the agent to the platform directly
610
648
 
611
649
  Writes the MCP server into the configuration of a client that has its own connect
612
650
  command: today that is Claude Code, and for the rest the command prints what to enter
613
651
  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.
652
+ database, applies migrations, calls functions, sets schedules, publishes app builds, reads
653
+ logs. The commands stay: code, builds and functions deploys are still done by the CLI,
654
+ because pushing sources through MCP means filling the agent context and spending your money.
617
655
 
618
656
  The command takes the access key from your own login and does not print it: usually
619
657
  the agent runs this command itself, and everything printed lands in its context and in
@@ -653,53 +691,57 @@ Plus a few pointer lines in ${(0, ui_1.bold)('AGENTS.md')}. A skill is picked up
653
691
  description matches the task, and "add a customers table" will not trigger it. AGENTS.md
654
692
  is always read by the agent, which is why the pointer is appended there, at the end of
655
693
  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
657
-
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.
694
+ dev: `${(0, ui_1.bold)('xflow dev')}: run an app on this machine
663
695
 
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.
696
+ Runs ${(0, ui_1.bold)('npm run dev')} in the app folder it stands in (127.0.0.1:3000) with the access key
697
+ of the organization that holds the project. Every ${(0, ui_1.bold)('/_fn/<name>')} call of the app goes to
698
+ the platform as this app, so the callers, the access and the person calling are the same
699
+ as on its address. Nothing about functions is kept on this machine: a function someone
700
+ else shipped answers here at once.
668
701
 
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.
702
+ Use it instead of a bare ${(0, ui_1.bold)('npm run dev')}: that one is handed no key and no app id, so
703
+ its calls to functions fail. The platform address comes from the CLI too, so the key goes
704
+ to the platform that issued it and nowhere else. It never reaches the browser.
674
705
 
675
706
  The server listens on 127.0.0.1 and nowhere else, because it holds that key: on a
676
707
  shared network, anything else would let a neighbour call the functions of the project
677
708
  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
709
+ pull: `${(0, ui_1.bold)('xflow pull')} [functions|apps/<folder>]: fetch the sources
679
710
 
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.
711
+ Every app and the project root are separate histories, and pull fetches one of them: in an
712
+ app folder that app, in the project root the root (apps/ stays as it is), or the one named.
713
+ It refuses to write into a non-empty folder: the platform cannot merge changes, that is
714
+ git's job.
682
715
 
683
- --into <dir> unpack alongside, to compare
684
- --revision <N> a specific revision (the list: xflow deployments)
716
+ --into <dir> unpack alongside, to compare; in the project root with nothing named
717
+ it fetches the whole project there
718
+ --revision <N> a specific revision of that history (xflow deployments lists them)
685
719
  --force overwrite the folder completely`,
686
- deploy: `${(0, ui_1.bold)('xflow deploy')}: build and release
720
+ deploy: `${(0, ui_1.bold)('xflow deploy')}: deploy the functions or build an app, then publish
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
+ xflow deploy functions -m "…" send the project root, check it, ship the functions
723
+ and their callers
724
+ xflow deploy apps/<folder> -m "…" send one app and build it; inside its folder a plain
725
+ xflow deploy does the same
726
+ xflow publish [build] point the app address at the newest build, or a named one
727
+ xflow rollback <build> the same, the number required
728
+ xflow deployments [functions] the builds of the app, or the deploys of the functions
729
+
730
+ In the project root a plain ${(0, ui_1.bold)('xflow deploy')} refuses and names the targets: functions and
731
+ apps never go in one run, because a change of functions reaches every app at once.
691
732
 
692
733
  -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)
734
+ user speaks, saying what changed (--message is the long form)
695
735
  --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
736
+ --revision <N> run a revision the platform already holds: that is how the functions
737
+ come back (xflow deployments functions lists them)
738
+ --project <id> with --revision only: the functions of that project, no folder needed
739
+ --app <id> with --revision only: that app, no folder needed
699
740
  --force allow overwriting the server revision while sending
700
741
  --allow-removals agree in advance to remove the functions gone from the sources
701
742
 
702
- The whole working copy goes up at once, as one revision. What is not sent: node_modules,
743
+ What goes up: an app sends its own folder; the project root sends everything but apps/,
744
+ plus the apps/<folder>/xflow.json files its callers are read from. Never sent: node_modules,
703
745
  .git, dist, build, .next, any .env, plus everything listed in .xflowignore and in the
704
746
  ignore field of xflow.json.
705
747
 
@@ -713,20 +755,21 @@ CLI shows whose work you are about to destroy and asks for confirmation by typin
713
755
  project name. In a non-interactive run (CI, an agent) there is no way to confirm: a
714
756
  version conflict has to fail the build rather than silently destroy somebody else's work.
715
757
 
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
758
+ The platform builds, in a clean sandbox on one Node version for everybody. Before that the
759
+ sources are checked: what breaks silently (the platform files, the calls in the entry point,
760
+ keys in the code, alert instead of a dialog) is printed as a list and nothing starts. What is
761
+ merely unfinished (the template page title, no icon) goes through and prints a line
762
+ ${(0, ui_1.bold)('Unfinished:')}. Fix it before telling the person the app is done.
763
+
764
+ ${(0, ui_1.bold)('deploy functions')} bundles everything in ${(0, ui_1.bold)('functions/<name>/index.ts')}. A function whose
765
+ code and variables did not change is left alone, and one that fails to ship fails the whole
766
+ deploy. It then records the callers and prints which functions are open to the whole
767
+ internet now. A change that touches data and screens goes in this order: the migration,
768
+ the functions, then every app that shows it; an app deploy warns when the root holds
769
+ changes nobody deployed.
770
+
771
+ A function gone from the sources would be deleted from the cloud along with its keys and
772
+ 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
773
  and asks; without one (CI, an agent) it stops and ${(0, ui_1.bold)('--allow-removals')} is the only way
731
774
  to agree. If the sources hold no functions at all while the cloud holds several, nothing
732
775
  is deleted and nothing is asked: that looks like a directory which never made it (a
@@ -735,60 +778,26 @@ ${(0, ui_1.bold)('functions/')} line in .xflowignore) rather than a decision.
735
778
  The database is not part of this: migrations change data in ways nothing can undo, so
736
779
  they stay their own command (${(0, ui_1.bold)('xflow db migrate')}).
737
780
 
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.
781
+ A build of an app is not shown to visitors until ${(0, ui_1.bold)('xflow publish')}: publishing points the
782
+ app address at a build and takes a second, and an older kept build is the rollback. Only
783
+ the app moves: the functions and the database are one per project and stay as they are.
784
+ The very first build of a new app publishes itself. The files of the three latest builds
785
+ are kept; ${(0, ui_1.bold)('xflow deployments')} marks the rest "no pages". Give a person the app address
786
+ the CLI prints, and name builds by their number.`,
787
+ init: `${(0, ui_1.bold)('xflow init')} [dir]: a new project with its first app
788
+
789
+ Creates the project on the platform and lays out its folder: the root xflow.json, a
790
+ package.json for the functions, empty ${(0, ui_1.bold)('functions/')} and ${(0, ui_1.bold)('migrations/')}, and the first app in
791
+ apps/<folder> from the platform template: React on Vite, Tailwind, components in
792
+ ${(0, ui_1.bold)('src/components/ui')} and blocks in ${(0, ui_1.bold)('src/components/blocks')}. Those are a starting set:
793
+ change them, restyle them or delete what you do not use.
760
794
 
761
795
  --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:
796
+ --app <folder> the folder of the first app (made from the name by default)
797
+ --no-app a project without apps: its functions are workflows
798
+ --server <name> the database of the project, right away on that server:
766
799
  platform, or an own server of the organization (xflow db list)
767
800
 
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.`,
801
+ More apps later: ${(0, ui_1.bold)('xflow apps add <folder>')}. If writing the files fails, the project stays
802
+ on the platform and the CLI says how to pick it up with the link command.`,
794
803
  };