squidcloudctl 1.0.2
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/README.md +37 -0
- package/package.json +39 -0
- package/squidlink.mjs +134 -0
package/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# squidlink
|
|
2
|
+
|
|
3
|
+
SquidVeil CLI — zero-exposure secrets management for SquidCloud.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g squidlink
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
squidlink secrets list
|
|
15
|
+
squidlink secrets create <name> <value>
|
|
16
|
+
squidlink secrets verify <name> <value>
|
|
17
|
+
squidlink init
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Commands
|
|
21
|
+
|
|
22
|
+
| Command | Description |
|
|
23
|
+
|---------|-------------|
|
|
24
|
+
| `squidlink secrets list` | List all secrets |
|
|
25
|
+
| `squidlink secrets create <name> <value>` | Create a new secret |
|
|
26
|
+
| `squidlink secrets rename <id> <name>` | Rename a secret |
|
|
27
|
+
| `squidlink secrets delete <id>` | Delete a secret |
|
|
28
|
+
| `squidlink secrets verify <name> <value>` | Verify a value against a stored secret |
|
|
29
|
+
| `squidlink secrets export` | Export as .squidproxy |
|
|
30
|
+
| `squidlink secrets init` | Initialize project config files |
|
|
31
|
+
| `squidlink whoami` | Show current user |
|
|
32
|
+
| `squidlink proxy-gen` | Generate proxy config |
|
|
33
|
+
| `squidlink config` | Show current config |
|
|
34
|
+
|
|
35
|
+
## License
|
|
36
|
+
|
|
37
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "squidcloudctl",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "SquidCloud CLI - zero-exposure secrets management",
|
|
5
|
+
"main": "squidlink.mjs",
|
|
6
|
+
"bin": {
|
|
7
|
+
"squidcloudctl": "squidlink.mjs"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"files": [
|
|
11
|
+
"squidlink.mjs",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "node squidlink.mjs --help"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"squidcloud",
|
|
19
|
+
"squidveil",
|
|
20
|
+
"secrets",
|
|
21
|
+
"cli"
|
|
22
|
+
],
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"tsup": "^8.5.1",
|
|
26
|
+
"typescript": "^7.0.2"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@noble/ciphers": "^2.3.0",
|
|
30
|
+
"@noble/curves": "^2.3.0",
|
|
31
|
+
"@noble/ed25519": "^2.3.0",
|
|
32
|
+
"@noble/hashes": "^2.3.0",
|
|
33
|
+
"chalk": "^6.0.0",
|
|
34
|
+
"cli-table3": "^0.6.5",
|
|
35
|
+
"commander": "^15.0.0",
|
|
36
|
+
"gradient-string": "^3.0.0",
|
|
37
|
+
"ora": "^9.4.1"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/squidlink.mjs
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
4
|
+
import { resolve } from 'path';
|
|
5
|
+
import { randomUUID } from 'crypto';
|
|
6
|
+
|
|
7
|
+
const program = new Command();
|
|
8
|
+
|
|
9
|
+
const API_URL = process.env.SQUIDCLOUD_API_URL || 'https://aouqcwbdoyrccjcrhzzi.supabase.co/functions/v1/cloudbliss-api';
|
|
10
|
+
|
|
11
|
+
function apiKey() {
|
|
12
|
+
const key = process.env.SQUIDCLOUD_API_KEY;
|
|
13
|
+
if (!key) {
|
|
14
|
+
console.error('Set SQUIDCLOUD_API_KEY env var or run: squidlink config set-api-key <key>');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
return key;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function api(path, opts = {}) {
|
|
21
|
+
const resp = await fetch(`${API_URL.replace(/\/+$/, '')}${path}`, {
|
|
22
|
+
method: opts.method || 'GET',
|
|
23
|
+
headers: {
|
|
24
|
+
'Content-Type': 'application/json',
|
|
25
|
+
'X-SquidCloud-Key': apiKey(),
|
|
26
|
+
},
|
|
27
|
+
body: opts.body ? JSON.stringify(opts.body) : undefined,
|
|
28
|
+
});
|
|
29
|
+
const data = await resp.json();
|
|
30
|
+
if (!resp.ok) throw new Error(data.error || `HTTP ${resp.status}`);
|
|
31
|
+
return data;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
program.name('squidlink').description('SquidVeil CLI — Zero-Exposure Secrets Management').version('1.0.0');
|
|
35
|
+
|
|
36
|
+
// init
|
|
37
|
+
program.command('init')
|
|
38
|
+
.description('Initialize .squidveil and .squidproxy in current project')
|
|
39
|
+
.option('-f, --force', 'Overwrite existing files')
|
|
40
|
+
.action((opts_) => {
|
|
41
|
+
const cwd = process.cwd();
|
|
42
|
+
const name = cwd.split('/').pop() || 'project';
|
|
43
|
+
const svPath = resolve(cwd, '.squidveil');
|
|
44
|
+
const spPath = resolve(cwd, '.squidproxy');
|
|
45
|
+
|
|
46
|
+
if (existsSync(svPath) && !opts.force) {
|
|
47
|
+
console.error('.squidveil already exists. Use --force.');
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const sv = `# .squidveil\n[project]\nid = "proj_${randomUUID().replace(/-/g, '').slice(0, 12)}"\nname = "${name}"\nenvironment = "production"\n\n[policy]\nfail_closed = true\naudit = true\nref_ttl = 60\nref_max_uses = 1\n\n[secrets]\nallow = ["*"]\n`;
|
|
52
|
+
const sp = `# .squidproxy\n# [[secret]]\n# name = "MY_SECRET"\n# type = "http_auth"\n# inject_header = "Authorization"\n# inject_format = "Bearer {secret}"\n`;
|
|
53
|
+
|
|
54
|
+
writeFileSync(svPath, sv);
|
|
55
|
+
writeFileSync(spPath, sp);
|
|
56
|
+
console.log('✓ .squidveil created');
|
|
57
|
+
console.log('✓ .squidproxy created');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// secrets group
|
|
61
|
+
const secrets = program.command('secrets').description('Manage secrets');
|
|
62
|
+
|
|
63
|
+
secrets.command('list')
|
|
64
|
+
.description('List all secrets')
|
|
65
|
+
.action(async () => {
|
|
66
|
+
const d = await api('/secrets');
|
|
67
|
+
const list = d.secrets || [];
|
|
68
|
+
if (!list.length) { console.log('No secrets.'); return; }
|
|
69
|
+
for (const s of list) console.log(`${s.name} ${s.id.slice(0, 8)} ${new Date(s.created_at).toLocaleDateString()}`);
|
|
70
|
+
console.log(`\n${list.length} secret(s)`);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
secrets.command('create <name> <value>')
|
|
74
|
+
.description('Create a secret')
|
|
75
|
+
.action(async (name, value) => {
|
|
76
|
+
const d = await api('/secrets', { method: 'POST', body: { name, value } });
|
|
77
|
+
console.log(`✓ Secret "${name}" created (${d.secret.id.slice(0, 8)})`);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
secrets.command('rename <id> <new-name>')
|
|
81
|
+
.description('Rename a secret')
|
|
82
|
+
.action(async (id, nn) => {
|
|
83
|
+
await api(`/secrets/${id}/rename`, { method: 'PATCH', body: { name: nn } });
|
|
84
|
+
console.log(`✓ Renamed to "${nn}"`);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
secrets.command('delete <id>')
|
|
88
|
+
.description('Delete a secret')
|
|
89
|
+
.action(async (id) => {
|
|
90
|
+
await api(`/secrets/${id}`, { method: 'DELETE' });
|
|
91
|
+
console.log('✓ Deleted');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
secrets.command('verify <name> <value>')
|
|
95
|
+
.description('Verify a value against a stored secret')
|
|
96
|
+
.action(async (name, value) => {
|
|
97
|
+
const d = await api(`/secrets/${name}/verify`, { method: 'POST', body: { value } });
|
|
98
|
+
if (d.matched) { console.log('MATCHED'); } else { console.log('NOT_MATCHED'); process.exit(1); }
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// config
|
|
102
|
+
const config = program.command('config').description('CLI configuration');
|
|
103
|
+
config.command('set-api-key <key>').action((k) => {
|
|
104
|
+
process.env.SQUIDCLOUD_API_KEY = k;
|
|
105
|
+
console.log('Set SQUIDCLOUD_API_KEY for this session. Add to .env to persist.');
|
|
106
|
+
});
|
|
107
|
+
config.command('show').action(() => {
|
|
108
|
+
console.log(`API URL: ${API_URL}`);
|
|
109
|
+
console.log(`API Key: ${process.env.SQUIDCLOUD_API_KEY ? '✓ set' : '✗ not set'}`);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// whoami
|
|
113
|
+
program.command('whoami')
|
|
114
|
+
.description('Verify API key identity')
|
|
115
|
+
.action(async () => {
|
|
116
|
+
const d = await api('/user');
|
|
117
|
+
console.log(`Authenticated: ${d.email || d.id}`);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// proxy
|
|
121
|
+
program.command('proxy-gen')
|
|
122
|
+
.description('Generate .squidproxy from existing secrets')
|
|
123
|
+
.action(async () => {
|
|
124
|
+
const d = await api('/secrets');
|
|
125
|
+
const list = d.secrets || [];
|
|
126
|
+
let out = '# .squidproxy — auto-generated\n\n';
|
|
127
|
+
for (const s of list) {
|
|
128
|
+
out += `[[secret]]\nname = "${s.name}"\ntype = "http_auth"\ninject_header = "Authorization"\ninject_format = "Bearer {secret}"\nallowed_hosts = []\nmethods = ["GET", "POST"]\nrate_limit = 500\n\n`;
|
|
129
|
+
}
|
|
130
|
+
writeFileSync(resolve(process.cwd(), '.squidproxy'), out);
|
|
131
|
+
console.log(`✓ Generated .squidproxy with ${list.length} secret(s)`);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
program.parse(process.argv);
|