secretcarousel 1.0.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/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ SecretCarousel Software License
2
+
3
+ Copyright (c) 2026 Tyga.Cloud Ltd
4
+ Company number 14643275, England and Wales
5
+
6
+ Permission is granted to use this software in connection with the
7
+ SecretCarousel service (https://secretcarousel.com) subject to the
8
+ following conditions:
9
+
10
+ 1. You may install, copy, and use this software solely to interact with
11
+ the SecretCarousel API using a valid API key (prefix: sc_).
12
+
13
+ 2. You may NOT modify, reverse-engineer, decompile, or create derivative
14
+ works of this software.
15
+
16
+ 3. You may NOT redistribute this software outside of the npm package
17
+ registry or the official SecretCarousel documentation.
18
+
19
+ 4. This software is provided "AS IS" without warranty of any kind,
20
+ express or implied, including but not limited to warranties of
21
+ merchantability, fitness for a particular purpose, and noninfringement.
22
+
23
+ 5. In no event shall Tyga.Cloud Ltd be liable for any claim, damages, or
24
+ other liability arising from the use of this software.
25
+
26
+ Terms of Service: https://secretcarousel.com/terms
27
+ Privacy Policy: https://secretcarousel.com/privacy
28
+
29
+ "SecretCarousel" is a trademark of Tyga.Cloud Ltd.
package/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # secretcarousel
2
+
3
+ The agent-native secret vault. Store, rotate, and share secrets from any AI coding agent.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g secretcarousel
9
+ ```
10
+
11
+ Or run directly:
12
+
13
+ ```bash
14
+ npx secretcarousel signup --tenant my-project
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ```bash
20
+ # Self-provision (zero friction, instant API key)
21
+ sc signup --tenant my-project
22
+
23
+ # Store a secret (AES-256-GCM encrypted at rest)
24
+ sc secrets:create --name DB_URL --value postgres://user:pass@host/db
25
+
26
+ # Retrieve (decrypted on demand, access logged)
27
+ sc secrets:get sec-abc123
28
+
29
+ # Export as .env
30
+ sc env:export --environment production > .env
31
+
32
+ # Rotate immediately
33
+ sc rotate sec-abc123
34
+ ```
35
+
36
+ ## Commands
37
+
38
+ | Command | Description |
39
+ |---------|-------------|
40
+ | `signup` | Self-provision a tenant and get API key |
41
+ | `config` | Set apiKey or baseUrl |
42
+ | `secrets:list` | List secrets |
43
+ | `secrets:get` | Get a secret value (decrypted) |
44
+ | `secrets:create` | Create a secret |
45
+ | `secrets:delete` | Delete a secret |
46
+ | `env:export` | Export secrets as .env file |
47
+ | `env:promote` | Promote secrets between environments |
48
+ | `audit:export` | Export audit logs (CSV/JSON) |
49
+ | `backup:create` | Create encrypted backup |
50
+ | `rotate` | Rotate a secret immediately |
51
+
52
+ ## Configuration
53
+
54
+ Environment variables (recommended for agents):
55
+
56
+ ```bash
57
+ export SC_API_KEY=sc_free_my_project_a1b2c3...
58
+ export SC_BASE_URL=https://secretcarousel.com # optional, this is the default
59
+ ```
60
+
61
+ Or save permanently:
62
+
63
+ ```bash
64
+ sc config apiKey sc_free_my_project_a1b2c3...
65
+ sc config baseUrl https://secretcarousel.com
66
+ ```
67
+
68
+ Config file: `~/.secretcarousel/config.json`
69
+
70
+ ## Features
71
+
72
+ - **Agent-native** — self-signup via API, no dashboard required
73
+ - **AES-256-GCM** — every secret encrypted at rest with unique keys
74
+ - **Zero dependencies** — uses Node.js 18+ built-in fetch
75
+ - **Versioning** — automatic version history on every update
76
+ - **Rotation** — cron-based or on-demand with webhook notifications
77
+ - **Sharing** — time-limited, view-limited share links with passphrase protection
78
+ - **Audit trail** — every operation logged with full context
79
+ - **Claim tokens** — cross-agent secret exchange via Buggazi contracts
80
+
81
+ ## Links
82
+
83
+ - Quick Start: https://secretcarousel.com/api
84
+ - Pricing: https://secretcarousel.com/#pricing
85
+ - Status: https://secretcarousel.com/health
86
+
87
+ ## License
88
+
89
+ Proprietary — Tyga.Cloud Ltd. See LICENSE file.
package/bin/cli.js ADDED
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+
3
+ const config = require('../lib/config');
4
+
5
+ const COMMANDS = {
6
+ 'signup': '../commands/signup',
7
+ 'config': '../commands/config',
8
+ 'secrets:list': '../commands/secrets-list',
9
+ 'secrets:get': '../commands/secrets-get',
10
+ 'secrets:create': '../commands/secrets-create',
11
+ 'secrets:delete': '../commands/secrets-delete',
12
+ 'env:export': '../commands/env-export',
13
+ 'env:promote': '../commands/env-promote',
14
+ 'audit:export': '../commands/audit-export',
15
+ 'backup:create': '../commands/backup-create',
16
+ 'rotate': '../commands/rotate'
17
+ };
18
+
19
+ function usage() {
20
+ console.log(`
21
+ SecretCarousel CLI v1.0.0 — The Agent-Native Secret Vault
22
+
23
+ Usage: sc <command> [options]
24
+
25
+ Getting Started:
26
+ signup Self-provision a tenant (zero friction)
27
+ config Set apiKey or baseUrl
28
+
29
+ Secrets:
30
+ secrets:list List secrets
31
+ secrets:get Get a secret value (decrypted)
32
+ secrets:create Create a secret (AES-256-GCM encrypted)
33
+ secrets:delete Delete a secret
34
+
35
+ Environment:
36
+ env:export Export secrets as .env file
37
+ env:promote Promote secrets between environments
38
+
39
+ Operations:
40
+ audit:export Export audit logs (CSV/JSON)
41
+ backup:create Create encrypted backup
42
+ rotate Rotate a secret immediately
43
+
44
+ Options:
45
+ --help Show this help message
46
+
47
+ Environment Variables:
48
+ SC_API_KEY API key (overrides config file)
49
+ SC_BASE_URL Base URL (default: https://secretcarousel.com)
50
+
51
+ Quick Start:
52
+ $ npx secretcarousel signup --tenant my-project
53
+ $ sc secrets:create --name DB_URL --value postgres://...
54
+ $ sc secrets:get sec-abc123
55
+
56
+ Config: ${config.CONFIG_FILE}
57
+ Docs: https://secretcarousel.com/api
58
+ `);
59
+ }
60
+
61
+ async function main() {
62
+ const args = process.argv.slice(2);
63
+
64
+ if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
65
+ usage();
66
+ process.exit(0);
67
+ }
68
+
69
+ const command = args[0];
70
+
71
+ if (!COMMANDS[command]) {
72
+ console.error(`Unknown command: ${command}\n`);
73
+ usage();
74
+ process.exit(1);
75
+ }
76
+
77
+ const handler = require(COMMANDS[command]);
78
+ try {
79
+ await handler(args.slice(1));
80
+ } catch (error) {
81
+ console.error(`Error: ${error.message}`);
82
+ process.exit(1);
83
+ }
84
+ }
85
+
86
+ main();
@@ -0,0 +1,12 @@
1
+ const { request } = require('../lib/api');
2
+
3
+ module.exports = async function (args) {
4
+ const query = {};
5
+ for (let i = 0; i < args.length; i += 2) {
6
+ if (args[i] && args[i + 1]) query[args[i].replace(/^--/, '')] = args[i + 1];
7
+ }
8
+
9
+ const qs = new URLSearchParams({ format: 'csv', ...query }).toString();
10
+ const result = await request('GET', '/api/v1/audit/export?' + qs);
11
+ console.log(typeof result === 'string' ? result : JSON.stringify(result, null, 2));
12
+ };
@@ -0,0 +1,13 @@
1
+ const { request } = require('../lib/api');
2
+
3
+ module.exports = async function (args) {
4
+ const data = {};
5
+ for (let i = 0; i < args.length; i += 2) {
6
+ if (args[i] && args[i + 1]) data[args[i].replace(/^--/, '')] = args[i + 1];
7
+ }
8
+
9
+ if (!data.name) data.name = `backup-${new Date().toISOString().slice(0, 10)}`;
10
+
11
+ const result = await request('POST', '/api/v1/backup', data);
12
+ console.log(JSON.stringify(result, null, 2));
13
+ };
@@ -0,0 +1,20 @@
1
+ const config = require('../lib/config');
2
+
3
+ module.exports = async function (args) {
4
+ if (args.length < 2) {
5
+ const cfg = config.load();
6
+ console.log(JSON.stringify({ ...cfg, apiKey: cfg.apiKey ? cfg.apiKey.slice(0, 12) + '...' : '(not set)' }, null, 2));
7
+ return;
8
+ }
9
+
10
+ const key = args[0];
11
+ const value = args[1];
12
+
13
+ if (!['apiKey', 'baseUrl'].includes(key)) {
14
+ console.error(`Unknown config key: ${key}. Valid: apiKey, baseUrl`);
15
+ process.exit(1);
16
+ }
17
+
18
+ config.set(key, value);
19
+ console.log(`Set ${key} = ${key === 'apiKey' ? value.slice(0, 12) + '...' : value}`);
20
+ };
@@ -0,0 +1,27 @@
1
+ const { request } = require('../lib/api');
2
+
3
+ module.exports = async function (args) {
4
+ const query = {};
5
+ for (let i = 0; i < args.length; i += 2) {
6
+ if (args[i] && args[i + 1]) query[args[i].replace(/^--/, '')] = args[i + 1];
7
+ }
8
+
9
+ if (!query.environment) {
10
+ console.error('Usage: sc env:export --environment <development|staging|production>');
11
+ process.exit(1);
12
+ }
13
+
14
+ const result = await request('GET', '/api/v1/secrets?environment=' + query.environment + '&limit=500');
15
+ const secrets = result.secrets || result;
16
+
17
+ // Output as .env format
18
+ for (const s of secrets) {
19
+ if (s.value) {
20
+ console.log(`${s.name}=${s.value}`);
21
+ } else {
22
+ // Need to fetch each secret value individually
23
+ const full = await request('GET', '/api/v1/secrets/' + (s.secretId || s._id));
24
+ if (full.value) console.log(`${s.name}=${full.value}`);
25
+ }
26
+ }
27
+ };
@@ -0,0 +1,36 @@
1
+ const { request } = require('../lib/api');
2
+
3
+ module.exports = async function (args) {
4
+ const data = {};
5
+ for (let i = 0; i < args.length; i += 2) {
6
+ if (args[i] && args[i + 1]) data[args[i].replace(/^--/, '')] = args[i + 1];
7
+ }
8
+
9
+ if (!data.from || !data.to) {
10
+ console.error('Usage: sc env:promote --from staging --to production');
11
+ process.exit(1);
12
+ }
13
+
14
+ // List secrets from source environment, create in target
15
+ const source = await request('GET', '/api/v1/secrets?environment=' + data.from + '&limit=500');
16
+ const secrets = source.secrets || source;
17
+ let promoted = 0;
18
+
19
+ for (const s of secrets) {
20
+ try {
21
+ const full = await request('GET', '/api/v1/secrets/' + (s.secretId || s._id));
22
+ await request('POST', '/api/v1/secrets', {
23
+ name: s.name,
24
+ value: full.value,
25
+ secretType: s.secretType,
26
+ environment: data.to,
27
+ tags: s.tags,
28
+ });
29
+ promoted++;
30
+ } catch (e) {
31
+ console.error(` Skip: ${s.name} — ${e.message}`);
32
+ }
33
+ }
34
+
35
+ console.log(`Promoted ${promoted}/${secrets.length} secrets from ${data.from} → ${data.to}`);
36
+ };
@@ -0,0 +1,7 @@
1
+ const { request } = require('../lib/api');
2
+
3
+ module.exports = async function (args) {
4
+ if (!args[0]) { console.error('Usage: sc rotate <secretId>'); process.exit(1); }
5
+ const result = await request('POST', '/api/v1/rotation/' + args[0] + '/rotate-now');
6
+ console.log(JSON.stringify(result, null, 2));
7
+ };
@@ -0,0 +1,16 @@
1
+ const { request } = require('../lib/api');
2
+
3
+ module.exports = async function (args) {
4
+ const data = {};
5
+ for (let i = 0; i < args.length; i += 2) {
6
+ if (args[i] && args[i + 1]) data[args[i].replace(/^--/, '')] = args[i + 1];
7
+ }
8
+
9
+ if (!data.name || !data.value) {
10
+ console.error('Usage: sc secrets:create --name NAME --value VALUE [--secretType TYPE] [--environment ENV]');
11
+ process.exit(1);
12
+ }
13
+
14
+ const result = await request('POST', '/api/v1/secrets', data);
15
+ console.log(JSON.stringify(result, null, 2));
16
+ };
@@ -0,0 +1,7 @@
1
+ const { request } = require('../lib/api');
2
+
3
+ module.exports = async function (args) {
4
+ if (!args[0]) { console.error('Usage: sc secrets:delete <secretId>'); process.exit(1); }
5
+ const result = await request('DELETE', '/api/v1/secrets/' + args[0]);
6
+ console.log(JSON.stringify(result, null, 2));
7
+ };
@@ -0,0 +1,7 @@
1
+ const { request } = require('../lib/api');
2
+
3
+ module.exports = async function (args) {
4
+ if (!args[0]) { console.error('Usage: sc secrets:get <secretId>'); process.exit(1); }
5
+ const result = await request('GET', '/api/v1/secrets/' + args[0]);
6
+ console.log(JSON.stringify(result, null, 2));
7
+ };
@@ -0,0 +1,12 @@
1
+ const { request } = require('../lib/api');
2
+
3
+ module.exports = async function (args) {
4
+ const query = {};
5
+ for (let i = 0; i < args.length; i += 2) {
6
+ if (args[i] && args[i + 1]) query[args[i].replace(/^--/, '')] = args[i + 1];
7
+ }
8
+
9
+ const qs = new URLSearchParams(query).toString();
10
+ const result = await request('GET', '/api/v1/secrets' + (qs ? '?' + qs : ''));
11
+ console.log(JSON.stringify(result, null, 2));
12
+ };
@@ -0,0 +1,32 @@
1
+ const { signup } = require('../lib/api');
2
+ const config = require('../lib/config');
3
+
4
+ module.exports = async function (args) {
5
+ const data = {};
6
+ for (let i = 0; i < args.length; i += 2) {
7
+ if (args[i] && args[i + 1]) data[args[i].replace(/^--/, '')] = args[i + 1];
8
+ }
9
+
10
+ if (!data.tenant) {
11
+ console.error('Usage: sc signup --tenant <project-name>\n');
12
+ console.error('Example: sc signup --tenant my-backend');
13
+ process.exit(1);
14
+ }
15
+
16
+ const result = await signup(data.tenant);
17
+
18
+ if (result.error) {
19
+ console.error(`Error: ${result.message || result.error}`);
20
+ process.exit(1);
21
+ }
22
+
23
+ // Auto-save the API key to config
24
+ config.set('apiKey', result.apiKey);
25
+
26
+ console.log(`\nTenant "${result.tenantId}" created!\n`);
27
+ console.log(`API Key: ${result.apiKey}`);
28
+ console.log(`Plan: ${result.plan}`);
29
+ console.log(`Limits: ${JSON.stringify(result.limits)}\n`);
30
+ console.log(`Key saved to ${config.CONFIG_FILE}`);
31
+ console.log(`\nNext: sc secrets:create --name DB_PASSWORD --value s3cur3!`);
32
+ };
package/lib/api.js ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Self-contained HTTP client for SecretCarousel API
3
+ * Zero dependencies — uses Node.js built-in fetch (18+)
4
+ */
5
+
6
+ const config = require('./config');
7
+
8
+ async function request(method, path, body) {
9
+ const cfg = config.load();
10
+ if (!cfg.apiKey) {
11
+ console.error('No API key configured.\n');
12
+ console.error('Set via environment variable:');
13
+ console.error(' export SC_API_KEY=sc_free_...\n');
14
+ console.error('Or configure permanently:');
15
+ console.error(' sc config apiKey sc_free_...\n');
16
+ console.error('Or self-signup for a new key:');
17
+ console.error(' sc signup --tenant my-project');
18
+ process.exit(1);
19
+ }
20
+
21
+ const url = cfg.baseUrl.replace(/\/+$/, '') + path;
22
+ const opts = {
23
+ method,
24
+ headers: {
25
+ 'X-API-Key': cfg.apiKey,
26
+ 'Content-Type': 'application/json',
27
+ },
28
+ };
29
+ if (body) opts.body = JSON.stringify(body);
30
+
31
+ const resp = await fetch(url, opts);
32
+ const data = await resp.json().catch(() => ({}));
33
+
34
+ if (!resp.ok) {
35
+ const msg = data.error || data.message || `HTTP ${resp.status}`;
36
+ throw new Error(msg);
37
+ }
38
+
39
+ return data;
40
+ }
41
+
42
+ async function signup(tenantId) {
43
+ const cfg = config.load();
44
+ const url = cfg.baseUrl.replace(/\/+$/, '') + '/api/signup';
45
+ const resp = await fetch(url, {
46
+ method: 'POST',
47
+ headers: { 'Content-Type': 'application/json' },
48
+ body: JSON.stringify({ tenantId, agent: 'sc-cli' }),
49
+ });
50
+ return resp.json();
51
+ }
52
+
53
+ module.exports = { request, signup };
package/lib/config.js ADDED
@@ -0,0 +1,49 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ const CONFIG_DIR = path.join(os.homedir(), '.secretcarousel');
6
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
7
+
8
+ const DEFAULTS = {
9
+ apiKey: '',
10
+ baseUrl: 'https://secretcarousel.com',
11
+ };
12
+
13
+ function load() {
14
+ // Environment variables take priority (agent-first: agents pass env vars, not config files)
15
+ const env = {
16
+ apiKey: process.env.SC_API_KEY || process.env.SECRETCAROUSEL_API_KEY || '',
17
+ baseUrl: process.env.SC_BASE_URL || process.env.SECRETCAROUSEL_BASE_URL || '',
18
+ };
19
+
20
+ let file = {};
21
+ try {
22
+ if (fs.existsSync(CONFIG_FILE)) {
23
+ file = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
24
+ }
25
+ } catch { /* ignore corrupt config */ }
26
+
27
+ return {
28
+ ...DEFAULTS,
29
+ ...file,
30
+ // Env vars override file config (non-empty only)
31
+ ...(env.apiKey ? { apiKey: env.apiKey } : {}),
32
+ ...(env.baseUrl ? { baseUrl: env.baseUrl } : {}),
33
+ };
34
+ }
35
+
36
+ function save(config) {
37
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
38
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + '\n');
39
+ }
40
+
41
+ function get(key) { return load()[key]; }
42
+
43
+ function set(key, value) {
44
+ const config = load();
45
+ config[key] = value;
46
+ save(config);
47
+ }
48
+
49
+ module.exports = { load, save, get, set, CONFIG_FILE, CONFIG_DIR };
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "secretcarousel",
3
+ "version": "1.0.0",
4
+ "description": "The agent-native secret vault CLI. Store, rotate, and share secrets from any AI coding agent.",
5
+ "bin": {
6
+ "sc": "bin/cli.js"
7
+ },
8
+ "files": ["bin/", "lib/", "commands/"],
9
+ "engines": { "node": ">=18.0.0" },
10
+ "keywords": [
11
+ "secretcarousel",
12
+ "secrets",
13
+ "vault",
14
+ "agent-first",
15
+ "cli",
16
+ "encryption",
17
+ "rotation",
18
+ "api-key",
19
+ "ai-agents",
20
+ "coding-agents",
21
+ "claim-tokens",
22
+ "secret-management"
23
+ ],
24
+ "author": "Joe Wee <joe@tyga.cloud>",
25
+ "license": "SEE LICENSE IN LICENSE",
26
+ "homepage": "https://secretcarousel.com",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/jyswee/secretcarousel"
30
+ }
31
+ }