@gabaltech/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/bin/gabal.js ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { Command } = require('commander');
5
+
6
+ const configCmd = require('../src/commands/config');
7
+ const usersCmd = require('../src/commands/users');
8
+ const deployCmd = require('../src/commands/deploy');
9
+ const blueprintsCmd = require('../src/commands/blueprints');
10
+ const apiKeysCmd = require('../src/commands/api-keys');
11
+ const loginCmd = require('../src/commands/login');
12
+ const generateCmd = require('../src/commands/generate');
13
+ const reviewCmd = require('../src/commands/review');
14
+ const docsCmd = require('../src/commands/docs');
15
+ const initCmd = require('../src/commands/init');
16
+
17
+ const program = new Command();
18
+
19
+ program
20
+ .name('gabal')
21
+ .description('Gabaltech platform CLI — design, generate, deploy, and manage AWS infrastructure')
22
+ .version('0.1.0')
23
+ .addCommand(loginCmd)
24
+ .addCommand(configCmd)
25
+ .addCommand(usersCmd)
26
+ .addCommand(deployCmd)
27
+ .addCommand(blueprintsCmd)
28
+ .addCommand(apiKeysCmd)
29
+ .addCommand(generateCmd)
30
+ .addCommand(reviewCmd)
31
+ .addCommand(docsCmd)
32
+ .addCommand(initCmd);
33
+
34
+ program.parseAsync(process.argv).catch(err => {
35
+ console.error('Error:', err.message);
36
+ process.exit(1);
37
+ });
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { Command } = require('commander');
5
+
6
+ const configCmd = require('../src/commands/config');
7
+ const usersCmd = require('../src/commands/users');
8
+ const deployCmd = require('../src/commands/deploy');
9
+ const blueprintsCmd = require('../src/commands/blueprints');
10
+ const apiKeysCmd = require('../src/commands/api-keys');
11
+
12
+ const program = new Command();
13
+
14
+ program
15
+ .name('gabaltech')
16
+ .description('Gabaltech platform CLI')
17
+ .version('0.1.0')
18
+ .addCommand(configCmd)
19
+ .addCommand(usersCmd)
20
+ .addCommand(deployCmd)
21
+ .addCommand(blueprintsCmd)
22
+ .addCommand(apiKeysCmd);
23
+
24
+ program.parseAsync(process.argv).catch(err => {
25
+ console.error('Error:', err.message);
26
+ process.exit(1);
27
+ });
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@gabaltech/cli",
3
+ "version": "0.1.0",
4
+ "description": "Gabaltech platform CLI — design, generate, deploy, and manage AWS infrastructure",
5
+ "bin": {
6
+ "gabal": "./bin/gabal.js"
7
+ },
8
+ "type": "commonjs",
9
+ "main": "src/index.js",
10
+ "scripts": {
11
+ "test": "jest --forceExit",
12
+ "lint": "eslint src/ bin/"
13
+ },
14
+ "dependencies": {
15
+ "@aws-sdk/client-cognito-identity-provider": "^3.0.0",
16
+ "chalk": "^4.1.2",
17
+ "cli-table3": "^0.6.5",
18
+ "commander": "^12.0.0",
19
+ "js-yaml": "^4.1.0"
20
+ },
21
+ "devDependencies": {
22
+ "jest": "^29.0.0"
23
+ },
24
+ "engines": {
25
+ "node": ">=18"
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://gitlab.com/gabaltech1/cli.git"
30
+ },
31
+ "license": "UNLICENSED",
32
+ "files": [
33
+ "bin/",
34
+ "src/"
35
+ ],
36
+ "publishConfig": {
37
+ "access": "public"
38
+ }
39
+ }
@@ -0,0 +1,52 @@
1
+ 'use strict';
2
+
3
+ const { Command } = require('commander');
4
+ const idp = require('../idp-client');
5
+ const { printTable } = require('../utils/table');
6
+
7
+ const apiKeysCmd = new Command('api-keys').description('Manage IDP API keys');
8
+
9
+ apiKeysCmd
10
+ .command('list')
11
+ .description('List all API keys (hashes not shown)')
12
+ .option('--json', 'Output raw JSON')
13
+ .action(async (opts) => {
14
+ const { keys } = await idp.listApiKeys();
15
+ if (opts.json) { console.log(JSON.stringify(keys, null, 2)); return; }
16
+ printTable(
17
+ ['ID', 'Name', 'Scopes', 'Created By', 'Last Used'],
18
+ keys.map(k => [
19
+ k.keyId.slice(0, 8),
20
+ k.name,
21
+ (k.scopes ?? []).join(', '),
22
+ k.createdBy,
23
+ k.lastUsed ? new Date(k.lastUsed).toLocaleString() : 'never',
24
+ ]),
25
+ );
26
+ });
27
+
28
+ apiKeysCmd
29
+ .command('create <name>')
30
+ .description('Create a new API key — the key is shown once, save it immediately')
31
+ .option('--scope <scope>', 'Add scope (repeatable)', (v, acc) => [...acc, v], [])
32
+ .action(async (name, opts) => {
33
+ const scopes = opts.scope.length ? opts.scope : ['admin'];
34
+ const result = await idp.createApiKey(name, scopes);
35
+ console.log('\nAPI key created. Save this — it will not be shown again:\n');
36
+ console.log(` ${result.key}\n`);
37
+ console.log(` ID: ${result.keyId}`);
38
+ console.log(` Name: ${result.name}`);
39
+ console.log(` Scopes: ${result.scopes.join(', ')}`);
40
+ console.log('');
41
+ console.log(`Run to store locally: gabaltech config set api-key ${result.key}`);
42
+ });
43
+
44
+ apiKeysCmd
45
+ .command('revoke <keyId>')
46
+ .description('Revoke an API key by ID')
47
+ .action(async (keyId) => {
48
+ await idp.revokeApiKey(keyId);
49
+ console.log(`Revoked: ${keyId}`);
50
+ });
51
+
52
+ module.exports = apiKeysCmd;
@@ -0,0 +1,37 @@
1
+ 'use strict';
2
+
3
+ const { Command } = require('commander');
4
+ const idp = require('../idp-client');
5
+ const { printTable } = require('../utils/table');
6
+
7
+ const blueprintsCmd = new Command('blueprints').description('Manage and provision service blueprints');
8
+
9
+ blueprintsCmd
10
+ .command('list')
11
+ .description('List available blueprint catalogs')
12
+ .option('--json', 'Output raw JSON')
13
+ .action(async (opts) => {
14
+ const { catalogs } = await idp.listCatalogs();
15
+ if (opts.json) { console.log(JSON.stringify(catalogs, null, 2)); return; }
16
+ printTable(
17
+ ['ID', 'Name', 'Description'],
18
+ catalogs.map(c => [c.id, c.name, c.description ?? '']),
19
+ );
20
+ });
21
+
22
+ blueprintsCmd
23
+ .command('provision <catalogId> <serviceName> <environment>')
24
+ .description('Provision a new service from a blueprint (e.g. dynamodb my-table dev)')
25
+ .option('--field <kv>', 'Field value in key=value format (repeatable)', (v, acc) => [...acc, v], [])
26
+ .option('--json', 'Output raw JSON')
27
+ .action(async (catalogId, serviceName, environment, opts) => {
28
+ const fields = Object.fromEntries(opts.field.map(kv => {
29
+ const idx = kv.indexOf('=');
30
+ return [kv.slice(0, idx), kv.slice(idx + 1)];
31
+ }));
32
+ const { deployment } = await idp.provision(catalogId, serviceName, environment, fields);
33
+ if (opts.json) { console.log(JSON.stringify(deployment, null, 2)); return; }
34
+ console.log(`Provisioned: ${deployment?.deploymentId ?? '?'} [${deployment?.status ?? '?'}]`);
35
+ });
36
+
37
+ module.exports = blueprintsCmd;
@@ -0,0 +1,46 @@
1
+ 'use strict';
2
+
3
+ const { Command } = require('commander');
4
+ const cfg = require('../config');
5
+
6
+ const configCmd = new Command('config')
7
+ .description('Manage CLI configuration (~/.gabaltech/config.json)');
8
+
9
+ configCmd
10
+ .command('set <key> <value>')
11
+ .description('Set a config value')
12
+ .action((key, value) => {
13
+ cfg.set(key, value);
14
+ console.log(`Set ${key}`);
15
+ });
16
+
17
+ configCmd
18
+ .command('get <key>')
19
+ .description('Get a config value')
20
+ .action((key) => {
21
+ const v = cfg.get(key);
22
+ if (v === undefined) { console.error(`Key not found: ${key}`); process.exit(1); }
23
+ console.log(v);
24
+ });
25
+
26
+ configCmd
27
+ .command('unset <key>')
28
+ .description('Remove a config value')
29
+ .action((key) => {
30
+ cfg.unset(key);
31
+ console.log(`Unset ${key}`);
32
+ });
33
+
34
+ configCmd
35
+ .command('list')
36
+ .description('Show all config values')
37
+ .action(() => {
38
+ const data = cfg.load();
39
+ if (!Object.keys(data).length) { console.log('No config set.'); return; }
40
+ for (const [k, v] of Object.entries(data)) {
41
+ const display = k === 'api-key' ? v.slice(0, 12) + '...' : v;
42
+ console.log(`${k}=${display}`);
43
+ }
44
+ });
45
+
46
+ module.exports = configCmd;
@@ -0,0 +1,47 @@
1
+ 'use strict';
2
+
3
+ const { Command } = require('commander');
4
+ const idp = require('../idp-client');
5
+ const { printTable } = require('../utils/table');
6
+
7
+ const deployCmd = new Command('deploy').description('Trigger and list deployments');
8
+
9
+ deployCmd
10
+ .command('trigger <service> <environment>')
11
+ .description('Trigger a deployment (e.g. website prod)')
12
+ .option('--version <tag>', 'Image tag / version', 'latest')
13
+ .option('--reason <reason>', 'Reason for deployment', 'CLI trigger')
14
+ .action(async (service, environment, opts) => {
15
+ const { deployment } = await idp.triggerDeploy(service, environment, opts.version, opts.reason);
16
+ console.log(`Deployment queued: ${deployment?.deploymentId ?? '?'} [${deployment?.status ?? '?'}]`);
17
+ });
18
+
19
+ deployCmd
20
+ .command('list')
21
+ .description('List recent deployments')
22
+ .option('--service <service>', 'Filter by service')
23
+ .option('--env <env>', 'Filter by environment')
24
+ .option('--limit <n>', 'Max results', '20')
25
+ .option('--json', 'Output raw JSON')
26
+ .action(async (opts) => {
27
+ const { deployments } = await idp.listDeployments({
28
+ service: opts.service,
29
+ environment: opts.env,
30
+ limit: opts.limit,
31
+ });
32
+ if (opts.json) { console.log(JSON.stringify(deployments, null, 2)); return; }
33
+ printTable(
34
+ ['ID', 'Service', 'Env', 'Version', 'Status', 'By', 'At'],
35
+ deployments.map(d => [
36
+ (d.deploymentId ?? '').slice(0, 8),
37
+ d.service ?? '',
38
+ d.environment ?? '',
39
+ d.version ?? '',
40
+ d.status ?? '',
41
+ d.requestedBy ?? '',
42
+ d.requestedAt ? new Date(d.requestedAt).toLocaleString() : '',
43
+ ]),
44
+ );
45
+ });
46
+
47
+ module.exports = deployCmd;
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ const { Command } = require('commander');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const idp = require('../idp-client');
7
+
8
+ const docsCmd = new Command('docs').description('Generate architecture docs from a design');
9
+
10
+ docsCmd
11
+ .option('--design <designId>', 'Design ID from the IDP')
12
+ .option('--output <dir>', 'Output directory', './gabaltech-docs')
13
+ .action(async (opts) => {
14
+ if (!opts.design) {
15
+ console.error('Error: --design <designId> is required');
16
+ process.exit(1);
17
+ }
18
+
19
+ const { docs } = await idp.generateDocs(opts.design);
20
+
21
+ const outDir = path.resolve(opts.output);
22
+ fs.mkdirSync(outDir, { recursive: true });
23
+ fs.writeFileSync(path.join(outDir, 'architecture.md'), docs.architecture ?? '', 'utf8');
24
+ fs.writeFileSync(path.join(outDir, 'runbook.md'), docs.runbook ?? '', 'utf8');
25
+
26
+ console.log(`Documentation generated → ${opts.output}/`);
27
+ });
28
+
29
+ module.exports = docsCmd;
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ const { Command } = require('commander');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const idp = require('../idp-client');
7
+
8
+ const generateCmd = new Command('generate').description('Generate Terraform from a design or prompt');
9
+
10
+ generateCmd
11
+ .option('--design <designId>', 'Design ID from the IDP')
12
+ .option('--prompt <text>', 'Describe your architecture in plain text')
13
+ .option('--output <dir>', 'Output directory', './gabaltech-infra')
14
+ .action(async (opts) => {
15
+ if (!opts.design && !opts.prompt) {
16
+ console.error('Error: provide --design <id> or --prompt "<text>"');
17
+ process.exit(1);
18
+ }
19
+
20
+ let payload;
21
+ if (opts.design) {
22
+ const design = await idp.getDesign(opts.design);
23
+ payload = { design };
24
+ } else {
25
+ payload = { prompt: opts.prompt };
26
+ }
27
+
28
+ const { terraform } = await idp.generateTerraform(payload);
29
+
30
+ const outDir = path.resolve(opts.output);
31
+ fs.mkdirSync(outDir, { recursive: true });
32
+ fs.writeFileSync(path.join(outDir, 'main.tf'), terraform.main ?? '', 'utf8');
33
+ fs.writeFileSync(path.join(outDir, 'variables.tf'), terraform.variables ?? '', 'utf8');
34
+ fs.writeFileSync(path.join(outDir, 'outputs.tf'), terraform.outputs ?? '', 'utf8');
35
+
36
+ console.log(`Terraform generated → ${opts.output}/`);
37
+ });
38
+
39
+ module.exports = generateCmd;
@@ -0,0 +1,27 @@
1
+ 'use strict';
2
+
3
+ const { Command } = require('commander');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const cfg = require('../config');
7
+
8
+ const initCmd = new Command('init').description('Scaffold a new Gabaltech project');
9
+
10
+ initCmd
11
+ .option('--name <project>', 'Project name', 'my-project')
12
+ .option('--type <type>', 'Project type: serverless|container|platform', 'container')
13
+ .action((opts) => {
14
+ const root = path.resolve(opts.name);
15
+ fs.mkdirSync(path.join(root, 'infra'), { recursive: true });
16
+ fs.mkdirSync(path.join(root, 'docs'), { recursive: true });
17
+ fs.writeFileSync(path.join(root, 'infra', '.gitkeep'), '', 'utf8');
18
+ fs.writeFileSync(path.join(root, 'docs', '.gitkeep'), '', 'utf8');
19
+ fs.writeFileSync(
20
+ path.join(root, 'gabaltech.json'),
21
+ JSON.stringify({ name: opts.name, type: opts.type, idpUrl: cfg.getIdpUrl() }, null, 2) + '\n',
22
+ 'utf8',
23
+ );
24
+ console.log(`Project scaffolded → ./${opts.name}/`);
25
+ });
26
+
27
+ module.exports = initCmd;
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ const { Command } = require('commander');
4
+ const cfg = require('../config');
5
+
6
+ const loginCmd = new Command('login').description('Authenticate with the Gabaltech IDP');
7
+
8
+ loginCmd
9
+ .option('--token <api-key>', 'API key to save')
10
+ .option('--url <url>', 'IDP URL (default: https://idp.gabaltech.co.uk)')
11
+ .action((opts) => {
12
+ if (opts.url) {
13
+ cfg.set('idp-url', opts.url);
14
+ }
15
+ if (opts.token) {
16
+ cfg.set('api-key', opts.token);
17
+ console.log('Logged in. API key saved.');
18
+ return;
19
+ }
20
+ console.log('To authenticate:');
21
+ console.log(' 1. Open https://idp.gabaltech.co.uk/admin/api-keys');
22
+ console.log(' 2. Create a new API key with scope: cli');
23
+ console.log(' 3. Run: gabal login --token <your-api-key>');
24
+ });
25
+
26
+ module.exports = loginCmd;
@@ -0,0 +1,41 @@
1
+ 'use strict';
2
+
3
+ const { Command } = require('commander');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const idp = require('../idp-client');
7
+
8
+ const SEVERITY_ORDER = { CRITICAL: 0, WARNING: 1, INFO: 2 };
9
+
10
+ const reviewCmd = new Command('review').description('AI review of Terraform files');
11
+
12
+ reviewCmd
13
+ .option('--file <path>', 'Single .tf file to review')
14
+ .option('--dir <path>', 'Directory of .tf files to review')
15
+ .action(async (opts) => {
16
+ if (!opts.file && !opts.dir) {
17
+ console.error('Error: provide --file <path> or --dir <path>');
18
+ process.exit(1);
19
+ }
20
+
21
+ let content;
22
+ if (opts.file) {
23
+ content = fs.readFileSync(path.resolve(opts.file), 'utf8');
24
+ } else {
25
+ const dir = path.resolve(opts.dir);
26
+ const files = fs.readdirSync(dir).filter(f => f.endsWith('.tf'));
27
+ if (!files.length) { console.error('No .tf files found in', dir); process.exit(1); }
28
+ content = files.map(f => fs.readFileSync(path.join(dir, f), 'utf8')).join('\n\n');
29
+ }
30
+
31
+ const { findings } = await idp.reviewTerraform(content);
32
+
33
+ if (!findings?.length) { console.log('No findings.'); return; }
34
+
35
+ findings
36
+ .slice()
37
+ .sort((a, b) => (SEVERITY_ORDER[a.severity] ?? 99) - (SEVERITY_ORDER[b.severity] ?? 99))
38
+ .forEach(f => console.log(`[${f.severity ?? 'INFO'}] ${f.message ?? f}`));
39
+ });
40
+
41
+ module.exports = reviewCmd;
@@ -0,0 +1,84 @@
1
+ 'use strict';
2
+
3
+ const { Command } = require('commander');
4
+ const fs = require('fs');
5
+ const yaml = require('js-yaml');
6
+ const idp = require('../idp-client');
7
+ const { printTable } = require('../utils/table');
8
+
9
+ const usersCmd = new Command('users').description('Manage Cognito users via IDP API');
10
+
11
+ usersCmd
12
+ .command('list')
13
+ .description('List all users')
14
+ .option('--json', 'Output raw JSON')
15
+ .action(async (opts) => {
16
+ const { users } = await idp.listUsers();
17
+ if (opts.json) { console.log(JSON.stringify(users, null, 2)); return; }
18
+ printTable(
19
+ ['Email', 'Name', 'Status', 'Enabled'],
20
+ users.map(u => [u.email, u.name || '', u.status, u.enabled ? 'yes' : 'no']),
21
+ );
22
+ });
23
+
24
+ usersCmd
25
+ .command('invite <email>')
26
+ .description('Invite a new user')
27
+ .option('--name <name>', 'Display name')
28
+ .option('--admin', 'Make user an approver')
29
+ .action(async (email, opts) => {
30
+ const result = await idp.inviteUser(email, opts.name, !!opts.admin);
31
+ console.log(`Invited: ${result.user?.username ?? email}`);
32
+ });
33
+
34
+ usersCmd
35
+ .command('delete <username>')
36
+ .description('Delete a user by username')
37
+ .action(async (username) => {
38
+ await idp.deleteUser(username);
39
+ console.log(`Deleted: ${username}`);
40
+ });
41
+
42
+ usersCmd
43
+ .command('set-admin <username>')
44
+ .description('Grant or revoke admin (approver) role')
45
+ .option('--revoke', 'Remove admin role instead')
46
+ .action(async (username, opts) => {
47
+ const is_admin = !opts.revoke;
48
+ await idp.patchUser(username, { is_admin });
49
+ console.log(`${is_admin ? 'Granted' : 'Revoked'} admin for ${username}`);
50
+ });
51
+
52
+ usersCmd
53
+ .command('sync')
54
+ .description('Sync users from a YAML or JSON file')
55
+ .requiredOption('--file <path>', 'Path to users file')
56
+ .option('--dry-run', 'Show what would happen without making changes')
57
+ .option('--json', 'Output raw JSON result')
58
+ .action(async (opts) => {
59
+ const raw = fs.readFileSync(opts.file, 'utf8');
60
+ const data = opts.file.endsWith('.json') ? JSON.parse(raw) : yaml.load(raw);
61
+ const users = Array.isArray(data) ? data : data.users;
62
+
63
+ if (!users?.length) { console.error('No users found in file'); process.exit(1); }
64
+
65
+ if (opts.dryRun) {
66
+ console.log(`Dry run — would sync ${users.length} users:`);
67
+ for (const u of users) console.log(` ${u.email}${u.is_admin ? ' [admin]' : ''}`);
68
+ return;
69
+ }
70
+
71
+ const result = await idp.syncUsers(users);
72
+
73
+ if (opts.json) { console.log(JSON.stringify(result, null, 2)); return; }
74
+ if (result.invited.length) console.log(`Invited (${result.invited.length}):`, result.invited.map(u => u.email).join(', '));
75
+ if (result.updated.length) console.log(`Updated (${result.updated.length}):`, result.updated.map(u => u.email).join(', '));
76
+ if (result.skipped.length) console.log(`Skipped (${result.skipped.length}):`, result.skipped.map(u => u.email).join(', '));
77
+ if (result.errors.length) {
78
+ console.error(`Errors (${result.errors.length}):`);
79
+ for (const e of result.errors) console.error(` ${e.email}: ${e.error}`);
80
+ process.exitCode = 1;
81
+ }
82
+ });
83
+
84
+ module.exports = usersCmd;
package/src/config.js ADDED
@@ -0,0 +1,55 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+
7
+ const CONFIG_DIR = path.join(os.homedir(), '.gabaltech');
8
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
9
+
10
+ function load() {
11
+ try {
12
+ return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
13
+ } catch {
14
+ return {};
15
+ }
16
+ }
17
+
18
+ function save(data) {
19
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
20
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(data, null, 2), 'utf8');
21
+ }
22
+
23
+ function get(key) {
24
+ return load()[key];
25
+ }
26
+
27
+ function set(key, value) {
28
+ const cfg = load();
29
+ cfg[key] = value;
30
+ save(cfg);
31
+ }
32
+
33
+ function unset(key) {
34
+ const cfg = load();
35
+ delete cfg[key];
36
+ save(cfg);
37
+ }
38
+
39
+ function getIdpUrl() {
40
+ return process.env.GABALTECH_IDP_URL || get('idp-url') || 'https://idp.gabaltech.co.uk';
41
+ }
42
+
43
+ function getApiKey() {
44
+ return process.env.GABALTECH_API_KEY || get('api-key');
45
+ }
46
+
47
+ function getAwsProfile() {
48
+ return process.env.AWS_PROFILE || get('aws-profile') || 'gabaltech';
49
+ }
50
+
51
+ function getCognitoPoolId() {
52
+ return process.env.COGNITO_USER_POOL_ID || get('cognito-pool-id');
53
+ }
54
+
55
+ module.exports = { load, save, get, set, unset, getIdpUrl, getApiKey, getAwsProfile, getCognitoPoolId };
@@ -0,0 +1,68 @@
1
+ 'use strict';
2
+
3
+ const cfg = require('./config');
4
+
5
+ async function request(method, path, body) {
6
+ const apiKey = cfg.getApiKey();
7
+ if (!apiKey) throw new Error('No API key configured. Run: gabaltech config set api-key <key>');
8
+
9
+ const url = `${cfg.getIdpUrl()}${path}`;
10
+ const headers = {
11
+ 'Authorization': `Bearer ${apiKey}`,
12
+ 'Content-Type': 'application/json',
13
+ 'Accept': 'application/json',
14
+ };
15
+
16
+ const res = await fetch(url, {
17
+ method,
18
+ headers,
19
+ body: body ? JSON.stringify(body) : undefined,
20
+ });
21
+
22
+ const text = await res.text();
23
+ let data;
24
+ try { data = JSON.parse(text); } catch { data = { raw: text }; }
25
+
26
+ if (!res.ok) {
27
+ throw new Error(data.error || `HTTP ${res.status}: ${text.slice(0, 200)}`);
28
+ }
29
+ return data;
30
+ }
31
+
32
+ const idp = {
33
+ // Users
34
+ listUsers: () => request('GET', '/api/admin/users'),
35
+ inviteUser: (email, name, is_admin) => request('POST', '/api/admin/users/invite', { email, name, is_admin }),
36
+ syncUsers: (users) => request('POST', '/api/admin/users/sync', { users }),
37
+ deleteUser: (username) => request('DELETE', `/api/admin/users/${encodeURIComponent(username)}`),
38
+ patchUser: (username, patch) => request('PATCH', `/api/admin/users/${encodeURIComponent(username)}`, patch),
39
+
40
+ // API keys
41
+ listApiKeys: () => request('GET', '/api/admin/api-keys'),
42
+ createApiKey: (name, scopes) => request('POST', '/api/admin/api-keys', { name, scopes }),
43
+ revokeApiKey: (keyId) => request('DELETE', `/api/admin/api-keys/${keyId}`),
44
+
45
+ // Deployments
46
+ listDeployments: (opts = {}) => {
47
+ const qs = new URLSearchParams(Object.entries(opts).filter(([, v]) => v)).toString();
48
+ return request('GET', `/api/deployments${qs ? '?' + qs : ''}`);
49
+ },
50
+ triggerDeploy: (service, environment, version, reason) =>
51
+ request('POST', '/api/deployments', { service, environment, version, reason }),
52
+
53
+ // Blueprints
54
+ listCatalogs: () => request('GET', '/api/catalogs'),
55
+ provision: (catalogId, serviceName, environment, fields) =>
56
+ request('POST', '/api/blueprints/provision', { catalogId, serviceName, environment, fields }),
57
+
58
+ // Auth
59
+ verifyApiKey: () => request('GET', '/api/auth/verify'),
60
+
61
+ // Designs + generation
62
+ getDesign: (id) => request('GET', `/api/designs/${encodeURIComponent(id)}`),
63
+ generateTerraform: (payload) => request('POST', '/api/generate/terraform', payload),
64
+ reviewTerraform: (content) => request('POST', '/api/review/terraform', { content }),
65
+ generateDocs: (designId) => request('POST', '/api/generate/docs', { designId }),
66
+ };
67
+
68
+ module.exports = idp;
@@ -0,0 +1,11 @@
1
+ 'use strict';
2
+
3
+ const Table = require('cli-table3');
4
+
5
+ function printTable(headers, rows) {
6
+ const t = new Table({ head: headers, style: { head: ['cyan'] } });
7
+ for (const row of rows) t.push(row);
8
+ console.log(t.toString());
9
+ }
10
+
11
+ module.exports = { printTable };