@bounc.ing/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/dist/api.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ interface Config {
2
+ apiKey?: string;
3
+ }
4
+ export declare function loadConfig(): Config;
5
+ export declare function saveConfig(config: Config): void;
6
+ export declare function api(path: string, options?: {
7
+ method?: string;
8
+ body?: unknown;
9
+ }): Promise<unknown>;
10
+ export {};
package/dist/api.js ADDED
@@ -0,0 +1,48 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ const CONFIG_DIR = join(homedir(), '.bouncing');
5
+ const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
6
+ const BASE_URL = 'https://dev.bounc.ing';
7
+ export function loadConfig() {
8
+ try {
9
+ return JSON.parse(readFileSync(CONFIG_FILE, 'utf-8'));
10
+ }
11
+ catch {
12
+ return {};
13
+ }
14
+ }
15
+ export function saveConfig(config) {
16
+ if (!existsSync(CONFIG_DIR))
17
+ mkdirSync(CONFIG_DIR, { recursive: true });
18
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + '\n');
19
+ }
20
+ function getApiKey() {
21
+ const envKey = process.env.BOUNCING_API_KEY;
22
+ if (envKey)
23
+ return envKey;
24
+ const config = loadConfig();
25
+ if (config.apiKey)
26
+ return config.apiKey;
27
+ console.error('No API key found. Set BOUNCING_API_KEY or run: bouncing login');
28
+ process.exit(1);
29
+ }
30
+ export async function api(path, options) {
31
+ const apiKey = getApiKey();
32
+ const headers = {
33
+ 'Authorization': `Bearer ${apiKey}`,
34
+ 'Content-Type': 'application/json',
35
+ };
36
+ const res = await fetch(`${BASE_URL}${path}`, {
37
+ method: options?.method ?? 'GET',
38
+ headers,
39
+ body: options?.body ? JSON.stringify(options.body) : undefined,
40
+ });
41
+ const json = await res.json();
42
+ if (!res.ok) {
43
+ const msg = json.error?.message ?? `API error ${res.status}`;
44
+ console.error(`Error: ${msg}`);
45
+ process.exit(1);
46
+ }
47
+ return json.data;
48
+ }
@@ -0,0 +1,4 @@
1
+ export declare function list(): Promise<void>;
2
+ export declare function create(slug: string, name?: string): Promise<void>;
3
+ export declare function get(slug: string): Promise<void>;
4
+ export declare function del(slug: string): Promise<void>;
@@ -0,0 +1,25 @@
1
+ import { api } from '../api.js';
2
+ export async function list() {
3
+ const apps = await api('/api/apps');
4
+ if (apps.length === 0) {
5
+ console.log('No apps.');
6
+ return;
7
+ }
8
+ console.log('Slug Display Name Status MAU Cap');
9
+ console.log('────────────────── ────────────────────────────── ──────── ───────');
10
+ for (const a of apps) {
11
+ console.log(`${a.slug.padEnd(20)}${a.display_name.padEnd(32)}${a.status.padEnd(10)}${a.mau_cap ?? 'none'}`);
12
+ }
13
+ }
14
+ export async function create(slug, name) {
15
+ const app = await api('/api/apps', { method: 'POST', body: { slug, display_name: name ?? slug } });
16
+ console.log(JSON.stringify(app, null, 2));
17
+ }
18
+ export async function get(slug) {
19
+ const app = await api(`/api/apps/${slug}`);
20
+ console.log(JSON.stringify(app, null, 2));
21
+ }
22
+ export async function del(slug) {
23
+ await api(`/api/apps/${slug}`, { method: 'DELETE' });
24
+ console.log(`Deleted ${slug}`);
25
+ }
@@ -0,0 +1,2 @@
1
+ export declare function show(): Promise<void>;
2
+ export declare function setCap(slug: string, cap: string): Promise<void>;
@@ -0,0 +1,12 @@
1
+ import { api } from '../api.js';
2
+ export async function show() {
3
+ const data = await api('/api/billing');
4
+ console.log(`MAU: ${data.mau}`);
5
+ console.log(`Tier: ${data.tier.label}`);
6
+ console.log(`Status: ${data.subscription.status}`);
7
+ }
8
+ export async function setCap(slug, cap) {
9
+ const capValue = cap === 'none' ? null : parseInt(cap, 10);
10
+ await api(`/api/apps/${slug}/mau-cap`, { method: 'PUT', body: { cap: capValue } });
11
+ console.log(`MAU cap for ${slug}: ${capValue ?? 'none'}`);
12
+ }
@@ -0,0 +1,3 @@
1
+ export declare function set(slug: string, domain: string): Promise<void>;
2
+ export declare function verify(slug: string): Promise<void>;
3
+ export declare function del(slug: string): Promise<void>;
@@ -0,0 +1,17 @@
1
+ import { api } from '../api.js';
2
+ export async function set(slug, domain) {
3
+ const result = await api(`/api/apps/${slug}/domain`, { method: 'POST', body: { domain } });
4
+ console.log(`Custom domain requested: ${result.domain}`);
5
+ console.log(`\nTo verify, add this DNS TXT record:\n`);
6
+ console.log(` Name: _bouncing-verify.${result.domain}`);
7
+ console.log(` Value: ${result.verification_token}\n`);
8
+ console.log(`Then run: bouncing domains verify ${slug}`);
9
+ }
10
+ export async function verify(slug) {
11
+ const result = await api(`/api/apps/${slug}/domain/verify`, { method: 'POST' });
12
+ console.log(result.message);
13
+ }
14
+ export async function del(slug) {
15
+ await api(`/api/apps/${slug}/domain`, { method: 'DELETE' });
16
+ console.log('Custom domain removed.');
17
+ }
@@ -0,0 +1,3 @@
1
+ export declare function list(): Promise<void>;
2
+ export declare function create(name: string): Promise<void>;
3
+ export declare function del(id: string): Promise<void>;
@@ -0,0 +1,22 @@
1
+ import { api } from '../api.js';
2
+ export async function list() {
3
+ const keys = await api('/api/keys');
4
+ if (keys.length === 0) {
5
+ console.log('No API keys.');
6
+ return;
7
+ }
8
+ console.log('ID Name Prefix Last Used');
9
+ console.log('──────────────────────────────── ──────────────────── ──────────────── ─────────');
10
+ for (const k of keys) {
11
+ const used = k.last_used_at ? new Date(k.last_used_at * 1000).toISOString().slice(0, 10) : 'Never';
12
+ console.log(`${k.id.padEnd(34)}${k.name.padEnd(22)}${k.key_prefix.padEnd(18)}${used}`);
13
+ }
14
+ }
15
+ export async function create(name) {
16
+ const key = await api('/api/keys', { method: 'POST', body: { name } });
17
+ console.log(`Key created. Save this — you won't see it again:\n\n ${key.key}\n`);
18
+ }
19
+ export async function del(id) {
20
+ await api(`/api/keys/${id}`, { method: 'DELETE' });
21
+ console.log(`Deleted key ${id}`);
22
+ }
@@ -0,0 +1,2 @@
1
+ export declare function list(slug: string): Promise<void>;
2
+ export declare function del(slug: string, userId: string): Promise<void>;
@@ -0,0 +1,17 @@
1
+ import { api } from '../api.js';
2
+ export async function list(slug) {
3
+ const users = await api(`/api/apps/${slug}/users`);
4
+ if (users.length === 0) {
5
+ console.log('No users.');
6
+ return;
7
+ }
8
+ console.log('ID Email Name');
9
+ console.log('──────────────────────────────── ────────────────────────────── ────────────────');
10
+ for (const u of users) {
11
+ console.log(`${u.id.padEnd(34)}${u.email.padEnd(32)}${u.name ?? ''}`);
12
+ }
13
+ }
14
+ export async function del(slug, userId) {
15
+ await api(`/api/apps/${slug}/users/${userId}`, { method: 'DELETE' });
16
+ console.log(`Deleted user ${userId}`);
17
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env node
2
+ import { saveConfig } from './api.js';
3
+ import * as apps from './commands/apps.js';
4
+ import * as users from './commands/users.js';
5
+ import * as billing from './commands/billing.js';
6
+ import * as keys from './commands/keys.js';
7
+ import * as domains from './commands/domains.js';
8
+ const args = process.argv.slice(2);
9
+ const cmd = args[0];
10
+ const sub = args[1];
11
+ const arg1 = args[2];
12
+ const arg2 = args[3];
13
+ function usage() {
14
+ console.log(`Usage: bouncing <command> [subcommand] [args]
15
+
16
+ Commands:
17
+ login Save API key
18
+ apps list List your apps
19
+ apps create <slug> [name] Create an app
20
+ apps get <slug> Get app details
21
+ apps delete <slug> Delete an app
22
+ users list <slug> List app users
23
+ users delete <slug> <id> Delete a user
24
+ billing Show MAU and tier
25
+ billing set-cap <slug> <cap> Set per-app MAU cap (or "none")
26
+ keys list List API keys
27
+ keys create <name> Create an API key
28
+ keys delete <id> Delete an API key
29
+ domains set <slug> <domain> Request custom domain
30
+ domains verify <slug> Verify domain DNS
31
+ domains delete <slug> Remove custom domain
32
+ `);
33
+ process.exit(1);
34
+ }
35
+ async function main() {
36
+ if (!cmd)
37
+ usage();
38
+ if (cmd === 'login') {
39
+ const readline = await import('node:readline');
40
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
41
+ const key = await new Promise((resolve) => rl.question('API key: ', resolve));
42
+ rl.close();
43
+ saveConfig({ apiKey: key.trim() });
44
+ console.log('Saved to ~/.bouncing/config.json');
45
+ return;
46
+ }
47
+ if (cmd === 'apps') {
48
+ if (sub === 'list')
49
+ return apps.list();
50
+ if (sub === 'create' && arg1)
51
+ return apps.create(arg1, arg2);
52
+ if (sub === 'get' && arg1)
53
+ return apps.get(arg1);
54
+ if (sub === 'delete' && arg1)
55
+ return apps.del(arg1);
56
+ usage();
57
+ }
58
+ if (cmd === 'users') {
59
+ if (sub === 'list' && arg1)
60
+ return users.list(arg1);
61
+ if (sub === 'delete' && arg1 && arg2)
62
+ return users.del(arg1, arg2);
63
+ usage();
64
+ }
65
+ if (cmd === 'billing') {
66
+ if (!sub)
67
+ return billing.show();
68
+ if (sub === 'set-cap' && arg1 && arg2)
69
+ return billing.setCap(arg1, arg2);
70
+ usage();
71
+ }
72
+ if (cmd === 'keys') {
73
+ if (sub === 'list')
74
+ return keys.list();
75
+ if (sub === 'create' && arg1)
76
+ return keys.create(arg1);
77
+ if (sub === 'delete' && arg1)
78
+ return keys.del(arg1);
79
+ usage();
80
+ }
81
+ if (cmd === 'domains') {
82
+ if (sub === 'set' && arg1 && arg2)
83
+ return domains.set(arg1, arg2);
84
+ if (sub === 'verify' && arg1)
85
+ return domains.verify(arg1);
86
+ if (sub === 'delete' && arg1)
87
+ return domains.del(arg1);
88
+ usage();
89
+ }
90
+ usage();
91
+ }
92
+ main().catch((err) => {
93
+ console.error(err.message);
94
+ process.exit(1);
95
+ });
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@bounc.ing/cli",
3
+ "version": "0.1.0",
4
+ "description": "CLI for managing bounc.ing apps",
5
+ "type": "module",
6
+ "bin": {
7
+ "bouncing": "./dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "typecheck": "tsc --noEmit"
12
+ },
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "license": "MIT",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/scttfrdmn/bouncing-managed",
20
+ "directory": "sdk/cli"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "^25.6.0",
24
+ "typescript": "^6.0.2"
25
+ }
26
+ }