@getxflow/cli 1.7.0 → 1.8.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
@@ -32,670 +32,674 @@ function help(topic) {
32
32
  // Falling through to the full help here is what this used to do, and it reads as an
33
33
  // answer: the agent asked about one command and got the whole screen back, with no
34
34
  // sign that the page it asked for does not exist.
35
- (0, ui_1.out)(`No page for "${topic}". Pages: ${Object.keys(TOPICS).sort().join(', ')}
35
+ (0, ui_1.out)(`No page for "${topic}". Pages: ${Object.keys(TOPICS).sort().join(', ')}
36
36
  Every command with a line of its own: xflow help`);
37
37
  return;
38
38
  }
39
- (0, ui_1.out)(`${(0, ui_1.bold)('xflow')} ${(0, ui_1.dim)(version_1.CLI_VERSION)} XFlow application hosting
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
-
44
- ${(0, ui_1.bold)('Getting started')}
45
- xflow login sign in through the browser
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
50
- xflow skills pick the agents that get the platform instructions
51
-
52
- ${(0, ui_1.bold)('Sources')}
53
- xflow status what is on the server and how the local copies differ
54
- xflow pull [functions|apps/<folder>] [--into dir] [--revision N]
55
- fetch the sources of the root or of one app
56
-
57
- ${(0, ui_1.bold)('Files')}
58
- xflow storage ls [folder] what the file storage of the project holds
59
- xflow storage push <dir> [--to folder] [--replace]
60
- upload heavy static assets: photos, video, PDFs
61
- xflow storage rm <address> delete one file
62
- xflow storage rm --folder <path> --yes
63
- delete a folder with everything in it
64
-
65
- ${(0, ui_1.bold)('Releasing')}
66
- xflow deploy functions -m "what changed"
67
- send the project root, ship the functions and their callers
68
- xflow deploy apps/<folder> -m "what changed"
69
- send one app and build it (a bare deploy inside its folder)
70
- xflow publish [build] point the app address at the newest build, or a named one
71
- xflow rollback <build> the same with an older build
72
- xflow deployments [functions] builds of the app, or deploys of the functions
73
-
74
- ${(0, ui_1.bold)('Functions')}
75
- xflow functions list what is deployed, its callers, open or not, last call
76
- xflow functions invoke <name> [--data '{"a":1}']
77
- call a function and print the answer
78
- xflow functions logs [name] function crashes and failed calls, 7 days
79
- xflow schedules list what runs on a timer
80
- xflow schedules set <name> "0 3 ? * * *"
81
- run a function on a schedule (UTC)
82
- xflow schedules rm <name> take a schedule off
83
- xflow env [check] function variables: what is stored, what is missing
84
- xflow env set NAME=value store a variable
85
- xflow env rm NAME delete a variable
86
- xflow connections connected accounts the project can use, and their variables
87
- xflow connections link <name> --as ALIAS
88
- give the functions the credentials of an account
89
- xflow connections unlink <ALIAS> [--yes]
90
- take them away again
91
-
92
- ${(0, ui_1.bold)('Database')}
93
- xflow db status which migrations are applied and which are waiting
94
- xflow db list databases of the projects of the organization, with sizes
95
- xflow db schema [table] tables of the project, or the columns of one
96
- xflow db query 'select ...' read data, in a read-only transaction
97
- xflow db exec 'update ...' a one-off data change, outside the migration history
98
- xflow db migrate [--dry-run] [--allow-destructive]
99
- apply the migrations from migrations/*.sql
100
-
101
- ${(0, ui_1.bold)('Reference')}
102
- xflow logs [--limit N] browser errors of the published apps
103
- xflow projects list projects of the organization
104
- xflow projects get [id] project card: apps, database, functions
105
- xflow apps the apps of this project and their folders
106
- xflow org organizations with a stored key, the active one marked
107
- xflow org members who is in the organization, and with which role
108
- xflow org switch <name|id> make another organization the active one
109
- xflow whoami whose key this is and what it can do
110
- xflow logout [--all] forget the key of the active organization (--all: every one)
111
- xflow update update the CLI itself, and the skill that ships with it
112
- xflow --version which version is installed
113
-
114
- ${(0, ui_1.bold)('Outside a project folder')}
115
- --project <id> name the project by id, for every command that can
116
- answer without the local files (xflow help project)
117
- --app <id> name an app by id: deploy --revision, publish, deployments, logs
118
-
119
- ${(0, ui_1.bold)('Environment')}
120
- XFLOW_TOKEN access key (for CI, instead of xflow login)
121
- XFLOW_API_URL platform address when it is not app.getxflow.com
122
-
123
- More about one command: xflow help <command>
39
+ (0, ui_1.out)(`${(0, ui_1.bold)('xflow')} ${(0, ui_1.dim)(version_1.CLI_VERSION)} XFlow application hosting
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
+
44
+ ${(0, ui_1.bold)('Getting started')}
45
+ xflow login sign in through the browser
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
50
+ xflow skills pick the agents that get the platform instructions
51
+
52
+ ${(0, ui_1.bold)('Sources')}
53
+ xflow status what is on the server and how the local copies differ
54
+ xflow pull [functions|apps/<folder>] [--into dir] [--revision N]
55
+ fetch the sources of the root or of one app
56
+
57
+ ${(0, ui_1.bold)('Files')}
58
+ xflow storage ls [folder] what the file storage of the project holds
59
+ xflow storage push <dir> [--to folder] [--replace]
60
+ upload heavy static assets: photos, video, PDFs
61
+ xflow storage rm <address> delete one file
62
+ xflow storage rm --folder <path> --yes
63
+ delete a folder with everything in it
64
+
65
+ ${(0, ui_1.bold)('Releasing')}
66
+ xflow deploy functions -m "what changed"
67
+ send the project root, ship the functions and their callers
68
+ xflow deploy apps/<folder> -m "what changed"
69
+ send one app and build it (a bare deploy inside its folder)
70
+ xflow publish [build] point the app address at the newest build, or a named one
71
+ xflow rollback <build> the same with an older build
72
+ xflow deployments [functions] builds of the app, or deploys of the functions
73
+
74
+ ${(0, ui_1.bold)('Functions')}
75
+ xflow functions list what is deployed, its callers, open or not, last call
76
+ xflow functions invoke <name> [--data '{"a":1}']
77
+ call a function and print the answer
78
+ xflow functions logs [name] function crashes and failed calls, 7 days
79
+ xflow schedules list what runs on a timer
80
+ xflow schedules set <name> "0 3 ? * * *"
81
+ run a function on a schedule (UTC)
82
+ xflow schedules rm <name> take a schedule off
83
+ xflow env [check] function variables: what is stored, what is missing
84
+ xflow env set NAME=value store a variable
85
+ xflow env rm NAME delete a variable
86
+ xflow connections connected accounts the project can use, and their variables
87
+ xflow connections link <name> --as ALIAS
88
+ give the functions the credentials of an account
89
+ xflow connections unlink <ALIAS> [--yes]
90
+ take them away again
91
+
92
+ ${(0, ui_1.bold)('Database')}
93
+ xflow db status which migrations are applied and which are waiting
94
+ xflow db list databases of the projects of the organization, with sizes
95
+ xflow db schema [table] tables of the project, or the columns of one
96
+ xflow db query 'select ...' read data, in a read-only transaction
97
+ xflow db exec 'update ...' a one-off data change, outside the migration history
98
+ xflow db logs errors the database raised, newest first
99
+ xflow db migrate [--dry-run] [--allow-destructive]
100
+ apply the migrations from migrations/*.sql
101
+
102
+ ${(0, ui_1.bold)('Reference')}
103
+ xflow logs [--limit N] browser errors of the published apps
104
+ xflow projects list projects of the organization
105
+ xflow projects get [id] project card: apps, database, functions
106
+ xflow apps the apps of this project and their folders
107
+ xflow org organizations with a stored key, the active one marked
108
+ xflow org members who is in the organization, and with which role
109
+ xflow org switch <name|id> make another organization the active one
110
+ xflow whoami whose key this is and what it can do
111
+ xflow logout [--all] forget the key of the active organization (--all: every one)
112
+ xflow update update the CLI itself, and the skill that ships with it
113
+ xflow --version which version is installed
114
+
115
+ ${(0, ui_1.bold)('Outside a project folder')}
116
+ --project <id> name the project by id, for every command that can
117
+ answer without the local files (xflow help project)
118
+ --app <id> name an app by id: deploy --revision, publish, deployments, logs
119
+
120
+ ${(0, ui_1.bold)('Environment')}
121
+ XFLOW_TOKEN access key (for CI, instead of xflow login)
122
+ XFLOW_API_URL platform address when it is not app.getxflow.com
123
+
124
+ More about one command: xflow help <command>
124
125
  Documentation: https://docs.getxflow.com (for agents: https://docs.getxflow.com/llms.txt)`);
125
126
  }
126
127
  const TOPICS = {
127
- org: `${(0, ui_1.bold)('xflow org')}: several organizations, one terminal
128
-
129
- One key per organization: every ${(0, ui_1.bold)('xflow login')} stores the key of the organization
130
- chosen in the browser next to the ones already stored, it does not replace them.
131
- Which key a command then uses, in order:
132
-
133
- 1. XFLOW_TOKEN, when set (and only for the default platform address)
134
- 2. the organization this folder is bound to (${(0, ui_1.bold)('.xflow/state.json')}, written by
135
- init, link and the first successful deploy or pull)
136
- 3. the active organization
137
-
138
- xflow org the stored organizations, the active one marked
139
- xflow org members who is in this one, and with which role
140
- xflow org switch <name|id> make another one active, no browser involved
141
- xflow logout forget the key of the active organization
142
- xflow logout --all forget every key of this platform address
143
-
144
- ${(0, ui_1.bold)('members')} reads only. Inviting people, changing a role and removing somebody stay
145
- in the platform interface, with the owner: there is no command for any of them.
146
-
147
- The switch is local: it changes which stored key is used and nothing happens on
148
- the platform. A folder bound to an organization is not affected, its commands
149
- stay in its own organization: two projects of two organizations in two terminals
150
- work without switching anything.
151
-
152
- A project id is unique across the whole platform, so "project not found" under
153
- the wrong organization can never touch somebody else's project. When that error
154
- names a project you know exists, the key is simply from another organization:
128
+ org: `${(0, ui_1.bold)('xflow org')}: several organizations, one terminal
129
+
130
+ One key per organization: every ${(0, ui_1.bold)('xflow login')} stores the key of the organization
131
+ chosen in the browser next to the ones already stored, it does not replace them.
132
+ Which key a command then uses, in order:
133
+
134
+ 1. XFLOW_TOKEN, when set (and only for the default platform address)
135
+ 2. the organization this folder is bound to (${(0, ui_1.bold)('.xflow/state.json')}, written by
136
+ init, link and the first successful deploy or pull)
137
+ 3. the active organization
138
+
139
+ xflow org the stored organizations, the active one marked
140
+ xflow org members who is in this one, and with which role
141
+ xflow org switch <name|id> make another one active, no browser involved
142
+ xflow logout forget the key of the active organization
143
+ xflow logout --all forget every key of this platform address
144
+
145
+ ${(0, ui_1.bold)('members')} reads only. Inviting people, changing a role and removing somebody stay
146
+ in the platform interface, with the owner: there is no command for any of them.
147
+
148
+ The switch is local: it changes which stored key is used and nothing happens on
149
+ the platform. A folder bound to an organization is not affected, its commands
150
+ stay in its own organization: two projects of two organizations in two terminals
151
+ work without switching anything.
152
+
153
+ A project id is unique across the whole platform, so "project not found" under
154
+ the wrong organization can never touch somebody else's project. When that error
155
+ names a project you know exists, the key is simply from another organization:
155
156
  check ${(0, ui_1.bold)('xflow org')}.`,
156
- project: `${(0, ui_1.bold)('--project <id>')}: a project without its folder
157
-
158
- Every command that only talks to the platform takes it, so a service project needs
159
- no working copy of its own:
160
-
161
- xflow db query --project 3f2a… 'select id, title from tasks where done = false'
162
- xflow functions invoke kanban --project 3f2a… --data-file card.json
163
-
164
- Which commands: ${(0, ui_1.bold)('db status')}, ${(0, ui_1.bold)('db query')}, ${(0, ui_1.bold)('db exec')}, ${(0, ui_1.bold)('db schema')}, ${(0, ui_1.bold)('functions list')},
165
- ${(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')},
166
- ${(0, ui_1.bold)('connections')}, ${(0, ui_1.bold)('connections link')}, ${(0, ui_1.bold)('connections unlink')}, ${(0, ui_1.bold)('schedules')},
167
- ${(0, ui_1.bold)('schedules set')}, ${(0, ui_1.bold)('schedules rm')}, ${(0, ui_1.bold)('storage ls')}, ${(0, ui_1.bold)('storage push')},
168
- ${(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
169
- them answer with less under the flag: ${(0, ui_1.bold)('db status')} gives the applied history alone,
170
- because comparing it against the local ${(0, ui_1.bold)('migrations/')} directory needs the folder,
171
- ${(0, ui_1.bold)('connections unlink')} removes the link without naming the functions that read
172
- its variables, for the same reason, and ${(0, ui_1.bold)('deploy')} takes the flag only together with
173
- ${(0, ui_1.bold)('--revision')}: that deploy runs a revision of the functions the platform already holds,
174
- so there is nothing to send and no folder to send it from.
175
-
176
- ${(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')},
177
- ${(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
178
- flags never go together.
179
-
180
- 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)('apps add')}, ${(0, ui_1.bold)('db migrate')},
182
- ${(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)('publish')} and ${(0, ui_1.bold)('rollback')} take ${(0, ui_1.bold)('--app')} rather than
184
- ${(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:
185
- they name the project by its id as an argument.
186
-
187
- The folder is then ignored whole, and that includes its ${(0, ui_1.bold)('xflow.json')}: the address
188
- is app.getxflow.com, or ${(0, ui_1.bold)('XFLOW_API_URL')} when it is set, and never the ${(0, ui_1.bold)('api')} field
189
- of a config that happens to be lying around. Standing in the folder of a self-hosted
190
- project, the flag therefore goes somewhere else than the folder does. An id handed to
191
- ${(0, ui_1.bold)('projects get')} or ${(0, ui_1.bold)('link')} behaves the same way: typed by hand, it is not redirected
192
- by a config lying around either.
193
-
194
- The id is echoed back on every run, with the organization that holds the project. It
195
- is the one place a project is named by hand, and a wrong id answers "not found" on a
196
- read but works on a write.
197
-
198
- With several organizations signed in, the one holding the project is looked for on
199
- every call: there is no folder to write the answer into, and a second place to
200
- remember it would be a second thing to keep true. When most calls go to one project,
157
+ project: `${(0, ui_1.bold)('--project <id>')}: a project without its folder
158
+
159
+ Every command that only talks to the platform takes it, so a service project needs
160
+ no working copy of its own:
161
+
162
+ xflow db query --project 3f2a… 'select id, title from tasks where done = false'
163
+ xflow functions invoke kanban --project 3f2a… --data-file card.json
164
+
165
+ Which commands: ${(0, ui_1.bold)('db status')}, ${(0, ui_1.bold)('db query')}, ${(0, ui_1.bold)('db exec')}, ${(0, ui_1.bold)('db schema')}, ${(0, ui_1.bold)('functions list')},
166
+ ${(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')},
167
+ ${(0, ui_1.bold)('connections')}, ${(0, ui_1.bold)('connections link')}, ${(0, ui_1.bold)('connections unlink')}, ${(0, ui_1.bold)('schedules')},
168
+ ${(0, ui_1.bold)('schedules set')}, ${(0, ui_1.bold)('schedules rm')}, ${(0, ui_1.bold)('storage ls')}, ${(0, ui_1.bold)('storage push')},
169
+ ${(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
170
+ them answer with less under the flag: ${(0, ui_1.bold)('db status')} gives the applied history alone,
171
+ because comparing it against the local ${(0, ui_1.bold)('migrations/')} directory needs the folder,
172
+ ${(0, ui_1.bold)('connections unlink')} removes the link without naming the functions that read
173
+ its variables, for the same reason, and ${(0, ui_1.bold)('deploy')} takes the flag only together with
174
+ ${(0, ui_1.bold)('--revision')}: that deploy runs a revision of the functions the platform already holds,
175
+ so there is nothing to send and no folder to send it from.
176
+
177
+ ${(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')},
178
+ ${(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
179
+ flags never go together.
180
+
181
+ The rest cannot answer without the working copy and have nothing to do with an id
182
+ 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')},
183
+ ${(0, ui_1.bold)('env check')}, ${(0, ui_1.bold)('env set')}. They answer about the files next to them, so
184
+ 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
185
+ ${(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:
186
+ they name the project by its id as an argument.
187
+
188
+ The folder is then ignored whole, and that includes its ${(0, ui_1.bold)('xflow.json')}: the address
189
+ is app.getxflow.com, or ${(0, ui_1.bold)('XFLOW_API_URL')} when it is set, and never the ${(0, ui_1.bold)('api')} field
190
+ of a config that happens to be lying around. Standing in the folder of a self-hosted
191
+ project, the flag therefore goes somewhere else than the folder does. An id handed to
192
+ ${(0, ui_1.bold)('projects get')} or ${(0, ui_1.bold)('link')} behaves the same way: typed by hand, it is not redirected
193
+ by a config lying around either.
194
+
195
+ The id is echoed back on every run, with the organization that holds the project. It
196
+ is the one place a project is named by hand, and a wrong id answers "not found" on a
197
+ read but works on a write.
198
+
199
+ With several organizations signed in, the one holding the project is looked for on
200
+ every call: there is no folder to write the answer into, and a second place to
201
+ remember it would be a second thing to keep true. When most calls go to one project,
201
202
  ${(0, ui_1.bold)('xflow org switch')} to its organization skips that search.`,
202
- projects: `${(0, ui_1.bold)('xflow projects')} and ${(0, ui_1.bold)('xflow apps')}: projects and their apps
203
-
204
- A project is a box: one database with its migrations, the cloud functions, secrets and
205
- members, and no code of its own. An app is code the platform builds and serves at
206
- https://<id>--<org>.xflowpage.ru, with its own versions and access level. One folder per
207
- project, apps in subfolders:
208
-
209
- xflow.json projectId, and who may call each function
210
- package.json dependencies of the functions
211
- functions/<name>/index.ts cloud functions, shared by every app
212
- migrations/0001_init.sql the one database of the project
213
- apps/<folder>/ an app: a Vite project with its own xflow.json (appId)
214
-
215
- xflow projects list every project, its apps count and age
216
- xflow projects get [id] one card: apps, database, functions and callers, schedules
217
- xflow apps the apps of this project, their folders and addresses
218
- --json as JSON, with the app ids
219
- xflow apps add <folder> a new app, laid into apps/<folder> from the template
220
- --name <name> its name (the folder by default)
221
- --color <color> its tile color (the platform picks one by default)
222
- xflow status in the root: the root and every app; in an app: that app
223
-
224
- The root and every app are separate histories: apps/** never travels with the root, the
225
- root never travels with an app. The app is its appId, so a folder can be renamed freely.
226
- Who opens an app (members of the project or anyone with the link) is set by a person
227
- in the web interface, not here.
228
-
229
- Both ${(0, ui_1.bold)('projects')} commands name the project positionally: the id is an argument, not a
230
- --project flag. Without one, ${(0, ui_1.bold)('get')} answers about the project of the folder it stands in.
231
-
232
- An id typed by hand is looked for across every signed-in organization, and the
233
- folder around the command is ignored whole, the way the ${(0, ui_1.bold)('--project')} flag does it
234
- (${(0, ui_1.bold)('xflow help project')}). So "not found" means the id is wrong, or its organization
235
- is missing from ${(0, ui_1.bold)('xflow org')}: never that the active key was simply the wrong one.
236
-
237
- ${(0, ui_1.bold)('list')} stays within one organization: the bound one inside a project folder, the
238
- active one outside. With several signed in, a line under the table says whose
239
- projects these are.
240
-
203
+ projects: `${(0, ui_1.bold)('xflow projects')} and ${(0, ui_1.bold)('xflow apps')}: projects and their apps
204
+
205
+ A project is a box: one database with its migrations, the cloud functions, secrets and
206
+ members, and no code of its own. An app is code the platform builds and serves at
207
+ https://<id>--<org>.xflowpage.ru, with its own versions and access level. One folder per
208
+ project, apps in subfolders:
209
+
210
+ xflow.json projectId, and who may call each function
211
+ package.json dependencies of the functions
212
+ functions/<name>/index.ts cloud functions, shared by every app
213
+ migrations/0001_init.sql the one database of the project
214
+ apps/<folder>/ an app: a Vite project with its own xflow.json (appId)
215
+
216
+ xflow projects list every project, its apps count and age
217
+ xflow projects get [id] one card: apps, database, functions and callers, schedules
218
+ xflow apps the apps of this project, their folders and addresses
219
+ --json as JSON, with the app ids
220
+ xflow apps add <folder> a new app, laid into apps/<folder> from the template
221
+ --name <name> its name (the folder by default)
222
+ --color <color> its tile color (the platform picks one by default)
223
+ xflow status in the root: the root and every app; in an app: that app
224
+
225
+ The root and every app are separate histories: apps/** never travels with the root, the
226
+ root never travels with an app. The app is its appId, so a folder can be renamed freely.
227
+ Who opens an app (members of the project or anyone with the link) is set by a person
228
+ in the web interface, not here.
229
+
230
+ Both ${(0, ui_1.bold)('projects')} commands name the project positionally: the id is an argument, not a
231
+ --project flag. Without one, ${(0, ui_1.bold)('get')} answers about the project of the folder it stands in.
232
+
233
+ An id typed by hand is looked for across every signed-in organization, and the
234
+ folder around the command is ignored whole, the way the ${(0, ui_1.bold)('--project')} flag does it
235
+ (${(0, ui_1.bold)('xflow help project')}). So "not found" means the id is wrong, or its organization
236
+ is missing from ${(0, ui_1.bold)('xflow org')}: never that the active key was simply the wrong one.
237
+
238
+ ${(0, ui_1.bold)('list')} stays within one organization: the bound one inside a project folder, the
239
+ active one outside. With several signed in, a line under the table says whose
240
+ projects these are.
241
+
241
242
  ${(0, ui_1.bold)('xflow link <id>')} binds the current folder to a project; ${(0, ui_1.bold)('xflow init')} makes a new one.`,
242
- link: `${(0, ui_1.bold)('xflow link')} <id>: bind this folder to a project
243
-
244
- Writes the root xflow.json and .xflow/state.json and lays down the instructions for the
245
- agents. The id comes from ${(0, ui_1.bold)('xflow projects list')}; a project of another signed-in
246
- organization is found too, and the folder is bound to that organization.
247
-
248
- An empty folder (.git alone counts as empty) gets the whole project: the latest revision
249
- of the root and of every app, each app in apps/<folder> under the folder name the project
250
- revision records. A folder with files keeps them as they are: ${(0, ui_1.bold)('xflow status')} says
251
- how they differ, ${(0, ui_1.bold)('xflow pull --into ./server-copy')} fetches the whole project next to them.
252
-
253
- In a folder that is already linked, the command points xflow.json at the other project and
243
+ link: `${(0, ui_1.bold)('xflow link')} <id>: bind this folder to a project
244
+
245
+ Writes the root xflow.json and .xflow/state.json and lays down the instructions for the
246
+ agents. The id comes from ${(0, ui_1.bold)('xflow projects list')}; a project of another signed-in
247
+ organization is found too, and the folder is bound to that organization.
248
+
249
+ An empty folder (.git alone counts as empty) gets the whole project: the latest revision
250
+ of the root and of every app, each app in apps/<folder> under the folder name the project
251
+ revision records. A folder with files keeps them as they are: ${(0, ui_1.bold)('xflow status')} says
252
+ how they differ, ${(0, ui_1.bold)('xflow pull --into ./server-copy')} fetches the whole project next to them.
253
+
254
+ In a folder that is already linked, the command points xflow.json at the other project and
254
255
  keeps the rest of the file. A new project instead of an existing one: ${(0, ui_1.bold)('xflow init')}.`,
255
- update: `${(0, ui_1.bold)('xflow update')}: bring the CLI up to date
256
-
257
- Installs the published version and, if anything changed, rewrites the platform
258
- instructions the agents already have: the skill ships inside the package and shares
259
- its version, so a fresh CLI next to a stale skill means the agent is reading last
260
- month's rules. Copies that are not installed are not created, so it is safe to run
261
- from any folder.
262
-
263
- The platform reports the published version with every answer, which is where the
264
- ${(0, ui_1.bold)('xflow N is out')} line comes from. It appears once a day at most, and never in CI,
265
- where nobody is going to update anything anyway.
266
-
267
- The command works out how this copy was installed from its own path, and refuses to
268
- cross package managers: an install made by pnpm or bun is printed as their own
269
- command rather than handed to npm, which would leave you with two copies and no way
270
- to tell which one answers. Two more cases end the same way. Under ${(0, ui_1.bold)('npx')} there is
271
- nothing to update, only a cache. And a copy inside a project's node_modules is a
272
- dependency of that project: ${(0, ui_1.bold)('npm i -D @getxflow/cli@latest')} is the fix, because
273
- inside npm scripts the local copy wins over anything installed globally.
274
-
275
- A refusal from the platform saying the CLI is too old is a different thing: that one
276
- is a hard gate on a changed contract, and until you run this command nothing else
256
+ update: `${(0, ui_1.bold)('xflow update')}: bring the CLI up to date
257
+
258
+ Installs the published version and, if anything changed, rewrites the platform
259
+ instructions the agents already have: the skill ships inside the package and shares
260
+ its version, so a fresh CLI next to a stale skill means the agent is reading last
261
+ month's rules. Copies that are not installed are not created, so it is safe to run
262
+ from any folder.
263
+
264
+ The platform reports the published version with every answer, which is where the
265
+ ${(0, ui_1.bold)('xflow N is out')} line comes from. It appears once a day at most, and never in CI,
266
+ where nobody is going to update anything anyway.
267
+
268
+ The command works out how this copy was installed from its own path, and refuses to
269
+ cross package managers: an install made by pnpm or bun is printed as their own
270
+ command rather than handed to npm, which would leave you with two copies and no way
271
+ to tell which one answers. Two more cases end the same way. Under ${(0, ui_1.bold)('npx')} there is
272
+ nothing to update, only a cache. And a copy inside a project's node_modules is a
273
+ dependency of that project: ${(0, ui_1.bold)('npm i -D @getxflow/cli@latest')} is the fix, because
274
+ inside npm scripts the local copy wins over anything installed globally.
275
+
276
+ A refusal from the platform saying the CLI is too old is a different thing: that one
277
+ is a hard gate on a changed contract, and until you run this command nothing else
277
278
  will work.`,
278
- db: `${(0, ui_1.bold)('xflow db')}: schema, data, migrations
279
-
280
- xflow db status what is applied, what is waiting
281
- xflow db list databases of the projects of the organization, with sizes
282
- xflow db schema [table] tables of the project, or the columns of one
283
- xflow db query 'select ...' read data (rows: the query's own limit, 50 without
284
- one, 300 at most; --json prints the values whole)
285
- xflow db query <file>.sql the same, the query read from a file
286
- xflow db exec 'update ...' a one-off data change: one INSERT, UPDATE, DELETE or MERGE
287
- (or <file>.sql; --allow-destructive, --json)
288
- xflow db migrate store the project root, then apply migrations/*.sql
289
-
290
- Every project has exactly one database of its own, shared by all of its apps and functions,
291
- and every command here works from any folder of the project. ${(0, ui_1.bold)('list')} is about the
292
- organization: one database per project, largest first, to see which project takes the room.
293
-
294
- ${(0, ui_1.bold)('schema')} answers a different question than the ${(0, ui_1.bold)('migrations/')} directory: the files say
295
- what you did, the schema says what is actually in there. ${(0, ui_1.bold)('query')} runs inside a READ ONLY transaction, so
296
- writes are rejected by the database itself, not by us reading your SQL. Single quotes around
297
- the query hold in PowerShell, bash and zsh, while cmd.exe wants double ones; a query that
298
- needs quotes of its own goes in a .sql file outside the project, because Windows PowerShell
299
- strips double quotes from arguments and a file in migrations/ would be taken for a
300
- migration. Its table cuts
301
- long values to keep the columns readable; ${(0, ui_1.bold)('--json')} prints them whole, together with
302
- the applied limit and its ceiling. ${(0, ui_1.bold)('status')} named by id (${(0, ui_1.bold)('--project')}) answers with
303
- the applied history alone: comparing against ${(0, ui_1.bold)('migrations/')} needs the folder.
304
-
305
- ${(0, ui_1.bold)('exec')} fixes data once: one INSERT, UPDATE, DELETE or MERGE, saved at once and kept out
306
- of the migration history, so a one-off fix no longer becomes a migration forever. Schema
307
- changes are refused: they stay with ${(0, ui_1.bold)('migrate')}. The platform judges the change by what the
308
- database reports, cascades and triggers included. Deleting rows, or updating more than 100
309
- in a table, asks for ${(0, ui_1.bold)('--allow-destructive')}, and those tables are copied for 7 days before it
310
- is saved; deleting also needs the right to destroy data on the key, as in migrations (see
311
- below). The rows it holds are locked while it runs, so a statement that waits on a running
312
- app is cancelled after a few seconds and changes nothing. There is no undo.
313
-
314
- ${(0, ui_1.bold)('migrate')}: the files live in the project root (${(0, ui_1.bold)('migrations/0001_init.sql')},
315
- ${(0, ui_1.bold)('migrations/0002_orders.sql')} and so on). The order comes from the file name, which
316
- is why the leading number is required. The command first stores the project root as a
317
- revision, so the migrations stay in its history and a clone gets them, then applies what is
318
- new. Every migration runs in its own transaction, the history is kept in the database
319
- itself, and anything already applied is not run again.
320
-
321
- --dry-run report what would be applied without touching the database
322
- --allow-destructive allow operations that destroy data
323
-
324
- About destructive ones. The platform keeps no database history and makes no backups,
325
- so ${(0, ui_1.bold)('DROP TABLE')}, ${(0, ui_1.bold)('DROP COLUMN')}, ${(0, ui_1.bold)('TRUNCATE')} and deleting rows (judged by what
326
- the database did, not by the text) need two things at once: this flag, and the right to destroy data on the
327
- access key. That right is off by default, and only its owner turns it on, in the
328
- platform settings, where the key permissions live: a flag is something an agent adds by itself, a
329
- right is not. With both in place, the contents of the affected tables are dumped and
330
- kept for 7 days: that is a "caught it right away" safety net, not a backup.
331
- ${(0, ui_1.bold)('DROP DATABASE')} is never allowed.
332
-
333
- Editing an already applied file achieves nothing: ${(0, ui_1.bold)('migrate')} refuses a name that is
334
- already applied with different content, and ${(0, ui_1.bold)('xflow db status')} lists such a file
279
+ db: `${(0, ui_1.bold)('xflow db')}: schema, data, migrations
280
+
281
+ xflow db status what is applied, what is waiting
282
+ xflow db list databases of the projects of the organization, with sizes
283
+ xflow db schema [table] tables of the project, or the columns of one
284
+ xflow db query 'select ...' read data (rows: the query's own limit, 50 without
285
+ one, 300 at most; --json prints the values whole)
286
+ xflow db query <file>.sql the same, the query read from a file
287
+ xflow db exec 'update ...' a one-off data change: one INSERT, UPDATE, DELETE or MERGE
288
+ (or <file>.sql; --allow-destructive, --json)
289
+ xflow db migrate store the project root, then apply migrations/*.sql
290
+ xflow db logs errors the database raised: failed and timed-out queries
291
+ with their SQL (cut to 2 KB), dropped connections; 7 days,
292
+ 1 on the Free plan (--limit, --cursor for older)
293
+
294
+ Every project has exactly one database of its own, shared by all of its apps and functions,
295
+ and every command here works from any folder of the project. ${(0, ui_1.bold)('list')} is about the
296
+ organization: one database per project, largest first, to see which project takes the room.
297
+
298
+ ${(0, ui_1.bold)('schema')} answers a different question than the ${(0, ui_1.bold)('migrations/')} directory: the files say
299
+ what you did, the schema says what is actually in there. ${(0, ui_1.bold)('query')} runs inside a READ ONLY transaction, so
300
+ writes are rejected by the database itself, not by us reading your SQL. Single quotes around
301
+ the query hold in PowerShell, bash and zsh, while cmd.exe wants double ones; a query that
302
+ needs quotes of its own goes in a .sql file outside the project, because Windows PowerShell
303
+ strips double quotes from arguments and a file in migrations/ would be taken for a
304
+ migration. Its table cuts
305
+ long values to keep the columns readable; ${(0, ui_1.bold)('--json')} prints them whole, together with
306
+ the applied limit and its ceiling. ${(0, ui_1.bold)('status')} named by id (${(0, ui_1.bold)('--project')}) answers with
307
+ the applied history alone: comparing against ${(0, ui_1.bold)('migrations/')} needs the folder.
308
+
309
+ ${(0, ui_1.bold)('exec')} fixes data once: one INSERT, UPDATE, DELETE or MERGE, saved at once and kept out
310
+ of the migration history, so a one-off fix no longer becomes a migration forever. Schema
311
+ changes are refused: they stay with ${(0, ui_1.bold)('migrate')}. The platform judges the change by what the
312
+ database reports, cascades and triggers included. Deleting rows, or updating more than 100
313
+ in a table, asks for ${(0, ui_1.bold)('--allow-destructive')}, and those tables are copied for 7 days before it
314
+ is saved; deleting also needs the right to destroy data on the key, as in migrations (see
315
+ below). The rows it holds are locked while it runs, so a statement that waits on a running
316
+ app is cancelled after a few seconds and changes nothing. There is no undo.
317
+
318
+ ${(0, ui_1.bold)('migrate')}: the files live in the project root (${(0, ui_1.bold)('migrations/0001_init.sql')},
319
+ ${(0, ui_1.bold)('migrations/0002_orders.sql')} and so on). The order comes from the file name, which
320
+ is why the leading number is required. The command first stores the project root as a
321
+ revision, so the migrations stay in its history and a clone gets them, then applies what is
322
+ new. Every migration runs in its own transaction, the history is kept in the database
323
+ itself, and anything already applied is not run again.
324
+
325
+ --dry-run report what would be applied without touching the database
326
+ --allow-destructive allow operations that destroy data
327
+
328
+ About destructive ones. The platform keeps no database history and makes no backups,
329
+ so ${(0, ui_1.bold)('DROP TABLE')}, ${(0, ui_1.bold)('DROP COLUMN')}, ${(0, ui_1.bold)('TRUNCATE')} and deleting rows (judged by what
330
+ the database did, not by the text) need two things at once: this flag, and the right to destroy data on the
331
+ access key. That right is off by default, and only its owner turns it on, in the
332
+ platform settings, where the key permissions live: a flag is something an agent adds by itself, a
333
+ right is not. With both in place, the contents of the affected tables are dumped and
334
+ kept for 7 days: that is a "caught it right away" safety net, not a backup.
335
+ ${(0, ui_1.bold)('DROP DATABASE')} is never allowed.
336
+
337
+ Editing an already applied file achieves nothing: ${(0, ui_1.bold)('migrate')} refuses a name that is
338
+ already applied with different content, and ${(0, ui_1.bold)('xflow db status')} lists such a file
335
339
  separately. Write a new migration instead.`,
336
- logs: `${(0, ui_1.bold)('xflow logs')} and ${(0, ui_1.bold)('xflow functions logs')}: project errors and calls
337
-
338
- Kept for 7 days (1 day on the Free plan), newest first, a page at a time.
339
- ${(0, ui_1.bold)('xflow logs')}: browser errors of the apps; in an app folder (or with ${(0, ui_1.bold)('--app <id>')}) that app,
340
- in the project root every app, each row named by its folder.
341
- ${(0, ui_1.bold)('xflow functions logs [name]')}: crashes with the tail of their console, and failed calls
342
- (timeout, out of memory, 5xx). A successful call is kept as its status and time only.
343
-
344
- --limit N how many to show (10 by default)
345
- --all functions: every call, not only the failed ones
346
- --cursor C the next, older page (the command prints it)
347
-
348
- A crash lands here with the last ${(0, ui_1.bold)('console.log')} lines of that call. A call that ran out of
349
- time or memory shows as a failed call without console: the cap of the plan is in ${(0, ui_1.bold)('xflow whoami')}.
350
- Those show up in the answer to ${(0, ui_1.bold)('xflow functions invoke')}.
351
-
352
- Browser errors are collected by ${(0, ui_1.bold)('src/utils/error-logger.ts')} of the template and
340
+ logs: `${(0, ui_1.bold)('xflow logs')} and ${(0, ui_1.bold)('xflow functions logs')}: project errors and calls
341
+
342
+ Kept for 7 days (1 day on the Free plan), newest first, a page at a time.
343
+ ${(0, ui_1.bold)('xflow logs')}: browser errors of the apps; in an app folder (or with ${(0, ui_1.bold)('--app <id>')}) that app,
344
+ in the project root every app, each row named by its folder.
345
+ ${(0, ui_1.bold)('xflow functions logs [name]')}: crashes with the tail of their console, and failed calls
346
+ (timeout, out of memory, 5xx). A successful call is kept as its status and time only.
347
+
348
+ --limit N how many to show (10 by default)
349
+ --all functions: every call, not only the failed ones
350
+ --cursor C the next, older page (the command prints it)
351
+
352
+ A crash lands here with the last ${(0, ui_1.bold)('console.log')} lines of that call. A call that ran out of
353
+ time or memory shows as a failed call without console: the cap of the plan is in ${(0, ui_1.bold)('xflow whoami')}.
354
+ Those show up in the answer to ${(0, ui_1.bold)('xflow functions invoke')}.
355
+
356
+ Browser errors are collected by ${(0, ui_1.bold)('src/utils/error-logger.ts')} of the template and
353
357
  only from the published addresses: a local ${(0, ui_1.bold)('xflow dev')} writes nothing here.`,
354
- storage: `${(0, ui_1.bold)('xflow storage')}: files of the application
355
-
356
- The sources archive is capped at 10 MB and is rebuilt and re-uploaded on every
357
- deploy, so photos, video and PDFs do not belong in the repository. File storage
358
- is the place for them: the files sit beside the versions, survive a build, and
359
- are metered against the plan of the organization.
360
-
361
- xflow storage ls [folder] what is there, with addresses and sizes
362
- xflow storage push ./media --to media
363
- upload a folder, keeping its structure
364
- xflow storage rm <address> delete one file
365
- xflow storage rm --folder photos --yes
366
- delete a folder with everything in it
367
-
368
- --to <folder> where to put it in the project storage
369
- --replace upload files whose size differs from the stored one
370
- --json machine-readable output, with every address
371
-
372
- ${(0, ui_1.bold)('push')} sends only what is missing: a file already stored under the same path and
373
- size is skipped, so running the command again after a broken connection is cheap.
374
- A file that differs is left alone unless ${(0, ui_1.bold)('--replace')} is given. Bytes go straight
375
- into the bucket, they do not pass through the platform.
376
-
377
- Addresses are permanent and belong to the record, not to the bytes: replacing a
378
- file keeps its address, so the links in the code and in the tables of the
379
- application keep working. That is why there is no delete-and-upload-again.
380
-
381
- The address is not public: it is a page of the platform that checks the rights of
382
- the viewer on every request, so a closed project stops serving its pictures too.
383
- Use the address as it came back, in ${(0, ui_1.bold)('<img src>')} and in the data of the application.
384
-
385
- Dot entries and symbolic links are skipped, and ${(0, ui_1.bold)('.xflowignore')} is not read here:
386
- those are the rules for the sources, and a media folder is exactly what one is
387
- asked to put there so it stays out of the archive.
388
-
389
- ${(0, ui_1.bold)('rm')} needs a right of its own (${(0, ui_1.bold)('Delete files')}, where the key permissions live in
390
- the platform settings), off by default. The reason is the neighbourhood: whatever the users of
391
- the application uploaded lives in the same folders, and there is no undo. A folder
392
- first answers with the number of files it holds and deletes only on ${(0, ui_1.bold)('--yes')}.
393
-
358
+ storage: `${(0, ui_1.bold)('xflow storage')}: files of the application
359
+
360
+ The sources archive is capped at 10 MB and is rebuilt and re-uploaded on every
361
+ deploy, so photos, video and PDFs do not belong in the repository. File storage
362
+ is the place for them: the files sit beside the versions, survive a build, and
363
+ are metered against the plan of the organization.
364
+
365
+ xflow storage ls [folder] what is there, with addresses and sizes
366
+ xflow storage push ./media --to media
367
+ upload a folder, keeping its structure
368
+ xflow storage rm <address> delete one file
369
+ xflow storage rm --folder photos --yes
370
+ delete a folder with everything in it
371
+
372
+ --to <folder> where to put it in the project storage
373
+ --replace upload files whose size differs from the stored one
374
+ --json machine-readable output, with every address
375
+
376
+ ${(0, ui_1.bold)('push')} sends only what is missing: a file already stored under the same path and
377
+ size is skipped, so running the command again after a broken connection is cheap.
378
+ A file that differs is left alone unless ${(0, ui_1.bold)('--replace')} is given. Bytes go straight
379
+ into the bucket, they do not pass through the platform.
380
+
381
+ Addresses are permanent and belong to the record, not to the bytes: replacing a
382
+ file keeps its address, so the links in the code and in the tables of the
383
+ application keep working. That is why there is no delete-and-upload-again.
384
+
385
+ The address is not public: it is a page of the platform that checks the rights of
386
+ the viewer on every request, so a closed project stops serving its pictures too.
387
+ Use the address as it came back, in ${(0, ui_1.bold)('<img src>')} and in the data of the application.
388
+
389
+ Dot entries and symbolic links are skipped, and ${(0, ui_1.bold)('.xflowignore')} is not read here:
390
+ those are the rules for the sources, and a media folder is exactly what one is
391
+ asked to put there so it stays out of the archive.
392
+
393
+ ${(0, ui_1.bold)('rm')} needs a right of its own (${(0, ui_1.bold)('Delete files')}, where the key permissions live in
394
+ the platform settings), off by default. The reason is the neighbourhood: whatever the users of
395
+ the application uploaded lives in the same folders, and there is no undo. A folder
396
+ first answers with the number of files it holds and deletes only on ${(0, ui_1.bold)('--yes')}.
397
+
394
398
  There is no command back: sources return with ${(0, ui_1.bold)('xflow pull')}, files do not.`,
395
- env: `${(0, ui_1.bold)('xflow env')}: environment variables of the functions
396
-
397
- Keys, passwords and third-party addresses are kept by the platform, not by the
398
- repository. A value goes up and never comes back: the only place to see it is inside
399
- the function. So keep your own copy wherever you got it from.
400
-
401
- xflow env what is stored (names, not values)
402
- xflow env check what the functions of this project are missing
403
- xflow env set SMTP_PASSWORD=… store
404
- xflow env rm SMTP_PASSWORD delete
405
-
406
- A variable belongs to this project: functions of other projects never see it.
407
-
408
- ${(0, ui_1.bold)('check')} reads the sources in ${(0, ui_1.bold)('functions/')} and looks for ${(0, ui_1.bold)('process.env.NAME')}
409
- references. The same rule applies on deploy: a function receives only the variables it
410
- mentions by name. A name assembled from an expression (${(0, ui_1.bold)("process.env['KEY_' + n]")}),
411
- reached through ${(0, ui_1.bold)('?.')}, destructured or spread never reaches the environment, so read
412
- variables literally. ${(0, ui_1.bold)('check')} names those reads and the build rejects them: a secret
413
- that quietly fails to arrive is found by the function falling over weeks later.
414
-
415
- The value reaches the function on deploy, not at the moment it is stored: after
416
- ${(0, ui_1.bold)('env set')} run ${(0, ui_1.bold)('xflow deploy functions')}. It ships a function whose code did not
417
- change but whose variables did, so nothing is left holding an old value. The same after a
418
- delete: a function already deployed keeps the old value until its next deploy. Apps get no
399
+ env: `${(0, ui_1.bold)('xflow env')}: environment variables of the functions
400
+
401
+ Keys, passwords and third-party addresses are kept by the platform, not by the
402
+ repository. A value goes up and never comes back: the only place to see it is inside
403
+ the function. So keep your own copy wherever you got it from.
404
+
405
+ xflow env what is stored (names, not values)
406
+ xflow env check what the functions of this project are missing
407
+ xflow env set SMTP_PASSWORD=… store
408
+ xflow env rm SMTP_PASSWORD delete
409
+
410
+ A variable belongs to this project: functions of other projects never see it.
411
+
412
+ ${(0, ui_1.bold)('check')} reads the sources in ${(0, ui_1.bold)('functions/')} and looks for ${(0, ui_1.bold)('process.env.NAME')}
413
+ references. The same rule applies on deploy: a function receives only the variables it
414
+ mentions by name. A name assembled from an expression (${(0, ui_1.bold)("process.env['KEY_' + n]")}),
415
+ reached through ${(0, ui_1.bold)('?.')}, destructured or spread never reaches the environment, so read
416
+ variables literally. ${(0, ui_1.bold)('check')} names those reads and the build rejects them: a secret
417
+ that quietly fails to arrive is found by the function falling over weeks later.
418
+
419
+ The value reaches the function on deploy, not at the moment it is stored: after
420
+ ${(0, ui_1.bold)('env set')} run ${(0, ui_1.bold)('xflow deploy functions')}. It ships a function whose code did not
421
+ change but whose variables did, so nothing is left holding an old value. The same after a
422
+ delete: a function already deployed keeps the old value until its next deploy. Apps get no
419
423
  variables at all: whatever a secret protects is reached through a function.`,
420
- connections: `${(0, ui_1.bold)('xflow connections')}: accounts connected to the organization
421
-
422
- Somebody signs in to Yandex Metrika, Bitrix or a mail service once, in the platform,
423
- and that account becomes a connection of the organization. Linked to a project, it
424
- hands its credentials to the cloud functions as environment variables. Nobody has to
425
- paste a token into the repository, and nobody sees the value: it goes straight from
426
- the platform into the function.
427
-
428
- xflow connections what this project can use, and what it already uses
429
- xflow connections link <name> --as ALIAS
430
- hand its credentials to the functions
431
- xflow connections unlink <ALIAS> take them away again
432
- xflow connections call <name> <path>
433
- ask that API something, once, without deploying
434
-
435
- Every row says whether the connection is linked to this project (its alias), which of
436
- its fields a call can fill in (${(0, ui_1.bold)('{CLIENT_ID}')} and the like) and what state the
437
- access is in. ${(0, ui_1.bold)('available, not linked')} is the useful one: the account exists in
438
- the organization, but this project gets nothing from it yet: link it.
439
-
440
- Both commands take the name of the connection, the first column of the list, and both
441
- also take its identifier. ${(0, ui_1.bold)('unlink')} takes the alias as well, and prefers it: the
442
- alias is the name your own code already uses. One organization can hold several accounts
443
- of the same service under one name; then the command prints their identifiers and asks
444
- for one of those instead of guessing.
445
-
446
- ${(0, ui_1.bold)('unlink')} refuses while a function still reads one of the variables and names
447
- those functions; ${(0, ui_1.bold)('--yes')} goes through anyway. Seeing them needs the sources,
448
- so under ${(0, ui_1.bold)('--project')} there is no such check: the link goes, and the variables
449
- that go with it are printed instead. Both commands take the flag, because an account
450
- linked to a service project without a folder has to be removable the same way.
451
-
452
- Variables are named after the alias: an OAuth connection called ${(0, ui_1.bold)('YANDEX_METRIKA')}
453
- gives ${(0, ui_1.bold)('YANDEX_METRIKA_TOKEN')}, a key-based one gives a variable per field. They
454
- arrive at a function on its next deploy, like every other variable, so after a change
455
- run ${(0, ui_1.bold)('xflow deploy functions')}. A token close to expiry is renewed by that deploy on the
456
- way; a revoked one is not renewed by anything until a human reconnects the account.
457
-
458
- Linking needs a right on the key, ${(0, ui_1.bold)('connections:link')}. Keys are issued with it,
459
- and a person can take it away in the platform settings, where the key permissions live; a key cannot give
460
- it back to itself. Losing it is what the refusal says, and the answer is to ask a person,
461
- not to look for another route. Either way only the accounts granted to you personally can
462
- be linked at all: that rule holds whatever the key is allowed to do.
463
-
464
- Connecting a new account and switching one off stay with a person, in the platform
465
- interface. There is no command for either.
466
-
467
- ${(0, ui_1.bold)('call')} answers the question an API only answers by being asked: what does this
468
- endpoint actually return, and is the field you need in it. The platform attaches the
469
- credentials of the connected account and makes the request itself, so nothing has to be
470
- deployed to find out, and the key never reaches you.
471
-
472
- xflow connections call WB /api/v1/supplier/stocks?dateFrom=2026-09-01
473
- xflow connections call AMO /api/v4/leads --method POST --data '{"name":"Test"}'
474
- xflow connections call OZON /v1/product/list --method POST --data '{}' \\
475
- --header '{"Client-Id":"{CLIENT_ID}"}'
476
-
477
- The path is written after the address of the connector, query string and all, or given in
478
- full as ${(0, ui_1.bold)('https://...')}. Only the hosts of that connector are reachable, and a
479
- refusal lists them. ${(0, ui_1.bold)('--method')} defaults to GET, or to POST when there is a body.
480
-
481
- ${(0, ui_1.bold)('{NAME}')} in the path or in a header value is filled in by the platform from the
482
- fields of the connection. That is how the two-header APIs are called: Ozon wants
483
- ${(0, ui_1.bold)('Client-Id')} beside its key, Yandex Direct wants ${(0, ui_1.bold)('Client-Login')} for an
484
- agency account, and you never see those values. Which names a connection has is the fourth
485
- column of ${(0, ui_1.bold)('xflow connections')}, printed in braces and the same whether the account
486
- is linked or not. Do not derive them from ${(0, ui_1.bold)('xflow env')}: that list is the variables
487
- of a link, and it names both more and less than this one. A name the connection does not
488
- have is left in the text as you typed it and the request still goes out, so an invented
489
- name reaches somebody's live account as text.
490
-
491
- The answer is printed head first, at most a few kilobytes, and it says so when there is
492
- more. Ask for the shape, not the volume: one day and ${(0, ui_1.bold)('limit=1')} tells you what the
493
- fields are called, while the whole dataset belongs in a cloud function. ${(0, ui_1.bold)('--json')}
494
- prints the envelope for a pipe: ${(0, ui_1.bold)("--json | jq '.response.body | keys'")} gives the
495
- field names without the body ever entering your context.
496
-
497
- An account linked to this project is called by its alias. One that is only available is
498
- called by its name, and no linking is needed: what the command asks is that you could have
499
- linked it yourself, which needs the ${(0, ui_1.bold)('connections:link')} right and the account being
500
- granted to you personally. Either way nothing widens: linked credentials already reach the
501
- functions of this project, and an account you may link you may also read by deploying a
502
- function into it. An account granted to somebody else stays out of reach, and the refusal
503
- says which of the two is missing, the right or the grant.
504
-
505
- The call goes out for real: ${(0, ui_1.bold)('POST')} and ${(0, ui_1.bold)('DELETE')} change things in
424
+ connections: `${(0, ui_1.bold)('xflow connections')}: accounts connected to the organization
425
+
426
+ Somebody signs in to Yandex Metrika, Bitrix or a mail service once, in the platform,
427
+ and that account becomes a connection of the organization. Linked to a project, it
428
+ hands its credentials to the cloud functions as environment variables. Nobody has to
429
+ paste a token into the repository, and nobody sees the value: it goes straight from
430
+ the platform into the function.
431
+
432
+ xflow connections what this project can use, and what it already uses
433
+ xflow connections link <name> --as ALIAS
434
+ hand its credentials to the functions
435
+ xflow connections unlink <ALIAS> take them away again
436
+ xflow connections call <name> <path>
437
+ ask that API something, once, without deploying
438
+
439
+ Every row says whether the connection is linked to this project (its alias), which of
440
+ its fields a call can fill in (${(0, ui_1.bold)('{CLIENT_ID}')} and the like) and what state the
441
+ access is in. ${(0, ui_1.bold)('available, not linked')} is the useful one: the account exists in
442
+ the organization, but this project gets nothing from it yet: link it.
443
+
444
+ Both commands take the name of the connection, the first column of the list, and both
445
+ also take its identifier. ${(0, ui_1.bold)('unlink')} takes the alias as well, and prefers it: the
446
+ alias is the name your own code already uses. One organization can hold several accounts
447
+ of the same service under one name; then the command prints their identifiers and asks
448
+ for one of those instead of guessing.
449
+
450
+ ${(0, ui_1.bold)('unlink')} refuses while a function still reads one of the variables and names
451
+ those functions; ${(0, ui_1.bold)('--yes')} goes through anyway. Seeing them needs the sources,
452
+ so under ${(0, ui_1.bold)('--project')} there is no such check: the link goes, and the variables
453
+ that go with it are printed instead. Both commands take the flag, because an account
454
+ linked to a service project without a folder has to be removable the same way.
455
+
456
+ Variables are named after the alias: an OAuth connection called ${(0, ui_1.bold)('YANDEX_METRIKA')}
457
+ gives ${(0, ui_1.bold)('YANDEX_METRIKA_TOKEN')}, a key-based one gives a variable per field. They
458
+ arrive at a function on its next deploy, like every other variable, so after a change
459
+ run ${(0, ui_1.bold)('xflow deploy functions')}. A token close to expiry is renewed by that deploy on the
460
+ way; a revoked one is not renewed by anything until a human reconnects the account.
461
+
462
+ Linking needs a right on the key, ${(0, ui_1.bold)('connections:link')}. Keys are issued with it,
463
+ and a person can take it away in the platform settings, where the key permissions live; a key cannot give
464
+ it back to itself. Losing it is what the refusal says, and the answer is to ask a person,
465
+ not to look for another route. Either way only the accounts granted to you personally can
466
+ be linked at all: that rule holds whatever the key is allowed to do.
467
+
468
+ Connecting a new account and switching one off stay with a person, in the platform
469
+ interface. There is no command for either.
470
+
471
+ ${(0, ui_1.bold)('call')} answers the question an API only answers by being asked: what does this
472
+ endpoint actually return, and is the field you need in it. The platform attaches the
473
+ credentials of the connected account and makes the request itself, so nothing has to be
474
+ deployed to find out, and the key never reaches you.
475
+
476
+ xflow connections call WB /api/v1/supplier/stocks?dateFrom=2026-09-01
477
+ xflow connections call AMO /api/v4/leads --method POST --data '{"name":"Test"}'
478
+ xflow connections call OZON /v1/product/list --method POST --data '{}' \\
479
+ --header '{"Client-Id":"{CLIENT_ID}"}'
480
+
481
+ The path is written after the address of the connector, query string and all, or given in
482
+ full as ${(0, ui_1.bold)('https://...')}. Only the hosts of that connector are reachable, and a
483
+ refusal lists them. ${(0, ui_1.bold)('--method')} defaults to GET, or to POST when there is a body.
484
+
485
+ ${(0, ui_1.bold)('{NAME}')} in the path or in a header value is filled in by the platform from the
486
+ fields of the connection. That is how the two-header APIs are called: Ozon wants
487
+ ${(0, ui_1.bold)('Client-Id')} beside its key, Yandex Direct wants ${(0, ui_1.bold)('Client-Login')} for an
488
+ agency account, and you never see those values. Which names a connection has is the fourth
489
+ column of ${(0, ui_1.bold)('xflow connections')}, printed in braces and the same whether the account
490
+ is linked or not. Do not derive them from ${(0, ui_1.bold)('xflow env')}: that list is the variables
491
+ of a link, and it names both more and less than this one. A name the connection does not
492
+ have is left in the text as you typed it and the request still goes out, so an invented
493
+ name reaches somebody's live account as text.
494
+
495
+ The answer is printed head first, at most a few kilobytes, and it says so when there is
496
+ more. Ask for the shape, not the volume: one day and ${(0, ui_1.bold)('limit=1')} tells you what the
497
+ fields are called, while the whole dataset belongs in a cloud function. ${(0, ui_1.bold)('--json')}
498
+ prints the envelope for a pipe: ${(0, ui_1.bold)("--json | jq '.response.body | keys'")} gives the
499
+ field names without the body ever entering your context.
500
+
501
+ An account linked to this project is called by its alias. One that is only available is
502
+ called by its name, and no linking is needed: what the command asks is that you could have
503
+ linked it yourself, which needs the ${(0, ui_1.bold)('connections:link')} right and the account being
504
+ granted to you personally. Either way nothing widens: linked credentials already reach the
505
+ functions of this project, and an account you may link you may also read by deploying a
506
+ function into it. An account granted to somebody else stays out of reach, and the refusal
507
+ says which of the two is missing, the right or the grant.
508
+
509
+ The call goes out for real: ${(0, ui_1.bold)('POST')} and ${(0, ui_1.bold)('DELETE')} change things in
506
510
  somebody's live account, and no flag will undo that.`,
507
- schedules: `${(0, ui_1.bold)('xflow schedules')}: running functions on a timer
508
-
509
- A schedule is a Yandex timer trigger: it calls the function itself, with no
510
- application and no browser involved.
511
-
512
- xflow schedules set report "0 3 ? * * *" every day at 03:00
513
- xflow schedules set digest "0 */4 ? * * *" every 4 hours
514
- xflow schedules rm report take off every schedule of a function
515
-
516
- Six fields: ${(0, ui_1.bold)('minutes hours day-of-month month day-of-week year')}. Exactly one of
517
- the day-of-month and day-of-week fields has to be ${(0, ui_1.bold)('?')}, which is not our quirk but
518
- how Yandex works. ${(0, ui_1.bold)('The time is always UTC')}, local time is not understood.
519
-
520
- --payload '{"mode":"full"}' the body the function will receive
521
- --payload-file body.json the same body from a file
522
-
523
- ${(0, ui_1.bold)('list')} prints that body next to the schedule, so what a timer sends can be read
524
- back without setting it again.
525
-
526
- A scheduled run arrives at the handler as a POST with ${(0, ui_1.bold)("event.xflow.caller === 'schedule'")}:
527
- the envelope is signed by the platform, so such an event cannot be forged from outside. A
528
- function without callers that only a schedule wakes is a workflow, and a project without
529
- apps is made of those. The function has to be deployed already, since the schedule points
530
- at it and not the other way round.
531
-
532
- A failed run shows up in ${(0, ui_1.bold)('xflow functions logs')}: nobody is watching a scheduled
511
+ schedules: `${(0, ui_1.bold)('xflow schedules')}: running functions on a timer
512
+
513
+ A schedule is a Yandex timer trigger: it calls the function itself, with no
514
+ application and no browser involved.
515
+
516
+ xflow schedules set report "0 3 ? * * *" every day at 03:00
517
+ xflow schedules set digest "0 */4 ? * * *" every 4 hours
518
+ xflow schedules rm report take off every schedule of a function
519
+
520
+ Six fields: ${(0, ui_1.bold)('minutes hours day-of-month month day-of-week year')}. Exactly one of
521
+ the day-of-month and day-of-week fields has to be ${(0, ui_1.bold)('?')}, which is not our quirk but
522
+ how Yandex works. ${(0, ui_1.bold)('The time is always UTC')}, local time is not understood.
523
+
524
+ --payload '{"mode":"full"}' the body the function will receive
525
+ --payload-file body.json the same body from a file
526
+
527
+ ${(0, ui_1.bold)('list')} prints that body next to the schedule, so what a timer sends can be read
528
+ back without setting it again.
529
+
530
+ A scheduled run arrives at the handler as a POST with ${(0, ui_1.bold)("event.xflow.caller === 'schedule'")}:
531
+ the envelope is signed by the platform, so such an event cannot be forged from outside. A
532
+ function without callers that only a schedule wakes is a workflow, and a project without
533
+ apps is made of those. The function has to be deployed already, since the schedule points
534
+ at it and not the other way round.
535
+
536
+ A failed run shows up in ${(0, ui_1.bold)('xflow functions logs')}: nobody is watching a scheduled
533
537
  function, so the wrapper reports a crash the same way it does on an ordinary call.`,
534
- functions: `${(0, ui_1.bold)('xflow functions')}: what is deployed, and calling it
535
-
536
- xflow functions list name, state, callers, open, keys, last call and error
537
- xflow functions invoke <name> call one as yourself, through the platform
538
- xflow functions logs [name] crashes: stack and console output
539
-
540
- Functions belong to the project and ship on their own step: ${(0, ui_1.bold)('xflow deploy functions')}
541
- sends the project root and deploys everything in ${(0, ui_1.bold)('functions/<name>/index.ts')}. Every app
542
- of the project runs the new code the moment it finishes; there is no publish step for them.
543
-
544
- Who may call a function is its callers, in the root xflow.json:
545
-
546
- { "projectId": "…", "functions": { "book-slot": { "callers": ["zapis"] } } }
547
-
548
- A caller is an app folder of this project (apps/zapis is "zapis"). A function nobody lists has no callers: a workflow, woken by a schedule, by a service
549
- holding a key a person issued, or by ${(0, ui_1.bold)('invoke')}. ${(0, ui_1.bold)('open')} in the list means a caller is an app
550
- anyone with the link opens, so anyone on the internet can call it. The address in the note
551
- under the table is for webhooks and other functions, with a key; apps call ${(0, ui_1.bold)('/_fn/<name>')} on
552
- their own address.
553
-
554
- ${(0, ui_1.bold)('invoke')} calls it as the person who owns the key: the platform checks the role, gives
555
- the function a one-minute ticket and calls it itself, so the function sees
556
- ${(0, ui_1.bold)("event.xflow.caller === 'platform'")} and the person in ${(0, ui_1.bold)('event.xflow.user')}.
557
-
558
- --data '{"a":1}' request body (the method becomes POST by default)
559
- --data-file body.json the same body from a file
560
- --method GET a different method
561
-
562
- The body has to be JSON, and it is checked before the call goes out: PowerShell
563
- strips double quotes from a native call, so ${(0, ui_1.bold)('--data')} needs them escaped there
564
- (${(0, ui_1.bold)('--data \'{\\"a\\":1}\'')}) and a file needs nothing.
565
-
566
- Prints the body of the answer and nothing else, so it goes straight into a pipe
567
- (${(0, ui_1.bold)('| jq')}, ${(0, ui_1.bold)('| python')}). A non-zero exit code on 4xx and 5xx: in CI such a call has to fail the step. A
568
- refusal of the guard names its reason in the body (${(0, ui_1.bold)('not_bound')}, ${(0, ui_1.bold)('no_access')},
538
+ functions: `${(0, ui_1.bold)('xflow functions')}: what is deployed, and calling it
539
+
540
+ xflow functions list name, state, callers, open, keys, last call and error
541
+ xflow functions invoke <name> call one as yourself, through the platform
542
+ xflow functions logs [name] crashes: stack and console output
543
+
544
+ Functions belong to the project and ship on their own step: ${(0, ui_1.bold)('xflow deploy functions')}
545
+ sends the project root and deploys everything in ${(0, ui_1.bold)('functions/<name>/index.ts')}. Every app
546
+ of the project runs the new code the moment it finishes; there is no publish step for them.
547
+
548
+ Who may call a function is its callers, in the root xflow.json:
549
+
550
+ { "projectId": "…", "functions": { "book-slot": { "callers": ["zapis"] } } }
551
+
552
+ A caller is an app folder of this project (apps/zapis is "zapis"). A function nobody lists has no callers: a workflow, woken by a schedule, by a service
553
+ holding a key a person issued, or by ${(0, ui_1.bold)('invoke')}. ${(0, ui_1.bold)('open')} in the list means a caller is an app
554
+ anyone with the link opens, so anyone on the internet can call it. The address in the note
555
+ under the table is for webhooks and other functions, with a key; apps call ${(0, ui_1.bold)('/_fn/<name>')} on
556
+ their own address.
557
+
558
+ ${(0, ui_1.bold)('invoke')} calls it as the person who owns the key: the platform checks the role, gives
559
+ the function a one-minute ticket and calls it itself, so the function sees
560
+ ${(0, ui_1.bold)("event.xflow.caller === 'platform'")} and the person in ${(0, ui_1.bold)('event.xflow.user')}.
561
+
562
+ --data '{"a":1}' request body (the method becomes POST by default)
563
+ --data-file body.json the same body from a file
564
+ --method GET a different method
565
+
566
+ The body has to be JSON, and it is checked before the call goes out: PowerShell
567
+ strips double quotes from a native call, so ${(0, ui_1.bold)('--data')} needs them escaped there
568
+ (${(0, ui_1.bold)('--data \'{\\"a\\":1}\'')}) and a file needs nothing.
569
+
570
+ Prints the body of the answer and nothing else, so it goes straight into a pipe
571
+ (${(0, ui_1.bold)('| jq')}, ${(0, ui_1.bold)('| python')}). A non-zero exit code on 4xx and 5xx: in CI such a call has to fail the step. A
572
+ refusal of the guard names its reason in the body (${(0, ui_1.bold)('not_bound')}, ${(0, ui_1.bold)('no_access')},
569
573
  ${(0, ui_1.bold)('functions_stopped')}); for a crash the hint points at ${(0, ui_1.bold)('xflow functions logs <name>')}.`,
570
- skills: `${(0, ui_1.bold)('xflow skills')}: platform instructions for an AI agent
571
-
572
- An agent has no way to know about XFlow: the platform is not in its training. The
573
- instructions explain how to release and publish, where the design system components
574
- come from, and where to look for production errors. ${(0, ui_1.bold)('init')} and ${(0, ui_1.bold)('link')} lay them
575
- down on their own for the common agents, so this command exists to pick tools
576
- precisely. Refreshing after a CLI update is not your job any more: the skill and the
577
- CLI ship as one version, and ${(0, ui_1.bold)('xflow update')} rewrites the copies you already have.
578
-
579
- Without flags it installs for Claude Code, Cursor and Codex in this project (globally:
580
- Claude Code, Codex and the shared folder) and refreshes every copy already installed.
581
-
582
- xflow skills list the agents, their folders and what is installed
583
- --agent claude,cursor exact agents instead of the defaults
584
- --global the home folder instead of the project
585
- --refresh rewrite the copies that exist, create none
586
-
587
- The format is shared (agentskills.io) and inside a project most tools read the shared
588
- .agents/skills folder, so the paths differ mostly in the home directory. Cursor also
589
- gets .cursor/rules/xflow.mdc: its older versions do not read the shared format.
590
-
591
- Plus a few pointer lines in ${(0, ui_1.bold)('AGENTS.md')}. A skill is picked up lazily, only when its
592
- description matches the task, and "add a customers table" will not trigger it. AGENTS.md
593
- is always read by the agent, which is why the pointer is appended there, at the end of
574
+ skills: `${(0, ui_1.bold)('xflow skills')}: platform instructions for an AI agent
575
+
576
+ An agent has no way to know about XFlow: the platform is not in its training. The
577
+ instructions explain how to release and publish, where the design system components
578
+ come from, and where to look for production errors. ${(0, ui_1.bold)('init')} and ${(0, ui_1.bold)('link')} lay them
579
+ down on their own for the common agents, so this command exists to pick tools
580
+ precisely. Refreshing after a CLI update is not your job any more: the skill and the
581
+ CLI ship as one version, and ${(0, ui_1.bold)('xflow update')} rewrites the copies you already have.
582
+
583
+ Without flags it installs for Claude Code, Cursor and Codex in this project (globally:
584
+ Claude Code, Codex and the shared folder) and refreshes every copy already installed.
585
+
586
+ xflow skills list the agents, their folders and what is installed
587
+ --agent claude,cursor exact agents instead of the defaults
588
+ --global the home folder instead of the project
589
+ --refresh rewrite the copies that exist, create none
590
+
591
+ The format is shared (agentskills.io) and inside a project most tools read the shared
592
+ .agents/skills folder, so the paths differ mostly in the home directory. Cursor also
593
+ gets .cursor/rules/xflow.mdc: its older versions do not read the shared format.
594
+
595
+ Plus a few pointer lines in ${(0, ui_1.bold)('AGENTS.md')}. A skill is picked up lazily, only when its
596
+ description matches the task, and "add a customers table" will not trigger it. AGENTS.md
597
+ is always read by the agent, which is why the pointer is appended there, at the end of
594
598
  the file and once. ${(0, ui_1.bold)('CLAUDE.md')} is left alone: Claude Code reads .claude/skills anyway.`,
595
- dev: `${(0, ui_1.bold)('xflow dev')}: run an app on this machine
596
-
597
- Runs ${(0, ui_1.bold)('npm run dev')} in the app folder it stands in (127.0.0.1:3000) with the access key
598
- of the organization that holds the project. Every ${(0, ui_1.bold)('/_fn/<name>')} call of the app goes to
599
- the platform as this app, so the callers, the access and the person calling are the same
600
- as on its address. Nothing about functions is kept on this machine: a function someone
601
- else shipped answers here at once.
602
-
603
- Use it instead of a bare ${(0, ui_1.bold)('npm run dev')}: that one is handed no key and no app id, so
604
- its calls to functions fail. The platform address comes from the CLI too, so the key goes
605
- to the platform that issued it and nowhere else. It never reaches the browser.
606
-
607
- The server listens on 127.0.0.1 and nowhere else, because it holds that key: on a
608
- shared network, anything else would let a neighbour call the functions of the project
599
+ dev: `${(0, ui_1.bold)('xflow dev')}: run an app on this machine
600
+
601
+ Runs ${(0, ui_1.bold)('npm run dev')} in the app folder it stands in (127.0.0.1:3000) with the access key
602
+ of the organization that holds the project. Every ${(0, ui_1.bold)('/_fn/<name>')} call of the app goes to
603
+ the platform as this app, so the callers, the access and the person calling are the same
604
+ as on its address. Nothing about functions is kept on this machine: a function someone
605
+ else shipped answers here at once.
606
+
607
+ Use it instead of a bare ${(0, ui_1.bold)('npm run dev')}: that one is handed no key and no app id, so
608
+ its calls to functions fail. The platform address comes from the CLI too, so the key goes
609
+ to the platform that issued it and nowhere else. It never reaches the browser.
610
+
611
+ The server listens on 127.0.0.1 and nowhere else, because it holds that key: on a
612
+ shared network, anything else would let a neighbour call the functions of the project
609
613
  as you. A phone on the same Wi-Fi therefore cannot open it either.`,
610
- pull: `${(0, ui_1.bold)('xflow pull')} [functions|apps/<folder>]: fetch the sources
611
-
612
- Every app and the project root are separate histories, and pull fetches one of them: in an
613
- app folder that app, in the project root the root (apps/ stays as it is), or the one named.
614
- It refuses to write into a non-empty folder: the platform cannot merge changes, that is
615
- git's job.
616
-
617
- --into <dir> unpack alongside, to compare; in the project root with nothing named
618
- it fetches the whole project there
619
- --revision <N> a specific revision of that history (xflow deployments lists them)
614
+ pull: `${(0, ui_1.bold)('xflow pull')} [functions|apps/<folder>]: fetch the sources
615
+
616
+ Every app and the project root are separate histories, and pull fetches one of them: in an
617
+ app folder that app, in the project root the root (apps/ stays as it is), or the one named.
618
+ It refuses to write into a non-empty folder: the platform cannot merge changes, that is
619
+ git's job.
620
+
621
+ --into <dir> unpack alongside, to compare; in the project root with nothing named
622
+ it fetches the whole project there
623
+ --revision <N> a specific revision of that history (xflow deployments lists them)
620
624
  --force overwrite the folder completely`,
621
- deploy: `${(0, ui_1.bold)('xflow deploy')}: deploy the functions or build an app, then publish
622
-
623
- xflow deploy functions -m "…" send the project root, check it, apply the new
624
- migrations, ship the functions and their callers
625
- xflow deploy apps/<folder> -m "…" send one app and build it; inside its folder a plain
626
- xflow deploy does the same
627
- xflow publish [build] point the app address at the newest build, or a named one
628
- xflow rollback <build> the same, the number required
629
- xflow deployments [functions] the builds of the app, or the deploys of the functions
630
-
631
- In the project root a plain ${(0, ui_1.bold)('xflow deploy')} refuses and names the targets: functions and
632
- apps never go in one run, because a change of functions reaches every app at once.
633
-
634
- -m <text> the version comment, required: one short line in the language the
635
- user speaks, saying what changed (--message is the long form)
636
- --revision <N> run a revision the platform already holds: that is how the functions
637
- come back (xflow deployments functions lists them)
638
- --project <id> with --revision only: the functions of that project, no folder needed
639
- --app <id> with --revision only: that app, no folder needed
640
- --force allow overwriting the server revision while sending
641
- --allow-removals agree in advance to remove the functions gone from the sources
642
-
643
- What goes up: an app sends its own folder; the project root sends everything but apps/,
644
- plus the apps/<folder>/xflow.json files its callers are read from. Never sent: node_modules,
645
- .git, dist, build, .next, any .env, plus everything listed in .xflowignore and in the
646
- ignore field of xflow.json.
647
-
648
- If the server holds a revision newer than the one you worked from, the sources are
649
- rejected and the build does not start. That means somebody deployed before you: fetch
650
- their changes alongside (${(0, ui_1.bold)('xflow pull --into ./server-copy')}), merge them in git on
651
- your side, and retry; or build what the server holds as it is with ${(0, ui_1.bold)('--revision <N>')}, the
652
- refusal names the number. A folder with no record of the server revision (a fresh clone, a CI
653
- checkout) is not rejected: its first deploy goes on top of the current revision and says
654
- so, the check applies from the next one. ${(0, ui_1.bold)('--force')} sends this folder over the server revision
655
- anyway. The overwritten revision is not lost at once: it stays in the history while it is among
656
- the last 10, and the deploy prints the ${(0, ui_1.bold)('xflow pull --revision <N>')} line that brings it back.
657
-
658
- The platform builds, in a clean sandbox on one Node version for everybody. Before that the
659
- sources are checked: what breaks silently (the platform files, the calls in the entry point,
660
- keys in the code, alert instead of a dialog) is printed as a list and nothing starts. What is
661
- merely unfinished (the template page title, no icon) goes through and prints a line
662
- ${(0, ui_1.bold)('Unfinished:')}. Fix it before telling the person the app is done.
663
-
664
- ${(0, ui_1.bold)('deploy functions')} first applies the new migrations of ${(0, ui_1.bold)('migrations/')}: if one fails or
665
- would destroy data, nothing ships, and ${(0, ui_1.bold)('xflow db migrate')} is where that gets settled. Then it
666
- bundles everything in ${(0, ui_1.bold)('functions/<name>/index.ts')}. A function whose
667
- code and variables did not change is left alone, and one that fails to ship fails the whole
668
- deploy. It then records the callers and prints which functions are open to the whole
669
- internet now. A change that touches data and screens goes in this order: the functions
670
- (with the migration), then every app that shows it; an app deploy warns when the root holds
671
- changes nobody deployed.
672
-
673
- A function gone from the sources would be deleted from the cloud along with its keys and
674
- schedules, and that is final. So the deploy does not start at all: it names the functions and stops, and ${(0, ui_1.bold)('--allow-removals')}
675
- is the way to agree. If the sources hold no functions at all while the cloud holds several, nothing
676
- is deleted and nothing is asked: that looks like a directory which never made it (a
677
- ${(0, ui_1.bold)('functions/')} line in .xflowignore) rather than a decision.
678
-
679
- A build of an app is not shown to visitors until ${(0, ui_1.bold)('xflow publish')}: publishing points the
680
- app address at a build and takes a second, and an older kept build is the rollback. Only
681
- the app moves: the functions and the database are one per project and stay as they are.
682
- The very first build of a new app publishes itself. The files of the three latest builds
683
- are kept; ${(0, ui_1.bold)('xflow deployments')} marks the rest "no pages". Give a person the app address
625
+ deploy: `${(0, ui_1.bold)('xflow deploy')}: deploy the functions or build an app, then publish
626
+
627
+ xflow deploy functions -m "…" send the project root, check it, apply the new
628
+ migrations, ship the functions and their callers
629
+ xflow deploy apps/<folder> -m "…" send one app and build it; inside its folder a plain
630
+ xflow deploy does the same
631
+ xflow publish [build] point the app address at the newest build, or a named one
632
+ xflow rollback <build> the same, the number required
633
+ xflow deployments [functions] the builds of the app, or the deploys of the functions
634
+
635
+ In the project root a plain ${(0, ui_1.bold)('xflow deploy')} refuses and names the targets: functions and
636
+ apps never go in one run, because a change of functions reaches every app at once.
637
+
638
+ -m <text> the version comment, required: one short line in the language the
639
+ user speaks, saying what changed (--message is the long form)
640
+ --revision <N> run a revision the platform already holds: that is how the functions
641
+ come back (xflow deployments functions lists them)
642
+ --project <id> with --revision only: the functions of that project, no folder needed
643
+ --app <id> with --revision only: that app, no folder needed
644
+ --force allow overwriting the server revision while sending
645
+ --allow-removals agree in advance to remove the functions gone from the sources
646
+
647
+ What goes up: an app sends its own folder; the project root sends everything but apps/,
648
+ plus the apps/<folder>/xflow.json files its callers are read from. Never sent: node_modules,
649
+ .git, dist, build, .next, any .env, plus everything listed in .xflowignore and in the
650
+ ignore field of xflow.json.
651
+
652
+ If the server holds a revision newer than the one you worked from, the sources are
653
+ rejected and the build does not start. That means somebody deployed before you: fetch
654
+ their changes alongside (${(0, ui_1.bold)('xflow pull --into ./server-copy')}), merge them in git on
655
+ your side, and retry; or build what the server holds as it is with ${(0, ui_1.bold)('--revision <N>')}, the
656
+ refusal names the number. A folder with no record of the server revision (a fresh clone, a CI
657
+ checkout) is not rejected: its first deploy goes on top of the current revision and says
658
+ so, the check applies from the next one. ${(0, ui_1.bold)('--force')} sends this folder over the server revision
659
+ anyway. The overwritten revision is not lost at once: it stays in the history while it is among
660
+ the last 10, and the deploy prints the ${(0, ui_1.bold)('xflow pull --revision <N>')} line that brings it back.
661
+
662
+ The platform builds, in a clean sandbox on one Node version for everybody. Before that the
663
+ sources are checked: what breaks silently (the platform files, the calls in the entry point,
664
+ keys in the code, alert instead of a dialog) is printed as a list and nothing starts. What is
665
+ merely unfinished (the template page title, no icon) goes through and prints a line
666
+ ${(0, ui_1.bold)('Unfinished:')}. Fix it before telling the person the app is done.
667
+
668
+ ${(0, ui_1.bold)('deploy functions')} first applies the new migrations of ${(0, ui_1.bold)('migrations/')}: if one fails or
669
+ would destroy data, nothing ships, and ${(0, ui_1.bold)('xflow db migrate')} is where that gets settled. Then it
670
+ bundles everything in ${(0, ui_1.bold)('functions/<name>/index.ts')}. A function whose
671
+ code and variables did not change is left alone, and one that fails to ship fails the whole
672
+ deploy. It then records the callers and prints which functions are open to the whole
673
+ internet now. A change that touches data and screens goes in this order: the functions
674
+ (with the migration), then every app that shows it; an app deploy warns when the root holds
675
+ changes nobody deployed.
676
+
677
+ A function gone from the sources would be deleted from the cloud along with its keys and
678
+ schedules, and that is final. So the deploy does not start at all: it names the functions and stops, and ${(0, ui_1.bold)('--allow-removals')}
679
+ is the way to agree. If the sources hold no functions at all while the cloud holds several, nothing
680
+ is deleted and nothing is asked: that looks like a directory which never made it (a
681
+ ${(0, ui_1.bold)('functions/')} line in .xflowignore) rather than a decision.
682
+
683
+ A build of an app is not shown to visitors until ${(0, ui_1.bold)('xflow publish')}: publishing points the
684
+ app address at a build and takes a second, and an older kept build is the rollback. Only
685
+ the app moves: the functions and the database are one per project and stay as they are.
686
+ The very first build of a new app publishes itself. The files of the three latest builds
687
+ are kept; ${(0, ui_1.bold)('xflow deployments')} marks the rest "no pages". Give a person the app address
684
688
  the CLI prints, and name builds by their number.`,
685
- init: `${(0, ui_1.bold)('xflow init')} [dir]: a new project with its first app
686
-
687
- Creates the project on the platform and lays out its folder: the root xflow.json, a
688
- package.json for the functions, empty ${(0, ui_1.bold)('functions/')} and ${(0, ui_1.bold)('migrations/')}, and the first app in
689
- apps/<folder> from the platform template: React on Vite, Tailwind, components in
690
- ${(0, ui_1.bold)('src/components/ui')} and blocks in ${(0, ui_1.bold)('src/components/blocks')}. Those are a starting set:
691
- change them, restyle them or delete what you do not use.
692
-
693
- --name <name> project name (the folder name by default)
694
- --app <folder> the folder of the first app (made from the name by default)
695
- --no-app a project without apps: its functions are workflows
696
- --org <name> the organization that gets the project; required when you are
697
- signed in to several (xflow org)
698
-
699
- More apps later: ${(0, ui_1.bold)('xflow apps add <folder>')}. If writing the files fails, the project stays
689
+ init: `${(0, ui_1.bold)('xflow init')} [dir]: a new project with its first app
690
+
691
+ Creates the project on the platform and lays out its folder: the root xflow.json, a
692
+ package.json for the functions, empty ${(0, ui_1.bold)('functions/')} and ${(0, ui_1.bold)('migrations/')}, and the first app in
693
+ apps/<folder> from the platform template: React on Vite, Tailwind, components in
694
+ ${(0, ui_1.bold)('src/components/ui')} and blocks in ${(0, ui_1.bold)('src/components/blocks')}. Those are a starting set:
695
+ change them, restyle them or delete what you do not use.
696
+
697
+ --name <name> project name (the folder name by default)
698
+ --app <folder> the folder of the first app (made from the name by default)
699
+ --no-app a project without apps: its functions are workflows
700
+ --org <name> the organization that gets the project; required when you are
701
+ signed in to several (xflow org)
702
+
703
+ More apps later: ${(0, ui_1.bold)('xflow apps add <folder>')}. If writing the files fails, the project stays
700
704
  on the platform and the CLI says how to pick it up with the link command.`,
701
705
  };