@norbix.ai/cli 0.1.0 → 0.2.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.
Files changed (41) hide show
  1. package/README.md +125 -13
  2. package/dist/base.js +92 -23
  3. package/dist/commands/api.js +15 -81
  4. package/dist/commands/configure.js +68 -0
  5. package/dist/commands/env/use.js +36 -7
  6. package/dist/commands/files/delete.js +1 -1
  7. package/dist/commands/files/download.js +1 -1
  8. package/dist/commands/files/info.js +1 -1
  9. package/dist/commands/files/list.js +1 -1
  10. package/dist/commands/files/publish.js +38 -0
  11. package/dist/commands/files/sign.js +1 -1
  12. package/dist/commands/files/unpublish.js +34 -0
  13. package/dist/commands/files/upload.js +1 -1
  14. package/dist/commands/hub.js +24 -0
  15. package/dist/commands/login.js +82 -36
  16. package/dist/commands/logout.js +11 -13
  17. package/dist/commands/profiles.js +25 -0
  18. package/dist/commands/push/archive.js +4 -1
  19. package/dist/commands/push/clone.js +4 -1
  20. package/dist/commands/push/delete.js +4 -1
  21. package/dist/commands/push/device/register.js +35 -0
  22. package/dist/commands/push/integration/default.js +19 -0
  23. package/dist/commands/push/integration/delete.js +28 -0
  24. package/dist/commands/push/integration/disable.js +19 -0
  25. package/dist/commands/push/integration/enable.js +19 -0
  26. package/dist/commands/push/integration/test.js +30 -0
  27. package/dist/commands/push/integration.js +16 -0
  28. package/dist/commands/push/integrations.js +20 -0
  29. package/dist/commands/push/preview.js +19 -0
  30. package/dist/commands/push/settings.js +16 -0
  31. package/dist/commands/push/unarchive.js +4 -1
  32. package/dist/commands/raw.js +86 -0
  33. package/dist/commands/whoami.js +25 -8
  34. package/dist/help.js +18 -0
  35. package/dist/lib/device-login.js +76 -0
  36. package/dist/lib/dispatch.js +151 -0
  37. package/dist/lib/namespace-command.js +148 -0
  38. package/dist/lib/profiles.js +138 -0
  39. package/dist/lib/publicFiles.js +63 -0
  40. package/oclif.manifest.json +3166 -725
  41. package/package.json +24 -12
@@ -14,7 +14,7 @@ export default class FilesSign extends BaseCommand {
14
14
  async run() {
15
15
  const { args, flags } = await this.parse(FilesSign);
16
16
  const client = this.client(flags);
17
- const integration = resolveIntegration(flags.integration, this.readStore().filesIntegrationId);
17
+ const integration = resolveIntegration(flags.integration, this.resolveContext(flags).filesIntegrationId);
18
18
  if (!integration) {
19
19
  this.error('No files integration ID. Pass --integration or run `norbix config set filesIntegrationId <id>`.');
20
20
  }
@@ -0,0 +1,34 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import { BaseCommand } from '../../base.js';
3
+ import { integrationFlag, resolveIntegration } from '../../lib/files.js';
4
+ import { callPublicFiles } from '../../lib/publicFiles.js';
5
+ export default class FilesUnpublish extends BaseCommand {
6
+ static args = {
7
+ remote: Args.string({ description: 'Remote file path, or folder prefix with --folder', required: true }),
8
+ };
9
+ static description = "Take away a file's (or a folder's) public link";
10
+ static examples = [
11
+ '<%= config.bin %> files unpublish invoices/2026/invoice.pdf',
12
+ '<%= config.bin %> files unpublish invoices --folder',
13
+ ];
14
+ static flags = {
15
+ folder: Flags.boolean({
16
+ description: 'Unpublish the whole folder prefix, and every link inside it',
17
+ }),
18
+ integration: integrationFlag,
19
+ };
20
+ async run() {
21
+ const { args, flags } = await this.parse(FilesUnpublish);
22
+ const ctx = this.resolveContext(flags);
23
+ this.client(flags);
24
+ const integration = resolveIntegration(flags.integration, ctx.filesIntegrationId);
25
+ if (!integration) {
26
+ this.error('No files integration ID. Pass --integration or run `norbix config set filesIntegrationId <id>`.');
27
+ }
28
+ await callPublicFiles(ctx, flags.folder ? 'makeFolderPrivate' : 'makeFilePrivate', { filesIntegrationId: integration, path: args.remote });
29
+ this.print(flags.folder
30
+ ? `${args.remote} is private again — every link inside it is gone`
31
+ : `${args.remote} is private again — its link now gives a 404`);
32
+ return { path: args.remote, public: false };
33
+ }
34
+ }
@@ -23,7 +23,7 @@ export default class FilesUpload extends BaseCommand {
23
23
  async run() {
24
24
  const { args, flags } = await this.parse(FilesUpload);
25
25
  const client = this.client(flags);
26
- const integration = resolveIntegration(flags.integration, this.readStore().filesIntegrationId);
26
+ const integration = resolveIntegration(flags.integration, this.resolveContext(flags).filesIntegrationId);
27
27
  if (!integration) {
28
28
  this.error('No files integration ID. Pass --integration or run `norbix config set filesIntegrationId <id>`.');
29
29
  }
@@ -0,0 +1,24 @@
1
+ import { NamespaceCommand } from '../lib/namespace-command.js';
2
+ export default class Hub extends NamespaceCommand {
3
+ target = 'hub';
4
+ static description = `Call any Hub endpoint with plain words.
5
+
6
+ Grammar: norbix hub <module> <words...> [id] [--field value]
7
+ The method is resolved from the words — plural means "list", singular (or a
8
+ positional id) means one item. Destructive verbs ask for confirmation
9
+ (skip with --yes). Add --dry-run to see what would be called.`;
10
+ static strict = false;
11
+ static examples = [
12
+ '<%= config.bin %> hub # list modules',
13
+ '<%= config.bin %> hub database # list database methods',
14
+ '<%= config.bin %> hub database aggregates get',
15
+ '<%= config.bin %> hub database aggregates delete maggr_123 --schemaId sch_456',
16
+ '<%= config.bin %> hub scheduler tasks get --pageSize 100',
17
+ '<%= config.bin %> hub logs get --level Error',
18
+ ];
19
+ async run() {
20
+ const { flagArgv, rest, help } = this.splitArgv();
21
+ const { flags } = await this.parse(Hub, flagArgv);
22
+ return this.dispatch(rest, flags, help);
23
+ }
24
+ }
@@ -2,64 +2,110 @@ import { input, password as passwordPrompt } from '@inquirer/prompts';
2
2
  import { Norbix } from '@norbix.ai/ts';
3
3
  import { Flags } from '@oclif/core';
4
4
  import { BaseCommand } from '../base.js';
5
- import { writeStore } from '../lib/store.js';
5
+ import { DeviceFlowUnsupportedError, openBrowser, pollDeviceToken, startDeviceFlow, } from '../lib/device-login.js';
6
+ import { SESSION_PATH, readProfiles, writeProfile, writeSession } from '../lib/profiles.js';
6
7
  export default class Login extends BaseCommand {
7
- static description = `Log in to Norbix and store credentials on this machine.
8
+ static description = `Log in to Norbix.
8
9
 
9
- Two ways to authenticate:
10
- 1. User login — prompts for user name and password, stores a bearer token.
11
- 2. API key — pass --api-key to store a long-lived key instead.`;
10
+ Default: browser login — the CLI shows a code, opens hub in your browser,
11
+ you approve there, and a session is saved to ~/.norbix/session.json. New
12
+ terminal windows reuse the session while it is valid. If the hub does not
13
+ support browser login yet, the CLI falls back to user + password.
14
+
15
+ --user / --password force the password flow.
16
+ --api-key saves a long-lived key into a profile instead
17
+ (same as \`norbix configure\`).
18
+
19
+ Commands run with --profile skip the session on purpose and use that
20
+ profile's API key.`;
12
21
  static examples = [
13
22
  '<%= config.bin %> login',
14
- '<%= config.bin %> login --user alice@example.com --project 5f1a...',
15
- '<%= config.bin %> login --api-key nbk_live_... --project 5f1a...',
23
+ '<%= config.bin %> login --user alice@example.com',
24
+ '<%= config.bin %> login --api-key nbk_... --project 5f1a... --profile ci',
16
25
  ];
17
26
  static flags = {
18
- user: Flags.string({ char: 'u', description: 'User name (email)' }),
19
- password: Flags.string({
20
- char: 'p',
21
- description: 'Password (prompted securely if omitted)',
22
- }),
27
+ user: Flags.string({ char: 'u', description: 'User name (email) — forces password login' }),
28
+ password: Flags.string({ char: 'p', description: 'Password (prompted securely if omitted)' }),
23
29
  };
24
30
  async run() {
25
31
  const { flags } = await this.parse(Login);
26
- const stored = this.readStore();
27
- const projectId = flags.project ??
28
- stored.projectId ??
29
- (await input({ message: 'Project ID:', required: true }));
30
- const region = flags.region ?? stored.region;
31
- // Mode 1: API key.
32
+ const profiles = readProfiles();
33
+ const profName = flags.profile ?? 'default';
34
+ const existing = profiles[profName] ?? {};
35
+ // Mode 1: API key → saved as a profile (long-lived identity).
32
36
  if (flags['api-key']) {
33
- writeStore(this.config.configDir, {
34
- ...stored,
35
- projectId,
36
- region,
37
- apiKey: flags['api-key'],
38
- bearerToken: undefined,
39
- refreshToken: undefined,
40
- userId: undefined,
41
- userName: undefined,
37
+ const projectId = flags.project ?? existing.project_id ?? (await input({ message: 'Project ID:', required: true }));
38
+ writeProfile(profName, {
39
+ ...existing,
40
+ api_key: flags['api-key'],
41
+ project_id: projectId,
42
+ region: flags.region ?? existing.region,
42
43
  });
43
- this.print(`API key saved for project ${projectId}. Run \`norbix whoami\` to verify.`);
44
- return { method: 'apiKey', projectId };
44
+ this.print(`API key saved to profile [${profName}]. Try \`norbix whoami${profName === 'default' ? '' : ` --profile ${profName}`}\` to verify.`);
45
+ return { method: 'apiKey', profile: profName, projectId };
46
+ }
47
+ const ctx = this.resolveContext(flags);
48
+ this.assertEndpoints(ctx);
49
+ // Mode 2 (default): browser login via the device flow.
50
+ if (!flags.user && !flags.password) {
51
+ try {
52
+ return await this.browserLogin(ctx.hubUrl, flags.project ?? existing.project_id, flags);
53
+ }
54
+ catch (error) {
55
+ if (!(error instanceof DeviceFlowUnsupportedError))
56
+ throw error;
57
+ this.log('Browser login is not available on this hub yet — falling back to password login.\n');
58
+ }
45
59
  }
46
- // Mode 2: user + password.
60
+ // Mode 3: user + password.
61
+ return this.passwordLogin(flags, existing);
62
+ }
63
+ async browserLogin(hubUrl, projectId, flags) {
64
+ const start = await startDeviceFlow(hubUrl, projectId);
65
+ this.log(`First, copy your one-time code: ${start.userCode}`);
66
+ if (process.stdout.isTTY) {
67
+ await input({ message: 'Press ENTER to open the browser...' });
68
+ openBrowser(start.verificationUriComplete ?? start.verificationUri);
69
+ }
70
+ this.log(`Or open this URL yourself: ${start.verificationUri}`);
71
+ this.log('Waiting for you to approve in the browser...');
72
+ const token = await pollDeviceToken(hubUrl, start, (m) => this.log(m));
73
+ writeSession({
74
+ bearerToken: token.bearerToken,
75
+ refreshToken: token.refreshToken,
76
+ projectId: token.projectId ?? projectId,
77
+ accountId: token.accountId ?? flags.account,
78
+ env: flags.env,
79
+ region: flags.region,
80
+ userId: token.userId,
81
+ userName: token.userName,
82
+ savedAt: new Date().toISOString(),
83
+ });
84
+ this.print(`Logged in as ${token.displayName ?? token.userName ?? 'user'}.\nSession saved to ${SESSION_PATH}.`);
85
+ return { method: 'browser', userId: token.userId, userName: token.userName };
86
+ }
87
+ async passwordLogin(flags, existing) {
88
+ const projectId = flags.project ?? existing.project_id ?? (await input({ message: 'Project ID:', required: true }));
89
+ const region = flags.region ?? existing.region;
47
90
  const userName = flags.user ?? (await input({ message: 'User name (email):', required: true }));
48
91
  const pwd = flags.password ?? (await passwordPrompt({ message: 'Password:', mask: '*' }));
49
- const client = new Norbix({ projectId, region, env: flags.env ?? stored.env, baseUrl: { api: stored.apiUrl, hub: stored.hubUrl } }, { envSource: {} });
92
+ const ctx = this.resolveContext({ ...flags, project: projectId, region });
93
+ const client = new Norbix({ projectId, region, env: flags.env ?? existing.env, baseUrl: { api: ctx.apiUrl, hub: ctx.hubUrl } }, { envSource: {} });
50
94
  const res = await client.login({ userName, password: pwd });
51
95
  if (!res.bearerToken)
52
96
  this.error('Login succeeded but no token was returned.');
53
- writeStore(this.config.configDir, {
54
- ...stored,
55
- projectId,
56
- region,
97
+ writeSession({
57
98
  bearerToken: res.bearerToken,
58
99
  refreshToken: res.refreshToken,
100
+ projectId,
101
+ accountId: flags.account ?? existing.account_id,
102
+ env: flags.env ?? existing.env,
103
+ region,
59
104
  userId: res.userId,
60
105
  userName: res.userName ?? userName,
106
+ savedAt: new Date().toISOString(),
61
107
  });
62
- this.print(`Logged in as ${res.displayName ?? res.userName ?? userName} (project ${projectId}).`);
108
+ this.print(`Logged in as ${res.displayName ?? res.userName ?? userName} (project ${projectId}).\nSession saved to ${SESSION_PATH}.`);
63
109
  return { method: 'credentials', projectId, userId: res.userId, userName: res.userName ?? userName };
64
110
  }
65
111
  }
@@ -1,17 +1,16 @@
1
- import { Flags } from '@oclif/core';
2
1
  import { BaseCommand } from '../base.js';
2
+ import { clearSession } from '../lib/profiles.js';
3
3
  import { writeStore } from '../lib/store.js';
4
4
  export default class Logout extends BaseCommand {
5
- static description = 'Remove stored credentials from this machine';
6
- static examples = ['<%= config.bin %> logout', '<%= config.bin %> logout --all'];
7
- static flags = {
8
- all: Flags.boolean({
9
- description: 'Also remove the stored API key (default: only the login token)',
10
- default: false,
11
- }),
12
- };
5
+ static description = `Remove the login session from this machine.
6
+
7
+ Profiles in ~/.norbix/config (API keys) are NOT touched — manage those with
8
+ \`norbix configure\` or by editing the file.`;
9
+ static examples = ['<%= config.bin %> logout'];
13
10
  async run() {
14
- const { flags } = await this.parse(Logout);
11
+ await this.parse(Logout);
12
+ clearSession();
13
+ // Also clear tokens from the legacy per-OS config location.
15
14
  const stored = this.readStore();
16
15
  writeStore(this.config.configDir, {
17
16
  ...stored,
@@ -19,9 +18,8 @@ export default class Logout extends BaseCommand {
19
18
  refreshToken: undefined,
20
19
  userId: undefined,
21
20
  userName: undefined,
22
- ...(flags.all ? { apiKey: undefined } : {}),
23
21
  });
24
- this.print(flags.all ? 'Logged out. API key removed too.' : 'Logged out.');
25
- return { loggedOut: true, removedApiKey: flags.all };
22
+ this.print('Logged out. Session removed.');
23
+ return { loggedOut: true };
26
24
  }
27
25
  }
@@ -0,0 +1,25 @@
1
+ import { BaseCommand } from '../base.js';
2
+ import { PROFILES_PATH, isSessionValid, readProfiles, readSession } from '../lib/profiles.js';
3
+ import { redact } from '../lib/store.js';
4
+ export default class Profiles extends BaseCommand {
5
+ static description = 'List profiles from ~/.norbix/config (keys redacted) and the active session';
6
+ static examples = ['<%= config.bin %> profiles'];
7
+ async run() {
8
+ await this.parse(Profiles);
9
+ const profiles = readProfiles();
10
+ const session = readSession();
11
+ const display = Object.fromEntries(Object.entries(profiles).map(([name, p]) => [
12
+ name,
13
+ { ...p, api_key: redact(p.api_key) },
14
+ ]));
15
+ const sessionInfo = session
16
+ ? {
17
+ user: session.userName ?? session.userId,
18
+ projectId: session.projectId,
19
+ valid: isSessionValid(session),
20
+ }
21
+ : undefined;
22
+ this.print({ file: PROFILES_PATH, profiles: display, session: sessionInfo ?? '(none — run `norbix login`)' });
23
+ return { profiles: display, session: sessionInfo };
24
+ }
25
+ }
@@ -9,7 +9,10 @@ export default class PushArchive extends BaseCommand {
9
9
  async run() {
10
10
  const { args, flags } = await this.parse(PushArchive);
11
11
  const client = this.client(flags);
12
- const res = await client.hub.notifications.archivePushTemplate({ id: args.id });
12
+ // The route token is `{Id}` and the transport fills tokens by exact name,
13
+ // so the request must carry `Id`. The generated type calls the field `id`,
14
+ // which throws NORBIX_MISSING_PATH_PARAM — hence the cast.
15
+ const res = await client.hub.notifications.archivePushTemplate({ Id: args.id });
13
16
  this.print(`Template ${args.id} archived.`);
14
17
  return res;
15
18
  }
@@ -9,7 +9,10 @@ export default class PushClone extends BaseCommand {
9
9
  async run() {
10
10
  const { args, flags } = await this.parse(PushClone);
11
11
  const client = this.client(flags);
12
- const res = await client.hub.notifications.clonePushTemplate({ id: args.id });
12
+ // The route token is `{Id}` and the transport fills tokens by exact name,
13
+ // so the request must carry `Id`. The generated type calls the field `id`,
14
+ // which throws NORBIX_MISSING_PATH_PARAM — hence the cast.
15
+ const res = await client.hub.notifications.clonePushTemplate({ Id: args.id });
13
16
  this.print(`Template ${args.id} cloned.`);
14
17
  return res;
15
18
  }
@@ -18,7 +18,10 @@ export default class PushDelete extends BaseCommand {
18
18
  if (!ok)
19
19
  return this.print('Cancelled.');
20
20
  }
21
- const res = await client.hub.notifications.deletePushTemplate({ id: args.id });
21
+ // The route token is `{Id}` and the transport fills tokens by exact name,
22
+ // so the request must carry `Id`. The generated type calls the field `id`,
23
+ // which throws NORBIX_MISSING_PATH_PARAM — hence the cast.
24
+ const res = await client.hub.notifications.deletePushTemplate({ Id: args.id });
22
25
  this.print(`Template ${args.id} deleted.`);
23
26
  return res;
24
27
  }
@@ -0,0 +1,35 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { BaseCommand } from '../../../base.js';
3
+ export default class PushDeviceRegister extends BaseCommand {
4
+ static description = `Register a device so push campaigns can reach it
5
+
6
+ A device belongs to a user, so --user is required. --os is the operating
7
+ system the token came from; the backend uses it to pick a provider.`;
8
+ static examples = [
9
+ '<%= config.bin %> push device register --user usr_123 --token dGVzdA== --os iOS',
10
+ ];
11
+ static flags = {
12
+ user: Flags.string({ required: true, description: 'User ID the device belongs to' }),
13
+ token: Flags.string({ required: true, description: 'Device token from the platform' }),
14
+ os: Flags.string({ required: true, description: 'Operating system, e.g. iOS or Android' }),
15
+ 'device-id': Flags.string({ description: 'Stable device identifier' }),
16
+ model: Flags.string({ description: 'Device model, e.g. iPhone 15' }),
17
+ name: Flags.string({ description: "The device's own name" }),
18
+ };
19
+ async run() {
20
+ const { flags } = await this.parse(PushDeviceRegister);
21
+ const client = this.client(flags);
22
+ const res = await client.hub.notifications.registerDevice({
23
+ userId: flags.user,
24
+ pushDeviceDto: {
25
+ deviceId: flags['device-id'],
26
+ deviceOs: flags.os,
27
+ token: flags.token,
28
+ modelName: flags.model,
29
+ deviceName: flags.name,
30
+ },
31
+ });
32
+ this.print(res);
33
+ return res;
34
+ }
35
+ }
@@ -0,0 +1,19 @@
1
+ import { Args } from '@oclif/core';
2
+ import { BaseCommand } from '../../../base.js';
3
+ export default class PushIntegrationDefault extends BaseCommand {
4
+ static description = 'Make a push integration the default one for this project';
5
+ static examples = ['<%= config.bin %> push integration default 66b2f0a1...'];
6
+ static args = {
7
+ id: Args.string({ required: true, description: 'Integration ID' }),
8
+ };
9
+ async run() {
10
+ const { args, flags } = await this.parse(PushIntegrationDefault);
11
+ const client = this.client(flags);
12
+ // The generated request type calls this field `id`, but the route token is
13
+ // `{Id}` and the transport fills tokens by exact name — passing `id`
14
+ // throws NORBIX_MISSING_PATH_PARAM. Send `Id` until the type is fixed.
15
+ const res = await client.hub.notifications.setPushIntegrationAsDefault({ Id: args.id });
16
+ this.print(`Integration ${args.id} is now the default.`);
17
+ return res;
18
+ }
19
+ }
@@ -0,0 +1,28 @@
1
+ import { confirm } from '@inquirer/prompts';
2
+ import { Args, Flags } from '@oclif/core';
3
+ import { BaseCommand } from '../../../base.js';
4
+ export default class PushIntegrationDelete extends BaseCommand {
5
+ static description = 'Delete a push integration';
6
+ static examples = ['<%= config.bin %> push integration delete 66b2f0a1... --yes'];
7
+ static args = {
8
+ id: Args.string({ required: true, description: 'Integration ID' }),
9
+ };
10
+ static flags = {
11
+ yes: Flags.boolean({ char: 'y', description: 'Skip the confirmation prompt', default: false }),
12
+ };
13
+ async run() {
14
+ const { args, flags } = await this.parse(PushIntegrationDelete);
15
+ const client = this.client(flags);
16
+ if (!flags.yes && process.stdout.isTTY) {
17
+ const ok = await confirm({ message: `Delete push integration ${args.id}?`, default: false });
18
+ if (!ok)
19
+ return this.print('Cancelled.');
20
+ }
21
+ // The generated request type calls this field `id`, but the route token is
22
+ // `{Id}` and the transport fills tokens by exact name — passing `id`
23
+ // throws NORBIX_MISSING_PATH_PARAM. Send `Id` until the type is fixed.
24
+ const res = await client.hub.notifications.deletePushIntegration({ Id: args.id });
25
+ this.print(`Integration ${args.id} deleted.`);
26
+ return res;
27
+ }
28
+ }
@@ -0,0 +1,19 @@
1
+ import { Args } from '@oclif/core';
2
+ import { BaseCommand } from '../../../base.js';
3
+ export default class PushIntegrationDisable extends BaseCommand {
4
+ static description = 'Turn a push integration off';
5
+ static examples = ['<%= config.bin %> push integration disable 66b2f0a1...'];
6
+ static args = {
7
+ id: Args.string({ required: true, description: 'Integration ID' }),
8
+ };
9
+ async run() {
10
+ const { args, flags } = await this.parse(PushIntegrationDisable);
11
+ const client = this.client(flags);
12
+ // The generated request type calls this field `id`, but the route token is
13
+ // `{Id}` and the transport fills tokens by exact name — passing `id`
14
+ // throws NORBIX_MISSING_PATH_PARAM. Send `Id` until the type is fixed.
15
+ const res = await client.hub.notifications.disablePushIntegration({ Id: args.id });
16
+ this.print(`Integration ${args.id} disabled.`);
17
+ return res;
18
+ }
19
+ }
@@ -0,0 +1,19 @@
1
+ import { Args } from '@oclif/core';
2
+ import { BaseCommand } from '../../../base.js';
3
+ export default class PushIntegrationEnable extends BaseCommand {
4
+ static description = 'Turn a push integration on';
5
+ static examples = ['<%= config.bin %> push integration enable 66b2f0a1...'];
6
+ static args = {
7
+ id: Args.string({ required: true, description: 'Integration ID' }),
8
+ };
9
+ async run() {
10
+ const { args, flags } = await this.parse(PushIntegrationEnable);
11
+ const client = this.client(flags);
12
+ // The generated request type calls this field `id`, but the route token is
13
+ // `{Id}` and the transport fills tokens by exact name — passing `id`
14
+ // throws NORBIX_MISSING_PATH_PARAM. Send `Id` until the type is fixed.
15
+ const res = await client.hub.notifications.enablePushIntegration({ Id: args.id });
16
+ this.print(`Integration ${args.id} enabled.`);
17
+ return res;
18
+ }
19
+ }
@@ -0,0 +1,30 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { BaseCommand } from '../../../base.js';
3
+ export default class PushIntegrationTest extends BaseCommand {
4
+ static description = `Send a test push through an integration
5
+
6
+ Point this at the Fake integration while you are developing — it accepts the
7
+ send and contacts no push service, so nothing reaches a real device.`;
8
+ static examples = [
9
+ '<%= config.bin %> push integration test --integration 66b2f0a1... --token dGVzdA== --family Ios',
10
+ ];
11
+ static flags = {
12
+ integration: Flags.string({ required: true, description: 'Integration ID to send through' }),
13
+ token: Flags.string({ description: 'Device token to send the test to' }),
14
+ family: Flags.string({
15
+ description: 'Which platform the token came from',
16
+ options: ['Ios', 'Android', 'Chrome', 'Safari', 'Expo'],
17
+ }),
18
+ };
19
+ async run() {
20
+ const { flags } = await this.parse(PushIntegrationTest);
21
+ const client = this.client(flags);
22
+ const res = await client.hub.notifications.testPushIntegration({
23
+ integrationId: flags.integration,
24
+ testToken: flags.token,
25
+ deliveryFamily: flags.family,
26
+ });
27
+ this.print(res);
28
+ return res;
29
+ }
30
+ }
@@ -0,0 +1,16 @@
1
+ import { Args } from '@oclif/core';
2
+ import { BaseCommand } from '../../base.js';
3
+ export default class PushIntegration extends BaseCommand {
4
+ static description = 'Show one push integration';
5
+ static examples = ['<%= config.bin %> push integration 66b2f0a1...'];
6
+ static args = {
7
+ id: Args.string({ required: true, description: 'Integration ID' }),
8
+ };
9
+ async run() {
10
+ const { args, flags } = await this.parse(PushIntegration);
11
+ const client = this.client(flags);
12
+ const res = await client.hub.notifications.getPushIntegration({ id: args.id });
13
+ this.print(res);
14
+ return res;
15
+ }
16
+ }
@@ -0,0 +1,20 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { BaseCommand } from '../../base.js';
3
+ export default class PushIntegrations extends BaseCommand {
4
+ static description = 'List the push integrations set up for this project';
5
+ static examples = ['<%= config.bin %> push integrations --page-size 50'];
6
+ static flags = {
7
+ 'page-size': Flags.integer({ description: 'Records per page' }),
8
+ after: Flags.string({ description: 'Cursor: fetch the page after this item' }),
9
+ };
10
+ async run() {
11
+ const { flags } = await this.parse(PushIntegrations);
12
+ const client = this.client(flags);
13
+ const res = await client.hub.notifications.getPushIntegrations({
14
+ pageSize: flags['page-size'],
15
+ startingAfter: flags.after,
16
+ });
17
+ this.print(res);
18
+ return res;
19
+ }
20
+ }
@@ -0,0 +1,19 @@
1
+ import { Args } from '@oclif/core';
2
+ import { BaseCommand } from '../../base.js';
3
+ export default class PushPreview extends BaseCommand {
4
+ static description = `Render the title, body and subtitle behind a push preview link
5
+
6
+ The hash is the opaque value the backend puts in a preview link — you cannot
7
+ build it yourself, so copy it out of the link.`;
8
+ static examples = ['<%= config.bin %> push preview 8f2a91c4...'];
9
+ static args = {
10
+ hash: Args.string({ required: true, description: 'Preview hash from the preview link' }),
11
+ };
12
+ async run() {
13
+ const { args, flags } = await this.parse(PushPreview);
14
+ const client = this.client(flags);
15
+ const res = await client.hub.notifications.previewPushNotification({ hash: args.hash });
16
+ this.print(res);
17
+ return res;
18
+ }
19
+ }
@@ -0,0 +1,16 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { BaseCommand } from '../../base.js';
3
+ export default class PushSettings extends BaseCommand {
4
+ static description = 'Show the project push settings';
5
+ static examples = ['<%= config.bin %> push settings'];
6
+ static flags = {
7
+ id: Flags.string({ description: 'Settings ID (defaults to the project settings)' }),
8
+ };
9
+ async run() {
10
+ const { flags } = await this.parse(PushSettings);
11
+ const client = this.client(flags);
12
+ const res = await client.hub.notifications.getPushSettings(flags.id ? { id: flags.id } : {});
13
+ this.print(res);
14
+ return res;
15
+ }
16
+ }
@@ -9,7 +9,10 @@ export default class PushUnarchive extends BaseCommand {
9
9
  async run() {
10
10
  const { args, flags } = await this.parse(PushUnarchive);
11
11
  const client = this.client(flags);
12
- const res = await client.hub.notifications.unArchivePushTemplate({ id: args.id });
12
+ // The route token is `{Id}` and the transport fills tokens by exact name,
13
+ // so the request must carry `Id`. The generated type calls the field `id`,
14
+ // which throws NORBIX_MISSING_PATH_PARAM — hence the cast.
15
+ const res = await client.hub.notifications.unArchivePushTemplate({ Id: args.id });
13
16
  this.print(`Template ${args.id} restored.`);
14
17
  return res;
15
18
  }