@getxflow/cli 0.10.5 → 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.
@@ -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)();
@@ -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 \`.agents/skills/xflow/SKILL.md\` before deploying, publishing,
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
- /** Best-effort install from init and link: a failure must not break the linking. */
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) {
@@ -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
- const files = (0, zip_1.dropDirectoryEntries)(entries);
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/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';
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getxflow/cli",
3
- "version": "0.10.5",
3
+ "version": "0.10.6",
4
4
  "description": "CLI for the XFlow platform: source sync, deployment and publishing of applications",
5
5
  "license": "UNLICENSED",
6
6
  "engines": {
@@ -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.5. They travel inside the package, so the copy
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