sigild 0.0.1 → 0.0.3

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 (163) hide show
  1. package/README.md +142 -33
  2. package/THREAT_MODEL.md +47 -19
  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 +90 -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 +197 -0
  30. package/dist/src/cli/main.js.map +1 -0
  31. package/dist/src/cli/paths.d.ts +18 -0
  32. package/dist/src/cli/paths.d.ts.map +1 -0
  33. package/dist/src/cli/paths.js +13 -0
  34. package/dist/src/cli/paths.js.map +1 -0
  35. package/dist/src/cli/portal.d.ts +59 -0
  36. package/dist/src/cli/portal.d.ts.map +1 -0
  37. package/dist/src/cli/portal.js +112 -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 +13 -0
  72. package/dist/src/daemon/methods.d.ts.map +1 -1
  73. package/dist/src/daemon/methods.js +50 -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/dist/src/policy/evaluate.d.ts +13 -0
  128. package/dist/src/policy/evaluate.d.ts.map +1 -0
  129. package/dist/src/policy/evaluate.js +73 -0
  130. package/dist/src/policy/evaluate.js.map +1 -0
  131. package/dist/src/policy/index.d.ts +5 -0
  132. package/dist/src/policy/index.d.ts.map +1 -0
  133. package/dist/src/policy/index.js +5 -0
  134. package/dist/src/policy/index.js.map +1 -0
  135. package/dist/src/policy/loader.d.ts +33 -0
  136. package/dist/src/policy/loader.d.ts.map +1 -0
  137. package/dist/src/policy/loader.js +170 -0
  138. package/dist/src/policy/loader.js.map +1 -0
  139. package/dist/src/policy/template.d.ts +10 -0
  140. package/dist/src/policy/template.d.ts.map +1 -0
  141. package/dist/src/policy/template.js +69 -0
  142. package/dist/src/policy/template.js.map +1 -0
  143. package/dist/src/policy/types.d.ts +62 -0
  144. package/dist/src/policy/types.d.ts.map +1 -0
  145. package/dist/src/policy/types.js +10 -0
  146. package/dist/src/policy/types.js.map +1 -0
  147. package/package.json +9 -3
  148. package/dist/src/bin/sigild.d.ts +0 -3
  149. package/dist/src/bin/sigild.d.ts.map +0 -1
  150. package/dist/src/bin/sigild.js +0 -30
  151. package/dist/src/bin/sigild.js.map +0 -1
  152. package/dist/src/daemon/rpc.d.ts +0 -61
  153. package/dist/src/daemon/rpc.d.ts.map +0 -1
  154. package/dist/src/daemon/rpc.js +0 -76
  155. package/dist/src/daemon/rpc.js.map +0 -1
  156. package/dist/src/daemon/runtime.d.ts +0 -40
  157. package/dist/src/daemon/runtime.d.ts.map +0 -1
  158. package/dist/src/daemon/runtime.js +0 -61
  159. package/dist/src/daemon/runtime.js.map +0 -1
  160. package/dist/src/daemon/server.d.ts +0 -53
  161. package/dist/src/daemon/server.d.ts.map +0 -1
  162. package/dist/src/daemon/server.js +0 -103
  163. package/dist/src/daemon/server.js.map +0 -1
@@ -0,0 +1,77 @@
1
+ import { ControlClientError, controlRequest, isControlError, } from '../control/index.js';
2
+ /**
3
+ * Send an unlock request to the running sigil-mcp. Encodes the passphrase as
4
+ * base64 for transport; does NOT clear the caller's buffer (the caller owns
5
+ * lifetime). Returns the parsed response, or a client error if the server
6
+ * was unreachable.
7
+ */
8
+ export async function unlock(opts) {
9
+ const passphraseB64 = opts.passphrase.toString('base64');
10
+ try {
11
+ const response = await controlRequest({
12
+ socketPath: opts.paths.controlSocket,
13
+ request: { method: 'unlock', passphraseB64 },
14
+ ...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),
15
+ });
16
+ return { response };
17
+ }
18
+ catch (err) {
19
+ if (err instanceof ControlClientError)
20
+ return { response: null, clientError: err };
21
+ throw err;
22
+ }
23
+ }
24
+ export async function lock(opts) {
25
+ try {
26
+ const response = await controlRequest({
27
+ socketPath: opts.paths.controlSocket,
28
+ request: { method: 'lock' },
29
+ ...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),
30
+ });
31
+ return { response };
32
+ }
33
+ catch (err) {
34
+ if (err instanceof ControlClientError)
35
+ return { response: null, clientError: err };
36
+ throw err;
37
+ }
38
+ }
39
+ /**
40
+ * Format a result for human-readable CLI output. Returns the message and the
41
+ * exit code the CLI should use.
42
+ */
43
+ export function formatResult(action, result) {
44
+ if (result.clientError) {
45
+ if (result.clientError.code === 'SERVER_DOWN') {
46
+ return {
47
+ message: `sigil-mcp is not running. Start a Claude Code session (which spawns it via your MCP config) and try again.`,
48
+ code: 1,
49
+ };
50
+ }
51
+ return { message: `sigil ${action}: ${result.clientError.message}`, code: 1 };
52
+ }
53
+ const resp = result.response;
54
+ if (isControlError(resp)) {
55
+ if (resp.code === 'WRONG_PASSPHRASE') {
56
+ return { message: `sigil ${action}: wrong passphrase`, code: 2 };
57
+ }
58
+ if (resp.code === 'ALREADY_UNLOCKED') {
59
+ return {
60
+ message: `sigil ${action}: already unlocked; run "sigil lock" first if you want to re-unlock`,
61
+ code: 1,
62
+ };
63
+ }
64
+ return { message: `sigil ${action}: ${resp.error} (${resp.code})`, code: 1 };
65
+ }
66
+ if (action === 'unlock') {
67
+ const n = resp.portals.length;
68
+ return {
69
+ message: n === 0
70
+ ? 'unlocked (no portals on disk yet — add one with "sigil portal add")'
71
+ : `unlocked ${n} portal${n === 1 ? '' : 's'}: ${resp.portals.map((p) => p.handle).join(', ')}`,
72
+ code: 0,
73
+ };
74
+ }
75
+ return { message: 'locked', code: 0 };
76
+ }
77
+ //# sourceMappingURL=unlock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unlock.js","sourceRoot":"","sources":["../../../src/cli/unlock.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { ControlRequest, ControlResponse } from './protocol.js';
2
+ export interface ControlRequestOpts {
3
+ /** Path of the control socket (defaults provided by callers). */
4
+ socketPath: string;
5
+ /** The request to send. */
6
+ request: ControlRequest;
7
+ /** Connect + I/O timeout (ms). Defaults to 5s. */
8
+ timeoutMs?: number;
9
+ }
10
+ /**
11
+ * Sends a single request to the running sigil-mcp control socket and returns
12
+ * the parsed response. Throws ControlClientError if:
13
+ * - the socket file is missing or refused (server not running)
14
+ * - the connection times out
15
+ * - the response is not valid JSON
16
+ *
17
+ * Note: a server-side denial (wrong passphrase, etc.) is NOT thrown — it
18
+ * comes back as a ControlResponse with `ok: false`. Callers branch on that.
19
+ */
20
+ export declare function controlRequest(opts: ControlRequestOpts): Promise<ControlResponse>;
21
+ export type ControlClientErrorCode = 'SERVER_DOWN' | 'CONNECT_FAILED' | 'TIMEOUT' | 'BAD_RESPONSE' | 'NO_RESPONSE';
22
+ export declare class ControlClientError extends Error {
23
+ readonly code: ControlClientErrorCode;
24
+ constructor(message: string, code: ControlClientErrorCode);
25
+ }
26
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/control/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAIrE,MAAM,WAAW,kBAAkB;IACjC,iEAAiE;IACjE,UAAU,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,OAAO,EAAE,cAAc,CAAC;IACxB,kDAAkD;IAClD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,eAAe,CAAC,CA8DvF;AAED,MAAM,MAAM,sBAAsB,GAC9B,aAAa,GACb,gBAAgB,GAChB,SAAS,GACT,cAAc,GACd,aAAa,CAAC;AAElB,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;gBAC1B,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,sBAAsB;CAK1D"}
@@ -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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"}