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.
Files changed (3) hide show
  1. package/README.md +37 -0
  2. package/package.json +39 -0
  3. 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);