@norbix.ai/cli 0.1.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/LICENSE +21 -0
- package/README.md +121 -0
- package/bin/run.js +4 -0
- package/dist/base.js +100 -0
- package/dist/commands/account/billing-portal.js +21 -0
- package/dist/commands/account/profile.js +12 -0
- package/dist/commands/account/projects.js +12 -0
- package/dist/commands/account/regions.js +12 -0
- package/dist/commands/account/status.js +12 -0
- package/dist/commands/account/team.js +12 -0
- package/dist/commands/account/usage.js +12 -0
- package/dist/commands/api.js +87 -0
- package/dist/commands/apikeys/list.js +12 -0
- package/dist/commands/apikeys/regenerate.js +25 -0
- package/dist/commands/config/get.js +20 -0
- package/dist/commands/config/list.js +18 -0
- package/dist/commands/config/set.js +25 -0
- package/dist/commands/config/unset.js +20 -0
- package/dist/commands/db/aggregate.js +42 -0
- package/dist/commands/db/aggregates.js +16 -0
- package/dist/commands/db/count.js +26 -0
- package/dist/commands/db/delete.js +45 -0
- package/dist/commands/db/distinct.js +25 -0
- package/dist/commands/db/find.js +33 -0
- package/dist/commands/db/get.js +20 -0
- package/dist/commands/db/insert-many.js +29 -0
- package/dist/commands/db/insert.js +26 -0
- package/dist/commands/db/replace.js +25 -0
- package/dist/commands/db/schema.js +16 -0
- package/dist/commands/db/schemas.js +20 -0
- package/dist/commands/db/taxonomies.js +20 -0
- package/dist/commands/db/taxonomy.js +16 -0
- package/dist/commands/db/update.js +44 -0
- package/dist/commands/email/archive.js +16 -0
- package/dist/commands/email/campaign.js +21 -0
- package/dist/commands/email/campaigns.js +20 -0
- package/dist/commands/email/clone.js +16 -0
- package/dist/commands/email/delete.js +25 -0
- package/dist/commands/email/stop.js +31 -0
- package/dist/commands/email/template.js +16 -0
- package/dist/commands/email/templates.js +18 -0
- package/dist/commands/email/unarchive.js +16 -0
- package/dist/commands/env/delete.js +28 -0
- package/dist/commands/env/list.js +12 -0
- package/dist/commands/env/use.js +17 -0
- package/dist/commands/files/delete.js +34 -0
- package/dist/commands/files/download.js +45 -0
- package/dist/commands/files/info.js +27 -0
- package/dist/commands/files/list.js +30 -0
- package/dist/commands/files/sign.js +29 -0
- package/dist/commands/files/upload.js +62 -0
- package/dist/commands/integrations.js +34 -0
- package/dist/commands/login.js +65 -0
- package/dist/commands/logout.js +27 -0
- package/dist/commands/logs/list.js +38 -0
- package/dist/commands/logs/settings.js +12 -0
- package/dist/commands/logs/trail.js +18 -0
- package/dist/commands/module.js +48 -0
- package/dist/commands/payments/disable.js +16 -0
- package/dist/commands/payments/enable.js +16 -0
- package/dist/commands/payments/integrations.js +12 -0
- package/dist/commands/payments/trigger.js +16 -0
- package/dist/commands/payments/triggers.js +12 -0
- package/dist/commands/push/archive.js +16 -0
- package/dist/commands/push/campaign.js +21 -0
- package/dist/commands/push/campaigns.js +20 -0
- package/dist/commands/push/clone.js +16 -0
- package/dist/commands/push/delete.js +25 -0
- package/dist/commands/push/stop.js +30 -0
- package/dist/commands/push/template.js +16 -0
- package/dist/commands/push/templates.js +18 -0
- package/dist/commands/push/unarchive.js +16 -0
- package/dist/commands/scheduler/delete.js +25 -0
- package/dist/commands/scheduler/disable.js +16 -0
- package/dist/commands/scheduler/enable.js +16 -0
- package/dist/commands/scheduler/get.js +16 -0
- package/dist/commands/scheduler/list.js +20 -0
- package/dist/commands/sms/archive.js +16 -0
- package/dist/commands/sms/campaign.js +21 -0
- package/dist/commands/sms/campaigns.js +20 -0
- package/dist/commands/sms/clone.js +16 -0
- package/dist/commands/sms/delete.js +25 -0
- package/dist/commands/sms/stop.js +30 -0
- package/dist/commands/sms/template.js +16 -0
- package/dist/commands/sms/templates.js +18 -0
- package/dist/commands/sms/unarchive.js +16 -0
- package/dist/commands/users/block.js +16 -0
- package/dist/commands/users/delete.js +25 -0
- package/dist/commands/users/get.js +16 -0
- package/dist/commands/users/invite.js +16 -0
- package/dist/commands/users/list.js +27 -0
- package/dist/commands/users/policies.js +12 -0
- package/dist/commands/users/roles.js +12 -0
- package/dist/commands/users/unblock.js +16 -0
- package/dist/commands/webhooks/disable.js +18 -0
- package/dist/commands/webhooks/enable.js +18 -0
- package/dist/commands/webhooks/remove.js +30 -0
- package/dist/commands/webhooks/secret.js +34 -0
- package/dist/commands/webhooks/show.js +12 -0
- package/dist/commands/whoami.js +43 -0
- package/dist/lib/files.js +35 -0
- package/dist/lib/json.js +21 -0
- package/dist/lib/store.js +39 -0
- package/oclif.manifest.json +8497 -0
- package/package.json +114 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 UAB Isidos
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Norbix CLI
|
|
2
|
+
|
|
3
|
+
Manage your Norbix projects from the terminal. Built on top of the official
|
|
4
|
+
[`@norbix.ai/ts`](https://www.npmjs.com/package/@norbix.ai/ts) SDK, so every
|
|
5
|
+
command talks to the same API the SDK does.
|
|
6
|
+
|
|
7
|
+
Works on **macOS**, **Linux** and **Windows** (Node.js 18+).
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install -g @norbix.ai/cli
|
|
13
|
+
|
|
14
|
+
# or run without installing:
|
|
15
|
+
npx @norbix.ai/cli --help
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick start
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
# 1. Log in (stores a token in your user config folder)
|
|
22
|
+
norbix login
|
|
23
|
+
# or with an API key:
|
|
24
|
+
norbix login --api-key nbk_... --project <projectId>
|
|
25
|
+
|
|
26
|
+
# 2. Check everything is wired up
|
|
27
|
+
norbix whoami
|
|
28
|
+
|
|
29
|
+
# 3. Use it
|
|
30
|
+
norbix db find orders --filter '{"status":"paid"}' --page-size 20
|
|
31
|
+
norbix logs list --level Error
|
|
32
|
+
norbix scheduler list
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Commands
|
|
36
|
+
|
|
37
|
+
| Command | What it does |
|
|
38
|
+
| --- | --- |
|
|
39
|
+
| `norbix login` / `logout` / `whoami` | Authenticate and inspect the current context |
|
|
40
|
+
| `norbix config list/get/set/unset` | Manage the local config file |
|
|
41
|
+
| `norbix db find/get/count/insert/update/delete/aggregate` | Work with database collections |
|
|
42
|
+
| `norbix files list/info/upload/download/sign/delete` | Upload, download and manage files |
|
|
43
|
+
| `norbix users list/get/invite/block/unblock/delete` | Manage project users (membership) |
|
|
44
|
+
| `norbix env list/use/delete` | Manage project environments |
|
|
45
|
+
| `norbix logs list/trail` | Read project logs, follow one correlation ID |
|
|
46
|
+
| `norbix scheduler list/get/enable/disable/delete` | Manage scheduler tasks |
|
|
47
|
+
| `norbix apikeys list/regenerate` | Show or regenerate project API keys |
|
|
48
|
+
| `norbix webhooks show/secret/enable/disable/remove` | Inspect the webhook integration |
|
|
49
|
+
| `norbix email templates/template/clone/archive/unarchive/delete/campaigns/campaign/stop` | Email templates and campaigns |
|
|
50
|
+
| `norbix push ...` / `norbix sms ...` | Push and SMS — same commands as email |
|
|
51
|
+
| `norbix account profile/status/usage/projects/team/regions/billing-portal` | Account-level info |
|
|
52
|
+
| `norbix payments integrations/triggers/trigger/enable/disable` | Payment integrations and triggers |
|
|
53
|
+
| `norbix integrations <module>` | List integrations of any module |
|
|
54
|
+
| `norbix module enable/disable <name>` | Turn a whole project module on or off |
|
|
55
|
+
| `norbix api <path>` | Call ANY endpoint directly (like `gh api`) — full API coverage |
|
|
56
|
+
| `norbix autocomplete` | Set up shell tab-completion (bash/zsh) |
|
|
57
|
+
|
|
58
|
+
Run `norbix <topic> --help` for flags and examples.
|
|
59
|
+
|
|
60
|
+
## Configuration
|
|
61
|
+
|
|
62
|
+
Every setting is resolved in this order (most specific wins):
|
|
63
|
+
|
|
64
|
+
1. **Flag** — `--project`, `--env`, `--region`, `--api-key`
|
|
65
|
+
2. **Environment variable** — `NORBIX_PROJECT_ID`, `NORBIX_ENV`, `NORBIX_REGION`,
|
|
66
|
+
`NORBIX_API_KEY`, `NORBIX_API_URL`, `NORBIX_HUB_URL`
|
|
67
|
+
3. **Config file** — written by `norbix login` and `norbix config set`
|
|
68
|
+
|
|
69
|
+
The config file lives in your user config folder (`~/.config/norbix/config.json`
|
|
70
|
+
on macOS/Linux, `%LOCALAPPDATA%\norbix\config.json` on Windows) and is created
|
|
71
|
+
with owner-only permissions (0600).
|
|
72
|
+
|
|
73
|
+
For CI/CD, skip `login` and use environment variables only:
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
export NORBIX_API_KEY=nbk_...
|
|
77
|
+
export NORBIX_PROJECT_ID=...
|
|
78
|
+
norbix db count orders
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## JSON output
|
|
82
|
+
|
|
83
|
+
Every command supports `--json` and prints clean JSON on stdout, so you can
|
|
84
|
+
pipe into `jq`:
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
norbix db find orders --json | jq '.list.items[] | ._id'
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Development
|
|
91
|
+
|
|
92
|
+
```sh
|
|
93
|
+
npm install
|
|
94
|
+
npm run build # compile TypeScript to dist/
|
|
95
|
+
./bin/run.js --help # run the local build
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Commands live in `src/commands/<topic>/<name>.ts` — one file per command
|
|
99
|
+
(oclif convention). Shared logic (auth resolution, config store, output) is in
|
|
100
|
+
`src/base.ts` and `src/lib/`.
|
|
101
|
+
|
|
102
|
+
## Files: pick your integration once
|
|
103
|
+
|
|
104
|
+
File commands need a files integration ID. Set it once:
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
norbix config set filesIntegrationId <id>
|
|
108
|
+
norbix files upload ./invoice.pdf invoices/2026/invoice.pdf
|
|
109
|
+
norbix files download invoices/2026/invoice.pdf
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Or pass `--integration <id>` (env var: `NORBIX_FILES_INTEGRATION_ID`).
|
|
113
|
+
|
|
114
|
+
## Roadmap
|
|
115
|
+
|
|
116
|
+
- Standalone binaries (no Node needed): Homebrew, curl installer, Scoop/winget
|
|
117
|
+
- Device-code login (no password typed into the terminal)
|
|
118
|
+
- OS keychain storage for tokens
|
|
119
|
+
- `norbix logs list --follow` (live tail via SSE)
|
|
120
|
+
|
|
121
|
+
See `RESEARCH.md` for the full background on these choices.
|
package/bin/run.js
ADDED
package/dist/base.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Norbix, NorbixError } from '@norbix.ai/ts';
|
|
2
|
+
import { Command, Flags } from '@oclif/core';
|
|
3
|
+
import { readStore } from './lib/store.js';
|
|
4
|
+
/**
|
|
5
|
+
* Base class for every Norbix CLI command.
|
|
6
|
+
*
|
|
7
|
+
* Resolution order for every setting (most specific wins):
|
|
8
|
+
* 1. command-line flag (--project, --env, --region, --api-key)
|
|
9
|
+
* 2. environment variable (NORBIX_PROJECT_ID, NORBIX_ENV, ...)
|
|
10
|
+
* 3. local config file (written by `norbix login` / `norbix config set`)
|
|
11
|
+
*
|
|
12
|
+
* Flags declare `env:` so oclif applies (1) and (2); (3) happens here.
|
|
13
|
+
*/
|
|
14
|
+
export class BaseCommand extends Command {
|
|
15
|
+
static enableJsonFlag = true;
|
|
16
|
+
static baseFlags = {
|
|
17
|
+
project: Flags.string({
|
|
18
|
+
description: 'Project ID',
|
|
19
|
+
env: 'NORBIX_PROJECT_ID',
|
|
20
|
+
helpGroup: 'GLOBAL',
|
|
21
|
+
}),
|
|
22
|
+
env: Flags.string({
|
|
23
|
+
description: 'Project environment (e.g. PROD, TEST)',
|
|
24
|
+
env: 'NORBIX_ENV',
|
|
25
|
+
helpGroup: 'GLOBAL',
|
|
26
|
+
}),
|
|
27
|
+
region: Flags.string({
|
|
28
|
+
description: 'Region code (e.g. nb-eu-germany)',
|
|
29
|
+
env: 'NORBIX_REGION',
|
|
30
|
+
helpGroup: 'GLOBAL',
|
|
31
|
+
}),
|
|
32
|
+
'api-key': Flags.string({
|
|
33
|
+
description: 'API key (overrides a stored login token)',
|
|
34
|
+
env: 'NORBIX_API_KEY',
|
|
35
|
+
helpGroup: 'GLOBAL',
|
|
36
|
+
}),
|
|
37
|
+
account: Flags.string({
|
|
38
|
+
description: 'Account ID (needed for account-scoped hub endpoints)',
|
|
39
|
+
env: 'NORBIX_ACCOUNT_ID',
|
|
40
|
+
helpGroup: 'GLOBAL',
|
|
41
|
+
}),
|
|
42
|
+
};
|
|
43
|
+
readStore() {
|
|
44
|
+
return readStore(this.config.configDir);
|
|
45
|
+
}
|
|
46
|
+
resolveContext(flags) {
|
|
47
|
+
const stored = this.readStore();
|
|
48
|
+
const apiKeyOverride = flags['api-key'];
|
|
49
|
+
return {
|
|
50
|
+
stored,
|
|
51
|
+
projectId: flags.project ?? stored.projectId,
|
|
52
|
+
accountId: flags.account ?? stored.accountId,
|
|
53
|
+
env: flags.env ?? stored.env,
|
|
54
|
+
region: flags.region ?? stored.region,
|
|
55
|
+
apiKey: apiKeyOverride ?? (stored.bearerToken ? undefined : stored.apiKey),
|
|
56
|
+
// An explicit --api-key / NORBIX_API_KEY wins over a stored login token.
|
|
57
|
+
bearerToken: apiKeyOverride ? undefined : stored.bearerToken,
|
|
58
|
+
apiUrl: stored.apiUrl ?? process.env.NORBIX_API_URL,
|
|
59
|
+
hubUrl: stored.hubUrl ?? process.env.NORBIX_HUB_URL,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/** Build an SDK client from flags + env + config file. Errors politely when auth is missing. */
|
|
63
|
+
client(flags, opts = {}) {
|
|
64
|
+
const ctx = this.resolveContext(flags);
|
|
65
|
+
if (!ctx.projectId) {
|
|
66
|
+
this.error('No project ID configured.\nRun `norbix login`, or `norbix config set projectId <id>`, or pass --project.');
|
|
67
|
+
}
|
|
68
|
+
if (opts.requireAuth !== false && !ctx.apiKey && !ctx.bearerToken) {
|
|
69
|
+
this.error('Not authenticated. Run `norbix login` or pass --api-key.');
|
|
70
|
+
}
|
|
71
|
+
return new Norbix({
|
|
72
|
+
projectId: ctx.projectId,
|
|
73
|
+
accountId: ctx.accountId,
|
|
74
|
+
apiKey: ctx.apiKey,
|
|
75
|
+
bearerToken: ctx.bearerToken,
|
|
76
|
+
env: ctx.env,
|
|
77
|
+
region: ctx.region,
|
|
78
|
+
baseUrl: { api: ctx.apiUrl, hub: ctx.hubUrl },
|
|
79
|
+
},
|
|
80
|
+
// The CLI already resolved env vars itself — don't let the SDK re-read them.
|
|
81
|
+
{ envSource: {} });
|
|
82
|
+
}
|
|
83
|
+
/** Pretty-print a result unless --json is active (oclif prints the return value then). */
|
|
84
|
+
print(data) {
|
|
85
|
+
if (!this.jsonEnabled()) {
|
|
86
|
+
this.log(typeof data === 'string' ? data : JSON.stringify(data, null, 2));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async catch(error) {
|
|
90
|
+
if (error instanceof NorbixError) {
|
|
91
|
+
const status = error.status;
|
|
92
|
+
const suffix = status ? ` (HTTP ${status})` : '';
|
|
93
|
+
const hint = status === 401
|
|
94
|
+
? '\nYour session may have expired. Run `norbix login` again.'
|
|
95
|
+
: '';
|
|
96
|
+
return this.error(`${error.message}${suffix}${hint}`);
|
|
97
|
+
}
|
|
98
|
+
return super.catch(error);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { BaseCommand } from '../../base.js';
|
|
3
|
+
export default class AccountBillingPortal extends BaseCommand {
|
|
4
|
+
static description = 'Get a Stripe billing portal URL for the account';
|
|
5
|
+
static examples = ['<%= config.bin %> account billing-portal'];
|
|
6
|
+
static flags = {
|
|
7
|
+
type: Flags.string({ description: 'Subscription type (backend enum, optional)' }),
|
|
8
|
+
'return-url': Flags.string({ description: 'Where Stripe sends the user back to' }),
|
|
9
|
+
};
|
|
10
|
+
async run() {
|
|
11
|
+
const { flags } = await this.parse(AccountBillingPortal);
|
|
12
|
+
const client = this.client(flags);
|
|
13
|
+
const res = await client.hub.account.getStripeBillingPortalUrl({
|
|
14
|
+
// Backend enum — passed through as-is when provided.
|
|
15
|
+
...(flags.type ? { subscriptionType: flags.type } : {}),
|
|
16
|
+
returnUrl: flags['return-url'],
|
|
17
|
+
});
|
|
18
|
+
this.print(res);
|
|
19
|
+
return res;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base.js';
|
|
2
|
+
export default class AccountProfile extends BaseCommand {
|
|
3
|
+
static description = 'Show the account profile';
|
|
4
|
+
static examples = ['<%= config.bin %> account profile'];
|
|
5
|
+
async run() {
|
|
6
|
+
const { flags } = await this.parse(AccountProfile);
|
|
7
|
+
const client = this.client(flags);
|
|
8
|
+
const res = await client.hub.account.getAccountProfile({});
|
|
9
|
+
this.print(res);
|
|
10
|
+
return res;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base.js';
|
|
2
|
+
export default class AccountProjects extends BaseCommand {
|
|
3
|
+
static description = 'List all projects in the account';
|
|
4
|
+
static examples = ['<%= config.bin %> account projects'];
|
|
5
|
+
async run() {
|
|
6
|
+
const { flags } = await this.parse(AccountProjects);
|
|
7
|
+
const client = this.client(flags);
|
|
8
|
+
const res = await client.hub.account.getProjects({});
|
|
9
|
+
this.print(res);
|
|
10
|
+
return res;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base.js';
|
|
2
|
+
export default class AccountRegions extends BaseCommand {
|
|
3
|
+
static description = 'List regions available to the account';
|
|
4
|
+
static examples = ['<%= config.bin %> account regions'];
|
|
5
|
+
async run() {
|
|
6
|
+
const { flags } = await this.parse(AccountRegions);
|
|
7
|
+
const client = this.client(flags);
|
|
8
|
+
const res = await client.hub.account.getAccountRegions({});
|
|
9
|
+
this.print(res);
|
|
10
|
+
return res;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base.js';
|
|
2
|
+
export default class AccountStatus extends BaseCommand {
|
|
3
|
+
static description = 'Show the account status (plan, verification, limits)';
|
|
4
|
+
static examples = ['<%= config.bin %> account status'];
|
|
5
|
+
async run() {
|
|
6
|
+
const { flags } = await this.parse(AccountStatus);
|
|
7
|
+
const client = this.client(flags);
|
|
8
|
+
const res = await client.hub.account.getAccountStatus({});
|
|
9
|
+
this.print(res);
|
|
10
|
+
return res;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base.js';
|
|
2
|
+
export default class AccountTeam extends BaseCommand {
|
|
3
|
+
static description = 'List account collaborators (team members)';
|
|
4
|
+
static examples = ['<%= config.bin %> account team'];
|
|
5
|
+
async run() {
|
|
6
|
+
const { flags } = await this.parse(AccountTeam);
|
|
7
|
+
const client = this.client(flags);
|
|
8
|
+
const res = await client.hub.account.getAccountCollaborators({});
|
|
9
|
+
this.print(res);
|
|
10
|
+
return res;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base.js';
|
|
2
|
+
export default class AccountUsage extends BaseCommand {
|
|
3
|
+
static description = 'Show usage-based billing for the account';
|
|
4
|
+
static examples = ['<%= config.bin %> account usage'];
|
|
5
|
+
async run() {
|
|
6
|
+
const { flags } = await this.parse(AccountUsage);
|
|
7
|
+
const client = this.client(flags);
|
|
8
|
+
const res = await client.hub.account.getAccountUsageBilling({});
|
|
9
|
+
this.print(res);
|
|
10
|
+
return res;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { BaseCommand } from '../base.js';
|
|
3
|
+
import { readJsonInput } from '../lib/json.js';
|
|
4
|
+
const DEFAULT_API = 'https://api.norbix.dev';
|
|
5
|
+
const DEFAULT_HUB = 'https://hub.norbix.dev';
|
|
6
|
+
/**
|
|
7
|
+
* Raw escape hatch (like `gh api`): call ANY Norbix endpoint, including the
|
|
8
|
+
* ones this CLI has no dedicated command for yet. Sends the same auth and
|
|
9
|
+
* scope headers the SDK transport sends.
|
|
10
|
+
*/
|
|
11
|
+
export default class Api extends BaseCommand {
|
|
12
|
+
static description = `Call any Norbix endpoint directly (escape hatch, like \`gh api\`).
|
|
13
|
+
|
|
14
|
+
Sends your auth token plus project/account/env/region headers automatically.
|
|
15
|
+
Use {version} in the path and it becomes v2.`;
|
|
16
|
+
static examples = [
|
|
17
|
+
'<%= config.bin %> api /v2/logs/settings --hub',
|
|
18
|
+
`<%= config.bin %> api '/{version}/database/collections/orders/count' --query filter='{"status":"paid"}'`,
|
|
19
|
+
`<%= config.bin %> api /v2/scheduler/tasks --hub --method POST --body '{"name":"nightly",...}'`,
|
|
20
|
+
];
|
|
21
|
+
static args = {
|
|
22
|
+
path: Args.string({ required: true, description: 'Endpoint path, e.g. /v2/logs or /{version}/logs' }),
|
|
23
|
+
};
|
|
24
|
+
static flags = {
|
|
25
|
+
hub: Flags.boolean({ description: 'Call the Hub service instead of the API service', default: false }),
|
|
26
|
+
method: Flags.string({ char: 'X', description: 'HTTP method', default: 'GET', options: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'] }),
|
|
27
|
+
body: Flags.string({ char: 'b', description: 'JSON request body (or `-` for stdin)' }),
|
|
28
|
+
query: Flags.string({ char: 'q', description: 'Query parameter key=value (repeatable)', multiple: true }),
|
|
29
|
+
};
|
|
30
|
+
async run() {
|
|
31
|
+
const { args, flags } = await this.parse(Api);
|
|
32
|
+
const ctx = this.resolveContext(flags);
|
|
33
|
+
if (!ctx.projectId)
|
|
34
|
+
this.error('No project ID configured. Run `norbix login` or pass --project.');
|
|
35
|
+
const token = ctx.bearerToken ?? ctx.apiKey;
|
|
36
|
+
if (!token)
|
|
37
|
+
this.error('Not authenticated. Run `norbix login` or pass --api-key.');
|
|
38
|
+
// Base URL: custom > default (+ region subdomain, same rule as the SDK).
|
|
39
|
+
let base = flags.hub ? (ctx.hubUrl ?? DEFAULT_HUB) : (ctx.apiUrl ?? DEFAULT_API);
|
|
40
|
+
const usesDefault = flags.hub ? !ctx.hubUrl : !ctx.apiUrl;
|
|
41
|
+
if (usesDefault && ctx.region)
|
|
42
|
+
base = base.replace('://', `://${ctx.region}.`);
|
|
43
|
+
const path = args.path.replace('{version}', 'v2');
|
|
44
|
+
const url = new URL(base.replace(/\/$/, '') + (path.startsWith('/') ? path : `/${path}`));
|
|
45
|
+
for (const pair of flags.query ?? []) {
|
|
46
|
+
const eq = pair.indexOf('=');
|
|
47
|
+
if (eq < 1)
|
|
48
|
+
this.error(`--query must be key=value, got "${pair}"`);
|
|
49
|
+
url.searchParams.append(pair.slice(0, eq), pair.slice(eq + 1));
|
|
50
|
+
}
|
|
51
|
+
const headers = {
|
|
52
|
+
Accept: 'application/json',
|
|
53
|
+
Authorization: `Bearer ${token}`,
|
|
54
|
+
'norbix-project-id': ctx.projectId,
|
|
55
|
+
'X-CM-ProjectId': ctx.projectId,
|
|
56
|
+
};
|
|
57
|
+
if (ctx.accountId) {
|
|
58
|
+
headers['norbix-account-id'] = ctx.accountId;
|
|
59
|
+
headers['X-CM-AccountId'] = ctx.accountId;
|
|
60
|
+
}
|
|
61
|
+
if (ctx.env && ctx.env !== 'PROD')
|
|
62
|
+
headers['norbix-env'] = ctx.env;
|
|
63
|
+
if (ctx.region)
|
|
64
|
+
headers['nb-region'] = ctx.region;
|
|
65
|
+
let body;
|
|
66
|
+
if (flags.body) {
|
|
67
|
+
body = await readJsonInput(flags.body, 'body');
|
|
68
|
+
headers['Content-Type'] = 'application/json';
|
|
69
|
+
}
|
|
70
|
+
const res = await fetch(url, { method: flags.method, headers, body });
|
|
71
|
+
const text = await res.text();
|
|
72
|
+
let data;
|
|
73
|
+
try {
|
|
74
|
+
data = JSON.parse(text);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
data = text;
|
|
78
|
+
}
|
|
79
|
+
if (!res.ok) {
|
|
80
|
+
this.warn(`HTTP ${res.status} ${res.statusText}`);
|
|
81
|
+
this.print(data);
|
|
82
|
+
this.exit(1);
|
|
83
|
+
}
|
|
84
|
+
this.print(data);
|
|
85
|
+
return data;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base.js';
|
|
2
|
+
export default class ApikeysList extends BaseCommand {
|
|
3
|
+
static description = "Show the project's API keys for the current environment";
|
|
4
|
+
static examples = ['<%= config.bin %> apikeys list', '<%= config.bin %> apikeys list --env TEST'];
|
|
5
|
+
async run() {
|
|
6
|
+
const { flags } = await this.parse(ApikeysList);
|
|
7
|
+
const client = this.client(flags);
|
|
8
|
+
const res = await client.api.apikeys.getApiKeys({ environment: flags.env });
|
|
9
|
+
this.print(res);
|
|
10
|
+
return res;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { confirm } from '@inquirer/prompts';
|
|
2
|
+
import { Flags } from '@oclif/core';
|
|
3
|
+
import { BaseCommand } from '../../base.js';
|
|
4
|
+
export default class ApikeysRegenerate extends BaseCommand {
|
|
5
|
+
static description = 'Regenerate the API keys for the current environment (old keys stop working!)';
|
|
6
|
+
static examples = ['<%= config.bin %> apikeys regenerate --yes'];
|
|
7
|
+
static flags = {
|
|
8
|
+
yes: Flags.boolean({ char: 'y', description: 'Skip the confirmation prompt', default: false }),
|
|
9
|
+
};
|
|
10
|
+
async run() {
|
|
11
|
+
const { flags } = await this.parse(ApikeysRegenerate);
|
|
12
|
+
const client = this.client(flags);
|
|
13
|
+
if (!flags.yes && process.stdout.isTTY) {
|
|
14
|
+
const ok = await confirm({
|
|
15
|
+
message: 'Regenerate API keys? Every service using the old keys will lose access.',
|
|
16
|
+
default: false,
|
|
17
|
+
});
|
|
18
|
+
if (!ok)
|
|
19
|
+
return this.print('Cancelled.');
|
|
20
|
+
}
|
|
21
|
+
const res = await client.api.apikeys.regenerateApiKeys({ environment: flags.env });
|
|
22
|
+
this.print(res);
|
|
23
|
+
return res;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Args } from '@oclif/core';
|
|
2
|
+
import { BaseCommand } from '../../base.js';
|
|
3
|
+
import { SETTABLE_KEYS, isSettableKey, redact } from '../../lib/store.js';
|
|
4
|
+
export default class ConfigGet extends BaseCommand {
|
|
5
|
+
static description = 'Print one configuration value';
|
|
6
|
+
static examples = ['<%= config.bin %> config get projectId'];
|
|
7
|
+
static args = {
|
|
8
|
+
key: Args.string({ required: true, description: `One of: ${SETTABLE_KEYS.join(', ')}` }),
|
|
9
|
+
};
|
|
10
|
+
async run() {
|
|
11
|
+
const { args } = await this.parse(ConfigGet);
|
|
12
|
+
if (!isSettableKey(args.key)) {
|
|
13
|
+
this.error(`Unknown key "${args.key}". Valid keys: ${SETTABLE_KEYS.join(', ')}`);
|
|
14
|
+
}
|
|
15
|
+
const stored = this.readStore();
|
|
16
|
+
const value = args.key === 'apiKey' ? redact(stored.apiKey) : stored[args.key];
|
|
17
|
+
this.print(value ?? '(not set)');
|
|
18
|
+
return { key: args.key, value };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base.js';
|
|
2
|
+
import { configFilePath, redact } from '../../lib/store.js';
|
|
3
|
+
export default class ConfigList extends BaseCommand {
|
|
4
|
+
static description = 'Show the local CLI configuration (secrets redacted)';
|
|
5
|
+
static examples = ['<%= config.bin %> config list'];
|
|
6
|
+
async run() {
|
|
7
|
+
await this.parse(ConfigList);
|
|
8
|
+
const stored = this.readStore();
|
|
9
|
+
const display = {
|
|
10
|
+
...stored,
|
|
11
|
+
apiKey: redact(stored.apiKey),
|
|
12
|
+
bearerToken: redact(stored.bearerToken),
|
|
13
|
+
refreshToken: redact(stored.refreshToken),
|
|
14
|
+
};
|
|
15
|
+
this.print({ file: configFilePath(this.config.configDir), ...display });
|
|
16
|
+
return display;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Args } from '@oclif/core';
|
|
2
|
+
import { BaseCommand } from '../../base.js';
|
|
3
|
+
import { SETTABLE_KEYS, isSettableKey, writeStore } from '../../lib/store.js';
|
|
4
|
+
export default class ConfigSet extends BaseCommand {
|
|
5
|
+
static description = 'Set one configuration value';
|
|
6
|
+
static examples = [
|
|
7
|
+
'<%= config.bin %> config set projectId 5f1a9f7e...',
|
|
8
|
+
'<%= config.bin %> config set region nb-eu-germany',
|
|
9
|
+
'<%= config.bin %> config set env TEST',
|
|
10
|
+
];
|
|
11
|
+
static args = {
|
|
12
|
+
key: Args.string({ required: true, description: `One of: ${SETTABLE_KEYS.join(', ')}` }),
|
|
13
|
+
value: Args.string({ required: true, description: 'New value' }),
|
|
14
|
+
};
|
|
15
|
+
async run() {
|
|
16
|
+
const { args } = await this.parse(ConfigSet);
|
|
17
|
+
if (!isSettableKey(args.key)) {
|
|
18
|
+
this.error(`Unknown key "${args.key}". Valid keys: ${SETTABLE_KEYS.join(', ')}`);
|
|
19
|
+
}
|
|
20
|
+
const stored = this.readStore();
|
|
21
|
+
writeStore(this.config.configDir, { ...stored, [args.key]: args.value });
|
|
22
|
+
this.print(`${args.key} = ${args.key === 'apiKey' ? '(saved)' : args.value}`);
|
|
23
|
+
return { key: args.key, saved: true };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Args } from '@oclif/core';
|
|
2
|
+
import { BaseCommand } from '../../base.js';
|
|
3
|
+
import { SETTABLE_KEYS, isSettableKey, writeStore } from '../../lib/store.js';
|
|
4
|
+
export default class ConfigUnset extends BaseCommand {
|
|
5
|
+
static description = 'Remove one configuration value';
|
|
6
|
+
static examples = ['<%= config.bin %> config unset region'];
|
|
7
|
+
static args = {
|
|
8
|
+
key: Args.string({ required: true, description: `One of: ${SETTABLE_KEYS.join(', ')}` }),
|
|
9
|
+
};
|
|
10
|
+
async run() {
|
|
11
|
+
const { args } = await this.parse(ConfigUnset);
|
|
12
|
+
if (!isSettableKey(args.key)) {
|
|
13
|
+
this.error(`Unknown key "${args.key}". Valid keys: ${SETTABLE_KEYS.join(', ')}`);
|
|
14
|
+
}
|
|
15
|
+
const stored = this.readStore();
|
|
16
|
+
writeStore(this.config.configDir, { ...stored, [args.key]: undefined });
|
|
17
|
+
this.print(`${args.key} removed.`);
|
|
18
|
+
return { key: args.key, removed: true };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { BaseCommand } from '../../base.js';
|
|
3
|
+
import { readJsonInput } from '../../lib/json.js';
|
|
4
|
+
export default class DbAggregate extends BaseCommand {
|
|
5
|
+
static description = 'Run an aggregation: an inline pipeline, or a saved aggregate by ID';
|
|
6
|
+
static examples = [
|
|
7
|
+
`<%= config.bin %> db aggregate orders --pipeline '[{"$group":{"_id":"$status","n":{"$sum":1}}}]'`,
|
|
8
|
+
'<%= config.bin %> db aggregate orders --id 66b2f0a1...',
|
|
9
|
+
];
|
|
10
|
+
static args = {
|
|
11
|
+
collection: Args.string({ required: true, description: 'Collection name' }),
|
|
12
|
+
};
|
|
13
|
+
static flags = {
|
|
14
|
+
pipeline: Flags.string({ description: 'JSON aggregation pipeline (or `-` for stdin)', exclusive: ['id'] }),
|
|
15
|
+
id: Flags.string({ description: 'Saved aggregate ID (runs executeAggregate)' }),
|
|
16
|
+
tokens: Flags.string({ description: 'JSON object of tokens for a saved aggregate', dependsOn: ['id'] }),
|
|
17
|
+
};
|
|
18
|
+
async run() {
|
|
19
|
+
const { args, flags } = await this.parse(DbAggregate);
|
|
20
|
+
const client = this.client(flags);
|
|
21
|
+
if (flags.id) {
|
|
22
|
+
const tokens = flags.tokens
|
|
23
|
+
? JSON.parse(await readJsonInput(flags.tokens, 'tokens'))
|
|
24
|
+
: undefined;
|
|
25
|
+
const res = await client.api.database.executeAggregate({
|
|
26
|
+
collectionName: args.collection,
|
|
27
|
+
aggregateId: flags.id,
|
|
28
|
+
tokens,
|
|
29
|
+
});
|
|
30
|
+
this.print(res);
|
|
31
|
+
return res;
|
|
32
|
+
}
|
|
33
|
+
if (!flags.pipeline)
|
|
34
|
+
this.error('Pass --pipeline with inline JSON, or --id for a saved aggregate.');
|
|
35
|
+
const res = await client.api.database.aggregate({
|
|
36
|
+
collectionName: args.collection,
|
|
37
|
+
pipeline: await readJsonInput(flags.pipeline, 'pipeline'),
|
|
38
|
+
});
|
|
39
|
+
this.print(res);
|
|
40
|
+
return res;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { BaseCommand } from '../../base.js';
|
|
3
|
+
export default class DbAggregates extends BaseCommand {
|
|
4
|
+
static description = 'List saved database aggregates';
|
|
5
|
+
static examples = ['<%= config.bin %> db aggregates', '<%= config.bin %> db aggregates --schema 66b2...'];
|
|
6
|
+
static flags = {
|
|
7
|
+
schema: Flags.string({ description: 'Only aggregates of this schema ID' }),
|
|
8
|
+
};
|
|
9
|
+
async run() {
|
|
10
|
+
const { flags } = await this.parse(DbAggregates);
|
|
11
|
+
const client = this.client(flags);
|
|
12
|
+
const res = await client.hub.database.getDatabaseAggregates({ schemaId: flags.schema });
|
|
13
|
+
this.print(res);
|
|
14
|
+
return res;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { BaseCommand } from '../../base.js';
|
|
3
|
+
import { readJsonInput } from '../../lib/json.js';
|
|
4
|
+
export default class DbCount extends BaseCommand {
|
|
5
|
+
static description = 'Count records in a collection';
|
|
6
|
+
static examples = [
|
|
7
|
+
'<%= config.bin %> db count orders',
|
|
8
|
+
`<%= config.bin %> db count orders --filter '{"status":"paid"}'`,
|
|
9
|
+
];
|
|
10
|
+
static args = {
|
|
11
|
+
collection: Args.string({ required: true, description: 'Collection name' }),
|
|
12
|
+
};
|
|
13
|
+
static flags = {
|
|
14
|
+
filter: Flags.string({ char: 'f', description: 'JSON filter (or `-` for stdin)' }),
|
|
15
|
+
};
|
|
16
|
+
async run() {
|
|
17
|
+
const { args, flags } = await this.parse(DbCount);
|
|
18
|
+
const client = this.client(flags);
|
|
19
|
+
const res = await client.api.database.count({
|
|
20
|
+
collectionName: args.collection,
|
|
21
|
+
filter: flags.filter ? await readJsonInput(flags.filter, 'filter') : undefined,
|
|
22
|
+
});
|
|
23
|
+
this.print(res);
|
|
24
|
+
return res;
|
|
25
|
+
}
|
|
26
|
+
}
|