@getxflow/cli 1.0.6 → 1.0.8
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/commands/auth.js +2 -0
- package/dist/commands/db.js +7 -1
- package/dist/session.js +8 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/skills/xflow/SKILL.md +23 -6
package/dist/commands/auth.js
CHANGED
|
@@ -141,6 +141,8 @@ async function whoami() {
|
|
|
141
141
|
const pkg = me.billing.package_title ? `, ${me.billing.package_title}` : '';
|
|
142
142
|
(0, ui_1.out)('');
|
|
143
143
|
(0, ui_1.out)(`Plan: ${me.billing.plan_title}${pkg}${SUBSCRIPTION_STATE[me.billing.status] ?? ''}`);
|
|
144
|
+
if (me.billing.functions_stopped)
|
|
145
|
+
(0, ui_1.warn)(me.billing.functions_stopped);
|
|
144
146
|
}
|
|
145
147
|
if (me.quotas) {
|
|
146
148
|
(0, ui_1.out)('');
|
package/dist/commands/db.js
CHANGED
|
@@ -148,7 +148,13 @@ function moveLine(move) {
|
|
|
148
148
|
return move.finished_at ? `${move.note} (${(0, ui_1.formatAge)(move.finished_at)})` : move.note;
|
|
149
149
|
}
|
|
150
150
|
function printDatabases(databases, total, truncated) {
|
|
151
|
-
(0, ui_1.table)(databases.map((database) => [
|
|
151
|
+
(0, ui_1.table)(databases.map((database) => [
|
|
152
|
+
database.name,
|
|
153
|
+
database.id,
|
|
154
|
+
database.schema ?? '-',
|
|
155
|
+
serverLabel(database.server),
|
|
156
|
+
typeof database.bytes === 'number' ? (0, ui_1.formatBytes)(database.bytes) : '',
|
|
157
|
+
]));
|
|
152
158
|
if (truncated)
|
|
153
159
|
(0, ui_1.note)((0, ui_1.dim)(` Shown ${databases.length} of ${total}`));
|
|
154
160
|
for (const database of databases) {
|
package/dist/session.js
CHANGED
|
@@ -55,12 +55,18 @@ function anonymous(config) {
|
|
|
55
55
|
*/
|
|
56
56
|
async function findOrgFor(apiUrl, path) {
|
|
57
57
|
const dead = [];
|
|
58
|
+
let frozen = null;
|
|
58
59
|
for (const candidate of (0, credentials_1.listOrgs)(apiUrl)) {
|
|
59
60
|
try {
|
|
60
61
|
await (0, api_1.apiJson)({ apiUrl, token: candidate.token }, path);
|
|
61
62
|
return { hit: candidate, dead };
|
|
62
63
|
}
|
|
63
64
|
catch (e) {
|
|
65
|
+
// A frozen organization refuses every path, so it may or may not hold this one.
|
|
66
|
+
if (e instanceof api_1.ApiError && e.limit?.code === 'subscription_blocked') {
|
|
67
|
+
frozen ??= e;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
64
70
|
if (e instanceof api_1.ApiError && (e.status === 401 || e.status === 403)) {
|
|
65
71
|
dead.push(candidate);
|
|
66
72
|
continue;
|
|
@@ -70,6 +76,8 @@ async function findOrgFor(apiUrl, path) {
|
|
|
70
76
|
throw e;
|
|
71
77
|
}
|
|
72
78
|
}
|
|
79
|
+
if (frozen)
|
|
80
|
+
throw frozen;
|
|
73
81
|
return { hit: null, dead };
|
|
74
82
|
}
|
|
75
83
|
function findProjectOrg(apiUrl, projectId) {
|
package/dist/version.js
CHANGED
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DEFAULT_API_URL = exports.CLI_VERSION = void 0;
|
|
4
4
|
/** Keep in sync with cli/package.json. */
|
|
5
|
-
exports.CLI_VERSION = '1.0.
|
|
5
|
+
exports.CLI_VERSION = '1.0.8';
|
|
6
6
|
/** Overridden by XFLOW_API_URL or the `api` field in xflow.json. */
|
|
7
7
|
exports.DEFAULT_API_URL = 'https://app.getxflow.com';
|
package/package.json
CHANGED
package/skills/xflow/SKILL.md
CHANGED
|
@@ -23,7 +23,7 @@ contains the fix.
|
|
|
23
23
|
|
|
24
24
|
## Keeping these instructions current
|
|
25
25
|
|
|
26
|
-
These instructions ship with xflow CLI 1.0.
|
|
26
|
+
These instructions ship with xflow CLI 1.0.8, so the copy you are reading can be older than
|
|
27
27
|
the CLI answering your commands.
|
|
28
28
|
|
|
29
29
|
When the CLI says this file does not match it, run `xflow skills --refresh` and then **read
|
|
@@ -59,7 +59,8 @@ the error points away from the cause. The sections below carry the details.
|
|
|
59
59
|
7. When `xflow db migrate` answers `db_write_locked`, or an INSERT or CREATE TABLE in a
|
|
60
60
|
function fails with `permission denied` (42501), the database is over its plan size and
|
|
61
61
|
Postgres itself refuses. Reads, updates and deletes keep working, so the fix is a migration
|
|
62
|
-
that deletes data, never a rewrite of the SQL. Write comes back within the hour.
|
|
62
|
+
that deletes data, never a rewrite of the SQL. Write comes back within the hour. The limit
|
|
63
|
+
is shared by the organization: `xflow db list` shows the size of each project database.
|
|
63
64
|
8. Heavy media does not travel with the sources. Photos, video and PDFs go up with
|
|
64
65
|
`xflow storage push`, not into the repository: the archive is capped at 10 MB.
|
|
65
66
|
|
|
@@ -120,6 +121,10 @@ halfway through the work into a sentence said before it starts.
|
|
|
120
121
|
deleting it.
|
|
121
122
|
- Function minutes are shared by the whole organization: every app, page and schedule spends
|
|
122
123
|
them. When they run out, every function of the organization stops (`functions_stopped`).
|
|
124
|
+
- An unpaid plan stops the functions at once, and five days later freezes the organization:
|
|
125
|
+
every command except `xflow whoami`, and every MCP tool, answers `subscription_blocked`,
|
|
126
|
+
apps and pages show a "suspended" page. Nothing is deleted and no code fixes it: tell the
|
|
127
|
+
person that the owner pays the invoice in the organization settings, section «Тариф» (Plan).
|
|
123
128
|
- The cap on how long one function call may run is written into the function when it is
|
|
124
129
|
deployed, so a plan change reaches the cloud only with the next `xflow deploy functions` of
|
|
125
130
|
each project, in both directions.
|
|
@@ -239,8 +244,8 @@ A person sets this in the web interface; you cannot, and you should not ask for
|
|
|
239
244
|
- The plan decides which levels exist: on Free an app opens only inside xflow, Start adds the
|
|
240
245
|
direct address and "Anyone with the link".
|
|
241
246
|
- At the closed level the platform signs people in, and a function receives the person in
|
|
242
|
-
`event.xflow.user` (`id`, `role` in the organization, `name`
|
|
243
|
-
(doctor, receptionist) in your own table keyed by `user.id`.
|
|
247
|
+
`event.xflow.user` (`id`, `role` in the organization, `name`, `access` to the project).
|
|
248
|
+
Keep roles inside the app (doctor, receptionist) in your own table keyed by `user.id`.
|
|
244
249
|
- People outside the organization (patients, pickers, clients) are not xflow accounts: make the
|
|
245
250
|
app "Anyone with the link" and give it its own sign-in, see below.
|
|
246
251
|
- At "Anyone with the link" the platform signs nobody in: `event.xflow.user` is null for
|
|
@@ -394,8 +399,8 @@ exports.handler = async (event) => {
|
|
|
394
399
|
// project), 'external' (an outside service with a key), 'schedule',
|
|
395
400
|
// 'platform' (a person through xflow functions invoke or the web interface)
|
|
396
401
|
// slug: the address name of the calling app (<id>--<org>) or page, null otherwise
|
|
397
|
-
// user: { id, role, name } of a signed-in person,
|
|
398
|
-
//
|
|
402
|
+
// user: { id, role, name, access } of a signed-in person, null for anonymous
|
|
403
|
+
// visitors, pages, keys. access: 'view' | 'edit' on this project
|
|
399
404
|
// ip: the visitor's address, null for a schedule
|
|
400
405
|
}
|
|
401
406
|
```
|
|
@@ -403,6 +408,18 @@ exports.handler = async (event) => {
|
|
|
403
408
|
Never trust an identity that arrives in the body or in a header of the request: the page lives
|
|
404
409
|
on someone else's computer. `event.xflow` is the only identity the platform stands behind.
|
|
405
410
|
|
|
411
|
+
Project access is separate from the role: a person who only views the project (`access:
|
|
412
|
+
'view'`) still reaches every function. A function that writes refuses them:
|
|
413
|
+
|
|
414
|
+
```js
|
|
415
|
+
if (event.xflow.user && event.xflow.user.access !== 'edit') {
|
|
416
|
+
return { statusCode: 403, body: JSON.stringify({ error: 'Read-only access' }) }
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
To remember who did something, store `user.id` and `user.name` in the row: the name is never
|
|
421
|
+
empty, and the app has no other way to look up other people.
|
|
422
|
+
|
|
406
423
|
A function that changes data should say who may call it instead of checking the role by hand:
|
|
407
424
|
|
|
408
425
|
```js
|