@i4ctime/q-ring 0.3.2 → 0.4.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/envelope.ts","../src/core/collapse.ts","../src/core/observer.ts","../src/core/entanglement.ts","../src/core/keyring.ts","../src/utils/hash.ts","../src/core/scope.ts","../src/core/hooks.ts","../src/core/tunnel.ts"],"sourcesContent":["/**\n * Quantum Envelope: the storage format for all q-ring secrets.\n *\n * Instead of storing raw strings, every secret is wrapped in an envelope\n * that carries quantum metadata: environment states (superposition),\n * TTL/expiry (decay), entanglement links, and access tracking (observer).\n */\n\nexport type Environment = string; // \"dev\" | \"staging\" | \"prod\" | custom\n\nexport interface EntanglementLink {\n /** Service name of the entangled scope */\n service: string;\n /** Key name in the entangled scope */\n key: string;\n}\n\nexport interface SecretMetadata {\n createdAt: string;\n updatedAt: string;\n /** ISO timestamp when this secret expires (quantum decay) */\n expiresAt?: string;\n /** TTL in seconds from creation/update */\n ttlSeconds?: number;\n /** Human-readable description */\n description?: string;\n /** Tags for organization */\n tags?: string[];\n /** Entanglement links to other secrets */\n entangled?: EntanglementLink[];\n /** Total number of times this secret has been read */\n accessCount: number;\n /** ISO timestamp of last read */\n lastAccessedAt?: string;\n /** Whether this secret is ephemeral (tunneling - not persisted to keyring) */\n ephemeral?: boolean;\n /** Format to use when auto-rotating (e.g. \"api-key\", \"password\", \"uuid\") */\n rotationFormat?: string;\n /** Prefix to use when auto-rotating api-key/token formats */\n rotationPrefix?: string;\n /** Provider name for liveness validation (e.g. \"openai\", \"stripe\", \"github\") */\n provider?: string;\n}\n\nexport interface QuantumEnvelope {\n /** Schema version for forward compatibility */\n v: 1;\n /** Simple value (when not in superposition) */\n value?: string;\n /** Superposition: environment-keyed values */\n states?: Record<Environment, string>;\n /** Default environment to collapse to when no context is available */\n defaultEnv?: Environment;\n /** Quantum metadata */\n meta: SecretMetadata;\n}\n\nexport function createEnvelope(\n value: string,\n opts?: Partial<Pick<QuantumEnvelope, \"states\" | \"defaultEnv\">> & {\n description?: string;\n tags?: string[];\n ttlSeconds?: number;\n expiresAt?: string;\n entangled?: EntanglementLink[];\n rotationFormat?: string;\n rotationPrefix?: string;\n provider?: string;\n },\n): QuantumEnvelope {\n const now = new Date().toISOString();\n\n let expiresAt = opts?.expiresAt;\n if (!expiresAt && opts?.ttlSeconds) {\n expiresAt = new Date(Date.now() + opts.ttlSeconds * 1000).toISOString();\n }\n\n return {\n v: 1,\n value: opts?.states ? undefined : value,\n states: opts?.states,\n defaultEnv: opts?.defaultEnv,\n meta: {\n createdAt: now,\n updatedAt: now,\n expiresAt,\n ttlSeconds: opts?.ttlSeconds,\n description: opts?.description,\n tags: opts?.tags,\n entangled: opts?.entangled,\n accessCount: 0,\n rotationFormat: opts?.rotationFormat,\n rotationPrefix: opts?.rotationPrefix,\n provider: opts?.provider,\n },\n };\n}\n\nexport function parseEnvelope(raw: string): QuantumEnvelope | null {\n try {\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === \"object\" && parsed.v === 1) {\n return parsed as QuantumEnvelope;\n }\n } catch {\n // Not a quantum envelope - legacy raw string\n }\n return null;\n}\n\n/**\n * Wrap a legacy raw string value into a quantum envelope.\n * Used for backward compatibility with secrets stored before the envelope format.\n */\nexport function wrapLegacy(rawValue: string): QuantumEnvelope {\n const now = new Date().toISOString();\n return {\n v: 1,\n value: rawValue,\n meta: {\n createdAt: now,\n updatedAt: now,\n accessCount: 0,\n },\n };\n}\n\nexport function serializeEnvelope(envelope: QuantumEnvelope): string {\n return JSON.stringify(envelope);\n}\n\n/**\n * Resolve the concrete value from a quantum envelope.\n * If in superposition, collapses based on the provided environment.\n */\nexport function collapseValue(\n envelope: QuantumEnvelope,\n env?: Environment,\n): string | null {\n if (envelope.states) {\n const targetEnv = env ?? envelope.defaultEnv;\n if (targetEnv && envelope.states[targetEnv]) {\n return envelope.states[targetEnv];\n }\n // If no env match, try default, then return null\n if (envelope.defaultEnv && envelope.states[envelope.defaultEnv]) {\n return envelope.states[envelope.defaultEnv];\n }\n // Last resort: return the first state\n const keys = Object.keys(envelope.states);\n if (keys.length > 0) {\n return envelope.states[keys[0]];\n }\n return null;\n }\n\n return envelope.value ?? null;\n}\n\nexport interface DecayStatus {\n isExpired: boolean;\n isStale: boolean;\n /** Percentage of lifetime elapsed (0-100+) */\n lifetimePercent: number;\n /** Seconds remaining until expiry, or negative if expired */\n secondsRemaining: number | null;\n /** Human-readable time remaining */\n timeRemaining: string | null;\n}\n\nexport function checkDecay(envelope: QuantumEnvelope): DecayStatus {\n if (!envelope.meta.expiresAt) {\n return {\n isExpired: false,\n isStale: false,\n lifetimePercent: 0,\n secondsRemaining: null,\n timeRemaining: null,\n };\n }\n\n const now = Date.now();\n const expires = new Date(envelope.meta.expiresAt).getTime();\n const created = new Date(envelope.meta.createdAt).getTime();\n const totalLifetime = expires - created;\n const elapsed = now - created;\n const remaining = expires - now;\n\n const lifetimePercent =\n totalLifetime > 0 ? Math.round((elapsed / totalLifetime) * 100) : 100;\n\n const secondsRemaining = Math.floor(remaining / 1000);\n\n let timeRemaining: string | null = null;\n if (remaining > 0) {\n const days = Math.floor(remaining / 86400000);\n const hours = Math.floor((remaining % 86400000) / 3600000);\n const minutes = Math.floor((remaining % 3600000) / 60000);\n\n if (days > 0) timeRemaining = `${days}d ${hours}h`;\n else if (hours > 0) timeRemaining = `${hours}h ${minutes}m`;\n else timeRemaining = `${minutes}m`;\n } else {\n timeRemaining = \"expired\";\n }\n\n return {\n isExpired: remaining <= 0,\n isStale: lifetimePercent >= 75,\n lifetimePercent,\n secondsRemaining,\n timeRemaining,\n };\n}\n\n/**\n * Record an access event on the envelope (observer effect).\n * Returns a new envelope with updated access metadata.\n */\nexport function recordAccess(envelope: QuantumEnvelope): QuantumEnvelope {\n return {\n ...envelope,\n meta: {\n ...envelope.meta,\n accessCount: envelope.meta.accessCount + 1,\n lastAccessedAt: new Date().toISOString(),\n },\n };\n}\n","/**\n * Wavefunction Collapse: auto-detect the current environment context.\n *\n * Resolution order (first match wins):\n * 1. Explicit --env flag\n * 2. QRING_ENV environment variable\n * 3. NODE_ENV environment variable\n * 4. Git branch heuristics (main/master → prod, develop → dev, staging → staging)\n * 5. .q-ring.json project config\n * 6. Default environment from the envelope\n */\n\nimport { execSync } from \"node:child_process\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { Environment } from \"./envelope.js\";\n\nconst BRANCH_ENV_MAP: Record<string, Environment> = {\n main: \"prod\",\n master: \"prod\",\n production: \"prod\",\n develop: \"dev\",\n development: \"dev\",\n dev: \"dev\",\n staging: \"staging\",\n stage: \"staging\",\n test: \"test\",\n testing: \"test\",\n};\n\nfunction detectGitBranch(cwd?: string): string | null {\n try {\n const branch = execSync(\"git rev-parse --abbrev-ref HEAD\", {\n cwd: cwd ?? process.cwd(),\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n encoding: \"utf8\",\n timeout: 3000,\n }).trim();\n return branch || null;\n } catch {\n return null;\n }\n}\n\nexport interface ManifestEntry {\n required?: boolean;\n description?: string;\n /** Expected format for auto-rotation (e.g. \"api-key\", \"password\", \"uuid\") */\n format?: string;\n /** Expected prefix (e.g. \"sk-\") */\n prefix?: string;\n /** Provider name for liveness validation (e.g. \"openai\", \"stripe\", \"github\") */\n provider?: string;\n /** Custom validation URL for generic HTTP provider */\n validationUrl?: string;\n}\n\nexport interface ProjectConfig {\n env?: Environment;\n defaultEnv?: Environment;\n branchMap?: Record<string, Environment>;\n /** Secrets manifest — declares required/expected secrets for this project */\n secrets?: Record<string, ManifestEntry>;\n}\n\nexport function readProjectConfig(projectPath?: string): ProjectConfig | null {\n const configPath = join(projectPath ?? process.cwd(), \".q-ring.json\");\n try {\n if (existsSync(configPath)) {\n return JSON.parse(readFileSync(configPath, \"utf8\"));\n }\n } catch {\n // invalid config\n }\n return null;\n}\n\nexport interface CollapseContext {\n /** Explicitly provided environment */\n explicit?: Environment;\n /** Project path for git/config detection */\n projectPath?: string;\n}\n\nexport interface CollapseResult {\n env: Environment;\n source:\n | \"explicit\"\n | \"QRING_ENV\"\n | \"NODE_ENV\"\n | \"git-branch\"\n | \"project-config\"\n | \"default\";\n}\n\nexport function collapseEnvironment(\n ctx: CollapseContext = {},\n): CollapseResult | null {\n if (ctx.explicit) {\n return { env: ctx.explicit, source: \"explicit\" };\n }\n\n const qringEnv = process.env.QRING_ENV;\n if (qringEnv) {\n return { env: qringEnv, source: \"QRING_ENV\" };\n }\n\n const nodeEnv = process.env.NODE_ENV;\n if (nodeEnv) {\n const mapped = mapEnvName(nodeEnv);\n return { env: mapped, source: \"NODE_ENV\" };\n }\n\n const config = readProjectConfig(ctx.projectPath);\n if (config?.env) {\n return { env: config.env, source: \"project-config\" };\n }\n\n const branch = detectGitBranch(ctx.projectPath);\n if (branch) {\n const branchMap = { ...BRANCH_ENV_MAP, ...config?.branchMap };\n const mapped = branchMap[branch] ?? matchGlob(branchMap, branch);\n if (mapped) {\n return { env: mapped, source: \"git-branch\" };\n }\n }\n\n if (config?.defaultEnv) {\n return { env: config.defaultEnv, source: \"project-config\" };\n }\n\n return null;\n}\n\n/**\n * Match a branch name against glob-style patterns in the branchMap.\n * Supports `*` as a wildcard (e.g., `release/*`, `feature/*`).\n */\nfunction matchGlob(\n branchMap: Record<string, Environment>,\n branch: string,\n): Environment | undefined {\n for (const [pattern, env] of Object.entries(branchMap)) {\n if (!pattern.includes(\"*\")) continue;\n const regex = new RegExp(\n \"^\" + pattern.replace(/\\*/g, \".*\") + \"$\",\n );\n if (regex.test(branch)) return env;\n }\n return undefined;\n}\n\nfunction mapEnvName(raw: string): Environment {\n const lower = raw.toLowerCase();\n if (lower === \"production\") return \"prod\";\n if (lower === \"development\") return \"dev\";\n return lower;\n}\n","/**\n * Observer Effect: every secret read/write/delete is logged.\n * Audit trail stored at ~/.config/q-ring/audit.jsonl\n *\n * The act of observation changes the state — each access increments\n * the envelope's access counter and records a timestamp.\n */\n\nimport { existsSync, mkdirSync, appendFileSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nexport type AuditAction =\n | \"read\"\n | \"write\"\n | \"delete\"\n | \"list\"\n | \"export\"\n | \"generate\"\n | \"entangle\"\n | \"tunnel\"\n | \"teleport\"\n | \"collapse\";\n\nexport interface AuditEvent {\n timestamp: string;\n action: AuditAction;\n key?: string;\n scope?: string;\n env?: string;\n source: \"cli\" | \"mcp\" | \"agent\" | \"api\";\n /** Additional context */\n detail?: string;\n /** Process ID for tracking */\n pid: number;\n}\n\nfunction getAuditDir(): string {\n const dir = join(homedir(), \".config\", \"q-ring\");\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n return dir;\n}\n\nfunction getAuditPath(): string {\n return join(getAuditDir(), \"audit.jsonl\");\n}\n\nexport function logAudit(event: Omit<AuditEvent, \"timestamp\" | \"pid\">): void {\n const full: AuditEvent = {\n ...event,\n timestamp: new Date().toISOString(),\n pid: process.pid,\n };\n\n try {\n appendFileSync(getAuditPath(), JSON.stringify(full) + \"\\n\");\n } catch {\n // audit logging should never crash the app\n }\n}\n\nexport interface AuditQuery {\n key?: string;\n action?: AuditAction;\n since?: string;\n limit?: number;\n}\n\nexport function queryAudit(query: AuditQuery = {}): AuditEvent[] {\n const path = getAuditPath();\n if (!existsSync(path)) return [];\n\n try {\n const lines = readFileSync(path, \"utf8\")\n .split(\"\\n\")\n .filter((l) => l.trim());\n\n let events: AuditEvent[] = lines\n .map((line) => {\n try {\n return JSON.parse(line) as AuditEvent;\n } catch {\n return null;\n }\n })\n .filter((e): e is AuditEvent => e !== null);\n\n if (query.key) {\n events = events.filter((e) => e.key === query.key);\n }\n if (query.action) {\n events = events.filter((e) => e.action === query.action);\n }\n if (query.since) {\n const since = new Date(query.since).getTime();\n events = events.filter(\n (e) => new Date(e.timestamp).getTime() >= since,\n );\n }\n\n events.sort(\n (a, b) =>\n new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(),\n );\n\n if (query.limit) {\n events = events.slice(0, query.limit);\n }\n\n return events;\n } catch {\n return [];\n }\n}\n\nexport interface AccessAnomaly {\n type: \"burst\" | \"unusual-hour\" | \"new-source\";\n description: string;\n events: AuditEvent[];\n}\n\n/**\n * Detect anomalous access patterns in the audit log.\n */\nexport function detectAnomalies(key?: string): AccessAnomaly[] {\n const recent = queryAudit({\n key,\n action: \"read\",\n since: new Date(Date.now() - 3600000).toISOString(), // last hour\n });\n\n const anomalies: AccessAnomaly[] = [];\n\n // Burst detection: more than 50 reads of the same key in an hour\n if (key && recent.length > 50) {\n anomalies.push({\n type: \"burst\",\n description: `${recent.length} reads of \"${key}\" in the last hour`,\n events: recent.slice(0, 10),\n });\n }\n\n // Unusual hour detection: access between 1am-5am local time\n const nightAccess = recent.filter((e) => {\n const hour = new Date(e.timestamp).getHours();\n return hour >= 1 && hour < 5;\n });\n\n if (nightAccess.length > 0) {\n anomalies.push({\n type: \"unusual-hour\",\n description: `${nightAccess.length} access(es) during unusual hours (1am-5am)`,\n events: nightAccess,\n });\n }\n\n return anomalies;\n}\n","/**\n * Quantum Entanglement: link secrets across projects.\n * When one entangled secret is rotated, all linked copies update.\n *\n * Entanglement is stored as metadata in the envelope. The entanglement\n * registry at ~/.config/q-ring/entanglement.json provides a reverse\n * lookup: given a secret, find all its entangled partners.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nexport interface EntanglementPair {\n /** Source: service/key */\n source: { service: string; key: string };\n /** Target: service/key */\n target: { service: string; key: string };\n /** When this entanglement was created */\n createdAt: string;\n}\n\ninterface EntanglementRegistry {\n pairs: EntanglementPair[];\n}\n\nfunction getRegistryPath(): string {\n const dir = join(homedir(), \".config\", \"q-ring\");\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n return join(dir, \"entanglement.json\");\n}\n\nfunction loadRegistry(): EntanglementRegistry {\n const path = getRegistryPath();\n if (!existsSync(path)) {\n return { pairs: [] };\n }\n try {\n return JSON.parse(readFileSync(path, \"utf8\"));\n } catch {\n return { pairs: [] };\n }\n}\n\nfunction saveRegistry(registry: EntanglementRegistry): void {\n writeFileSync(getRegistryPath(), JSON.stringify(registry, null, 2));\n}\n\nexport function entangle(\n source: { service: string; key: string },\n target: { service: string; key: string },\n): void {\n const registry = loadRegistry();\n\n const exists = registry.pairs.some(\n (p) =>\n p.source.service === source.service &&\n p.source.key === source.key &&\n p.target.service === target.service &&\n p.target.key === target.key,\n );\n\n if (!exists) {\n registry.pairs.push({\n source,\n target,\n createdAt: new Date().toISOString(),\n });\n // Bidirectional: add reverse link too\n registry.pairs.push({\n source: target,\n target: source,\n createdAt: new Date().toISOString(),\n });\n saveRegistry(registry);\n }\n}\n\nexport function disentangle(\n source: { service: string; key: string },\n target: { service: string; key: string },\n): void {\n const registry = loadRegistry();\n registry.pairs = registry.pairs.filter(\n (p) =>\n !(\n (p.source.service === source.service &&\n p.source.key === source.key &&\n p.target.service === target.service &&\n p.target.key === target.key) ||\n (p.source.service === target.service &&\n p.source.key === target.key &&\n p.target.service === source.service &&\n p.target.key === source.key)\n ),\n );\n saveRegistry(registry);\n}\n\n/**\n * Find all entangled partners for a given secret.\n */\nexport function findEntangled(\n source: { service: string; key: string },\n): { service: string; key: string }[] {\n const registry = loadRegistry();\n return registry.pairs\n .filter(\n (p) =>\n p.source.service === source.service && p.source.key === source.key,\n )\n .map((p) => p.target);\n}\n\n/**\n * List all entanglement pairs.\n */\nexport function listEntanglements(): EntanglementPair[] {\n return loadRegistry().pairs;\n}\n","import { Entry, findCredentials } from \"@napi-rs/keyring\";\nimport {\n resolveScope,\n globalService,\n projectService,\n type Scope,\n} from \"./scope.js\";\nimport {\n type QuantumEnvelope,\n type Environment,\n createEnvelope,\n parseEnvelope,\n wrapLegacy,\n serializeEnvelope,\n collapseValue,\n checkDecay,\n recordAccess,\n type DecayStatus,\n type EntanglementLink,\n} from \"./envelope.js\";\nimport { collapseEnvironment, type CollapseContext } from \"./collapse.js\";\nimport { logAudit, type AuditAction } from \"./observer.js\";\nimport { findEntangled, entangle as entangleLink, disentangle as disentangleLink } from \"./entanglement.js\";\nimport { fireHooks } from \"./hooks.js\";\n\nexport interface SecretEntry {\n key: string;\n scope: Scope;\n value?: string;\n envelope?: QuantumEnvelope;\n decay?: DecayStatus;\n}\n\nexport interface KeyringOptions {\n scope?: Scope;\n projectPath?: string;\n /** Environment for superposition collapse */\n env?: Environment;\n /** Audit source */\n source?: \"cli\" | \"mcp\" | \"agent\" | \"api\";\n /** Skip audit logging (for internal polling like the dashboard) */\n silent?: boolean;\n}\n\nexport interface SetSecretOptions extends KeyringOptions {\n /** Environment states for superposition */\n states?: Record<Environment, string>;\n /** Default environment */\n defaultEnv?: Environment;\n /** TTL in seconds (quantum decay) */\n ttlSeconds?: number;\n /** Expiry timestamp */\n expiresAt?: string;\n /** Description */\n description?: string;\n /** Tags */\n tags?: string[];\n /** Format for auto-rotation (e.g. \"api-key\", \"password\", \"uuid\") */\n rotationFormat?: string;\n /** Prefix for auto-rotation (e.g. \"sk-\") */\n rotationPrefix?: string;\n /** Provider for liveness validation (e.g. \"openai\", \"stripe\") */\n provider?: string;\n}\n\nfunction readEnvelope(service: string, key: string): QuantumEnvelope | null {\n const entry = new Entry(service, key);\n const raw = entry.getPassword();\n if (raw === null) return null;\n\n const envelope = parseEnvelope(raw);\n return envelope ?? wrapLegacy(raw);\n}\n\nfunction writeEnvelope(\n service: string,\n key: string,\n envelope: QuantumEnvelope,\n): void {\n const entry = new Entry(service, key);\n entry.setPassword(serializeEnvelope(envelope));\n}\n\nfunction resolveEnv(opts: KeyringOptions): Environment | undefined {\n if (opts.env) return opts.env;\n const result = collapseEnvironment({ projectPath: opts.projectPath });\n return result?.env;\n}\n\n/**\n * Retrieve a secret by key, with scope resolution and superposition collapse.\n * Records the access in the audit log (observer effect).\n */\nexport function getSecret(\n key: string,\n opts: KeyringOptions = {},\n): string | null {\n const scopes = resolveScope(opts);\n const env = resolveEnv(opts);\n const source = opts.source ?? \"cli\";\n\n for (const { service, scope } of scopes) {\n const envelope = readEnvelope(service, key);\n if (!envelope) continue;\n\n // Check decay\n const decay = checkDecay(envelope);\n if (decay.isExpired) {\n logAudit({\n action: \"read\",\n key,\n scope,\n source,\n detail: \"blocked: secret expired (quantum decay)\",\n });\n continue;\n }\n\n // Collapse superposition\n const value = collapseValue(envelope, env);\n if (value === null) continue;\n\n // Observer effect: record access and persist\n const updated = recordAccess(envelope);\n writeEnvelope(service, key, updated);\n\n logAudit({ action: \"read\", key, scope, env, source });\n\n return value;\n }\n\n return null;\n}\n\n/**\n * Get the full envelope for a secret (for inspection, no value extraction).\n */\nexport function getEnvelope(\n key: string,\n opts: KeyringOptions = {},\n): { envelope: QuantumEnvelope; scope: Scope } | null {\n const scopes = resolveScope(opts);\n\n for (const { service, scope } of scopes) {\n const envelope = readEnvelope(service, key);\n if (envelope) return { envelope, scope };\n }\n\n return null;\n}\n\n/**\n * Store a secret with quantum metadata.\n */\nexport function setSecret(\n key: string,\n value: string,\n opts: SetSecretOptions = {},\n): void {\n const scope = opts.scope ?? \"global\";\n const scopes = resolveScope({ ...opts, scope });\n const { service } = scopes[0];\n const source = opts.source ?? \"cli\";\n\n // Check if there's an existing envelope to preserve metadata\n const existing = readEnvelope(service, key);\n\n let envelope: QuantumEnvelope;\n\n const rotFmt = opts.rotationFormat ?? existing?.meta.rotationFormat;\n const rotPfx = opts.rotationPrefix ?? existing?.meta.rotationPrefix;\n const prov = opts.provider ?? existing?.meta.provider;\n\n if (opts.states) {\n envelope = createEnvelope(\"\", {\n states: opts.states,\n defaultEnv: opts.defaultEnv,\n description: opts.description,\n tags: opts.tags,\n ttlSeconds: opts.ttlSeconds,\n expiresAt: opts.expiresAt,\n entangled: existing?.meta.entangled,\n rotationFormat: rotFmt,\n rotationPrefix: rotPfx,\n provider: prov,\n });\n } else {\n envelope = createEnvelope(value, {\n description: opts.description,\n tags: opts.tags,\n ttlSeconds: opts.ttlSeconds,\n expiresAt: opts.expiresAt,\n entangled: existing?.meta.entangled,\n rotationFormat: rotFmt,\n rotationPrefix: rotPfx,\n provider: prov,\n });\n }\n\n // Preserve access count from existing\n if (existing) {\n envelope.meta.createdAt = existing.meta.createdAt;\n envelope.meta.accessCount = existing.meta.accessCount;\n }\n\n writeEnvelope(service, key, envelope);\n logAudit({ action: \"write\", key, scope, source });\n\n // Propagate to entangled secrets\n const entangled = findEntangled({ service, key });\n for (const target of entangled) {\n try {\n const targetEnvelope = readEnvelope(target.service, target.key);\n if (targetEnvelope) {\n if (opts.states) {\n targetEnvelope.states = opts.states;\n } else {\n targetEnvelope.value = value;\n }\n targetEnvelope.meta.updatedAt = new Date().toISOString();\n writeEnvelope(target.service, target.key, targetEnvelope);\n logAudit({\n action: \"entangle\",\n key: target.key,\n scope: \"global\",\n source,\n detail: `propagated from ${key}`,\n });\n }\n } catch {\n // entangled target may not exist\n }\n }\n\n fireHooks({\n action: \"write\",\n key,\n scope,\n timestamp: new Date().toISOString(),\n source,\n }, envelope.meta.tags).catch(() => {});\n}\n\n/**\n * Delete a secret from the specified scope (or both if unscoped).\n */\nexport function deleteSecret(\n key: string,\n opts: KeyringOptions = {},\n): boolean {\n const scopes = resolveScope(opts);\n const source = opts.source ?? \"cli\";\n let deleted = false;\n\n for (const { service, scope } of scopes) {\n const entry = new Entry(service, key);\n try {\n if (entry.deleteCredential()) {\n deleted = true;\n logAudit({ action: \"delete\", key, scope, source });\n fireHooks({\n action: \"delete\",\n key,\n scope,\n timestamp: new Date().toISOString(),\n source,\n }).catch(() => {});\n }\n } catch {\n // not found\n }\n }\n\n return deleted;\n}\n\n/**\n * Check whether a secret exists in any applicable scope.\n */\nexport function hasSecret(\n key: string,\n opts: KeyringOptions = {},\n): boolean {\n const scopes = resolveScope(opts);\n\n for (const { service } of scopes) {\n const envelope = readEnvelope(service, key);\n if (envelope) {\n const decay = checkDecay(envelope);\n if (!decay.isExpired) return true;\n }\n }\n\n return false;\n}\n\n/**\n * List all secrets across applicable scopes with quantum metadata.\n */\nexport function listSecrets(opts: KeyringOptions = {}): SecretEntry[] {\n const source = opts.source ?? \"cli\";\n const services: { service: string; scope: Scope }[] = [];\n\n if (!opts.scope || opts.scope === \"global\") {\n services.push({ service: globalService(), scope: \"global\" });\n }\n\n if ((!opts.scope || opts.scope === \"project\") && opts.projectPath) {\n services.push({\n service: projectService(opts.projectPath),\n scope: \"project\",\n });\n }\n\n const results: SecretEntry[] = [];\n const seen = new Set<string>();\n\n for (const { service, scope } of services) {\n try {\n const credentials = findCredentials(service);\n for (const cred of credentials) {\n const id = `${scope}:${cred.account}`;\n if (seen.has(id)) continue;\n seen.add(id);\n\n const envelope = parseEnvelope(cred.password) ?? wrapLegacy(cred.password);\n const decay = checkDecay(envelope);\n\n results.push({\n key: cred.account,\n scope,\n envelope,\n decay,\n });\n }\n } catch {\n // keyring unavailable\n }\n }\n\n if (!opts.silent) {\n logAudit({ action: \"list\", source });\n }\n\n return results.sort((a, b) => a.key.localeCompare(b.key));\n}\n\n/**\n * Export all secrets with their values (for .env or JSON export).\n * Collapses superposition based on detected environment.\n */\nexport function exportSecrets(\n opts: KeyringOptions & { format?: \"env\" | \"json\"; keys?: string[]; tags?: string[] } = {},\n): string {\n const format = opts.format ?? \"env\";\n const env = resolveEnv(opts);\n let entries = listSecrets(opts);\n const source = opts.source ?? \"cli\";\n\n if (opts.keys?.length) {\n const keySet = new Set(opts.keys);\n entries = entries.filter((e) => keySet.has(e.key));\n }\n\n if (opts.tags?.length) {\n entries = entries.filter((e) =>\n opts.tags!.some((t) => e.envelope?.meta.tags?.includes(t)),\n );\n }\n\n const merged = new Map<string, string>();\n\n const globalEntries = entries.filter((e) => e.scope === \"global\");\n const projectEntries = entries.filter((e) => e.scope === \"project\");\n\n for (const entry of [...globalEntries, ...projectEntries]) {\n if (entry.envelope) {\n const decay = checkDecay(entry.envelope);\n if (decay.isExpired) continue;\n\n const value = collapseValue(entry.envelope, env);\n if (value !== null) {\n merged.set(entry.key, value);\n }\n }\n }\n\n logAudit({ action: \"export\", source, detail: `format=${format}` });\n\n if (format === \"json\") {\n const obj: Record<string, string> = {};\n for (const [key, value] of merged) {\n obj[key] = value;\n }\n return JSON.stringify(obj, null, 2);\n }\n\n const lines: string[] = [];\n for (const [key, value] of merged) {\n const escaped = value\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, \"\\\\n\");\n lines.push(`${key}=\"${escaped}\"`);\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * Create an entanglement between two secrets.\n */\nexport function entangleSecrets(\n sourceKey: string,\n sourceOpts: KeyringOptions,\n targetKey: string,\n targetOpts: KeyringOptions,\n): void {\n const sourceScopes = resolveScope({ ...sourceOpts, scope: sourceOpts.scope ?? \"global\" });\n const targetScopes = resolveScope({ ...targetOpts, scope: targetOpts.scope ?? \"global\" });\n\n const source = { service: sourceScopes[0].service, key: sourceKey };\n const target = { service: targetScopes[0].service, key: targetKey };\n\n entangleLink(source, target);\n logAudit({\n action: \"entangle\",\n key: sourceKey,\n source: sourceOpts.source ?? \"cli\",\n detail: `entangled with ${targetKey}`,\n });\n}\n\n/**\n * Remove an entanglement between two secrets.\n */\nexport function disentangleSecrets(\n sourceKey: string,\n sourceOpts: KeyringOptions,\n targetKey: string,\n targetOpts: KeyringOptions,\n): void {\n const sourceScopes = resolveScope({ ...sourceOpts, scope: sourceOpts.scope ?? \"global\" });\n const targetScopes = resolveScope({ ...targetOpts, scope: targetOpts.scope ?? \"global\" });\n\n const source = { service: sourceScopes[0].service, key: sourceKey };\n const target = { service: targetScopes[0].service, key: targetKey };\n\n disentangleLink(source, target);\n logAudit({\n action: \"entangle\",\n key: sourceKey,\n source: sourceOpts.source ?? \"cli\",\n detail: `disentangled from ${targetKey}`,\n });\n}\n","import { createHash } from \"node:crypto\";\n\nexport function hashProjectPath(projectPath: string): string {\n return createHash(\"sha256\").update(projectPath).digest(\"hex\").slice(0, 12);\n}\n","import { hashProjectPath } from \"../utils/hash.js\";\n\nconst SERVICE_PREFIX = \"q-ring\";\n\nexport type Scope = \"global\" | \"project\";\n\nexport interface ResolvedScope {\n scope: Scope;\n service: string;\n projectPath?: string;\n}\n\nexport function globalService(): string {\n return `${SERVICE_PREFIX}:global`;\n}\n\nexport function projectService(projectPath: string): string {\n const hash = hashProjectPath(projectPath);\n return `${SERVICE_PREFIX}:project:${hash}`;\n}\n\nexport function resolveScope(opts: {\n scope?: Scope;\n projectPath?: string;\n}): ResolvedScope[] {\n const { scope, projectPath } = opts;\n\n if (scope === \"global\") {\n return [{ scope: \"global\", service: globalService() }];\n }\n\n if (scope === \"project\") {\n if (!projectPath) {\n throw new Error(\"Project path is required for project scope\");\n }\n return [\n { scope: \"project\", service: projectService(projectPath), projectPath },\n ];\n }\n\n // No explicit scope: return project-first for resolution (project overrides global)\n if (projectPath) {\n return [\n { scope: \"project\", service: projectService(projectPath), projectPath },\n { scope: \"global\", service: globalService() },\n ];\n }\n\n return [{ scope: \"global\", service: globalService() }];\n}\n\nexport function parseServiceName(service: string): ResolvedScope {\n if (service === globalService()) {\n return { scope: \"global\", service };\n }\n\n if (service.startsWith(`${SERVICE_PREFIX}:project:`)) {\n return { scope: \"project\", service };\n }\n\n return { scope: \"global\", service };\n}\n","/**\n * Hook system: fire callbacks when secrets are created, updated, or deleted.\n * Supports shell commands, HTTP webhooks, and process signals.\n *\n * Registry stored at ~/.config/q-ring/hooks.json\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { exec } from \"node:child_process\";\nimport { request as httpsRequest } from \"node:https\";\nimport { request as httpRequest } from \"node:http\";\nimport { randomUUID } from \"node:crypto\";\nimport { logAudit } from \"./observer.js\";\n\nexport type HookType = \"shell\" | \"http\" | \"signal\";\nexport type HookAction = \"write\" | \"delete\" | \"rotate\";\n\nexport interface HookMatch {\n key?: string;\n keyPattern?: string;\n tag?: string;\n scope?: \"global\" | \"project\";\n action?: HookAction[];\n}\n\nexport interface HookEntry {\n id: string;\n type: HookType;\n match: HookMatch;\n command?: string;\n url?: string;\n signal?: { target: string; signal?: string };\n description?: string;\n createdAt: string;\n enabled: boolean;\n}\n\nexport interface HookPayload {\n action: HookAction;\n key: string;\n scope: string;\n timestamp: string;\n source: \"cli\" | \"mcp\" | \"agent\" | \"api\";\n}\n\nexport interface HookResult {\n hookId: string;\n success: boolean;\n message: string;\n}\n\ninterface HookRegistry {\n hooks: HookEntry[];\n}\n\nfunction getRegistryPath(): string {\n const dir = join(homedir(), \".config\", \"q-ring\");\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n return join(dir, \"hooks.json\");\n}\n\nfunction loadRegistry(): HookRegistry {\n const path = getRegistryPath();\n if (!existsSync(path)) {\n return { hooks: [] };\n }\n try {\n return JSON.parse(readFileSync(path, \"utf8\"));\n } catch {\n return { hooks: [] };\n }\n}\n\nfunction saveRegistry(registry: HookRegistry): void {\n writeFileSync(getRegistryPath(), JSON.stringify(registry, null, 2));\n}\n\nexport function registerHook(\n entry: Omit<HookEntry, \"id\" | \"createdAt\">,\n): HookEntry {\n const registry = loadRegistry();\n const hook: HookEntry = {\n ...entry,\n id: randomUUID().slice(0, 8),\n createdAt: new Date().toISOString(),\n };\n registry.hooks.push(hook);\n saveRegistry(registry);\n return hook;\n}\n\nexport function removeHook(id: string): boolean {\n const registry = loadRegistry();\n const before = registry.hooks.length;\n registry.hooks = registry.hooks.filter((h) => h.id !== id);\n if (registry.hooks.length < before) {\n saveRegistry(registry);\n return true;\n }\n return false;\n}\n\nexport function listHooks(): HookEntry[] {\n return loadRegistry().hooks;\n}\n\nexport function enableHook(id: string): boolean {\n const registry = loadRegistry();\n const hook = registry.hooks.find((h) => h.id === id);\n if (!hook) return false;\n hook.enabled = true;\n saveRegistry(registry);\n return true;\n}\n\nexport function disableHook(id: string): boolean {\n const registry = loadRegistry();\n const hook = registry.hooks.find((h) => h.id === id);\n if (!hook) return false;\n hook.enabled = false;\n saveRegistry(registry);\n return true;\n}\n\nfunction matchesHook(\n hook: HookEntry,\n payload: HookPayload,\n tags?: string[],\n): boolean {\n if (!hook.enabled) return false;\n\n const m = hook.match;\n\n if (m.action?.length && !m.action.includes(payload.action)) return false;\n\n if (m.key && m.key !== payload.key) return false;\n\n if (m.keyPattern) {\n const regex = new RegExp(\n \"^\" + m.keyPattern.replace(/\\*/g, \".*\") + \"$\",\n \"i\",\n );\n if (!regex.test(payload.key)) return false;\n }\n\n if (m.tag && (!tags || !tags.includes(m.tag))) return false;\n\n if (m.scope && m.scope !== payload.scope) return false;\n\n return true;\n}\n\nfunction executeShell(command: string, payload: HookPayload): Promise<HookResult> {\n return new Promise((resolve) => {\n const env = {\n ...process.env,\n QRING_HOOK_KEY: payload.key,\n QRING_HOOK_ACTION: payload.action,\n QRING_HOOK_SCOPE: payload.scope,\n };\n\n exec(command, { timeout: 30000, env }, (err, stdout, stderr) => {\n if (err) {\n resolve({ hookId: \"\", success: false, message: `Shell error: ${err.message}` });\n } else {\n resolve({ hookId: \"\", success: true, message: stdout.trim() || \"OK\" });\n }\n });\n });\n}\n\nfunction executeHttp(url: string, payload: HookPayload): Promise<HookResult> {\n return new Promise((resolve) => {\n const body = JSON.stringify(payload);\n const parsedUrl = new URL(url);\n const reqFn = parsedUrl.protocol === \"https:\" ? httpsRequest : httpRequest;\n\n const req = reqFn(\n url,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(body),\n \"User-Agent\": \"q-ring-hooks/1.0\",\n },\n timeout: 10000,\n },\n (res) => {\n let data = \"\";\n res.on(\"data\", (chunk) => (data += chunk));\n res.on(\"end\", () => {\n resolve({\n hookId: \"\",\n success: (res.statusCode ?? 0) >= 200 && (res.statusCode ?? 0) < 300,\n message: `HTTP ${res.statusCode}`,\n });\n });\n },\n );\n\n req.on(\"error\", (err) => {\n resolve({ hookId: \"\", success: false, message: `HTTP error: ${err.message}` });\n });\n req.on(\"timeout\", () => {\n req.destroy();\n resolve({ hookId: \"\", success: false, message: \"HTTP timeout\" });\n });\n req.write(body);\n req.end();\n });\n}\n\nfunction executeSignal(\n target: string,\n signal: string = \"SIGHUP\",\n): Promise<HookResult> {\n return new Promise((resolve) => {\n const pid = parseInt(target, 10);\n if (!isNaN(pid)) {\n try {\n process.kill(pid, signal as NodeJS.Signals);\n resolve({ hookId: \"\", success: true, message: `Signal ${signal} sent to PID ${pid}` });\n } catch (err) {\n resolve({ hookId: \"\", success: false, message: `Signal error: ${err instanceof Error ? err.message : String(err)}` });\n }\n return;\n }\n\n exec(`pgrep -f \"${target}\"`, { timeout: 5000 }, (err, stdout) => {\n if (err || !stdout.trim()) {\n resolve({ hookId: \"\", success: false, message: `Process \"${target}\" not found` });\n return;\n }\n const pids = stdout.trim().split(\"\\n\").map((p) => parseInt(p.trim(), 10)).filter((p) => !isNaN(p));\n let sent = 0;\n for (const p of pids) {\n try {\n process.kill(p, signal as NodeJS.Signals);\n sent++;\n } catch { /* ignore dead PIDs */ }\n }\n resolve({ hookId: \"\", success: sent > 0, message: `Signal ${signal} sent to ${sent} process(es)` });\n });\n });\n}\n\nasync function executeHook(\n hook: HookEntry,\n payload: HookPayload,\n): Promise<HookResult> {\n let result: HookResult;\n\n switch (hook.type) {\n case \"shell\":\n result = hook.command\n ? await executeShell(hook.command, payload)\n : { hookId: hook.id, success: false, message: \"No command specified\" };\n break;\n case \"http\":\n result = hook.url\n ? await executeHttp(hook.url, payload)\n : { hookId: hook.id, success: false, message: \"No URL specified\" };\n break;\n case \"signal\":\n result = hook.signal\n ? await executeSignal(hook.signal.target, hook.signal.signal)\n : { hookId: hook.id, success: false, message: \"No signal target specified\" };\n break;\n default:\n result = { hookId: hook.id, success: false, message: `Unknown hook type: ${hook.type}` };\n }\n\n result.hookId = hook.id;\n return result;\n}\n\n/**\n * Fire all matching hooks for a given payload. Fire-and-forget — never blocks\n * the caller on hook failures.\n */\nexport async function fireHooks(\n payload: HookPayload,\n tags?: string[],\n): Promise<HookResult[]> {\n const hooks = listHooks();\n const matching = hooks.filter((h) => matchesHook(h, payload, tags));\n\n if (matching.length === 0) return [];\n\n const results = await Promise.allSettled(\n matching.map((h) => executeHook(h, payload)),\n );\n\n const hookResults: HookResult[] = [];\n for (const r of results) {\n if (r.status === \"fulfilled\") {\n hookResults.push(r.value);\n } else {\n hookResults.push({\n hookId: \"unknown\",\n success: false,\n message: r.reason?.message ?? \"Hook execution failed\",\n });\n }\n }\n\n for (const r of hookResults) {\n try {\n logAudit({\n action: \"write\",\n key: payload.key,\n scope: payload.scope,\n source: payload.source,\n detail: `hook:${r.hookId} ${r.success ? \"ok\" : \"fail\"} — ${r.message}`,\n });\n } catch { /* never crash on audit logging */ }\n }\n\n return hookResults;\n}\n","/**\n * Quantum Tunneling: ephemeral secrets that exist only in memory.\n *\n * Tunneled secrets are never persisted to the OS keyring. They live\n * in a process-scoped in-memory store with optional auto-expiry.\n * Useful for passing secrets between agents without touching disk.\n */\n\ninterface TunnelEntry {\n value: string;\n createdAt: number;\n expiresAt?: number;\n accessCount: number;\n /** Max number of reads before auto-destruct */\n maxReads?: number;\n}\n\nconst tunnelStore = new Map<string, TunnelEntry>();\n\nlet cleanupInterval: ReturnType<typeof setInterval> | null = null;\n\nfunction ensureCleanup(): void {\n if (cleanupInterval) return;\n cleanupInterval = setInterval(() => {\n const now = Date.now();\n for (const [id, entry] of tunnelStore) {\n if (entry.expiresAt && now >= entry.expiresAt) {\n tunnelStore.delete(id);\n }\n }\n if (tunnelStore.size === 0 && cleanupInterval) {\n clearInterval(cleanupInterval);\n cleanupInterval = null;\n }\n }, 5000);\n\n // Don't prevent process exit\n if (cleanupInterval && typeof cleanupInterval === \"object\" && \"unref\" in cleanupInterval) {\n cleanupInterval.unref();\n }\n}\n\nexport interface TunnelOptions {\n /** TTL in seconds */\n ttlSeconds?: number;\n /** Self-destruct after N reads */\n maxReads?: number;\n}\n\n/**\n * Create a tunneled (ephemeral) secret. Returns a tunnel ID.\n */\nexport function tunnelCreate(\n value: string,\n opts: TunnelOptions = {},\n): string {\n const id = `tun_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;\n const now = Date.now();\n\n tunnelStore.set(id, {\n value,\n createdAt: now,\n expiresAt: opts.ttlSeconds ? now + opts.ttlSeconds * 1000 : undefined,\n accessCount: 0,\n maxReads: opts.maxReads,\n });\n\n ensureCleanup();\n return id;\n}\n\n/**\n * Read a tunneled secret by ID. Returns null if expired or not found.\n * Each read increments the access counter; auto-destructs after maxReads.\n */\nexport function tunnelRead(id: string): string | null {\n const entry = tunnelStore.get(id);\n if (!entry) return null;\n\n if (entry.expiresAt && Date.now() >= entry.expiresAt) {\n tunnelStore.delete(id);\n return null;\n }\n\n entry.accessCount++;\n\n if (entry.maxReads && entry.accessCount >= entry.maxReads) {\n const value = entry.value;\n tunnelStore.delete(id);\n return value;\n }\n\n return entry.value;\n}\n\n/**\n * Destroy a tunneled secret immediately.\n */\nexport function tunnelDestroy(id: string): boolean {\n return tunnelStore.delete(id);\n}\n\n/**\n * List all active tunnel IDs (never exposes values).\n */\nexport function tunnelList(): {\n id: string;\n createdAt: number;\n expiresAt?: number;\n accessCount: number;\n maxReads?: number;\n}[] {\n const now = Date.now();\n const result: ReturnType<typeof tunnelList> = [];\n\n for (const [id, entry] of tunnelStore) {\n if (entry.expiresAt && now >= entry.expiresAt) {\n tunnelStore.delete(id);\n continue;\n }\n result.push({\n id,\n createdAt: entry.createdAt,\n expiresAt: entry.expiresAt,\n accessCount: entry.accessCount,\n maxReads: entry.maxReads,\n });\n }\n\n return result;\n}\n"],"mappings":"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","names":["existsSync","readFileSync","join","existsSync","readFileSync","mkdirSync","join","homedir","existsSync","readFileSync","writeFileSync","mkdirSync","join","homedir","getRegistryPath","join","homedir","existsSync","mkdirSync","loadRegistry","readFileSync","saveRegistry","writeFileSync"]}
@@ -20,7 +20,10 @@ function createEnvelope(value, opts) {
20
20
  description: opts?.description,
21
21
  tags: opts?.tags,
22
22
  entangled: opts?.entangled,
23
- accessCount: 0
23
+ accessCount: 0,
24
+ rotationFormat: opts?.rotationFormat,
25
+ rotationPrefix: opts?.rotationPrefix,
26
+ provider: opts?.provider
24
27
  }
25
28
  };
26
29
  }
@@ -173,7 +176,7 @@ function collapseEnvironment(ctx = {}) {
173
176
  const branch = detectGitBranch(ctx.projectPath);
174
177
  if (branch) {
175
178
  const branchMap = { ...BRANCH_ENV_MAP, ...config?.branchMap };
176
- const mapped = branchMap[branch];
179
+ const mapped = branchMap[branch] ?? matchGlob(branchMap, branch);
177
180
  if (mapped) {
178
181
  return { env: mapped, source: "git-branch" };
179
182
  }
@@ -183,6 +186,16 @@ function collapseEnvironment(ctx = {}) {
183
186
  }
184
187
  return null;
185
188
  }
189
+ function matchGlob(branchMap, branch) {
190
+ for (const [pattern, env] of Object.entries(branchMap)) {
191
+ if (!pattern.includes("*")) continue;
192
+ const regex = new RegExp(
193
+ "^" + pattern.replace(/\*/g, ".*") + "$"
194
+ );
195
+ if (regex.test(branch)) return env;
196
+ }
197
+ return void 0;
198
+ }
186
199
  function mapEnvName(raw) {
187
200
  const lower = raw.toLowerCase();
188
201
  if (lower === "production") return "prod";
@@ -323,6 +336,13 @@ function entangle(source, target) {
323
336
  saveRegistry(registry);
324
337
  }
325
338
  }
339
+ function disentangle(source, target) {
340
+ const registry = loadRegistry();
341
+ registry.pairs = registry.pairs.filter(
342
+ (p) => !(p.source.service === source.service && p.source.key === source.key && p.target.service === target.service && p.target.key === target.key || p.source.service === target.service && p.source.key === target.key && p.target.service === source.service && p.target.key === source.key)
343
+ );
344
+ saveRegistry(registry);
345
+ }
326
346
  function findEntangled(source) {
327
347
  const registry = loadRegistry();
328
348
  return registry.pairs.filter(
@@ -373,6 +393,229 @@ function resolveScope(opts) {
373
393
  return [{ scope: "global", service: globalService() }];
374
394
  }
375
395
 
396
+ // src/core/hooks.ts
397
+ import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3 } from "fs";
398
+ import { join as join4 } from "path";
399
+ import { homedir as homedir3 } from "os";
400
+ import { exec } from "child_process";
401
+ import { request as httpsRequest } from "https";
402
+ import { request as httpRequest } from "http";
403
+ import { randomUUID } from "crypto";
404
+ function getRegistryPath2() {
405
+ const dir = join4(homedir3(), ".config", "q-ring");
406
+ if (!existsSync4(dir)) {
407
+ mkdirSync3(dir, { recursive: true });
408
+ }
409
+ return join4(dir, "hooks.json");
410
+ }
411
+ function loadRegistry2() {
412
+ const path = getRegistryPath2();
413
+ if (!existsSync4(path)) {
414
+ return { hooks: [] };
415
+ }
416
+ try {
417
+ return JSON.parse(readFileSync4(path, "utf8"));
418
+ } catch {
419
+ return { hooks: [] };
420
+ }
421
+ }
422
+ function saveRegistry2(registry) {
423
+ writeFileSync2(getRegistryPath2(), JSON.stringify(registry, null, 2));
424
+ }
425
+ function registerHook(entry) {
426
+ const registry = loadRegistry2();
427
+ const hook = {
428
+ ...entry,
429
+ id: randomUUID().slice(0, 8),
430
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
431
+ };
432
+ registry.hooks.push(hook);
433
+ saveRegistry2(registry);
434
+ return hook;
435
+ }
436
+ function removeHook(id) {
437
+ const registry = loadRegistry2();
438
+ const before = registry.hooks.length;
439
+ registry.hooks = registry.hooks.filter((h) => h.id !== id);
440
+ if (registry.hooks.length < before) {
441
+ saveRegistry2(registry);
442
+ return true;
443
+ }
444
+ return false;
445
+ }
446
+ function listHooks() {
447
+ return loadRegistry2().hooks;
448
+ }
449
+ function enableHook(id) {
450
+ const registry = loadRegistry2();
451
+ const hook = registry.hooks.find((h) => h.id === id);
452
+ if (!hook) return false;
453
+ hook.enabled = true;
454
+ saveRegistry2(registry);
455
+ return true;
456
+ }
457
+ function disableHook(id) {
458
+ const registry = loadRegistry2();
459
+ const hook = registry.hooks.find((h) => h.id === id);
460
+ if (!hook) return false;
461
+ hook.enabled = false;
462
+ saveRegistry2(registry);
463
+ return true;
464
+ }
465
+ function matchesHook(hook, payload, tags) {
466
+ if (!hook.enabled) return false;
467
+ const m = hook.match;
468
+ if (m.action?.length && !m.action.includes(payload.action)) return false;
469
+ if (m.key && m.key !== payload.key) return false;
470
+ if (m.keyPattern) {
471
+ const regex = new RegExp(
472
+ "^" + m.keyPattern.replace(/\*/g, ".*") + "$",
473
+ "i"
474
+ );
475
+ if (!regex.test(payload.key)) return false;
476
+ }
477
+ if (m.tag && (!tags || !tags.includes(m.tag))) return false;
478
+ if (m.scope && m.scope !== payload.scope) return false;
479
+ return true;
480
+ }
481
+ function executeShell(command, payload) {
482
+ return new Promise((resolve) => {
483
+ const env = {
484
+ ...process.env,
485
+ QRING_HOOK_KEY: payload.key,
486
+ QRING_HOOK_ACTION: payload.action,
487
+ QRING_HOOK_SCOPE: payload.scope
488
+ };
489
+ exec(command, { timeout: 3e4, env }, (err, stdout, stderr) => {
490
+ if (err) {
491
+ resolve({ hookId: "", success: false, message: `Shell error: ${err.message}` });
492
+ } else {
493
+ resolve({ hookId: "", success: true, message: stdout.trim() || "OK" });
494
+ }
495
+ });
496
+ });
497
+ }
498
+ function executeHttp(url, payload) {
499
+ return new Promise((resolve) => {
500
+ const body = JSON.stringify(payload);
501
+ const parsedUrl = new URL(url);
502
+ const reqFn = parsedUrl.protocol === "https:" ? httpsRequest : httpRequest;
503
+ const req = reqFn(
504
+ url,
505
+ {
506
+ method: "POST",
507
+ headers: {
508
+ "Content-Type": "application/json",
509
+ "Content-Length": Buffer.byteLength(body),
510
+ "User-Agent": "q-ring-hooks/1.0"
511
+ },
512
+ timeout: 1e4
513
+ },
514
+ (res) => {
515
+ let data = "";
516
+ res.on("data", (chunk) => data += chunk);
517
+ res.on("end", () => {
518
+ resolve({
519
+ hookId: "",
520
+ success: (res.statusCode ?? 0) >= 200 && (res.statusCode ?? 0) < 300,
521
+ message: `HTTP ${res.statusCode}`
522
+ });
523
+ });
524
+ }
525
+ );
526
+ req.on("error", (err) => {
527
+ resolve({ hookId: "", success: false, message: `HTTP error: ${err.message}` });
528
+ });
529
+ req.on("timeout", () => {
530
+ req.destroy();
531
+ resolve({ hookId: "", success: false, message: "HTTP timeout" });
532
+ });
533
+ req.write(body);
534
+ req.end();
535
+ });
536
+ }
537
+ function executeSignal(target, signal = "SIGHUP") {
538
+ return new Promise((resolve) => {
539
+ const pid = parseInt(target, 10);
540
+ if (!isNaN(pid)) {
541
+ try {
542
+ process.kill(pid, signal);
543
+ resolve({ hookId: "", success: true, message: `Signal ${signal} sent to PID ${pid}` });
544
+ } catch (err) {
545
+ resolve({ hookId: "", success: false, message: `Signal error: ${err instanceof Error ? err.message : String(err)}` });
546
+ }
547
+ return;
548
+ }
549
+ exec(`pgrep -f "${target}"`, { timeout: 5e3 }, (err, stdout) => {
550
+ if (err || !stdout.trim()) {
551
+ resolve({ hookId: "", success: false, message: `Process "${target}" not found` });
552
+ return;
553
+ }
554
+ const pids = stdout.trim().split("\n").map((p) => parseInt(p.trim(), 10)).filter((p) => !isNaN(p));
555
+ let sent = 0;
556
+ for (const p of pids) {
557
+ try {
558
+ process.kill(p, signal);
559
+ sent++;
560
+ } catch {
561
+ }
562
+ }
563
+ resolve({ hookId: "", success: sent > 0, message: `Signal ${signal} sent to ${sent} process(es)` });
564
+ });
565
+ });
566
+ }
567
+ async function executeHook(hook, payload) {
568
+ let result;
569
+ switch (hook.type) {
570
+ case "shell":
571
+ result = hook.command ? await executeShell(hook.command, payload) : { hookId: hook.id, success: false, message: "No command specified" };
572
+ break;
573
+ case "http":
574
+ result = hook.url ? await executeHttp(hook.url, payload) : { hookId: hook.id, success: false, message: "No URL specified" };
575
+ break;
576
+ case "signal":
577
+ result = hook.signal ? await executeSignal(hook.signal.target, hook.signal.signal) : { hookId: hook.id, success: false, message: "No signal target specified" };
578
+ break;
579
+ default:
580
+ result = { hookId: hook.id, success: false, message: `Unknown hook type: ${hook.type}` };
581
+ }
582
+ result.hookId = hook.id;
583
+ return result;
584
+ }
585
+ async function fireHooks(payload, tags) {
586
+ const hooks = listHooks();
587
+ const matching = hooks.filter((h) => matchesHook(h, payload, tags));
588
+ if (matching.length === 0) return [];
589
+ const results = await Promise.allSettled(
590
+ matching.map((h) => executeHook(h, payload))
591
+ );
592
+ const hookResults = [];
593
+ for (const r of results) {
594
+ if (r.status === "fulfilled") {
595
+ hookResults.push(r.value);
596
+ } else {
597
+ hookResults.push({
598
+ hookId: "unknown",
599
+ success: false,
600
+ message: r.reason?.message ?? "Hook execution failed"
601
+ });
602
+ }
603
+ }
604
+ for (const r of hookResults) {
605
+ try {
606
+ logAudit({
607
+ action: "write",
608
+ key: payload.key,
609
+ scope: payload.scope,
610
+ source: payload.source,
611
+ detail: `hook:${r.hookId} ${r.success ? "ok" : "fail"} \u2014 ${r.message}`
612
+ });
613
+ } catch {
614
+ }
615
+ }
616
+ return hookResults;
617
+ }
618
+
376
619
  // src/core/keyring.ts
377
620
  function readEnvelope(service, key) {
378
621
  const entry = new Entry(service, key);
@@ -432,6 +675,9 @@ function setSecret(key, value, opts = {}) {
432
675
  const source = opts.source ?? "cli";
433
676
  const existing = readEnvelope(service, key);
434
677
  let envelope;
678
+ const rotFmt = opts.rotationFormat ?? existing?.meta.rotationFormat;
679
+ const rotPfx = opts.rotationPrefix ?? existing?.meta.rotationPrefix;
680
+ const prov = opts.provider ?? existing?.meta.provider;
435
681
  if (opts.states) {
436
682
  envelope = createEnvelope("", {
437
683
  states: opts.states,
@@ -440,7 +686,10 @@ function setSecret(key, value, opts = {}) {
440
686
  tags: opts.tags,
441
687
  ttlSeconds: opts.ttlSeconds,
442
688
  expiresAt: opts.expiresAt,
443
- entangled: existing?.meta.entangled
689
+ entangled: existing?.meta.entangled,
690
+ rotationFormat: rotFmt,
691
+ rotationPrefix: rotPfx,
692
+ provider: prov
444
693
  });
445
694
  } else {
446
695
  envelope = createEnvelope(value, {
@@ -448,7 +697,10 @@ function setSecret(key, value, opts = {}) {
448
697
  tags: opts.tags,
449
698
  ttlSeconds: opts.ttlSeconds,
450
699
  expiresAt: opts.expiresAt,
451
- entangled: existing?.meta.entangled
700
+ entangled: existing?.meta.entangled,
701
+ rotationFormat: rotFmt,
702
+ rotationPrefix: rotPfx,
703
+ provider: prov
452
704
  });
453
705
  }
454
706
  if (existing) {
@@ -480,6 +732,14 @@ function setSecret(key, value, opts = {}) {
480
732
  } catch {
481
733
  }
482
734
  }
735
+ fireHooks({
736
+ action: "write",
737
+ key,
738
+ scope,
739
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
740
+ source
741
+ }, envelope.meta.tags).catch(() => {
742
+ });
483
743
  }
484
744
  function deleteSecret(key, opts = {}) {
485
745
  const scopes = resolveScope(opts);
@@ -491,6 +751,14 @@ function deleteSecret(key, opts = {}) {
491
751
  if (entry.deleteCredential()) {
492
752
  deleted = true;
493
753
  logAudit({ action: "delete", key, scope, source });
754
+ fireHooks({
755
+ action: "delete",
756
+ key,
757
+ scope,
758
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
759
+ source
760
+ }).catch(() => {
761
+ });
494
762
  }
495
763
  } catch {
496
764
  }
@@ -546,6 +814,48 @@ function listSecrets(opts = {}) {
546
814
  }
547
815
  return results.sort((a, b) => a.key.localeCompare(b.key));
548
816
  }
817
+ function exportSecrets(opts = {}) {
818
+ const format = opts.format ?? "env";
819
+ const env = resolveEnv(opts);
820
+ let entries = listSecrets(opts);
821
+ const source = opts.source ?? "cli";
822
+ if (opts.keys?.length) {
823
+ const keySet = new Set(opts.keys);
824
+ entries = entries.filter((e) => keySet.has(e.key));
825
+ }
826
+ if (opts.tags?.length) {
827
+ entries = entries.filter(
828
+ (e) => opts.tags.some((t) => e.envelope?.meta.tags?.includes(t))
829
+ );
830
+ }
831
+ const merged = /* @__PURE__ */ new Map();
832
+ const globalEntries = entries.filter((e) => e.scope === "global");
833
+ const projectEntries = entries.filter((e) => e.scope === "project");
834
+ for (const entry of [...globalEntries, ...projectEntries]) {
835
+ if (entry.envelope) {
836
+ const decay = checkDecay(entry.envelope);
837
+ if (decay.isExpired) continue;
838
+ const value = collapseValue(entry.envelope, env);
839
+ if (value !== null) {
840
+ merged.set(entry.key, value);
841
+ }
842
+ }
843
+ }
844
+ logAudit({ action: "export", source, detail: `format=${format}` });
845
+ if (format === "json") {
846
+ const obj = {};
847
+ for (const [key, value] of merged) {
848
+ obj[key] = value;
849
+ }
850
+ return JSON.stringify(obj, null, 2);
851
+ }
852
+ const lines = [];
853
+ for (const [key, value] of merged) {
854
+ const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
855
+ lines.push(`${key}="${escaped}"`);
856
+ }
857
+ return lines.join("\n");
858
+ }
549
859
  function entangleSecrets(sourceKey, sourceOpts, targetKey, targetOpts) {
550
860
  const sourceScopes = resolveScope({ ...sourceOpts, scope: sourceOpts.scope ?? "global" });
551
861
  const targetScopes = resolveScope({ ...targetOpts, scope: targetOpts.scope ?? "global" });
@@ -559,6 +869,19 @@ function entangleSecrets(sourceKey, sourceOpts, targetKey, targetOpts) {
559
869
  detail: `entangled with ${targetKey}`
560
870
  });
561
871
  }
872
+ function disentangleSecrets(sourceKey, sourceOpts, targetKey, targetOpts) {
873
+ const sourceScopes = resolveScope({ ...sourceOpts, scope: sourceOpts.scope ?? "global" });
874
+ const targetScopes = resolveScope({ ...targetOpts, scope: targetOpts.scope ?? "global" });
875
+ const source = { service: sourceScopes[0].service, key: sourceKey };
876
+ const target = { service: targetScopes[0].service, key: targetKey };
877
+ disentangle(source, target);
878
+ logAudit({
879
+ action: "entangle",
880
+ key: sourceKey,
881
+ source: sourceOpts.source ?? "cli",
882
+ detail: `disentangled from ${targetKey}`
883
+ });
884
+ }
562
885
 
563
886
  // src/core/tunnel.ts
564
887
  var tunnelStore = /* @__PURE__ */ new Map();
@@ -633,21 +956,30 @@ function tunnelList() {
633
956
 
634
957
  export {
635
958
  checkDecay,
959
+ readProjectConfig,
636
960
  collapseEnvironment,
637
961
  logAudit,
638
962
  queryAudit,
639
963
  detectAnomalies,
640
964
  listEntanglements,
965
+ registerHook,
966
+ removeHook,
967
+ listHooks,
968
+ enableHook,
969
+ disableHook,
970
+ fireHooks,
641
971
  getSecret,
642
972
  getEnvelope,
643
973
  setSecret,
644
974
  deleteSecret,
645
975
  hasSecret,
646
976
  listSecrets,
977
+ exportSecrets,
647
978
  entangleSecrets,
979
+ disentangleSecrets,
648
980
  tunnelCreate,
649
981
  tunnelRead,
650
982
  tunnelDestroy,
651
983
  tunnelList
652
984
  };
653
- //# sourceMappingURL=chunk-3WTTWJYU.js.map
985
+ //# sourceMappingURL=chunk-IGNU622R.js.map