@i4ctime/q-ring 0.2.8 → 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 +2 -2
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/core/envelope.ts
|
|
4
|
+
function createEnvelope(value, opts) {
|
|
5
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6
|
+
let expiresAt = opts?.expiresAt;
|
|
7
|
+
if (!expiresAt && opts?.ttlSeconds) {
|
|
8
|
+
expiresAt = new Date(Date.now() + opts.ttlSeconds * 1e3).toISOString();
|
|
9
|
+
}
|
|
10
|
+
return {
|
|
11
|
+
v: 1,
|
|
12
|
+
value: opts?.states ? void 0 : value,
|
|
13
|
+
states: opts?.states,
|
|
14
|
+
defaultEnv: opts?.defaultEnv,
|
|
15
|
+
meta: {
|
|
16
|
+
createdAt: now,
|
|
17
|
+
updatedAt: now,
|
|
18
|
+
expiresAt,
|
|
19
|
+
ttlSeconds: opts?.ttlSeconds,
|
|
20
|
+
description: opts?.description,
|
|
21
|
+
tags: opts?.tags,
|
|
22
|
+
entangled: opts?.entangled,
|
|
23
|
+
accessCount: 0
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function parseEnvelope(raw) {
|
|
28
|
+
try {
|
|
29
|
+
const parsed = JSON.parse(raw);
|
|
30
|
+
if (parsed && typeof parsed === "object" && parsed.v === 1) {
|
|
31
|
+
return parsed;
|
|
32
|
+
}
|
|
33
|
+
} catch {
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
function wrapLegacy(rawValue) {
|
|
38
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
39
|
+
return {
|
|
40
|
+
v: 1,
|
|
41
|
+
value: rawValue,
|
|
42
|
+
meta: {
|
|
43
|
+
createdAt: now,
|
|
44
|
+
updatedAt: now,
|
|
45
|
+
accessCount: 0
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function serializeEnvelope(envelope) {
|
|
50
|
+
return JSON.stringify(envelope);
|
|
51
|
+
}
|
|
52
|
+
function collapseValue(envelope, env) {
|
|
53
|
+
if (envelope.states) {
|
|
54
|
+
const targetEnv = env ?? envelope.defaultEnv;
|
|
55
|
+
if (targetEnv && envelope.states[targetEnv]) {
|
|
56
|
+
return envelope.states[targetEnv];
|
|
57
|
+
}
|
|
58
|
+
if (envelope.defaultEnv && envelope.states[envelope.defaultEnv]) {
|
|
59
|
+
return envelope.states[envelope.defaultEnv];
|
|
60
|
+
}
|
|
61
|
+
const keys = Object.keys(envelope.states);
|
|
62
|
+
if (keys.length > 0) {
|
|
63
|
+
return envelope.states[keys[0]];
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return envelope.value ?? null;
|
|
68
|
+
}
|
|
69
|
+
function checkDecay(envelope) {
|
|
70
|
+
if (!envelope.meta.expiresAt) {
|
|
71
|
+
return {
|
|
72
|
+
isExpired: false,
|
|
73
|
+
isStale: false,
|
|
74
|
+
lifetimePercent: 0,
|
|
75
|
+
secondsRemaining: null,
|
|
76
|
+
timeRemaining: null
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const now = Date.now();
|
|
80
|
+
const expires = new Date(envelope.meta.expiresAt).getTime();
|
|
81
|
+
const created = new Date(envelope.meta.createdAt).getTime();
|
|
82
|
+
const totalLifetime = expires - created;
|
|
83
|
+
const elapsed = now - created;
|
|
84
|
+
const remaining = expires - now;
|
|
85
|
+
const lifetimePercent = totalLifetime > 0 ? Math.round(elapsed / totalLifetime * 100) : 100;
|
|
86
|
+
const secondsRemaining = Math.floor(remaining / 1e3);
|
|
87
|
+
let timeRemaining = null;
|
|
88
|
+
if (remaining > 0) {
|
|
89
|
+
const days = Math.floor(remaining / 864e5);
|
|
90
|
+
const hours = Math.floor(remaining % 864e5 / 36e5);
|
|
91
|
+
const minutes = Math.floor(remaining % 36e5 / 6e4);
|
|
92
|
+
if (days > 0) timeRemaining = `${days}d ${hours}h`;
|
|
93
|
+
else if (hours > 0) timeRemaining = `${hours}h ${minutes}m`;
|
|
94
|
+
else timeRemaining = `${minutes}m`;
|
|
95
|
+
} else {
|
|
96
|
+
timeRemaining = "expired";
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
isExpired: remaining <= 0,
|
|
100
|
+
isStale: lifetimePercent >= 75,
|
|
101
|
+
lifetimePercent,
|
|
102
|
+
secondsRemaining,
|
|
103
|
+
timeRemaining
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function recordAccess(envelope) {
|
|
107
|
+
return {
|
|
108
|
+
...envelope,
|
|
109
|
+
meta: {
|
|
110
|
+
...envelope.meta,
|
|
111
|
+
accessCount: envelope.meta.accessCount + 1,
|
|
112
|
+
lastAccessedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/core/collapse.ts
|
|
118
|
+
import { execSync } from "child_process";
|
|
119
|
+
import { existsSync, readFileSync } from "fs";
|
|
120
|
+
import { join } from "path";
|
|
121
|
+
var BRANCH_ENV_MAP = {
|
|
122
|
+
main: "prod",
|
|
123
|
+
master: "prod",
|
|
124
|
+
production: "prod",
|
|
125
|
+
develop: "dev",
|
|
126
|
+
development: "dev",
|
|
127
|
+
dev: "dev",
|
|
128
|
+
staging: "staging",
|
|
129
|
+
stage: "staging",
|
|
130
|
+
test: "test",
|
|
131
|
+
testing: "test"
|
|
132
|
+
};
|
|
133
|
+
function detectGitBranch(cwd) {
|
|
134
|
+
try {
|
|
135
|
+
const branch = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
136
|
+
cwd: cwd ?? process.cwd(),
|
|
137
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
138
|
+
encoding: "utf8",
|
|
139
|
+
timeout: 3e3
|
|
140
|
+
}).trim();
|
|
141
|
+
return branch || null;
|
|
142
|
+
} catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function readProjectConfig(projectPath) {
|
|
147
|
+
const configPath = join(projectPath ?? process.cwd(), ".q-ring.json");
|
|
148
|
+
try {
|
|
149
|
+
if (existsSync(configPath)) {
|
|
150
|
+
return JSON.parse(readFileSync(configPath, "utf8"));
|
|
151
|
+
}
|
|
152
|
+
} catch {
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
function collapseEnvironment(ctx = {}) {
|
|
157
|
+
if (ctx.explicit) {
|
|
158
|
+
return { env: ctx.explicit, source: "explicit" };
|
|
159
|
+
}
|
|
160
|
+
const qringEnv = process.env.QRING_ENV;
|
|
161
|
+
if (qringEnv) {
|
|
162
|
+
return { env: qringEnv, source: "QRING_ENV" };
|
|
163
|
+
}
|
|
164
|
+
const nodeEnv = process.env.NODE_ENV;
|
|
165
|
+
if (nodeEnv) {
|
|
166
|
+
const mapped = mapEnvName(nodeEnv);
|
|
167
|
+
return { env: mapped, source: "NODE_ENV" };
|
|
168
|
+
}
|
|
169
|
+
const config = readProjectConfig(ctx.projectPath);
|
|
170
|
+
if (config?.env) {
|
|
171
|
+
return { env: config.env, source: "project-config" };
|
|
172
|
+
}
|
|
173
|
+
const branch = detectGitBranch(ctx.projectPath);
|
|
174
|
+
if (branch) {
|
|
175
|
+
const branchMap = { ...BRANCH_ENV_MAP, ...config?.branchMap };
|
|
176
|
+
const mapped = branchMap[branch];
|
|
177
|
+
if (mapped) {
|
|
178
|
+
return { env: mapped, source: "git-branch" };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (config?.defaultEnv) {
|
|
182
|
+
return { env: config.defaultEnv, source: "project-config" };
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
function mapEnvName(raw) {
|
|
187
|
+
const lower = raw.toLowerCase();
|
|
188
|
+
if (lower === "production") return "prod";
|
|
189
|
+
if (lower === "development") return "dev";
|
|
190
|
+
return lower;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/core/observer.ts
|
|
194
|
+
import { existsSync as existsSync2, mkdirSync, appendFileSync, readFileSync as readFileSync2 } from "fs";
|
|
195
|
+
import { join as join2 } from "path";
|
|
196
|
+
import { homedir } from "os";
|
|
197
|
+
function getAuditDir() {
|
|
198
|
+
const dir = join2(homedir(), ".config", "q-ring");
|
|
199
|
+
if (!existsSync2(dir)) {
|
|
200
|
+
mkdirSync(dir, { recursive: true });
|
|
201
|
+
}
|
|
202
|
+
return dir;
|
|
203
|
+
}
|
|
204
|
+
function getAuditPath() {
|
|
205
|
+
return join2(getAuditDir(), "audit.jsonl");
|
|
206
|
+
}
|
|
207
|
+
function logAudit(event) {
|
|
208
|
+
const full = {
|
|
209
|
+
...event,
|
|
210
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
211
|
+
pid: process.pid
|
|
212
|
+
};
|
|
213
|
+
try {
|
|
214
|
+
appendFileSync(getAuditPath(), JSON.stringify(full) + "\n");
|
|
215
|
+
} catch {
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function queryAudit(query = {}) {
|
|
219
|
+
const path = getAuditPath();
|
|
220
|
+
if (!existsSync2(path)) return [];
|
|
221
|
+
try {
|
|
222
|
+
const lines = readFileSync2(path, "utf8").split("\n").filter((l) => l.trim());
|
|
223
|
+
let events = lines.map((line) => {
|
|
224
|
+
try {
|
|
225
|
+
return JSON.parse(line);
|
|
226
|
+
} catch {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
}).filter((e) => e !== null);
|
|
230
|
+
if (query.key) {
|
|
231
|
+
events = events.filter((e) => e.key === query.key);
|
|
232
|
+
}
|
|
233
|
+
if (query.action) {
|
|
234
|
+
events = events.filter((e) => e.action === query.action);
|
|
235
|
+
}
|
|
236
|
+
if (query.since) {
|
|
237
|
+
const since = new Date(query.since).getTime();
|
|
238
|
+
events = events.filter(
|
|
239
|
+
(e) => new Date(e.timestamp).getTime() >= since
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
events.sort(
|
|
243
|
+
(a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
|
|
244
|
+
);
|
|
245
|
+
if (query.limit) {
|
|
246
|
+
events = events.slice(0, query.limit);
|
|
247
|
+
}
|
|
248
|
+
return events;
|
|
249
|
+
} catch {
|
|
250
|
+
return [];
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function detectAnomalies(key) {
|
|
254
|
+
const recent = queryAudit({
|
|
255
|
+
key,
|
|
256
|
+
action: "read",
|
|
257
|
+
since: new Date(Date.now() - 36e5).toISOString()
|
|
258
|
+
// last hour
|
|
259
|
+
});
|
|
260
|
+
const anomalies = [];
|
|
261
|
+
if (key && recent.length > 50) {
|
|
262
|
+
anomalies.push({
|
|
263
|
+
type: "burst",
|
|
264
|
+
description: `${recent.length} reads of "${key}" in the last hour`,
|
|
265
|
+
events: recent.slice(0, 10)
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
const nightAccess = recent.filter((e) => {
|
|
269
|
+
const hour = new Date(e.timestamp).getHours();
|
|
270
|
+
return hour >= 1 && hour < 5;
|
|
271
|
+
});
|
|
272
|
+
if (nightAccess.length > 0) {
|
|
273
|
+
anomalies.push({
|
|
274
|
+
type: "unusual-hour",
|
|
275
|
+
description: `${nightAccess.length} access(es) during unusual hours (1am-5am)`,
|
|
276
|
+
events: nightAccess
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
return anomalies;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// src/core/entanglement.ts
|
|
283
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync, mkdirSync as mkdirSync2 } from "fs";
|
|
284
|
+
import { join as join3 } from "path";
|
|
285
|
+
import { homedir as homedir2 } from "os";
|
|
286
|
+
function getRegistryPath() {
|
|
287
|
+
const dir = join3(homedir2(), ".config", "q-ring");
|
|
288
|
+
if (!existsSync3(dir)) {
|
|
289
|
+
mkdirSync2(dir, { recursive: true });
|
|
290
|
+
}
|
|
291
|
+
return join3(dir, "entanglement.json");
|
|
292
|
+
}
|
|
293
|
+
function loadRegistry() {
|
|
294
|
+
const path = getRegistryPath();
|
|
295
|
+
if (!existsSync3(path)) {
|
|
296
|
+
return { pairs: [] };
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
return JSON.parse(readFileSync3(path, "utf8"));
|
|
300
|
+
} catch {
|
|
301
|
+
return { pairs: [] };
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function saveRegistry(registry) {
|
|
305
|
+
writeFileSync(getRegistryPath(), JSON.stringify(registry, null, 2));
|
|
306
|
+
}
|
|
307
|
+
function entangle(source, target) {
|
|
308
|
+
const registry = loadRegistry();
|
|
309
|
+
const exists = registry.pairs.some(
|
|
310
|
+
(p) => p.source.service === source.service && p.source.key === source.key && p.target.service === target.service && p.target.key === target.key
|
|
311
|
+
);
|
|
312
|
+
if (!exists) {
|
|
313
|
+
registry.pairs.push({
|
|
314
|
+
source,
|
|
315
|
+
target,
|
|
316
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
317
|
+
});
|
|
318
|
+
registry.pairs.push({
|
|
319
|
+
source: target,
|
|
320
|
+
target: source,
|
|
321
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
322
|
+
});
|
|
323
|
+
saveRegistry(registry);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
function findEntangled(source) {
|
|
327
|
+
const registry = loadRegistry();
|
|
328
|
+
return registry.pairs.filter(
|
|
329
|
+
(p) => p.source.service === source.service && p.source.key === source.key
|
|
330
|
+
).map((p) => p.target);
|
|
331
|
+
}
|
|
332
|
+
function listEntanglements() {
|
|
333
|
+
return loadRegistry().pairs;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// src/core/keyring.ts
|
|
337
|
+
import { Entry, findCredentials } from "@napi-rs/keyring";
|
|
338
|
+
|
|
339
|
+
// src/utils/hash.ts
|
|
340
|
+
import { createHash } from "crypto";
|
|
341
|
+
function hashProjectPath(projectPath) {
|
|
342
|
+
return createHash("sha256").update(projectPath).digest("hex").slice(0, 12);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// src/core/scope.ts
|
|
346
|
+
var SERVICE_PREFIX = "q-ring";
|
|
347
|
+
function globalService() {
|
|
348
|
+
return `${SERVICE_PREFIX}:global`;
|
|
349
|
+
}
|
|
350
|
+
function projectService(projectPath) {
|
|
351
|
+
const hash = hashProjectPath(projectPath);
|
|
352
|
+
return `${SERVICE_PREFIX}:project:${hash}`;
|
|
353
|
+
}
|
|
354
|
+
function resolveScope(opts) {
|
|
355
|
+
const { scope, projectPath } = opts;
|
|
356
|
+
if (scope === "global") {
|
|
357
|
+
return [{ scope: "global", service: globalService() }];
|
|
358
|
+
}
|
|
359
|
+
if (scope === "project") {
|
|
360
|
+
if (!projectPath) {
|
|
361
|
+
throw new Error("Project path is required for project scope");
|
|
362
|
+
}
|
|
363
|
+
return [
|
|
364
|
+
{ scope: "project", service: projectService(projectPath), projectPath }
|
|
365
|
+
];
|
|
366
|
+
}
|
|
367
|
+
if (projectPath) {
|
|
368
|
+
return [
|
|
369
|
+
{ scope: "project", service: projectService(projectPath), projectPath },
|
|
370
|
+
{ scope: "global", service: globalService() }
|
|
371
|
+
];
|
|
372
|
+
}
|
|
373
|
+
return [{ scope: "global", service: globalService() }];
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// src/core/keyring.ts
|
|
377
|
+
function readEnvelope(service, key) {
|
|
378
|
+
const entry = new Entry(service, key);
|
|
379
|
+
const raw = entry.getPassword();
|
|
380
|
+
if (raw === null) return null;
|
|
381
|
+
const envelope = parseEnvelope(raw);
|
|
382
|
+
return envelope ?? wrapLegacy(raw);
|
|
383
|
+
}
|
|
384
|
+
function writeEnvelope(service, key, envelope) {
|
|
385
|
+
const entry = new Entry(service, key);
|
|
386
|
+
entry.setPassword(serializeEnvelope(envelope));
|
|
387
|
+
}
|
|
388
|
+
function resolveEnv(opts) {
|
|
389
|
+
if (opts.env) return opts.env;
|
|
390
|
+
const result = collapseEnvironment({ projectPath: opts.projectPath });
|
|
391
|
+
return result?.env;
|
|
392
|
+
}
|
|
393
|
+
function getSecret(key, opts = {}) {
|
|
394
|
+
const scopes = resolveScope(opts);
|
|
395
|
+
const env = resolveEnv(opts);
|
|
396
|
+
const source = opts.source ?? "cli";
|
|
397
|
+
for (const { service, scope } of scopes) {
|
|
398
|
+
const envelope = readEnvelope(service, key);
|
|
399
|
+
if (!envelope) continue;
|
|
400
|
+
const decay = checkDecay(envelope);
|
|
401
|
+
if (decay.isExpired) {
|
|
402
|
+
logAudit({
|
|
403
|
+
action: "read",
|
|
404
|
+
key,
|
|
405
|
+
scope,
|
|
406
|
+
source,
|
|
407
|
+
detail: "blocked: secret expired (quantum decay)"
|
|
408
|
+
});
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
const value = collapseValue(envelope, env);
|
|
412
|
+
if (value === null) continue;
|
|
413
|
+
const updated = recordAccess(envelope);
|
|
414
|
+
writeEnvelope(service, key, updated);
|
|
415
|
+
logAudit({ action: "read", key, scope, env, source });
|
|
416
|
+
return value;
|
|
417
|
+
}
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
function getEnvelope(key, opts = {}) {
|
|
421
|
+
const scopes = resolveScope(opts);
|
|
422
|
+
for (const { service, scope } of scopes) {
|
|
423
|
+
const envelope = readEnvelope(service, key);
|
|
424
|
+
if (envelope) return { envelope, scope };
|
|
425
|
+
}
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
function setSecret(key, value, opts = {}) {
|
|
429
|
+
const scope = opts.scope ?? "global";
|
|
430
|
+
const scopes = resolveScope({ ...opts, scope });
|
|
431
|
+
const { service } = scopes[0];
|
|
432
|
+
const source = opts.source ?? "cli";
|
|
433
|
+
const existing = readEnvelope(service, key);
|
|
434
|
+
let envelope;
|
|
435
|
+
if (opts.states) {
|
|
436
|
+
envelope = createEnvelope("", {
|
|
437
|
+
states: opts.states,
|
|
438
|
+
defaultEnv: opts.defaultEnv,
|
|
439
|
+
description: opts.description,
|
|
440
|
+
tags: opts.tags,
|
|
441
|
+
ttlSeconds: opts.ttlSeconds,
|
|
442
|
+
expiresAt: opts.expiresAt,
|
|
443
|
+
entangled: existing?.meta.entangled
|
|
444
|
+
});
|
|
445
|
+
} else {
|
|
446
|
+
envelope = createEnvelope(value, {
|
|
447
|
+
description: opts.description,
|
|
448
|
+
tags: opts.tags,
|
|
449
|
+
ttlSeconds: opts.ttlSeconds,
|
|
450
|
+
expiresAt: opts.expiresAt,
|
|
451
|
+
entangled: existing?.meta.entangled
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
if (existing) {
|
|
455
|
+
envelope.meta.createdAt = existing.meta.createdAt;
|
|
456
|
+
envelope.meta.accessCount = existing.meta.accessCount;
|
|
457
|
+
}
|
|
458
|
+
writeEnvelope(service, key, envelope);
|
|
459
|
+
logAudit({ action: "write", key, scope, source });
|
|
460
|
+
const entangled = findEntangled({ service, key });
|
|
461
|
+
for (const target of entangled) {
|
|
462
|
+
try {
|
|
463
|
+
const targetEnvelope = readEnvelope(target.service, target.key);
|
|
464
|
+
if (targetEnvelope) {
|
|
465
|
+
if (opts.states) {
|
|
466
|
+
targetEnvelope.states = opts.states;
|
|
467
|
+
} else {
|
|
468
|
+
targetEnvelope.value = value;
|
|
469
|
+
}
|
|
470
|
+
targetEnvelope.meta.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
471
|
+
writeEnvelope(target.service, target.key, targetEnvelope);
|
|
472
|
+
logAudit({
|
|
473
|
+
action: "entangle",
|
|
474
|
+
key: target.key,
|
|
475
|
+
scope: "global",
|
|
476
|
+
source,
|
|
477
|
+
detail: `propagated from ${key}`
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
} catch {
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
function deleteSecret(key, opts = {}) {
|
|
485
|
+
const scopes = resolveScope(opts);
|
|
486
|
+
const source = opts.source ?? "cli";
|
|
487
|
+
let deleted = false;
|
|
488
|
+
for (const { service, scope } of scopes) {
|
|
489
|
+
const entry = new Entry(service, key);
|
|
490
|
+
try {
|
|
491
|
+
if (entry.deleteCredential()) {
|
|
492
|
+
deleted = true;
|
|
493
|
+
logAudit({ action: "delete", key, scope, source });
|
|
494
|
+
}
|
|
495
|
+
} catch {
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return deleted;
|
|
499
|
+
}
|
|
500
|
+
function listSecrets(opts = {}) {
|
|
501
|
+
const source = opts.source ?? "cli";
|
|
502
|
+
const services = [];
|
|
503
|
+
if (!opts.scope || opts.scope === "global") {
|
|
504
|
+
services.push({ service: globalService(), scope: "global" });
|
|
505
|
+
}
|
|
506
|
+
if ((!opts.scope || opts.scope === "project") && opts.projectPath) {
|
|
507
|
+
services.push({
|
|
508
|
+
service: projectService(opts.projectPath),
|
|
509
|
+
scope: "project"
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
const results = [];
|
|
513
|
+
const seen = /* @__PURE__ */ new Set();
|
|
514
|
+
for (const { service, scope } of services) {
|
|
515
|
+
try {
|
|
516
|
+
const credentials = findCredentials(service);
|
|
517
|
+
for (const cred of credentials) {
|
|
518
|
+
const id = `${scope}:${cred.account}`;
|
|
519
|
+
if (seen.has(id)) continue;
|
|
520
|
+
seen.add(id);
|
|
521
|
+
const envelope = parseEnvelope(cred.password) ?? wrapLegacy(cred.password);
|
|
522
|
+
const decay = checkDecay(envelope);
|
|
523
|
+
results.push({
|
|
524
|
+
key: cred.account,
|
|
525
|
+
scope,
|
|
526
|
+
envelope,
|
|
527
|
+
decay
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
} catch {
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
logAudit({ action: "list", source });
|
|
534
|
+
return results.sort((a, b) => a.key.localeCompare(b.key));
|
|
535
|
+
}
|
|
536
|
+
function exportSecrets(opts = {}) {
|
|
537
|
+
const format = opts.format ?? "env";
|
|
538
|
+
const env = resolveEnv(opts);
|
|
539
|
+
const entries = listSecrets(opts);
|
|
540
|
+
const source = opts.source ?? "cli";
|
|
541
|
+
const merged = /* @__PURE__ */ new Map();
|
|
542
|
+
const globalEntries = entries.filter((e) => e.scope === "global");
|
|
543
|
+
const projectEntries = entries.filter((e) => e.scope === "project");
|
|
544
|
+
for (const entry of [...globalEntries, ...projectEntries]) {
|
|
545
|
+
if (entry.envelope) {
|
|
546
|
+
const decay = checkDecay(entry.envelope);
|
|
547
|
+
if (decay.isExpired) continue;
|
|
548
|
+
const value = collapseValue(entry.envelope, env);
|
|
549
|
+
if (value !== null) {
|
|
550
|
+
merged.set(entry.key, value);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
logAudit({ action: "export", source, detail: `format=${format}` });
|
|
555
|
+
if (format === "json") {
|
|
556
|
+
const obj = {};
|
|
557
|
+
for (const [key, value] of merged) {
|
|
558
|
+
obj[key] = value;
|
|
559
|
+
}
|
|
560
|
+
return JSON.stringify(obj, null, 2);
|
|
561
|
+
}
|
|
562
|
+
const lines = [];
|
|
563
|
+
for (const [key, value] of merged) {
|
|
564
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
|
|
565
|
+
lines.push(`${key}="${escaped}"`);
|
|
566
|
+
}
|
|
567
|
+
return lines.join("\n");
|
|
568
|
+
}
|
|
569
|
+
function entangleSecrets(sourceKey, sourceOpts, targetKey, targetOpts) {
|
|
570
|
+
const sourceScopes = resolveScope({ ...sourceOpts, scope: sourceOpts.scope ?? "global" });
|
|
571
|
+
const targetScopes = resolveScope({ ...targetOpts, scope: targetOpts.scope ?? "global" });
|
|
572
|
+
const source = { service: sourceScopes[0].service, key: sourceKey };
|
|
573
|
+
const target = { service: targetScopes[0].service, key: targetKey };
|
|
574
|
+
entangle(source, target);
|
|
575
|
+
logAudit({
|
|
576
|
+
action: "entangle",
|
|
577
|
+
key: sourceKey,
|
|
578
|
+
source: sourceOpts.source ?? "cli",
|
|
579
|
+
detail: `entangled with ${targetKey}`
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// src/core/tunnel.ts
|
|
584
|
+
var tunnelStore = /* @__PURE__ */ new Map();
|
|
585
|
+
var cleanupInterval = null;
|
|
586
|
+
function ensureCleanup() {
|
|
587
|
+
if (cleanupInterval) return;
|
|
588
|
+
cleanupInterval = setInterval(() => {
|
|
589
|
+
const now = Date.now();
|
|
590
|
+
for (const [id, entry] of tunnelStore) {
|
|
591
|
+
if (entry.expiresAt && now >= entry.expiresAt) {
|
|
592
|
+
tunnelStore.delete(id);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
if (tunnelStore.size === 0 && cleanupInterval) {
|
|
596
|
+
clearInterval(cleanupInterval);
|
|
597
|
+
cleanupInterval = null;
|
|
598
|
+
}
|
|
599
|
+
}, 5e3);
|
|
600
|
+
if (cleanupInterval && typeof cleanupInterval === "object" && "unref" in cleanupInterval) {
|
|
601
|
+
cleanupInterval.unref();
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
function tunnelCreate(value, opts = {}) {
|
|
605
|
+
const id = `tun_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
606
|
+
const now = Date.now();
|
|
607
|
+
tunnelStore.set(id, {
|
|
608
|
+
value,
|
|
609
|
+
createdAt: now,
|
|
610
|
+
expiresAt: opts.ttlSeconds ? now + opts.ttlSeconds * 1e3 : void 0,
|
|
611
|
+
accessCount: 0,
|
|
612
|
+
maxReads: opts.maxReads
|
|
613
|
+
});
|
|
614
|
+
ensureCleanup();
|
|
615
|
+
return id;
|
|
616
|
+
}
|
|
617
|
+
function tunnelRead(id) {
|
|
618
|
+
const entry = tunnelStore.get(id);
|
|
619
|
+
if (!entry) return null;
|
|
620
|
+
if (entry.expiresAt && Date.now() >= entry.expiresAt) {
|
|
621
|
+
tunnelStore.delete(id);
|
|
622
|
+
return null;
|
|
623
|
+
}
|
|
624
|
+
entry.accessCount++;
|
|
625
|
+
if (entry.maxReads && entry.accessCount >= entry.maxReads) {
|
|
626
|
+
const value = entry.value;
|
|
627
|
+
tunnelStore.delete(id);
|
|
628
|
+
return value;
|
|
629
|
+
}
|
|
630
|
+
return entry.value;
|
|
631
|
+
}
|
|
632
|
+
function tunnelDestroy(id) {
|
|
633
|
+
return tunnelStore.delete(id);
|
|
634
|
+
}
|
|
635
|
+
function tunnelList() {
|
|
636
|
+
const now = Date.now();
|
|
637
|
+
const result = [];
|
|
638
|
+
for (const [id, entry] of tunnelStore) {
|
|
639
|
+
if (entry.expiresAt && now >= entry.expiresAt) {
|
|
640
|
+
tunnelStore.delete(id);
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
result.push({
|
|
644
|
+
id,
|
|
645
|
+
createdAt: entry.createdAt,
|
|
646
|
+
expiresAt: entry.expiresAt,
|
|
647
|
+
accessCount: entry.accessCount,
|
|
648
|
+
maxReads: entry.maxReads
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
return result;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
export {
|
|
655
|
+
checkDecay,
|
|
656
|
+
collapseEnvironment,
|
|
657
|
+
logAudit,
|
|
658
|
+
queryAudit,
|
|
659
|
+
detectAnomalies,
|
|
660
|
+
listEntanglements,
|
|
661
|
+
getSecret,
|
|
662
|
+
getEnvelope,
|
|
663
|
+
setSecret,
|
|
664
|
+
deleteSecret,
|
|
665
|
+
listSecrets,
|
|
666
|
+
exportSecrets,
|
|
667
|
+
entangleSecrets,
|
|
668
|
+
tunnelCreate,
|
|
669
|
+
tunnelRead,
|
|
670
|
+
tunnelDestroy,
|
|
671
|
+
tunnelList
|
|
672
|
+
};
|
|
673
|
+
//# sourceMappingURL=chunk-WQPJ2FTM.js.map
|