humanenv 0.1.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/README.md +303 -0
- package/package.json +38 -0
- package/packages/cli/package.json +15 -0
- package/packages/cli/src/bin.js +228 -0
- package/packages/client/bin.js +174 -0
- package/packages/client/build.js +57 -0
- package/packages/client/dist/cli.js +1041 -0
- package/packages/client/dist/index.cjs +333 -0
- package/packages/client/dist/index.mjs +296 -0
- package/packages/client/package.json +24 -0
- package/packages/client/src/cli/bin.js +228 -0
- package/packages/client/src/cli/entry.js +465 -0
- package/packages/client/src/index.ts +31 -0
- package/packages/client/src/shared/buffer-shim.d.ts +4 -0
- package/packages/client/src/shared/crypto.ts +98 -0
- package/packages/client/src/shared/errors.ts +32 -0
- package/packages/client/src/shared/index.ts +3 -0
- package/packages/client/src/shared/types.ts +118 -0
- package/packages/client/src/ws-manager.ts +263 -0
- package/packages/server/package.json +21 -0
- package/packages/server/src/auth.ts +13 -0
- package/packages/server/src/db/index.ts +19 -0
- package/packages/server/src/db/interface.ts +33 -0
- package/packages/server/src/db/mongo.ts +166 -0
- package/packages/server/src/db/sqlite.ts +180 -0
- package/packages/server/src/index.ts +123 -0
- package/packages/server/src/pk-manager.ts +79 -0
- package/packages/server/src/routes/index.ts +110 -0
- package/packages/server/src/views/index.ejs +359 -0
- package/packages/server/src/ws/router.ts +263 -0
- package/packages/shared/package.json +13 -0
- package/packages/shared/src/buffer-shim.d.ts +4 -0
- package/packages/shared/src/crypto.ts +98 -0
- package/packages/shared/src/errors.ts +32 -0
- package/packages/shared/src/index.ts +3 -0
- package/packages/shared/src/types.ts +119 -0
package/README.md
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# humanenv
|
|
2
|
+
|
|
3
|
+
Securely inject environment variables into your application so that AI agents and non-human consumers **cannot access secrets directly**. The server is the single source of truth. Secrets are encrypted at rest and exist in memory only when explicitly requested.
|
|
4
|
+
|
|
5
|
+
## Core Idea
|
|
6
|
+
|
|
7
|
+
Instead of storing `.env` files that every process can read, `humanenv` sits between your app and its secrets. Clients connect via WebSocket, authenticate with a per-project API key + device fingerprint, retrieve individual values, use them, and null them out. No bulk dump possible.
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
humanenv (client) ──[WS:port]── humanenv-server ──[SQLite/MongoDB]── encrypted envs
|
|
13
|
+
│ │
|
|
14
|
+
├─ JS SDK ── your app ────────┤
|
|
15
|
+
├─ CLI ── terminal ───────────┤
|
|
16
|
+
└─ .agents/skills/ ── AI agents read skill, use humanenv.get()
|
|
17
|
+
|
|
18
|
+
humanenv-server ──[HTTP:port]── Admin UI (Vue3 SPA)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
### 1. Start the Server
|
|
24
|
+
|
|
25
|
+
Run directly from source (after `npm install`):
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# From the monorepo root
|
|
29
|
+
npx tsx packages/server/src/index.ts --port 3056 --basicAuth
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Or after building:
|
|
33
|
+
```bash
|
|
34
|
+
node packages/server/dist/index.js --port 3056 --basicAuth
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Flags:
|
|
38
|
+
- `--port <number>` — override default 3056 (also supports `PORT` env)
|
|
39
|
+
- `--basicAuth` — require HTTP Basic Auth for admin UI (reads `BASIC_AUTH_USERNAME`/`BASIC_AUTH_PASSWORD`, falls back to `admin/admin`)
|
|
40
|
+
- `MONGODB_URI=<uri>` — use MongoDB instead of SQLite
|
|
41
|
+
|
|
42
|
+
### 2. First-Time Admin Setup
|
|
43
|
+
|
|
44
|
+
1. Open `http://localhost:3056` in your browser
|
|
45
|
+
2. If `HUMANENV_MNEMONIC` env var is **not set**, the UI prompts you to generate or paste a 12-word recovery phrase
|
|
46
|
+
3. **Save the phrase securely** — it is never stored. Losing it means all encrypted data is unrecoverable
|
|
47
|
+
4. Create your first project
|
|
48
|
+
|
|
49
|
+
### 3. Create a Project & API Key
|
|
50
|
+
|
|
51
|
+
In the Admin UI:
|
|
52
|
+
1. Create a project (e.g., `my-app`)
|
|
53
|
+
2. Under **API Keys**, generate a new key
|
|
54
|
+
3. Copy the plain API key — it will not be shown again
|
|
55
|
+
|
|
56
|
+
### 4. Authenticate the CLI
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Set up credentials
|
|
60
|
+
npx tsx packages/cli/src/bin.js auth \
|
|
61
|
+
--project-name my-app \
|
|
62
|
+
--server-url http://localhost:3056 \
|
|
63
|
+
--api-key <your-api-key>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
This stores credentials at `~/.humanenv/credentials.json`.
|
|
67
|
+
|
|
68
|
+
### 5. Retrieve a Secret
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npx tsx packages/cli/src/bin.js get API_KEY
|
|
72
|
+
# Outputs: sk-proj-xxxxxxxxxxxx
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 6. Use in Your Application
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
import humanenv from 'humanenv'
|
|
79
|
+
|
|
80
|
+
humanenv.config({
|
|
81
|
+
serverUrl: 'http://localhost:3056',
|
|
82
|
+
projectName: 'my-app',
|
|
83
|
+
projectApiKey: 'your-api-key',
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// Single key
|
|
87
|
+
let apiKey = await humanenv.get('API_KEY')
|
|
88
|
+
someLib.use(apiKey)
|
|
89
|
+
apiKey = null // always null after use
|
|
90
|
+
|
|
91
|
+
// Multiple keys at once
|
|
92
|
+
let { API_KEY, DATABASE_URL } = await humanenv.get(['API_KEY', 'DATABASE_URL'])
|
|
93
|
+
db.connect(DATABASE_URL)
|
|
94
|
+
DATABASE_URL = null
|
|
95
|
+
API_KEY = null
|
|
96
|
+
|
|
97
|
+
// Set/unset secrets at runtime
|
|
98
|
+
await humanenv.set('API_KEY', 'new-value')
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
CommonJS compatible:
|
|
102
|
+
```javascript
|
|
103
|
+
const humanenv = require('humanenv').default
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## CLI Reference
|
|
107
|
+
|
|
108
|
+
Commands:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
humanenv # Auto-generate .agents/skills/humanenv-usage/SKILL.md
|
|
112
|
+
# Outputs skill to stdout if non-TTY (for agents)
|
|
113
|
+
# Outputs help if TTY (for humans)
|
|
114
|
+
|
|
115
|
+
humanenv auth --project-name <name> --server-url <url> [--api-key <key>]
|
|
116
|
+
# Authenticate with the server
|
|
117
|
+
|
|
118
|
+
humanenv get <key> # Retrieve an env value
|
|
119
|
+
humanenv set <key> <value> # Update or create an env value
|
|
120
|
+
|
|
121
|
+
humanenv server [--port 3056] [--basicAuth]
|
|
122
|
+
# Start the server in-process
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Skill Auto-Generation
|
|
126
|
+
|
|
127
|
+
Running `humanenv` without arguments creates `.agents/skills/humanenv-usage/SKILL.md` in the current directory. This skill teaches AI agents how to use humanenv correctly:
|
|
128
|
+
- Never log or dump sensitive values
|
|
129
|
+
- Always null variables after use
|
|
130
|
+
- Never write secrets to files
|
|
131
|
+
- Use `humanenv.get('key')` individually
|
|
132
|
+
|
|
133
|
+
## Security Model
|
|
134
|
+
|
|
135
|
+
### Encryption at Rest
|
|
136
|
+
|
|
137
|
+
All env values are encrypted with AES-256-GCM before persistence. The encryption key (PK) is derived from a BIP39 12-word mnemonic using PBKDF2 (SHA-256, 100k iterations).
|
|
138
|
+
|
|
139
|
+
The PK **never touches disk**. Only a SHA-256 hash of the PK is stored in the database for verification.
|
|
140
|
+
|
|
141
|
+
### Zero-Touch Restarts via HUMANENV_MNEMONIC
|
|
142
|
+
|
|
143
|
+
For production use (Docker, K8s, CI), set the mnemonic as an environment variable:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
HUMANENV_MNEMONIC="word1 word2 word3 ... word12" humanenv server
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
The PK is derived on startup — no manual entry required. If the env var is **not set**, the server blocks client requests and waits for admin input via the UI.
|
|
150
|
+
|
|
151
|
+
### Client Authentication
|
|
152
|
+
|
|
153
|
+
Each client authenticates with:
|
|
154
|
+
1. **Project name** — must exist on the server
|
|
155
|
+
2. **API key** — per-project secret (encrypted at rest)
|
|
156
|
+
3. **Fingerprint** — deterministic hash of hostname + platform + arch + Node version
|
|
157
|
+
|
|
158
|
+
Clients must be **whitelisted** by an admin before they can retrieve secrets. New clients send a pending request visible in real-time in the Admin UI.
|
|
159
|
+
|
|
160
|
+
### API-Mode Only Envs
|
|
161
|
+
|
|
162
|
+
Secrets can be flagged as **api-mode only**. These are accessible only via the WebSocket SDK (your app), not via the CLI. Non-human agents cannot bypass this: the CLI enforces the gate.
|
|
163
|
+
|
|
164
|
+
### Threat Matrix
|
|
165
|
+
|
|
166
|
+
| Scenario | Mitigation |
|
|
167
|
+
|---|---|
|
|
168
|
+
| Agent reads .env files | No .env files exist |
|
|
169
|
+
| Agent logs env values | Skill instructs against it; SDK does not auto-log |
|
|
170
|
+
| Database dump leaked | All values encrypted; PK not in database |
|
|
171
|
+
| Server restart | PK from env var or manual admin re-entry |
|
|
172
|
+
| Rogue client connects | Fingerprint + API key + whitelist required |
|
|
173
|
+
| Memory dump | Developer must null values after use |
|
|
174
|
+
|
|
175
|
+
## Server Configuration
|
|
176
|
+
|
|
177
|
+
### Ports & Auth
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
# Environment variables
|
|
181
|
+
PORT=3056 # Server port
|
|
182
|
+
BASIC_AUTH_USERNAME=admin # Admin UI username
|
|
183
|
+
BASIC_AUTH_PASSWORD=secret # Admin UI password
|
|
184
|
+
|
|
185
|
+
# Flags
|
|
186
|
+
--port 3056 # Override PORT
|
|
187
|
+
--basicAuth # Enable basic auth for admin UI
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Database
|
|
191
|
+
|
|
192
|
+
**SQLite (default):**
|
|
193
|
+
```bash
|
|
194
|
+
# Uses ~/.humanenv/humanenv.db automatically
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**MongoDB:**
|
|
198
|
+
```bash
|
|
199
|
+
MONGODB_URI="mongodb://localhost:27017" npx tsx packages/server/src/index.ts
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
MongoDB connection failure at bootstrap falls back to SQLite with a warning. Runtime MongoDB disconnections trigger infinite retry with 10-second delays.
|
|
203
|
+
|
|
204
|
+
### Data Directory
|
|
205
|
+
|
|
206
|
+
All persistent data (SQLite DB, credentials) lives in `~/.humanenv/` by default.
|
|
207
|
+
|
|
208
|
+
## Admin UI
|
|
209
|
+
|
|
210
|
+
Access at `http://localhost:<PORT>`.
|
|
211
|
+
|
|
212
|
+
### Dashboard
|
|
213
|
+
- Create/delete projects
|
|
214
|
+
- View project list with creation timestamps
|
|
215
|
+
|
|
216
|
+
### Per-Project Management
|
|
217
|
+
|
|
218
|
+
**Envs Tab:**
|
|
219
|
+
- Add key-value pairs
|
|
220
|
+
- Toggle "api-mode-only" flag per env
|
|
221
|
+
- Update or delete existing envs
|
|
222
|
+
|
|
223
|
+
**API Keys Tab:**
|
|
224
|
+
- Generate new keys (with optional TTL in seconds)
|
|
225
|
+
- Revoke/rotate existing keys
|
|
226
|
+
- Toggle auto-accept for client API key generation requests
|
|
227
|
+
|
|
228
|
+
**Whitelist Tab:**
|
|
229
|
+
- Add approved fingerprints manually
|
|
230
|
+
- Review pending requests from unknown clients
|
|
231
|
+
- Accept or reject in real-time
|
|
232
|
+
|
|
233
|
+
**Real-Time Notifications:**
|
|
234
|
+
Toast notifications appear when:
|
|
235
|
+
- A new client sends a whitelist request
|
|
236
|
+
- A client requests API key generation
|
|
237
|
+
|
|
238
|
+
## Project Structure
|
|
239
|
+
|
|
240
|
+
```
|
|
241
|
+
packages/
|
|
242
|
+
shared/ # Shared types, crypto utilities, error codes
|
|
243
|
+
src/
|
|
244
|
+
crypto.ts # AES-256-GCM, PBKDF2, BIP39 mnemonic, fingerprints
|
|
245
|
+
errors.ts # ErrorCode enum and HumanEnvError class
|
|
246
|
+
types.ts # Interfaces + SKILL_CONTENT template
|
|
247
|
+
index.ts # Re-exports
|
|
248
|
+
server/ # Express + WebSocket server + Admin UI
|
|
249
|
+
src/
|
|
250
|
+
index.ts # Server entry, Express setup, route wiring
|
|
251
|
+
pk-manager.ts # Private key lifecycle (derive, verify, encrypt, decrypt)
|
|
252
|
+
auth.ts # HTTP Basic Auth middleware
|
|
253
|
+
db/
|
|
254
|
+
interface.ts # IDatabaseProvider interface
|
|
255
|
+
sqlite.ts # better-sqlite3 implementation
|
|
256
|
+
mongo.ts # MongoDB (native driver) implementation
|
|
257
|
+
index.ts # Factory: try Mongo, fallback to SQLite
|
|
258
|
+
routes/
|
|
259
|
+
index.ts # REST endpoints: projects, envs, api keys, whitelist
|
|
260
|
+
ws/
|
|
261
|
+
router.ts # WebSocket handler: client SDK + admin UI channels
|
|
262
|
+
views/
|
|
263
|
+
index.ejs # Admin UI (Vue3, Tailwind, DaisyUI via CDN)
|
|
264
|
+
client/ # npm package (humanenv) — SDK for apps
|
|
265
|
+
src/
|
|
266
|
+
index.ts # Main API: config(), get(), set(), disconnect()
|
|
267
|
+
ws-manager.ts # WebSocket connection, auth, auto-reconnect
|
|
268
|
+
cli/ # CLI tool
|
|
269
|
+
src/
|
|
270
|
+
bin.js # commander CLI: auth, get, set, server
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Error Codes
|
|
274
|
+
|
|
275
|
+
| Code | Description |
|
|
276
|
+
|---|---|
|
|
277
|
+
| `SERVER_PK_NOT_AVAILABLE` | Server has no PK in memory; admin must provide mnemonic |
|
|
278
|
+
| `CLIENT_AUTH_INVALID_PROJECT_NAME` | Project name does not exist |
|
|
279
|
+
| `CLIENT_AUTH_NOT_WHITELISTED` | Client fingerprint not approved |
|
|
280
|
+
| `CLIENT_AUTH_INVALID_API_KEY` | API key is invalid or expired |
|
|
281
|
+
| `CLIENT_CONN_MAX_RETRIES_EXCEEDED` | WS reconnection limit reached |
|
|
282
|
+
| `ENV_API_MODE_ONLY` | Env flagged as API-mode only; CLI access denied |
|
|
283
|
+
|
|
284
|
+
## Development
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
npm install # Monorepo install (workspaces)
|
|
288
|
+
|
|
289
|
+
# Start server (dev)
|
|
290
|
+
cd packages/server && npx tsx src/index.ts
|
|
291
|
+
|
|
292
|
+
# Start CLI (dev)
|
|
293
|
+
cd packages/cli && node src/bin.js get MY_KEY
|
|
294
|
+
|
|
295
|
+
# Type check
|
|
296
|
+
npx tsc --noEmit -p packages/server
|
|
297
|
+
npx tsc --noEmit -p packages/client
|
|
298
|
+
npx tsc --noEmit -p packages/shared
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## License
|
|
302
|
+
|
|
303
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "humanenv",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Securely inject environment variables into your app. Secrets for humans only.",
|
|
5
|
+
"author": "arancibiajav@gmail.com",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git@github.com:javimosch/humanenv.git"
|
|
9
|
+
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"workspaces": [
|
|
12
|
+
"packages/shared",
|
|
13
|
+
"packages/server",
|
|
14
|
+
"packages/client",
|
|
15
|
+
"packages/cli"
|
|
16
|
+
],
|
|
17
|
+
"bin": {
|
|
18
|
+
"humanenv": "packages/client/dist/cli.js"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "npm run build --workspaces --if-present",
|
|
22
|
+
"server": "npm run dev --workspace=packages/server",
|
|
23
|
+
"cli": "node packages/cli/src/bin.js",
|
|
24
|
+
"test": "node --import tsx --test packages/*/tests/*.test.ts",
|
|
25
|
+
"test:shared": "node --import tsx --test packages/shared/tests/*.test.ts",
|
|
26
|
+
"test:server": "node --import tsx --test packages/server/tests/*.test.ts"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/basic-auth": "^1.1.8",
|
|
30
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
31
|
+
"@types/ejs": "^3.1.5",
|
|
32
|
+
"@types/express": "^5.0.6",
|
|
33
|
+
"@types/node": "^20.14.0",
|
|
34
|
+
"@types/ws": "^8.18.1",
|
|
35
|
+
"typescript": "^5.7.0",
|
|
36
|
+
"tsx": "^4.19.0"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "humanenv-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"bin": {
|
|
6
|
+
"humanenv": "./src/bin.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"dev": "node src/bin.js"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"commander": "^12.1.0",
|
|
13
|
+
"humanenv-shared": "file:../shared"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { Command } = require('commander')
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
const os = require('os')
|
|
6
|
+
const { generateFingerprint, SKILL_CONTENT } = require('humanenv-shared')
|
|
7
|
+
|
|
8
|
+
const program = new Command()
|
|
9
|
+
const CREDENTIALS_DIR = path.join(os.homedir(), '.humanenv')
|
|
10
|
+
|
|
11
|
+
function ensureCredentialsDir() {
|
|
12
|
+
if (!fs.existsSync(CREDENTIALS_DIR)) fs.mkdirSync(CREDENTIALS_DIR, { recursive: true })
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function readCredentials() {
|
|
16
|
+
const p = path.join(CREDENTIALS_DIR, 'credentials.json')
|
|
17
|
+
if (!fs.existsSync(p)) return null
|
|
18
|
+
try { return JSON.parse(fs.readFileSync(p, 'utf8')) } catch { return null }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function writeCredentials(data) {
|
|
22
|
+
ensureCredentialsDir()
|
|
23
|
+
fs.writeFileSync(path.join(CREDENTIALS_DIR, 'credentials.json'), JSON.stringify(data, null, 2), 'utf8')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function ensureSkillFile() {
|
|
27
|
+
const skillPath = path.join(process.cwd(), '.agents', 'skills', 'humanenv-usage', 'SKILL.md')
|
|
28
|
+
if (!fs.existsSync(skillPath)) {
|
|
29
|
+
fs.mkdirSync(path.dirname(skillPath), { recursive: true })
|
|
30
|
+
fs.writeFileSync(skillPath, SKILL_CONTENT, 'utf8')
|
|
31
|
+
if (process.stdout.isTTY) console.log('Generated .agents/skills/humanenv-usage/SKILL.md')
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ============================================================
|
|
36
|
+
// Main entry: humanenv (no args)
|
|
37
|
+
// ============================================================
|
|
38
|
+
|
|
39
|
+
program
|
|
40
|
+
.action(() => {
|
|
41
|
+
ensureSkillFile()
|
|
42
|
+
if (!process.stdout.isTTY) {
|
|
43
|
+
// Non-TTY: output skill content for agents
|
|
44
|
+
const skillPath = path.join(process.cwd(), '.agents', 'skills', 'humanenv-usage', 'SKILL.md')
|
|
45
|
+
console.log(fs.readFileSync(skillPath, 'utf8'))
|
|
46
|
+
} else {
|
|
47
|
+
// TTY: show human-friendly help
|
|
48
|
+
console.log('HumanEnv - Secure environment variable injection')
|
|
49
|
+
console.log('')
|
|
50
|
+
console.log('Usage:')
|
|
51
|
+
console.log(' humanenv auth --project-name <name> --server-url <url> [--api-key <key>]')
|
|
52
|
+
console.log(' humanenv auth --project-name <name> --server-url <url> --generate-api-key')
|
|
53
|
+
console.log(' humanenv get <key>')
|
|
54
|
+
console.log(' humanenv set <key> <value>')
|
|
55
|
+
console.log(' humanenv server [--port 3056] [--basicAuth]')
|
|
56
|
+
console.log('')
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
// ============================================================
|
|
61
|
+
// Auth command
|
|
62
|
+
// ============================================================
|
|
63
|
+
|
|
64
|
+
program
|
|
65
|
+
.command('auth')
|
|
66
|
+
.option('--project-name <name>')
|
|
67
|
+
.option('--server-url <url>')
|
|
68
|
+
.option('--api-key <key>')
|
|
69
|
+
.option('--generate-api-key', false)
|
|
70
|
+
.action(async (opts) => {
|
|
71
|
+
ensureSkillFile()
|
|
72
|
+
if (!opts.projectName || !opts.serverUrl) {
|
|
73
|
+
console.error('Error: --project-name and --server-url required')
|
|
74
|
+
process.exit(1)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const creds = {
|
|
78
|
+
projectName: opts.projectName,
|
|
79
|
+
serverUrl: opts.serverUrl,
|
|
80
|
+
apiKey: opts.apiKey || undefined,
|
|
81
|
+
}
|
|
82
|
+
writeCredentials(creds)
|
|
83
|
+
|
|
84
|
+
if (opts.generateApiKey) {
|
|
85
|
+
const { HumanEnvClient } = require('humanenv/dist/ws-manager')
|
|
86
|
+
const client = new HumanEnvClient({
|
|
87
|
+
serverUrl: opts.serverUrl,
|
|
88
|
+
projectName: opts.projectName,
|
|
89
|
+
projectApiKey: opts.apiKey || '',
|
|
90
|
+
maxRetries: 3,
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
await client.connect()
|
|
95
|
+
const result = await new Promise((resolve, reject) => {
|
|
96
|
+
client.disconnect()
|
|
97
|
+
// For CLI generate-api-key, we use a simple HTTP call instead
|
|
98
|
+
// since WS API key generation is complex
|
|
99
|
+
resolve(null)
|
|
100
|
+
})
|
|
101
|
+
console.log('API key generation request sent. Admin must approve in dashboard.')
|
|
102
|
+
} catch (e) {
|
|
103
|
+
console.error('Failed to connect:', e.message)
|
|
104
|
+
process.exit(1)
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
// Verify credentials by connecting
|
|
108
|
+
const { HumanEnvClient } = require('humanenv/dist/ws-manager')
|
|
109
|
+
const client = new HumanEnvClient({
|
|
110
|
+
serverUrl: opts.serverUrl,
|
|
111
|
+
projectName: opts.projectName,
|
|
112
|
+
projectApiKey: opts.apiKey || '',
|
|
113
|
+
maxRetries: 3,
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
await client.connect()
|
|
118
|
+
console.log('Authenticated successfully.')
|
|
119
|
+
client.disconnect()
|
|
120
|
+
} catch (e) {
|
|
121
|
+
console.error('Auth failed:', e.message)
|
|
122
|
+
process.exit(1)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
console.log('Credentials stored in', path.join(CREDENTIALS_DIR, 'credentials.json'))
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
// ============================================================
|
|
130
|
+
// Get command
|
|
131
|
+
// ============================================================
|
|
132
|
+
|
|
133
|
+
program
|
|
134
|
+
.command('get')
|
|
135
|
+
.argument('<key>', 'Environment variable key')
|
|
136
|
+
.action(async (key) => {
|
|
137
|
+
const creds = readCredentials()
|
|
138
|
+
if (!creds) {
|
|
139
|
+
console.error('Error: Not authenticated. Run: humanenv auth --project-name <name> --server-url <url>')
|
|
140
|
+
process.exit(1)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const { HumanEnvClient } = require('humanenv/dist/ws-manager')
|
|
144
|
+
const client = new HumanEnvClient({
|
|
145
|
+
serverUrl: creds.serverUrl,
|
|
146
|
+
projectName: creds.projectName,
|
|
147
|
+
projectApiKey: creds.apiKey || '',
|
|
148
|
+
maxRetries: 3,
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
await client.connect()
|
|
153
|
+
const value = await client.get(key)
|
|
154
|
+
// Non-TTY: output raw value only
|
|
155
|
+
if (!process.stdout.isTTY) {
|
|
156
|
+
process.stdout.write(value)
|
|
157
|
+
} else {
|
|
158
|
+
console.log(value)
|
|
159
|
+
}
|
|
160
|
+
client.disconnect()
|
|
161
|
+
} catch (e) {
|
|
162
|
+
console.error('Failed to get env:', e.message)
|
|
163
|
+
process.exit(1)
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
// ============================================================
|
|
168
|
+
// Set command
|
|
169
|
+
// ============================================================
|
|
170
|
+
|
|
171
|
+
program
|
|
172
|
+
.command('set')
|
|
173
|
+
.argument('<key>', 'Environment variable key')
|
|
174
|
+
.argument('<value>', 'Environment variable value')
|
|
175
|
+
.action(async (key, value) => {
|
|
176
|
+
const creds = readCredentials()
|
|
177
|
+
if (!creds) {
|
|
178
|
+
console.error('Error: Not authenticated. Run: humanenv auth --project-name <name> --server-url <url>')
|
|
179
|
+
process.exit(1)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const { HumanEnvClient } = require('humanenv/dist/ws-manager')
|
|
183
|
+
const client = new HumanEnvClient({
|
|
184
|
+
serverUrl: creds.serverUrl,
|
|
185
|
+
projectName: creds.projectName,
|
|
186
|
+
projectApiKey: creds.apiKey || '',
|
|
187
|
+
maxRetries: 3,
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
await client.connect()
|
|
192
|
+
await client.set(key, value)
|
|
193
|
+
console.log('Set', key)
|
|
194
|
+
client.disconnect()
|
|
195
|
+
} catch (e) {
|
|
196
|
+
console.error('Failed to set env:', e.message)
|
|
197
|
+
process.exit(1)
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
// ============================================================
|
|
202
|
+
// Server command (delegates to server package)
|
|
203
|
+
// ============================================================
|
|
204
|
+
|
|
205
|
+
program
|
|
206
|
+
.command('server')
|
|
207
|
+
.option('--port <number>')
|
|
208
|
+
.option('--basicAuth', false)
|
|
209
|
+
.action((opts) => {
|
|
210
|
+
const portArg = opts.port ? `--port=${opts.port}` : ''
|
|
211
|
+
const basicAuthArg = opts.basicAuth ? '--basicAuth' : ''
|
|
212
|
+
const serverPath = path.join(__dirname, '..', '..', 'server', 'src', 'index.ts')
|
|
213
|
+
const serverJs = fs.existsSync(path.join(__dirname, '..', '..', 'server', 'dist', 'index.js'))
|
|
214
|
+
? path.join(__dirname, '..', '..', 'server', 'dist', 'index.js')
|
|
215
|
+
: null
|
|
216
|
+
|
|
217
|
+
if (serverJs && fs.existsSync(serverJs)) {
|
|
218
|
+
require('child_process').fork(serverJs, [portArg, basicAuthArg].filter(Boolean), { stdio: 'inherit' })
|
|
219
|
+
} else {
|
|
220
|
+
// Fallback: run via tsx
|
|
221
|
+
const { spawn } = require('child_process')
|
|
222
|
+
const args = [serverPath, portArg, basicAuthArg].filter(Boolean)
|
|
223
|
+
const child = spawn('npx', ['tsx', ...args], { stdio: 'inherit', shell: process.platform === 'win32' })
|
|
224
|
+
child.on('close', (code) => process.exit(code))
|
|
225
|
+
}
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
program.parse(process.argv)
|