@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 +10 -0
- package/dist/api.js +48 -0
- package/dist/commands/apps.d.ts +4 -0
- package/dist/commands/apps.js +25 -0
- package/dist/commands/billing.d.ts +2 -0
- package/dist/commands/billing.js +12 -0
- package/dist/commands/domains.d.ts +3 -0
- package/dist/commands/domains.js +17 -0
- package/dist/commands/keys.d.ts +3 -0
- package/dist/commands/keys.js +22 -0
- package/dist/commands/users.d.ts +2 -0
- package/dist/commands/users.js +17 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +95 -0
- package/package.json +26 -0
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,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,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,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,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,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
|
+
}
|
package/dist/index.d.ts
ADDED
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
|
+
}
|