claude-remote-approver 0.3.5 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.mjs +2 -2
- package/package.json +1 -1
- package/src/hook.mjs +6 -6
- package/src/ntfy.mjs +53 -26
package/bin/cli.mjs
CHANGED
|
@@ -87,7 +87,7 @@ export async function main(args, deps) {
|
|
|
87
87
|
try {
|
|
88
88
|
input = JSON.parse(deps.stdin);
|
|
89
89
|
} catch {
|
|
90
|
-
const deny = { hookSpecificOutput: { decision: { behavior: "deny" } } };
|
|
90
|
+
const deny = { hookSpecificOutput: { hookEventName: "PermissionRequest", decision: { behavior: "deny" } } };
|
|
91
91
|
deps.stdout.write(JSON.stringify(deny) + "\n");
|
|
92
92
|
break;
|
|
93
93
|
}
|
|
@@ -96,7 +96,7 @@ export async function main(args, deps) {
|
|
|
96
96
|
try {
|
|
97
97
|
result = await deps.processHook(input, deps);
|
|
98
98
|
} catch {
|
|
99
|
-
const deny = { hookSpecificOutput: { decision: { behavior: "deny" } } };
|
|
99
|
+
const deny = { hookSpecificOutput: { hookEventName: "PermissionRequest", decision: { behavior: "deny" } } };
|
|
100
100
|
deps.stdout.write(JSON.stringify(deny) + "\n");
|
|
101
101
|
break;
|
|
102
102
|
}
|
package/package.json
CHANGED
package/src/hook.mjs
CHANGED
|
@@ -45,7 +45,7 @@ export async function processHook(input, { loadConfig, sendNotification, waitFor
|
|
|
45
45
|
const config = loadConfig();
|
|
46
46
|
|
|
47
47
|
if (!config.topic) {
|
|
48
|
-
return { hookSpecificOutput: { decision: { behavior: "deny" } } };
|
|
48
|
+
return { hookSpecificOutput: { hookEventName: "PermissionRequest", decision: { behavior: "deny" } } };
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
const requestId = crypto.randomUUID();
|
|
@@ -61,8 +61,8 @@ export async function processHook(input, { loadConfig, sendNotification, waitFor
|
|
|
61
61
|
actions,
|
|
62
62
|
requestId,
|
|
63
63
|
});
|
|
64
|
-
} catch {
|
|
65
|
-
return { hookSpecificOutput: { decision: { behavior: "deny" } } };
|
|
64
|
+
} catch (err) {
|
|
65
|
+
return { hookSpecificOutput: { hookEventName: "PermissionRequest", decision: { behavior: "deny" } } };
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
let response;
|
|
@@ -73,10 +73,10 @@ export async function processHook(input, { loadConfig, sendNotification, waitFor
|
|
|
73
73
|
requestId,
|
|
74
74
|
timeout: config.timeout * 1000,
|
|
75
75
|
});
|
|
76
|
-
} catch {
|
|
77
|
-
return { hookSpecificOutput: { decision: { behavior: "deny" } } };
|
|
76
|
+
} catch (err) {
|
|
77
|
+
return { hookSpecificOutput: { hookEventName: "PermissionRequest", decision: { behavior: "deny" } } };
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
const behavior = response.approved ? "allow" : "deny";
|
|
81
|
-
return { hookSpecificOutput: { decision: { behavior } } };
|
|
81
|
+
return { hookSpecificOutput: { hookEventName: "PermissionRequest", decision: { behavior } } };
|
|
82
82
|
}
|
package/src/ntfy.mjs
CHANGED
|
@@ -24,44 +24,71 @@ export async function sendNotification({ server, topic, title, message, actions,
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
27
|
+
* Subscribe to the response topic via SSE and wait for a matching requestId.
|
|
28
28
|
*
|
|
29
|
-
* @param {{ server: string, topic: string, requestId: string, timeout: number
|
|
29
|
+
* @param {{ server: string, topic: string, requestId: string, timeout: number }} params
|
|
30
30
|
* @returns {Promise<{ approved: boolean }>}
|
|
31
31
|
*/
|
|
32
|
-
export async function waitForResponse({ server, topic, requestId, timeout
|
|
32
|
+
export async function waitForResponse({ server, topic, requestId, timeout }) {
|
|
33
33
|
const baseUrl = server.replace(/\/+$/, '');
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
const
|
|
34
|
+
const url = `${baseUrl}/${topic}-response/json`;
|
|
35
|
+
|
|
36
|
+
const controller = new AbortController();
|
|
37
|
+
|
|
38
|
+
/** @type {ReturnType<typeof setTimeout> | undefined} */
|
|
39
|
+
let timer;
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const response = await fetch(url, { signal: controller.signal });
|
|
43
|
+
const reader = response.body.getReader();
|
|
44
|
+
const decoder = new TextDecoder();
|
|
45
|
+
let buffer = '';
|
|
46
|
+
|
|
47
|
+
// Listen to abort so we can cancel the reader even when the mock stream
|
|
48
|
+
// never closes (the real fetch would propagate the signal, but mocks may not).
|
|
49
|
+
const onAbort = () => reader.cancel();
|
|
50
|
+
controller.signal.addEventListener('abort', onAbort);
|
|
51
|
+
|
|
52
|
+
// Start the timeout AFTER fetch resolves so we measure waiting time only.
|
|
53
|
+
timer = setTimeout(() => controller.abort(), timeout);
|
|
37
54
|
|
|
38
|
-
while (Date.now() - startTime < timeout) {
|
|
39
55
|
try {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
while (true) {
|
|
57
|
+
const { done, value } = await reader.read();
|
|
58
|
+
if (done) break;
|
|
59
|
+
|
|
60
|
+
buffer += decoder.decode(value, { stream: true });
|
|
61
|
+
const lines = buffer.split('\n');
|
|
62
|
+
buffer = lines.pop();
|
|
63
|
+
|
|
64
|
+
for (const line of lines) {
|
|
65
|
+
if (!line.trim()) continue;
|
|
66
|
+
try {
|
|
67
|
+
const event = JSON.parse(line);
|
|
68
|
+
const parsed = JSON.parse(event.message);
|
|
69
|
+
if (parsed.requestId === requestId) {
|
|
70
|
+
clearTimeout(timer);
|
|
71
|
+
controller.signal.removeEventListener('abort', onAbort);
|
|
72
|
+
return { approved: parsed.approved };
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
// skip non-JSON lines
|
|
52
76
|
}
|
|
53
|
-
} catch {
|
|
54
|
-
// skip non-JSON lines
|
|
55
77
|
}
|
|
56
78
|
}
|
|
57
|
-
}
|
|
58
|
-
|
|
79
|
+
} finally {
|
|
80
|
+
controller.signal.removeEventListener('abort', onAbort);
|
|
59
81
|
}
|
|
60
82
|
|
|
61
|
-
|
|
83
|
+
clearTimeout(timer);
|
|
84
|
+
return { approved: false };
|
|
85
|
+
} catch (err) {
|
|
86
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
87
|
+
if (err?.name !== "AbortError") {
|
|
88
|
+
console.error("[claude-remote-approver] waitForResponse error:", err);
|
|
89
|
+
}
|
|
90
|
+
return { approved: false };
|
|
62
91
|
}
|
|
63
|
-
|
|
64
|
-
return { approved: false };
|
|
65
92
|
}
|
|
66
93
|
|
|
67
94
|
/**
|