diff --git a/bin/ccc-keygen.mjs b/bin/ccc-keygen.mjs new file mode 100755 index 0000000..a7d8dfb --- /dev/null +++ b/bin/ccc-keygen.mjs @@ -0,0 +1,85 @@ +#!/usr/bin/env node +import { randomBytes } from 'crypto'; +import { readFileSync, writeFileSync, existsSync } from 'fs'; + +function get_arg(argv, flag) { + const i = argv.indexOf(flag); + return i !== -1 ? argv[i + 1] : null; +} + +function has_flag(argv, flag) { + return argv.includes(flag); +} + +function parse_names(value, flag) { + if (!value) { + console.error(`${flag} requires a comma-separated list of usernames`); + process.exit(1); + } + return value.split(',').map(s => s.trim()).filter(Boolean); +} + +function generate_secret() { + return randomBytes(32).toString('hex'); +} + +function read_secrets_file(input_path) { + if (!existsSync(input_path)) { + console.error(`Secrets file not found: ${input_path}`); + process.exit(1); + } + try { + return JSON.parse(readFileSync(input_path, 'utf8')); + } catch (err) { + console.error(`Cannot read secrets file: ${err.message}`); + process.exit(1); + } +} + +function write_secrets_file(output_path, data) { + writeFileSync(output_path, JSON.stringify(data, null, '\t') + '\n', 'utf8'); + console.log(`Written: ${output_path}`); +} + +const argv = process.argv; +const create_arg = get_arg(argv, '--create'); +const filter_arg = get_arg(argv, '--filter'); +const input_arg = get_arg(argv, '--input') || 'secrets.json'; +const output_arg = get_arg(argv, '--output'); + +if (!create_arg && !filter_arg) { + console.error( + 'Usage:\n' + + ' ccc-keygen --create [--output secrets.json]\n' + + ' ccc-keygen --filter [--input secrets.json] [--output filtered-secrets.json]' + ); + process.exit(1); +} + +if (create_arg) { + const names = parse_names(create_arg, '--create'); + const output_path = output_arg || 'secrets.json'; + const users = {}; + for (const name of names) { + users[name] = { secret: generate_secret(), canApprove: [] }; + } + write_secrets_file(output_path, { users }); + console.log(`Created users: ${names.join(', ')}`); + console.log('Edit canApprove lists to configure approval permissions.'); +} + +if (filter_arg) { + const names = parse_names(filter_arg, '--filter'); + const output_path = output_arg || 'filtered-secrets.json'; + const source = read_secrets_file(input_arg); + const users = {}; + for (const name of names) { + if (!source.users?.[name]) { + console.error(`User '${name}' not found in ${input_arg}`); + process.exit(1); + } + users[name] = source.users[name]; + } + write_secrets_file(output_path, { users }); + console.log(`Filtered users: ${names.join(', ')}`); +} diff --git a/package.json b/package.json index 92283f9..3454e4d 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "bin": { "ccc-server": "bin/ccc-server.mjs", "ccc-client": "bin/ccc-client.mjs", - "ccc-queue": "bin/ccc-queue.mjs" + "ccc-queue": "bin/ccc-queue.mjs", + "ccc-keygen": "bin/ccc-keygen.mjs" }, "dependencies": { "blessed": "^0.1.81", diff --git a/secrets.example.json b/secrets.example.json index bb31364..f93c8b7 100644 --- a/secrets.example.json +++ b/secrets.example.json @@ -1,6 +1,6 @@ { "users": { - "agent": { "secret": "change-me-agent", "canApprove": [] }, - "user": { "secret": "change-me-user", "canApprove": ["agent"] } + "": { "secret": "", "canApprove": [] }, + "": { "secret": "", "canApprove": [""] } } }