@i4ctime/q-ring 0.2.9 → 0.3.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/dist/chunk-HOMNGA55.js +651 -0
- package/dist/chunk-HOMNGA55.js.map +1 -0
- package/dist/chunk-WQPJ2FTM.js +673 -0
- package/dist/chunk-WQPJ2FTM.js.map +1 -0
- package/dist/dashboard-7GII7BS2.js +482 -0
- package/dist/dashboard-7GII7BS2.js.map +1 -0
- package/dist/dashboard-JTALLJM6.js +482 -0
- package/dist/dashboard-JTALLJM6.js.map +1 -0
- package/dist/index.js +48 -648
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +35 -626
- package/dist/mcp.js.map +1 -1
- package/package.json +1 -1
package/dist/mcp.js
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
checkDecay,
|
|
4
|
+
collapseEnvironment,
|
|
5
|
+
deleteSecret,
|
|
6
|
+
detectAnomalies,
|
|
7
|
+
entangleSecrets,
|
|
8
|
+
getEnvelope,
|
|
9
|
+
getSecret,
|
|
10
|
+
hasSecret,
|
|
11
|
+
listSecrets,
|
|
12
|
+
logAudit,
|
|
13
|
+
queryAudit,
|
|
14
|
+
setSecret,
|
|
15
|
+
tunnelCreate,
|
|
16
|
+
tunnelDestroy,
|
|
17
|
+
tunnelList,
|
|
18
|
+
tunnelRead
|
|
19
|
+
} from "./chunk-HOMNGA55.js";
|
|
2
20
|
|
|
3
21
|
// src/mcp.ts
|
|
4
22
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -7,561 +25,6 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
7
25
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
26
|
import { z } from "zod";
|
|
9
27
|
|
|
10
|
-
// src/core/keyring.ts
|
|
11
|
-
import { Entry, findCredentials } from "@napi-rs/keyring";
|
|
12
|
-
|
|
13
|
-
// src/utils/hash.ts
|
|
14
|
-
import { createHash } from "crypto";
|
|
15
|
-
function hashProjectPath(projectPath) {
|
|
16
|
-
return createHash("sha256").update(projectPath).digest("hex").slice(0, 12);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// src/core/scope.ts
|
|
20
|
-
var SERVICE_PREFIX = "q-ring";
|
|
21
|
-
function globalService() {
|
|
22
|
-
return `${SERVICE_PREFIX}:global`;
|
|
23
|
-
}
|
|
24
|
-
function projectService(projectPath) {
|
|
25
|
-
const hash = hashProjectPath(projectPath);
|
|
26
|
-
return `${SERVICE_PREFIX}:project:${hash}`;
|
|
27
|
-
}
|
|
28
|
-
function resolveScope(opts2) {
|
|
29
|
-
const { scope, projectPath } = opts2;
|
|
30
|
-
if (scope === "global") {
|
|
31
|
-
return [{ scope: "global", service: globalService() }];
|
|
32
|
-
}
|
|
33
|
-
if (scope === "project") {
|
|
34
|
-
if (!projectPath) {
|
|
35
|
-
throw new Error("Project path is required for project scope");
|
|
36
|
-
}
|
|
37
|
-
return [
|
|
38
|
-
{ scope: "project", service: projectService(projectPath), projectPath }
|
|
39
|
-
];
|
|
40
|
-
}
|
|
41
|
-
if (projectPath) {
|
|
42
|
-
return [
|
|
43
|
-
{ scope: "project", service: projectService(projectPath), projectPath },
|
|
44
|
-
{ scope: "global", service: globalService() }
|
|
45
|
-
];
|
|
46
|
-
}
|
|
47
|
-
return [{ scope: "global", service: globalService() }];
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// src/core/envelope.ts
|
|
51
|
-
function createEnvelope(value, opts2) {
|
|
52
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
53
|
-
let expiresAt = opts2?.expiresAt;
|
|
54
|
-
if (!expiresAt && opts2?.ttlSeconds) {
|
|
55
|
-
expiresAt = new Date(Date.now() + opts2.ttlSeconds * 1e3).toISOString();
|
|
56
|
-
}
|
|
57
|
-
return {
|
|
58
|
-
v: 1,
|
|
59
|
-
value: opts2?.states ? void 0 : value,
|
|
60
|
-
states: opts2?.states,
|
|
61
|
-
defaultEnv: opts2?.defaultEnv,
|
|
62
|
-
meta: {
|
|
63
|
-
createdAt: now,
|
|
64
|
-
updatedAt: now,
|
|
65
|
-
expiresAt,
|
|
66
|
-
ttlSeconds: opts2?.ttlSeconds,
|
|
67
|
-
description: opts2?.description,
|
|
68
|
-
tags: opts2?.tags,
|
|
69
|
-
entangled: opts2?.entangled,
|
|
70
|
-
accessCount: 0
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
function parseEnvelope(raw) {
|
|
75
|
-
try {
|
|
76
|
-
const parsed = JSON.parse(raw);
|
|
77
|
-
if (parsed && typeof parsed === "object" && parsed.v === 1) {
|
|
78
|
-
return parsed;
|
|
79
|
-
}
|
|
80
|
-
} catch {
|
|
81
|
-
}
|
|
82
|
-
return null;
|
|
83
|
-
}
|
|
84
|
-
function wrapLegacy(rawValue) {
|
|
85
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
86
|
-
return {
|
|
87
|
-
v: 1,
|
|
88
|
-
value: rawValue,
|
|
89
|
-
meta: {
|
|
90
|
-
createdAt: now,
|
|
91
|
-
updatedAt: now,
|
|
92
|
-
accessCount: 0
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
function serializeEnvelope(envelope) {
|
|
97
|
-
return JSON.stringify(envelope);
|
|
98
|
-
}
|
|
99
|
-
function collapseValue(envelope, env) {
|
|
100
|
-
if (envelope.states) {
|
|
101
|
-
const targetEnv = env ?? envelope.defaultEnv;
|
|
102
|
-
if (targetEnv && envelope.states[targetEnv]) {
|
|
103
|
-
return envelope.states[targetEnv];
|
|
104
|
-
}
|
|
105
|
-
if (envelope.defaultEnv && envelope.states[envelope.defaultEnv]) {
|
|
106
|
-
return envelope.states[envelope.defaultEnv];
|
|
107
|
-
}
|
|
108
|
-
const keys = Object.keys(envelope.states);
|
|
109
|
-
if (keys.length > 0) {
|
|
110
|
-
return envelope.states[keys[0]];
|
|
111
|
-
}
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
return envelope.value ?? null;
|
|
115
|
-
}
|
|
116
|
-
function checkDecay(envelope) {
|
|
117
|
-
if (!envelope.meta.expiresAt) {
|
|
118
|
-
return {
|
|
119
|
-
isExpired: false,
|
|
120
|
-
isStale: false,
|
|
121
|
-
lifetimePercent: 0,
|
|
122
|
-
secondsRemaining: null,
|
|
123
|
-
timeRemaining: null
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
const now = Date.now();
|
|
127
|
-
const expires = new Date(envelope.meta.expiresAt).getTime();
|
|
128
|
-
const created = new Date(envelope.meta.createdAt).getTime();
|
|
129
|
-
const totalLifetime = expires - created;
|
|
130
|
-
const elapsed = now - created;
|
|
131
|
-
const remaining = expires - now;
|
|
132
|
-
const lifetimePercent = totalLifetime > 0 ? Math.round(elapsed / totalLifetime * 100) : 100;
|
|
133
|
-
const secondsRemaining = Math.floor(remaining / 1e3);
|
|
134
|
-
let timeRemaining = null;
|
|
135
|
-
if (remaining > 0) {
|
|
136
|
-
const days = Math.floor(remaining / 864e5);
|
|
137
|
-
const hours = Math.floor(remaining % 864e5 / 36e5);
|
|
138
|
-
const minutes = Math.floor(remaining % 36e5 / 6e4);
|
|
139
|
-
if (days > 0) timeRemaining = `${days}d ${hours}h`;
|
|
140
|
-
else if (hours > 0) timeRemaining = `${hours}h ${minutes}m`;
|
|
141
|
-
else timeRemaining = `${minutes}m`;
|
|
142
|
-
} else {
|
|
143
|
-
timeRemaining = "expired";
|
|
144
|
-
}
|
|
145
|
-
return {
|
|
146
|
-
isExpired: remaining <= 0,
|
|
147
|
-
isStale: lifetimePercent >= 75,
|
|
148
|
-
lifetimePercent,
|
|
149
|
-
secondsRemaining,
|
|
150
|
-
timeRemaining
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
function recordAccess(envelope) {
|
|
154
|
-
return {
|
|
155
|
-
...envelope,
|
|
156
|
-
meta: {
|
|
157
|
-
...envelope.meta,
|
|
158
|
-
accessCount: envelope.meta.accessCount + 1,
|
|
159
|
-
lastAccessedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// src/core/collapse.ts
|
|
165
|
-
import { execSync } from "child_process";
|
|
166
|
-
import { existsSync, readFileSync } from "fs";
|
|
167
|
-
import { join } from "path";
|
|
168
|
-
var BRANCH_ENV_MAP = {
|
|
169
|
-
main: "prod",
|
|
170
|
-
master: "prod",
|
|
171
|
-
production: "prod",
|
|
172
|
-
develop: "dev",
|
|
173
|
-
development: "dev",
|
|
174
|
-
dev: "dev",
|
|
175
|
-
staging: "staging",
|
|
176
|
-
stage: "staging",
|
|
177
|
-
test: "test",
|
|
178
|
-
testing: "test"
|
|
179
|
-
};
|
|
180
|
-
function detectGitBranch(cwd) {
|
|
181
|
-
try {
|
|
182
|
-
const branch = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
183
|
-
cwd: cwd ?? process.cwd(),
|
|
184
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
185
|
-
encoding: "utf8",
|
|
186
|
-
timeout: 3e3
|
|
187
|
-
}).trim();
|
|
188
|
-
return branch || null;
|
|
189
|
-
} catch {
|
|
190
|
-
return null;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
function readProjectConfig(projectPath) {
|
|
194
|
-
const configPath = join(projectPath ?? process.cwd(), ".q-ring.json");
|
|
195
|
-
try {
|
|
196
|
-
if (existsSync(configPath)) {
|
|
197
|
-
return JSON.parse(readFileSync(configPath, "utf8"));
|
|
198
|
-
}
|
|
199
|
-
} catch {
|
|
200
|
-
}
|
|
201
|
-
return null;
|
|
202
|
-
}
|
|
203
|
-
function collapseEnvironment(ctx = {}) {
|
|
204
|
-
if (ctx.explicit) {
|
|
205
|
-
return { env: ctx.explicit, source: "explicit" };
|
|
206
|
-
}
|
|
207
|
-
const qringEnv = process.env.QRING_ENV;
|
|
208
|
-
if (qringEnv) {
|
|
209
|
-
return { env: qringEnv, source: "QRING_ENV" };
|
|
210
|
-
}
|
|
211
|
-
const nodeEnv = process.env.NODE_ENV;
|
|
212
|
-
if (nodeEnv) {
|
|
213
|
-
const mapped = mapEnvName(nodeEnv);
|
|
214
|
-
return { env: mapped, source: "NODE_ENV" };
|
|
215
|
-
}
|
|
216
|
-
const config = readProjectConfig(ctx.projectPath);
|
|
217
|
-
if (config?.env) {
|
|
218
|
-
return { env: config.env, source: "project-config" };
|
|
219
|
-
}
|
|
220
|
-
const branch = detectGitBranch(ctx.projectPath);
|
|
221
|
-
if (branch) {
|
|
222
|
-
const branchMap = { ...BRANCH_ENV_MAP, ...config?.branchMap };
|
|
223
|
-
const mapped = branchMap[branch];
|
|
224
|
-
if (mapped) {
|
|
225
|
-
return { env: mapped, source: "git-branch" };
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
if (config?.defaultEnv) {
|
|
229
|
-
return { env: config.defaultEnv, source: "project-config" };
|
|
230
|
-
}
|
|
231
|
-
return null;
|
|
232
|
-
}
|
|
233
|
-
function mapEnvName(raw) {
|
|
234
|
-
const lower = raw.toLowerCase();
|
|
235
|
-
if (lower === "production") return "prod";
|
|
236
|
-
if (lower === "development") return "dev";
|
|
237
|
-
return lower;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// src/core/observer.ts
|
|
241
|
-
import { existsSync as existsSync2, mkdirSync, appendFileSync, readFileSync as readFileSync2 } from "fs";
|
|
242
|
-
import { join as join2 } from "path";
|
|
243
|
-
import { homedir } from "os";
|
|
244
|
-
function getAuditDir() {
|
|
245
|
-
const dir = join2(homedir(), ".config", "q-ring");
|
|
246
|
-
if (!existsSync2(dir)) {
|
|
247
|
-
mkdirSync(dir, { recursive: true });
|
|
248
|
-
}
|
|
249
|
-
return dir;
|
|
250
|
-
}
|
|
251
|
-
function getAuditPath() {
|
|
252
|
-
return join2(getAuditDir(), "audit.jsonl");
|
|
253
|
-
}
|
|
254
|
-
function logAudit(event) {
|
|
255
|
-
const full = {
|
|
256
|
-
...event,
|
|
257
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
258
|
-
pid: process.pid
|
|
259
|
-
};
|
|
260
|
-
try {
|
|
261
|
-
appendFileSync(getAuditPath(), JSON.stringify(full) + "\n");
|
|
262
|
-
} catch {
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
function queryAudit(query = {}) {
|
|
266
|
-
const path = getAuditPath();
|
|
267
|
-
if (!existsSync2(path)) return [];
|
|
268
|
-
try {
|
|
269
|
-
const lines = readFileSync2(path, "utf8").split("\n").filter((l) => l.trim());
|
|
270
|
-
let events = lines.map((line) => {
|
|
271
|
-
try {
|
|
272
|
-
return JSON.parse(line);
|
|
273
|
-
} catch {
|
|
274
|
-
return null;
|
|
275
|
-
}
|
|
276
|
-
}).filter((e) => e !== null);
|
|
277
|
-
if (query.key) {
|
|
278
|
-
events = events.filter((e) => e.key === query.key);
|
|
279
|
-
}
|
|
280
|
-
if (query.action) {
|
|
281
|
-
events = events.filter((e) => e.action === query.action);
|
|
282
|
-
}
|
|
283
|
-
if (query.since) {
|
|
284
|
-
const since = new Date(query.since).getTime();
|
|
285
|
-
events = events.filter(
|
|
286
|
-
(e) => new Date(e.timestamp).getTime() >= since
|
|
287
|
-
);
|
|
288
|
-
}
|
|
289
|
-
events.sort(
|
|
290
|
-
(a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
|
|
291
|
-
);
|
|
292
|
-
if (query.limit) {
|
|
293
|
-
events = events.slice(0, query.limit);
|
|
294
|
-
}
|
|
295
|
-
return events;
|
|
296
|
-
} catch {
|
|
297
|
-
return [];
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
function detectAnomalies(key) {
|
|
301
|
-
const recent = queryAudit({
|
|
302
|
-
key,
|
|
303
|
-
action: "read",
|
|
304
|
-
since: new Date(Date.now() - 36e5).toISOString()
|
|
305
|
-
// last hour
|
|
306
|
-
});
|
|
307
|
-
const anomalies = [];
|
|
308
|
-
if (key && recent.length > 50) {
|
|
309
|
-
anomalies.push({
|
|
310
|
-
type: "burst",
|
|
311
|
-
description: `${recent.length} reads of "${key}" in the last hour`,
|
|
312
|
-
events: recent.slice(0, 10)
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
const nightAccess = recent.filter((e) => {
|
|
316
|
-
const hour = new Date(e.timestamp).getHours();
|
|
317
|
-
return hour >= 1 && hour < 5;
|
|
318
|
-
});
|
|
319
|
-
if (nightAccess.length > 0) {
|
|
320
|
-
anomalies.push({
|
|
321
|
-
type: "unusual-hour",
|
|
322
|
-
description: `${nightAccess.length} access(es) during unusual hours (1am-5am)`,
|
|
323
|
-
events: nightAccess
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
return anomalies;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// src/core/entanglement.ts
|
|
330
|
-
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync, mkdirSync as mkdirSync2 } from "fs";
|
|
331
|
-
import { join as join3 } from "path";
|
|
332
|
-
import { homedir as homedir2 } from "os";
|
|
333
|
-
function getRegistryPath() {
|
|
334
|
-
const dir = join3(homedir2(), ".config", "q-ring");
|
|
335
|
-
if (!existsSync3(dir)) {
|
|
336
|
-
mkdirSync2(dir, { recursive: true });
|
|
337
|
-
}
|
|
338
|
-
return join3(dir, "entanglement.json");
|
|
339
|
-
}
|
|
340
|
-
function loadRegistry() {
|
|
341
|
-
const path = getRegistryPath();
|
|
342
|
-
if (!existsSync3(path)) {
|
|
343
|
-
return { pairs: [] };
|
|
344
|
-
}
|
|
345
|
-
try {
|
|
346
|
-
return JSON.parse(readFileSync3(path, "utf8"));
|
|
347
|
-
} catch {
|
|
348
|
-
return { pairs: [] };
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
function saveRegistry(registry) {
|
|
352
|
-
writeFileSync(getRegistryPath(), JSON.stringify(registry, null, 2));
|
|
353
|
-
}
|
|
354
|
-
function entangle(source, target) {
|
|
355
|
-
const registry = loadRegistry();
|
|
356
|
-
const exists = registry.pairs.some(
|
|
357
|
-
(p) => p.source.service === source.service && p.source.key === source.key && p.target.service === target.service && p.target.key === target.key
|
|
358
|
-
);
|
|
359
|
-
if (!exists) {
|
|
360
|
-
registry.pairs.push({
|
|
361
|
-
source,
|
|
362
|
-
target,
|
|
363
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
364
|
-
});
|
|
365
|
-
registry.pairs.push({
|
|
366
|
-
source: target,
|
|
367
|
-
target: source,
|
|
368
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
369
|
-
});
|
|
370
|
-
saveRegistry(registry);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
function findEntangled(source) {
|
|
374
|
-
const registry = loadRegistry();
|
|
375
|
-
return registry.pairs.filter(
|
|
376
|
-
(p) => p.source.service === source.service && p.source.key === source.key
|
|
377
|
-
).map((p) => p.target);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// src/core/keyring.ts
|
|
381
|
-
function readEnvelope(service, key) {
|
|
382
|
-
const entry = new Entry(service, key);
|
|
383
|
-
const raw = entry.getPassword();
|
|
384
|
-
if (raw === null) return null;
|
|
385
|
-
const envelope = parseEnvelope(raw);
|
|
386
|
-
return envelope ?? wrapLegacy(raw);
|
|
387
|
-
}
|
|
388
|
-
function writeEnvelope(service, key, envelope) {
|
|
389
|
-
const entry = new Entry(service, key);
|
|
390
|
-
entry.setPassword(serializeEnvelope(envelope));
|
|
391
|
-
}
|
|
392
|
-
function resolveEnv(opts2) {
|
|
393
|
-
if (opts2.env) return opts2.env;
|
|
394
|
-
const result = collapseEnvironment({ projectPath: opts2.projectPath });
|
|
395
|
-
return result?.env;
|
|
396
|
-
}
|
|
397
|
-
function getSecret(key, opts2 = {}) {
|
|
398
|
-
const scopes = resolveScope(opts2);
|
|
399
|
-
const env = resolveEnv(opts2);
|
|
400
|
-
const source = opts2.source ?? "cli";
|
|
401
|
-
for (const { service, scope } of scopes) {
|
|
402
|
-
const envelope = readEnvelope(service, key);
|
|
403
|
-
if (!envelope) continue;
|
|
404
|
-
const decay = checkDecay(envelope);
|
|
405
|
-
if (decay.isExpired) {
|
|
406
|
-
logAudit({
|
|
407
|
-
action: "read",
|
|
408
|
-
key,
|
|
409
|
-
scope,
|
|
410
|
-
source,
|
|
411
|
-
detail: "blocked: secret expired (quantum decay)"
|
|
412
|
-
});
|
|
413
|
-
continue;
|
|
414
|
-
}
|
|
415
|
-
const value = collapseValue(envelope, env);
|
|
416
|
-
if (value === null) continue;
|
|
417
|
-
const updated = recordAccess(envelope);
|
|
418
|
-
writeEnvelope(service, key, updated);
|
|
419
|
-
logAudit({ action: "read", key, scope, env, source });
|
|
420
|
-
return value;
|
|
421
|
-
}
|
|
422
|
-
return null;
|
|
423
|
-
}
|
|
424
|
-
function getEnvelope(key, opts2 = {}) {
|
|
425
|
-
const scopes = resolveScope(opts2);
|
|
426
|
-
for (const { service, scope } of scopes) {
|
|
427
|
-
const envelope = readEnvelope(service, key);
|
|
428
|
-
if (envelope) return { envelope, scope };
|
|
429
|
-
}
|
|
430
|
-
return null;
|
|
431
|
-
}
|
|
432
|
-
function setSecret(key, value, opts2 = {}) {
|
|
433
|
-
const scope = opts2.scope ?? "global";
|
|
434
|
-
const scopes = resolveScope({ ...opts2, scope });
|
|
435
|
-
const { service } = scopes[0];
|
|
436
|
-
const source = opts2.source ?? "cli";
|
|
437
|
-
const existing = readEnvelope(service, key);
|
|
438
|
-
let envelope;
|
|
439
|
-
if (opts2.states) {
|
|
440
|
-
envelope = createEnvelope("", {
|
|
441
|
-
states: opts2.states,
|
|
442
|
-
defaultEnv: opts2.defaultEnv,
|
|
443
|
-
description: opts2.description,
|
|
444
|
-
tags: opts2.tags,
|
|
445
|
-
ttlSeconds: opts2.ttlSeconds,
|
|
446
|
-
expiresAt: opts2.expiresAt,
|
|
447
|
-
entangled: existing?.meta.entangled
|
|
448
|
-
});
|
|
449
|
-
} else {
|
|
450
|
-
envelope = createEnvelope(value, {
|
|
451
|
-
description: opts2.description,
|
|
452
|
-
tags: opts2.tags,
|
|
453
|
-
ttlSeconds: opts2.ttlSeconds,
|
|
454
|
-
expiresAt: opts2.expiresAt,
|
|
455
|
-
entangled: existing?.meta.entangled
|
|
456
|
-
});
|
|
457
|
-
}
|
|
458
|
-
if (existing) {
|
|
459
|
-
envelope.meta.createdAt = existing.meta.createdAt;
|
|
460
|
-
envelope.meta.accessCount = existing.meta.accessCount;
|
|
461
|
-
}
|
|
462
|
-
writeEnvelope(service, key, envelope);
|
|
463
|
-
logAudit({ action: "write", key, scope, source });
|
|
464
|
-
const entangled = findEntangled({ service, key });
|
|
465
|
-
for (const target of entangled) {
|
|
466
|
-
try {
|
|
467
|
-
const targetEnvelope = readEnvelope(target.service, target.key);
|
|
468
|
-
if (targetEnvelope) {
|
|
469
|
-
if (opts2.states) {
|
|
470
|
-
targetEnvelope.states = opts2.states;
|
|
471
|
-
} else {
|
|
472
|
-
targetEnvelope.value = value;
|
|
473
|
-
}
|
|
474
|
-
targetEnvelope.meta.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
475
|
-
writeEnvelope(target.service, target.key, targetEnvelope);
|
|
476
|
-
logAudit({
|
|
477
|
-
action: "entangle",
|
|
478
|
-
key: target.key,
|
|
479
|
-
scope: "global",
|
|
480
|
-
source,
|
|
481
|
-
detail: `propagated from ${key}`
|
|
482
|
-
});
|
|
483
|
-
}
|
|
484
|
-
} catch {
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
function deleteSecret(key, opts2 = {}) {
|
|
489
|
-
const scopes = resolveScope(opts2);
|
|
490
|
-
const source = opts2.source ?? "cli";
|
|
491
|
-
let deleted = false;
|
|
492
|
-
for (const { service, scope } of scopes) {
|
|
493
|
-
const entry = new Entry(service, key);
|
|
494
|
-
try {
|
|
495
|
-
if (entry.deleteCredential()) {
|
|
496
|
-
deleted = true;
|
|
497
|
-
logAudit({ action: "delete", key, scope, source });
|
|
498
|
-
}
|
|
499
|
-
} catch {
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
return deleted;
|
|
503
|
-
}
|
|
504
|
-
function hasSecret(key, opts2 = {}) {
|
|
505
|
-
const scopes = resolveScope(opts2);
|
|
506
|
-
for (const { service } of scopes) {
|
|
507
|
-
const envelope = readEnvelope(service, key);
|
|
508
|
-
if (envelope) {
|
|
509
|
-
const decay = checkDecay(envelope);
|
|
510
|
-
if (!decay.isExpired) return true;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
return false;
|
|
514
|
-
}
|
|
515
|
-
function listSecrets(opts2 = {}) {
|
|
516
|
-
const source = opts2.source ?? "cli";
|
|
517
|
-
const services = [];
|
|
518
|
-
if (!opts2.scope || opts2.scope === "global") {
|
|
519
|
-
services.push({ service: globalService(), scope: "global" });
|
|
520
|
-
}
|
|
521
|
-
if ((!opts2.scope || opts2.scope === "project") && opts2.projectPath) {
|
|
522
|
-
services.push({
|
|
523
|
-
service: projectService(opts2.projectPath),
|
|
524
|
-
scope: "project"
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
const results = [];
|
|
528
|
-
const seen = /* @__PURE__ */ new Set();
|
|
529
|
-
for (const { service, scope } of services) {
|
|
530
|
-
try {
|
|
531
|
-
const credentials = findCredentials(service);
|
|
532
|
-
for (const cred of credentials) {
|
|
533
|
-
const id = `${scope}:${cred.account}`;
|
|
534
|
-
if (seen.has(id)) continue;
|
|
535
|
-
seen.add(id);
|
|
536
|
-
const envelope = parseEnvelope(cred.password) ?? wrapLegacy(cred.password);
|
|
537
|
-
const decay = checkDecay(envelope);
|
|
538
|
-
results.push({
|
|
539
|
-
key: cred.account,
|
|
540
|
-
scope,
|
|
541
|
-
envelope,
|
|
542
|
-
decay
|
|
543
|
-
});
|
|
544
|
-
}
|
|
545
|
-
} catch {
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
logAudit({ action: "list", source });
|
|
549
|
-
return results.sort((a, b) => a.key.localeCompare(b.key));
|
|
550
|
-
}
|
|
551
|
-
function entangleSecrets(sourceKey, sourceOpts, targetKey, targetOpts) {
|
|
552
|
-
const sourceScopes = resolveScope({ ...sourceOpts, scope: sourceOpts.scope ?? "global" });
|
|
553
|
-
const targetScopes = resolveScope({ ...targetOpts, scope: targetOpts.scope ?? "global" });
|
|
554
|
-
const source = { service: sourceScopes[0].service, key: sourceKey };
|
|
555
|
-
const target = { service: targetScopes[0].service, key: targetKey };
|
|
556
|
-
entangle(source, target);
|
|
557
|
-
logAudit({
|
|
558
|
-
action: "entangle",
|
|
559
|
-
key: sourceKey,
|
|
560
|
-
source: sourceOpts.source ?? "cli",
|
|
561
|
-
detail: `entangled with ${targetKey}`
|
|
562
|
-
});
|
|
563
|
-
}
|
|
564
|
-
|
|
565
28
|
// src/core/noise.ts
|
|
566
29
|
import { randomBytes, randomInt } from "crypto";
|
|
567
30
|
var ALPHA_NUM = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
@@ -716,77 +179,6 @@ function runHealthScan(config = {}) {
|
|
|
716
179
|
return report;
|
|
717
180
|
}
|
|
718
181
|
|
|
719
|
-
// src/core/tunnel.ts
|
|
720
|
-
var tunnelStore = /* @__PURE__ */ new Map();
|
|
721
|
-
var cleanupInterval = null;
|
|
722
|
-
function ensureCleanup() {
|
|
723
|
-
if (cleanupInterval) return;
|
|
724
|
-
cleanupInterval = setInterval(() => {
|
|
725
|
-
const now = Date.now();
|
|
726
|
-
for (const [id, entry] of tunnelStore) {
|
|
727
|
-
if (entry.expiresAt && now >= entry.expiresAt) {
|
|
728
|
-
tunnelStore.delete(id);
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
if (tunnelStore.size === 0 && cleanupInterval) {
|
|
732
|
-
clearInterval(cleanupInterval);
|
|
733
|
-
cleanupInterval = null;
|
|
734
|
-
}
|
|
735
|
-
}, 5e3);
|
|
736
|
-
if (cleanupInterval && typeof cleanupInterval === "object" && "unref" in cleanupInterval) {
|
|
737
|
-
cleanupInterval.unref();
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
function tunnelCreate(value, opts2 = {}) {
|
|
741
|
-
const id = `tun_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
742
|
-
const now = Date.now();
|
|
743
|
-
tunnelStore.set(id, {
|
|
744
|
-
value,
|
|
745
|
-
createdAt: now,
|
|
746
|
-
expiresAt: opts2.ttlSeconds ? now + opts2.ttlSeconds * 1e3 : void 0,
|
|
747
|
-
accessCount: 0,
|
|
748
|
-
maxReads: opts2.maxReads
|
|
749
|
-
});
|
|
750
|
-
ensureCleanup();
|
|
751
|
-
return id;
|
|
752
|
-
}
|
|
753
|
-
function tunnelRead(id) {
|
|
754
|
-
const entry = tunnelStore.get(id);
|
|
755
|
-
if (!entry) return null;
|
|
756
|
-
if (entry.expiresAt && Date.now() >= entry.expiresAt) {
|
|
757
|
-
tunnelStore.delete(id);
|
|
758
|
-
return null;
|
|
759
|
-
}
|
|
760
|
-
entry.accessCount++;
|
|
761
|
-
if (entry.maxReads && entry.accessCount >= entry.maxReads) {
|
|
762
|
-
const value = entry.value;
|
|
763
|
-
tunnelStore.delete(id);
|
|
764
|
-
return value;
|
|
765
|
-
}
|
|
766
|
-
return entry.value;
|
|
767
|
-
}
|
|
768
|
-
function tunnelDestroy(id) {
|
|
769
|
-
return tunnelStore.delete(id);
|
|
770
|
-
}
|
|
771
|
-
function tunnelList() {
|
|
772
|
-
const now = Date.now();
|
|
773
|
-
const result = [];
|
|
774
|
-
for (const [id, entry] of tunnelStore) {
|
|
775
|
-
if (entry.expiresAt && now >= entry.expiresAt) {
|
|
776
|
-
tunnelStore.delete(id);
|
|
777
|
-
continue;
|
|
778
|
-
}
|
|
779
|
-
result.push({
|
|
780
|
-
id,
|
|
781
|
-
createdAt: entry.createdAt,
|
|
782
|
-
expiresAt: entry.expiresAt,
|
|
783
|
-
accessCount: entry.accessCount,
|
|
784
|
-
maxReads: entry.maxReads
|
|
785
|
-
});
|
|
786
|
-
}
|
|
787
|
-
return result;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
182
|
// src/core/teleport.ts
|
|
791
183
|
import {
|
|
792
184
|
randomBytes as randomBytes2,
|
|
@@ -1316,6 +708,23 @@ ${preview}`);
|
|
|
1316
708
|
return text(summary.join("\n"));
|
|
1317
709
|
}
|
|
1318
710
|
);
|
|
711
|
+
let dashboardInstance = null;
|
|
712
|
+
server2.tool(
|
|
713
|
+
"status_dashboard",
|
|
714
|
+
"Launch the quantum status dashboard \u2014 a local web page showing live health, decay timers, superposition states, entanglement graph, tunnels, audit log, and anomaly alerts. Returns the URL to open in a browser.",
|
|
715
|
+
{
|
|
716
|
+
port: z.number().optional().default(9876).describe("Port to serve on")
|
|
717
|
+
},
|
|
718
|
+
async (params) => {
|
|
719
|
+
if (dashboardInstance) {
|
|
720
|
+
return text(`Dashboard already running at http://127.0.0.1:${dashboardInstance.port}`);
|
|
721
|
+
}
|
|
722
|
+
const { startDashboardServer } = await import("./dashboard-7GII7BS2.js");
|
|
723
|
+
dashboardInstance = startDashboardServer({ port: params.port });
|
|
724
|
+
return text(`Dashboard started at http://127.0.0.1:${dashboardInstance.port}
|
|
725
|
+
Open this URL in a browser to see live quantum status.`);
|
|
726
|
+
}
|
|
727
|
+
);
|
|
1319
728
|
server2.tool(
|
|
1320
729
|
"agent_scan",
|
|
1321
730
|
"Run an autonomous agent health scan: checks decay, staleness, anomalies, and optionally auto-rotates expired secrets. Returns a structured report.",
|