@getxflow/cli 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/help.js CHANGED
@@ -32,673 +32,670 @@ 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: stack and console output
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 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>
124
124
  Documentation: https://docs.getxflow.com (for agents: https://docs.getxflow.com/llms.txt)`);
125
125
  }
126
126
  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:
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:
155
155
  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,
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,
201
201
  ${(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
-
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
+
241
241
  ${(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
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
254
254
  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
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
277
277
  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
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
335
335
  separately. Write a new migration instead.`,
336
- logs: `${(0, ui_1.bold)('xflow logs')} and ${(0, ui_1.bold)('xflow functions logs')}: project errors
337
-
338
- Two streams: crashes of the cloud functions of the project, and browser errors of each
339
- app. ${(0, ui_1.bold)('xflow logs')} in an app folder (or with ${(0, ui_1.bold)('--app <id>')}) shows that app, in the
340
- project root every app, each row named by its folder. The last 200 entries of each stream
341
- are kept, older ones fall off.
342
-
343
- --limit N how many to show (10 by default)
344
-
345
- A function reports its own crashes: Yandex exposes its logs only over gRPC, so the
346
- wrapper catches the exception, carries the tail of the console with it (the last 40
347
- ${(0, ui_1.bold)('console.log')} lines of that call) and sends it to the platform. Successful calls
348
- write nothing, otherwise every request would pay for it in latency.
349
-
350
- What never reaches this list: a crash while the module is starting (the function
351
- never gets as far as the wrapper), going over the execution time cap of the plan
352
- (${(0, ui_1.bold)('xflow whoami')} names it), and running out of memory.
353
- Those show up in the answer to ${(0, ui_1.bold)('xflow functions invoke')}.
354
-
355
- Browser errors are collected by ${(0, ui_1.bold)('src/utils/error-logger.ts')} of the template and
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
356
353
  only from the published addresses: a local ${(0, ui_1.bold)('xflow dev')} writes nothing here.`,
357
- storage: `${(0, ui_1.bold)('xflow storage')}: files of the application
358
-
359
- The sources archive is capped at 10 MB and is rebuilt and re-uploaded on every
360
- deploy, so photos, video and PDFs do not belong in the repository. File storage
361
- is the place for them: the files sit beside the versions, survive a build, and
362
- are metered against the plan of the organization.
363
-
364
- xflow storage ls [folder] what is there, with addresses and sizes
365
- xflow storage push ./media --to media
366
- upload a folder, keeping its structure
367
- xflow storage rm <address> delete one file
368
- xflow storage rm --folder photos --yes
369
- delete a folder with everything in it
370
-
371
- --to <folder> where to put it in the project storage
372
- --replace upload files whose size differs from the stored one
373
- --json machine-readable output, with every address
374
-
375
- ${(0, ui_1.bold)('push')} sends only what is missing: a file already stored under the same path and
376
- size is skipped, so running the command again after a broken connection is cheap.
377
- A file that differs is left alone unless ${(0, ui_1.bold)('--replace')} is given. Bytes go straight
378
- into the bucket, they do not pass through the platform.
379
-
380
- Addresses are permanent and belong to the record, not to the bytes: replacing a
381
- file keeps its address, so the links in the code and in the tables of the
382
- application keep working. That is why there is no delete-and-upload-again.
383
-
384
- The address is not public: it is a page of the platform that checks the rights of
385
- the viewer on every request, so a closed project stops serving its pictures too.
386
- Use the address as it came back, in ${(0, ui_1.bold)('<img src>')} and in the data of the application.
387
-
388
- Dot entries and symbolic links are skipped, and ${(0, ui_1.bold)('.xflowignore')} is not read here:
389
- those are the rules for the sources, and a media folder is exactly what one is
390
- asked to put there so it stays out of the archive.
391
-
392
- ${(0, ui_1.bold)('rm')} needs a right of its own (${(0, ui_1.bold)('Delete files')}, where the key permissions live in
393
- the platform settings), off by default. The reason is the neighbourhood: whatever the users of
394
- the application uploaded lives in the same folders, and there is no undo. A folder
395
- first answers with the number of files it holds and deletes only on ${(0, ui_1.bold)('--yes')}.
396
-
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
+
397
394
  There is no command back: sources return with ${(0, ui_1.bold)('xflow pull')}, files do not.`,
398
- env: `${(0, ui_1.bold)('xflow env')}: environment variables of the functions
399
-
400
- Keys, passwords and third-party addresses are kept by the platform, not by the
401
- repository. A value goes up and never comes back: the only place to see it is inside
402
- the function. So keep your own copy wherever you got it from.
403
-
404
- xflow env what is stored (names, not values)
405
- xflow env check what the functions of this project are missing
406
- xflow env set SMTP_PASSWORD=… store
407
- xflow env rm SMTP_PASSWORD delete
408
-
409
- A variable belongs to this project: functions of other projects never see it.
410
-
411
- ${(0, ui_1.bold)('check')} reads the sources in ${(0, ui_1.bold)('functions/')} and looks for ${(0, ui_1.bold)('process.env.NAME')}
412
- references. The same rule applies on deploy: a function receives only the variables it
413
- mentions by name. A name assembled from an expression (${(0, ui_1.bold)("process.env['KEY_' + n]")}),
414
- reached through ${(0, ui_1.bold)('?.')}, destructured or spread never reaches the environment, so read
415
- variables literally. ${(0, ui_1.bold)('check')} names those reads and the build rejects them: a secret
416
- that quietly fails to arrive is found by the function falling over weeks later.
417
-
418
- The value reaches the function on deploy, not at the moment it is stored: after
419
- ${(0, ui_1.bold)('env set')} run ${(0, ui_1.bold)('xflow deploy functions')}. It ships a function whose code did not
420
- change but whose variables did, so nothing is left holding an old value. The same after a
421
- delete: a function already deployed keeps the old value until its next deploy. Apps get no
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
422
419
  variables at all: whatever a secret protects is reached through a function.`,
423
- connections: `${(0, ui_1.bold)('xflow connections')}: accounts connected to the organization
424
-
425
- Somebody signs in to Yandex Metrika, Bitrix or a mail service once, in the platform,
426
- and that account becomes a connection of the organization. Linked to a project, it
427
- hands its credentials to the cloud functions as environment variables. Nobody has to
428
- paste a token into the repository, and nobody sees the value: it goes straight from
429
- the platform into the function.
430
-
431
- xflow connections what this project can use, and what it already uses
432
- xflow connections link <name> --as ALIAS
433
- hand its credentials to the functions
434
- xflow connections unlink <ALIAS> take them away again
435
- xflow connections call <name> <path>
436
- ask that API something, once, without deploying
437
-
438
- Every row says whether the connection is linked to this project (its alias), which of
439
- its fields a call can fill in (${(0, ui_1.bold)('{CLIENT_ID}')} and the like) and what state the
440
- access is in. ${(0, ui_1.bold)('available, not linked')} is the useful one: the account exists in
441
- the organization, but this project gets nothing from it yet: link it.
442
-
443
- Both commands take the name of the connection, the first column of the list, and both
444
- also take its identifier. ${(0, ui_1.bold)('unlink')} takes the alias as well, and prefers it: the
445
- alias is the name your own code already uses. One organization can hold several accounts
446
- of the same service under one name; then the command prints their identifiers and asks
447
- for one of those instead of guessing.
448
-
449
- ${(0, ui_1.bold)('unlink')} refuses while a function still reads one of the variables and names
450
- those functions; ${(0, ui_1.bold)('--yes')} goes through anyway. Seeing them needs the sources,
451
- so under ${(0, ui_1.bold)('--project')} there is no such check: the link goes, and the variables
452
- that go with it are printed instead. Both commands take the flag, because an account
453
- linked to a service project without a folder has to be removable the same way.
454
-
455
- Variables are named after the alias: an OAuth connection called ${(0, ui_1.bold)('YANDEX_METRIKA')}
456
- gives ${(0, ui_1.bold)('YANDEX_METRIKA_TOKEN')}, a key-based one gives a variable per field. They
457
- arrive at a function on its next deploy, like every other variable, so after a change
458
- run ${(0, ui_1.bold)('xflow deploy functions')}. A token close to expiry is renewed by that deploy on the
459
- way; a revoked one is not renewed by anything until a human reconnects the account.
460
-
461
- Linking needs a right on the key, ${(0, ui_1.bold)('connections:link')}. Keys are issued with it,
462
- and a person can take it away in the platform settings, where the key permissions live; a key cannot give
463
- it back to itself. Losing it is what the refusal says, and the answer is to ask a person,
464
- not to look for another route. Either way only the accounts granted to you personally can
465
- be linked at all: that rule holds whatever the key is allowed to do.
466
-
467
- Connecting a new account and switching one off stay with a person, in the platform
468
- interface. There is no command for either.
469
-
470
- ${(0, ui_1.bold)('call')} answers the question an API only answers by being asked: what does this
471
- endpoint actually return, and is the field you need in it. The platform attaches the
472
- credentials of the connected account and makes the request itself, so nothing has to be
473
- deployed to find out, and the key never reaches you.
474
-
475
- xflow connections call WB /api/v1/supplier/stocks?dateFrom=2026-09-01
476
- xflow connections call AMO /api/v4/leads --method POST --data '{"name":"Test"}'
477
- xflow connections call OZON /v1/product/list --method POST --data '{}' \\
478
- --header '{"Client-Id":"{CLIENT_ID}"}'
479
-
480
- The path is written after the address of the connector, query string and all, or given in
481
- full as ${(0, ui_1.bold)('https://...')}. Only the hosts of that connector are reachable, and a
482
- refusal lists them. ${(0, ui_1.bold)('--method')} defaults to GET, or to POST when there is a body.
483
-
484
- ${(0, ui_1.bold)('{NAME}')} in the path or in a header value is filled in by the platform from the
485
- fields of the connection. That is how the two-header APIs are called: Ozon wants
486
- ${(0, ui_1.bold)('Client-Id')} beside its key, Yandex Direct wants ${(0, ui_1.bold)('Client-Login')} for an
487
- agency account, and you never see those values. Which names a connection has is the fourth
488
- column of ${(0, ui_1.bold)('xflow connections')}, printed in braces and the same whether the account
489
- is linked or not. Do not derive them from ${(0, ui_1.bold)('xflow env')}: that list is the variables
490
- of a link, and it names both more and less than this one. A name the connection does not
491
- have is left in the text as you typed it and the request still goes out, so an invented
492
- name reaches somebody's live account as text.
493
-
494
- The answer is printed head first, at most a few kilobytes, and it says so when there is
495
- more. Ask for the shape, not the volume: one day and ${(0, ui_1.bold)('limit=1')} tells you what the
496
- fields are called, while the whole dataset belongs in a cloud function. ${(0, ui_1.bold)('--json')}
497
- prints the envelope for a pipe: ${(0, ui_1.bold)("--json | jq '.response.body | keys'")} gives the
498
- field names without the body ever entering your context.
499
-
500
- An account linked to this project is called by its alias. One that is only available is
501
- called by its name, and no linking is needed: what the command asks is that you could have
502
- linked it yourself, which needs the ${(0, ui_1.bold)('connections:link')} right and the account being
503
- granted to you personally. Either way nothing widens: linked credentials already reach the
504
- functions of this project, and an account you may link you may also read by deploying a
505
- function into it. An account granted to somebody else stays out of reach, and the refusal
506
- says which of the two is missing, the right or the grant.
507
-
508
- The call goes out for real: ${(0, ui_1.bold)('POST')} and ${(0, ui_1.bold)('DELETE')} change things in
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
509
506
  somebody's live account, and no flag will undo that.`,
510
- schedules: `${(0, ui_1.bold)('xflow schedules')}: running functions on a timer
511
-
512
- A schedule is a Yandex timer trigger: it calls the function itself, with no
513
- application and no browser involved.
514
-
515
- xflow schedules set report "0 3 ? * * *" every day at 03:00
516
- xflow schedules set digest "0 */4 ? * * *" every 4 hours
517
- xflow schedules rm report take off every schedule of a function
518
-
519
- Six fields: ${(0, ui_1.bold)('minutes hours day-of-month month day-of-week year')}. Exactly one of
520
- the day-of-month and day-of-week fields has to be ${(0, ui_1.bold)('?')}, which is not our quirk but
521
- how Yandex works. ${(0, ui_1.bold)('The time is always UTC')}, local time is not understood.
522
-
523
- --payload '{"mode":"full"}' the body the function will receive
524
- --payload-file body.json the same body from a file
525
-
526
- ${(0, ui_1.bold)('list')} prints that body next to the schedule, so what a timer sends can be read
527
- back without setting it again.
528
-
529
- A scheduled run arrives at the handler as a POST with ${(0, ui_1.bold)("event.xflow.caller === 'schedule'")}:
530
- the envelope is signed by the platform, so such an event cannot be forged from outside. A
531
- function without callers that only a schedule wakes is a workflow, and a project without
532
- apps is made of those. The function has to be deployed already, since the schedule points
533
- at it and not the other way round.
534
-
535
- A failed run shows up in ${(0, ui_1.bold)('xflow functions logs')}: nobody is watching a scheduled
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
536
533
  function, so the wrapper reports a crash the same way it does on an ordinary call.`,
537
- functions: `${(0, ui_1.bold)('xflow functions')}: what is deployed, and calling it
538
-
539
- xflow functions list name, state, callers, open, keys, last call and error
540
- xflow functions invoke <name> call one as yourself, through the platform
541
- xflow functions logs [name] crashes: stack and console output
542
-
543
- Functions belong to the project and ship on their own step: ${(0, ui_1.bold)('xflow deploy functions')}
544
- sends the project root and deploys everything in ${(0, ui_1.bold)('functions/<name>/index.ts')}. Every app
545
- of the project runs the new code the moment it finishes; there is no publish step for them.
546
-
547
- Who may call a function is its callers, in the root xflow.json:
548
-
549
- { "projectId": "…", "functions": { "book-slot": { "callers": ["zapis"] } } }
550
-
551
- 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
552
- 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
553
- anyone with the link opens, so anyone on the internet can call it. The address in the note
554
- under the table is for webhooks and other functions, with a key; apps call ${(0, ui_1.bold)('/_fn/<name>')} on
555
- their own address.
556
-
557
- ${(0, ui_1.bold)('invoke')} calls it as the person who owns the key: the platform checks the role, gives
558
- the function a one-minute ticket and calls it itself, so the function sees
559
- ${(0, ui_1.bold)("event.xflow.caller === 'platform'")} and the person in ${(0, ui_1.bold)('event.xflow.user')}.
560
-
561
- --data '{"a":1}' request body (the method becomes POST by default)
562
- --data-file body.json the same body from a file
563
- --method GET a different method
564
-
565
- The body has to be JSON, and it is checked before the call goes out: PowerShell
566
- strips double quotes from a native call, so ${(0, ui_1.bold)('--data')} needs them escaped there
567
- (${(0, ui_1.bold)('--data \'{\\"a\\":1}\'')}) and a file needs nothing.
568
-
569
- Prints the body of the answer and nothing else, so it goes straight into a pipe
570
- (${(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
571
- refusal of the guard names its reason in the body (${(0, ui_1.bold)('not_bound')}, ${(0, ui_1.bold)('no_access')},
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')},
572
569
  ${(0, ui_1.bold)('functions_stopped')}); for a crash the hint points at ${(0, ui_1.bold)('xflow functions logs <name>')}.`,
573
- skills: `${(0, ui_1.bold)('xflow skills')}: platform instructions for an AI agent
574
-
575
- An agent has no way to know about XFlow: the platform is not in its training. The
576
- instructions explain how to release and publish, where the design system components
577
- come from, and where to look for production errors. ${(0, ui_1.bold)('init')} and ${(0, ui_1.bold)('link')} lay them
578
- down on their own for the common agents, so this command exists to pick tools
579
- precisely. Refreshing after a CLI update is not your job any more: the skill and the
580
- CLI ship as one version, and ${(0, ui_1.bold)('xflow update')} rewrites the copies you already have.
581
-
582
- Without flags it installs for Claude Code, Cursor and Codex in this project (globally:
583
- Claude Code, Codex and the shared folder) and refreshes every copy already installed.
584
-
585
- xflow skills list the agents, their folders and what is installed
586
- --agent claude,cursor exact agents instead of the defaults
587
- --global the home folder instead of the project
588
- --refresh rewrite the copies that exist, create none
589
-
590
- The format is shared (agentskills.io) and inside a project most tools read the shared
591
- .agents/skills folder, so the paths differ mostly in the home directory. Cursor also
592
- gets .cursor/rules/xflow.mdc: its older versions do not read the shared format.
593
-
594
- Plus a few pointer lines in ${(0, ui_1.bold)('AGENTS.md')}. A skill is picked up lazily, only when its
595
- description matches the task, and "add a customers table" will not trigger it. AGENTS.md
596
- is always read by the agent, which is why the pointer is appended there, at the end of
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
597
594
  the file and once. ${(0, ui_1.bold)('CLAUDE.md')} is left alone: Claude Code reads .claude/skills anyway.`,
598
- dev: `${(0, ui_1.bold)('xflow dev')}: run an app on this machine
599
-
600
- Runs ${(0, ui_1.bold)('npm run dev')} in the app folder it stands in (127.0.0.1:3000) with the access key
601
- of the organization that holds the project. Every ${(0, ui_1.bold)('/_fn/<name>')} call of the app goes to
602
- the platform as this app, so the callers, the access and the person calling are the same
603
- as on its address. Nothing about functions is kept on this machine: a function someone
604
- else shipped answers here at once.
605
-
606
- Use it instead of a bare ${(0, ui_1.bold)('npm run dev')}: that one is handed no key and no app id, so
607
- its calls to functions fail. The platform address comes from the CLI too, so the key goes
608
- to the platform that issued it and nowhere else. It never reaches the browser.
609
-
610
- The server listens on 127.0.0.1 and nowhere else, because it holds that key: on a
611
- shared network, anything else would let a neighbour call the functions of the project
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
612
609
  as you. A phone on the same Wi-Fi therefore cannot open it either.`,
613
- pull: `${(0, ui_1.bold)('xflow pull')} [functions|apps/<folder>]: fetch the sources
614
-
615
- Every app and the project root are separate histories, and pull fetches one of them: in an
616
- app folder that app, in the project root the root (apps/ stays as it is), or the one named.
617
- It refuses to write into a non-empty folder: the platform cannot merge changes, that is
618
- git's job.
619
-
620
- --into <dir> unpack alongside, to compare; in the project root with nothing named
621
- it fetches the whole project there
622
- --revision <N> a specific revision of that history (xflow deployments lists them)
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)
623
620
  --force overwrite the folder completely`,
624
- deploy: `${(0, ui_1.bold)('xflow deploy')}: deploy the functions or build an app, then publish
625
-
626
- xflow deploy functions -m "…" send the project root, check it, apply the new
627
- migrations, ship the functions and their callers
628
- xflow deploy apps/<folder> -m "…" send one app and build it; inside its folder a plain
629
- xflow deploy does the same
630
- xflow publish [build] point the app address at the newest build, or a named one
631
- xflow rollback <build> the same, the number required
632
- xflow deployments [functions] the builds of the app, or the deploys of the functions
633
-
634
- In the project root a plain ${(0, ui_1.bold)('xflow deploy')} refuses and names the targets: functions and
635
- apps never go in one run, because a change of functions reaches every app at once.
636
-
637
- -m <text> the version comment, required: one short line in the language the
638
- user speaks, saying what changed (--message is the long form)
639
- --revision <N> run a revision the platform already holds: that is how the functions
640
- come back (xflow deployments functions lists them)
641
- --project <id> with --revision only: the functions of that project, no folder needed
642
- --app <id> with --revision only: that app, no folder needed
643
- --force allow overwriting the server revision while sending
644
- --allow-removals agree in advance to remove the functions gone from the sources
645
-
646
- What goes up: an app sends its own folder; the project root sends everything but apps/,
647
- plus the apps/<folder>/xflow.json files its callers are read from. Never sent: node_modules,
648
- .git, dist, build, .next, any .env, plus everything listed in .xflowignore and in the
649
- ignore field of xflow.json.
650
-
651
- If the server holds a revision newer than the one you worked from, the sources are
652
- rejected and the build does not start. That means somebody deployed before you: fetch
653
- their changes alongside (${(0, ui_1.bold)('xflow pull --into ./server-copy')}), merge them in git on
654
- your side, and retry; or build what the server holds as it is with ${(0, ui_1.bold)('--revision <N>')}, the
655
- refusal names the number. A folder with no record of the server revision (a fresh clone, a CI
656
- checkout) is not rejected: its first deploy goes on top of the current revision and says
657
- so, the check applies from the next one. ${(0, ui_1.bold)('--force')} sends this folder over the server revision
658
- anyway. The overwritten revision is not lost at once: it stays in the history while it is among
659
- the last 10, and the deploy prints the ${(0, ui_1.bold)('xflow pull --revision <N>')} line that brings it back.
660
-
661
- The platform builds, in a clean sandbox on one Node version for everybody. Before that the
662
- sources are checked: what breaks silently (the platform files, the calls in the entry point,
663
- keys in the code, alert instead of a dialog) is printed as a list and nothing starts. What is
664
- merely unfinished (the template page title, no icon) goes through and prints a line
665
- ${(0, ui_1.bold)('Unfinished:')}. Fix it before telling the person the app is done.
666
-
667
- ${(0, ui_1.bold)('deploy functions')} first applies the new migrations of ${(0, ui_1.bold)('migrations/')}: if one fails or
668
- would destroy data, nothing ships, and ${(0, ui_1.bold)('xflow db migrate')} is where that gets settled. Then it
669
- bundles everything in ${(0, ui_1.bold)('functions/<name>/index.ts')}. A function whose
670
- code and variables did not change is left alone, and one that fails to ship fails the whole
671
- deploy. It then records the callers and prints which functions are open to the whole
672
- internet now. A change that touches data and screens goes in this order: the functions
673
- (with the migration), then every app that shows it; an app deploy warns when the root holds
674
- changes nobody deployed.
675
-
676
- A function gone from the sources would be deleted from the cloud along with its keys and
677
- 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')}
678
- is the way to agree. If the sources hold no functions at all while the cloud holds several, nothing
679
- is deleted and nothing is asked: that looks like a directory which never made it (a
680
- ${(0, ui_1.bold)('functions/')} line in .xflowignore) rather than a decision.
681
-
682
- A build of an app is not shown to visitors until ${(0, ui_1.bold)('xflow publish')}: publishing points the
683
- app address at a build and takes a second, and an older kept build is the rollback. Only
684
- the app moves: the functions and the database are one per project and stay as they are.
685
- The very first build of a new app publishes itself. The files of the three latest builds
686
- are kept; ${(0, ui_1.bold)('xflow deployments')} marks the rest "no pages". Give a person the app address
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
687
684
  the CLI prints, and name builds by their number.`,
688
- init: `${(0, ui_1.bold)('xflow init')} [dir]: a new project with its first app
689
-
690
- Creates the project on the platform and lays out its folder: the root xflow.json, a
691
- package.json for the functions, empty ${(0, ui_1.bold)('functions/')} and ${(0, ui_1.bold)('migrations/')}, and the first app in
692
- apps/<folder> from the platform template: React on Vite, Tailwind, components in
693
- ${(0, ui_1.bold)('src/components/ui')} and blocks in ${(0, ui_1.bold)('src/components/blocks')}. Those are a starting set:
694
- change them, restyle them or delete what you do not use.
695
-
696
- --name <name> project name (the folder name by default)
697
- --app <folder> the folder of the first app (made from the name by default)
698
- --no-app a project without apps: its functions are workflows
699
- --org <name> the organization that gets the project; required when you are
700
- signed in to several (xflow org)
701
-
702
- More apps later: ${(0, ui_1.bold)('xflow apps add <folder>')}. If writing the files fails, the project stays
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
703
700
  on the platform and the CLI says how to pick it up with the link command.`,
704
701
  };