@norbix.ai/cli 0.2.0 → 0.3.0

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/README.md CHANGED
@@ -40,6 +40,7 @@ norbix scheduler list
40
40
  | `norbix config list/get/set/unset` | Manage the local config file |
41
41
  | `norbix db find/get/count/insert/update/delete/aggregate` | Work with database collections |
42
42
  | `norbix files list/info/upload/download/sign/delete/publish/unpublish` | Upload, download, manage and publish files |
43
+ | `norbix files integrations test <id>` | Check that a files integration really works (live upload/read/list/delete probe) |
43
44
  | `norbix users list/get/invite/block/unblock/delete` | Manage project users (membership) |
44
45
  | `norbix env list/use/delete` | Manage project environments |
45
46
  | `norbix logs list/trail` | Read project logs, follow one correlation ID |
@@ -148,7 +149,7 @@ norbix files download invoices/2026/invoice.pdf
148
149
 
149
150
  Or pass `--integration <id>` (env var: `NORBIX_FILES_INTEGRATION_ID`).
150
151
 
151
- ### The eight file commands
152
+ ### The file commands
152
153
 
153
154
  | Command | What it does |
154
155
  | --- | --- |
@@ -160,6 +161,7 @@ Or pass `--integration <id>` (env var: `NORBIX_FILES_INTEGRATION_ID`).
160
161
  | `norbix files delete <path> [--yes]` | Delete one file. Asks first unless you pass `--yes`. |
161
162
  | `norbix files publish <path> [--folder]` | Give the file — or the whole folder — a link anyone can open, and print it. |
162
163
  | `norbix files unpublish <path> [--folder]` | Take that link away again. |
164
+ | `norbix files integrations test <id>` | Check that the storage behind an integration really works. See below. |
163
165
 
164
166
  Upload and download take more than one step, so the file bytes never pass
165
167
  through Norbix:
@@ -212,6 +214,27 @@ Four rules worth knowing:
212
214
  * Every dead link answers the same plain `404`: unknown, renamed, unpublished,
213
215
  deleted. A more precise answer would tell a stranger that the file is there.
214
216
 
217
+ ### Testing an integration
218
+
219
+ `norbix files integrations test <id>` runs a live probe against the storage
220
+ behind the integration: it uploads a small file, reads it, lists its folder and
221
+ deletes it again. One line per step:
222
+
223
+ ```sh
224
+ norbix files integrations test 55555555-5555-5555-5555-555555555555
225
+ # UploadFile OK
226
+ # GetFile FAILED
227
+ # - NoSuchKey: the object was not found
228
+ # GetAllFiles NOT_TESTED
229
+ # DeleteFile NOT_TESTED
230
+ # › Error: The files integration test failed: GetFile FAILED (2 later steps not tested).
231
+ ```
232
+
233
+ `NOT_TESTED` means the step was skipped because an earlier one failed. The
234
+ command exits `0` only when every step is `OK`, so it works in a script or a
235
+ CI job. `--json` prints the steps as data and keeps the same exit code. The
236
+ probe writes to the storage, so the key needs the `files:create` permission.
237
+
215
238
  ## Tests
216
239
 
217
240
  ```sh
@@ -0,0 +1,85 @@
1
+ import { Args } from '@oclif/core';
2
+ import { BaseCommand } from '../../../base.js';
3
+ import { callTestFilesIntegration, normaliseResult, } from '../../../lib/filesIntegrationTest.js';
4
+ export default class FilesIntegrationsTest extends BaseCommand {
5
+ static description = `Check that a files integration really works
6
+
7
+ Runs a live probe against the storage: uploads a small file, reads it, lists
8
+ its folder and deletes it again. Prints one line per step — OK, FAILED or
9
+ NOT_TESTED (skipped because an earlier step failed) — with the errors of a
10
+ failed step. Exits non-zero unless every step is OK.
11
+
12
+ Needs the files:create permission, because the probe writes to the storage.`;
13
+ static examples = [
14
+ '<%= config.bin %> files integrations test 55555555-5555-5555-5555-555555555555',
15
+ '<%= config.bin %> files integrations test 55555555-5555-5555-5555-555555555555 --json',
16
+ ];
17
+ static args = {
18
+ id: Args.string({ required: true, description: 'Files integration ID' }),
19
+ };
20
+ async run() {
21
+ const { args, flags } = await this.parse(FilesIntegrationsTest);
22
+ const ctx = this.resolveContext(flags);
23
+ // Builds nothing itself, but it is what refuses early and with a useful
24
+ // sentence when there is no project, region or credentials.
25
+ this.client(flags);
26
+ // TODO(10b-API-TEST): switch to `client.api.files.testFilesIntegration`
27
+ // once @norbix.ai/ts releases it (sdk-ts PR #44) — see the lib file.
28
+ const res = await callTestFilesIntegration(ctx, args.id);
29
+ const output = summarise(args.id, res);
30
+ if (this.jsonEnabled()) {
31
+ // oclif prints the returned value; the exit code still tells a script
32
+ // whether the integration works.
33
+ if (!output.ok)
34
+ process.exitCode = 2;
35
+ return output;
36
+ }
37
+ for (const line of render(output.steps))
38
+ this.log(line);
39
+ if (!output.ok) {
40
+ this.error(failureMessage(output));
41
+ }
42
+ this.log(`\nAll ${output.steps.length} steps passed.`);
43
+ return output;
44
+ }
45
+ }
46
+ function summarise(id, res) {
47
+ const steps = (res.items ?? []).map((item) => ({
48
+ operation: item.operation,
49
+ result: normaliseResult(item.result),
50
+ errors: item.errors ?? [],
51
+ }));
52
+ const errors = (res.responseStatus?.errors ?? [])
53
+ .map((e) => e.message ?? e.errorCode ?? '')
54
+ .filter(Boolean);
55
+ const gatewayOk = res.responseStatus?.isSuccess !== false;
56
+ if (!gatewayOk && errors.length === 0) {
57
+ errors.push('the gateway answered isSuccess: false without an error message');
58
+ }
59
+ const ok = gatewayOk && steps.length > 0 && steps.every((s) => s.result === 'OK');
60
+ return { filesIntegrationId: id, ok, steps, errors };
61
+ }
62
+ function render(steps) {
63
+ const width = Math.max(0, ...steps.map((s) => s.operation.length));
64
+ const lines = [];
65
+ for (const step of steps) {
66
+ lines.push(`${step.operation.padEnd(width)} ${step.result}`);
67
+ for (const error of step.errors)
68
+ lines.push(`${' '.repeat(width)} - ${error}`);
69
+ }
70
+ return lines;
71
+ }
72
+ function failureMessage(output) {
73
+ if (output.errors.length > 0) {
74
+ return `The files integration test was refused: ${output.errors.join('; ')}`;
75
+ }
76
+ if (output.steps.length === 0) {
77
+ return 'The gateway returned no test steps, so nothing was proven.';
78
+ }
79
+ const notOk = output.steps.filter((s) => s.result !== 'OK');
80
+ const failed = notOk.filter((s) => s.result !== 'NOT_TESTED');
81
+ const skipped = notOk.length - failed.length;
82
+ const named = (failed.length > 0 ? failed : notOk).map((s) => `${s.operation} ${s.result}`).join(', ');
83
+ const tail = failed.length > 0 && skipped > 0 ? ` (${skipped} later step${skipped === 1 ? '' : 's'} not tested)` : '';
84
+ return `The files integration test failed: ${named}${tail}.`;
85
+ }
@@ -0,0 +1,57 @@
1
+ import { NorbixAuthError, NorbixError, NorbixValidationError } from '@norbix.ai/ts';
2
+ /** The API version the SDK talks by default. Kept in one place. */
3
+ const API_VERSION = 'v2';
4
+ export async function callTestFilesIntegration(ctx, filesIntegrationId) {
5
+ const token = ctx.bearerToken ?? ctx.apiKey;
6
+ const headers = new Headers({ Accept: 'application/json' });
7
+ if (token)
8
+ headers.set('Authorization', `Bearer ${token}`);
9
+ if (ctx.projectId) {
10
+ headers.set('norbix-project-id', ctx.projectId);
11
+ headers.set('X-CM-ProjectId', ctx.projectId);
12
+ }
13
+ if (ctx.accountId) {
14
+ headers.set('norbix-account-id', ctx.accountId);
15
+ headers.set('X-CM-AccountId', ctx.accountId);
16
+ }
17
+ if (ctx.env && ctx.env !== 'PROD')
18
+ headers.set('norbix-env', ctx.env);
19
+ if (ctx.region)
20
+ headers.set('nb-region', ctx.region);
21
+ const base = ctx.apiUrl.endsWith('/') ? ctx.apiUrl.slice(0, -1) : ctx.apiUrl;
22
+ const url = `${base}/${API_VERSION}/files/${encodeURIComponent(filesIntegrationId)}/test`;
23
+ const response = await fetch(url, { headers, method: 'POST' });
24
+ const text = await response.text();
25
+ let raw;
26
+ if (text) {
27
+ try {
28
+ raw = JSON.parse(text);
29
+ }
30
+ catch {
31
+ raw = text;
32
+ }
33
+ }
34
+ if (!response.ok)
35
+ throw errorFrom(response.status, raw, url);
36
+ return (raw && typeof raw === 'object' ? raw : {});
37
+ }
38
+ /** Same mapping as the SDK transport: 401/403 → auth, 400 → validation, else NorbixError. */
39
+ function errorFrom(status, raw, url) {
40
+ const body = raw && typeof raw === 'object' ? raw : undefined;
41
+ const payload = body?.responseStatus ?? body;
42
+ const message = payload?.message ??
43
+ body?.responseStatus?.errors?.find((e) => e.message)?.message ??
44
+ `Request failed with status ${status}`;
45
+ const opts = { code: payload?.errorCode, message, raw, status, url };
46
+ if (status === 401 || status === 403)
47
+ return new NorbixAuthError(opts);
48
+ if (status === 400)
49
+ return new NorbixValidationError(opts);
50
+ return new NorbixError(opts);
51
+ }
52
+ /** `Failed` / `NotTested` / `NOT_TESTED` → `FAILED` / `NOT_TESTED`. */
53
+ export function normaliseResult(result) {
54
+ if (!result)
55
+ return 'UNKNOWN';
56
+ return result.replaceAll(/([a-z])([A-Z])/g, '$1_$2').replaceAll(/[\s-]+/g, '_').toUpperCase();
57
+ }
@@ -4607,18 +4607,18 @@
4607
4607
  "unarchive.js"
4608
4608
  ]
4609
4609
  },
4610
- "env:delete": {
4610
+ "files:delete": {
4611
4611
  "aliases": [],
4612
4612
  "args": {
4613
- "name": {
4614
- "description": "Environment name (PROD cannot be deleted)",
4615
- "name": "name",
4613
+ "remote": {
4614
+ "description": "Remote file path",
4615
+ "name": "remote",
4616
4616
  "required": true
4617
4617
  }
4618
4618
  },
4619
- "description": "Delete a non-PROD environment (cascades its integrations)",
4619
+ "description": "Delete one file",
4620
4620
  "examples": [
4621
- "<%= config.bin %> env delete STAGING --yes"
4621
+ "<%= config.bin %> files delete invoices/2026/invoice.pdf --yes"
4622
4622
  ],
4623
4623
  "flags": {
4624
4624
  "json": {
@@ -4682,6 +4682,15 @@
4682
4682
  "multiple": false,
4683
4683
  "type": "option"
4684
4684
  },
4685
+ "integration": {
4686
+ "char": "i",
4687
+ "description": "Files integration ID",
4688
+ "env": "NORBIX_FILES_INTEGRATION_ID",
4689
+ "name": "integration",
4690
+ "hasDynamicHelp": false,
4691
+ "multiple": false,
4692
+ "type": "option"
4693
+ },
4685
4694
  "yes": {
4686
4695
  "char": "y",
4687
4696
  "description": "Skip the confirmation prompt",
@@ -4692,7 +4701,7 @@
4692
4701
  },
4693
4702
  "hasDynamicHelp": false,
4694
4703
  "hiddenAliases": [],
4695
- "id": "env:delete",
4704
+ "id": "files:delete",
4696
4705
  "pluginAlias": "@norbix.ai/cli",
4697
4706
  "pluginName": "@norbix.ai/cli",
4698
4707
  "pluginType": "core",
@@ -4702,16 +4711,28 @@
4702
4711
  "relativePath": [
4703
4712
  "dist",
4704
4713
  "commands",
4705
- "env",
4714
+ "files",
4706
4715
  "delete.js"
4707
4716
  ]
4708
4717
  },
4709
- "env:list": {
4718
+ "files:download": {
4710
4719
  "aliases": [],
4711
- "args": {},
4712
- "description": "List the project's environments (PROD is always first)",
4720
+ "args": {
4721
+ "remote": {
4722
+ "description": "Remote file path",
4723
+ "name": "remote",
4724
+ "required": true
4725
+ },
4726
+ "file": {
4727
+ "description": "Local target file (default: remote file name in current folder)",
4728
+ "name": "file",
4729
+ "required": false
4730
+ }
4731
+ },
4732
+ "description": "Download a file to disk (via a signed URL)",
4713
4733
  "examples": [
4714
- "<%= config.bin %> env list"
4734
+ "<%= config.bin %> files download invoices/2026/invoice.pdf",
4735
+ "<%= config.bin %> files download invoices/2026/invoice.pdf ./local-copy.pdf"
4715
4736
  ],
4716
4737
  "flags": {
4717
4738
  "json": {
@@ -4774,11 +4795,20 @@
4774
4795
  "hasDynamicHelp": false,
4775
4796
  "multiple": false,
4776
4797
  "type": "option"
4798
+ },
4799
+ "integration": {
4800
+ "char": "i",
4801
+ "description": "Files integration ID",
4802
+ "env": "NORBIX_FILES_INTEGRATION_ID",
4803
+ "name": "integration",
4804
+ "hasDynamicHelp": false,
4805
+ "multiple": false,
4806
+ "type": "option"
4777
4807
  }
4778
4808
  },
4779
4809
  "hasDynamicHelp": false,
4780
4810
  "hiddenAliases": [],
4781
- "id": "env:list",
4811
+ "id": "files:download",
4782
4812
  "pluginAlias": "@norbix.ai/cli",
4783
4813
  "pluginName": "@norbix.ai/cli",
4784
4814
  "pluginType": "core",
@@ -4788,24 +4818,22 @@
4788
4818
  "relativePath": [
4789
4819
  "dist",
4790
4820
  "commands",
4791
- "env",
4792
- "list.js"
4821
+ "files",
4822
+ "download.js"
4793
4823
  ]
4794
4824
  },
4795
- "env:use": {
4825
+ "files:info": {
4796
4826
  "aliases": [],
4797
4827
  "args": {
4798
- "name": {
4799
- "description": "Environment name (e.g. PROD, TEST)",
4800
- "name": "name",
4828
+ "remote": {
4829
+ "description": "Remote file path",
4830
+ "name": "remote",
4801
4831
  "required": true
4802
4832
  }
4803
4833
  },
4804
- "description": "Set the default environment for future commands.\n\nWritten to the active session when you are logged in; with --profile it is\nwritten into that profile in ~/.norbix/config; otherwise to the [default]\nprofile. Override per command with --env.",
4834
+ "description": "Show metadata for one file",
4805
4835
  "examples": [
4806
- "<%= config.bin %> env use TEST",
4807
- "<%= config.bin %> env use PROD",
4808
- "<%= config.bin %> env use STAGING --profile fitskin-prod"
4836
+ "<%= config.bin %> files info invoices/2026/invoice.pdf"
4809
4837
  ],
4810
4838
  "flags": {
4811
4839
  "json": {
@@ -4868,11 +4896,20 @@
4868
4896
  "hasDynamicHelp": false,
4869
4897
  "multiple": false,
4870
4898
  "type": "option"
4899
+ },
4900
+ "integration": {
4901
+ "char": "i",
4902
+ "description": "Files integration ID",
4903
+ "env": "NORBIX_FILES_INTEGRATION_ID",
4904
+ "name": "integration",
4905
+ "hasDynamicHelp": false,
4906
+ "multiple": false,
4907
+ "type": "option"
4871
4908
  }
4872
4909
  },
4873
4910
  "hasDynamicHelp": false,
4874
4911
  "hiddenAliases": [],
4875
- "id": "env:use",
4912
+ "id": "files:info",
4876
4913
  "pluginAlias": "@norbix.ai/cli",
4877
4914
  "pluginName": "@norbix.ai/cli",
4878
4915
  "pluginType": "core",
@@ -4882,22 +4919,23 @@
4882
4919
  "relativePath": [
4883
4920
  "dist",
4884
4921
  "commands",
4885
- "env",
4886
- "use.js"
4922
+ "files",
4923
+ "info.js"
4887
4924
  ]
4888
4925
  },
4889
- "files:delete": {
4926
+ "files:list": {
4890
4927
  "aliases": [],
4891
4928
  "args": {
4892
- "remote": {
4893
- "description": "Remote file path",
4894
- "name": "remote",
4895
- "required": true
4929
+ "path": {
4930
+ "description": "Folder path (default: root)",
4931
+ "name": "path",
4932
+ "required": false
4896
4933
  }
4897
4934
  },
4898
- "description": "Delete one file",
4935
+ "description": "List files in a folder of a files integration",
4899
4936
  "examples": [
4900
- "<%= config.bin %> files delete invoices/2026/invoice.pdf --yes"
4937
+ "<%= config.bin %> files list --integration 66b2...",
4938
+ "<%= config.bin %> files list invoices/2026"
4901
4939
  ],
4902
4940
  "flags": {
4903
4941
  "json": {
@@ -4969,18 +5007,11 @@
4969
5007
  "hasDynamicHelp": false,
4970
5008
  "multiple": false,
4971
5009
  "type": "option"
4972
- },
4973
- "yes": {
4974
- "char": "y",
4975
- "description": "Skip the confirmation prompt",
4976
- "name": "yes",
4977
- "allowNo": false,
4978
- "type": "boolean"
4979
5010
  }
4980
5011
  },
4981
5012
  "hasDynamicHelp": false,
4982
5013
  "hiddenAliases": [],
4983
- "id": "files:delete",
5014
+ "id": "files:list",
4984
5015
  "pluginAlias": "@norbix.ai/cli",
4985
5016
  "pluginName": "@norbix.ai/cli",
4986
5017
  "pluginType": "core",
@@ -4991,27 +5022,22 @@
4991
5022
  "dist",
4992
5023
  "commands",
4993
5024
  "files",
4994
- "delete.js"
5025
+ "list.js"
4995
5026
  ]
4996
5027
  },
4997
- "files:download": {
5028
+ "files:publish": {
4998
5029
  "aliases": [],
4999
5030
  "args": {
5000
5031
  "remote": {
5001
- "description": "Remote file path",
5032
+ "description": "Remote file path, or folder prefix with --folder",
5002
5033
  "name": "remote",
5003
5034
  "required": true
5004
- },
5005
- "file": {
5006
- "description": "Local target file (default: remote file name in current folder)",
5007
- "name": "file",
5008
- "required": false
5009
5035
  }
5010
5036
  },
5011
- "description": "Download a file to disk (via a signed URL)",
5037
+ "description": "Make a file (or a whole folder) readable by anyone holding its link — no sign-in needed",
5012
5038
  "examples": [
5013
- "<%= config.bin %> files download invoices/2026/invoice.pdf",
5014
- "<%= config.bin %> files download invoices/2026/invoice.pdf ./local-copy.pdf"
5039
+ "<%= config.bin %> files publish invoices/2026/invoice.pdf",
5040
+ "<%= config.bin %> files publish invoices --folder"
5015
5041
  ],
5016
5042
  "flags": {
5017
5043
  "json": {
@@ -5075,6 +5101,12 @@
5075
5101
  "multiple": false,
5076
5102
  "type": "option"
5077
5103
  },
5104
+ "folder": {
5105
+ "description": "Publish the whole folder prefix instead of one file",
5106
+ "name": "folder",
5107
+ "allowNo": false,
5108
+ "type": "boolean"
5109
+ },
5078
5110
  "integration": {
5079
5111
  "char": "i",
5080
5112
  "description": "Files integration ID",
@@ -5087,7 +5119,7 @@
5087
5119
  },
5088
5120
  "hasDynamicHelp": false,
5089
5121
  "hiddenAliases": [],
5090
- "id": "files:download",
5122
+ "id": "files:publish",
5091
5123
  "pluginAlias": "@norbix.ai/cli",
5092
5124
  "pluginName": "@norbix.ai/cli",
5093
5125
  "pluginType": "core",
@@ -5098,10 +5130,10 @@
5098
5130
  "dist",
5099
5131
  "commands",
5100
5132
  "files",
5101
- "download.js"
5133
+ "publish.js"
5102
5134
  ]
5103
5135
  },
5104
- "files:info": {
5136
+ "files:sign": {
5105
5137
  "aliases": [],
5106
5138
  "args": {
5107
5139
  "remote": {
@@ -5110,9 +5142,9 @@
5110
5142
  "required": true
5111
5143
  }
5112
5144
  },
5113
- "description": "Show metadata for one file",
5145
+ "description": "Get a temporary signed download URL for a file",
5114
5146
  "examples": [
5115
- "<%= config.bin %> files info invoices/2026/invoice.pdf"
5147
+ "<%= config.bin %> files sign invoices/2026/invoice.pdf --expires 3600"
5116
5148
  ],
5117
5149
  "flags": {
5118
5150
  "json": {
@@ -5184,11 +5216,18 @@
5184
5216
  "hasDynamicHelp": false,
5185
5217
  "multiple": false,
5186
5218
  "type": "option"
5219
+ },
5220
+ "expires": {
5221
+ "description": "URL lifetime in seconds",
5222
+ "name": "expires",
5223
+ "hasDynamicHelp": false,
5224
+ "multiple": false,
5225
+ "type": "option"
5187
5226
  }
5188
5227
  },
5189
5228
  "hasDynamicHelp": false,
5190
5229
  "hiddenAliases": [],
5191
- "id": "files:info",
5230
+ "id": "files:sign",
5192
5231
  "pluginAlias": "@norbix.ai/cli",
5193
5232
  "pluginName": "@norbix.ai/cli",
5194
5233
  "pluginType": "core",
@@ -5199,22 +5238,22 @@
5199
5238
  "dist",
5200
5239
  "commands",
5201
5240
  "files",
5202
- "info.js"
5241
+ "sign.js"
5203
5242
  ]
5204
5243
  },
5205
- "files:list": {
5244
+ "files:unpublish": {
5206
5245
  "aliases": [],
5207
5246
  "args": {
5208
- "path": {
5209
- "description": "Folder path (default: root)",
5210
- "name": "path",
5211
- "required": false
5247
+ "remote": {
5248
+ "description": "Remote file path, or folder prefix with --folder",
5249
+ "name": "remote",
5250
+ "required": true
5212
5251
  }
5213
5252
  },
5214
- "description": "List files in a folder of a files integration",
5253
+ "description": "Take away a file's (or a folder's) public link",
5215
5254
  "examples": [
5216
- "<%= config.bin %> files list --integration 66b2...",
5217
- "<%= config.bin %> files list invoices/2026"
5255
+ "<%= config.bin %> files unpublish invoices/2026/invoice.pdf",
5256
+ "<%= config.bin %> files unpublish invoices --folder"
5218
5257
  ],
5219
5258
  "flags": {
5220
5259
  "json": {
@@ -5278,6 +5317,12 @@
5278
5317
  "multiple": false,
5279
5318
  "type": "option"
5280
5319
  },
5320
+ "folder": {
5321
+ "description": "Unpublish the whole folder prefix, and every link inside it",
5322
+ "name": "folder",
5323
+ "allowNo": false,
5324
+ "type": "boolean"
5325
+ },
5281
5326
  "integration": {
5282
5327
  "char": "i",
5283
5328
  "description": "Files integration ID",
@@ -5290,7 +5335,7 @@
5290
5335
  },
5291
5336
  "hasDynamicHelp": false,
5292
5337
  "hiddenAliases": [],
5293
- "id": "files:list",
5338
+ "id": "files:unpublish",
5294
5339
  "pluginAlias": "@norbix.ai/cli",
5295
5340
  "pluginName": "@norbix.ai/cli",
5296
5341
  "pluginType": "core",
@@ -5301,22 +5346,27 @@
5301
5346
  "dist",
5302
5347
  "commands",
5303
5348
  "files",
5304
- "list.js"
5349
+ "unpublish.js"
5305
5350
  ]
5306
5351
  },
5307
- "files:publish": {
5352
+ "files:upload": {
5308
5353
  "aliases": [],
5309
5354
  "args": {
5355
+ "file": {
5356
+ "description": "Local file to upload",
5357
+ "name": "file",
5358
+ "required": true
5359
+ },
5310
5360
  "remote": {
5311
- "description": "Remote file path, or folder prefix with --folder",
5361
+ "description": "Remote path (default: file name in the root folder)",
5312
5362
  "name": "remote",
5313
- "required": true
5363
+ "required": false
5314
5364
  }
5315
5365
  },
5316
- "description": "Make a file (or a whole folder) readable by anyone holding its link — no sign-in needed",
5366
+ "description": "Upload a local file (three steps: get signed URL, PUT bytes, commit).",
5317
5367
  "examples": [
5318
- "<%= config.bin %> files publish invoices/2026/invoice.pdf",
5319
- "<%= config.bin %> files publish invoices --folder"
5368
+ "<%= config.bin %> files upload ./invoice.pdf",
5369
+ "<%= config.bin %> files upload ./invoice.pdf invoices/2026/invoice.pdf"
5320
5370
  ],
5321
5371
  "flags": {
5322
5372
  "json": {
@@ -5380,12 +5430,6 @@
5380
5430
  "multiple": false,
5381
5431
  "type": "option"
5382
5432
  },
5383
- "folder": {
5384
- "description": "Publish the whole folder prefix instead of one file",
5385
- "name": "folder",
5386
- "allowNo": false,
5387
- "type": "boolean"
5388
- },
5389
5433
  "integration": {
5390
5434
  "char": "i",
5391
5435
  "description": "Files integration ID",
@@ -5394,11 +5438,18 @@
5394
5438
  "hasDynamicHelp": false,
5395
5439
  "multiple": false,
5396
5440
  "type": "option"
5441
+ },
5442
+ "content-type": {
5443
+ "description": "MIME type (default: guessed from extension)",
5444
+ "name": "content-type",
5445
+ "hasDynamicHelp": false,
5446
+ "multiple": false,
5447
+ "type": "option"
5397
5448
  }
5398
5449
  },
5399
5450
  "hasDynamicHelp": false,
5400
5451
  "hiddenAliases": [],
5401
- "id": "files:publish",
5452
+ "id": "files:upload",
5402
5453
  "pluginAlias": "@norbix.ai/cli",
5403
5454
  "pluginName": "@norbix.ai/cli",
5404
5455
  "pluginType": "core",
@@ -5409,21 +5460,21 @@
5409
5460
  "dist",
5410
5461
  "commands",
5411
5462
  "files",
5412
- "publish.js"
5463
+ "upload.js"
5413
5464
  ]
5414
5465
  },
5415
- "files:sign": {
5466
+ "env:delete": {
5416
5467
  "aliases": [],
5417
5468
  "args": {
5418
- "remote": {
5419
- "description": "Remote file path",
5420
- "name": "remote",
5469
+ "name": {
5470
+ "description": "Environment name (PROD cannot be deleted)",
5471
+ "name": "name",
5421
5472
  "required": true
5422
5473
  }
5423
5474
  },
5424
- "description": "Get a temporary signed download URL for a file",
5475
+ "description": "Delete a non-PROD environment (cascades its integrations)",
5425
5476
  "examples": [
5426
- "<%= config.bin %> files sign invoices/2026/invoice.pdf --expires 3600"
5477
+ "<%= config.bin %> env delete STAGING --yes"
5427
5478
  ],
5428
5479
  "flags": {
5429
5480
  "json": {
@@ -5487,26 +5538,17 @@
5487
5538
  "multiple": false,
5488
5539
  "type": "option"
5489
5540
  },
5490
- "integration": {
5491
- "char": "i",
5492
- "description": "Files integration ID",
5493
- "env": "NORBIX_FILES_INTEGRATION_ID",
5494
- "name": "integration",
5495
- "hasDynamicHelp": false,
5496
- "multiple": false,
5497
- "type": "option"
5498
- },
5499
- "expires": {
5500
- "description": "URL lifetime in seconds",
5501
- "name": "expires",
5502
- "hasDynamicHelp": false,
5503
- "multiple": false,
5504
- "type": "option"
5541
+ "yes": {
5542
+ "char": "y",
5543
+ "description": "Skip the confirmation prompt",
5544
+ "name": "yes",
5545
+ "allowNo": false,
5546
+ "type": "boolean"
5505
5547
  }
5506
5548
  },
5507
5549
  "hasDynamicHelp": false,
5508
5550
  "hiddenAliases": [],
5509
- "id": "files:sign",
5551
+ "id": "env:delete",
5510
5552
  "pluginAlias": "@norbix.ai/cli",
5511
5553
  "pluginName": "@norbix.ai/cli",
5512
5554
  "pluginType": "core",
@@ -5516,23 +5558,16 @@
5516
5558
  "relativePath": [
5517
5559
  "dist",
5518
5560
  "commands",
5519
- "files",
5520
- "sign.js"
5561
+ "env",
5562
+ "delete.js"
5521
5563
  ]
5522
5564
  },
5523
- "files:unpublish": {
5565
+ "env:list": {
5524
5566
  "aliases": [],
5525
- "args": {
5526
- "remote": {
5527
- "description": "Remote file path, or folder prefix with --folder",
5528
- "name": "remote",
5529
- "required": true
5530
- }
5531
- },
5532
- "description": "Take away a file's (or a folder's) public link",
5567
+ "args": {},
5568
+ "description": "List the project's environments (PROD is always first)",
5533
5569
  "examples": [
5534
- "<%= config.bin %> files unpublish invoices/2026/invoice.pdf",
5535
- "<%= config.bin %> files unpublish invoices --folder"
5570
+ "<%= config.bin %> env list"
5536
5571
  ],
5537
5572
  "flags": {
5538
5573
  "json": {
@@ -5582,31 +5617,16 @@
5582
5617
  "description": "Account ID (needed for account-scoped hub endpoints)",
5583
5618
  "env": "NORBIX_ACCOUNT_ID",
5584
5619
  "helpGroup": "GLOBAL",
5585
- "name": "account",
5586
- "hasDynamicHelp": false,
5587
- "multiple": false,
5588
- "type": "option"
5589
- },
5590
- "profile": {
5591
- "description": "Use this profile from ~/.norbix/config (ignores the login session)",
5592
- "env": "NORBIX_PROFILE",
5593
- "helpGroup": "GLOBAL",
5594
- "name": "profile",
5595
- "hasDynamicHelp": false,
5596
- "multiple": false,
5597
- "type": "option"
5598
- },
5599
- "folder": {
5600
- "description": "Unpublish the whole folder prefix, and every link inside it",
5601
- "name": "folder",
5602
- "allowNo": false,
5603
- "type": "boolean"
5620
+ "name": "account",
5621
+ "hasDynamicHelp": false,
5622
+ "multiple": false,
5623
+ "type": "option"
5604
5624
  },
5605
- "integration": {
5606
- "char": "i",
5607
- "description": "Files integration ID",
5608
- "env": "NORBIX_FILES_INTEGRATION_ID",
5609
- "name": "integration",
5625
+ "profile": {
5626
+ "description": "Use this profile from ~/.norbix/config (ignores the login session)",
5627
+ "env": "NORBIX_PROFILE",
5628
+ "helpGroup": "GLOBAL",
5629
+ "name": "profile",
5610
5630
  "hasDynamicHelp": false,
5611
5631
  "multiple": false,
5612
5632
  "type": "option"
@@ -5614,7 +5634,7 @@
5614
5634
  },
5615
5635
  "hasDynamicHelp": false,
5616
5636
  "hiddenAliases": [],
5617
- "id": "files:unpublish",
5637
+ "id": "env:list",
5618
5638
  "pluginAlias": "@norbix.ai/cli",
5619
5639
  "pluginName": "@norbix.ai/cli",
5620
5640
  "pluginType": "core",
@@ -5624,28 +5644,24 @@
5624
5644
  "relativePath": [
5625
5645
  "dist",
5626
5646
  "commands",
5627
- "files",
5628
- "unpublish.js"
5647
+ "env",
5648
+ "list.js"
5629
5649
  ]
5630
5650
  },
5631
- "files:upload": {
5651
+ "env:use": {
5632
5652
  "aliases": [],
5633
5653
  "args": {
5634
- "file": {
5635
- "description": "Local file to upload",
5636
- "name": "file",
5654
+ "name": {
5655
+ "description": "Environment name (e.g. PROD, TEST)",
5656
+ "name": "name",
5637
5657
  "required": true
5638
- },
5639
- "remote": {
5640
- "description": "Remote path (default: file name in the root folder)",
5641
- "name": "remote",
5642
- "required": false
5643
5658
  }
5644
5659
  },
5645
- "description": "Upload a local file (three steps: get signed URL, PUT bytes, commit).",
5660
+ "description": "Set the default environment for future commands.\n\nWritten to the active session when you are logged in; with --profile it is\nwritten into that profile in ~/.norbix/config; otherwise to the [default]\nprofile. Override per command with --env.",
5646
5661
  "examples": [
5647
- "<%= config.bin %> files upload ./invoice.pdf",
5648
- "<%= config.bin %> files upload ./invoice.pdf invoices/2026/invoice.pdf"
5662
+ "<%= config.bin %> env use TEST",
5663
+ "<%= config.bin %> env use PROD",
5664
+ "<%= config.bin %> env use STAGING --profile fitskin-prod"
5649
5665
  ],
5650
5666
  "flags": {
5651
5667
  "json": {
@@ -5708,27 +5724,11 @@
5708
5724
  "hasDynamicHelp": false,
5709
5725
  "multiple": false,
5710
5726
  "type": "option"
5711
- },
5712
- "integration": {
5713
- "char": "i",
5714
- "description": "Files integration ID",
5715
- "env": "NORBIX_FILES_INTEGRATION_ID",
5716
- "name": "integration",
5717
- "hasDynamicHelp": false,
5718
- "multiple": false,
5719
- "type": "option"
5720
- },
5721
- "content-type": {
5722
- "description": "MIME type (default: guessed from extension)",
5723
- "name": "content-type",
5724
- "hasDynamicHelp": false,
5725
- "multiple": false,
5726
- "type": "option"
5727
5727
  }
5728
5728
  },
5729
5729
  "hasDynamicHelp": false,
5730
5730
  "hiddenAliases": [],
5731
- "id": "files:upload",
5731
+ "id": "env:use",
5732
5732
  "pluginAlias": "@norbix.ai/cli",
5733
5733
  "pluginName": "@norbix.ai/cli",
5734
5734
  "pluginType": "core",
@@ -5738,8 +5738,8 @@
5738
5738
  "relativePath": [
5739
5739
  "dist",
5740
5740
  "commands",
5741
- "files",
5742
- "upload.js"
5741
+ "env",
5742
+ "use.js"
5743
5743
  ]
5744
5744
  },
5745
5745
  "logs:list": {
@@ -10306,12 +10306,19 @@
10306
10306
  "show.js"
10307
10307
  ]
10308
10308
  },
10309
- "push:device:register": {
10309
+ "files:integrations:test": {
10310
10310
  "aliases": [],
10311
- "args": {},
10312
- "description": "Register a device so push campaigns can reach it\n\nA device belongs to a user, so --user is required. --os is the operating\nsystem the token came from; the backend uses it to pick a provider.",
10311
+ "args": {
10312
+ "id": {
10313
+ "description": "Files integration ID",
10314
+ "name": "id",
10315
+ "required": true
10316
+ }
10317
+ },
10318
+ "description": "Check that a files integration really works\n\nRuns a live probe against the storage: uploads a small file, reads it, lists\nits folder and deletes it again. Prints one line per step — OK, FAILED or\nNOT_TESTED (skipped because an earlier step failed) — with the errors of a\nfailed step. Exits non-zero unless every step is OK.\n\nNeeds the files:create permission, because the probe writes to the storage.",
10313
10319
  "examples": [
10314
- "<%= config.bin %> push device register --user usr_123 --token dGVzdA== --os iOS"
10320
+ "<%= config.bin %> files integrations test 55555555-5555-5555-5555-555555555555",
10321
+ "<%= config.bin %> files integrations test 55555555-5555-5555-5555-555555555555 --json"
10315
10322
  ],
10316
10323
  "flags": {
10317
10324
  "json": {
@@ -10374,56 +10381,11 @@
10374
10381
  "hasDynamicHelp": false,
10375
10382
  "multiple": false,
10376
10383
  "type": "option"
10377
- },
10378
- "user": {
10379
- "description": "User ID the device belongs to",
10380
- "name": "user",
10381
- "required": true,
10382
- "hasDynamicHelp": false,
10383
- "multiple": false,
10384
- "type": "option"
10385
- },
10386
- "token": {
10387
- "description": "Device token from the platform",
10388
- "name": "token",
10389
- "required": true,
10390
- "hasDynamicHelp": false,
10391
- "multiple": false,
10392
- "type": "option"
10393
- },
10394
- "os": {
10395
- "description": "Operating system, e.g. iOS or Android",
10396
- "name": "os",
10397
- "required": true,
10398
- "hasDynamicHelp": false,
10399
- "multiple": false,
10400
- "type": "option"
10401
- },
10402
- "device-id": {
10403
- "description": "Stable device identifier",
10404
- "name": "device-id",
10405
- "hasDynamicHelp": false,
10406
- "multiple": false,
10407
- "type": "option"
10408
- },
10409
- "model": {
10410
- "description": "Device model, e.g. iPhone 15",
10411
- "name": "model",
10412
- "hasDynamicHelp": false,
10413
- "multiple": false,
10414
- "type": "option"
10415
- },
10416
- "name": {
10417
- "description": "The device's own name",
10418
- "name": "name",
10419
- "hasDynamicHelp": false,
10420
- "multiple": false,
10421
- "type": "option"
10422
10384
  }
10423
10385
  },
10424
10386
  "hasDynamicHelp": false,
10425
10387
  "hiddenAliases": [],
10426
- "id": "push:device:register",
10388
+ "id": "files:integrations:test",
10427
10389
  "pluginAlias": "@norbix.ai/cli",
10428
10390
  "pluginName": "@norbix.ai/cli",
10429
10391
  "pluginType": "core",
@@ -10433,9 +10395,9 @@
10433
10395
  "relativePath": [
10434
10396
  "dist",
10435
10397
  "commands",
10436
- "push",
10437
- "device",
10438
- "register.js"
10398
+ "files",
10399
+ "integrations",
10400
+ "test.js"
10439
10401
  ]
10440
10402
  },
10441
10403
  "push:integration:default": {
@@ -10932,7 +10894,139 @@
10932
10894
  "integration",
10933
10895
  "test.js"
10934
10896
  ]
10897
+ },
10898
+ "push:device:register": {
10899
+ "aliases": [],
10900
+ "args": {},
10901
+ "description": "Register a device so push campaigns can reach it\n\nA device belongs to a user, so --user is required. --os is the operating\nsystem the token came from; the backend uses it to pick a provider.",
10902
+ "examples": [
10903
+ "<%= config.bin %> push device register --user usr_123 --token dGVzdA== --os iOS"
10904
+ ],
10905
+ "flags": {
10906
+ "json": {
10907
+ "description": "Format output as json.",
10908
+ "helpGroup": "GLOBAL",
10909
+ "name": "json",
10910
+ "allowNo": false,
10911
+ "type": "boolean"
10912
+ },
10913
+ "project": {
10914
+ "description": "Project ID",
10915
+ "env": "NORBIX_PROJECT_ID",
10916
+ "helpGroup": "GLOBAL",
10917
+ "name": "project",
10918
+ "hasDynamicHelp": false,
10919
+ "multiple": false,
10920
+ "type": "option"
10921
+ },
10922
+ "env": {
10923
+ "description": "Project environment (e.g. PROD, TEST)",
10924
+ "env": "NORBIX_ENV",
10925
+ "helpGroup": "GLOBAL",
10926
+ "name": "env",
10927
+ "hasDynamicHelp": false,
10928
+ "multiple": false,
10929
+ "type": "option"
10930
+ },
10931
+ "region": {
10932
+ "description": "Region code (e.g. nb-eu-germany)",
10933
+ "env": "NORBIX_REGION",
10934
+ "helpGroup": "GLOBAL",
10935
+ "name": "region",
10936
+ "hasDynamicHelp": false,
10937
+ "multiple": false,
10938
+ "type": "option"
10939
+ },
10940
+ "api-key": {
10941
+ "description": "API key (overrides profile and session)",
10942
+ "env": "NORBIX_API_KEY",
10943
+ "helpGroup": "GLOBAL",
10944
+ "name": "api-key",
10945
+ "hasDynamicHelp": false,
10946
+ "multiple": false,
10947
+ "type": "option"
10948
+ },
10949
+ "account": {
10950
+ "description": "Account ID (needed for account-scoped hub endpoints)",
10951
+ "env": "NORBIX_ACCOUNT_ID",
10952
+ "helpGroup": "GLOBAL",
10953
+ "name": "account",
10954
+ "hasDynamicHelp": false,
10955
+ "multiple": false,
10956
+ "type": "option"
10957
+ },
10958
+ "profile": {
10959
+ "description": "Use this profile from ~/.norbix/config (ignores the login session)",
10960
+ "env": "NORBIX_PROFILE",
10961
+ "helpGroup": "GLOBAL",
10962
+ "name": "profile",
10963
+ "hasDynamicHelp": false,
10964
+ "multiple": false,
10965
+ "type": "option"
10966
+ },
10967
+ "user": {
10968
+ "description": "User ID the device belongs to",
10969
+ "name": "user",
10970
+ "required": true,
10971
+ "hasDynamicHelp": false,
10972
+ "multiple": false,
10973
+ "type": "option"
10974
+ },
10975
+ "token": {
10976
+ "description": "Device token from the platform",
10977
+ "name": "token",
10978
+ "required": true,
10979
+ "hasDynamicHelp": false,
10980
+ "multiple": false,
10981
+ "type": "option"
10982
+ },
10983
+ "os": {
10984
+ "description": "Operating system, e.g. iOS or Android",
10985
+ "name": "os",
10986
+ "required": true,
10987
+ "hasDynamicHelp": false,
10988
+ "multiple": false,
10989
+ "type": "option"
10990
+ },
10991
+ "device-id": {
10992
+ "description": "Stable device identifier",
10993
+ "name": "device-id",
10994
+ "hasDynamicHelp": false,
10995
+ "multiple": false,
10996
+ "type": "option"
10997
+ },
10998
+ "model": {
10999
+ "description": "Device model, e.g. iPhone 15",
11000
+ "name": "model",
11001
+ "hasDynamicHelp": false,
11002
+ "multiple": false,
11003
+ "type": "option"
11004
+ },
11005
+ "name": {
11006
+ "description": "The device's own name",
11007
+ "name": "name",
11008
+ "hasDynamicHelp": false,
11009
+ "multiple": false,
11010
+ "type": "option"
11011
+ }
11012
+ },
11013
+ "hasDynamicHelp": false,
11014
+ "hiddenAliases": [],
11015
+ "id": "push:device:register",
11016
+ "pluginAlias": "@norbix.ai/cli",
11017
+ "pluginName": "@norbix.ai/cli",
11018
+ "pluginType": "core",
11019
+ "strict": true,
11020
+ "enableJsonFlag": true,
11021
+ "isESM": true,
11022
+ "relativePath": [
11023
+ "dist",
11024
+ "commands",
11025
+ "push",
11026
+ "device",
11027
+ "register.js"
11028
+ ]
10935
11029
  }
10936
11030
  },
10937
- "version": "0.2.0"
11031
+ "version": "0.3.0"
10938
11032
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@norbix.ai/cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Norbix CLI — manage your Norbix projects from the terminal. Built on the official TypeScript SDK.",
5
5
  "author": "UAB Isidos",
6
6
  "license": "MIT",
@@ -92,7 +92,12 @@
92
92
  "description": "Work with database collections (find, insert, update, delete)"
93
93
  },
94
94
  "files": {
95
- "description": "Upload, download and manage files"
95
+ "description": "Upload, download and manage files",
96
+ "subtopics": {
97
+ "integrations": {
98
+ "description": "Check the storage behind a files integration"
99
+ }
100
+ }
96
101
  },
97
102
  "env": {
98
103
  "description": "Manage project environments (PROD, TEST, ...)"