@i4ctime/q-ring 0.10.1 → 0.11.5

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/mcp.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ PACKAGE_VERSION,
3
4
  checkDecay,
4
5
  checkExecPolicy,
5
6
  checkKeyReadPolicy,
@@ -13,6 +14,7 @@ import {
13
14
  exportAudit,
14
15
  exportSecrets,
15
16
  fireHooks,
17
+ forget,
16
18
  getEnvelope,
17
19
  getExecMaxRuntime,
18
20
  getPolicySummary,
@@ -20,12 +22,15 @@ import {
20
22
  hasSecret,
21
23
  httpRequest,
22
24
  listHooks,
25
+ listMemory,
23
26
  listSecrets,
24
27
  logAudit,
25
28
  queryAudit,
26
29
  readProjectConfig,
30
+ recall,
27
31
  registerHook,
28
32
  registry,
33
+ remember,
29
34
  removeHook,
30
35
  setSecret,
31
36
  tunnelCreate,
@@ -33,7 +38,7 @@ import {
33
38
  tunnelList,
34
39
  tunnelRead,
35
40
  verifyAuditChain
36
- } from "./chunk-E46HG5NM.js";
41
+ } from "./chunk-MQM4DLPI.js";
37
42
 
38
43
  // src/mcp.ts
39
44
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -41,23 +46,6 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
41
46
  // src/mcp/server.ts
42
47
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
43
48
 
44
- // src/version.ts
45
- import { readFileSync } from "fs";
46
- import { dirname, join } from "path";
47
- import { fileURLToPath } from "url";
48
- function readPackageVersion() {
49
- try {
50
- const here = dirname(fileURLToPath(import.meta.url));
51
- const pkgPath = join(here, "..", "package.json");
52
- const raw = readFileSync(pkgPath, "utf8");
53
- const pkg = JSON.parse(raw);
54
- return typeof pkg.version === "string" ? pkg.version : "0.0.0";
55
- } catch {
56
- return "0.0.0";
57
- }
58
- }
59
- var PACKAGE_VERSION = readPackageVersion();
60
-
61
49
  // src/mcp/tools/secrets.ts
62
50
  import { z as z2 } from "zod";
63
51
 
@@ -163,7 +151,7 @@ function estimateEntropy(secret) {
163
151
  }
164
152
 
165
153
  // src/core/import.ts
166
- import { readFileSync as readFileSync2 } from "fs";
154
+ import { readFileSync } from "fs";
167
155
  function parseDotenv(content) {
168
156
  const result = /* @__PURE__ */ new Map();
169
157
  const lines = content.split(/\r?\n/);
@@ -195,7 +183,7 @@ function parseDotenv(content) {
195
183
  function importDotenv(filePathOrContent, options = {}) {
196
184
  let content;
197
185
  try {
198
- content = readFileSync2(filePathOrContent, "utf8");
186
+ content = readFileSync(filePathOrContent, "utf8");
199
187
  } catch {
200
188
  content = filePathOrContent;
201
189
  }
@@ -350,7 +338,7 @@ function registerSecretTools(server2) {
350
338
  );
351
339
  server2.tool(
352
340
  "set_secret",
353
- "[secrets] Store a secret with optional quantum metadata: TTL (decay), environment state (superposition), description, tags.",
341
+ "[secrets] Create or overwrite a secret value plus optional metadata (TTL/decay, per-env superposition, description, tags). Overwrites existing values for the same key/scope; records access in the audit log. Use import_dotenv for bulk .env ingest. Subject to tool policy; no external rate limits beyond provider calls when validating elsewhere.",
354
342
  {
355
343
  key: z2.string().describe("The secret key name"),
356
344
  value: z2.string().describe("The secret value"),
@@ -413,7 +401,7 @@ function registerSecretTools(server2) {
413
401
  );
414
402
  server2.tool(
415
403
  "delete_secret",
416
- "[secrets] Remove a secret from the keyring.",
404
+ "[secrets] Permanently remove a secret value from the keyring for the given scope/path (not recoverable from q-ring). Does not remove hooks, tunnels, or entanglement metadata alone\u2014use remove_hook, tunnel_destroy, or disentangle_secrets respectively. Returns success or not-found text; subject to tool policy.",
417
405
  {
418
406
  key: z2.string().describe("The secret key name"),
419
407
  scope,
@@ -596,7 +584,7 @@ function registerSecretTools(server2) {
596
584
  );
597
585
  server2.tool(
598
586
  "entangle_secrets",
599
- "[secrets] Create a quantum entanglement between two secrets. When the source is rotated/updated, the target automatically receives the same value.",
587
+ "[secrets] Link two keys so source updates/rotations propagate the same value to the target (mutates metadata; future writes sync). Reverse with disentangle_secrets without deleting values; do not confuse with set_secret (single-key write). Subject to tool policy.",
600
588
  {
601
589
  sourceKey: z2.string().describe("Source secret key"),
602
590
  targetKey: z2.string().describe("Target secret key"),
@@ -630,7 +618,7 @@ function registerSecretTools(server2) {
630
618
  );
631
619
  server2.tool(
632
620
  "disentangle_secrets",
633
- "[secrets] Remove a quantum entanglement between two secrets. They will no longer synchronize on rotation.",
621
+ "[secrets] Remove the sync link between two keys so rotations stop propagating. Does not delete either secret\u2014use delete_secret to erase values. Contrast entangle_secrets (creates link). Safe if the link was already absent; updates metadata; subject to tool policy.",
634
622
  {
635
623
  sourceKey: z2.string().describe("Source secret key"),
636
624
  targetKey: z2.string().describe("Target secret key"),
@@ -1416,7 +1404,7 @@ function registerAuditTools(server2) {
1416
1404
  );
1417
1405
  server2.tool(
1418
1406
  "detect_anomalies",
1419
- "[audit] Scan for anomalous secret access patterns: burst reads, unusual-hour access. Returns findings and recommendations.",
1407
+ "[audit] Read-only scan of audit history for burst reads and unusual-hour access (text lines per finding). Optional key filter. Use health_check for full decay inventory + anomaly count in scope; use agent_scan for multi-project JSON reports or optional auto-rotation. Does not mutate secrets.",
1420
1408
  {
1421
1409
  key: z6.string().optional().describe("Check anomalies for a specific key")
1422
1410
  },
@@ -1431,7 +1419,7 @@ function registerAuditTools(server2) {
1431
1419
  );
1432
1420
  server2.tool(
1433
1421
  "health_check",
1434
- "[health] Run a comprehensive health check on all secrets: decay status, staleness, anomalies, entropy assessment.",
1422
+ "[health] Read-only scoped pass: decay/stale/expired counts, per-secret issue lines, plus audit-derived anomalies. No writes. Use check_project for .q-ring.json manifest compliance; use detect_anomalies for audit-pattern-only triage; use agent_scan for multi-project JSON or optional autoRotate credential replacement.",
1435
1423
  {
1436
1424
  scope: scope4,
1437
1425
  projectPath: projectPath4,
@@ -1680,7 +1668,7 @@ function registerHookTools(server2) {
1680
1668
  );
1681
1669
  server2.tool(
1682
1670
  "remove_hook",
1683
- "[hooks] Remove a registered hook by ID.",
1671
+ "[hooks] Remove a lifecycle hook entry by id from the hook registry only (stops callbacks; does not touch secret values). Call list_hooks first for ids. Contrast delete_secret (credential removal) or tunnel_destroy (ephemeral tunnel). Returns success or not-found; subject to tool policy.",
1684
1672
  {
1685
1673
  id: z8.string().describe("Hook ID to remove")
1686
1674
  },
@@ -1983,8 +1971,8 @@ async function execCommand(opts2) {
1983
1971
  }
1984
1972
 
1985
1973
  // src/core/scan.ts
1986
- import { readFileSync as readFileSync3, readdirSync, statSync } from "fs";
1987
- import { join as join2 } from "path";
1974
+ import { readFileSync as readFileSync2, readdirSync, statSync } from "fs";
1975
+ import { join } from "path";
1988
1976
 
1989
1977
  // src/core/secrets-detect.ts
1990
1978
  var SECRET_ASSIGNMENT_PATTERN = /((?:api_?key|secret|token|password|auth|credential|access_?key)[a-z0-9_]*)\s*[:=]\s*(['"])([^'"]+)\2/gi;
@@ -2078,7 +2066,7 @@ function scanCodebase(dir) {
2078
2066
  }
2079
2067
  for (const entry of entries) {
2080
2068
  if (IGNORE_DIRS.has(entry)) continue;
2081
- const fullPath = join2(currentDir, entry);
2069
+ const fullPath = join(currentDir, entry);
2082
2070
  let stat;
2083
2071
  try {
2084
2072
  stat = statSync(fullPath);
@@ -2092,7 +2080,7 @@ function scanCodebase(dir) {
2092
2080
  if (IGNORE_EXTS.has(ext) || entry.endsWith(".lock")) continue;
2093
2081
  let content;
2094
2082
  try {
2095
- content = readFileSync3(fullPath, "utf8");
2083
+ content = readFileSync2(fullPath, "utf8");
2096
2084
  } catch {
2097
2085
  continue;
2098
2086
  }
@@ -2122,7 +2110,7 @@ function scanCodebase(dir) {
2122
2110
  }
2123
2111
 
2124
2112
  // src/core/linter.ts
2125
- import { readFileSync as readFileSync4, writeFileSync, existsSync } from "fs";
2113
+ import { readFileSync as readFileSync3, writeFileSync, existsSync } from "fs";
2126
2114
  import { basename, extname } from "path";
2127
2115
  var ENV_REF_BY_EXT = {
2128
2116
  ".ts": (k) => `process.env.${k}`,
@@ -2153,7 +2141,7 @@ function lintFiles(files, opts2 = {}) {
2153
2141
  if (!existsSync(file)) continue;
2154
2142
  let content;
2155
2143
  try {
2156
- content = readFileSync4(file, "utf8");
2144
+ content = readFileSync3(file, "utf8");
2157
2145
  } catch {
2158
2146
  continue;
2159
2147
  }
@@ -2351,7 +2339,7 @@ ${result.stderr}`);
2351
2339
  let dashboardInstance = null;
2352
2340
  server2.tool(
2353
2341
  "status_dashboard",
2354
- "[dashboard] 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.",
2342
+ "[dashboard] Launch the quantum status dashboard \u2014 a local SSE-driven web page showing live KPIs (secrets, env, protected, approvals, hooks, 24h reads, anomalies), health summary, environment, .q-ring.json manifest gaps, governance policy summary, sortable searchable secrets table, decay/superposition/entanglement/tunnel cards, active approvals & hooks, agent memory, anomaly alerts, and a filterable 24h audit feed. Returns the URL to open in a browser. Never exposes secret values.",
2355
2343
  {
2356
2344
  port: z9.number().optional().default(9876).describe("Port to serve on")
2357
2345
  },
@@ -2363,7 +2351,7 @@ ${result.stderr}`);
2363
2351
  `Dashboard already running at http://127.0.0.1:${dashboardInstance.port}`
2364
2352
  );
2365
2353
  }
2366
- const { startDashboardServer } = await import("./dashboard-73QYM5FU.js");
2354
+ const { startDashboardServer } = await import("./dashboard-FSCJDLJX.js");
2367
2355
  dashboardInstance = startDashboardServer({ port: params.port });
2368
2356
  return text(
2369
2357
  `Dashboard started at http://127.0.0.1:${dashboardInstance.port}
@@ -2373,7 +2361,7 @@ Open this URL in a browser to see live quantum status.`
2373
2361
  );
2374
2362
  server2.tool(
2375
2363
  "agent_scan",
2376
- "[agent] Run an autonomous agent health scan: checks decay, staleness, anomalies, and optionally auto-rotates expired secrets. Returns a structured report.",
2364
+ "[agent] Multi-project health pass: decay, staleness, audit anomalies, manifest gaps; returns JSON. Prefer health_check for a read-only scoped decay/anomaly text summary (no writes). Prefer detect_anomalies for audit-pattern spikes on one key. With autoRotate=true, overwrites expired secret values in the keyring (credential change\u2014not undoable); leave false unless intentional rotation. Same policy gates as other MCP tools; no separate external auth.",
2377
2365
  {
2378
2366
  autoRotate: z9.boolean().optional().default(false).describe("Auto-rotate expired secrets with generated values"),
2379
2367
  projectPaths: z9.array(z9.string()).optional().describe("Project paths to monitor")
@@ -2392,122 +2380,6 @@ Open this URL in a browser to see live quantum status.`
2392
2380
 
2393
2381
  // src/mcp/tools/agent.ts
2394
2382
  import { z as z10 } from "zod";
2395
-
2396
- // src/core/memory.ts
2397
- import { existsSync as existsSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2, mkdirSync } from "fs";
2398
- import { join as join3 } from "path";
2399
- import { homedir, hostname, userInfo } from "os";
2400
- import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, createHash, randomBytes as randomBytes3 } from "crypto";
2401
- import { Entry } from "@napi-rs/keyring";
2402
- var MEMORY_FILE = "agent-memory.enc";
2403
- var KEYRING_SERVICE = "qring-memory-key";
2404
- var KEYRING_ACCOUNT = "encryption-key";
2405
- function getMemoryDir() {
2406
- const dir = join3(homedir(), ".config", "q-ring");
2407
- if (!existsSync2(dir)) {
2408
- mkdirSync(dir, { recursive: true });
2409
- }
2410
- return dir;
2411
- }
2412
- function getMemoryPath() {
2413
- return join3(getMemoryDir(), MEMORY_FILE);
2414
- }
2415
- function deriveLegacyKey() {
2416
- const fingerprint = `qring-memory:${hostname()}:${userInfo().username}`;
2417
- return createHash("sha256").update(fingerprint).digest();
2418
- }
2419
- function getOrCreateKey() {
2420
- try {
2421
- const entry = new Entry(KEYRING_SERVICE, KEYRING_ACCOUNT);
2422
- const stored = entry.getPassword();
2423
- if (stored) return Buffer.from(stored, "base64");
2424
- const key = randomBytes3(32);
2425
- entry.setPassword(key.toString("base64"));
2426
- return key;
2427
- } catch {
2428
- console.warn("[q-ring] OS keyring unavailable for memory key \u2014 falling back to machine-derived key");
2429
- return deriveLegacyKey();
2430
- }
2431
- }
2432
- function encryptWith(data, key) {
2433
- const iv = randomBytes3(12);
2434
- const cipher = createCipheriv2("aes-256-gcm", key, iv);
2435
- const encrypted = Buffer.concat([cipher.update(data, "utf8"), cipher.final()]);
2436
- const tag = cipher.getAuthTag();
2437
- return `${iv.toString("base64")}:${tag.toString("base64")}:${encrypted.toString("base64")}`;
2438
- }
2439
- function decryptWith(blob, key) {
2440
- const parts = blob.split(":");
2441
- if (parts.length !== 3) throw new Error("Invalid encrypted format");
2442
- const iv = Buffer.from(parts[0], "base64");
2443
- const tag = Buffer.from(parts[1], "base64");
2444
- const encrypted = Buffer.from(parts[2], "base64");
2445
- const decipher = createDecipheriv2("aes-256-gcm", key, iv);
2446
- decipher.setAuthTag(tag);
2447
- return decipher.update(encrypted) + decipher.final("utf8");
2448
- }
2449
- function encrypt(data) {
2450
- return encryptWith(data, getOrCreateKey());
2451
- }
2452
- function decrypt(blob) {
2453
- const key = getOrCreateKey();
2454
- try {
2455
- return decryptWith(blob, key);
2456
- } catch {
2457
- const legacy = deriveLegacyKey();
2458
- const plain = decryptWith(blob, legacy);
2459
- writeFileSync2(getMemoryPath(), encryptWith(plain, key), "utf8");
2460
- return plain;
2461
- }
2462
- }
2463
- function loadStore() {
2464
- const path = getMemoryPath();
2465
- if (!existsSync2(path)) {
2466
- return { entries: {} };
2467
- }
2468
- try {
2469
- const raw = readFileSync5(path, "utf8");
2470
- const decrypted = decrypt(raw);
2471
- return JSON.parse(decrypted);
2472
- } catch {
2473
- return { entries: {} };
2474
- }
2475
- }
2476
- function saveStore(store) {
2477
- const json = JSON.stringify(store);
2478
- const encrypted = encrypt(json);
2479
- writeFileSync2(getMemoryPath(), encrypted, "utf8");
2480
- }
2481
- function remember(key, value) {
2482
- const store = loadStore();
2483
- store.entries[key] = {
2484
- value,
2485
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2486
- };
2487
- saveStore(store);
2488
- }
2489
- function recall(key) {
2490
- const store = loadStore();
2491
- return store.entries[key]?.value ?? null;
2492
- }
2493
- function listMemory() {
2494
- const store = loadStore();
2495
- return Object.entries(store.entries).map(([key, entry]) => ({
2496
- key,
2497
- updatedAt: entry.updatedAt
2498
- }));
2499
- }
2500
- function forget(key) {
2501
- const store = loadStore();
2502
- if (key in store.entries) {
2503
- delete store.entries[key];
2504
- saveStore(store);
2505
- return true;
2506
- }
2507
- return false;
2508
- }
2509
-
2510
- // src/mcp/tools/agent.ts
2511
2383
  function registerAgentTools(server2) {
2512
2384
  server2.tool(
2513
2385
  "agent_remember",