speclock 2.1.1 → 3.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/README.md +55 -5
- package/package.json +10 -4
- package/src/cli/index.js +247 -3
- package/src/core/auth.js +341 -0
- package/src/core/compliance.js +1 -1
- package/src/core/conflict.js +363 -0
- package/src/core/crypto.js +158 -0
- package/src/core/enforcer.js +314 -0
- package/src/core/engine.js +111 -781
- package/src/core/memory.js +191 -0
- package/src/core/pre-commit-semantic.js +284 -0
- package/src/core/sessions.js +128 -0
- package/src/core/storage.js +23 -4
- package/src/core/tracking.js +98 -0
- package/src/mcp/http-server.js +134 -7
- package/src/mcp/server.js +206 -4
package/README.md
CHANGED
|
@@ -58,6 +58,9 @@ No other tool does this. Not Claude's native memory. Not Mem0. Not CLAUDE.md fil
|
|
|
58
58
|
| Git-aware (checkpoints, rollback) | No | No | No | **Yes** |
|
|
59
59
|
| Drift detection | No | No | No | **Yes — scans changes against locks** |
|
|
60
60
|
| CI/CD integration | No | No | No | **Yes — GitHub Actions** |
|
|
61
|
+
| **Hard enforcement (block violations)** | No | No | No | **Yes — hard mode blocks above threshold** |
|
|
62
|
+
| **API Key Auth + RBAC** | No | No | No | **Yes** |
|
|
63
|
+
| **Encrypted Storage (AES-256)** | No | No | No | **Yes** |
|
|
61
64
|
| Multi-agent timeline | No | No | No | **Yes** |
|
|
62
65
|
| Cross-platform | Claude only | MCP only | Tool-specific | **Universal (MCP + npm)** |
|
|
63
66
|
|
|
@@ -192,10 +195,10 @@ Result: NO CONFLICT (confidence: 7%)
|
|
|
192
195
|
| Mode | Platforms | How It Works |
|
|
193
196
|
|------|-----------|--------------|
|
|
194
197
|
| **MCP Remote** | Lovable, bolt.diy, Base44 | Connect via URL — no install needed |
|
|
195
|
-
| **MCP Local** | Claude Code, Cursor, Windsurf, Cline | `npx speclock serve` —
|
|
198
|
+
| **MCP Local** | Claude Code, Cursor, Windsurf, Cline | `npx speclock serve` — 28 tools via MCP |
|
|
196
199
|
| **npm File-Based** | Bolt.new, Aider, Rocket.new | `npx speclock setup` — AI reads SPECLOCK.md + uses CLI |
|
|
197
200
|
|
|
198
|
-
##
|
|
201
|
+
## 28 MCP Tools
|
|
199
202
|
|
|
200
203
|
### Memory Management
|
|
201
204
|
| Tool | Purpose |
|
|
@@ -249,6 +252,14 @@ Result: NO CONFLICT (confidence: 7%)
|
|
|
249
252
|
| `speclock_verify_audit` | Verify HMAC audit chain integrity — tamper detection |
|
|
250
253
|
| `speclock_export_compliance` | Generate SOC 2 / HIPAA / CSV compliance reports |
|
|
251
254
|
|
|
255
|
+
### Hard Enforcement (v2.5)
|
|
256
|
+
| Tool | Purpose |
|
|
257
|
+
|------|---------|
|
|
258
|
+
| `speclock_set_enforcement` | Set enforcement mode: advisory (warn) or hard (block) |
|
|
259
|
+
| `speclock_override_lock` | Override a lock with justification — logged to audit trail |
|
|
260
|
+
| `speclock_semantic_audit` | Semantic pre-commit: analyze code changes vs locks |
|
|
261
|
+
| `speclock_override_history` | View lock override history for audit review |
|
|
262
|
+
|
|
252
263
|
## Auto-Guard: Locks That Actually Work
|
|
253
264
|
|
|
254
265
|
When you add a lock, SpecLock **automatically finds and guards related files**:
|
|
@@ -317,6 +328,12 @@ speclock audit-verify # Verify HMAC audit chain integrity
|
|
|
317
328
|
speclock export --format <soc2|hipaa|csv> # Compliance export
|
|
318
329
|
speclock license # Show license tier and usage
|
|
319
330
|
|
|
331
|
+
# Hard Enforcement (v2.5)
|
|
332
|
+
speclock enforce <advisory|hard> # Set enforcement mode
|
|
333
|
+
speclock override <lockId> <reason> # Override a lock with justification
|
|
334
|
+
speclock overrides [--lock <id>] # Show override history
|
|
335
|
+
speclock audit-semantic # Semantic pre-commit audit
|
|
336
|
+
|
|
320
337
|
# Other
|
|
321
338
|
speclock status # Show brain summary
|
|
322
339
|
speclock serve [--project <path>] # Start MCP server
|
|
@@ -372,6 +389,38 @@ SOC 2 reports include: constraint change history, access logs, decision audit tr
|
|
|
372
389
|
```
|
|
373
390
|
Audits changed files against locks, posts PR comments, fails workflow on violations.
|
|
374
391
|
|
|
392
|
+
## Hard Enforcement (v2.5)
|
|
393
|
+
|
|
394
|
+
### Advisory vs Hard Mode
|
|
395
|
+
```
|
|
396
|
+
Advisory mode (default): AI gets a warning, decides what to do
|
|
397
|
+
Hard mode: AI is BLOCKED — cannot proceed (MCP returns isError: true)
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
- **Block threshold**: Configurable (default 70%). Only HIGH confidence conflicts block.
|
|
401
|
+
- **Override mechanism**: `speclock_override_lock` with a reason (logged to audit trail)
|
|
402
|
+
- **Escalation**: Lock overridden 3+ times → auto-creates a review note
|
|
403
|
+
- **Semantic pre-commit**: Parses actual git diff content, runs semantic analysis against locks
|
|
404
|
+
|
|
405
|
+
## Security & Access Control (v3.0)
|
|
406
|
+
|
|
407
|
+
### API Key Authentication
|
|
408
|
+
SHA-256 hashed keys stored server-side. HTTP transport uses `Authorization: Bearer <key>` headers. MCP transport authenticates via the `SPECLOCK_API_KEY` environment variable. Keys are never stored in plaintext.
|
|
409
|
+
|
|
410
|
+
### RBAC (4 Roles)
|
|
411
|
+
| Role | Permissions |
|
|
412
|
+
|------|-------------|
|
|
413
|
+
| **viewer** | Read-only access to context, locks, decisions, and events |
|
|
414
|
+
| **developer** | Read + override locks with a documented reason |
|
|
415
|
+
| **architect** | Read + write (add/remove locks, decisions) + override |
|
|
416
|
+
| **admin** | Full access — manage keys, roles, enforcement settings, and all operations |
|
|
417
|
+
|
|
418
|
+
### AES-256-GCM Encryption
|
|
419
|
+
Transparent encrypt-on-write / decrypt-on-read for `brain.json` and `events.log`. Encryption key is derived via PBKDF2 from the `SPECLOCK_ENCRYPTION_KEY` environment variable. Authenticated encryption (GCM) ensures both confidentiality and integrity. **HIPAA 2026 compliant.**
|
|
420
|
+
|
|
421
|
+
### Test Coverage
|
|
422
|
+
**300 tests passing** (up from 186 in v2.5). Full coverage for authentication, authorization, encryption, semantic detection, and audit chain integrity.
|
|
423
|
+
|
|
375
424
|
---
|
|
376
425
|
|
|
377
426
|
## Architecture
|
|
@@ -382,14 +431,15 @@ Audits changed files against locks, posts PR comments, fails workflow on violati
|
|
|
382
431
|
└──────────────┬──────────────────┬────────────────────┘
|
|
383
432
|
│ │
|
|
384
433
|
MCP Protocol File-Based (npm)
|
|
385
|
-
(
|
|
434
|
+
(28 tool calls) (reads SPECLOCK.md +
|
|
386
435
|
.speclock/context/latest.md,
|
|
387
436
|
runs CLI commands)
|
|
388
437
|
│ │
|
|
389
438
|
┌──────────────▼──────────────────▼────────────────────┐
|
|
390
439
|
│ SpecLock Core Engine │
|
|
391
440
|
│ Memory | Tracking | Enforcement | Git | Intelligence │
|
|
392
|
-
│ Audit | Compliance | License
|
|
441
|
+
│ Audit | Compliance | License | Auth | RBAC │
|
|
442
|
+
│ AES-256-GCM Encryption (brain.json, events.log) │
|
|
393
443
|
└──────────────────────┬───────────────────────────────┘
|
|
394
444
|
│
|
|
395
445
|
.speclock/
|
|
@@ -419,4 +469,4 @@ MIT License - see [LICENSE](LICENSE) file.
|
|
|
419
469
|
|
|
420
470
|
---
|
|
421
471
|
|
|
422
|
-
*SpecLock
|
|
472
|
+
*SpecLock v3.0.0 — Semantic conflict detection + enterprise audit & compliance. 100% detection, 0% false positives. HMAC audit chain, SOC 2/HIPAA exports. Hard enforcement mode. API Key Auth + RBAC. AES-256-GCM encrypted storage. 300 tests passing. Because remembering isn't enough — AI needs to respect boundaries.*
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "speclock",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "AI constraint engine with
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "AI constraint engine with API key auth, RBAC, AES-256-GCM encryption, hard enforcement, semantic pre-commit, HMAC audit chain, SOC 2/HIPAA compliance. 100% detection, 0% false positives. 28 MCP tools + CLI. Enterprise-ready.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/mcp/server.js",
|
|
7
7
|
"bin": {
|
|
@@ -36,7 +36,12 @@
|
|
|
36
36
|
"hipaa",
|
|
37
37
|
"compliance",
|
|
38
38
|
"audit-trail",
|
|
39
|
-
"hmac"
|
|
39
|
+
"hmac",
|
|
40
|
+
"encryption",
|
|
41
|
+
"aes-256",
|
|
42
|
+
"api-key",
|
|
43
|
+
"authentication",
|
|
44
|
+
"rbac"
|
|
40
45
|
],
|
|
41
46
|
"author": "Sandeep Roy (https://github.com/sgroy10)",
|
|
42
47
|
"license": "MIT",
|
|
@@ -64,6 +69,7 @@
|
|
|
64
69
|
"LICENSE"
|
|
65
70
|
],
|
|
66
71
|
"devDependencies": {
|
|
67
|
-
"esbuild": "^0.27.3"
|
|
72
|
+
"esbuild": "^0.27.3",
|
|
73
|
+
"jest": "^30.2.0"
|
|
68
74
|
}
|
|
69
75
|
}
|
package/src/cli/index.js
CHANGED
|
@@ -23,10 +23,26 @@ import {
|
|
|
23
23
|
verifyAuditChain,
|
|
24
24
|
exportCompliance,
|
|
25
25
|
getLicenseInfo,
|
|
26
|
+
enforceConflictCheck,
|
|
27
|
+
setEnforcementMode,
|
|
28
|
+
overrideLock,
|
|
29
|
+
getOverrideHistory,
|
|
30
|
+
getEnforcementConfig,
|
|
31
|
+
semanticAudit,
|
|
26
32
|
} from "../core/engine.js";
|
|
27
33
|
import { generateContext } from "../core/context.js";
|
|
28
34
|
import { readBrain } from "../core/storage.js";
|
|
29
35
|
import { installHook, removeHook } from "../core/hooks.js";
|
|
36
|
+
import {
|
|
37
|
+
isAuthEnabled,
|
|
38
|
+
enableAuth,
|
|
39
|
+
disableAuth,
|
|
40
|
+
createApiKey,
|
|
41
|
+
rotateApiKey,
|
|
42
|
+
revokeApiKey,
|
|
43
|
+
listApiKeys,
|
|
44
|
+
} from "../core/auth.js";
|
|
45
|
+
import { isEncryptionEnabled } from "../core/crypto.js";
|
|
30
46
|
|
|
31
47
|
// --- Argument parsing ---
|
|
32
48
|
|
|
@@ -82,7 +98,7 @@ function refreshContext(root) {
|
|
|
82
98
|
|
|
83
99
|
function printHelp() {
|
|
84
100
|
console.log(`
|
|
85
|
-
SpecLock
|
|
101
|
+
SpecLock v3.0.0 — AI Constraint Engine (Auth + RBAC + Encryption + Hard Enforcement)
|
|
86
102
|
Developed by Sandeep Roy (github.com/sgroy10)
|
|
87
103
|
|
|
88
104
|
Usage: speclock <command> [options]
|
|
@@ -105,7 +121,11 @@ Commands:
|
|
|
105
121
|
hook install Install git pre-commit hook
|
|
106
122
|
hook remove Remove git pre-commit hook
|
|
107
123
|
audit Audit staged files against locks
|
|
124
|
+
audit-semantic Semantic audit: analyze code changes vs locks
|
|
108
125
|
audit-verify Verify HMAC audit chain integrity
|
|
126
|
+
enforce <advisory|hard> Set enforcement mode (advisory=warn, hard=block)
|
|
127
|
+
override <lockId> <reason> Override a lock with justification
|
|
128
|
+
overrides [--lock <id>] Show override history
|
|
109
129
|
export --format <soc2|hipaa|csv> Export compliance report
|
|
110
130
|
license Show license tier and usage info
|
|
111
131
|
context Generate and print context pack
|
|
@@ -126,10 +146,22 @@ Options:
|
|
|
126
146
|
|
|
127
147
|
Templates: nextjs, react, express, supabase, stripe, security-hardened
|
|
128
148
|
|
|
149
|
+
Security (v3.0):
|
|
150
|
+
auth status Show auth status and active keys
|
|
151
|
+
auth create-key --role <role> Create API key (viewer/developer/architect/admin)
|
|
152
|
+
auth rotate-key <keyId> Rotate an API key
|
|
153
|
+
auth revoke-key <keyId> Revoke an API key
|
|
154
|
+
auth list-keys List all API keys
|
|
155
|
+
auth enable Enable API key authentication
|
|
156
|
+
auth disable Disable authentication
|
|
157
|
+
encrypt [status] Show encryption status
|
|
158
|
+
|
|
129
159
|
Enterprise:
|
|
130
160
|
SPECLOCK_AUDIT_SECRET HMAC secret for audit chain (env var)
|
|
131
161
|
SPECLOCK_LICENSE_KEY License key for Pro/Enterprise features
|
|
132
162
|
SPECLOCK_LLM_KEY API key for LLM-powered conflict detection
|
|
163
|
+
SPECLOCK_ENCRYPTION_KEY Master key for AES-256-GCM encryption
|
|
164
|
+
SPECLOCK_API_KEY API key for MCP server auth
|
|
133
165
|
|
|
134
166
|
Examples:
|
|
135
167
|
npx speclock setup --goal "Build PawPalace pet shop" --template nextjs
|
|
@@ -176,6 +208,8 @@ function showStatus(root) {
|
|
|
176
208
|
}
|
|
177
209
|
|
|
178
210
|
console.log(`Recent changes: ${brain.state.recentChanges.length}`);
|
|
211
|
+
console.log(`Auth: ${isAuthEnabled(root) ? "enabled" : "disabled"}`);
|
|
212
|
+
console.log(`Encryption: ${isEncryptionEnabled() ? "enabled (AES-256-GCM)" : "disabled"}`);
|
|
179
213
|
console.log("");
|
|
180
214
|
}
|
|
181
215
|
|
|
@@ -385,10 +419,12 @@ Tip: When starting a new chat, tell the AI:
|
|
|
385
419
|
console.error('Usage: speclock check "what you plan to do"');
|
|
386
420
|
process.exit(1);
|
|
387
421
|
}
|
|
388
|
-
const result =
|
|
422
|
+
const result = enforceConflictCheck(root, text);
|
|
389
423
|
if (result.hasConflict) {
|
|
390
|
-
console.log(`\
|
|
424
|
+
console.log(`\n${result.blocked ? "BLOCKED" : "CONFLICT DETECTED"}`);
|
|
391
425
|
console.log("=".repeat(50));
|
|
426
|
+
console.log(`Mode: ${result.mode} | Threshold: ${result.threshold}%`);
|
|
427
|
+
console.log("");
|
|
392
428
|
for (const lock of result.conflictingLocks) {
|
|
393
429
|
console.log(` [${lock.level}] "${lock.text}"`);
|
|
394
430
|
console.log(` Confidence: ${lock.confidence}%`);
|
|
@@ -400,6 +436,9 @@ Tip: When starting a new chat, tell the AI:
|
|
|
400
436
|
console.log("");
|
|
401
437
|
}
|
|
402
438
|
console.log(result.analysis);
|
|
439
|
+
if (result.blocked) {
|
|
440
|
+
process.exit(1);
|
|
441
|
+
}
|
|
403
442
|
} else {
|
|
404
443
|
console.log(`No conflicts found. Safe to proceed with: "${text}"`);
|
|
405
444
|
}
|
|
@@ -674,6 +713,211 @@ Tip: When starting a new chat, tell the AI:
|
|
|
674
713
|
return;
|
|
675
714
|
}
|
|
676
715
|
|
|
716
|
+
// --- ENFORCE (v2.5) ---
|
|
717
|
+
if (cmd === "enforce") {
|
|
718
|
+
const mode = args[0];
|
|
719
|
+
if (!mode || (mode !== "advisory" && mode !== "hard")) {
|
|
720
|
+
console.error("Usage: speclock enforce <advisory|hard> [--threshold 70]");
|
|
721
|
+
process.exit(1);
|
|
722
|
+
}
|
|
723
|
+
const flags = parseFlags(args.slice(1));
|
|
724
|
+
const options = {};
|
|
725
|
+
if (flags.threshold) options.blockThreshold = parseInt(flags.threshold, 10);
|
|
726
|
+
if (flags.override !== undefined) options.allowOverride = flags.override !== "false";
|
|
727
|
+
const result = setEnforcementMode(root, mode, options);
|
|
728
|
+
if (!result.success) {
|
|
729
|
+
console.error(result.error);
|
|
730
|
+
process.exit(1);
|
|
731
|
+
}
|
|
732
|
+
console.log(`\nEnforcement mode: ${result.mode.toUpperCase()}`);
|
|
733
|
+
console.log(`Block threshold: ${result.config.blockThreshold}%`);
|
|
734
|
+
console.log(`Overrides: ${result.config.allowOverride ? "allowed" : "disabled"}`);
|
|
735
|
+
if (result.mode === "hard") {
|
|
736
|
+
console.log(`\nHard mode active — conflicts above ${result.config.blockThreshold}% confidence will BLOCK actions.`);
|
|
737
|
+
}
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// --- OVERRIDE (v2.5) ---
|
|
742
|
+
if (cmd === "override") {
|
|
743
|
+
const lockId = args[0];
|
|
744
|
+
const reason = args.slice(1).join(" ");
|
|
745
|
+
if (!lockId || !reason) {
|
|
746
|
+
console.error("Usage: speclock override <lockId> <reason>");
|
|
747
|
+
process.exit(1);
|
|
748
|
+
}
|
|
749
|
+
const result = overrideLock(root, lockId, "(CLI override)", reason);
|
|
750
|
+
if (!result.success) {
|
|
751
|
+
console.error(result.error);
|
|
752
|
+
process.exit(1);
|
|
753
|
+
}
|
|
754
|
+
console.log(`Lock overridden: "${result.lockText}"`);
|
|
755
|
+
console.log(`Override count: ${result.overrideCount}`);
|
|
756
|
+
if (result.escalated) {
|
|
757
|
+
console.log(`\n${result.escalationMessage}`);
|
|
758
|
+
}
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// --- OVERRIDES (v2.5) ---
|
|
763
|
+
if (cmd === "overrides") {
|
|
764
|
+
const flags = parseFlags(args);
|
|
765
|
+
const result = getOverrideHistory(root, flags.lock || null);
|
|
766
|
+
if (result.total === 0) {
|
|
767
|
+
console.log("No overrides recorded.");
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
console.log(`\nOverride History (${result.total})`);
|
|
771
|
+
console.log("=".repeat(50));
|
|
772
|
+
for (const o of result.overrides) {
|
|
773
|
+
console.log(`[${o.at.substring(0, 19)}] Lock: "${o.lockText}"`);
|
|
774
|
+
console.log(` Action: ${o.action}`);
|
|
775
|
+
console.log(` Reason: ${o.reason}`);
|
|
776
|
+
console.log("");
|
|
777
|
+
}
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// --- AUDIT-SEMANTIC (v2.5) ---
|
|
782
|
+
if (cmd === "audit-semantic") {
|
|
783
|
+
const result = semanticAudit(root);
|
|
784
|
+
console.log(`\nSemantic Pre-Commit Audit`);
|
|
785
|
+
console.log("=".repeat(50));
|
|
786
|
+
console.log(`Mode: ${result.mode} | Threshold: ${result.threshold}%`);
|
|
787
|
+
console.log(`Files analyzed: ${result.filesChecked}`);
|
|
788
|
+
console.log(`Active locks: ${result.activeLocks}`);
|
|
789
|
+
console.log(`Violations: ${result.violations.length}`);
|
|
790
|
+
if (result.violations.length > 0) {
|
|
791
|
+
console.log("");
|
|
792
|
+
for (const v of result.violations) {
|
|
793
|
+
console.log(` [${v.level}] ${v.file} (confidence: ${v.confidence}%)`);
|
|
794
|
+
console.log(` Lock: "${v.lockText}"`);
|
|
795
|
+
console.log(` Reason: ${v.reason}`);
|
|
796
|
+
if (v.addedLines !== undefined) {
|
|
797
|
+
console.log(` Changes: +${v.addedLines} / -${v.removedLines} lines`);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
console.log(`\n${result.message}`);
|
|
802
|
+
process.exit(result.blocked ? 1 : 0);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// --- AUTH (v3.0) ---
|
|
806
|
+
if (cmd === "auth") {
|
|
807
|
+
const sub = args[0];
|
|
808
|
+
if (!sub || sub === "status") {
|
|
809
|
+
const enabled = isAuthEnabled(root);
|
|
810
|
+
console.log(`\nAuth Status: ${enabled ? "ENABLED" : "DISABLED"}`);
|
|
811
|
+
if (enabled) {
|
|
812
|
+
const keys = listApiKeys(root);
|
|
813
|
+
const active = keys.keys.filter(k => k.active);
|
|
814
|
+
console.log(`Active keys: ${active.length}`);
|
|
815
|
+
for (const k of active) {
|
|
816
|
+
console.log(` ${k.id} — ${k.name} (${k.role}) — last used: ${k.lastUsed || "never"}`);
|
|
817
|
+
}
|
|
818
|
+
} else {
|
|
819
|
+
console.log("Run 'speclock auth create-key --role admin' to enable auth.");
|
|
820
|
+
}
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
if (sub === "create-key") {
|
|
824
|
+
const flags = parseFlags(args.slice(1));
|
|
825
|
+
const role = flags.role || "developer";
|
|
826
|
+
const name = flags.name || flags._.join(" ") || "";
|
|
827
|
+
const result = createApiKey(root, role, name);
|
|
828
|
+
if (!result.success) {
|
|
829
|
+
console.error(result.error);
|
|
830
|
+
process.exit(1);
|
|
831
|
+
}
|
|
832
|
+
console.log(`\nAPI Key Created`);
|
|
833
|
+
console.log("=".repeat(50));
|
|
834
|
+
console.log(`Key ID: ${result.keyId}`);
|
|
835
|
+
console.log(`Role: ${result.role}`);
|
|
836
|
+
console.log(`Name: ${result.name}`);
|
|
837
|
+
console.log(`\nRaw Key: ${result.rawKey}`);
|
|
838
|
+
console.log(`\nSave this key — it CANNOT be retrieved later.`);
|
|
839
|
+
console.log(`\nUsage:`);
|
|
840
|
+
console.log(` HTTP: Authorization: Bearer ${result.rawKey}`);
|
|
841
|
+
console.log(` MCP: Set SPECLOCK_API_KEY=${result.rawKey} in MCP config`);
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
if (sub === "rotate-key") {
|
|
845
|
+
const keyId = args[1];
|
|
846
|
+
if (!keyId) {
|
|
847
|
+
console.error("Usage: speclock auth rotate-key <keyId>");
|
|
848
|
+
process.exit(1);
|
|
849
|
+
}
|
|
850
|
+
const result = rotateApiKey(root, keyId);
|
|
851
|
+
if (!result.success) {
|
|
852
|
+
console.error(result.error);
|
|
853
|
+
process.exit(1);
|
|
854
|
+
}
|
|
855
|
+
console.log(`\nKey Rotated`);
|
|
856
|
+
console.log(`Old key: ${result.oldKeyId} (revoked)`);
|
|
857
|
+
console.log(`New key: ${result.newKeyId}`);
|
|
858
|
+
console.log(`Raw Key: ${result.rawKey}`);
|
|
859
|
+
console.log(`\nSave this key — it CANNOT be retrieved later.`);
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
if (sub === "revoke-key") {
|
|
863
|
+
const keyId = args[1];
|
|
864
|
+
if (!keyId) {
|
|
865
|
+
console.error("Usage: speclock auth revoke-key <keyId>");
|
|
866
|
+
process.exit(1);
|
|
867
|
+
}
|
|
868
|
+
const reason = args.slice(2).join(" ") || "manual";
|
|
869
|
+
const result = revokeApiKey(root, keyId, reason);
|
|
870
|
+
if (!result.success) {
|
|
871
|
+
console.error(result.error);
|
|
872
|
+
process.exit(1);
|
|
873
|
+
}
|
|
874
|
+
console.log(`Key revoked: ${result.keyId} (${result.name}, ${result.role})`);
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
if (sub === "list-keys") {
|
|
878
|
+
const result = listApiKeys(root);
|
|
879
|
+
console.log(`\nAPI Keys (auth ${result.enabled ? "enabled" : "disabled"}):`);
|
|
880
|
+
console.log("=".repeat(50));
|
|
881
|
+
if (result.keys.length === 0) {
|
|
882
|
+
console.log(" No keys configured.");
|
|
883
|
+
} else {
|
|
884
|
+
for (const k of result.keys) {
|
|
885
|
+
const status = k.active ? "active" : `revoked (${k.revokedAt?.substring(0, 10) || "unknown"})`;
|
|
886
|
+
console.log(` ${k.id} — ${k.name} (${k.role}) [${status}]`);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
if (sub === "enable") {
|
|
892
|
+
enableAuth(root);
|
|
893
|
+
console.log("Auth enabled. API keys are now required for HTTP access.");
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
if (sub === "disable") {
|
|
897
|
+
disableAuth(root);
|
|
898
|
+
console.log("Auth disabled. All operations allowed without keys.");
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
console.error("Usage: speclock auth <create-key|rotate-key|revoke-key|list-keys|enable|disable|status>");
|
|
902
|
+
process.exit(1);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// --- ENCRYPT STATUS (v3.0) ---
|
|
906
|
+
if (cmd === "encrypt") {
|
|
907
|
+
const sub = args[0];
|
|
908
|
+
if (sub === "status" || !sub) {
|
|
909
|
+
const enabled = isEncryptionEnabled();
|
|
910
|
+
console.log(`\nEncryption: ${enabled ? "ENABLED (AES-256-GCM)" : "DISABLED"}`);
|
|
911
|
+
if (!enabled) {
|
|
912
|
+
console.log("Set SPECLOCK_ENCRYPTION_KEY env var to enable encryption.");
|
|
913
|
+
console.log("All data will be encrypted at rest (brain.json + events.log).");
|
|
914
|
+
}
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
console.error("Usage: speclock encrypt [status]");
|
|
918
|
+
process.exit(1);
|
|
919
|
+
}
|
|
920
|
+
|
|
677
921
|
// --- STATUS ---
|
|
678
922
|
if (cmd === "status") {
|
|
679
923
|
showStatus(root);
|