@xano/cli 1.0.6-beta.5 → 1.0.6-beta.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.
Files changed (55) hide show
  1. package/README.md +95 -4
  2. package/dist/commands/branch/list/index.js +5 -1
  3. package/dist/commands/ephemeral/list/index.js +2 -1
  4. package/dist/commands/ephemeral/static_host/build/list/index.d.ts +2 -3
  5. package/dist/commands/ephemeral/static_host/build/list/index.js +19 -40
  6. package/dist/commands/ephemeral/static_host/list/index.d.ts +2 -3
  7. package/dist/commands/ephemeral/static_host/list/index.js +20 -40
  8. package/dist/commands/function/list/index.d.ts +4 -4
  9. package/dist/commands/function/list/index.js +30 -38
  10. package/dist/commands/knowledge/list/index.js +2 -1
  11. package/dist/commands/platform/list/index.js +13 -14
  12. package/dist/commands/release/list/index.js +13 -14
  13. package/dist/commands/sandbox/env/list/index.js +4 -0
  14. package/dist/commands/sandbox/unit_test/list/index.d.ts +2 -0
  15. package/dist/commands/sandbox/unit_test/list/index.js +9 -14
  16. package/dist/commands/sandbox/unit_test/run_all/index.d.ts +1 -0
  17. package/dist/commands/sandbox/unit_test/run_all/index.js +67 -37
  18. package/dist/commands/sandbox/workflow_test/list/index.d.ts +2 -0
  19. package/dist/commands/sandbox/workflow_test/list/index.js +9 -14
  20. package/dist/commands/sandbox/workflow_test/run_all/index.d.ts +1 -0
  21. package/dist/commands/sandbox/workflow_test/run_all/index.js +65 -35
  22. package/dist/commands/static_host/build/list/index.d.ts +2 -3
  23. package/dist/commands/static_host/build/list/index.js +19 -40
  24. package/dist/commands/static_host/build/pull/index.js +12 -9
  25. package/dist/commands/static_host/list/index.d.ts +2 -3
  26. package/dist/commands/static_host/list/index.js +20 -40
  27. package/dist/commands/static_host/migrate/index.js +14 -10
  28. package/dist/commands/tenant/backup/list/index.js +13 -14
  29. package/dist/commands/tenant/cluster/list/index.js +9 -10
  30. package/dist/commands/tenant/env/list/index.js +4 -0
  31. package/dist/commands/tenant/list/index.js +19 -20
  32. package/dist/commands/tenant/snapshot/list/index.js +5 -1
  33. package/dist/commands/tenant/unit_test/list/index.d.ts +4 -2
  34. package/dist/commands/tenant/unit_test/list/index.js +9 -14
  35. package/dist/commands/tenant/unit_test/run_all/index.d.ts +1 -0
  36. package/dist/commands/tenant/unit_test/run_all/index.js +67 -37
  37. package/dist/commands/tenant/workflow_test/list/index.d.ts +4 -2
  38. package/dist/commands/tenant/workflow_test/list/index.js +9 -14
  39. package/dist/commands/tenant/workflow_test/run_all/index.d.ts +1 -0
  40. package/dist/commands/tenant/workflow_test/run_all/index.js +65 -35
  41. package/dist/commands/unit_test/list/index.d.ts +3 -1
  42. package/dist/commands/unit_test/list/index.js +9 -14
  43. package/dist/commands/unit_test/run_all/index.d.ts +1 -0
  44. package/dist/commands/unit_test/run_all/index.js +68 -40
  45. package/dist/commands/workflow_test/list/index.d.ts +3 -1
  46. package/dist/commands/workflow_test/list/index.js +10 -13
  47. package/dist/commands/workflow_test/run_all/index.d.ts +1 -0
  48. package/dist/commands/workflow_test/run_all/index.js +67 -36
  49. package/dist/commands/workspace/list/index.js +7 -13
  50. package/dist/utils/concurrency.d.ts +29 -0
  51. package/dist/utils/concurrency.js +54 -0
  52. package/dist/utils/paging.d.ts +141 -0
  53. package/dist/utils/paging.js +194 -0
  54. package/oclif.manifest.json +3017 -2891
  55. package/package.json +1 -1
package/README.md CHANGED
@@ -39,6 +39,38 @@ Destructive commands include imperative safety prefixes in their help text and f
39
39
 
40
40
  These warnings are layer 1 of broader push-safety work; ephemeral sandbox environments and push preview remain the structural safeguards.
41
41
 
42
+ ### Paging on list commands
43
+
44
+ The Metadata API pages unevenly, so `list` commands differ in what they expose. Every command reports only what its endpoint actually returns — the CLI never guesses whether more results exist.
45
+
46
+ | Commands | Flags | Footer |
47
+ |---|---|---|
48
+ | `function list`, `unit_test list`, `workflow_test list` (plus `sandbox` / `tenant` variants) | `--page`, `--per_page` | `Page 2 · 50 shown · next: --page 3` |
49
+ | `static_host list`, `static_host build list` (plus `ephemeral` variants) | `--page` only | `Page 2 · 100 shown · 340 total` |
50
+ | `branch list`, `workspace list`, `tenant snapshot list`, `sandbox env list`, `tenant env list` | none | `12 branches` |
51
+ | `tenant list`, `release list`, `platform list`, `tenant cluster list`, `ephemeral list` | none | none |
52
+ | `tenant backup list` | `--page` only (pre-existing) | none |
53
+
54
+ Notes:
55
+
56
+ - **`--per_page` is only offered where the endpoint accepts it.** Static-host endpoints hardcode 100 items per page server-side, so those commands take `--page` alone. `xano static_host list --per_page 10` previously parsed but did nothing; it is now rejected rather than silently ignored.
57
+ - **All paged commands default `--per_page` to 50.** The test-list commands previously requested 10000 internally so nothing was ever cut off; now that the footer and the JSON envelope both report position, a page that stops at 50 is visible rather than silent. Raise it (up to 10000) when you want everything in one call.
58
+ - **The last group has no CLI paging.** Those endpoints page server-side but return a plain array with no page or total metadata, so the CLI cannot tell you where you are. Rather than show a page number with no context — or infer "more available" from a full page, which is wrong exactly when the result count is a multiple of the page size — these commands are left as-is. **They return at most 25–50 items** (50 for tenants and ephemerals, 25 for releases, platforms, and tenant backups). If you have more than that, query the Metadata API directly until those endpoints return paging metadata.
59
+ - **`--output json` returns an envelope, not a bare array.** Every list command emits `{count, items, ...}`, with `page`, `per_page`, `next_page`, `prev_page`, and `total` included only when the endpoint actually reports them. This is a **breaking change** for scripts that parsed the previous bare array — read `.items` instead.
60
+
61
+ ```jsonc
62
+ // xano function list -o json
63
+ {
64
+ "count": 50,
65
+ "page": 2,
66
+ "per_page": 50,
67
+ "next_page": 3, // absent on the last page — never inferred
68
+ "items": [ /* ... */ ]
69
+ }
70
+ ```
71
+
72
+ The point of the envelope is that a script gets the same honest stop condition a human gets from the footer. Without it the only signal available is `items.length < per_page`, which is wrong exactly when the result count is a multiple of the page size. Absence of `next_page` means the server said there is no next page; it is never omitted as a guess.
73
+
42
74
  ### Authentication
43
75
 
44
76
  ```bash
@@ -197,6 +229,35 @@ xano workspace git pull -r https://github.com/owner/private-repo -t ghp_xxx
197
229
  xano workspace git pull -r https://github.com/owner/repo --path subdir
198
230
  ```
199
231
 
232
+ ### Knowledge
233
+
234
+ Knowledge items are user-authored docs and skills (e.g. `CLAUDE.md`, `AGENTS.md`, runbooks)
235
+ attached to a workspace. Each knowledge item's **name is a path** (e.g. `some/thing/CLAUDE.md`):
236
+ `pull` writes its content to that path under the output directory, and `push` turns each local
237
+ file into a knowledge item named by its relative path.
238
+
239
+ Push matches local files to remote items by name. Existing items are updated (content only —
240
+ description, mode, tags, and other metadata are preserved); new files are created. The
241
+ knowledge type for new items is inferred from the filename: `AGENTS.md` → `agents.md`,
242
+ `SKILL.md` → `skill`, everything else → `doc`. Hidden files (dotfiles) and `node_modules`
243
+ are skipped.
244
+
245
+ ```bash
246
+ # Pull knowledge files to local paths (defaults to current directory)
247
+ xano knowledge pull
248
+ xano knowledge pull -d ./knowledge # Specify output directory
249
+ xano knowledge pull -b dev # Specific branch
250
+
251
+ # Push local files as knowledge (defaults to current directory, only changed files)
252
+ xano knowledge push
253
+ xano knowledge push -d ./knowledge # Push from a specific directory
254
+ xano knowledge push --dry-run # Preview changes without pushing
255
+ xano knowledge push --sync --delete # Full push + delete remote knowledge not included
256
+ xano knowledge push --force # Skip preview and confirmation (for CI/CD)
257
+ xano knowledge push -i "guides/*" # Push only matching files
258
+ xano knowledge push -e "**/README.md" # Push all files except READMEs
259
+ ```
260
+
200
261
  ### Branches
201
262
 
202
263
  All branch commands use **branch labels** (e.g., `v1`, `dev`), not IDs.
@@ -372,9 +433,10 @@ xano platform get <platform_id>
372
433
  #### Unit Tests
373
434
 
374
435
  ```bash
375
- # List unit tests
436
+ # List unit tests (returns all tests by default; --per_page narrows the page)
376
437
  xano unit_test list
377
438
  xano unit_test list --branch dev --obj-type function
439
+ xano unit_test list --page 2 --per_page 25
378
440
 
379
441
  # Run a single unit test
380
442
  xano unit_test run <unit_test_id>
@@ -388,9 +450,10 @@ xano unit_test run_all --branch dev --obj-type function
388
450
  #### Workflow Tests
389
451
 
390
452
  ```bash
391
- # List workflow tests
453
+ # List workflow tests (returns all tests by default; --per_page narrows the page)
392
454
  xano workflow_test list
393
455
  xano workflow_test list --branch dev
456
+ xano workflow_test list --page 2 --per_page 25
394
457
 
395
458
  # Get workflow test details
396
459
  xano workflow_test get <workflow_test_id>
@@ -404,10 +467,35 @@ xano workflow_test run <workflow_test_id>
404
467
  xano workflow_test run_all
405
468
  xano workflow_test run_all --branch dev
406
469
 
470
+ # Run tests in parallel (default is 1 — sequential)
471
+ xano unit_test run_all --concurrency 4
472
+ xano workflow_test run_all --concurrency 4
473
+
474
+ # Sandbox and tenant variants take the same paging flags
475
+ xano sandbox unit_test list --page 2 --per_page 25
476
+ xano sandbox workflow_test list --page 2 --per_page 25
477
+ xano tenant unit_test list my-tenant --page 2 --per_page 25
478
+ xano tenant workflow_test list my-tenant --page 2 --per_page 25
479
+
407
480
  # Delete a workflow test
408
481
  xano workflow_test delete <workflow_test_id>
409
482
  ```
410
483
 
484
+ #### Running tests in parallel
485
+
486
+ Every `run_all` command takes `--concurrency` (default `1`):
487
+
488
+ ```bash
489
+ xano unit_test run_all --concurrency 4
490
+ xano tenant unit_test run_all my-tenant --concurrency 8
491
+ ```
492
+
493
+ Output stays in test order regardless of concurrency, so a parallel run is still diffable against a sequential one, and the JSON `results` array keeps a stable order.
494
+
495
+ `run_all` discovers the test list by paging at 100 per request rather than asking for the whole suite in one response, and stops when the API reports no next page.
496
+
497
+ **The default is 1 on purpose.** These tests execute against a shared workspace database, so tests that touch the same tables or records can interfere with each other when run at the same time — producing failures that look like flakiness rather than a real regression. Raise `--concurrency` once you know your tests are independent.
498
+
411
499
  ### Tenants
412
500
 
413
501
  Manage tenants, their environment variables, backups, deployments, and clusters.
@@ -664,16 +752,19 @@ xano sandbox reset --force
664
752
  ### Static Hosts
665
753
 
666
754
  ```bash
667
- # List static hosts
755
+ # List static hosts. Page size is fixed at 100 by the API, so there is no
756
+ # --per_page flag; the footer reports the true total.
668
757
  xano static_host list
758
+ xano static_host list --page 2
669
759
 
670
760
  # Create / get / edit a static host
671
761
  xano static_host create marketing --description "Marketing site"
672
762
  xano static_host get marketing
673
763
  xano static_host edit marketing --name marketing-v2 --description "Updated"
674
764
 
675
- # List builds
765
+ # List builds (--page only; page size is fixed at 100 by the API)
676
766
  xano static_host build list default
767
+ xano static_host build list default --page 2
677
768
 
678
769
  # Get build details
679
770
  xano static_host build get default --build_id 52
@@ -1,5 +1,6 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import BaseCommand from '../../../base-command.js';
3
+ import { buildPagingJson, formatPagingFooter } from '../../../utils/paging.js';
3
4
  export function filterBackups(branches, includeBackups) {
4
5
  return includeBackups ? branches : branches.filter((b) => !b.backup);
5
6
  }
@@ -82,7 +83,7 @@ Available branches:
82
83
  const branches = filterBackups(allBranches, flags.backups);
83
84
  // Output results
84
85
  if (flags.output === 'json') {
85
- this.log(JSON.stringify(branches, null, 2));
86
+ this.log(JSON.stringify(buildPagingJson({ items: branches }, { tier: 'none' }), null, 2));
86
87
  }
87
88
  else {
88
89
  // summary format
@@ -97,6 +98,9 @@ Available branches:
97
98
  this.log(` - ${branch.label}${liveIndicator}${backupIndicator}`);
98
99
  }
99
100
  }
101
+ const footer = formatPagingFooter({ items: branches }, { noun: 'branch', nounPlural: 'branches', tier: 'none' });
102
+ if (footer)
103
+ this.log(footer);
100
104
  }
101
105
  }
102
106
  catch (error) {
@@ -1,5 +1,6 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import BaseCommand from '../../../base-command.js';
3
+ import { buildPagingJson } from '../../../utils/paging.js';
3
4
  export default class EphemeralList extends BaseCommand {
4
5
  static description = 'List ephemeral tenants in a workspace, or across the whole instance with --global';
5
6
  static examples = [
@@ -73,7 +74,7 @@ Ephemeral tenants (all workspaces):
73
74
  this.error('Unexpected API response format');
74
75
  }
75
76
  if (flags.output === 'json') {
76
- this.log(JSON.stringify(tenants, null, 2));
77
+ this.log(JSON.stringify(buildPagingJson({ items: tenants }, { tier: 'none' }), null, 2));
77
78
  }
78
79
  else if (tenants.length === 0) {
79
80
  this.log('No ephemeral tenants found');
@@ -6,11 +6,10 @@ export default class EphemeralStaticHostBuildList extends BaseCommand {
6
6
  static description: string;
7
7
  static examples: string[];
8
8
  static flags: {
9
- output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
- page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
11
- per_page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
12
9
  'static-host': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
10
  workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
12
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
14
13
  config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
14
  profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
15
  verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
@@ -1,5 +1,6 @@
1
1
  import { Args, Flags } from '@oclif/core';
2
2
  import BaseCommand from '../../../../../base-command.js';
3
+ import { buildPagingJson, buildPagingParams, formatPagingFooter, normalizeListResponse, pagingFlags } from '../../../../../utils/paging.js';
3
4
  export default class EphemeralStaticHostBuildList extends BaseCommand {
4
5
  static args = {
5
6
  tenant_name: Args.string({
@@ -20,13 +21,16 @@ Available builds:
20
21
  - staging (ID: 2) - Status: completed
21
22
  `,
22
23
  `$ xano ephemeral static_host build list e4f2-9ab1-xyz1 -H default -w 40 --output json
23
- [
24
- {
25
- "id": 1,
26
- "name": "v1.0.0",
27
- "status": "completed"
28
- }
29
- ]
24
+ {
25
+ "count": 1,
26
+ "page": 1,
27
+ "total": 12,
28
+ "items": [
29
+ {
30
+ "id": 52
31
+ }
32
+ ]
33
+ }
30
34
  `,
31
35
  `$ xano ephemeral static_host build list e4f2-9ab1-xyz1 -H default -p staging -o json --page 2
32
36
  [
@@ -46,16 +50,7 @@ Available builds:
46
50
  options: ['summary', 'json'],
47
51
  required: false,
48
52
  }),
49
- page: Flags.integer({
50
- default: 1,
51
- description: 'Page number for pagination',
52
- required: false,
53
- }),
54
- per_page: Flags.integer({
55
- default: 50,
56
- description: 'Number of results per page',
57
- required: false,
58
- }),
53
+ ...pagingFlags('page-only-envelope', { fixedPerPage: 100 }),
59
54
  'static-host': Flags.string({
60
55
  char: 'H',
61
56
  description: 'Static host name',
@@ -86,13 +81,7 @@ Available builds:
86
81
  ` 2. Set it in your profile using: xano profile:edit ${profileName} -w <workspace_id>`);
87
82
  }
88
83
  // Build query parameters
89
- const queryParams = new URLSearchParams({
90
- page: flags.page.toString(),
91
- });
92
- // Only add per_page if it's not the default value
93
- if (flags.per_page !== 50) {
94
- queryParams.append('per_page', flags.per_page.toString());
95
- }
84
+ const queryParams = new URLSearchParams(buildPagingParams(flags, 'page-only-envelope'));
96
85
  // Construct the API URL
97
86
  const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/static_host/${staticHost}/build?${queryParams.toString()}`;
98
87
  // Fetch builds from the API
@@ -108,24 +97,11 @@ Available builds:
108
97
  const errorText = await response.text();
109
98
  this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
110
99
  }
111
- const data = await response.json();
112
- // Handle different response formats
113
- let builds;
114
- if (Array.isArray(data)) {
115
- builds = data;
116
- }
117
- else if (data && typeof data === 'object' && 'builds' in data && Array.isArray(data.builds)) {
118
- builds = data.builds;
119
- }
120
- else if (data && typeof data === 'object' && 'items' in data && Array.isArray(data.items)) {
121
- builds = data.items;
122
- }
123
- else {
124
- this.error('Unexpected API response format');
125
- }
100
+ const list = normalizeListResponse(await response.json(), ['builds']);
101
+ const builds = list.items;
126
102
  // Output results
127
103
  if (flags.output === 'json') {
128
- this.log(JSON.stringify(builds, null, 2));
104
+ this.log(JSON.stringify(buildPagingJson(list, { page: flags.page, tier: 'page-only-envelope' }), null, 2));
129
105
  }
130
106
  else {
131
107
  // summary format
@@ -144,6 +120,9 @@ Available builds:
144
120
  }
145
121
  }
146
122
  }
123
+ const footer = formatPagingFooter(list, { noun: 'build', page: flags.page, tier: 'page-only-envelope' });
124
+ if (footer)
125
+ this.log(footer);
147
126
  }
148
127
  }
149
128
  catch (error) {
@@ -6,10 +6,9 @@ export default class EphemeralStaticHostList extends BaseCommand {
6
6
  static description: string;
7
7
  static examples: string[];
8
8
  static flags: {
9
- output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
- page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
11
- per_page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
12
9
  workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
11
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
12
  config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
13
  profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
14
  verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
@@ -1,5 +1,6 @@
1
1
  import { Args, Flags } from '@oclif/core';
2
2
  import BaseCommand from '../../../../base-command.js';
3
+ import { buildPagingJson, buildPagingParams, formatPagingFooter, normalizeListResponse, pagingFlags } from '../../../../utils/paging.js';
3
4
  export default class EphemeralStaticHostList extends BaseCommand {
4
5
  static args = {
5
6
  tenant_name: Args.string({
@@ -20,13 +21,17 @@ Available static hosts:
20
21
  - another-host (ID: 2)
21
22
  `,
22
23
  `$ xano ephemeral static_host list e4f2-9ab1-xyz1 -w 40 --output json
23
- [
24
- {
25
- "id": 1,
26
- "name": "my-static-host",
27
- "domain": "example.com"
28
- }
29
- ]
24
+ {
25
+ "count": 1,
26
+ "page": 1,
27
+ "total": 3,
28
+ "items": [
29
+ {
30
+ "id": 1,
31
+ "name": "my-static-host"
32
+ }
33
+ ]
34
+ }
30
35
  `,
31
36
  `$ xano ephemeral static_host list e4f2-9ab1-xyz1 -p staging -o json --page 2
32
37
  [
@@ -46,16 +51,7 @@ Available static hosts:
46
51
  options: ['summary', 'json'],
47
52
  required: false,
48
53
  }),
49
- page: Flags.integer({
50
- default: 1,
51
- description: 'Page number for pagination',
52
- required: false,
53
- }),
54
- per_page: Flags.integer({
55
- default: 50,
56
- description: 'Number of results per page',
57
- required: false,
58
- }),
54
+ ...pagingFlags('page-only-envelope', { fixedPerPage: 100 }),
59
55
  workspace: Flags.string({
60
56
  char: 'w',
61
57
  description: 'Workspace ID (optional if set in profile)',
@@ -80,13 +76,7 @@ Available static hosts:
80
76
  ` 2. Set it in your profile using: xano profile:edit ${profileName} -w <workspace_id>`);
81
77
  }
82
78
  // Build query parameters
83
- const queryParams = new URLSearchParams({
84
- page: flags.page.toString(),
85
- });
86
- // Only add per_page if it's not the default value
87
- if (flags.per_page !== 50) {
88
- queryParams.append('per_page', flags.per_page.toString());
89
- }
79
+ const queryParams = new URLSearchParams(buildPagingParams(flags, 'page-only-envelope'));
90
80
  // Construct the API URL
91
81
  const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/static_host?${queryParams.toString()}`;
92
82
  // Fetch static hosts from the API
@@ -102,24 +92,11 @@ Available static hosts:
102
92
  const errorText = await response.text();
103
93
  this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
104
94
  }
105
- const data = await response.json();
106
- // Handle different response formats
107
- let staticHosts;
108
- if (Array.isArray(data)) {
109
- staticHosts = data;
110
- }
111
- else if (data && typeof data === 'object' && 'static_hosts' in data && Array.isArray(data.static_hosts)) {
112
- staticHosts = data.static_hosts;
113
- }
114
- else if (data && typeof data === 'object' && 'items' in data && Array.isArray(data.items)) {
115
- staticHosts = data.items;
116
- }
117
- else {
118
- this.error('Unexpected API response format');
119
- }
95
+ const list = normalizeListResponse(await response.json(), ['static_hosts']);
96
+ const staticHosts = list.items;
120
97
  // Output results
121
98
  if (flags.output === 'json') {
122
- this.log(JSON.stringify(staticHosts, null, 2));
99
+ this.log(JSON.stringify(buildPagingJson(list, { page: flags.page, tier: 'page-only-envelope' }), null, 2));
123
100
  }
124
101
  else {
125
102
  // summary format
@@ -138,6 +115,9 @@ Available static hosts:
138
115
  }
139
116
  }
140
117
  }
118
+ const footer = formatPagingFooter(list, { noun: 'static host', page: flags.page, tier: 'page-only-envelope' });
119
+ if (footer)
120
+ this.log(footer);
141
121
  }
142
122
  }
143
123
  catch (error) {
@@ -4,14 +4,14 @@ export default class FunctionList extends BaseCommand {
4
4
  static description: string;
5
5
  static examples: string[];
6
6
  static flags: {
7
+ sort: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
8
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
10
+ per_page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
7
11
  include_draft: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
12
  include_xanoscript: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
13
  order: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
14
  output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
- page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
12
- per_page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
13
- sort: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
14
- workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
15
  config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
16
  profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
17
  verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
@@ -1,5 +1,6 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import BaseCommand from '../../../base-command.js';
3
+ import { buildPagingJson, formatPagingFooter, normalizeListResponse, pagingFlags } from '../../../utils/paging.js';
3
4
  export default class FunctionList extends BaseCommand {
4
5
  static args = {};
5
6
  static description = 'List all functions in a workspace from the Xano Metadata API';
@@ -16,20 +17,30 @@ Available functions:
16
17
  - another-function (ID: 2)
17
18
  `,
18
19
  `$ xano function:list -w 40 --output json
19
- [
20
- {
21
- "id": 1,
22
- "name": "function-1"
23
- }
24
- ]
20
+ {
21
+ "count": 1,
22
+ "page": 1,
23
+ "per_page": 50,
24
+ "next_page": 2,
25
+ "items": [
26
+ {
27
+ "id": 1,
28
+ "name": "function-1"
29
+ }
30
+ ]
31
+ }
25
32
  `,
26
33
  `$ xano function:list -p staging -o json --include_draft
27
- [
28
- {
29
- "id": 1,
30
- "name": "function-1"
31
- }
32
- ]
34
+ {
35
+ "count": 1,
36
+ "page": 1,
37
+ "items": [
38
+ {
39
+ "id": 1,
40
+ "name": "function-1"
41
+ }
42
+ ]
43
+ }
33
44
  `,
34
45
  ];
35
46
  static flags = {
@@ -57,16 +68,7 @@ Available functions:
57
68
  options: ['summary', 'json'],
58
69
  required: false,
59
70
  }),
60
- page: Flags.integer({
61
- default: 1,
62
- description: 'Page number for pagination',
63
- required: false,
64
- }),
65
- per_page: Flags.integer({
66
- default: 50,
67
- description: 'Number of results per page',
68
- required: false,
69
- }),
71
+ ...pagingFlags('envelope', { maxPerPage: 10_000 }),
70
72
  sort: Flags.string({
71
73
  default: 'created_at',
72
74
  description: 'Sort field',
@@ -118,24 +120,11 @@ Available functions:
118
120
  const errorText = await response.text();
119
121
  this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
120
122
  }
121
- const data = await response.json();
122
- // Handle different response formats
123
- let functions;
124
- if (Array.isArray(data)) {
125
- functions = data;
126
- }
127
- else if (data && typeof data === 'object' && 'functions' in data && Array.isArray(data.functions)) {
128
- functions = data.functions;
129
- }
130
- else if (data && typeof data === 'object' && 'items' in data && Array.isArray(data.items)) {
131
- functions = data.items;
132
- }
133
- else {
134
- this.error('Unexpected API response format');
135
- }
123
+ const list = normalizeListResponse(await response.json(), ['functions']);
124
+ const functions = list.items;
136
125
  // Output results
137
126
  if (flags.output === 'json') {
138
- this.log(JSON.stringify(functions, null, 2));
127
+ this.log(JSON.stringify(buildPagingJson(list, { page: flags.page, perPage: flags.per_page, tier: 'envelope' }), null, 2));
139
128
  }
140
129
  else {
141
130
  // summary format
@@ -153,6 +142,9 @@ Available functions:
153
142
  }
154
143
  }
155
144
  }
145
+ const footer = formatPagingFooter(list, { noun: 'function', page: flags.page, tier: 'envelope' });
146
+ if (footer)
147
+ this.log(footer);
156
148
  }
157
149
  }
158
150
  catch (error) {
@@ -1,5 +1,6 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import BaseCommand from '../../../base-command.js';
3
+ import { buildPagingJson } from '../../../utils/paging.js';
3
4
  export default class KnowledgeList extends BaseCommand {
4
5
  static args = {};
5
6
  static description = 'List workspace knowledge and skills as plain-text markdown';
@@ -92,7 +93,7 @@ export default class KnowledgeList extends BaseCommand {
92
93
  const data = await response.json();
93
94
  const items = Array.isArray(data) ? data : (data.items ?? []);
94
95
  if (flags.output === 'json') {
95
- this.log(JSON.stringify(items, null, 2));
96
+ this.log(JSON.stringify(buildPagingJson({ items }, { tier: 'none' }), null, 2));
96
97
  return;
97
98
  }
98
99
  this.log(this.formatMarkdown(items));
@@ -1,5 +1,6 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import BaseCommand from '../../../base-command.js';
3
+ import { buildPagingJson } from '../../../utils/paging.js';
3
4
  export default class PlatformList extends BaseCommand {
4
5
  static description = 'List all platforms';
5
6
  static examples = [
@@ -47,22 +48,20 @@ Platforms:
47
48
  this.error('Unexpected API response format');
48
49
  }
49
50
  if (flags.output === 'json') {
50
- this.log(JSON.stringify(platforms, null, 2));
51
+ this.log(JSON.stringify(buildPagingJson({ items: platforms }, { tier: 'none' }), null, 2));
52
+ }
53
+ else if (platforms.length === 0) {
54
+ this.log('No platforms found');
51
55
  }
52
56
  else {
53
- if (platforms.length === 0) {
54
- this.log('No platforms found');
55
- }
56
- else {
57
- this.log('Platforms:');
58
- for (const platform of platforms) {
59
- const label = platform.name || `ID: ${platform.id}`;
60
- const helmTag = platform.helm?.tag ? ` | Helm: ${platform.helm.tag}` : '';
61
- const created = platform.created_at
62
- ? ` | Created: ${new Date(platform.created_at).toISOString().split('T')[0]}`
63
- : '';
64
- this.log(` ${label}${helmTag}${created}`);
65
- }
57
+ this.log('Platforms:');
58
+ for (const platform of platforms) {
59
+ const label = platform.name || `ID: ${platform.id}`;
60
+ const helmTag = platform.helm?.tag ? ` | Helm: ${platform.helm.tag}` : '';
61
+ const created = platform.created_at
62
+ ? ` | Created: ${new Date(platform.created_at).toISOString().split('T')[0]}`
63
+ : '';
64
+ this.log(` ${label}${helmTag}${created}`);
66
65
  }
67
66
  }
68
67
  }