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