@getxflow/cli 0.10.4 → 0.10.6
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/db.js +8 -1
- package/dist/commands/projects.js +20 -2
- package/dist/commands/skills.js +22 -2
- package/dist/commands/sources.js +3 -1
- package/dist/help.js +1 -1
- package/dist/tree.js +16 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/skills/xflow/SKILL.md +3 -2
package/dist/commands/db.js
CHANGED
|
@@ -177,7 +177,14 @@ async function dbQuery(args) {
|
|
|
177
177
|
}
|
|
178
178
|
(0, ui_1.table)([data.columns, ...data.rows.map((row) => data.columns.map((column) => cell(row[column])))]);
|
|
179
179
|
if (data.truncated) {
|
|
180
|
-
|
|
180
|
+
// "raise --limit" used to be the whole advice, and for a long time it was
|
|
181
|
+
// impossible to follow: the ceiling and the default were the same number, so
|
|
182
|
+
// the flag could only ask for less. Now it can ask for more, and the line
|
|
183
|
+
// says so only while there is room left.
|
|
184
|
+
const room = data.limit !== undefined && data.limit_max !== undefined && data.limit < data.limit_max;
|
|
185
|
+
(0, ui_1.note)((0, ui_1.dim)(room
|
|
186
|
+
? ` Shown ${data.rows.length} of ${data.total}: raise --limit (up to ${data.limit_max}), or narrow the query`
|
|
187
|
+
: ` Shown ${data.rows.length} of ${data.total}: narrow the query, with a where clause or an offset`));
|
|
181
188
|
}
|
|
182
189
|
}
|
|
183
190
|
async function dbMigrate(args) {
|
|
@@ -100,6 +100,18 @@ async function init(args) {
|
|
|
100
100
|
(0, ui_1.out)(' npm run dev # develop');
|
|
101
101
|
(0, ui_1.out)(' xflow deploy # send the code, build and release');
|
|
102
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Linking is a step, not a task. Without this the agent reports the command as done and
|
|
105
|
+
* goes quiet, leaving the person in front of a folder: it has to read the skill and come
|
|
106
|
+
* back with a question instead.
|
|
107
|
+
*/
|
|
108
|
+
function nextAfterLink(skillReady) {
|
|
109
|
+
(0, ui_1.out)('');
|
|
110
|
+
(0, ui_1.out)(` ${(0, ui_1.bold)('Next:')}`);
|
|
111
|
+
if (skillReady)
|
|
112
|
+
(0, ui_1.out)(` read ${skills_1.SKILL_IN_PROJECT}: how this platform is built and shipped`);
|
|
113
|
+
(0, ui_1.out)(' then ask the user what to build or change, and start there');
|
|
114
|
+
}
|
|
103
115
|
async function link(args) {
|
|
104
116
|
const root = (0, config_1.findProjectRoot)() ?? process.cwd();
|
|
105
117
|
const existing = (0, node_fs_1.existsSync)((0, node_path_1.join)(root, config_1.CONFIG_FILE)) ? (0, config_1.readConfig)(root) : undefined;
|
|
@@ -148,11 +160,13 @@ async function link(args) {
|
|
|
148
160
|
write(dir, '.env', (0, template_1.envFile)(card.project_token, client.apiUrl, card.functions));
|
|
149
161
|
(0, ui_1.note)((0, ui_1.dim)(' Created .env with the project token and the function addresses'));
|
|
150
162
|
}
|
|
151
|
-
(0, skills_1.installSkillQuietly)(dir);
|
|
163
|
+
const skillReady = (0, skills_1.installSkillQuietly)(dir);
|
|
152
164
|
(0, ui_1.ok)(`The folder is linked to the project "${card.name}"`);
|
|
153
165
|
(0, ui_1.note)((0, ui_1.dim)(` ${(0, node_path_1.join)(dir, config_1.CONFIG_FILE)}`));
|
|
154
|
-
if (!fresh)
|
|
166
|
+
if (!fresh) {
|
|
167
|
+
nextAfterLink(skillReady);
|
|
155
168
|
return;
|
|
169
|
+
}
|
|
156
170
|
// A clone without the code is half a clone, and the folder has to remember
|
|
157
171
|
// which revision it works from: without that the first push has nothing to
|
|
158
172
|
// compare against and stops.
|
|
@@ -164,11 +178,15 @@ async function link(args) {
|
|
|
164
178
|
catch (e) {
|
|
165
179
|
if (e instanceof api_1.ApiError && e.status === 404) {
|
|
166
180
|
(0, ui_1.note)((0, ui_1.dim)(' The project has no sources yet: write the code and run xflow deploy'));
|
|
181
|
+
nextAfterLink(skillReady);
|
|
167
182
|
return;
|
|
168
183
|
}
|
|
184
|
+
// No next step while the code is missing: the pull has to succeed first.
|
|
169
185
|
(0, ui_1.warn)(`The sources did not arrive: ${e instanceof Error ? e.message : e}`);
|
|
170
186
|
(0, ui_1.note)((0, ui_1.dim)(' The folder is linked, fetch them separately: xflow pull'));
|
|
187
|
+
return;
|
|
171
188
|
}
|
|
189
|
+
nextAfterLink(skillReady);
|
|
172
190
|
}
|
|
173
191
|
async function list() {
|
|
174
192
|
const root = (0, config_1.findProjectRoot)();
|
package/dist/commands/skills.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SKILL_IN_PROJECT = void 0;
|
|
4
|
+
exports.skillCopyPaths = skillCopyPaths;
|
|
3
5
|
exports.replacePointer = replacePointer;
|
|
4
6
|
exports.copyIsStale = copyIsStale;
|
|
5
7
|
exports.staleSkillCopy = staleSkillCopy;
|
|
@@ -28,13 +30,25 @@ const AGENTS = [
|
|
|
28
30
|
const DEFAULT_PROJECT = ['claude', 'cursor', 'codex'];
|
|
29
31
|
const DEFAULT_GLOBAL = ['claude', 'codex', 'universal'];
|
|
30
32
|
const CURSOR_RULE = ['.cursor', 'rules', 'xflow.mdc'];
|
|
33
|
+
/** Written by every default install, so the pointer and the link nudge can both name it. */
|
|
34
|
+
exports.SKILL_IN_PROJECT = '.agents/skills/xflow/SKILL.md';
|
|
35
|
+
/**
|
|
36
|
+
* Every place inside a project a copy of the skill can live. The archive of sources is
|
|
37
|
+
* built around this list, so a new agent in the table above is kept out of it by adding
|
|
38
|
+
* the agent and nothing else.
|
|
39
|
+
*/
|
|
40
|
+
function skillCopyPaths() {
|
|
41
|
+
const paths = new Set(AGENTS.map((agent) => `${[...agent.project, 'xflow'].join('/')}/`));
|
|
42
|
+
paths.add(CURSOR_RULE.join('/'));
|
|
43
|
+
return [...paths];
|
|
44
|
+
}
|
|
31
45
|
/** AGENTS.md is always read, unlike the lazily loaded skill: a short pointer lives there. */
|
|
32
46
|
const POINTER_MARKER = '<!-- xflow-skill -->';
|
|
33
47
|
const POINTER = `${POINTER_MARKER}
|
|
34
48
|
## XFlow
|
|
35
49
|
|
|
36
50
|
This project is hosted on the XFlow platform and ships through the \`xflow\` CLI, not
|
|
37
|
-
through a git push. Read
|
|
51
|
+
through a git push. Read \`${exports.SKILL_IN_PROJECT}\` before deploying, publishing,
|
|
38
52
|
rolling back, touching the database or migrations, cloud functions, schedules,
|
|
39
53
|
environment variables, file storage and uploads, connected accounts or production logs.
|
|
40
54
|
Command list: \`xflow help\`.
|
|
@@ -242,15 +256,21 @@ function defaultIds(base, global) {
|
|
|
242
256
|
}
|
|
243
257
|
return [...ids];
|
|
244
258
|
}
|
|
245
|
-
/**
|
|
259
|
+
/**
|
|
260
|
+
* Best-effort install from init and link: a failure must not break the linking.
|
|
261
|
+
* Reports whether the skill is in place, so the caller does not send an agent to a
|
|
262
|
+
* file that was never written.
|
|
263
|
+
*/
|
|
246
264
|
function installSkillQuietly(base) {
|
|
247
265
|
try {
|
|
248
266
|
if (install(base, defaultIds(base, false), false).length > 0) {
|
|
249
267
|
(0, ui_1.note)((0, ui_1.dim)(' The platform instructions are laid out for the AI agent'));
|
|
250
268
|
}
|
|
269
|
+
return true;
|
|
251
270
|
}
|
|
252
271
|
catch {
|
|
253
272
|
(0, ui_1.note)((0, ui_1.dim)(' Could not lay out the AI agent instructions: xflow skills'));
|
|
273
|
+
return false;
|
|
254
274
|
}
|
|
255
275
|
}
|
|
256
276
|
function pathLabel(agent, global) {
|
package/dist/commands/sources.js
CHANGED
|
@@ -142,7 +142,9 @@ async function downloadRevision(client, projectId, target, revision) {
|
|
|
142
142
|
(0, ui_1.warn)('The hash of the downloaded tree did not match the server one: the contents may have changed in transit');
|
|
143
143
|
}
|
|
144
144
|
(0, node_fs_1.mkdirSync)(target, { recursive: true });
|
|
145
|
-
|
|
145
|
+
// The hash above covers the archive as the server holds it; what lands on disk is the
|
|
146
|
+
// code, without the skill copies older revisions still carry.
|
|
147
|
+
const files = (0, zip_1.dropDirectoryEntries)(entries).filter((entry) => !(0, tree_1.isSkillCopy)(entry.path));
|
|
146
148
|
for (const entry of files) {
|
|
147
149
|
const path = (0, node_path_1.join)(target, entry.path);
|
|
148
150
|
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(path), { recursive: true });
|
package/dist/help.js
CHANGED
|
@@ -203,7 +203,7 @@ will work.`,
|
|
|
203
203
|
xflow db status what is applied and what is waiting
|
|
204
204
|
xflow db list databases of the organization, and who is on them
|
|
205
205
|
xflow db schema [table] tables of the project, or the columns of one
|
|
206
|
-
xflow db query "select ..." read data (--limit N)
|
|
206
|
+
xflow db query "select ..." read data (--limit N: 50 by default, 300 at most)
|
|
207
207
|
xflow db migrate apply migrations/*.sql
|
|
208
208
|
|
|
209
209
|
${(0, ui_1.bold)('list')} is about the organization, not about this project: it names the logical
|
package/dist/tree.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isSkillCopy = isSkillCopy;
|
|
3
4
|
exports.loadIgnoreRules = loadIgnoreRules;
|
|
4
5
|
exports.collectFiles = collectFiles;
|
|
5
6
|
exports.treeHash = treeHash;
|
|
@@ -7,6 +8,7 @@ exports.heaviest = heaviest;
|
|
|
7
8
|
const node_crypto_1 = require("node:crypto");
|
|
8
9
|
const node_fs_1 = require("node:fs");
|
|
9
10
|
const node_path_1 = require("node:path");
|
|
11
|
+
const skills_1 = require("./commands/skills");
|
|
10
12
|
/** Never sent. */
|
|
11
13
|
const DEFAULT_IGNORE = [
|
|
12
14
|
'node_modules/',
|
|
@@ -22,6 +24,10 @@ const DEFAULT_IGNORE = [
|
|
|
22
24
|
// Compiler cache: rewritten by every local build, so it would make a new
|
|
23
25
|
// revision out of nothing.
|
|
24
26
|
'tsconfig.tsbuildinfo',
|
|
27
|
+
// The skill ships inside the CLI and is laid out by init and link, so a copy of it
|
|
28
|
+
// is not project code. Sent once, it comes back with every clone and overwrites the
|
|
29
|
+
// fresh copy the CLI has just written with the one deployed back then.
|
|
30
|
+
...(0, skills_1.skillCopyPaths)(),
|
|
25
31
|
];
|
|
26
32
|
const IGNORE_FILE = '.xflowignore';
|
|
27
33
|
function globToRegExpSource(pattern) {
|
|
@@ -58,6 +64,16 @@ function compileRule(raw) {
|
|
|
58
64
|
const suffix = dirOnly ? '/' : '($|/)';
|
|
59
65
|
return { re: new RegExp(`${prefix}${source}${suffix}`) };
|
|
60
66
|
}
|
|
67
|
+
const skillRules = (0, skills_1.skillCopyPaths)()
|
|
68
|
+
.map(compileRule)
|
|
69
|
+
.filter((rule) => rule !== null);
|
|
70
|
+
/**
|
|
71
|
+
* The same paths on the way back: revisions deployed before the rule above still carry a
|
|
72
|
+
* copy, and unpacking it would put a stale skill in front of the agent.
|
|
73
|
+
*/
|
|
74
|
+
function isSkillCopy(path) {
|
|
75
|
+
return skillRules.some((rule) => rule.re.test(path));
|
|
76
|
+
}
|
|
61
77
|
/** Defaults, the .xflowignore file and the ignore field of xflow.json. */
|
|
62
78
|
function loadIgnoreRules(root, extra = []) {
|
|
63
79
|
const lines = [...DEFAULT_IGNORE, ...extra];
|
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 = '0.10.
|
|
5
|
+
exports.CLI_VERSION = '0.10.6';
|
|
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
|
@@ -24,7 +24,7 @@ contains the fix.
|
|
|
24
24
|
|
|
25
25
|
## Keeping these instructions current
|
|
26
26
|
|
|
27
|
-
These instructions ship with xflow CLI 0.10.
|
|
27
|
+
These instructions ship with xflow CLI 0.10.6. They travel inside the package, so the copy
|
|
28
28
|
you are reading can be older than the CLI answering your commands, and nothing about that
|
|
29
29
|
is visible in the text itself.
|
|
30
30
|
|
|
@@ -402,7 +402,8 @@ repository: that is somebody else's project. `xflow db list` names the databases
|
|
|
402
402
|
organization and the projects on each, which is where you find out who else is on yours.
|
|
403
403
|
For the same reason `migrations/` is not the
|
|
404
404
|
schema. It says what you did; `xflow db schema [table]` says what is in the database right
|
|
405
|
-
now, and `xflow db query "select ..."` reads it inside a READ ONLY transaction
|
|
405
|
+
now, and `xflow db query "select ..."` reads it inside a READ ONLY transaction (50 rows by
|
|
406
|
+
default, `--limit` raises that to 300). Look before
|
|
406
407
|
you write a migration against a shared database.
|
|
407
408
|
|
|
408
409
|
## File storage
|