handsoff 0.1.2-beta.0 → 0.1.2-beta.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.
- package/config.example.toml +15 -22
- package/dist/cli/index.js +169 -57
- package/dist/cli/index.js.map +1 -1
- package/dist/gateway/process.js +3113 -2860
- package/dist/gateway/process.js.map +1 -1
- package/package.json +1 -1
package/config.example.toml
CHANGED
|
@@ -2,20 +2,9 @@
|
|
|
2
2
|
# Copy to ~/.handsoff/config.toml and fill in your credentials
|
|
3
3
|
|
|
4
4
|
[general]
|
|
5
|
-
default_agent = "claude"
|
|
6
5
|
log_level = "info"
|
|
7
6
|
hook_server_port = 9876
|
|
8
7
|
|
|
9
|
-
[channel.telegram]
|
|
10
|
-
enabled = true
|
|
11
|
-
bot_token = "YOUR_BOT_TOKEN"
|
|
12
|
-
allowed_users = [123456789]
|
|
13
|
-
|
|
14
|
-
[channel.feishu]
|
|
15
|
-
enabled = false
|
|
16
|
-
app_id = ""
|
|
17
|
-
app_secret = ""
|
|
18
|
-
|
|
19
8
|
[agent.claude]
|
|
20
9
|
adapter = "hooks"
|
|
21
10
|
binary = "claude"
|
|
@@ -23,22 +12,26 @@ work_dir = ""
|
|
|
23
12
|
model = ""
|
|
24
13
|
permission_mode = ""
|
|
25
14
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
15
|
+
[agent.codex]
|
|
16
|
+
enabled = false
|
|
17
|
+
command = ["codex", "app-server", "--listen", "stdio://"]
|
|
18
|
+
work_dir = ""
|
|
19
|
+
model = ""
|
|
20
|
+
approval_policy = ""
|
|
30
21
|
|
|
31
|
-
[
|
|
22
|
+
[channel.app]
|
|
32
23
|
enabled = false
|
|
33
24
|
channel_id = "app:desktop-001"
|
|
34
25
|
auth_token = "change-me"
|
|
35
26
|
notify_types = ["*"]
|
|
36
27
|
heartbeat_interval_ms = 30000
|
|
37
28
|
|
|
38
|
-
[
|
|
29
|
+
[channel.telegram]
|
|
30
|
+
enabled = true
|
|
31
|
+
bot_token = "YOUR_BOT_TOKEN"
|
|
32
|
+
allowed_users = [123456789]
|
|
33
|
+
|
|
34
|
+
[channel.feishu]
|
|
39
35
|
enabled = false
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
model = ""
|
|
43
|
-
approval_policy = ""
|
|
44
|
-
sandbox_mode = ""
|
|
36
|
+
app_id = ""
|
|
37
|
+
app_secret = ""
|
package/dist/cli/index.js
CHANGED
|
@@ -8,7 +8,13 @@ import { Command } from "commander";
|
|
|
8
8
|
|
|
9
9
|
// src/shared/settings-merge.ts
|
|
10
10
|
function isHandsoffHook(hook) {
|
|
11
|
-
|
|
11
|
+
if (hook.type === "http" && hook.url) {
|
|
12
|
+
return hook.url.includes("/hook/");
|
|
13
|
+
}
|
|
14
|
+
if (hook.type === "command" && hook.command) {
|
|
15
|
+
return hook.command.includes("/hook/") && hook.command.includes("curl");
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
12
18
|
}
|
|
13
19
|
function isHandsoffEntry(entry) {
|
|
14
20
|
return entry.hooks.some((hook) => isHandsoffHook(hook));
|
|
@@ -35,10 +41,15 @@ function mergeHooks(existing, newHooks) {
|
|
|
35
41
|
const existingEntries = cleaned.hooks?.[eventName] || [];
|
|
36
42
|
const newEntries = newHooks[eventName] || [];
|
|
37
43
|
const existingUrls = new Set(
|
|
38
|
-
existingEntries.flatMap((e) => e.hooks.map((h) => h.url))
|
|
44
|
+
existingEntries.flatMap((e) => e.hooks.map((h) => h.url).filter(Boolean))
|
|
45
|
+
);
|
|
46
|
+
const existingCommands = new Set(
|
|
47
|
+
existingEntries.flatMap((e) => e.hooks.map((h) => h.command).filter(Boolean))
|
|
39
48
|
);
|
|
40
49
|
const uniqueNewEntries = newEntries.filter(
|
|
41
|
-
(entry) => !entry.hooks.some(
|
|
50
|
+
(entry) => !entry.hooks.some(
|
|
51
|
+
(h) => h.url && existingUrls.has(h.url) || h.command && existingCommands.has(h.command)
|
|
52
|
+
)
|
|
42
53
|
);
|
|
43
54
|
const merged = [...uniqueNewEntries, ...existingEntries];
|
|
44
55
|
if (merged.length > 0) {
|
|
@@ -124,7 +135,6 @@ import dotenv from "dotenv";
|
|
|
124
135
|
dotenv.config();
|
|
125
136
|
var DEFAULT_CONFIG = {
|
|
126
137
|
general: {
|
|
127
|
-
default_agent: "claude",
|
|
128
138
|
log_level: "info",
|
|
129
139
|
hook_server_port: 9876
|
|
130
140
|
},
|
|
@@ -148,6 +158,11 @@ var DEFAULT_CONFIG = {
|
|
|
148
158
|
timeout_ms: 3e5,
|
|
149
159
|
// 5 minutes
|
|
150
160
|
default_on_timeout: "deny"
|
|
161
|
+
},
|
|
162
|
+
app: {
|
|
163
|
+
enabled: false,
|
|
164
|
+
channel_id: "",
|
|
165
|
+
auth_token: ""
|
|
151
166
|
}
|
|
152
167
|
},
|
|
153
168
|
agent: {
|
|
@@ -156,21 +171,16 @@ var DEFAULT_CONFIG = {
|
|
|
156
171
|
binary: "claude",
|
|
157
172
|
work_dir: "",
|
|
158
173
|
model: "",
|
|
159
|
-
permission_mode: ""
|
|
160
|
-
permission: {
|
|
161
|
-
lowRiskTools: ["Read", "Glob", "Grep", "List", "WebSearch", "codesearch"]
|
|
162
|
-
}
|
|
174
|
+
permission_mode: ""
|
|
163
175
|
},
|
|
164
176
|
codex: {
|
|
165
177
|
enabled: false,
|
|
166
178
|
command: ["codex", "app-server", "--listen", "stdio://"],
|
|
167
179
|
work_dir: "",
|
|
168
180
|
model: "",
|
|
169
|
-
approval_policy: ""
|
|
170
|
-
sandbox_mode: ""
|
|
181
|
+
approval_policy: ""
|
|
171
182
|
}
|
|
172
183
|
},
|
|
173
|
-
channels: {},
|
|
174
184
|
bindings: {}
|
|
175
185
|
};
|
|
176
186
|
function getConfigPath() {
|
|
@@ -218,16 +228,21 @@ function mergeWithDefaults(raw) {
|
|
|
218
228
|
10
|
|
219
229
|
),
|
|
220
230
|
default_on_timeout: process.env.HANDSOFF_PERMISSION_DEFAULT || raw.channel?.permission?.default_on_timeout || DEFAULT_CONFIG.channel.permission.default_on_timeout
|
|
231
|
+
},
|
|
232
|
+
app: {
|
|
233
|
+
...DEFAULT_CONFIG.channel.app,
|
|
234
|
+
...raw.channel?.app || {},
|
|
235
|
+
enabled: raw.channel?.app?.enabled === true,
|
|
236
|
+
channel_id: String(raw.channel?.app?.channel_id || ""),
|
|
237
|
+
auth_token: String(raw.channel?.app?.auth_token || ""),
|
|
238
|
+
heartbeat_interval_ms: raw.channel?.app?.heartbeat_interval_ms ? parseInt(String(raw.channel?.app?.heartbeat_interval_ms), 10) : void 0,
|
|
239
|
+
notify_types: Array.isArray(raw.channel?.app?.notify_types) ? raw.channel.app.notify_types.map(String) : void 0
|
|
221
240
|
}
|
|
222
241
|
},
|
|
223
242
|
agent: {
|
|
224
243
|
claude: {
|
|
225
244
|
...DEFAULT_CONFIG.agent.claude,
|
|
226
|
-
...raw.agent?.claude || {}
|
|
227
|
-
permission: {
|
|
228
|
-
...DEFAULT_CONFIG.agent.claude.permission,
|
|
229
|
-
...raw.agent?.claude?.permission || {}
|
|
230
|
-
}
|
|
245
|
+
...raw.agent?.claude || {}
|
|
231
246
|
},
|
|
232
247
|
codex: {
|
|
233
248
|
...DEFAULT_CONFIG.agent.codex,
|
|
@@ -235,21 +250,9 @@ function mergeWithDefaults(raw) {
|
|
|
235
250
|
command: raw.agent?.codex?.command ? raw.agent.codex.command : DEFAULT_CONFIG.agent.codex.command,
|
|
236
251
|
work_dir: raw.agent?.codex?.work_dir || DEFAULT_CONFIG.agent.codex.work_dir,
|
|
237
252
|
model: raw.agent?.codex?.model || DEFAULT_CONFIG.agent.codex.model,
|
|
238
|
-
approval_policy: raw.agent?.codex?.approval_policy || DEFAULT_CONFIG.agent.codex.approval_policy
|
|
239
|
-
sandbox_mode: raw.agent?.codex?.sandbox_mode || DEFAULT_CONFIG.agent.codex.sandbox_mode
|
|
253
|
+
approval_policy: raw.agent?.codex?.approval_policy || DEFAULT_CONFIG.agent.codex.approval_policy
|
|
240
254
|
}
|
|
241
255
|
},
|
|
242
|
-
channels: {
|
|
243
|
-
...DEFAULT_CONFIG.channels,
|
|
244
|
-
...raw.channels || {},
|
|
245
|
-
app: raw.channels?.app ? {
|
|
246
|
-
enabled: raw.channels.app.enabled === true,
|
|
247
|
-
channel_id: String(raw.channels.app.channel_id || ""),
|
|
248
|
-
auth_token: String(raw.channels.app.auth_token || ""),
|
|
249
|
-
heartbeat_interval_ms: raw.channels.app.heartbeat_interval_ms ? parseInt(String(raw.channels.app.heartbeat_interval_ms), 10) : void 0,
|
|
250
|
-
notify_types: Array.isArray(raw.channels.app.notify_types) ? raw.channels.app.notify_types.map(String) : void 0
|
|
251
|
-
} : DEFAULT_CONFIG.channels?.app
|
|
252
|
-
},
|
|
253
256
|
bindings: {
|
|
254
257
|
...DEFAULT_CONFIG.bindings,
|
|
255
258
|
...raw.bindings || {}
|
|
@@ -297,7 +300,10 @@ var en_default = {
|
|
|
297
300
|
injecting: "Injecting hooks...",
|
|
298
301
|
injected: "Hooks injected",
|
|
299
302
|
failed: "Failed: {{error}}",
|
|
300
|
-
remoteModeSkipHooks: "Remote mode does not require hooks; skipping injection."
|
|
303
|
+
remoteModeSkipHooks: "Remote mode does not require hooks; skipping injection.",
|
|
304
|
+
cleaningHooks: "Cleaning up hooks from previous mode...",
|
|
305
|
+
hooksCleaned: "Hooks cleaned",
|
|
306
|
+
cleanupWarning: "Hook cleanup warning: {{error}}"
|
|
301
307
|
},
|
|
302
308
|
channel: {
|
|
303
309
|
configuring: "Configuring {{channel}}",
|
|
@@ -516,6 +522,22 @@ var en_default = {
|
|
|
516
522
|
unauthorized: "You are not authorized to use this bot.",
|
|
517
523
|
received: "Message received.",
|
|
518
524
|
processingError: "Error processing message."
|
|
525
|
+
},
|
|
526
|
+
eventTitles: {
|
|
527
|
+
sessionStart: "\u{1F680} Session Started",
|
|
528
|
+
sessionEnd: "\u{1F44B} Session Ended",
|
|
529
|
+
turnFinished: "\u2705 Task Completed",
|
|
530
|
+
turnThinking: "\u{1F4AD} Thinking...",
|
|
531
|
+
toolPost: "\u{1F527} Tool Call",
|
|
532
|
+
toolExecuted: "\u2705 Tool Executed",
|
|
533
|
+
toolFailure: "\u274C Tool Failed",
|
|
534
|
+
permissionRequest: "\u{1F510} Permission Request",
|
|
535
|
+
permissionResponse: "\u{1F4CB} Permission Response",
|
|
536
|
+
questionRequest: "\u2753 Question",
|
|
537
|
+
questionResponse: "\u{1F4CB} Question Response",
|
|
538
|
+
agentMessage: "\u{1F916} Agent Message",
|
|
539
|
+
error: "\u26A0\uFE0F Error",
|
|
540
|
+
fallback: "\u{1F4CB} Notification"
|
|
519
541
|
}
|
|
520
542
|
};
|
|
521
543
|
|
|
@@ -783,6 +805,16 @@ function extractHandsoffHookInfo(entry) {
|
|
|
783
805
|
}
|
|
784
806
|
return { isHandsoff: false };
|
|
785
807
|
}
|
|
808
|
+
function detectPermissionMode() {
|
|
809
|
+
const settingsPath = join5(homedir5(), ".claude", "settings.json");
|
|
810
|
+
if (!existsSync4(settingsPath)) return null;
|
|
811
|
+
try {
|
|
812
|
+
const settings = JSON.parse(readFileSync4(settingsPath, "utf-8"));
|
|
813
|
+
return settings.permissionMode ?? null;
|
|
814
|
+
} catch {
|
|
815
|
+
return null;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
786
818
|
|
|
787
819
|
// src/cli/wizard/detectors/codex.ts
|
|
788
820
|
import { execSync as execSync2 } from "child_process";
|
|
@@ -864,6 +896,18 @@ var prompts = {
|
|
|
864
896
|
{ name: "Remote mode (managed two-way)", value: "remote" }
|
|
865
897
|
]
|
|
866
898
|
}),
|
|
899
|
+
permissionModeSelect: (defaultValue) => select({
|
|
900
|
+
message: "Permission Mode",
|
|
901
|
+
choices: [
|
|
902
|
+
{ name: "acceptEdits \u2014 Auto-approve safe edits (Recommended)", value: "acceptEdits" },
|
|
903
|
+
{ name: "default \u2014 Ask via TTY or hook", value: "default" },
|
|
904
|
+
{ name: "bypassPermissions \u2014 Approve all, no prompts", value: "bypassPermissions" },
|
|
905
|
+
{ name: "dontAsk \u2014 Deny all tool execution", value: "dontAsk" },
|
|
906
|
+
{ name: "plan \u2014 Plan only, no tool execution", value: "plan" },
|
|
907
|
+
{ name: "auto \u2014 Auto-handle based on risk level", value: "auto" }
|
|
908
|
+
],
|
|
909
|
+
default: defaultValue ?? "acceptEdits"
|
|
910
|
+
}),
|
|
867
911
|
cliHookSelect: (selected) => checkbox({
|
|
868
912
|
message: t("wizard.cli.hookCheckbox"),
|
|
869
913
|
choices: [
|
|
@@ -900,10 +944,10 @@ var prompts = {
|
|
|
900
944
|
notifyTypes: (selected) => checkbox({
|
|
901
945
|
message: t("wizard.notify.checkbox"),
|
|
902
946
|
choices: [
|
|
903
|
-
{ name: "
|
|
904
|
-
{ name: "
|
|
905
|
-
{ name: "finished", value: "finished", checked: selected?.includes("finished") },
|
|
906
|
-
{ name: "
|
|
947
|
+
{ name: "permission:request", value: "permission:request", checked: selected?.includes("permission:request") },
|
|
948
|
+
{ name: "question:request", value: "question:request", checked: selected?.includes("question:request") },
|
|
949
|
+
{ name: "turn:finished", value: "turn:finished", checked: selected?.includes("turn:finished") },
|
|
950
|
+
{ name: "session:start", value: "session:start", checked: selected?.includes("session:start") },
|
|
907
951
|
{ name: "error", value: "error", checked: selected?.includes("error") }
|
|
908
952
|
]
|
|
909
953
|
}),
|
|
@@ -933,8 +977,8 @@ var prompts = {
|
|
|
933
977
|
pairContinue: () => select({
|
|
934
978
|
message: "Add another pair?",
|
|
935
979
|
choices: [
|
|
936
|
-
{ name: "
|
|
937
|
-
{ name: "
|
|
980
|
+
{ name: "Finish setup", value: "finish" },
|
|
981
|
+
{ name: "Add another pair", value: "continue" }
|
|
938
982
|
]
|
|
939
983
|
}),
|
|
940
984
|
agentSelect: (agents) => select({
|
|
@@ -1006,6 +1050,13 @@ ${STEP} \u2014 Configure Agent
|
|
|
1006
1050
|
return { action: "next", next: "cli-menu" };
|
|
1007
1051
|
}
|
|
1008
1052
|
} else {
|
|
1053
|
+
const spinner = ora(t("wizard.cli.cleaningHooks")).start();
|
|
1054
|
+
try {
|
|
1055
|
+
cleanupClaudeHooks(detection.settingsPath);
|
|
1056
|
+
spinner.succeed(t("wizard.cli.hooksCleaned"));
|
|
1057
|
+
} catch (err) {
|
|
1058
|
+
spinner.warn(t("wizard.cli.cleanupWarning", { error: String(err) }));
|
|
1059
|
+
}
|
|
1009
1060
|
console.log(pc.yellow(`! ${t("wizard.cli.remoteModeSkipHooks")}`));
|
|
1010
1061
|
try {
|
|
1011
1062
|
execSync3("claude --version", { stdio: "ignore" });
|
|
@@ -1023,6 +1074,9 @@ ${STEP} \u2014 Configure Agent
|
|
|
1023
1074
|
}
|
|
1024
1075
|
};
|
|
1025
1076
|
state.validationResults.set("claude", { ok: true });
|
|
1077
|
+
if (mode === "remote") {
|
|
1078
|
+
return { action: "next", next: "permission-mode" };
|
|
1079
|
+
}
|
|
1026
1080
|
return { action: "next", next: "channel-select" };
|
|
1027
1081
|
}
|
|
1028
1082
|
if (cliName === "codex") {
|
|
@@ -1087,8 +1141,7 @@ function parseCommandInput(input2) {
|
|
|
1087
1141
|
function injectClaudeHooks(settingsPath, events) {
|
|
1088
1142
|
const port = loadConfig().general.hook_server_port;
|
|
1089
1143
|
const token = getOrCreateHookToken();
|
|
1090
|
-
const
|
|
1091
|
-
const hooksConfig = generateHooksConfig2(baseUrl, events);
|
|
1144
|
+
const hooksConfig = generateHooksConfig2(port, token, events);
|
|
1092
1145
|
let currentSettings = {};
|
|
1093
1146
|
if (existsSync5(settingsPath)) {
|
|
1094
1147
|
try {
|
|
@@ -1103,17 +1156,40 @@ function injectClaudeHooks(settingsPath, events) {
|
|
|
1103
1156
|
mkdirSync4(dirname3(settingsPath), { recursive: true });
|
|
1104
1157
|
writeFileSync4(settingsPath, JSON.stringify(mergedSettings, null, 2));
|
|
1105
1158
|
}
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1159
|
+
var COMMAND_ONLY_HOOKS = ["SessionStart", "Setup"];
|
|
1160
|
+
function generateHooksConfig2(port, token, events) {
|
|
1161
|
+
const unifiedUrl = `http://localhost:${port}/hook/${token}/event`;
|
|
1162
|
+
const curlCommand = `curl -s -X POST -H 'Content-Type: application/json' -d @- ${unifiedUrl}`;
|
|
1163
|
+
const createEntry = (event) => {
|
|
1164
|
+
const isCommandOnly = COMMAND_ONLY_HOOKS.includes(event);
|
|
1165
|
+
return {
|
|
1166
|
+
matcher: "",
|
|
1167
|
+
hooks: [{
|
|
1168
|
+
type: isCommandOnly ? "command" : "http",
|
|
1169
|
+
...isCommandOnly ? { command: curlCommand } : { url: unifiedUrl }
|
|
1170
|
+
}]
|
|
1171
|
+
};
|
|
1172
|
+
};
|
|
1111
1173
|
const config = {};
|
|
1112
1174
|
for (const event of events) {
|
|
1113
1175
|
config[event] = [createEntry(event)];
|
|
1114
1176
|
}
|
|
1115
1177
|
return config;
|
|
1116
1178
|
}
|
|
1179
|
+
function cleanupClaudeHooks(settingsPath) {
|
|
1180
|
+
if (!existsSync5(settingsPath)) return;
|
|
1181
|
+
let currentSettings = {};
|
|
1182
|
+
try {
|
|
1183
|
+
currentSettings = JSON.parse(readFileSync5(settingsPath, "utf-8"));
|
|
1184
|
+
} catch {
|
|
1185
|
+
}
|
|
1186
|
+
const cleanedSettings = removeHandsoffHooks(currentSettings);
|
|
1187
|
+
if (!cleanedSettings.hooks || Object.keys(cleanedSettings.hooks).length === 0) {
|
|
1188
|
+
delete cleanedSettings.hooks;
|
|
1189
|
+
}
|
|
1190
|
+
mkdirSync4(dirname3(settingsPath), { recursive: true });
|
|
1191
|
+
writeFileSync4(settingsPath, JSON.stringify(cleanedSettings, null, 2));
|
|
1192
|
+
}
|
|
1117
1193
|
|
|
1118
1194
|
// src/cli/wizard/detectors/channel.ts
|
|
1119
1195
|
async function detectChannel(name) {
|
|
@@ -1343,7 +1419,7 @@ ${STEP3} \u2014 Select Agent
|
|
|
1343
1419
|
if (result.next === "cli-menu") {
|
|
1344
1420
|
return { action: "next", next: "agent-select" };
|
|
1345
1421
|
}
|
|
1346
|
-
return { action: "next", next:
|
|
1422
|
+
return { action: "next", next: result.next };
|
|
1347
1423
|
}
|
|
1348
1424
|
|
|
1349
1425
|
// src/shared/channelInstance.ts
|
|
@@ -1373,7 +1449,6 @@ ${STEP4} \u2014 Select Channel
|
|
|
1373
1449
|
const loggerInstanceId = "logger:default";
|
|
1374
1450
|
const loggerBoundAgent = Object.entries(currentBindings).find(([, v]) => v === loggerInstanceId)?.[0];
|
|
1375
1451
|
const loggerStatus = loggerBoundAgent ? pc6.green(`[bound to ${loggerBoundAgent}]`) : loggerEnabled ? pc6.green("[configured]") : pc6.gray("[not configured]");
|
|
1376
|
-
channels.push({ name: "logger", value: "logger", status: loggerStatus });
|
|
1377
1452
|
if (mergedTelegram?.enabled && mergedTelegram.bot_token) {
|
|
1378
1453
|
const instanceId = getChannelInstanceId("telegram", mergedTelegram.bot_token);
|
|
1379
1454
|
const boundAgent = Object.entries(currentBindings).find(([, v]) => v === instanceId)?.[0];
|
|
@@ -1390,6 +1465,7 @@ ${STEP4} \u2014 Select Channel
|
|
|
1390
1465
|
} else {
|
|
1391
1466
|
channels.push({ name: "feishu", value: "feishu", status: pc6.gray("[not configured]") });
|
|
1392
1467
|
}
|
|
1468
|
+
channels.push({ name: "logger", value: "logger", status: loggerStatus });
|
|
1393
1469
|
const choice = await prompts.channelSelect(channels);
|
|
1394
1470
|
state.currentChannelName = choice;
|
|
1395
1471
|
const result = await stepChannelConfig(state);
|
|
@@ -1719,6 +1795,33 @@ ${t("wizard.final.done")}
|
|
|
1719
1795
|
return { action: "next", next: "done" };
|
|
1720
1796
|
}
|
|
1721
1797
|
|
|
1798
|
+
// src/cli/wizard/steps/permission-mode.ts
|
|
1799
|
+
import pc10 from "picocolors";
|
|
1800
|
+
var STEP7 = "STEP 3";
|
|
1801
|
+
async function stepPermissionMode(state) {
|
|
1802
|
+
console.log(pc10.bold(`
|
|
1803
|
+
${STEP7} \u2014 Permission Mode
|
|
1804
|
+
`));
|
|
1805
|
+
console.log(pc10.gray(`${"\u2500".repeat(50)}
|
|
1806
|
+
`));
|
|
1807
|
+
const config = loadConfig();
|
|
1808
|
+
const current = state.pendingChanges?.agent?.claude;
|
|
1809
|
+
const saved = current?.permission_mode ?? config.agent.claude?.permission_mode ?? null;
|
|
1810
|
+
const detected = detectPermissionMode();
|
|
1811
|
+
const initial = saved ?? detected ?? "acceptEdits";
|
|
1812
|
+
console.log(pc10.gray("Controls how Claude Code handles permission requests.\n"));
|
|
1813
|
+
if (detected && !saved) {
|
|
1814
|
+
console.log(pc10.gray(`Detected from ~/.claude/settings.json: ${detected}
|
|
1815
|
+
`));
|
|
1816
|
+
}
|
|
1817
|
+
const selected = await prompts.permissionModeSelect(initial);
|
|
1818
|
+
state.pendingChanges ??= {};
|
|
1819
|
+
state.pendingChanges.agent ??= {};
|
|
1820
|
+
state.pendingChanges.agent.claude ??= {};
|
|
1821
|
+
state.pendingChanges.agent.claude.permission_mode = selected;
|
|
1822
|
+
return { action: "next", next: "channel-select" };
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1722
1825
|
// src/cli/wizard/engine.ts
|
|
1723
1826
|
var WizardEngine = class {
|
|
1724
1827
|
state;
|
|
@@ -1779,6 +1882,8 @@ var WizardEngine = class {
|
|
|
1779
1882
|
return await stepPairContinue(this.state);
|
|
1780
1883
|
case "cli":
|
|
1781
1884
|
return await stepCli(this.state);
|
|
1885
|
+
case "permission-mode":
|
|
1886
|
+
return await stepPermissionMode(this.state);
|
|
1782
1887
|
case "cli-menu":
|
|
1783
1888
|
return await stepCliMenu(this.state);
|
|
1784
1889
|
case "channel-config":
|
|
@@ -2255,22 +2360,29 @@ function writeSettings2(settingsPath, settings) {
|
|
|
2255
2360
|
}
|
|
2256
2361
|
function generateHooksConfig3(port, token) {
|
|
2257
2362
|
const unifiedUrl = `http://localhost:${port}/hook/${token}/event`;
|
|
2258
|
-
const
|
|
2363
|
+
const createHttpHookEntry = () => ({
|
|
2259
2364
|
matcher: "",
|
|
2260
2365
|
hooks: [{ type: "http", url: unifiedUrl }]
|
|
2261
2366
|
});
|
|
2367
|
+
const curlCommand = `curl -s -X POST -H 'Content-Type: application/json' -d @- ${unifiedUrl}`;
|
|
2368
|
+
const createCommandHookEntry = () => ({
|
|
2369
|
+
matcher: "",
|
|
2370
|
+
hooks: [{ type: "command", command: curlCommand }]
|
|
2371
|
+
});
|
|
2262
2372
|
return {
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
SessionEnd: [
|
|
2272
|
-
UserPromptSubmit: [
|
|
2273
|
-
PermissionRequest: [
|
|
2373
|
+
// HTTP type hooks
|
|
2374
|
+
Stop: [createHttpHookEntry()],
|
|
2375
|
+
PostToolUse: [createHttpHookEntry()],
|
|
2376
|
+
PostToolUseFailure: [createHttpHookEntry()],
|
|
2377
|
+
Notification: [createHttpHookEntry()],
|
|
2378
|
+
PreToolUse: [createHttpHookEntry()],
|
|
2379
|
+
SubagentStart: [createHttpHookEntry()],
|
|
2380
|
+
SubagentStop: [createHttpHookEntry()],
|
|
2381
|
+
SessionEnd: [createHttpHookEntry()],
|
|
2382
|
+
UserPromptSubmit: [createHttpHookEntry()],
|
|
2383
|
+
PermissionRequest: [createHttpHookEntry()],
|
|
2384
|
+
// Command type hooks (HTTP not supported by Claude)
|
|
2385
|
+
SessionStart: [createCommandHookEntry()]
|
|
2274
2386
|
};
|
|
2275
2387
|
}
|
|
2276
2388
|
function registerClaudeCommand(program2) {
|