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 +29 -0
- package/README.md +89 -0
- package/bin/cli.js +86 -0
- package/commands/audit-export.js +12 -0
- package/commands/backup-create.js +13 -0
- package/commands/config.js +20 -0
- package/commands/env-export.js +27 -0
- package/commands/env-promote.js +36 -0
- package/commands/rotate.js +7 -0
- package/commands/secrets-create.js +16 -0
- package/commands/secrets-delete.js +7 -0
- package/commands/secrets-get.js +7 -0
- package/commands/secrets-list.js +12 -0
- package/commands/signup.js +32 -0
- package/lib/api.js +53 -0
- package/lib/config.js +49 -0
- package/package.json +31 -0
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
|
+
}
|