sigild 0.0.1 → 0.0.2

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.
Files changed (143) hide show
  1. package/README.md +107 -34
  2. package/THREAT_MODEL.md +32 -15
  3. package/dist/src/bin/sigil-hook-post.d.ts +3 -0
  4. package/dist/src/bin/sigil-hook-post.d.ts.map +1 -0
  5. package/dist/src/bin/sigil-hook-post.js +15 -0
  6. package/dist/src/bin/sigil-hook-post.js.map +1 -0
  7. package/dist/src/bin/sigil-hook-pre.d.ts +3 -0
  8. package/dist/src/bin/sigil-hook-pre.d.ts.map +1 -0
  9. package/dist/src/bin/sigil-hook-pre.js +18 -0
  10. package/dist/src/bin/sigil-hook-pre.js.map +1 -0
  11. package/dist/src/bin/sigil-mcp.d.ts +3 -0
  12. package/dist/src/bin/sigil-mcp.d.ts.map +1 -0
  13. package/dist/src/bin/sigil-mcp.js +87 -0
  14. package/dist/src/bin/sigil-mcp.js.map +1 -0
  15. package/dist/src/bin/sigil.d.ts +3 -0
  16. package/dist/src/bin/sigil.d.ts.map +1 -0
  17. package/dist/src/bin/sigil.js +9 -0
  18. package/dist/src/bin/sigil.js.map +1 -0
  19. package/dist/src/cli/args.d.ts +26 -0
  20. package/dist/src/cli/args.d.ts.map +1 -0
  21. package/dist/src/cli/args.js +36 -0
  22. package/dist/src/cli/args.js.map +1 -0
  23. package/dist/src/cli/index.d.ts +7 -0
  24. package/dist/src/cli/index.d.ts.map +1 -0
  25. package/dist/src/cli/index.js +7 -0
  26. package/dist/src/cli/index.js.map +1 -0
  27. package/dist/src/cli/main.d.ts +26 -0
  28. package/dist/src/cli/main.d.ts.map +1 -0
  29. package/dist/src/cli/main.js +152 -0
  30. package/dist/src/cli/main.js.map +1 -0
  31. package/dist/src/cli/paths.d.ts +17 -0
  32. package/dist/src/cli/paths.d.ts.map +1 -0
  33. package/dist/src/cli/paths.js +12 -0
  34. package/dist/src/cli/paths.js.map +1 -0
  35. package/dist/src/cli/portal.d.ts +50 -0
  36. package/dist/src/cli/portal.d.ts.map +1 -0
  37. package/dist/src/cli/portal.js +93 -0
  38. package/dist/src/cli/portal.js.map +1 -0
  39. package/dist/src/cli/status.d.ts +28 -0
  40. package/dist/src/cli/status.d.ts.map +1 -0
  41. package/dist/src/cli/status.js +59 -0
  42. package/dist/src/cli/status.js.map +1 -0
  43. package/dist/src/cli/unlock.d.ts +36 -0
  44. package/dist/src/cli/unlock.d.ts.map +1 -0
  45. package/dist/src/cli/unlock.js +77 -0
  46. package/dist/src/cli/unlock.js.map +1 -0
  47. package/dist/src/control/client.d.ts +26 -0
  48. package/dist/src/control/client.d.ts.map +1 -0
  49. package/dist/src/control/client.js +76 -0
  50. package/dist/src/control/client.js.map +1 -0
  51. package/dist/src/control/index.d.ts +4 -0
  52. package/dist/src/control/index.d.ts.map +1 -0
  53. package/dist/src/control/index.js +4 -0
  54. package/dist/src/control/index.js.map +1 -0
  55. package/dist/src/control/protocol.d.ts +54 -0
  56. package/dist/src/control/protocol.d.ts.map +1 -0
  57. package/dist/src/control/protocol.js +60 -0
  58. package/dist/src/control/protocol.js.map +1 -0
  59. package/dist/src/control/server.d.ts +52 -0
  60. package/dist/src/control/server.d.ts.map +1 -0
  61. package/dist/src/control/server.js +199 -0
  62. package/dist/src/control/server.js.map +1 -0
  63. package/dist/src/daemon/handles.d.ts +35 -6
  64. package/dist/src/daemon/handles.d.ts.map +1 -1
  65. package/dist/src/daemon/handles.js +83 -28
  66. package/dist/src/daemon/handles.js.map +1 -1
  67. package/dist/src/daemon/index.d.ts +2 -3
  68. package/dist/src/daemon/index.d.ts.map +1 -1
  69. package/dist/src/daemon/index.js +2 -3
  70. package/dist/src/daemon/index.js.map +1 -1
  71. package/dist/src/daemon/methods.d.ts +6 -0
  72. package/dist/src/daemon/methods.d.ts.map +1 -1
  73. package/dist/src/daemon/methods.js +13 -1
  74. package/dist/src/daemon/methods.js.map +1 -1
  75. package/dist/src/hooks/command-scanner.d.ts +5 -0
  76. package/dist/src/hooks/command-scanner.d.ts.map +1 -0
  77. package/dist/src/hooks/command-scanner.js +117 -0
  78. package/dist/src/hooks/command-scanner.js.map +1 -0
  79. package/dist/src/hooks/glob.d.ts +8 -0
  80. package/dist/src/hooks/glob.d.ts.map +1 -0
  81. package/dist/src/hooks/glob.js +98 -0
  82. package/dist/src/hooks/glob.js.map +1 -0
  83. package/dist/src/hooks/index.d.ts +9 -0
  84. package/dist/src/hooks/index.d.ts.map +1 -0
  85. package/dist/src/hooks/index.js +9 -0
  86. package/dist/src/hooks/index.js.map +1 -0
  87. package/dist/src/hooks/install.d.ts +29 -0
  88. package/dist/src/hooks/install.d.ts.map +1 -0
  89. package/dist/src/hooks/install.js +86 -0
  90. package/dist/src/hooks/install.js.map +1 -0
  91. package/dist/src/hooks/path-blocker.d.ts +29 -0
  92. package/dist/src/hooks/path-blocker.d.ts.map +1 -0
  93. package/dist/src/hooks/path-blocker.js +59 -0
  94. package/dist/src/hooks/path-blocker.js.map +1 -0
  95. package/dist/src/hooks/post-tool-use.d.ts +13 -0
  96. package/dist/src/hooks/post-tool-use.d.ts.map +1 -0
  97. package/dist/src/hooks/post-tool-use.js +45 -0
  98. package/dist/src/hooks/post-tool-use.js.map +1 -0
  99. package/dist/src/hooks/pre-tool-use.d.ts +8 -0
  100. package/dist/src/hooks/pre-tool-use.d.ts.map +1 -0
  101. package/dist/src/hooks/pre-tool-use.js +38 -0
  102. package/dist/src/hooks/pre-tool-use.js.map +1 -0
  103. package/dist/src/hooks/protocol.d.ts +41 -0
  104. package/dist/src/hooks/protocol.d.ts.map +1 -0
  105. package/dist/src/hooks/protocol.js +27 -0
  106. package/dist/src/hooks/protocol.js.map +1 -0
  107. package/dist/src/hooks/redactor.d.ts +19 -0
  108. package/dist/src/hooks/redactor.d.ts.map +1 -0
  109. package/dist/src/hooks/redactor.js +71 -0
  110. package/dist/src/hooks/redactor.js.map +1 -0
  111. package/dist/src/mcp/index.d.ts +4 -0
  112. package/dist/src/mcp/index.d.ts.map +1 -0
  113. package/dist/src/mcp/index.js +4 -0
  114. package/dist/src/mcp/index.js.map +1 -0
  115. package/dist/src/mcp/protocol.d.ts +98 -0
  116. package/dist/src/mcp/protocol.d.ts.map +1 -0
  117. package/dist/src/mcp/protocol.js +79 -0
  118. package/dist/src/mcp/protocol.js.map +1 -0
  119. package/dist/src/mcp/server.d.ts +46 -0
  120. package/dist/src/mcp/server.d.ts.map +1 -0
  121. package/dist/src/mcp/server.js +108 -0
  122. package/dist/src/mcp/server.js.map +1 -0
  123. package/dist/src/mcp/tools.d.ts +16 -0
  124. package/dist/src/mcp/tools.d.ts.map +1 -0
  125. package/dist/src/mcp/tools.js +117 -0
  126. package/dist/src/mcp/tools.js.map +1 -0
  127. package/package.json +8 -3
  128. package/dist/src/bin/sigild.d.ts +0 -3
  129. package/dist/src/bin/sigild.d.ts.map +0 -1
  130. package/dist/src/bin/sigild.js +0 -30
  131. package/dist/src/bin/sigild.js.map +0 -1
  132. package/dist/src/daemon/rpc.d.ts +0 -61
  133. package/dist/src/daemon/rpc.d.ts.map +0 -1
  134. package/dist/src/daemon/rpc.js +0 -76
  135. package/dist/src/daemon/rpc.js.map +0 -1
  136. package/dist/src/daemon/runtime.d.ts +0 -40
  137. package/dist/src/daemon/runtime.d.ts.map +0 -1
  138. package/dist/src/daemon/runtime.js +0 -61
  139. package/dist/src/daemon/runtime.js.map +0 -1
  140. package/dist/src/daemon/server.d.ts +0 -53
  141. package/dist/src/daemon/server.d.ts.map +0 -1
  142. package/dist/src/daemon/server.js +0 -103
  143. package/dist/src/daemon/server.js.map +0 -1
@@ -0,0 +1,76 @@
1
+ import { createConnection } from 'node:net';
2
+ const DEFAULT_TIMEOUT_MS = 5_000;
3
+ /**
4
+ * Sends a single request to the running sigil-mcp control socket and returns
5
+ * the parsed response. Throws ControlClientError if:
6
+ * - the socket file is missing or refused (server not running)
7
+ * - the connection times out
8
+ * - the response is not valid JSON
9
+ *
10
+ * Note: a server-side denial (wrong passphrase, etc.) is NOT thrown — it
11
+ * comes back as a ControlResponse with `ok: false`. Callers branch on that.
12
+ */
13
+ export async function controlRequest(opts) {
14
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
15
+ const sock = createConnection(opts.socketPath);
16
+ sock.setEncoding('utf8');
17
+ let timer = null;
18
+ const cleanupTimer = () => { if (timer)
19
+ clearTimeout(timer); timer = null; };
20
+ return new Promise((resolve, reject) => {
21
+ timer = setTimeout(() => {
22
+ sock.destroy();
23
+ reject(new ControlClientError(`control socket timeout after ${timeoutMs}ms`, 'TIMEOUT'));
24
+ }, timeoutMs);
25
+ let buf = '';
26
+ let resolved = false;
27
+ const finish = (fn) => {
28
+ if (resolved)
29
+ return;
30
+ resolved = true;
31
+ cleanupTimer();
32
+ fn();
33
+ };
34
+ sock.on('connect', () => {
35
+ sock.write(JSON.stringify(opts.request) + '\n');
36
+ });
37
+ sock.on('data', (chunk) => {
38
+ buf += chunk;
39
+ const nl = buf.indexOf('\n');
40
+ if (nl === -1)
41
+ return;
42
+ const line = buf.slice(0, nl);
43
+ sock.end();
44
+ try {
45
+ const parsed = JSON.parse(line);
46
+ finish(() => resolve(parsed));
47
+ }
48
+ catch {
49
+ finish(() => reject(new ControlClientError('control socket returned non-JSON response', 'BAD_RESPONSE')));
50
+ }
51
+ });
52
+ sock.on('end', () => {
53
+ if (!resolved) {
54
+ if (buf.length === 0) {
55
+ finish(() => reject(new ControlClientError('control socket closed without sending a response', 'NO_RESPONSE')));
56
+ }
57
+ }
58
+ });
59
+ sock.on('error', (err) => {
60
+ const code = err.code;
61
+ let mapped = 'CONNECT_FAILED';
62
+ if (code === 'ENOENT' || code === 'ECONNREFUSED')
63
+ mapped = 'SERVER_DOWN';
64
+ finish(() => reject(new ControlClientError(`control socket: ${err.message}`, mapped)));
65
+ });
66
+ });
67
+ }
68
+ export class ControlClientError extends Error {
69
+ code;
70
+ constructor(message, code) {
71
+ super(message);
72
+ this.name = 'ControlClientError';
73
+ this.code = code;
74
+ }
75
+ }
76
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/control/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { CONTROL_SOCKET_VERSION, type ControlRequest, type ControlResponse, type ControlSuccess, type ControlError, type ControlErrorCode, type PortalSummary, isControlError, parseControlRequest, } from './protocol.js';
2
+ export { type ControlServerOpts, type ControlServerHandle, type ControlLogEvent, startControlServer, } from './server.js';
3
+ export { type ControlRequestOpts, type ControlClientErrorCode, ControlClientError, controlRequest, } from './client.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/control/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,cAAc,EACd,mBAAmB,GACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC3B,kBAAkB,EAClB,cAAc,GACf,MAAM,aAAa,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { CONTROL_SOCKET_VERSION, isControlError, parseControlRequest, } from './protocol.js';
2
+ export { startControlServer, } from './server.js';
3
+ export { ControlClientError, controlRequest, } from './client.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/control/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EAOtB,cAAc,EACd,mBAAmB,GACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAIL,kBAAkB,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EAGL,kBAAkB,EAClB,cAAc,GACf,MAAM,aAAa,CAAC"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Wire protocol for the sigil-mcp control socket.
3
+ *
4
+ * One NDJSON request per connection, one NDJSON response, then the server
5
+ * half-closes. The socket lives at `~/.sigil/control.sock` (0600).
6
+ *
7
+ * Methods:
8
+ * - unlock: load keyfiles from disk into the running MCP server's
9
+ * HandleTable. Passphrase is shipped as base64 because raw bytes
10
+ * don't survive JSON's UTF-8 round-trip cleanly. Server decodes
11
+ * into a Buffer + zeroizes after use.
12
+ * - lock: zeroize the HandleTable. Re-lockable; a later unlock works.
13
+ * - status: probe — returns PID + unlocked flag + portal count.
14
+ *
15
+ * Error responses always carry a machine-readable `code` so the CLI can
16
+ * branch on it without parsing the message.
17
+ */
18
+ export declare const CONTROL_SOCKET_VERSION = 1;
19
+ export type ControlRequest = {
20
+ method: 'unlock';
21
+ passphraseB64: string;
22
+ } | {
23
+ method: 'lock';
24
+ } | {
25
+ method: 'status';
26
+ };
27
+ export type ControlResponse = ControlSuccess | ControlError;
28
+ export interface ControlSuccess {
29
+ ok: true;
30
+ /** Server protocol version — clients should verify this matches. */
31
+ version: number;
32
+ /** Process ID of the running sigil-mcp. */
33
+ pid: number;
34
+ /** Whether the HandleTable is currently unlocked. */
35
+ unlocked: boolean;
36
+ /** Currently loaded portals (empty when locked, or when zero on disk). */
37
+ portals: PortalSummary[];
38
+ }
39
+ export interface PortalSummary {
40
+ handle: string;
41
+ kind: 'eth';
42
+ address: string;
43
+ }
44
+ export interface ControlError {
45
+ ok: false;
46
+ /** Stable error code. */
47
+ code: ControlErrorCode;
48
+ /** Human-readable message. */
49
+ error: string;
50
+ }
51
+ export type ControlErrorCode = 'INVALID_REQUEST' | 'UNKNOWN_METHOD' | 'ALREADY_UNLOCKED' | 'WRONG_PASSPHRASE' | 'KEYS_LOAD_FAILED' | 'INTERNAL';
52
+ export declare function parseControlRequest(line: string): ControlRequest | ControlError;
53
+ export declare function isControlError(resp: ControlResponse): resp is ControlError;
54
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../../src/control/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,MAAM,MAAM,cAAc,GACtB;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,GAC3C;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,MAAM,EAAE,QAAQ,CAAA;CAAE,CAAC;AAEzB,MAAM,MAAM,eAAe,GACvB,cAAc,GACd,YAAY,CAAC;AAEjB,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,IAAI,CAAC;IACT,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,qDAAqD;IACrD,QAAQ,EAAE,OAAO,CAAC;IAClB,0EAA0E;IAC1E,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,KAAK,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,KAAK,CAAC;IACV,yBAAyB;IACzB,IAAI,EAAE,gBAAgB,CAAC;IACvB,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,gBAAgB,GACxB,iBAAiB,GACjB,gBAAgB,GAChB,kBAAkB,GAClB,kBAAkB,GAClB,kBAAkB,GAClB,UAAU,CAAC;AAMf,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,YAAY,CA8B/E;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,IAAI,YAAY,CAE1E"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Wire protocol for the sigil-mcp control socket.
3
+ *
4
+ * One NDJSON request per connection, one NDJSON response, then the server
5
+ * half-closes. The socket lives at `~/.sigil/control.sock` (0600).
6
+ *
7
+ * Methods:
8
+ * - unlock: load keyfiles from disk into the running MCP server's
9
+ * HandleTable. Passphrase is shipped as base64 because raw bytes
10
+ * don't survive JSON's UTF-8 round-trip cleanly. Server decodes
11
+ * into a Buffer + zeroizes after use.
12
+ * - lock: zeroize the HandleTable. Re-lockable; a later unlock works.
13
+ * - status: probe — returns PID + unlocked flag + portal count.
14
+ *
15
+ * Error responses always carry a machine-readable `code` so the CLI can
16
+ * branch on it without parsing the message.
17
+ */
18
+ export const CONTROL_SOCKET_VERSION = 1;
19
+ // ---------------------------------------------------------------------------
20
+ // Parsing helpers
21
+ // ---------------------------------------------------------------------------
22
+ export function parseControlRequest(line) {
23
+ let raw;
24
+ try {
25
+ raw = JSON.parse(line);
26
+ }
27
+ catch {
28
+ return invalid('request is not valid JSON');
29
+ }
30
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
31
+ return invalid('request must be a JSON object');
32
+ }
33
+ const obj = raw;
34
+ const method = obj['method'];
35
+ if (typeof method !== 'string') {
36
+ return invalid('request.method must be a string');
37
+ }
38
+ if (method === 'lock' || method === 'status') {
39
+ return { method };
40
+ }
41
+ if (method === 'unlock') {
42
+ const p = obj['passphraseB64'];
43
+ if (typeof p !== 'string') {
44
+ return invalid('unlock.passphraseB64 must be a string');
45
+ }
46
+ return { method: 'unlock', passphraseB64: p };
47
+ }
48
+ return {
49
+ ok: false,
50
+ code: 'UNKNOWN_METHOD',
51
+ error: `unknown control method "${method}"`,
52
+ };
53
+ }
54
+ export function isControlError(resp) {
55
+ return resp.ok === false;
56
+ }
57
+ function invalid(message) {
58
+ return { ok: false, code: 'INVALID_REQUEST', error: message };
59
+ }
60
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../../src/control/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC;AA6CxC,8EAA8E;AAC9E,kBAAkB;AAClB,8EAA8E;AAE9E,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC,2BAA2B,CAAC,CAAC;IAC9C,CAAC;IACD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAClE,OAAO,OAAO,CAAC,+BAA+B,CAAC,CAAC;IAClD,CAAC;IACD,MAAM,GAAG,GAAG,GAA8B,CAAC;IAC3C,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC7B,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,OAAO,OAAO,CAAC,iCAAiC,CAAC,CAAC;IACpD,CAAC;IACD,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC7C,OAAO,EAAE,MAAM,EAAE,CAAC;IACpB,CAAC;IACD,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;QACxB,MAAM,CAAC,GAAG,GAAG,CAAC,eAAe,CAAC,CAAC;QAC/B,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;YAC1B,OAAO,OAAO,CAAC,uCAAuC,CAAC,CAAC;QAC1D,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC,EAAE,CAAC;IAChD,CAAC;IACD,OAAO;QACL,EAAE,EAAE,KAAK;QACT,IAAI,EAAE,gBAAgB;QACtB,KAAK,EAAE,2BAA2B,MAAM,GAAG;KAC5C,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAqB;IAClD,OAAO,IAAI,CAAC,EAAE,KAAK,KAAK,CAAC;AAC3B,CAAC;AAED,SAAS,OAAO,CAAC,OAAe;IAC9B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,iBAAiB,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AAChE,CAAC"}
@@ -0,0 +1,52 @@
1
+ import { HandleTable } from '../daemon/handles.js';
2
+ export interface ControlServerOpts {
3
+ /** Path to bind the Unix socket. */
4
+ socketPath: string;
5
+ /** Directory of encrypted keyfiles to load on unlock. */
6
+ keysDir: string;
7
+ /** Live HandleTable shared with the MCP server. */
8
+ handles: HandleTable;
9
+ /** Optional log sink — defaults to no-op. */
10
+ onLog?: (event: ControlLogEvent) => void;
11
+ /** Optional process ID override (tests). */
12
+ pid?: number;
13
+ }
14
+ export type ControlLogEvent = {
15
+ kind: 'listening';
16
+ path: string;
17
+ } | {
18
+ kind: 'accepted';
19
+ } | {
20
+ kind: 'request';
21
+ method: string;
22
+ } | {
23
+ kind: 'response';
24
+ ok: boolean;
25
+ code?: string;
26
+ } | {
27
+ kind: 'parse_error';
28
+ line: string;
29
+ } | {
30
+ kind: 'stale_socket_cleared';
31
+ path: string;
32
+ } | {
33
+ kind: 'bind_error';
34
+ error: string;
35
+ };
36
+ export interface ControlServerHandle {
37
+ /** Returns when the server socket is closed (and the file unlinked). */
38
+ close(): Promise<void>;
39
+ /** The bound socket path (echoed back for convenience). */
40
+ socketPath: string;
41
+ }
42
+ /**
43
+ * Start a control listener bound to `socketPath`. Returns once the server is
44
+ * listening. On EADDRINUSE, probes the existing socket: if it answers, throws
45
+ * (a live sigil-mcp owns it); if it doesn't, unlinks the stale socket and
46
+ * re-binds.
47
+ *
48
+ * The server is unref'd — it doesn't keep the event loop alive on its own,
49
+ * so sigil-mcp can still exit when stdin closes.
50
+ */
51
+ export declare function startControlServer(opts: ControlServerOpts): Promise<ControlServerHandle>;
52
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/control/server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmB,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAWpE,MAAM,WAAW,iBAAiB;IAChC,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,OAAO,EAAE,WAAW,CAAC;IACrB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;IACzC,4CAA4C;IAC5C,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,EAAE,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,sBAAsB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1C,MAAM,WAAW,mBAAmB;IAClC,wEAAwE;IACxE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,2DAA2D;IAC3D,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA8B9F"}
@@ -0,0 +1,199 @@
1
+ import { chmodSync, existsSync, unlinkSync } from 'node:fs';
2
+ import { createConnection, createServer } from 'node:net';
3
+ import { HandleLoadError } from '../daemon/handles.js';
4
+ import { CONTROL_SOCKET_VERSION, parseControlRequest, } from './protocol.js';
5
+ /**
6
+ * Start a control listener bound to `socketPath`. Returns once the server is
7
+ * listening. On EADDRINUSE, probes the existing socket: if it answers, throws
8
+ * (a live sigil-mcp owns it); if it doesn't, unlinks the stale socket and
9
+ * re-binds.
10
+ *
11
+ * The server is unref'd — it doesn't keep the event loop alive on its own,
12
+ * so sigil-mcp can still exit when stdin closes.
13
+ */
14
+ export async function startControlServer(opts) {
15
+ const log = (e) => opts.onLog?.(e);
16
+ const pid = opts.pid ?? process.pid;
17
+ const server = createServer((sock) => {
18
+ handleConnection(sock, opts, pid, log).catch((err) => {
19
+ log({ kind: 'bind_error', error: err.message });
20
+ sock.destroy();
21
+ });
22
+ });
23
+ server.unref();
24
+ await bindWithStaleSocketRecovery(server, opts.socketPath, log);
25
+ try {
26
+ chmodSync(opts.socketPath, 0o600);
27
+ }
28
+ catch {
29
+ // Best-effort; on some platforms this may not be needed.
30
+ }
31
+ log({ kind: 'listening', path: opts.socketPath });
32
+ return {
33
+ socketPath: opts.socketPath,
34
+ close: () => new Promise((resolve) => {
35
+ server.close(() => {
36
+ try {
37
+ unlinkSync(opts.socketPath);
38
+ }
39
+ catch { /* may already be gone */ }
40
+ resolve();
41
+ });
42
+ }),
43
+ };
44
+ }
45
+ // ---------------------------------------------------------------------------
46
+ // Bind + stale-socket recovery
47
+ // ---------------------------------------------------------------------------
48
+ async function bindWithStaleSocketRecovery(server, socketPath, log) {
49
+ try {
50
+ await listen(server, socketPath);
51
+ return;
52
+ }
53
+ catch (err) {
54
+ if (err.code !== 'EADDRINUSE')
55
+ throw err;
56
+ }
57
+ // Something is at the socket path. Probe it.
58
+ const alive = await isSocketAlive(socketPath);
59
+ if (alive) {
60
+ throw new Error(`control socket ${socketPath} is already in use by another sigil-mcp process`);
61
+ }
62
+ // Stale — unlink and retry.
63
+ try {
64
+ unlinkSync(socketPath);
65
+ }
66
+ catch { /* race: someone else cleaned it */ }
67
+ log({ kind: 'stale_socket_cleared', path: socketPath });
68
+ await listen(server, socketPath);
69
+ }
70
+ function listen(server, socketPath) {
71
+ return new Promise((resolve, reject) => {
72
+ const onError = (err) => {
73
+ server.removeListener('listening', onListening);
74
+ reject(err);
75
+ };
76
+ const onListening = () => {
77
+ server.removeListener('error', onError);
78
+ resolve();
79
+ };
80
+ server.once('error', onError);
81
+ server.once('listening', onListening);
82
+ server.listen(socketPath);
83
+ });
84
+ }
85
+ function isSocketAlive(socketPath) {
86
+ if (!existsSync(socketPath))
87
+ return Promise.resolve(false);
88
+ return new Promise((resolve) => {
89
+ const sock = createConnection(socketPath);
90
+ const cleanup = (alive) => {
91
+ sock.removeAllListeners();
92
+ sock.destroy();
93
+ resolve(alive);
94
+ };
95
+ sock.once('connect', () => cleanup(true));
96
+ sock.once('error', () => cleanup(false));
97
+ });
98
+ }
99
+ // ---------------------------------------------------------------------------
100
+ // Per-connection handler
101
+ // ---------------------------------------------------------------------------
102
+ async function handleConnection(sock, opts, pid, log) {
103
+ log({ kind: 'accepted' });
104
+ let buf = '';
105
+ // Read until we have one line, then dispatch.
106
+ const line = await new Promise((resolve) => {
107
+ sock.setEncoding('utf8');
108
+ const onData = (chunk) => {
109
+ buf += chunk;
110
+ const nl = buf.indexOf('\n');
111
+ if (nl !== -1) {
112
+ sock.off('data', onData);
113
+ resolve(buf.slice(0, nl));
114
+ }
115
+ };
116
+ sock.on('data', onData);
117
+ sock.once('end', () => resolve(buf.length > 0 ? buf : null));
118
+ sock.once('error', () => resolve(null));
119
+ });
120
+ if (line === null) {
121
+ sock.end();
122
+ return;
123
+ }
124
+ const parsed = parseControlRequest(line);
125
+ if ('ok' in parsed && parsed.ok === false) {
126
+ log({ kind: 'parse_error', line });
127
+ write(sock, parsed);
128
+ return;
129
+ }
130
+ const request = parsed;
131
+ log({ kind: 'request', method: request.method });
132
+ const response = dispatch(request, opts, pid);
133
+ log({ kind: 'response', ok: response.ok, ...(response.ok ? {} : { code: response.code }) });
134
+ write(sock, response);
135
+ }
136
+ function write(sock, resp) {
137
+ sock.end(JSON.stringify(resp) + '\n');
138
+ }
139
+ // ---------------------------------------------------------------------------
140
+ // Dispatch
141
+ // ---------------------------------------------------------------------------
142
+ function dispatch(req, opts, pid) {
143
+ switch (req.method) {
144
+ case 'status':
145
+ return statusResponse(opts.handles, pid);
146
+ case 'lock':
147
+ opts.handles.lock();
148
+ return statusResponse(opts.handles, pid);
149
+ case 'unlock':
150
+ return doUnlock(req.passphraseB64, opts, pid);
151
+ }
152
+ }
153
+ function doUnlock(passphraseB64, opts, pid) {
154
+ if (opts.handles.isUnlocked()) {
155
+ return {
156
+ ok: false,
157
+ code: 'ALREADY_UNLOCKED',
158
+ error: 'sigil is already unlocked; run "sigil lock" first to re-unlock',
159
+ };
160
+ }
161
+ let passphrase;
162
+ try {
163
+ passphrase = Buffer.from(passphraseB64, 'base64');
164
+ }
165
+ catch {
166
+ return { ok: false, code: 'INVALID_REQUEST', error: 'passphraseB64 is not valid base64' };
167
+ }
168
+ try {
169
+ opts.handles.loadFromDir(opts.keysDir, passphrase);
170
+ }
171
+ catch (err) {
172
+ if (err instanceof HandleLoadError) {
173
+ // Distinguish wrong-passphrase from other load failures so the CLI can
174
+ // suggest re-running unlock specifically.
175
+ if (/wrong passphrase|tampered/.test(err.message)) {
176
+ return { ok: false, code: 'WRONG_PASSPHRASE', error: err.message };
177
+ }
178
+ return { ok: false, code: 'KEYS_LOAD_FAILED', error: err.message };
179
+ }
180
+ return { ok: false, code: 'INTERNAL', error: err.message };
181
+ }
182
+ finally {
183
+ passphrase.fill(0);
184
+ }
185
+ return statusResponse(opts.handles, pid);
186
+ }
187
+ function statusResponse(handles, pid) {
188
+ const portals = handles.isUnlocked()
189
+ ? handles.list().map((p) => ({ handle: p.handle, kind: p.kind, address: p.address }))
190
+ : [];
191
+ return {
192
+ ok: true,
193
+ version: CONTROL_SOCKET_VERSION,
194
+ pid,
195
+ unlocked: handles.isUnlocked(),
196
+ portals,
197
+ };
198
+ }
199
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/control/server.ts"],"names":[],"mappings":"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"}
@@ -11,9 +11,16 @@ export declare class HandleLoadError extends Error {
11
11
  /**
12
12
  * In-memory registry of handle → unlocked SecretBuffer.
13
13
  *
14
- * The expectation is that this is constructed once at daemon startup, populated
15
- * via loadFromDir (or addEntry from tests), and disposed once at shutdown.
16
- * After dispose() any further get/list calls throw.
14
+ * Lifecycle inside sigil-mcp:
15
+ * - Constructed empty + locked at startup.
16
+ * - `sigil unlock` calls loadFromDir / addEntry → table becomes unlocked.
17
+ * - `sigil lock` calls lock() → entries zeroed + cleared, table re-lockable.
18
+ * - Process exit calls dispose() → final teardown, no further use.
19
+ *
20
+ * The unlocked flag is tracked separately from entry count so that an unlock
21
+ * with zero portals on disk still distinguishes "no portals exist" (handle
22
+ * lookup → PORTAL_NOT_FOUND) from "never unlocked" (handle lookup →
23
+ * DAEMON_LOCKED).
17
24
  */
18
25
  export declare class HandleTable {
19
26
  #private;
@@ -24,17 +31,31 @@ export declare class HandleTable {
24
31
  static handleFromFilename(filename: string): string | null;
25
32
  /**
26
33
  * Load every keyfile in `dir` named `<handle>.sigil`, decrypting each with
27
- * the same passphrase. Throws HandleLoadError on any failure.
34
+ * the same passphrase. Throws HandleLoadError on any failure; on failure
35
+ * the table is left locked with any partially-loaded entries zeroized.
28
36
  *
29
37
  * Order: deterministic by sorted filename, so audit logs and list_portals
30
38
  * output are stable across restarts.
39
+ *
40
+ * Marks the table as unlocked on success — even if the directory was
41
+ * empty (zero portals to load). After that, sign calls see PORTAL_NOT_FOUND
42
+ * instead of DAEMON_LOCKED.
31
43
  */
32
44
  loadFromDir(dir: string, passphrase: Buffer): void;
33
45
  /**
34
46
  * Add a handle directly (for tests or for non-file-backed key sources).
35
- * Takes ownership of the SecretBuffer; dispose() will zeroize it.
47
+ * Takes ownership of the SecretBuffer; lock()/dispose() will zeroize it.
48
+ * Does NOT flip the unlocked flag — loadFromDir does that once after a
49
+ * successful pass. Tests that want an unlocked table should call
50
+ * markUnlocked() explicitly.
36
51
  */
37
52
  addEntry(handle: string, secret: SecretBuffer): void;
53
+ /**
54
+ * Explicitly mark the table as unlocked without loading anything. Useful
55
+ * for tests that pre-populate via addEntry and want sign methods to
56
+ * succeed.
57
+ */
58
+ markUnlocked(): void;
38
59
  has(handle: string): boolean;
39
60
  /**
40
61
  * Returns the SecretBuffer for the handle. Caller must NOT dispose it —
@@ -42,8 +63,16 @@ export declare class HandleTable {
42
63
  */
43
64
  get(handle: string): SecretBuffer | undefined;
44
65
  list(): PortalInfo[];
66
+ isUnlocked(): boolean;
67
+ /**
68
+ * Zeroize every entry and re-lock the table. The table remains usable —
69
+ * a subsequent unlock can repopulate it. Idempotent.
70
+ */
71
+ lock(): void;
45
72
  /**
46
- * Zeroize every key and mark the table unusable. Idempotent.
73
+ * Final teardown: zeroize every key and mark the table unusable. After
74
+ * dispose(), get/has/list/lock/markUnlocked/addEntry/loadFromDir throw.
75
+ * Idempotent.
47
76
  */
48
77
  dispose(): void;
49
78
  get isDisposed(): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"handles.d.ts","sourceRoot":"","sources":["../../../src/daemon/handles.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,YAAY,EAGlB,MAAM,oBAAoB,CAAC;AAQ5B,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,KAAK,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,eAAgB,SAAQ,KAAK;IACxC,SAAkB,KAAK,CAAC,EAAE,OAAO,CAAC;gBACtB,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAK7C;AAED;;;;;;GAMG;AACH,qBAAa,WAAW;;IAKtB,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;IAMjE,MAAM,CAAC,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAO1D;;;;;;OAMG;IACH,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAgClD;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI;IAcpD,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAK5B;;;OAGG;IACH,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAK7C,IAAI,IAAI,UAAU,EAAE;IAKpB;;OAEG;IACH,OAAO,IAAI,IAAI;IAOf,IAAI,UAAU,IAAI,OAAO,CAExB;CACF"}
1
+ {"version":3,"file":"handles.d.ts","sourceRoot":"","sources":["../../../src/daemon/handles.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,YAAY,EAGlB,MAAM,oBAAoB,CAAC;AAQ5B,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,KAAK,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,eAAgB,SAAQ,KAAK;IACxC,SAAkB,KAAK,CAAC,EAAE,OAAO,CAAC;gBACtB,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAK7C;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,WAAW;;IAMtB,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;IAMjE,MAAM,CAAC,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAO1D;;;;;;;;;;;OAWG;IACH,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAyClD;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI;IAcpD;;;;OAIG;IACH,YAAY,IAAI,IAAI;IAKpB,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAK5B;;;OAGG;IACH,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAK7C,IAAI,IAAI,UAAU,EAAE;IAKpB,UAAU,IAAI,OAAO;IAKrB;;;OAGG;IACH,IAAI,IAAI,IAAI;IAOZ;;;;OAIG;IACH,OAAO,IAAI,IAAI;IAQf,IAAI,UAAU,IAAI,OAAO,CAExB;CACF"}