@getxflow/cli 0.9.1 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/help.js CHANGED
@@ -3,10 +3,29 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.help = help;
4
4
  const ui_1 = require("./ui");
5
5
  const version_1 = require("./version");
6
+ /**
7
+ * A page keyed by something other than the command that leads to it. `xflow help invoke`
8
+ * is nobody's first guess when the command is `xflow functions invoke`, and a page that
9
+ * covers a neighbour answers for it too.
10
+ */
11
+ const ALIASES = {
12
+ functions: 'invoke',
13
+ publish: 'deploy',
14
+ deployments: 'rollback',
15
+ };
6
16
  /** Help text. For agents this is the reference documentation. */
7
17
  function help(topic) {
8
- if (topic && TOPICS[topic]) {
9
- (0, ui_1.out)(TOPICS[topic]);
18
+ if (topic) {
19
+ const page = TOPICS[ALIASES[topic] ?? topic];
20
+ if (page) {
21
+ (0, ui_1.out)(page);
22
+ return;
23
+ }
24
+ // Falling through to the full help here is what this used to do, and it reads as an
25
+ // answer: the agent asked about one command and got the whole screen back, with no
26
+ // sign that the page it asked for does not exist.
27
+ (0, ui_1.out)(`No page for "${topic}". Pages: ${Object.keys(TOPICS).sort().join(', ')}
28
+ Every command with a line of its own: xflow help`);
10
29
  return;
11
30
  }
12
31
  (0, ui_1.out)(`${(0, ui_1.bold)('xflow')} ${(0, ui_1.dim)(version_1.CLI_VERSION)} XFlow application hosting
@@ -24,6 +43,14 @@ ${(0, ui_1.bold)('Sources')}
24
43
  xflow pull [--into dir] [--revision N]
25
44
  fetch the sources (the latest revision by default)
26
45
 
46
+ ${(0, ui_1.bold)('Files')}
47
+ xflow storage ls [folder] what the file storage of the project holds
48
+ xflow storage push <dir> [--to folder] [--replace]
49
+ upload heavy static assets: photos, video, PDFs
50
+ xflow storage rm <address> delete one file
51
+ xflow storage rm --folder <path> --yes
52
+ delete a folder with everything in it
53
+
27
54
  ${(0, ui_1.bold)('Releasing')}
28
55
  xflow deploy [--no-push] send the code, ship the functions, build on the platform
29
56
  xflow publish show the dev version to visitors
@@ -64,6 +91,7 @@ ${(0, ui_1.bold)('Reference')}
64
91
  xflow whoami whose key this is and what it can do
65
92
  xflow logout [--all] forget the key of the active organization (--all: every one)
66
93
  xflow update update the CLI itself, and the skill that ships with it
94
+ xflow --version which version is installed
67
95
 
68
96
  ${(0, ui_1.bold)('Environment')}
69
97
  XFLOW_TOKEN access key (for CI, instead of xflow login)
@@ -179,6 +207,47 @@ Those show up in the answer to ${(0, ui_1.bold)('xflow functions invoke')}.
179
207
 
180
208
  Browser errors are collected by ${(0, ui_1.bold)('src/utils/error-logger.ts')} of the template and
181
209
  only from released addresses: a local ${(0, ui_1.bold)('npm run dev')} writes nothing here.`,
210
+ storage: `${(0, ui_1.bold)('xflow storage')}: files of the application
211
+
212
+ The sources archive is capped at 10 MB and is rebuilt and re-uploaded on every
213
+ deploy, so photos, video and PDFs do not belong in the repository. File storage
214
+ is the place for them: the files sit beside the versions, survive a build, and
215
+ are metered against the plan of the organization.
216
+
217
+ xflow storage ls [folder] what is there, with addresses and sizes
218
+ xflow storage push ./media --to media
219
+ upload a folder, keeping its structure
220
+ xflow storage rm <address> delete one file
221
+ xflow storage rm --folder photos --yes
222
+ delete a folder with everything in it
223
+
224
+ --to <folder> where to put it in the project storage
225
+ --replace upload files whose size differs from the stored one
226
+ --json machine-readable output, with every address
227
+
228
+ ${(0, ui_1.bold)('push')} sends only what is missing: a file already stored under the same path and
229
+ size is skipped, so running the command again after a broken connection is cheap.
230
+ A file that differs is left alone unless ${(0, ui_1.bold)('--replace')} is given. Bytes go straight
231
+ into the bucket, they do not pass through the platform.
232
+
233
+ Addresses are permanent and belong to the record, not to the bytes: replacing a
234
+ file keeps its address, so the links in the code and in the tables of the
235
+ application keep working. That is why there is no delete-and-upload-again.
236
+
237
+ The address is not public: it is a page of the platform that checks the rights of
238
+ the viewer on every request, so a closed project stops serving its pictures too.
239
+ Use the address as it came back, in ${(0, ui_1.bold)('<img src>')} and in the data of the application.
240
+
241
+ Dot entries and symbolic links are skipped, and ${(0, ui_1.bold)('.xflowignore')} is not read here:
242
+ those are the rules for the sources, and a media folder is exactly what one is
243
+ asked to put there so it stays out of the archive.
244
+
245
+ ${(0, ui_1.bold)('rm')} needs a right of its own (${(0, ui_1.bold)('Delete files')} in the Developers section of the
246
+ platform), off by default. The reason is the neighbourhood: whatever the users of
247
+ the application uploaded lives in the same folders, and there is no undo. A folder
248
+ first answers with the number of files it holds and deletes only on ${(0, ui_1.bold)('--yes')}.
249
+
250
+ There is no command back: sources return with ${(0, ui_1.bold)('xflow pull')}, files do not.`,
182
251
  env: `${(0, ui_1.bold)('xflow env')}: environment variables of the functions
183
252
 
184
253
  Keys, passwords and third-party addresses are kept by the platform, not by the
package/dist/state.js CHANGED
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.shouldNudge = shouldNudge;
4
+ exports.shouldNudgeSkill = shouldNudgeSkill;
5
+ exports.markSkillNudged = markSkillNudged;
4
6
  exports.markNudged = markNudged;
5
7
  const node_fs_1 = require("node:fs");
6
8
  const node_os_1 = require("node:os");
@@ -33,11 +35,43 @@ function shouldNudge(version) {
33
35
  return true;
34
36
  return Date.now() - at > NUDGE_INTERVAL_MS;
35
37
  }
36
- /** Best effort: a read-only home folder must not break the command that just worked. */
38
+ /**
39
+ * Same window, but per stale copy rather than per version. The key carries the CLI
40
+ * version, so the day a release actually changes the skill the notice speaks again
41
+ * instead of staying quiet on yesterday's answer.
42
+ *
43
+ * A window and not a single "already said it": the copy stays wrong until somebody
44
+ * refreshes it, and an agent whose session starts tomorrow has to hear about it too.
45
+ */
46
+ function shouldNudgeSkill(key) {
47
+ if (process.env.CI)
48
+ return false;
49
+ const at = read().skillNudgedAt?.[key];
50
+ const said = at ? Date.parse(at) : Number.NaN;
51
+ if (!Number.isFinite(said))
52
+ return true;
53
+ return Date.now() - said > NUDGE_INTERVAL_MS;
54
+ }
55
+ /** Entries pile up across versions and folders, so the stale ones go on every write. */
56
+ function markSkillNudged(key) {
57
+ const now = Date.now();
58
+ const kept = {};
59
+ for (const [seen, at] of Object.entries(read().skillNudgedAt ?? {})) {
60
+ const said = Date.parse(at);
61
+ if (Number.isFinite(said) && now - said < NUDGE_INTERVAL_MS * 30)
62
+ kept[seen] = at;
63
+ }
64
+ kept[key] = new Date(now).toISOString();
65
+ write({ skillNudgedAt: kept });
66
+ }
37
67
  function markNudged(version) {
68
+ write({ nudgedVersion: version, nudgedAt: new Date().toISOString() });
69
+ }
70
+ /** Best effort: a read-only home folder must not break the command that just worked. */
71
+ function write(patch) {
38
72
  try {
39
73
  (0, node_fs_1.mkdirSync)((0, node_path_1.join)((0, node_os_1.homedir)(), '.xflow'), { recursive: true, mode: 0o700 });
40
- const state = { ...read(), nudgedVersion: version, nudgedAt: new Date().toISOString() };
74
+ const state = { ...read(), ...patch };
41
75
  (0, node_fs_1.writeFileSync)(statePath(), `${JSON.stringify(state, null, 2)}\n`, 'utf-8');
42
76
  }
43
77
  catch {
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.9.1';
5
+ exports.CLI_VERSION = '0.10.1';
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.9.1",
3
+ "version": "0.10.1",
4
4
  "description": "CLI for the XFlow platform: source sync, deployment and publishing of applications",
5
5
  "license": "UNLICENSED",
6
6
  "engines": {