@getxflow/cli 0.9.1 → 0.10.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
@@ -9,404 +9,453 @@ function help(topic) {
9
9
  (0, ui_1.out)(TOPICS[topic]);
10
10
  return;
11
11
  }
12
- (0, ui_1.out)(`${(0, ui_1.bold)('xflow')} ${(0, ui_1.dim)(version_1.CLI_VERSION)} XFlow application hosting
13
-
14
- ${(0, ui_1.bold)('Getting started')}
15
- xflow login sign in through the browser
16
- xflow init [dir] new project from the platform template
17
- xflow templates which templates are available
18
- xflow link <id> link this folder to a project, an empty one also gets the sources
19
- xflow skills pick the agents that get the platform instructions
20
- xflow mcp install give the agent platform access without a terminal
21
-
22
- ${(0, ui_1.bold)('Sources')}
23
- xflow status what is on the server and how the local copy differs
24
- xflow pull [--into dir] [--revision N]
25
- fetch the sources (the latest revision by default)
26
-
27
- ${(0, ui_1.bold)('Releasing')}
28
- xflow deploy [--no-push] send the code, ship the functions, build on the platform
29
- xflow publish show the dev version to visitors
30
- xflow rollback <version number> serve the pages of an earlier build
31
- xflow deployments version history
32
-
33
- ${(0, ui_1.bold)('Functions')}
34
- xflow functions list what is deployed (they ship with xflow deploy)
35
- xflow functions invoke <name> [--data '{"a":1}']
36
- call a function and print the answer
37
- xflow functions logs [name] function crashes: stack and console output
38
- xflow schedules list what runs on a timer
39
- xflow schedules set <name> "0 3 ? * * *"
40
- run a function on a schedule (UTC)
41
- xflow schedules rm <name> take a schedule off
42
- xflow env [check] function variables: what is stored, what is missing
43
- xflow env set NAME=value store a variable
44
- xflow env rm NAME delete a variable
45
- xflow connections connected accounts the project can use, and their variables
46
- xflow connections link <name> --as ALIAS
47
- give the functions the credentials of an account
48
- xflow connections unlink <ALIAS> [--force]
49
- take them away again
50
-
51
- ${(0, ui_1.bold)('Database')}
52
- xflow db status which migrations are applied and which are waiting
53
- xflow db schema [table] tables of the project, or the columns of one
54
- xflow db query "select ..." read data, in a read-only transaction
55
- xflow db migrate [--dry-run] [--allow-destructive]
56
- apply the migrations from migrations/*.sql
57
-
58
- ${(0, ui_1.bold)('Reference')}
59
- xflow logs [--limit N] browser errors from the released application
60
- xflow projects list projects of the organization
61
- xflow projects get [id] project card
62
- xflow org organizations with a stored key, the active one marked
63
- xflow org switch <name|id> make another organization the active one
64
- xflow whoami whose key this is and what it can do
65
- xflow logout [--all] forget the key of the active organization (--all: every one)
66
- xflow update update the CLI itself, and the skill that ships with it
67
-
68
- ${(0, ui_1.bold)('Environment')}
69
- XFLOW_TOKEN access key (for CI, instead of xflow login)
70
- XFLOW_API_URL platform address when it is not app.getxflow.com
71
-
12
+ (0, ui_1.out)(`${(0, ui_1.bold)('xflow')} ${(0, ui_1.dim)(version_1.CLI_VERSION)} XFlow application hosting
13
+
14
+ ${(0, ui_1.bold)('Getting started')}
15
+ xflow login sign in through the browser
16
+ xflow init [dir] new project from the platform template
17
+ xflow templates which templates are available
18
+ xflow link <id> link this folder to a project, an empty one also gets the sources
19
+ xflow skills pick the agents that get the platform instructions
20
+ xflow mcp install give the agent platform access without a terminal
21
+
22
+ ${(0, ui_1.bold)('Sources')}
23
+ xflow status what is on the server and how the local copy differs
24
+ xflow pull [--into dir] [--revision N]
25
+ fetch the sources (the latest revision by default)
26
+
27
+ ${(0, ui_1.bold)('Files')}
28
+ xflow storage ls [folder] what the file storage of the project holds
29
+ xflow storage push <dir> [--to folder] [--replace]
30
+ upload heavy static assets: photos, video, PDFs
31
+ xflow storage rm <address> delete one file
32
+ xflow storage rm --folder <path> --yes
33
+ delete a folder with everything in it
34
+
35
+ ${(0, ui_1.bold)('Releasing')}
36
+ xflow deploy [--no-push] send the code, ship the functions, build on the platform
37
+ xflow publish show the dev version to visitors
38
+ xflow rollback <version number> serve the pages of an earlier build
39
+ xflow deployments version history
40
+
41
+ ${(0, ui_1.bold)('Functions')}
42
+ xflow functions list what is deployed (they ship with xflow deploy)
43
+ xflow functions invoke <name> [--data '{"a":1}']
44
+ call a function and print the answer
45
+ xflow functions logs [name] function crashes: stack and console output
46
+ xflow schedules list what runs on a timer
47
+ xflow schedules set <name> "0 3 ? * * *"
48
+ run a function on a schedule (UTC)
49
+ xflow schedules rm <name> take a schedule off
50
+ xflow env [check] function variables: what is stored, what is missing
51
+ xflow env set NAME=value store a variable
52
+ xflow env rm NAME delete a variable
53
+ xflow connections connected accounts the project can use, and their variables
54
+ xflow connections link <name> --as ALIAS
55
+ give the functions the credentials of an account
56
+ xflow connections unlink <ALIAS> [--force]
57
+ take them away again
58
+
59
+ ${(0, ui_1.bold)('Database')}
60
+ xflow db status which migrations are applied and which are waiting
61
+ xflow db schema [table] tables of the project, or the columns of one
62
+ xflow db query "select ..." read data, in a read-only transaction
63
+ xflow db migrate [--dry-run] [--allow-destructive]
64
+ apply the migrations from migrations/*.sql
65
+
66
+ ${(0, ui_1.bold)('Reference')}
67
+ xflow logs [--limit N] browser errors from the released application
68
+ xflow projects list projects of the organization
69
+ xflow projects get [id] project card
70
+ xflow org organizations with a stored key, the active one marked
71
+ xflow org switch <name|id> make another organization the active one
72
+ xflow whoami whose key this is and what it can do
73
+ xflow logout [--all] forget the key of the active organization (--all: every one)
74
+ xflow update update the CLI itself, and the skill that ships with it
75
+
76
+ ${(0, ui_1.bold)('Environment')}
77
+ XFLOW_TOKEN access key (for CI, instead of xflow login)
78
+ XFLOW_API_URL platform address when it is not app.getxflow.com
79
+
72
80
  More about one command: xflow help <command>`);
73
81
  }
74
82
  const TOPICS = {
75
- org: `${(0, ui_1.bold)('xflow org')}: several organizations, one terminal
76
-
77
- One key per organization: every ${(0, ui_1.bold)('xflow login')} stores the key of the organization
78
- chosen in the browser next to the ones already stored, it does not replace them.
79
- Which key a command then uses, in order:
80
-
81
- 1. XFLOW_TOKEN, when set (and only for the default platform address)
82
- 2. the organization this folder is bound to (${(0, ui_1.bold)('.xflow/state.json')}, written by
83
- init, link and the first successful deploy or pull)
84
- 3. the active organization
85
-
86
- xflow org the stored organizations, the active one marked
87
- xflow org switch <name|id> make another one active, no browser involved
88
- xflow logout forget the key of the active organization
89
- xflow logout --all forget every key of this platform address
90
-
91
- The switch is local: it changes which stored key is used and nothing happens on
92
- the platform. A folder bound to an organization is not affected, its commands
93
- stay in its own organization: two projects of two organizations in two terminals
94
- work without switching anything.
95
-
96
- Outside a project folder the switch also rewrites the key in the agent config
97
- (see ${(0, ui_1.bold)('xflow mcp')}) when an xflow entry already exists there; a running agent
98
- session picks the new key up only after a restart.
99
-
100
- A project id is unique across the whole platform, so "project not found" under
101
- the wrong organization can never touch somebody else's project. When that error
102
- names a project you know exists, the key is simply from another organization:
83
+ org: `${(0, ui_1.bold)('xflow org')}: several organizations, one terminal
84
+
85
+ One key per organization: every ${(0, ui_1.bold)('xflow login')} stores the key of the organization
86
+ chosen in the browser next to the ones already stored, it does not replace them.
87
+ Which key a command then uses, in order:
88
+
89
+ 1. XFLOW_TOKEN, when set (and only for the default platform address)
90
+ 2. the organization this folder is bound to (${(0, ui_1.bold)('.xflow/state.json')}, written by
91
+ init, link and the first successful deploy or pull)
92
+ 3. the active organization
93
+
94
+ xflow org the stored organizations, the active one marked
95
+ xflow org switch <name|id> make another one active, no browser involved
96
+ xflow logout forget the key of the active organization
97
+ xflow logout --all forget every key of this platform address
98
+
99
+ The switch is local: it changes which stored key is used and nothing happens on
100
+ the platform. A folder bound to an organization is not affected, its commands
101
+ stay in its own organization: two projects of two organizations in two terminals
102
+ work without switching anything.
103
+
104
+ Outside a project folder the switch also rewrites the key in the agent config
105
+ (see ${(0, ui_1.bold)('xflow mcp')}) when an xflow entry already exists there; a running agent
106
+ session picks the new key up only after a restart.
107
+
108
+ A project id is unique across the whole platform, so "project not found" under
109
+ the wrong organization can never touch somebody else's project. When that error
110
+ names a project you know exists, the key is simply from another organization:
103
111
  check ${(0, ui_1.bold)('xflow org')}.`,
104
- update: `${(0, ui_1.bold)('xflow update')}: bring the CLI up to date
105
-
106
- Installs the published version and, if anything changed, rewrites the platform
107
- instructions the agents already have: the skill ships inside the package and shares
108
- its version, so a fresh CLI next to a stale skill means the agent is reading last
109
- month's rules. Copies that are not installed are not created, so it is safe to run
110
- from any folder.
111
-
112
- The platform reports the published version with every answer, which is where the
113
- ${(0, ui_1.bold)('xflow N is out')} line comes from. It appears once a day at most, and never in CI,
114
- where nobody is going to update anything anyway.
115
-
116
- The command works out how this copy was installed from its own path, and refuses to
117
- cross package managers: an install made by pnpm or bun is printed as their own
118
- command rather than handed to npm, which would leave you with two copies and no way
119
- to tell which one answers. Two more cases end the same way. Under ${(0, ui_1.bold)('npx')} there is
120
- nothing to update, only a cache. And a copy inside a project's node_modules is a
121
- dependency of that project: ${(0, ui_1.bold)('npm i -D @getxflow/cli@latest')} is the fix, because
122
- inside npm scripts the local copy wins over anything installed globally.
123
-
124
- A refusal from the platform saying the CLI is too old is a different thing: that one
125
- is a hard gate on a changed contract, and until you run this command nothing else
112
+ update: `${(0, ui_1.bold)('xflow update')}: bring the CLI up to date
113
+
114
+ Installs the published version and, if anything changed, rewrites the platform
115
+ instructions the agents already have: the skill ships inside the package and shares
116
+ its version, so a fresh CLI next to a stale skill means the agent is reading last
117
+ month's rules. Copies that are not installed are not created, so it is safe to run
118
+ from any folder.
119
+
120
+ The platform reports the published version with every answer, which is where the
121
+ ${(0, ui_1.bold)('xflow N is out')} line comes from. It appears once a day at most, and never in CI,
122
+ where nobody is going to update anything anyway.
123
+
124
+ The command works out how this copy was installed from its own path, and refuses to
125
+ cross package managers: an install made by pnpm or bun is printed as their own
126
+ command rather than handed to npm, which would leave you with two copies and no way
127
+ to tell which one answers. Two more cases end the same way. Under ${(0, ui_1.bold)('npx')} there is
128
+ nothing to update, only a cache. And a copy inside a project's node_modules is a
129
+ dependency of that project: ${(0, ui_1.bold)('npm i -D @getxflow/cli@latest')} is the fix, because
130
+ inside npm scripts the local copy wins over anything installed globally.
131
+
132
+ A refusal from the platform saying the CLI is too old is a different thing: that one
133
+ is a hard gate on a changed contract, and until you run this command nothing else
126
134
  will work.`,
127
- db: `${(0, ui_1.bold)('xflow db')}: schema, data, migrations
128
-
129
- xflow db status what is applied and what is waiting
130
- xflow db schema [table] tables of the project, or the columns of one
131
- xflow db query "select ..." read data (--limit N)
132
- xflow db migrate apply migrations/*.sql
133
-
134
- ${(0, ui_1.bold)('schema')} answers a different question than the ${(0, ui_1.bold)('migrations/')} directory: one logical
135
- database is shared by several projects, so the files say what you did, and the schema
136
- says what is actually in there. ${(0, ui_1.bold)('query')} runs inside a READ ONLY transaction, so
137
- writes are rejected by the database itself, not by us reading your SQL.
138
-
139
- ${(0, ui_1.bold)('migrate')}: the files live in the repository (${(0, ui_1.bold)('migrations/0001_init.sql')},
140
- ${(0, ui_1.bold)('migrations/0002_orders.sql')} and so on). The order comes from the file name, which
141
- is why the leading number is required. Every migration runs in its own transaction, the
142
- history is kept in the database itself, and anything already applied is not run again.
143
-
144
- --dry-run report what would be applied without touching the database
145
- --allow-destructive allow operations that destroy data
146
-
147
- About destructive ones. The platform keeps no database history and makes no backups,
148
- so ${(0, ui_1.bold)('DROP TABLE')}, ${(0, ui_1.bold)('DROP COLUMN')}, ${(0, ui_1.bold)('TRUNCATE')} and ${(0, ui_1.bold)('DELETE FROM')} without a
149
- condition need two things at once: this flag, and the right to destroy data on the
150
- access key. That right is off by default, and only its owner turns it on, in the
151
- platform settings under Developers: a flag is something an agent adds by itself, a
152
- right is not. With both in place, the contents of the affected tables are dumped and
153
- kept for 7 days: that is a "caught it right away" safety net, not a backup.
154
- ${(0, ui_1.bold)('DROP DATABASE')} is never allowed, because the database is shared across the
155
- organization.
156
-
157
- Editing an already applied file achieves nothing: the comparison is by name, not by
158
- content. ${(0, ui_1.bold)('xflow db status')} lists such a file separately, so write a new migration
159
- instead.
160
-
161
- One logical database can be attached to several projects, so the history may hold
162
- migrations that are not in your repository. That is normal, but it also means your
135
+ db: `${(0, ui_1.bold)('xflow db')}: schema, data, migrations
136
+
137
+ xflow db status what is applied and what is waiting
138
+ xflow db schema [table] tables of the project, or the columns of one
139
+ xflow db query "select ..." read data (--limit N)
140
+ xflow db migrate apply migrations/*.sql
141
+
142
+ ${(0, ui_1.bold)('schema')} answers a different question than the ${(0, ui_1.bold)('migrations/')} directory: one logical
143
+ database is shared by several projects, so the files say what you did, and the schema
144
+ says what is actually in there. ${(0, ui_1.bold)('query')} runs inside a READ ONLY transaction, so
145
+ writes are rejected by the database itself, not by us reading your SQL.
146
+
147
+ ${(0, ui_1.bold)('migrate')}: the files live in the repository (${(0, ui_1.bold)('migrations/0001_init.sql')},
148
+ ${(0, ui_1.bold)('migrations/0002_orders.sql')} and so on). The order comes from the file name, which
149
+ is why the leading number is required. Every migration runs in its own transaction, the
150
+ history is kept in the database itself, and anything already applied is not run again.
151
+
152
+ --dry-run report what would be applied without touching the database
153
+ --allow-destructive allow operations that destroy data
154
+
155
+ About destructive ones. The platform keeps no database history and makes no backups,
156
+ so ${(0, ui_1.bold)('DROP TABLE')}, ${(0, ui_1.bold)('DROP COLUMN')}, ${(0, ui_1.bold)('TRUNCATE')} and ${(0, ui_1.bold)('DELETE FROM')} without a
157
+ condition need two things at once: this flag, and the right to destroy data on the
158
+ access key. That right is off by default, and only its owner turns it on, in the
159
+ platform settings under Developers: a flag is something an agent adds by itself, a
160
+ right is not. With both in place, the contents of the affected tables are dumped and
161
+ kept for 7 days: that is a "caught it right away" safety net, not a backup.
162
+ ${(0, ui_1.bold)('DROP DATABASE')} is never allowed, because the database is shared across the
163
+ organization.
164
+
165
+ Editing an already applied file achieves nothing: the comparison is by name, not by
166
+ content. ${(0, ui_1.bold)('xflow db status')} lists such a file separately, so write a new migration
167
+ instead.
168
+
169
+ One logical database can be attached to several projects, so the history may hold
170
+ migrations that are not in your repository. That is normal, but it also means your
163
171
  migration can break somebody else's application.`,
164
- logs: `${(0, ui_1.bold)('xflow logs')} and ${(0, ui_1.bold)('xflow functions logs')}: project errors
165
-
166
- One stream per project: crashes of the released application in the browser and
167
- crashes of the cloud functions. The last 200 entries are kept, older ones fall off.
168
-
169
- --limit N how many to show (10 by default)
170
-
171
- A function reports its own crashes: Yandex exposes its logs only over gRPC, so the
172
- wrapper catches the exception, carries the tail of the console with it (the last 40
173
- ${(0, ui_1.bold)('console.log')} lines of that call) and sends it to the platform. Successful calls
174
- write nothing, otherwise every request would pay for it in latency.
175
-
176
- What never reaches this list: a crash while the module is starting (the function
177
- never gets as far as the wrapper), going over 90 seconds, and running out of memory.
178
- Those show up in the answer to ${(0, ui_1.bold)('xflow functions invoke')}.
179
-
180
- Browser errors are collected by ${(0, ui_1.bold)('src/utils/error-logger.ts')} of the template and
172
+ logs: `${(0, ui_1.bold)('xflow logs')} and ${(0, ui_1.bold)('xflow functions logs')}: project errors
173
+
174
+ One stream per project: crashes of the released application in the browser and
175
+ crashes of the cloud functions. The last 200 entries are kept, older ones fall off.
176
+
177
+ --limit N how many to show (10 by default)
178
+
179
+ A function reports its own crashes: Yandex exposes its logs only over gRPC, so the
180
+ wrapper catches the exception, carries the tail of the console with it (the last 40
181
+ ${(0, ui_1.bold)('console.log')} lines of that call) and sends it to the platform. Successful calls
182
+ write nothing, otherwise every request would pay for it in latency.
183
+
184
+ What never reaches this list: a crash while the module is starting (the function
185
+ never gets as far as the wrapper), going over 90 seconds, and running out of memory.
186
+ Those show up in the answer to ${(0, ui_1.bold)('xflow functions invoke')}.
187
+
188
+ Browser errors are collected by ${(0, ui_1.bold)('src/utils/error-logger.ts')} of the template and
181
189
  only from released addresses: a local ${(0, ui_1.bold)('npm run dev')} writes nothing here.`,
182
- env: `${(0, ui_1.bold)('xflow env')}: environment variables of the functions
183
-
184
- Keys, passwords and third-party addresses are kept by the platform, not by the
185
- repository. A value goes up and never comes back: the only place to see it is inside
186
- the function. So keep your own copy wherever you got it from.
187
-
188
- xflow env what is stored (names, not values)
189
- xflow env check what the functions of this project are missing
190
- xflow env set SMTP_PASSWORD=… store
191
- xflow env rm SMTP_PASSWORD delete
192
-
193
- --scope project visible to this project only, not the whole organization
194
-
195
- ${(0, ui_1.bold)('check')} reads the sources in ${(0, ui_1.bold)('functions/')} and looks for ${(0, ui_1.bold)('process.env.NAME')}
196
- references. The same rule applies on deploy: a function receives only the variables it
197
- mentions by name. A name assembled from an expression (${(0, ui_1.bold)("process.env['KEY_' + n]")}),
198
- reached through ${(0, ui_1.bold)('?.')}, destructured or spread never reaches the environment, so read
199
- variables literally. ${(0, ui_1.bold)('check')} names those reads and the build rejects them: a secret
200
- that quietly fails to arrive is found by the function falling over weeks later.
201
-
202
- The value reaches the function on deploy, not at the moment it is stored: after
203
- ${(0, ui_1.bold)('env set')} run ${(0, ui_1.bold)('xflow deploy')}. The build ships a function whose code did not
204
- change but whose variables did, so nothing is left holding an old value. The same after a
190
+ storage: `${(0, ui_1.bold)('xflow storage')}: files of the application
191
+
192
+ The sources archive is capped at 10 MB and is rebuilt and re-uploaded on every
193
+ deploy, so photos, video and PDFs do not belong in the repository. File storage
194
+ is the place for them: the files sit beside the versions, survive a build, and
195
+ are metered against the plan of the organization.
196
+
197
+ xflow storage ls [folder] what is there, with addresses and sizes
198
+ xflow storage push ./media --to media
199
+ upload a folder, keeping its structure
200
+ xflow storage rm <address> delete one file
201
+ xflow storage rm --folder photos --yes
202
+ delete a folder with everything in it
203
+
204
+ --to <folder> where to put it in the project storage
205
+ --replace upload files whose size differs from the stored one
206
+ --json machine-readable output, with every address
207
+
208
+ ${(0, ui_1.bold)('push')} sends only what is missing: a file already stored under the same path and
209
+ size is skipped, so running the command again after a broken connection is cheap.
210
+ A file that differs is left alone unless ${(0, ui_1.bold)('--replace')} is given. Bytes go straight
211
+ into the bucket, they do not pass through the platform.
212
+
213
+ Addresses are permanent and belong to the record, not to the bytes: replacing a
214
+ file keeps its address, so the links in the code and in the tables of the
215
+ application keep working. That is why there is no delete-and-upload-again.
216
+
217
+ The address is not public: it is a page of the platform that checks the rights of
218
+ the viewer on every request, so a closed project stops serving its pictures too.
219
+ Use the address as it came back, in ${(0, ui_1.bold)('<img src>')} and in the data of the application.
220
+
221
+ Dot entries and symbolic links are skipped, and ${(0, ui_1.bold)('.xflowignore')} is not read here:
222
+ those are the rules for the sources, and a media folder is exactly what one is
223
+ asked to put there so it stays out of the archive.
224
+
225
+ ${(0, ui_1.bold)('rm')} needs a right of its own (${(0, ui_1.bold)('Delete files')} in the Developers section of the
226
+ platform), off by default. The reason is the neighbourhood: whatever the users of
227
+ the application uploaded lives in the same folders, and there is no undo. A folder
228
+ first answers with the number of files it holds and deletes only on ${(0, ui_1.bold)('--yes')}.
229
+
230
+ There is no command back: sources return with ${(0, ui_1.bold)('xflow pull')}, files do not.`,
231
+ env: `${(0, ui_1.bold)('xflow env')}: environment variables of the functions
232
+
233
+ Keys, passwords and third-party addresses are kept by the platform, not by the
234
+ repository. A value goes up and never comes back: the only place to see it is inside
235
+ the function. So keep your own copy wherever you got it from.
236
+
237
+ xflow env what is stored (names, not values)
238
+ xflow env check what the functions of this project are missing
239
+ xflow env set SMTP_PASSWORD=… store
240
+ xflow env rm SMTP_PASSWORD delete
241
+
242
+ --scope project visible to this project only, not the whole organization
243
+
244
+ ${(0, ui_1.bold)('check')} reads the sources in ${(0, ui_1.bold)('functions/')} and looks for ${(0, ui_1.bold)('process.env.NAME')}
245
+ references. The same rule applies on deploy: a function receives only the variables it
246
+ mentions by name. A name assembled from an expression (${(0, ui_1.bold)("process.env['KEY_' + n]")}),
247
+ reached through ${(0, ui_1.bold)('?.')}, destructured or spread never reaches the environment, so read
248
+ variables literally. ${(0, ui_1.bold)('check')} names those reads and the build rejects them: a secret
249
+ that quietly fails to arrive is found by the function falling over weeks later.
250
+
251
+ The value reaches the function on deploy, not at the moment it is stored: after
252
+ ${(0, ui_1.bold)('env set')} run ${(0, ui_1.bold)('xflow deploy')}. The build ships a function whose code did not
253
+ change but whose variables did, so nothing is left holding an old value. The same after a
205
254
  delete: a function already deployed keeps the old value until its next deploy.`,
206
- connections: `${(0, ui_1.bold)('xflow connections')}: accounts connected to the organization
207
-
208
- Somebody signs in to Yandex Metrika, Bitrix or a mail service once, in the platform,
209
- and that account becomes a connection of the organization. Linked to a project, it
210
- hands its credentials to the cloud functions as environment variables. Nobody has to
211
- paste a token into the repository, and nobody sees the value: it goes straight from
212
- the platform into the function.
213
-
214
- xflow connections what this project can use, and what it already uses
215
- xflow connections link <name> --as ALIAS
216
- hand its credentials to the functions
217
- xflow connections unlink <ALIAS> take them away again
218
-
219
- Every row says whether the connection is linked to this project (its alias) and what
220
- state the access is in. ${(0, ui_1.bold)('available, not linked')} is the useful one: the account
221
- exists in the organization, but this project gets nothing from it yet: link it.
222
-
223
- Both commands take the name of the connection, the first column of the list, and both
224
- also take its identifier. ${(0, ui_1.bold)('unlink')} takes the alias as well, and prefers it: the
225
- alias is the name your own code already uses. One organization can hold several accounts
226
- of the same service under one name; then the command prints their identifiers and asks
227
- for one of those instead of guessing.
228
-
229
- ${(0, ui_1.bold)('unlink')} refuses while a function still reads one of the variables and names
230
- those functions; ${(0, ui_1.bold)('--force')} goes through anyway.
231
-
232
- Variables are named after the alias: an OAuth connection called ${(0, ui_1.bold)('YANDEX_METRIKA')}
233
- gives ${(0, ui_1.bold)('YANDEX_METRIKA_TOKEN')}, a key-based one gives a variable per field. They
234
- arrive at a function on its next deploy, like every other variable, so after a change
235
- run ${(0, ui_1.bold)('xflow deploy')}. A token close to expiry is renewed by any build on the way;
236
- a revoked one is not renewed by anything until a human reconnects the account.
237
-
238
- Linking needs a right on the key, ${(0, ui_1.bold)('connections:link')}. Keys are issued with it,
239
- and a person can take it away in the platform settings under Developers; a key cannot give
240
- it back to itself. Losing it is what the refusal says, and the answer is to ask a person,
241
- not to look for another route. Either way only the accounts granted to you personally can
242
- be linked at all: that rule holds whatever the key is allowed to do.
243
-
244
- Connecting a new account and switching one off stay with a person, in the platform
255
+ connections: `${(0, ui_1.bold)('xflow connections')}: accounts connected to the organization
256
+
257
+ Somebody signs in to Yandex Metrika, Bitrix or a mail service once, in the platform,
258
+ and that account becomes a connection of the organization. Linked to a project, it
259
+ hands its credentials to the cloud functions as environment variables. Nobody has to
260
+ paste a token into the repository, and nobody sees the value: it goes straight from
261
+ the platform into the function.
262
+
263
+ xflow connections what this project can use, and what it already uses
264
+ xflow connections link <name> --as ALIAS
265
+ hand its credentials to the functions
266
+ xflow connections unlink <ALIAS> take them away again
267
+
268
+ Every row says whether the connection is linked to this project (its alias) and what
269
+ state the access is in. ${(0, ui_1.bold)('available, not linked')} is the useful one: the account
270
+ exists in the organization, but this project gets nothing from it yet: link it.
271
+
272
+ Both commands take the name of the connection, the first column of the list, and both
273
+ also take its identifier. ${(0, ui_1.bold)('unlink')} takes the alias as well, and prefers it: the
274
+ alias is the name your own code already uses. One organization can hold several accounts
275
+ of the same service under one name; then the command prints their identifiers and asks
276
+ for one of those instead of guessing.
277
+
278
+ ${(0, ui_1.bold)('unlink')} refuses while a function still reads one of the variables and names
279
+ those functions; ${(0, ui_1.bold)('--force')} goes through anyway.
280
+
281
+ Variables are named after the alias: an OAuth connection called ${(0, ui_1.bold)('YANDEX_METRIKA')}
282
+ gives ${(0, ui_1.bold)('YANDEX_METRIKA_TOKEN')}, a key-based one gives a variable per field. They
283
+ arrive at a function on its next deploy, like every other variable, so after a change
284
+ run ${(0, ui_1.bold)('xflow deploy')}. A token close to expiry is renewed by any build on the way;
285
+ a revoked one is not renewed by anything until a human reconnects the account.
286
+
287
+ Linking needs a right on the key, ${(0, ui_1.bold)('connections:link')}. Keys are issued with it,
288
+ and a person can take it away in the platform settings under Developers; a key cannot give
289
+ it back to itself. Losing it is what the refusal says, and the answer is to ask a person,
290
+ not to look for another route. Either way only the accounts granted to you personally can
291
+ be linked at all: that rule holds whatever the key is allowed to do.
292
+
293
+ Connecting a new account and switching one off stay with a person, in the platform
245
294
  interface. There is no command for either.`,
246
- schedules: `${(0, ui_1.bold)('xflow schedules')}: running functions on a timer
247
-
248
- A schedule is a Yandex timer trigger: it calls the function itself, with no
249
- application and no browser involved.
250
-
251
- xflow schedules set report "0 3 ? * * *" every day at 03:00
252
- xflow schedules set digest "0 */4 ? * * *" every 4 hours
253
- xflow schedules rm report take off every schedule of a function
254
-
255
- Six fields: ${(0, ui_1.bold)('minutes hours day-of-month month day-of-week year')}. Exactly one of
256
- the day-of-month and day-of-week fields has to be ${(0, ui_1.bold)('?')}, which is not our quirk but
257
- how Yandex works. ${(0, ui_1.bold)('The time is always UTC')}, local time is not understood.
258
-
259
- --payload '{"mode":"full"}' the body the function will receive
260
-
261
- A scheduled run arrives at the handler as a POST with no headers, and the project
262
- token is not checked on it: such an event cannot be forged from outside. The function
263
- has to be deployed already, since the schedule points at it and not the other way round.
264
-
265
- A failed run shows up in ${(0, ui_1.bold)('xflow functions logs')}: nobody is watching a scheduled
295
+ schedules: `${(0, ui_1.bold)('xflow schedules')}: running functions on a timer
296
+
297
+ A schedule is a Yandex timer trigger: it calls the function itself, with no
298
+ application and no browser involved.
299
+
300
+ xflow schedules set report "0 3 ? * * *" every day at 03:00
301
+ xflow schedules set digest "0 */4 ? * * *" every 4 hours
302
+ xflow schedules rm report take off every schedule of a function
303
+
304
+ Six fields: ${(0, ui_1.bold)('minutes hours day-of-month month day-of-week year')}. Exactly one of
305
+ the day-of-month and day-of-week fields has to be ${(0, ui_1.bold)('?')}, which is not our quirk but
306
+ how Yandex works. ${(0, ui_1.bold)('The time is always UTC')}, local time is not understood.
307
+
308
+ --payload '{"mode":"full"}' the body the function will receive
309
+
310
+ A scheduled run arrives at the handler as a POST with no headers, and the project
311
+ token is not checked on it: such an event cannot be forged from outside. The function
312
+ has to be deployed already, since the schedule points at it and not the other way round.
313
+
314
+ A failed run shows up in ${(0, ui_1.bold)('xflow functions logs')}: nobody is watching a scheduled
266
315
  function, so the wrapper reports a crash the same way it does on an ordinary call.`,
267
- invoke: `${(0, ui_1.bold)('xflow functions invoke')} <name>: call a function
268
-
269
- Calls it exactly the way the application does: the project token plus a visitor pass
270
- for the person who owns the key, so the function sees a real caller and its role.
271
- Both are taken from the platform, no local .env is needed.
272
-
273
- --data '{"a":1}' request body (the method becomes POST by default)
274
- --method GET a different method
275
-
276
- Prints the status, the response time and the body. A non-zero exit code on 4xx and
277
- 5xx: in CI such a call has to fail the step. The cause of a crash is shown by
316
+ invoke: `${(0, ui_1.bold)('xflow functions invoke')} <name>: call a function
317
+
318
+ Calls it exactly the way the application does: the project token plus a visitor pass
319
+ for the person who owns the key, so the function sees a real caller and its role.
320
+ Both are taken from the platform, no local .env is needed.
321
+
322
+ --data '{"a":1}' request body (the method becomes POST by default)
323
+ --method GET a different method
324
+
325
+ Prints the status, the response time and the body. A non-zero exit code on 4xx and
326
+ 5xx: in CI such a call has to fail the step. The cause of a crash is shown by
278
327
  ${(0, ui_1.bold)('xflow functions logs <name>')}.`,
279
- mcp: `${(0, ui_1.bold)('xflow mcp install')}: connect the agent to the platform directly
280
-
281
- Writes the MCP server into the configuration of a client that has its own connect
282
- command: today that is Claude Code, and for the rest the command prints what to enter
283
- by hand. After that the agent runs the platform without a terminal: it inspects the
284
- database, applies migrations, ships and calls functions, sets schedules, reads logs.
285
- The commands stay: code, builds and releases are still done by the CLI, because
286
- pushing sources through MCP means filling the agent context and spending your money.
287
-
288
- The command takes the access key from your own login and does not print it: usually
289
- the agent runs this command itself, and everything printed lands in its context and in
290
- the chat history.
291
-
292
- --show-token print the key anyway (not while under an agent)
293
-
294
- There is one key per person per organization and its scope is not narrowed: the agent
295
- sees every project of the organization and names the one it needs. It takes the
328
+ mcp: `${(0, ui_1.bold)('xflow mcp install')}: connect the agent to the platform directly
329
+
330
+ Writes the MCP server into the configuration of a client that has its own connect
331
+ command: today that is Claude Code, and for the rest the command prints what to enter
332
+ by hand. After that the agent runs the platform without a terminal: it inspects the
333
+ database, applies migrations, ships and calls functions, sets schedules, reads logs.
334
+ The commands stay: code, builds and releases are still done by the CLI, because
335
+ pushing sources through MCP means filling the agent context and spending your money.
336
+
337
+ The command takes the access key from your own login and does not print it: usually
338
+ the agent runs this command itself, and everything printed lands in its context and in
339
+ the chat history.
340
+
341
+ --show-token print the key anyway (not while under an agent)
342
+
343
+ There is one key per person per organization and its scope is not narrowed: the agent
344
+ sees every project of the organization and names the one it needs. It takes the
296
345
  project from the ${(0, ui_1.bold)('project_id')} argument, not from your folder, which it cannot see.`,
297
- skills: `${(0, ui_1.bold)('xflow skills')}: platform instructions for an AI agent
298
-
299
- An agent has no way to know about XFlow: the platform is not in its training. The
300
- instructions explain how to release and publish, where the design system components
301
- come from, and where to look for production errors. ${(0, ui_1.bold)('init')} and ${(0, ui_1.bold)('link')} lay them
302
- down on their own for the common agents, so this command exists to pick tools
303
- precisely. Refreshing after a CLI update is not your job any more: the skill and the
304
- CLI ship as one version, and ${(0, ui_1.bold)('xflow update')} rewrites the copies you already have.
305
-
306
- Run in a terminal, it asks two questions: where (this project, or the home folder,
307
- which makes the skill visible in every project) and for which agents. Copies that are
308
- already installed come pre-selected and are updated in place. Without a terminal, in
309
- CI or when an agent runs the command, there are no questions: the defaults plus every
310
- already installed copy are refreshed.
311
-
312
- xflow skills list the agents, their folders and what is installed
313
- --agent claude,cursor exact agents, no questions asked
314
- --global the home folder instead of the project
315
- --yes no questions: defaults plus what is installed
316
- --refresh rewrite the copies that exist, create none
317
-
318
- The format is shared (agentskills.io) and inside a project most tools read the shared
319
- .agents/skills folder, so the paths differ mostly in the home directory. Cursor also
320
- gets .cursor/rules/xflow.mdc: its older versions do not read the shared format.
321
-
322
- Plus a few pointer lines in ${(0, ui_1.bold)('AGENTS.md')}. A skill is picked up lazily, only when its
323
- description matches the task, and "add a customers table" will not trigger it. AGENTS.md
324
- is always read by the agent, which is why the pointer is appended there, at the end of
346
+ skills: `${(0, ui_1.bold)('xflow skills')}: platform instructions for an AI agent
347
+
348
+ An agent has no way to know about XFlow: the platform is not in its training. The
349
+ instructions explain how to release and publish, where the design system components
350
+ come from, and where to look for production errors. ${(0, ui_1.bold)('init')} and ${(0, ui_1.bold)('link')} lay them
351
+ down on their own for the common agents, so this command exists to pick tools
352
+ precisely. Refreshing after a CLI update is not your job any more: the skill and the
353
+ CLI ship as one version, and ${(0, ui_1.bold)('xflow update')} rewrites the copies you already have.
354
+
355
+ Run in a terminal, it asks two questions: where (this project, or the home folder,
356
+ which makes the skill visible in every project) and for which agents. Copies that are
357
+ already installed come pre-selected and are updated in place. Without a terminal, in
358
+ CI or when an agent runs the command, there are no questions: the defaults plus every
359
+ already installed copy are refreshed.
360
+
361
+ xflow skills list the agents, their folders and what is installed
362
+ --agent claude,cursor exact agents, no questions asked
363
+ --global the home folder instead of the project
364
+ --yes no questions: defaults plus what is installed
365
+ --refresh rewrite the copies that exist, create none
366
+
367
+ The format is shared (agentskills.io) and inside a project most tools read the shared
368
+ .agents/skills folder, so the paths differ mostly in the home directory. Cursor also
369
+ gets .cursor/rules/xflow.mdc: its older versions do not read the shared format.
370
+
371
+ Plus a few pointer lines in ${(0, ui_1.bold)('AGENTS.md')}. A skill is picked up lazily, only when its
372
+ description matches the task, and "add a customers table" will not trigger it. AGENTS.md
373
+ is always read by the agent, which is why the pointer is appended there, at the end of
325
374
  the file and once. ${(0, ui_1.bold)('CLAUDE.md')} is left alone: Claude Code reads .claude/skills anyway.`,
326
- pull: `${(0, ui_1.bold)('xflow pull')}: fetch the sources
327
-
328
- By default it fetches the latest revision into the project folder and refuses to write
329
- into a non-empty one: the platform cannot merge changes, that is git's job.
330
-
331
- --into <dir> unpack alongside, to compare
332
- --revision <N> a specific revision (the list: xflow deployments)
375
+ pull: `${(0, ui_1.bold)('xflow pull')}: fetch the sources
376
+
377
+ By default it fetches the latest revision into the project folder and refuses to write
378
+ into a non-empty one: the platform cannot merge changes, that is git's job.
379
+
380
+ --into <dir> unpack alongside, to compare
381
+ --revision <N> a specific revision (the list: xflow deployments)
333
382
  --force overwrite the folder completely`,
334
- deploy: `${(0, ui_1.bold)('xflow deploy')}: build and release
335
-
336
- Three steps: sending the sources, shipping the cloud functions, building the application.
337
- The build command and the output directory come from xflow.json (npm run build and dist
338
- by default).
339
-
340
- --no-push do not send sources, build from the latest server revision
341
- --force allow overwriting the server revision while sending
342
- --allow-removals agree in advance to remove the functions gone from the sources
343
-
344
- The whole working copy goes up at once, as one revision. What is not sent: node_modules,
345
- .git, dist, build, .next, any .env, plus everything listed in .xflowignore and in the
346
- ignore field of xflow.json.
347
-
348
- If the server holds a revision newer than the one you worked from, the sources are
349
- rejected and the build does not start. That means somebody deployed before you: fetch
350
- their changes alongside (${(0, ui_1.bold)('xflow pull --into ./server-copy')}), merge them in git on
351
- your side, and retry. ${(0, ui_1.bold)('--force')} overwrites the server revision, and before that the
352
- CLI shows whose work you are about to destroy and asks for confirmation by typing the
353
- project name. In a non-interactive run (CI, an agent) there is no way to confirm: a
354
- version conflict has to fail the build rather than silently destroy somebody else's work.
355
-
356
- The platform builds, in a clean sandbox on one Node version for everybody, so "it
357
- worked on my machine" no longer depends on your machine. Before the build the project
358
- is checked against the template: mismatches are printed as a list and the build does
359
- not start at all.
360
-
361
- Everything in ${(0, ui_1.bold)('functions/<name>/index.ts')} is bundled and shipped by the same run,
362
- before the application is built. That order is not a convention but the only one that
363
- works: the addresses of the functions are baked into the bundle, so they have to exist
364
- first. A function whose code and variables did not change is left alone, and a function
365
- that fails to ship fails the whole build.
366
-
367
- A function gone from the sources would be deleted from the cloud along with its
368
- schedules, and that is final: one created again later gets a different address. So the
369
- build does not start at all until you say yes. In a terminal the CLI names the functions
370
- and asks; without one (CI, an agent) it stops and ${(0, ui_1.bold)('--allow-removals')} is the only way
371
- to agree. If the sources hold no functions at all while the cloud holds several, nothing
372
- is deleted and nothing is asked: that looks like a directory which never made it (a
373
- ${(0, ui_1.bold)('functions/')} line in .xflowignore) rather than a decision.
374
-
375
- The database is not part of this: migrations change data in ways nothing can undo, so
376
- they stay their own command (${(0, ui_1.bold)('xflow db migrate')}).
377
-
378
- The built version is visible on the project page, and the CLI prints the link. Visitors
379
- see it after ${(0, ui_1.bold)('xflow publish')}. The address of the build itself is not printed:
380
- it carries the version number, and after the next publish such a link quietly serves an
383
+ deploy: `${(0, ui_1.bold)('xflow deploy')}: build and release
384
+
385
+ Three steps: sending the sources, shipping the cloud functions, building the application.
386
+ The build command and the output directory come from xflow.json (npm run build and dist
387
+ by default).
388
+
389
+ --no-push do not send sources, build from the latest server revision
390
+ --force allow overwriting the server revision while sending
391
+ --allow-removals agree in advance to remove the functions gone from the sources
392
+
393
+ The whole working copy goes up at once, as one revision. What is not sent: node_modules,
394
+ .git, dist, build, .next, any .env, plus everything listed in .xflowignore and in the
395
+ ignore field of xflow.json.
396
+
397
+ If the server holds a revision newer than the one you worked from, the sources are
398
+ rejected and the build does not start. That means somebody deployed before you: fetch
399
+ their changes alongside (${(0, ui_1.bold)('xflow pull --into ./server-copy')}), merge them in git on
400
+ your side, and retry. ${(0, ui_1.bold)('--force')} overwrites the server revision, and before that the
401
+ CLI shows whose work you are about to destroy and asks for confirmation by typing the
402
+ project name. In a non-interactive run (CI, an agent) there is no way to confirm: a
403
+ version conflict has to fail the build rather than silently destroy somebody else's work.
404
+
405
+ The platform builds, in a clean sandbox on one Node version for everybody, so "it
406
+ worked on my machine" no longer depends on your machine. Before the build the project
407
+ is checked against the template: mismatches are printed as a list and the build does
408
+ not start at all.
409
+
410
+ Everything in ${(0, ui_1.bold)('functions/<name>/index.ts')} is bundled and shipped by the same run,
411
+ before the application is built. That order is not a convention but the only one that
412
+ works: the addresses of the functions are baked into the bundle, so they have to exist
413
+ first. A function whose code and variables did not change is left alone, and a function
414
+ that fails to ship fails the whole build.
415
+
416
+ A function gone from the sources would be deleted from the cloud along with its
417
+ schedules, and that is final: one created again later gets a different address. So the
418
+ build does not start at all until you say yes. In a terminal the CLI names the functions
419
+ and asks; without one (CI, an agent) it stops and ${(0, ui_1.bold)('--allow-removals')} is the only way
420
+ to agree. If the sources hold no functions at all while the cloud holds several, nothing
421
+ is deleted and nothing is asked: that looks like a directory which never made it (a
422
+ ${(0, ui_1.bold)('functions/')} line in .xflowignore) rather than a decision.
423
+
424
+ The database is not part of this: migrations change data in ways nothing can undo, so
425
+ they stay their own command (${(0, ui_1.bold)('xflow db migrate')}).
426
+
427
+ The built version is visible on the project page, and the CLI prints the link. Visitors
428
+ see it after ${(0, ui_1.bold)('xflow publish')}. The address of the build itself is not printed:
429
+ it carries the version number, and after the next publish such a link quietly serves an
381
430
  old copy.`,
382
- init: `${(0, ui_1.bold)('xflow init')} [dir]: a new project
383
-
384
- Lays down the platform template: React on Vite, Tailwind, a set of components
385
- (${(0, ui_1.bold)('src/components/ui')}) and ready-made blocks (${(0, ui_1.bold)('src/components/blocks')}): table,
386
- form, filters, kanban, charts. Projects created in the web get the same template, which
387
- is what makes the applications look alike.
388
-
389
- Building the interface on top of these components and the tokens in ${(0, ui_1.bold)('src/index.css')}
390
- is not a formality: your own palette on top of them looks foreign inside the platform.
391
-
392
- --name <name> project name (the folder name by default)
393
- --template <id> a different template (the list: xflow templates)
394
- --database <id> attach a logical database of the organization
395
-
396
- The order: the project is created on the platform first, because without it there is
397
- nowhere to get the token for .env. If writing the files fails, the project stays empty
431
+ init: `${(0, ui_1.bold)('xflow init')} [dir]: a new project
432
+
433
+ Lays down the platform template: React on Vite, Tailwind, a set of components
434
+ (${(0, ui_1.bold)('src/components/ui')}) and ready-made blocks (${(0, ui_1.bold)('src/components/blocks')}): table,
435
+ form, filters, kanban, charts. Projects created in the web get the same template, which
436
+ is what makes the applications look alike.
437
+
438
+ Building the interface on top of these components and the tokens in ${(0, ui_1.bold)('src/index.css')}
439
+ is not a formality: your own palette on top of them looks foreign inside the platform.
440
+
441
+ --name <name> project name (the folder name by default)
442
+ --template <id> a different template (the list: xflow templates)
443
+ --database <id> attach a logical database of the organization
444
+
445
+ The order: the project is created on the platform first, because without it there is
446
+ nowhere to get the token for .env. If writing the files fails, the project stays empty
398
447
  and the CLI explains how to pick it up with the link command.`,
399
- rollback: `${(0, ui_1.bold)('xflow rollback')} <version number>: serve the pages of an earlier build
400
-
401
- Points the project at the pages of that build. ${(0, ui_1.bold)('Only the pages come back.')} Cloud
402
- functions and the database are one per project: they are not versioned, they are shared
403
- with the published application, and they stay exactly as they are now. A page from August
404
- will be talking to today's functions.
405
-
406
- The sources stay at their own revision too. Fetching the code of that build is a separate
407
- command (${(0, ui_1.bold)('xflow pull --revision N --into ../old-version')}, keep the copy outside the
408
- project folder). Building that code over the current one is a further, deliberate step: it
409
- replaces the server revision, and whatever is newer on the platform goes away.
410
-
448
+ rollback: `${(0, ui_1.bold)('xflow rollback')} <version number>: serve the pages of an earlier build
449
+
450
+ Points the project at the pages of that build. ${(0, ui_1.bold)('Only the pages come back.')} Cloud
451
+ functions and the database are one per project: they are not versioned, they are shared
452
+ with the published application, and they stay exactly as they are now. A page from August
453
+ will be talking to today's functions.
454
+
455
+ The sources stay at their own revision too. Fetching the code of that build is a separate
456
+ command (${(0, ui_1.bold)('xflow pull --revision N --into ../old-version')}, keep the copy outside the
457
+ project folder). Building that code over the current one is a further, deliberate step: it
458
+ replaces the server revision, and whatever is newer on the platform goes away.
459
+
411
460
  Visitors keep seeing the published pages until ${(0, ui_1.bold)('xflow publish')} is run.`,
412
461
  };