handsoff 0.1.3 → 0.1.6-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +489 -344
- package/dist/cli/index.js.map +1 -1
- package/dist/gateway/process.js +6485 -7577
- package/dist/gateway/process.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -122,12 +122,12 @@ function resetCredentials() {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
// src/cli/attach.ts
|
|
125
|
-
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as
|
|
125
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
126
126
|
import { homedir as homedir3 } from "os";
|
|
127
127
|
import { join as join3, dirname as dirname2 } from "path";
|
|
128
128
|
|
|
129
129
|
// src/config.ts
|
|
130
|
-
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
130
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2 } from "fs";
|
|
131
131
|
import { join as join2 } from "path";
|
|
132
132
|
import { homedir as homedir2 } from "os";
|
|
133
133
|
import TOML from "@iarna/toml";
|
|
@@ -151,8 +151,7 @@ var DEFAULT_CONFIG = {
|
|
|
151
151
|
enabled: false,
|
|
152
152
|
app_id: "",
|
|
153
153
|
app_secret: "",
|
|
154
|
-
allowed_users: []
|
|
155
|
-
notify_types: []
|
|
154
|
+
allowed_users: []
|
|
156
155
|
},
|
|
157
156
|
permission: {
|
|
158
157
|
timeout_ms: 3e5,
|
|
@@ -199,23 +198,20 @@ function mergeWithDefaults(raw) {
|
|
|
199
198
|
channel: {
|
|
200
199
|
logger: {
|
|
201
200
|
...DEFAULT_CONFIG.channel.logger,
|
|
202
|
-
...raw.channel?.logger || {}
|
|
203
|
-
notify_types: raw.channel?.logger?.notify_types
|
|
201
|
+
...raw.channel?.logger || {}
|
|
204
202
|
},
|
|
205
203
|
telegram: {
|
|
206
204
|
...DEFAULT_CONFIG.channel.telegram,
|
|
207
205
|
...raw.channel?.telegram || {},
|
|
208
206
|
allowed_users: (raw.channel?.telegram?.allowed_users || []).map(String),
|
|
209
|
-
bot_token: process.env.HANDSOFF_TELEGRAM_BOT_TOKEN || raw.channel?.telegram?.bot_token || ""
|
|
210
|
-
notify_types: raw.channel?.telegram?.notify_types
|
|
207
|
+
bot_token: process.env.HANDSOFF_TELEGRAM_BOT_TOKEN || raw.channel?.telegram?.bot_token || ""
|
|
211
208
|
},
|
|
212
209
|
feishu: {
|
|
213
210
|
...DEFAULT_CONFIG.channel.feishu,
|
|
214
211
|
...raw.channel?.feishu || {},
|
|
215
212
|
allowed_users: raw.channel?.feishu?.allowed_users || [],
|
|
216
213
|
app_id: process.env.HANDSOFF_FEISHU_APP_ID || raw.channel?.feishu?.app_id || "",
|
|
217
|
-
app_secret: process.env.HANDSOFF_FEISHU_APP_SECRET || raw.channel?.feishu?.app_secret || ""
|
|
218
|
-
notify_types: raw.channel?.feishu?.notify_types
|
|
214
|
+
app_secret: process.env.HANDSOFF_FEISHU_APP_SECRET || raw.channel?.feishu?.app_secret || ""
|
|
219
215
|
},
|
|
220
216
|
permission: {
|
|
221
217
|
...DEFAULT_CONFIG.channel.permission,
|
|
@@ -235,8 +231,7 @@ function mergeWithDefaults(raw) {
|
|
|
235
231
|
enabled: raw.channel?.app?.enabled === true,
|
|
236
232
|
channel_id: String(raw.channel?.app?.channel_id || ""),
|
|
237
233
|
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
|
|
234
|
+
heartbeat_interval_ms: raw.channel?.app?.heartbeat_interval_ms ? parseInt(String(raw.channel?.app?.heartbeat_interval_ms), 10) : void 0
|
|
240
235
|
}
|
|
241
236
|
},
|
|
242
237
|
agent: {
|
|
@@ -256,9 +251,44 @@ function mergeWithDefaults(raw) {
|
|
|
256
251
|
bindings: {
|
|
257
252
|
...DEFAULT_CONFIG.bindings,
|
|
258
253
|
...raw.bindings || {}
|
|
259
|
-
}
|
|
254
|
+
},
|
|
255
|
+
workspaces: raw.workspaces || {}
|
|
260
256
|
};
|
|
261
257
|
}
|
|
258
|
+
function migrateLegacyBindings(config) {
|
|
259
|
+
if (!config.bindings) return config;
|
|
260
|
+
const entries = Object.entries(config.bindings);
|
|
261
|
+
const isLegacy = entries.some(
|
|
262
|
+
([k, v]) => !k.includes(":") && String(v).includes(":")
|
|
263
|
+
);
|
|
264
|
+
if (!isLegacy) return config;
|
|
265
|
+
const newConfig = {
|
|
266
|
+
...config,
|
|
267
|
+
workspaces: { ...config.workspaces || {} },
|
|
268
|
+
bindings: {}
|
|
269
|
+
};
|
|
270
|
+
for (const [agentType, channelId] of entries) {
|
|
271
|
+
const wsId = agentType;
|
|
272
|
+
newConfig.workspaces[wsId] = {
|
|
273
|
+
name: agentType,
|
|
274
|
+
description: "Migrated from legacy binding"
|
|
275
|
+
};
|
|
276
|
+
if (channelId) {
|
|
277
|
+
newConfig.bindings[channelId] = wsId;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
const configPath = getConfigPath();
|
|
282
|
+
const raw = TOML.parse(readFileSync2(configPath, "utf-8"));
|
|
283
|
+
raw.workspaces = newConfig.workspaces;
|
|
284
|
+
raw.bindings = newConfig.bindings;
|
|
285
|
+
writeFileSync2(configPath, TOML.stringify(raw), "utf-8");
|
|
286
|
+
console.log("[Config] Migrated legacy bindings to workspace format");
|
|
287
|
+
} catch (err) {
|
|
288
|
+
console.warn("[Config] Failed to persist migrated config:", err);
|
|
289
|
+
}
|
|
290
|
+
return newConfig;
|
|
291
|
+
}
|
|
262
292
|
function loadConfig() {
|
|
263
293
|
const configPath = getConfigPath();
|
|
264
294
|
if (!existsSync2(configPath)) {
|
|
@@ -267,7 +297,8 @@ function loadConfig() {
|
|
|
267
297
|
try {
|
|
268
298
|
const content = readFileSync2(configPath, "utf-8");
|
|
269
299
|
const raw = TOML.parse(content);
|
|
270
|
-
|
|
300
|
+
const config = mergeWithDefaults(raw);
|
|
301
|
+
return migrateLegacyBindings(config);
|
|
271
302
|
} catch (error) {
|
|
272
303
|
console.error(`Failed to load config from ${configPath}:`, error);
|
|
273
304
|
return DEFAULT_CONFIG;
|
|
@@ -285,63 +316,6 @@ var en_default = {
|
|
|
285
316
|
channel: "[ Channel Configuration ]",
|
|
286
317
|
complete: "[ Setup Complete ]"
|
|
287
318
|
},
|
|
288
|
-
cli: {
|
|
289
|
-
runAgain: "Run init again when Claude Code is installed.",
|
|
290
|
-
detected: "Claude Code detected.",
|
|
291
|
-
hooksInstalled: "Hooks already installed.",
|
|
292
|
-
hooksNotInstalled: "Hooks not installed.",
|
|
293
|
-
hooksTokenMismatch: "Hooks have outdated token - reconfiguration required.",
|
|
294
|
-
actionQuestion: "Select action:",
|
|
295
|
-
injectAction: "Inject hooks configuration",
|
|
296
|
-
updateAction: "Update hooks configuration",
|
|
297
|
-
backAction: "Back",
|
|
298
|
-
hookCheckbox: "Select hooks:",
|
|
299
|
-
noHooksSelected: "No hooks selected, skipping injection.",
|
|
300
|
-
injecting: "Injecting hooks...",
|
|
301
|
-
injected: "Hooks injected",
|
|
302
|
-
failed: "Failed: {{error}}",
|
|
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}}"
|
|
307
|
-
},
|
|
308
|
-
channel: {
|
|
309
|
-
configuring: "Configuring {{channel}}",
|
|
310
|
-
telegram: {
|
|
311
|
-
botToken: "Telegram bot token:",
|
|
312
|
-
allowedUsers: "Allowed Telegram user IDs (comma-separated):",
|
|
313
|
-
testing: "Testing Telegram connection...",
|
|
314
|
-
connected: "Connected to Telegram",
|
|
315
|
-
sendingTest: "Sending test message...",
|
|
316
|
-
testSent: "Test message sent",
|
|
317
|
-
testContent: "Handsoff test message",
|
|
318
|
-
testWarning: "Could not send test message (user may not have messaged bot yet)",
|
|
319
|
-
apiError: "API error: {{status}}",
|
|
320
|
-
connFailed: "Connection failed: {{error}}"
|
|
321
|
-
},
|
|
322
|
-
feishu: {
|
|
323
|
-
appId: "Feishu App ID:",
|
|
324
|
-
appSecret: "Feishu App Secret:",
|
|
325
|
-
allowedUsers: "Allowed User IDs (comma-separated, e.g., ou_xxx,oc_xxx):",
|
|
326
|
-
required: "app_id and app_secret are required. Skipping."
|
|
327
|
-
}
|
|
328
|
-
},
|
|
329
|
-
codex: {
|
|
330
|
-
enable: "Enable Codex agent integration?",
|
|
331
|
-
command: "Codex app-server command:",
|
|
332
|
-
workDir: "Default Codex working directory (optional):",
|
|
333
|
-
model: "Default Codex model (optional):",
|
|
334
|
-
approvalPolicy: "Default Codex approval policy (optional):",
|
|
335
|
-
detected: "Codex CLI detected.",
|
|
336
|
-
notDetected: "Codex CLI not found in PATH.",
|
|
337
|
-
enabled: "Codex integration enabled.",
|
|
338
|
-
disabled: "Codex integration disabled."
|
|
339
|
-
},
|
|
340
|
-
notify: {
|
|
341
|
-
title: "Notification types for {{channel}}",
|
|
342
|
-
checkbox: "Notification types:",
|
|
343
|
-
saved: "Notification types saved for {{channel}}"
|
|
344
|
-
},
|
|
345
319
|
menu: {
|
|
346
320
|
select: "Select:",
|
|
347
321
|
next: "--- Next ---",
|
|
@@ -351,81 +325,137 @@ var en_default = {
|
|
|
351
325
|
backToMenu: "Back to menu",
|
|
352
326
|
restartQuestion: "Restart Gateway now?"
|
|
353
327
|
},
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
328
|
+
step: {
|
|
329
|
+
cli: {
|
|
330
|
+
runAgain: "Run init again when Claude Code is installed.",
|
|
331
|
+
detected: "Claude Code detected.",
|
|
332
|
+
hooksInstalled: "Hooks already installed.",
|
|
333
|
+
hooksNotInstalled: "Hooks not installed.",
|
|
334
|
+
hooksTokenMismatch: "Hooks have outdated token - reconfiguration required.",
|
|
335
|
+
actionQuestion: "Select action:",
|
|
336
|
+
injectAction: "Inject hooks configuration",
|
|
337
|
+
updateAction: "Update hooks configuration",
|
|
338
|
+
backAction: "Back",
|
|
339
|
+
hookCheckbox: "Select hooks:",
|
|
340
|
+
noHooksSelected: "No hooks selected, skipping injection.",
|
|
341
|
+
injecting: "Injecting hooks...",
|
|
342
|
+
injected: "Hooks injected",
|
|
343
|
+
failed: "Failed: {{error}}",
|
|
344
|
+
remoteModeSkipHooks: "Remote mode does not require hooks; skipping injection.",
|
|
345
|
+
cleaningHooks: "Cleaning up hooks from previous mode...",
|
|
346
|
+
hooksCleaned: "Hooks cleaned",
|
|
347
|
+
cleanupWarning: "Hook cleanup warning: {{error}}"
|
|
348
|
+
},
|
|
349
|
+
channel: {
|
|
350
|
+
configuring: "Configuring {{channel}}",
|
|
351
|
+
telegram: {
|
|
352
|
+
botToken: "Telegram bot token:",
|
|
353
|
+
allowedUsers: "Allowed Telegram user IDs (comma-separated):",
|
|
354
|
+
testing: "Testing Telegram connection...",
|
|
355
|
+
connected: "Connected to Telegram",
|
|
356
|
+
sendingTest: "Sending test message...",
|
|
357
|
+
testSent: "Test message sent",
|
|
358
|
+
testContent: "Handsoff test message",
|
|
359
|
+
testWarning: "Could not send test message (user may not have messaged bot yet)",
|
|
360
|
+
apiError: "API error: {{status}}",
|
|
361
|
+
connFailed: "Connection failed: {{error}}"
|
|
362
|
+
},
|
|
363
|
+
feishu: {
|
|
364
|
+
appId: "Feishu App ID:",
|
|
365
|
+
appSecret: "Feishu App Secret:",
|
|
366
|
+
allowedUsers: "Allowed User IDs (comma-separated, e.g., ou_xxx,oc_xxx):",
|
|
367
|
+
required: "app_id and app_secret are required. Skipping."
|
|
368
|
+
},
|
|
369
|
+
subscribedEvents: "Notification types:"
|
|
370
|
+
},
|
|
371
|
+
codex: {
|
|
372
|
+
enable: "Enable Codex agent integration?",
|
|
373
|
+
command: "Codex app-server command:",
|
|
374
|
+
workDir: "Default Codex working directory (optional):",
|
|
375
|
+
model: "Default Codex model (optional):",
|
|
376
|
+
approvalPolicy: "Default Codex approval policy (optional):",
|
|
377
|
+
detected: "Codex CLI detected.",
|
|
378
|
+
notDetected: "Codex CLI not found in PATH.",
|
|
379
|
+
enabled: "Codex integration enabled.",
|
|
380
|
+
disabled: "Codex integration disabled."
|
|
381
|
+
},
|
|
382
|
+
final: {
|
|
383
|
+
noItems: "No items configured.",
|
|
384
|
+
itemOk: "{{item}}: OK",
|
|
385
|
+
running: "Gateway is running",
|
|
386
|
+
runningChanges: "Gateway is running with pending changes",
|
|
387
|
+
configModified: "Configuration was modified. Restart required to apply changes.",
|
|
388
|
+
runningReady: "Gateway is already running and ready.",
|
|
389
|
+
notRunning: "Gateway is not running",
|
|
390
|
+
configModifiedStart: "Configuration was modified. Starting Gateway with new configuration.",
|
|
391
|
+
restarting: "Restarting...",
|
|
392
|
+
restarted: "Gateway restarted with new configuration",
|
|
393
|
+
starting: "Starting...",
|
|
394
|
+
started: "Gateway started",
|
|
395
|
+
startFailed: "Failed: {{error}}",
|
|
396
|
+
restartTip: "Run `handsoff gateway start` to restart.",
|
|
397
|
+
startTip: "Run `handsoff gateway start` to start.",
|
|
398
|
+
done: "Done."
|
|
399
|
+
}
|
|
392
400
|
}
|
|
393
401
|
},
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
402
|
+
cli: {
|
|
403
|
+
gateway: {
|
|
404
|
+
stopping: "Stopping daemon...",
|
|
405
|
+
startedOnPort: "Gateway started on port {{port}}",
|
|
406
|
+
stopped: "Daemon stopped",
|
|
407
|
+
notRunning: "Daemon is not running",
|
|
408
|
+
restartedOnPort: "Gateway restarted on port {{port}}",
|
|
409
|
+
startFailed: "Failed to start Gateway",
|
|
410
|
+
stopFailed: "Failed to stop Gateway",
|
|
411
|
+
restartFailed: "Failed to restart Gateway",
|
|
412
|
+
error: " Error: {{error}}",
|
|
413
|
+
checkLogs: "Check logs at: {{path}}",
|
|
414
|
+
status: {
|
|
415
|
+
title: "Gateway Status:",
|
|
416
|
+
running: "Running: {{value}}",
|
|
417
|
+
sessions: "Sessions: {{count}}",
|
|
418
|
+
clients: "Connected clients: {{count}}",
|
|
419
|
+
activeSessions: "Active sessions:",
|
|
420
|
+
notResponding: "Gateway is not responding",
|
|
421
|
+
notRunning: "Gateway is not running"
|
|
422
|
+
}
|
|
409
423
|
},
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
424
|
+
daemon: {
|
|
425
|
+
alreadyRunning: "Daemon is already running",
|
|
426
|
+
startedOnPort: "Daemon started on port {{port}}",
|
|
427
|
+
startFailed: "Daemon failed to start. Check logs for details.",
|
|
428
|
+
checkLogs: "Check logs at: {{path}}",
|
|
429
|
+
portInUse: "Warning: Port {{port}} still in use after {{ms}}ms",
|
|
430
|
+
startError: "Failed to start daemon: {{error}}",
|
|
431
|
+
lackingToken: "Daemon is running but PID file lacks token, restarting...",
|
|
432
|
+
staleProcess: "Daemon process exists but HTTP server is not responding, restarting...",
|
|
433
|
+
scriptNotFound: "Daemon script not found: {{path}}"
|
|
416
434
|
},
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
435
|
+
status: {
|
|
436
|
+
title: "=== Handsoff Status ===",
|
|
437
|
+
daemon: {
|
|
438
|
+
running: "Daemon: \u2713 Running",
|
|
439
|
+
notRunning: "Daemon: \u2717 Not running"
|
|
440
|
+
},
|
|
441
|
+
port: "Port: {{port}}",
|
|
442
|
+
logLevel: "Log Level: {{level}}",
|
|
443
|
+
channels: "Channels:",
|
|
444
|
+
telegram: {
|
|
445
|
+
enabled: "Telegram: \u2713 Enabled",
|
|
446
|
+
disabled: "Telegram: \u2717 Disabled"
|
|
447
|
+
},
|
|
448
|
+
feishu: {
|
|
449
|
+
enabled: "Feishu: \u2713 Enabled",
|
|
450
|
+
disabled: "Feishu: \u2717 Disabled"
|
|
451
|
+
},
|
|
452
|
+
settingsBackup: "Settings: \u2713 Backup exists",
|
|
453
|
+
settingsNoBackup: "Settings: \u2717 No backup",
|
|
454
|
+
paths: "Paths:",
|
|
455
|
+
pathConfig: "Config: {{path}}",
|
|
456
|
+
pathLogs: "Logs: {{path}}",
|
|
457
|
+
pathBackup: "Backup: {{path}}"
|
|
420
458
|
},
|
|
421
|
-
settingsBackup: "Settings: \u2713 Backup exists",
|
|
422
|
-
settingsNoBackup: "Settings: \u2717 No backup",
|
|
423
|
-
paths: "Paths:",
|
|
424
|
-
pathConfig: "Config: {{path}}",
|
|
425
|
-
pathLogs: "Logs: {{path}}",
|
|
426
|
-
pathBackup: "Backup: {{path}}"
|
|
427
|
-
},
|
|
428
|
-
cli: {
|
|
429
459
|
init: {
|
|
430
460
|
configExists: "Configuration exists. Continue?",
|
|
431
461
|
cancelled: "Cancelled."
|
|
@@ -492,7 +522,72 @@ var en_default = {
|
|
|
492
522
|
stopping: "\nStopping debug server..."
|
|
493
523
|
}
|
|
494
524
|
},
|
|
495
|
-
|
|
525
|
+
commands: {
|
|
526
|
+
start: {
|
|
527
|
+
title: "Handsoff",
|
|
528
|
+
hasWorkspace: "Current workspace: **{{name}}** (`{{id}}`).",
|
|
529
|
+
noWorkspace: "No workspace is bound to this channel yet.",
|
|
530
|
+
noWorkspaceWithList: "No workspace is selected for this channel. Available workspaces:",
|
|
531
|
+
switchPrompt: "Use `/workspace switch <id>` to select one.",
|
|
532
|
+
howToUse: "How to use it",
|
|
533
|
+
sendText: "Send plain text to start a session with the default agent (claude).",
|
|
534
|
+
useAgentPrefix: "Use `/claude <text>` or `/codex <text>` to talk to a specific agent.",
|
|
535
|
+
createWorkspace: "Use `/workspace create <name>` to create a workspace.",
|
|
536
|
+
listWorkspaces: "Use `/workspace list` to see available workspaces.",
|
|
537
|
+
switchWorkspace: "Use `/workspace switch <id>` to switch workspace.",
|
|
538
|
+
startFresh: "Use `/new` or `/clear` to start fresh in this chat.",
|
|
539
|
+
inspectSession: "Use `/session` to inspect the current chat session.",
|
|
540
|
+
seeHelp: "Use `/help` to see system commands."
|
|
541
|
+
},
|
|
542
|
+
workspace: {
|
|
543
|
+
noWorkspaces: "No workspaces yet. Use `/workspace create <name>` to create one.",
|
|
544
|
+
listTitle: "Workspaces",
|
|
545
|
+
listItem: "- `{{id}}` \u2014 {{name}} ({{cwd}})",
|
|
546
|
+
noWorkspaceBound: "No workspace is bound to this channel. Use `/workspace switch <id>`.",
|
|
547
|
+
currentTitle: "Current Workspace",
|
|
548
|
+
currentId: "ID: `{{id}}`",
|
|
549
|
+
currentName: "Name: {{name}}",
|
|
550
|
+
currentCwd: "CWD: `{{cwd}}`",
|
|
551
|
+
currentDescription: "Description: {{description}}",
|
|
552
|
+
switchUsage: "Usage: /workspace switch <id>",
|
|
553
|
+
notFound: "Workspace `{{id}}` not found.",
|
|
554
|
+
availableWorkspaces: "Available workspaces:",
|
|
555
|
+
switched: "Switched to workspace '{{name}}' ({{id}}).",
|
|
556
|
+
createUsage: "Usage: /workspace create <name> [--cwd <path>] [--id <id>] [--desc <text>]",
|
|
557
|
+
alreadyExists: "Workspace `{{id}}` already exists.",
|
|
558
|
+
created: "Created workspace '{{name}}' (`{{id}}`) at {{cwd}}",
|
|
559
|
+
createdAndBound: "Created workspace '{{name}}' (`{{id}}`) at {{cwd}} and auto-bound to this channel. You can start sending messages now.",
|
|
560
|
+
deleteUsage: "Usage: /workspace delete <id>",
|
|
561
|
+
deleted: "Deleted workspace '{{name}}' (`{{id}}`).",
|
|
562
|
+
infoNoWorkspaceBound: "No workspace bound to this channel. Use `/workspace switch <id>` or `/workspace info <id>`.",
|
|
563
|
+
infoTitle: "Workspace: {{name}}",
|
|
564
|
+
infoCreated: "Created: {{createdAt}}",
|
|
565
|
+
infoPermissionMode: "Permission Mode: {{mode}}",
|
|
566
|
+
unbindNoWorkspace: "No workspace is bound to this channel.",
|
|
567
|
+
unbound: "Unbound workspace '{{name}}' from this channel.",
|
|
568
|
+
rootNoWorkspace: "No workspace bound. Use `/workspace list` to see available workspaces, or `/workspace switch <id>` to bind one.",
|
|
569
|
+
rootCurrent: "Current workspace: '{{name}}' (`{{id}}`). Use /workspace <subcommand> for actions."
|
|
570
|
+
}
|
|
571
|
+
},
|
|
572
|
+
notify: {
|
|
573
|
+
titles: {
|
|
574
|
+
sessionStart: "\u{1F680} Session Started",
|
|
575
|
+
sessionEnd: "\u{1F44B} Session Ended",
|
|
576
|
+
turnStarted: "\u{1F504} Turn Started",
|
|
577
|
+
turnFinished: "\u2705 Task Completed",
|
|
578
|
+
turnThinking: "\u{1F4AD} Thinking...",
|
|
579
|
+
toolPost: "\u{1F527} Tool Call",
|
|
580
|
+
toolExecuted: "\u2705 Tool Executed",
|
|
581
|
+
toolFailure: "\u274C Tool Failed",
|
|
582
|
+
permissionRequest: "\u{1F510} Permission Request",
|
|
583
|
+
permissionResponse: "\u{1F4CB} Permission Response",
|
|
584
|
+
questionRequest: "\u2753 Question",
|
|
585
|
+
questionResponse: "\u{1F4CB} Question Response",
|
|
586
|
+
systemEvent: "\u{1F4CB} System Event",
|
|
587
|
+
agentMessage: "\u{1F916} Agent Message",
|
|
588
|
+
error: "\u26A0\uFE0F Error",
|
|
589
|
+
fallback: "\u{1F4CB} Notification"
|
|
590
|
+
},
|
|
496
591
|
permission: {
|
|
497
592
|
title: "\u{1F510} Permission Request",
|
|
498
593
|
session: "Session: {{sessionId}}",
|
|
@@ -521,23 +616,62 @@ var en_default = {
|
|
|
521
616
|
},
|
|
522
617
|
unauthorized: "You are not authorized to use this bot.",
|
|
523
618
|
received: "Message received.",
|
|
524
|
-
processingError: "Error processing message."
|
|
619
|
+
processingError: "Error processing message.",
|
|
620
|
+
fallback: {
|
|
621
|
+
permission: "Reply /approve or /deny"
|
|
622
|
+
}
|
|
525
623
|
},
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
624
|
+
agent: {
|
|
625
|
+
claude: {
|
|
626
|
+
command: {
|
|
627
|
+
model: {
|
|
628
|
+
description: "Set default model for Claude",
|
|
629
|
+
usage: "Usage: /model <model-name>",
|
|
630
|
+
usageHint: "Usage: /model <model-name>",
|
|
631
|
+
success: "Model set to {{model}}"
|
|
632
|
+
},
|
|
633
|
+
permission: {
|
|
634
|
+
description: "Set permission mode for Claude",
|
|
635
|
+
usage: "Usage: /permission <mode>",
|
|
636
|
+
invalidMode: "Invalid mode: {{mode}}. Valid modes: {{modes}}",
|
|
637
|
+
success: "Permission mode set to {{mode}}"
|
|
638
|
+
}
|
|
639
|
+
},
|
|
640
|
+
concurrentTurn: "Claude is still processing the previous message. Please wait.",
|
|
641
|
+
sessionEndedUnexpectedly: "Claude session ended unexpectedly."
|
|
642
|
+
},
|
|
643
|
+
codex: {
|
|
644
|
+
clientTitle: "Handsoff Codex Client",
|
|
645
|
+
concurrentTurn: "Codex is still processing the previous message. Please wait."
|
|
646
|
+
}
|
|
647
|
+
},
|
|
648
|
+
channel: {
|
|
649
|
+
fallback: {
|
|
650
|
+
replyByNumber: "Please reply with the number:"
|
|
651
|
+
},
|
|
652
|
+
feishu: {
|
|
653
|
+
card: {
|
|
654
|
+
taskCompleted: "\u2705 Task Completed",
|
|
655
|
+
noOutput: "No output",
|
|
656
|
+
permissionRequired: "Permission Required",
|
|
657
|
+
questionTitle: "Question",
|
|
658
|
+
errorTitle: "\u274C Error",
|
|
659
|
+
submit: "Submit"
|
|
660
|
+
},
|
|
661
|
+
confirm: {
|
|
662
|
+
denyTitle: "Confirm Deny",
|
|
663
|
+
denyText: "Are you sure you want to deny?",
|
|
664
|
+
alwaysAllowTitle: "Confirm Always Allow",
|
|
665
|
+
alwaysAllowText: "Are you sure you want to always allow?"
|
|
666
|
+
},
|
|
667
|
+
toast: {
|
|
668
|
+
processed: "Processed"
|
|
669
|
+
}
|
|
670
|
+
},
|
|
671
|
+
telegram: {
|
|
672
|
+
invalidRequest: "Invalid request",
|
|
673
|
+
unauthorized: "Unauthorized"
|
|
674
|
+
}
|
|
541
675
|
}
|
|
542
676
|
};
|
|
543
677
|
|
|
@@ -559,12 +693,12 @@ function getSettingsPath() {
|
|
|
559
693
|
function backupSettings(settingsPath) {
|
|
560
694
|
const backupPath = settingsPath + ".handsoff-backup";
|
|
561
695
|
if (existsSync3(settingsPath)) {
|
|
562
|
-
|
|
696
|
+
writeFileSync3(backupPath, readFileSync3(settingsPath, "utf-8"));
|
|
563
697
|
}
|
|
564
698
|
}
|
|
565
699
|
function writeSettings(settingsPath, settings) {
|
|
566
700
|
mkdirSync2(dirname2(settingsPath), { recursive: true });
|
|
567
|
-
|
|
701
|
+
writeFileSync3(settingsPath, JSON.stringify(settings, null, 2));
|
|
568
702
|
}
|
|
569
703
|
function getGatewayBaseUrl(port) {
|
|
570
704
|
return `http://localhost:${port}`;
|
|
@@ -662,7 +796,7 @@ function createInitialState(hasExistingConfig) {
|
|
|
662
796
|
}
|
|
663
797
|
|
|
664
798
|
// src/cli/wizard/applicator.ts
|
|
665
|
-
import { writeFileSync as
|
|
799
|
+
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
666
800
|
import { join as join4 } from "path";
|
|
667
801
|
import { homedir as homedir4 } from "os";
|
|
668
802
|
import TOML2 from "@iarna/toml";
|
|
@@ -709,7 +843,7 @@ var ConfigApplicator = class {
|
|
|
709
843
|
mkdirSync3(configDir, { recursive: true });
|
|
710
844
|
const configPath = join4(configDir, "config.toml");
|
|
711
845
|
const content = TOML2.stringify(finalConfig);
|
|
712
|
-
|
|
846
|
+
writeFileSync4(configPath, content);
|
|
713
847
|
}
|
|
714
848
|
};
|
|
715
849
|
|
|
@@ -846,47 +980,61 @@ function detectCodex() {
|
|
|
846
980
|
|
|
847
981
|
// src/cli/wizard/prompts.ts
|
|
848
982
|
import { confirm, input, checkbox, select } from "@inquirer/prompts";
|
|
983
|
+
var EVENT_TYPE_CHOICES = [
|
|
984
|
+
{ name: t("notify.titles.sessionStart"), value: "session:start" },
|
|
985
|
+
{ name: t("notify.titles.sessionEnd"), value: "session:end" },
|
|
986
|
+
{ name: t("notify.titles.turnStarted"), value: "turn:started" },
|
|
987
|
+
{ name: t("notify.titles.turnThinking"), value: "turn:thinking" },
|
|
988
|
+
{ name: t("notify.titles.turnFinished"), value: "turn:finished" },
|
|
989
|
+
{ name: t("notify.titles.toolPost"), value: "tool:post" },
|
|
990
|
+
{ name: t("notify.titles.toolExecuted"), value: "tool:executed" },
|
|
991
|
+
{ name: t("notify.titles.toolFailure"), value: "tool:failure" },
|
|
992
|
+
{ name: t("notify.titles.permissionRequest"), value: "permission:request" },
|
|
993
|
+
{ name: t("notify.titles.questionRequest"), value: "question:request" },
|
|
994
|
+
{ name: t("notify.titles.systemEvent"), value: "system:event" },
|
|
995
|
+
{ name: t("notify.titles.error"), value: "error" }
|
|
996
|
+
];
|
|
849
997
|
var prompts = {
|
|
850
998
|
feishuAppId: (current) => input({
|
|
851
|
-
message: t("wizard.channel.feishu.appId"),
|
|
999
|
+
message: t("wizard.step.channel.feishu.appId"),
|
|
852
1000
|
default: current || ""
|
|
853
1001
|
}),
|
|
854
1002
|
feishuAppSecret: (current) => input({
|
|
855
|
-
message: t("wizard.channel.feishu.appSecret"),
|
|
1003
|
+
message: t("wizard.step.channel.feishu.appSecret"),
|
|
856
1004
|
default: current || ""
|
|
857
1005
|
}),
|
|
858
1006
|
feishuAllowedUsers: (current) => input({
|
|
859
|
-
message: t("wizard.channel.feishu.allowedUsers"),
|
|
1007
|
+
message: t("wizard.step.channel.feishu.allowedUsers"),
|
|
860
1008
|
default: current || ""
|
|
861
1009
|
}),
|
|
862
1010
|
codexEnable: (enabled) => confirm({
|
|
863
|
-
message: t("wizard.codex.enable"),
|
|
1011
|
+
message: t("wizard.step.codex.enable"),
|
|
864
1012
|
default: enabled
|
|
865
1013
|
}),
|
|
866
1014
|
codexCommand: (current) => input({
|
|
867
|
-
message: t("wizard.codex.command"),
|
|
1015
|
+
message: t("wizard.step.codex.command"),
|
|
868
1016
|
default: current || "codex app-server --listen stdio://"
|
|
869
1017
|
}),
|
|
870
1018
|
codexWorkDir: (current) => input({
|
|
871
|
-
message: t("wizard.codex.workDir"),
|
|
1019
|
+
message: t("wizard.step.codex.workDir"),
|
|
872
1020
|
default: current || ""
|
|
873
1021
|
}),
|
|
874
1022
|
codexModel: (current) => input({
|
|
875
|
-
message: t("wizard.codex.model"),
|
|
1023
|
+
message: t("wizard.step.codex.model"),
|
|
876
1024
|
default: current || ""
|
|
877
1025
|
}),
|
|
878
1026
|
codexApprovalPolicy: (current) => input({
|
|
879
|
-
message: t("wizard.codex.approvalPolicy"),
|
|
1027
|
+
message: t("wizard.step.codex.approvalPolicy"),
|
|
880
1028
|
default: current || ""
|
|
881
1029
|
}),
|
|
882
1030
|
cliActionSelect: (hooked) => select({
|
|
883
|
-
message: t("wizard.cli.actionQuestion"),
|
|
1031
|
+
message: t("wizard.step.cli.actionQuestion"),
|
|
884
1032
|
choices: hooked ? [
|
|
885
|
-
{ name: t("wizard.cli.updateAction"), value: "update" },
|
|
886
|
-
{ name: t("wizard.cli.backAction"), value: "back" }
|
|
1033
|
+
{ name: t("wizard.step.cli.updateAction"), value: "update" },
|
|
1034
|
+
{ name: t("wizard.step.cli.backAction"), value: "back" }
|
|
887
1035
|
] : [
|
|
888
|
-
{ name: t("wizard.cli.injectAction"), value: "inject" },
|
|
889
|
-
{ name: t("wizard.cli.backAction"), value: "back" }
|
|
1036
|
+
{ name: t("wizard.step.cli.injectAction"), value: "inject" },
|
|
1037
|
+
{ name: t("wizard.step.cli.backAction"), value: "back" }
|
|
890
1038
|
]
|
|
891
1039
|
}),
|
|
892
1040
|
claudeMode: () => select({
|
|
@@ -909,7 +1057,7 @@ var prompts = {
|
|
|
909
1057
|
default: defaultValue ?? "acceptEdits"
|
|
910
1058
|
}),
|
|
911
1059
|
cliHookSelect: (selected) => checkbox({
|
|
912
|
-
message: t("wizard.cli.hookCheckbox"),
|
|
1060
|
+
message: t("wizard.step.cli.hookCheckbox"),
|
|
913
1061
|
choices: [
|
|
914
1062
|
{ name: "Stop", value: "Stop", checked: selected?.includes("Stop") ?? true },
|
|
915
1063
|
{ name: "SessionStart", value: "SessionStart", checked: selected?.includes("SessionStart") ?? true },
|
|
@@ -924,11 +1072,11 @@ var prompts = {
|
|
|
924
1072
|
]
|
|
925
1073
|
}),
|
|
926
1074
|
botToken: (current) => input({
|
|
927
|
-
message: t("wizard.channel.telegram.botToken"),
|
|
1075
|
+
message: t("wizard.step.channel.telegram.botToken"),
|
|
928
1076
|
default: current || ""
|
|
929
1077
|
}),
|
|
930
1078
|
allowedUsers: (current) => input({
|
|
931
|
-
message: t("wizard.channel.telegram.allowedUsers"),
|
|
1079
|
+
message: t("wizard.step.channel.telegram.allowedUsers"),
|
|
932
1080
|
default: current || ""
|
|
933
1081
|
}),
|
|
934
1082
|
channelMenuSelect: (channels) => select({
|
|
@@ -941,16 +1089,6 @@ var prompts = {
|
|
|
941
1089
|
{ name: t("wizard.menu.next"), value: "__next__" }
|
|
942
1090
|
]
|
|
943
1091
|
}),
|
|
944
|
-
notifyTypes: (selected) => checkbox({
|
|
945
|
-
message: t("wizard.notify.checkbox"),
|
|
946
|
-
choices: [
|
|
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") },
|
|
951
|
-
{ name: "error", value: "error", checked: selected?.includes("error") }
|
|
952
|
-
]
|
|
953
|
-
}),
|
|
954
1092
|
channelConfigError: () => select({
|
|
955
1093
|
message: t("wizard.menu.testFailed"),
|
|
956
1094
|
choices: [
|
|
@@ -988,11 +1126,18 @@ var prompts = {
|
|
|
988
1126
|
channelSelect: (channels) => select({
|
|
989
1127
|
message: "Select a channel to configure:",
|
|
990
1128
|
choices: channels.map((c) => ({ name: `${c.name} ${c.status}`, value: c.value }))
|
|
1129
|
+
}),
|
|
1130
|
+
subscribedEventsSelect: (selected) => checkbox({
|
|
1131
|
+
message: t("wizard.step.channel.subscribedEvents"),
|
|
1132
|
+
choices: EVENT_TYPE_CHOICES.map((choice) => ({
|
|
1133
|
+
...choice,
|
|
1134
|
+
checked: selected?.includes(choice.value) ?? true
|
|
1135
|
+
}))
|
|
991
1136
|
})
|
|
992
1137
|
};
|
|
993
1138
|
|
|
994
1139
|
// src/cli/wizard/steps/cli.ts
|
|
995
|
-
import { writeFileSync as
|
|
1140
|
+
import { writeFileSync as writeFileSync5, existsSync as existsSync5, readFileSync as readFileSync5, mkdirSync as mkdirSync4 } from "fs";
|
|
996
1141
|
import { dirname as dirname3 } from "path";
|
|
997
1142
|
import pc from "picocolors";
|
|
998
1143
|
import ora from "ora";
|
|
@@ -1009,21 +1154,21 @@ ${STEP} \u2014 Configure Agent
|
|
|
1009
1154
|
const detection = state.claudeDetection ?? detectClaude();
|
|
1010
1155
|
if (!detection.supported) {
|
|
1011
1156
|
console.log(pc.yellow(`! ${detection.message}`));
|
|
1012
|
-
console.log(pc.gray(` ${t("wizard.cli.runAgain")}
|
|
1157
|
+
console.log(pc.gray(` ${t("wizard.step.cli.runAgain")}
|
|
1013
1158
|
`));
|
|
1014
1159
|
return { action: "next", next: "cli-menu" };
|
|
1015
1160
|
}
|
|
1016
|
-
console.log(pc.green(`* ${t("wizard.cli.detected")}`));
|
|
1161
|
+
console.log(pc.green(`* ${t("wizard.step.cli.detected")}`));
|
|
1017
1162
|
console.log(pc.gray(` Settings: ${detection.settingsPath}`));
|
|
1018
1163
|
const port = loadConfig().general.hook_server_port;
|
|
1019
1164
|
const token = getHookToken();
|
|
1020
1165
|
console.log(pc.gray(` Gateway: http://localhost:${port}/hook/${token ? token.substring(0, 8) + "..." : "unknown"}`));
|
|
1021
1166
|
if (detection.hooked) {
|
|
1022
|
-
console.log(pc.green(` ${t("wizard.cli.hooksInstalled")}`));
|
|
1167
|
+
console.log(pc.green(` ${t("wizard.step.cli.hooksInstalled")}`));
|
|
1023
1168
|
} else if (detection.tokenMismatch) {
|
|
1024
|
-
console.log(pc.red(` ${t("wizard.cli.hooksTokenMismatch")}`));
|
|
1169
|
+
console.log(pc.red(` ${t("wizard.step.cli.hooksTokenMismatch")}`));
|
|
1025
1170
|
} else {
|
|
1026
|
-
console.log(pc.yellow(` ${t("wizard.cli.hooksNotInstalled")}`));
|
|
1171
|
+
console.log(pc.yellow(` ${t("wizard.step.cli.hooksNotInstalled")}`));
|
|
1027
1172
|
}
|
|
1028
1173
|
console.log("");
|
|
1029
1174
|
const mode = await prompts.claudeMode();
|
|
@@ -1035,29 +1180,29 @@ ${STEP} \u2014 Configure Agent
|
|
|
1035
1180
|
}
|
|
1036
1181
|
const selectedHooks = await prompts.cliHookSelect();
|
|
1037
1182
|
if (selectedHooks.length === 0) {
|
|
1038
|
-
console.log(pc.yellow(`! ${t("wizard.cli.noHooksSelected")}
|
|
1183
|
+
console.log(pc.yellow(`! ${t("wizard.step.cli.noHooksSelected")}
|
|
1039
1184
|
`));
|
|
1040
1185
|
return { action: "next", next: "cli-menu" };
|
|
1041
1186
|
}
|
|
1042
|
-
const spinner = ora(t("wizard.cli.injecting")).start();
|
|
1187
|
+
const spinner = ora(t("wizard.step.cli.injecting")).start();
|
|
1043
1188
|
try {
|
|
1044
1189
|
injectClaudeHooks(detection.settingsPath, selectedHooks);
|
|
1045
|
-
spinner.succeed(t("wizard.cli.injected"));
|
|
1190
|
+
spinner.succeed(t("wizard.step.cli.injected"));
|
|
1046
1191
|
} catch (err) {
|
|
1047
|
-
spinner.fail(t("wizard.cli.failed", { error: String(err) }));
|
|
1192
|
+
spinner.fail(t("wizard.step.cli.failed", { error: String(err) }));
|
|
1048
1193
|
state.itemStatus.set("claude", "error");
|
|
1049
1194
|
state.validationResults.set("claude", { ok: false, message: String(err) });
|
|
1050
1195
|
return { action: "next", next: "cli-menu" };
|
|
1051
1196
|
}
|
|
1052
1197
|
} else {
|
|
1053
|
-
const spinner = ora(t("wizard.cli.cleaningHooks")).start();
|
|
1198
|
+
const spinner = ora(t("wizard.step.cli.cleaningHooks")).start();
|
|
1054
1199
|
try {
|
|
1055
1200
|
cleanupClaudeHooks(detection.settingsPath);
|
|
1056
|
-
spinner.succeed(t("wizard.cli.hooksCleaned"));
|
|
1201
|
+
spinner.succeed(t("wizard.step.cli.hooksCleaned"));
|
|
1057
1202
|
} catch (err) {
|
|
1058
|
-
spinner.warn(t("wizard.cli.cleanupWarning", { error: String(err) }));
|
|
1203
|
+
spinner.warn(t("wizard.step.cli.cleanupWarning", { error: String(err) }));
|
|
1059
1204
|
}
|
|
1060
|
-
console.log(pc.yellow(`! ${t("wizard.cli.remoteModeSkipHooks")}`));
|
|
1205
|
+
console.log(pc.yellow(`! ${t("wizard.step.cli.remoteModeSkipHooks")}`));
|
|
1061
1206
|
try {
|
|
1062
1207
|
execSync3("claude --version", { stdio: "ignore" });
|
|
1063
1208
|
} catch {
|
|
@@ -1084,14 +1229,14 @@ ${STEP} \u2014 Configure Agent
|
|
|
1084
1229
|
const config = loadConfig();
|
|
1085
1230
|
const current = config.agent.codex;
|
|
1086
1231
|
if (!detection.installed) {
|
|
1087
|
-
console.log(pc.yellow(`! ${t("wizard.codex.notDetected")}`));
|
|
1232
|
+
console.log(pc.yellow(`! ${t("wizard.step.codex.notDetected")}`));
|
|
1088
1233
|
console.log(pc.gray(` ${detection.message}
|
|
1089
1234
|
`));
|
|
1090
1235
|
state.itemStatus.set("codex", "not-found");
|
|
1091
1236
|
state.validationResults.set("codex", { ok: false, message: detection.message });
|
|
1092
1237
|
return { action: "next", next: "cli-menu" };
|
|
1093
1238
|
}
|
|
1094
|
-
console.log(pc.green(`* ${t("wizard.codex.detected")}`));
|
|
1239
|
+
console.log(pc.green(`* ${t("wizard.step.codex.detected")}`));
|
|
1095
1240
|
console.log(pc.gray(` Binary: ${detection.binaryPath}`));
|
|
1096
1241
|
console.log(pc.gray(` Version: ${detection.version || "unknown"}`));
|
|
1097
1242
|
console.log("");
|
|
@@ -1105,8 +1250,8 @@ ${STEP} \u2014 Configure Agent
|
|
|
1105
1250
|
}
|
|
1106
1251
|
};
|
|
1107
1252
|
state.itemStatus.set("codex", "unconfigured");
|
|
1108
|
-
state.validationResults.set("codex", { ok: true, message: t("wizard.codex.disabled") });
|
|
1109
|
-
console.log(pc.gray(` ${t("wizard.codex.disabled")}
|
|
1253
|
+
state.validationResults.set("codex", { ok: true, message: t("wizard.step.codex.disabled") });
|
|
1254
|
+
console.log(pc.gray(` ${t("wizard.step.codex.disabled")}
|
|
1110
1255
|
`));
|
|
1111
1256
|
return { action: "next", next: "cli-menu" };
|
|
1112
1257
|
}
|
|
@@ -1127,8 +1272,8 @@ ${STEP} \u2014 Configure Agent
|
|
|
1127
1272
|
};
|
|
1128
1273
|
state.itemStatus.set("codex", "configured");
|
|
1129
1274
|
state.sessionConfigured.add("codex");
|
|
1130
|
-
state.validationResults.set("codex", { ok: true, message: t("wizard.codex.enabled") });
|
|
1131
|
-
console.log(pc.green(` ${t("wizard.codex.enabled")}
|
|
1275
|
+
state.validationResults.set("codex", { ok: true, message: t("wizard.step.codex.enabled") });
|
|
1276
|
+
console.log(pc.green(` ${t("wizard.step.codex.enabled")}
|
|
1132
1277
|
`));
|
|
1133
1278
|
return { action: "next", next: "channel-select" };
|
|
1134
1279
|
}
|
|
@@ -1154,7 +1299,7 @@ function injectClaudeHooks(settingsPath, events) {
|
|
|
1154
1299
|
hooksConfig
|
|
1155
1300
|
);
|
|
1156
1301
|
mkdirSync4(dirname3(settingsPath), { recursive: true });
|
|
1157
|
-
|
|
1302
|
+
writeFileSync5(settingsPath, JSON.stringify(mergedSettings, null, 2));
|
|
1158
1303
|
}
|
|
1159
1304
|
var COMMAND_ONLY_HOOKS = ["SessionStart", "Setup"];
|
|
1160
1305
|
function generateHooksConfig2(port, token, events) {
|
|
@@ -1188,7 +1333,7 @@ function cleanupClaudeHooks(settingsPath) {
|
|
|
1188
1333
|
delete cleanedSettings.hooks;
|
|
1189
1334
|
}
|
|
1190
1335
|
mkdirSync4(dirname3(settingsPath), { recursive: true });
|
|
1191
|
-
|
|
1336
|
+
writeFileSync5(settingsPath, JSON.stringify(cleanedSettings, null, 2));
|
|
1192
1337
|
}
|
|
1193
1338
|
|
|
1194
1339
|
// src/cli/wizard/detectors/channel.ts
|
|
@@ -1288,20 +1433,20 @@ ${STEP2} \u2014 Configure Channel
|
|
|
1288
1433
|
const botToken = await prompts.botToken(currentConfig.bot_token);
|
|
1289
1434
|
const allowedUsers = await prompts.allowedUsers(String(currentConfig.allowed_users || ""));
|
|
1290
1435
|
const userIds = allowedUsers.split(",").map((s) => s.trim()).filter((s) => s.length > 0 && /^\d+$/.test(s));
|
|
1291
|
-
const spinner = ora2(t("wizard.channel.telegram.testing")).start();
|
|
1436
|
+
const spinner = ora2(t("wizard.step.channel.telegram.testing")).start();
|
|
1292
1437
|
let connected = false;
|
|
1293
1438
|
try {
|
|
1294
1439
|
const response = await fetch(`https://api.telegram.org/bot${botToken}/getMe`, {
|
|
1295
1440
|
signal: AbortSignal.timeout(8e3)
|
|
1296
1441
|
});
|
|
1297
1442
|
if (!response.ok) {
|
|
1298
|
-
spinner.fail(t("wizard.channel.telegram.apiError", { status: response.status }));
|
|
1443
|
+
spinner.fail(t("wizard.step.channel.telegram.apiError", { status: response.status }));
|
|
1299
1444
|
} else {
|
|
1300
|
-
spinner.succeed(t("wizard.channel.telegram.connected"));
|
|
1445
|
+
spinner.succeed(t("wizard.step.channel.telegram.connected"));
|
|
1301
1446
|
connected = true;
|
|
1302
1447
|
}
|
|
1303
1448
|
} catch (err) {
|
|
1304
|
-
spinner.fail(t("wizard.channel.telegram.connFailed", { error: String(err) }));
|
|
1449
|
+
spinner.fail(t("wizard.step.channel.telegram.connFailed", { error: String(err) }));
|
|
1305
1450
|
}
|
|
1306
1451
|
if (!connected) {
|
|
1307
1452
|
state.itemStatus.set(channelName, "error");
|
|
@@ -1312,21 +1457,23 @@ ${STEP2} \u2014 Configure Channel
|
|
|
1312
1457
|
state.itemStatus.set(channelName, "unconfigured");
|
|
1313
1458
|
return { action: "next", next: "channel-select" };
|
|
1314
1459
|
}
|
|
1315
|
-
const msgSpinner = ora2(t("wizard.channel.telegram.sendingTest")).start();
|
|
1460
|
+
const msgSpinner = ora2(t("wizard.step.channel.telegram.sendingTest")).start();
|
|
1316
1461
|
try {
|
|
1317
1462
|
await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {
|
|
1318
1463
|
method: "POST",
|
|
1319
1464
|
headers: { "Content-Type": "application/json" },
|
|
1320
|
-
body: JSON.stringify({ chat_id: userIds[0] || 0, text: t("wizard.channel.telegram.testContent") }),
|
|
1465
|
+
body: JSON.stringify({ chat_id: userIds[0] || 0, text: t("wizard.step.channel.telegram.testContent") }),
|
|
1321
1466
|
signal: AbortSignal.timeout(8e3)
|
|
1322
1467
|
});
|
|
1323
|
-
msgSpinner.succeed(t("wizard.channel.telegram.testSent"));
|
|
1468
|
+
msgSpinner.succeed(t("wizard.step.channel.telegram.testSent"));
|
|
1324
1469
|
} catch {
|
|
1325
|
-
msgSpinner.warn(t("wizard.channel.telegram.testWarning"));
|
|
1470
|
+
msgSpinner.warn(t("wizard.step.channel.telegram.testWarning"));
|
|
1326
1471
|
}
|
|
1472
|
+
const existingEvents = Array.isArray(currentConfig.subscribed_events) ? currentConfig.subscribed_events : void 0;
|
|
1473
|
+
const selectedEvents = await prompts.subscribedEventsSelect(existingEvents);
|
|
1327
1474
|
state.pendingChanges.channel = {
|
|
1328
1475
|
...state.pendingChanges.channel || {},
|
|
1329
|
-
telegram: { enabled: true, bot_token: botToken, allowed_users: userIds }
|
|
1476
|
+
telegram: { enabled: true, bot_token: botToken, allowed_users: userIds, subscribed_events: selectedEvents }
|
|
1330
1477
|
};
|
|
1331
1478
|
state.itemStatus.set("telegram", "configured");
|
|
1332
1479
|
state.sessionConfigured.add("telegram");
|
|
@@ -1347,15 +1494,17 @@ ${STEP2} \u2014 Configure Channel
|
|
|
1347
1494
|
const appSecret = await prompts.feishuAppSecret(String(currentConfig.app_secret || ""));
|
|
1348
1495
|
const allowedUsersInput = await prompts.feishuAllowedUsers(String(currentConfig.allowed_users || ""));
|
|
1349
1496
|
if (!appId || !appSecret) {
|
|
1350
|
-
console.log(pc3.yellow(`! ${t("wizard.channel.feishu.required")}
|
|
1497
|
+
console.log(pc3.yellow(`! ${t("wizard.step.channel.feishu.required")}
|
|
1351
1498
|
`));
|
|
1352
1499
|
state.itemStatus.set("feishu", "unconfigured");
|
|
1353
1500
|
return { action: "next", next: "channel-select" };
|
|
1354
1501
|
}
|
|
1355
1502
|
const allowedUsers = allowedUsersInput.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
1503
|
+
const existingEvents = Array.isArray(currentConfig.subscribed_events) ? currentConfig.subscribed_events : void 0;
|
|
1504
|
+
const selectedEvents = await prompts.subscribedEventsSelect(existingEvents);
|
|
1356
1505
|
state.pendingChanges.channel = {
|
|
1357
1506
|
...state.pendingChanges.channel || {},
|
|
1358
|
-
feishu: { enabled: true, app_id: appId, app_secret: appSecret, allowed_users: allowedUsers }
|
|
1507
|
+
feishu: { enabled: true, app_id: appId, app_secret: appSecret, allowed_users: allowedUsers, subscribed_events: selectedEvents }
|
|
1359
1508
|
};
|
|
1360
1509
|
state.itemStatus.set("feishu", "configured");
|
|
1361
1510
|
state.sessionConfigured.add("feishu");
|
|
@@ -1365,49 +1514,26 @@ ${STEP2} \u2014 Configure Channel
|
|
|
1365
1514
|
}
|
|
1366
1515
|
}
|
|
1367
1516
|
|
|
1368
|
-
// src/cli/wizard/steps/channel-notify.ts
|
|
1369
|
-
import pc4 from "picocolors";
|
|
1370
|
-
async function stepChannelNotify(state) {
|
|
1371
|
-
const channelName = state.currentChannelName || "telegram";
|
|
1372
|
-
console.log(pc4.bold(`
|
|
1373
|
-
${t("wizard.notify.title", { channel: channelName })}
|
|
1374
|
-
`));
|
|
1375
|
-
const cfg = loadConfig();
|
|
1376
|
-
const channelConfig = cfg.channel[channelName] || {};
|
|
1377
|
-
const currentTypes = channelConfig.notify_types || [];
|
|
1378
|
-
const selected = await prompts.notifyTypes(currentTypes);
|
|
1379
|
-
if (!state.pendingChanges.channel) state.pendingChanges.channel = {};
|
|
1380
|
-
if (!state.pendingChanges.channel[channelName]) state.pendingChanges.channel[channelName] = {};
|
|
1381
|
-
state.pendingChanges.channel[channelName] = {
|
|
1382
|
-
...state.pendingChanges.channel[channelName],
|
|
1383
|
-
notify_types: selected
|
|
1384
|
-
};
|
|
1385
|
-
console.log(pc4.green(`
|
|
1386
|
-
\u2713 ${t("wizard.notify.saved", { channel: channelName })}
|
|
1387
|
-
`));
|
|
1388
|
-
return { action: "next", next: "channel-menu" };
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
1517
|
// src/cli/wizard/steps/agent-select.ts
|
|
1392
|
-
import
|
|
1518
|
+
import pc4 from "picocolors";
|
|
1393
1519
|
var STEP3 = "STEP 1";
|
|
1394
1520
|
async function stepAgentSelect(state) {
|
|
1395
|
-
console.log(
|
|
1521
|
+
console.log(pc4.bold(`
|
|
1396
1522
|
${STEP3} \u2014 Select Agent
|
|
1397
1523
|
`));
|
|
1398
|
-
console.log(
|
|
1524
|
+
console.log(pc4.gray(`${"\u2500".repeat(50)}
|
|
1399
1525
|
`));
|
|
1400
1526
|
const config = loadConfig();
|
|
1401
1527
|
const currentBindings = config.bindings ?? {};
|
|
1402
1528
|
const agents = [];
|
|
1403
1529
|
const claudeConfigured = !!config.agent.claude;
|
|
1404
1530
|
const claudeBoundTo = currentBindings["claude"];
|
|
1405
|
-
const claudeStatus = claudeBoundTo ?
|
|
1531
|
+
const claudeStatus = claudeBoundTo ? pc4.green(`[bound to ${claudeBoundTo}]`) : claudeConfigured ? pc4.green("[configured]") : pc4.gray("[not configured]");
|
|
1406
1532
|
agents.push({ name: "claude", value: "claude", status: claudeStatus });
|
|
1407
1533
|
const codexDetection = detectCodex();
|
|
1408
1534
|
const codexConfigured = !!config.agent.codex?.enabled;
|
|
1409
1535
|
const codexBoundTo = currentBindings["codex"];
|
|
1410
|
-
const codexStatus = codexBoundTo ?
|
|
1536
|
+
const codexStatus = codexBoundTo ? pc4.green(`[bound to ${codexBoundTo}]`) : codexConfigured ? pc4.green("[configured]") : codexDetection.installed ? pc4.gray("[not configured]") : pc4.red("[not installed]");
|
|
1411
1537
|
agents.push({ name: "codex", value: "codex", status: codexStatus });
|
|
1412
1538
|
const choice = await prompts.agentSelect(agents);
|
|
1413
1539
|
state.currentAgentName = choice;
|
|
@@ -1430,13 +1556,13 @@ function getChannelInstanceId(channelType, credential) {
|
|
|
1430
1556
|
}
|
|
1431
1557
|
|
|
1432
1558
|
// src/cli/wizard/steps/channel-select.ts
|
|
1433
|
-
import
|
|
1559
|
+
import pc5 from "picocolors";
|
|
1434
1560
|
var STEP4 = "STEP 3";
|
|
1435
1561
|
async function stepChannelSelect(state) {
|
|
1436
|
-
console.log(
|
|
1562
|
+
console.log(pc5.bold(`
|
|
1437
1563
|
${STEP4} \u2014 Select Channel
|
|
1438
1564
|
`));
|
|
1439
|
-
console.log(
|
|
1565
|
+
console.log(pc5.gray(`${"\u2500".repeat(50)}
|
|
1440
1566
|
`));
|
|
1441
1567
|
const config = loadConfig();
|
|
1442
1568
|
const currentBindings = config.bindings ?? {};
|
|
@@ -1448,22 +1574,22 @@ ${STEP4} \u2014 Select Channel
|
|
|
1448
1574
|
const loggerEnabled = mergedLogger?.enabled === true;
|
|
1449
1575
|
const loggerInstanceId = "logger:default";
|
|
1450
1576
|
const loggerBoundAgent = Object.entries(currentBindings).find(([, v]) => v === loggerInstanceId)?.[0];
|
|
1451
|
-
const loggerStatus = loggerBoundAgent ?
|
|
1577
|
+
const loggerStatus = loggerBoundAgent ? pc5.green(`[bound to ${loggerBoundAgent}]`) : loggerEnabled ? pc5.green("[configured]") : pc5.gray("[not configured]");
|
|
1452
1578
|
if (mergedTelegram?.enabled && mergedTelegram.bot_token) {
|
|
1453
1579
|
const instanceId = getChannelInstanceId("telegram", mergedTelegram.bot_token);
|
|
1454
1580
|
const boundAgent = Object.entries(currentBindings).find(([, v]) => v === instanceId)?.[0];
|
|
1455
|
-
const status = boundAgent ?
|
|
1581
|
+
const status = boundAgent ? pc5.green(`[bound to ${boundAgent}]`) : pc5.green("[configured]");
|
|
1456
1582
|
channels.push({ name: "telegram", value: "telegram", status });
|
|
1457
1583
|
} else {
|
|
1458
|
-
channels.push({ name: "telegram", value: "telegram", status:
|
|
1584
|
+
channels.push({ name: "telegram", value: "telegram", status: pc5.gray("[not configured]") });
|
|
1459
1585
|
}
|
|
1460
1586
|
if (mergedFeishu?.enabled && mergedFeishu.app_id) {
|
|
1461
1587
|
const instanceId = getChannelInstanceId("feishu", mergedFeishu.app_id);
|
|
1462
1588
|
const boundAgent = Object.entries(currentBindings).find(([, v]) => v === instanceId)?.[0];
|
|
1463
|
-
const status = boundAgent ?
|
|
1589
|
+
const status = boundAgent ? pc5.green(`[bound to ${boundAgent}]`) : pc5.green("[configured]");
|
|
1464
1590
|
channels.push({ name: "feishu", value: "feishu", status });
|
|
1465
1591
|
} else {
|
|
1466
|
-
channels.push({ name: "feishu", value: "feishu", status:
|
|
1592
|
+
channels.push({ name: "feishu", value: "feishu", status: pc5.gray("[not configured]") });
|
|
1467
1593
|
}
|
|
1468
1594
|
channels.push({ name: "logger", value: "logger", status: loggerStatus });
|
|
1469
1595
|
const choice = await prompts.channelSelect(channels);
|
|
@@ -1476,15 +1602,15 @@ ${STEP4} \u2014 Select Channel
|
|
|
1476
1602
|
}
|
|
1477
1603
|
|
|
1478
1604
|
// src/cli/wizard/steps/binding-confirm.ts
|
|
1479
|
-
import
|
|
1605
|
+
import pc6 from "picocolors";
|
|
1480
1606
|
var STEP5 = "STEP 5";
|
|
1481
1607
|
async function stepBindingConfirm(state) {
|
|
1482
1608
|
const agentName = state.currentAgentName;
|
|
1483
1609
|
const channelName = state.currentChannelName;
|
|
1484
|
-
console.log(
|
|
1610
|
+
console.log(pc6.bold(`
|
|
1485
1611
|
${STEP5} \u2014 Confirm Binding
|
|
1486
1612
|
`));
|
|
1487
|
-
console.log(
|
|
1613
|
+
console.log(pc6.gray(`${"\u2500".repeat(50)}
|
|
1488
1614
|
`));
|
|
1489
1615
|
const config = loadConfig();
|
|
1490
1616
|
const currentBindings = { ...config.bindings ?? {} };
|
|
@@ -1507,8 +1633,8 @@ ${STEP5} \u2014 Confirm Binding
|
|
|
1507
1633
|
instanceId = "logger:default";
|
|
1508
1634
|
channelCurrentAgent = Object.entries(currentBindings).find(([, v]) => v === instanceId)?.[0];
|
|
1509
1635
|
}
|
|
1510
|
-
console.log(` ${
|
|
1511
|
-
console.log(` ${
|
|
1636
|
+
console.log(` ${pc6.cyan(agentName)} current binding: ${agentCurrentChannel ? pc6.yellow(agentCurrentChannel) : pc6.gray("none")}`);
|
|
1637
|
+
console.log(` ${pc6.cyan(channelName)} current binding: ${channelCurrentAgent ? pc6.yellow(channelCurrentAgent) : pc6.gray("none")}`);
|
|
1512
1638
|
console.log("");
|
|
1513
1639
|
const choice = await prompts.bindingConfirm(agentName, channelName);
|
|
1514
1640
|
if (choice === "confirm" && instanceId) {
|
|
@@ -1519,26 +1645,26 @@ ${STEP5} \u2014 Confirm Binding
|
|
|
1519
1645
|
}
|
|
1520
1646
|
}
|
|
1521
1647
|
if (oldChannelId && oldChannelId !== instanceId) {
|
|
1522
|
-
console.log(
|
|
1648
|
+
console.log(pc6.yellow(` \u26A0 ${agentName} migrated from ${oldChannelId} to ${instanceId}`));
|
|
1523
1649
|
}
|
|
1524
1650
|
state.bindings[agentName] = instanceId;
|
|
1525
1651
|
state.sessionConfigured.add(`binding:${agentName}`);
|
|
1526
|
-
console.log(
|
|
1652
|
+
console.log(pc6.green(` \u2713 ${agentName} bound to ${channelName}`));
|
|
1527
1653
|
} else {
|
|
1528
|
-
console.log(
|
|
1654
|
+
console.log(pc6.gray(` Binding cancelled`));
|
|
1529
1655
|
}
|
|
1530
1656
|
console.log("");
|
|
1531
1657
|
return { action: "next", next: "pair-continue" };
|
|
1532
1658
|
}
|
|
1533
1659
|
|
|
1534
1660
|
// src/cli/wizard/steps/pair-continue.ts
|
|
1535
|
-
import
|
|
1661
|
+
import pc7 from "picocolors";
|
|
1536
1662
|
var STEP6 = "STEP 6";
|
|
1537
1663
|
async function stepPairContinue(state) {
|
|
1538
|
-
console.log(
|
|
1664
|
+
console.log(pc7.bold(`
|
|
1539
1665
|
${STEP6} \u2014 Add Another Pair?
|
|
1540
1666
|
`));
|
|
1541
|
-
console.log(
|
|
1667
|
+
console.log(pc7.gray(`${"\u2500".repeat(50)}
|
|
1542
1668
|
`));
|
|
1543
1669
|
const choice = await prompts.pairContinue();
|
|
1544
1670
|
if (choice === "continue") {
|
|
@@ -1557,7 +1683,7 @@ import { fileURLToPath } from "url";
|
|
|
1557
1683
|
import { mkdirSync as mkdirSync5, existsSync as existsSync7 } from "fs";
|
|
1558
1684
|
|
|
1559
1685
|
// src/shared/pidfile.ts
|
|
1560
|
-
import { writeFileSync as
|
|
1686
|
+
import { writeFileSync as writeFileSync6, readFileSync as readFileSync6, existsSync as existsSync6, unlinkSync } from "fs";
|
|
1561
1687
|
function readPidFile(pidFilePath) {
|
|
1562
1688
|
if (!existsSync6(pidFilePath)) {
|
|
1563
1689
|
return null;
|
|
@@ -1591,6 +1717,18 @@ function isPortInUse(port) {
|
|
|
1591
1717
|
});
|
|
1592
1718
|
});
|
|
1593
1719
|
}
|
|
1720
|
+
async function isHttpReady(port, timeoutMs = 2e3) {
|
|
1721
|
+
const controller = new AbortController();
|
|
1722
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1723
|
+
try {
|
|
1724
|
+
const response = await fetch(`http://localhost:${port}/health`, { signal: controller.signal });
|
|
1725
|
+
clearTimeout(timer);
|
|
1726
|
+
return response.ok;
|
|
1727
|
+
} catch {
|
|
1728
|
+
clearTimeout(timer);
|
|
1729
|
+
return false;
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1594
1732
|
var DaemonManager = class {
|
|
1595
1733
|
pidFilePath;
|
|
1596
1734
|
port;
|
|
@@ -1622,12 +1760,18 @@ var DaemonManager = class {
|
|
|
1622
1760
|
const existingPidData = readPidFile(this.pidFilePath);
|
|
1623
1761
|
if (existingPidData && isProcessRunning(existingPidData.pid)) {
|
|
1624
1762
|
if (!existingPidData.token) {
|
|
1625
|
-
console.log(t("daemon.lackingToken"));
|
|
1763
|
+
console.log(t("cli.daemon.lackingToken"));
|
|
1626
1764
|
await this.stop();
|
|
1627
1765
|
await this.waitForPortFree();
|
|
1628
1766
|
} else {
|
|
1629
|
-
|
|
1630
|
-
|
|
1767
|
+
const ready = await isHttpReady(this.port, 3e3);
|
|
1768
|
+
if (ready) {
|
|
1769
|
+
console.log(t("cli.daemon.alreadyRunning"));
|
|
1770
|
+
return true;
|
|
1771
|
+
}
|
|
1772
|
+
console.log(t("cli.daemon.staleProcess"));
|
|
1773
|
+
await this.stop();
|
|
1774
|
+
await this.waitForPortFree();
|
|
1631
1775
|
}
|
|
1632
1776
|
}
|
|
1633
1777
|
try {
|
|
@@ -1641,7 +1785,7 @@ var DaemonManager = class {
|
|
|
1641
1785
|
daemonScript = join6(__dirname3, "..", "..", "src", "gateway", "process.ts");
|
|
1642
1786
|
}
|
|
1643
1787
|
if (!existsSync7(daemonScript)) {
|
|
1644
|
-
this.lastError = t("daemon.scriptNotFound", { path: daemonScript });
|
|
1788
|
+
this.lastError = t("cli.daemon.scriptNotFound", { path: daemonScript });
|
|
1645
1789
|
console.error(this.lastError);
|
|
1646
1790
|
return false;
|
|
1647
1791
|
}
|
|
@@ -1660,23 +1804,26 @@ var DaemonManager = class {
|
|
|
1660
1804
|
});
|
|
1661
1805
|
child.unref();
|
|
1662
1806
|
let pidData = null;
|
|
1663
|
-
for (let i = 0; i <
|
|
1664
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1807
|
+
for (let i = 0; i < 150; i++) {
|
|
1808
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
1665
1809
|
pidData = readPidFile(this.pidFilePath);
|
|
1666
1810
|
if (pidData) {
|
|
1667
1811
|
if (isProcessRunning(pidData.pid)) {
|
|
1668
|
-
|
|
1669
|
-
|
|
1812
|
+
const httpReady = await isHttpReady(this.port, 2e3);
|
|
1813
|
+
if (httpReady) {
|
|
1814
|
+
console.log(t("cli.daemon.startedOnPort", { port: this.port }));
|
|
1815
|
+
return true;
|
|
1816
|
+
}
|
|
1670
1817
|
}
|
|
1671
1818
|
}
|
|
1672
1819
|
}
|
|
1673
|
-
this.lastError = t("daemon.startFailed");
|
|
1820
|
+
this.lastError = t("cli.daemon.startFailed");
|
|
1674
1821
|
console.error(this.lastError);
|
|
1675
|
-
console.error(t("daemon.checkLogs", { path: logFile }));
|
|
1822
|
+
console.error(t("cli.daemon.checkLogs", { path: logFile }));
|
|
1676
1823
|
return false;
|
|
1677
1824
|
} catch (error) {
|
|
1678
1825
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1679
|
-
this.lastError = t("daemon.startError", { error: errorMsg });
|
|
1826
|
+
this.lastError = t("cli.daemon.startError", { error: errorMsg });
|
|
1680
1827
|
console.error(this.lastError);
|
|
1681
1828
|
return false;
|
|
1682
1829
|
}
|
|
@@ -1709,7 +1856,7 @@ var DaemonManager = class {
|
|
|
1709
1856
|
const startTime = Date.now();
|
|
1710
1857
|
while (await isPortInUse(this.port)) {
|
|
1711
1858
|
if (Date.now() - startTime > maxWaitMs) {
|
|
1712
|
-
console.log(t("daemon.portInUse", { port: this.port, ms: maxWaitMs }));
|
|
1859
|
+
console.log(t("cli.daemon.portInUse", { port: this.port, ms: maxWaitMs }));
|
|
1713
1860
|
break;
|
|
1714
1861
|
}
|
|
1715
1862
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
@@ -1720,20 +1867,20 @@ var DaemonManager = class {
|
|
|
1720
1867
|
// src/cli/wizard/steps/final.ts
|
|
1721
1868
|
import { join as join7 } from "path";
|
|
1722
1869
|
import { homedir as homedir6 } from "os";
|
|
1723
|
-
import
|
|
1870
|
+
import pc8 from "picocolors";
|
|
1724
1871
|
import ora3 from "ora";
|
|
1725
1872
|
async function stepFinal(state) {
|
|
1726
|
-
console.log(
|
|
1873
|
+
console.log(pc8.bold(`
|
|
1727
1874
|
${t("wizard.section.complete")}
|
|
1728
1875
|
`));
|
|
1729
1876
|
if (state.sessionConfigured.size === 0) {
|
|
1730
|
-
console.log(
|
|
1877
|
+
console.log(pc8.gray(` ${t("wizard.step.final.noItems")}
|
|
1731
1878
|
`));
|
|
1732
1879
|
} else {
|
|
1733
1880
|
for (const item of state.sessionConfigured) {
|
|
1734
1881
|
const result = state.validationResults.get(item);
|
|
1735
1882
|
if (result?.ok) {
|
|
1736
|
-
console.log(
|
|
1883
|
+
console.log(pc8.green(`* ${t("wizard.step.final.itemOk", { item })}`));
|
|
1737
1884
|
}
|
|
1738
1885
|
}
|
|
1739
1886
|
console.log("");
|
|
@@ -1744,74 +1891,74 @@ ${t("wizard.section.complete")}
|
|
|
1744
1891
|
const hasChanges = state.sessionConfigured.size > 0 || Object.keys(state.pendingChanges).length > 0 || Object.keys(state.bindings).length > 0;
|
|
1745
1892
|
if (daemon.isRunning()) {
|
|
1746
1893
|
if (hasChanges) {
|
|
1747
|
-
console.log(
|
|
1894
|
+
console.log(pc8.blue(`i ${t("wizard.step.final.runningChanges")}
|
|
1748
1895
|
`));
|
|
1749
|
-
console.log(
|
|
1896
|
+
console.log(pc8.yellow(` ${t("wizard.step.final.configModified")}
|
|
1750
1897
|
`));
|
|
1751
1898
|
const shouldRestart = await prompts.restartGateway();
|
|
1752
1899
|
if (shouldRestart) {
|
|
1753
|
-
const spinner = ora3(t("wizard.final.restarting")).start();
|
|
1900
|
+
const spinner = ora3(t("wizard.step.final.restarting")).start();
|
|
1754
1901
|
await daemon.stop();
|
|
1755
1902
|
const started = await daemon.start();
|
|
1756
1903
|
if (started) {
|
|
1757
|
-
spinner.succeed(t("wizard.final.restarted"));
|
|
1904
|
+
spinner.succeed(t("wizard.step.final.restarted"));
|
|
1758
1905
|
} else {
|
|
1759
|
-
spinner.fail(t("wizard.final.startFailed", { error: daemon.getLastError() ?? "unknown" }));
|
|
1906
|
+
spinner.fail(t("wizard.step.final.startFailed", { error: daemon.getLastError() ?? "unknown" }));
|
|
1760
1907
|
}
|
|
1761
1908
|
} else {
|
|
1762
|
-
console.log(
|
|
1909
|
+
console.log(pc8.gray(` ${t("wizard.step.final.restartTip")}
|
|
1763
1910
|
`));
|
|
1764
1911
|
}
|
|
1765
1912
|
} else {
|
|
1766
|
-
console.log(
|
|
1913
|
+
console.log(pc8.blue(`i ${t("wizard.step.final.running")}
|
|
1767
1914
|
`));
|
|
1768
|
-
console.log(
|
|
1915
|
+
console.log(pc8.green(` ${t("wizard.step.final.runningReady")}
|
|
1769
1916
|
`));
|
|
1770
1917
|
}
|
|
1771
1918
|
} else {
|
|
1772
|
-
console.log(
|
|
1919
|
+
console.log(pc8.blue(`i ${t("wizard.step.final.notRunning")}
|
|
1773
1920
|
`));
|
|
1774
1921
|
if (hasChanges) {
|
|
1775
|
-
console.log(
|
|
1922
|
+
console.log(pc8.yellow(` ${t("wizard.step.final.configModifiedStart")}
|
|
1776
1923
|
`));
|
|
1777
1924
|
}
|
|
1778
1925
|
const shouldStart = await prompts.restartGateway();
|
|
1779
1926
|
if (shouldStart) {
|
|
1780
|
-
const spinner2 = ora3(t("wizard.final.starting")).start();
|
|
1927
|
+
const spinner2 = ora3(t("wizard.step.final.starting")).start();
|
|
1781
1928
|
const started = await daemon.start();
|
|
1782
1929
|
if (started) {
|
|
1783
|
-
spinner2.succeed(t("wizard.final.started"));
|
|
1930
|
+
spinner2.succeed(t("wizard.step.final.started"));
|
|
1784
1931
|
} else {
|
|
1785
|
-
spinner2.fail(t("wizard.final.startFailed", { error: daemon.getLastError() ?? "unknown" }));
|
|
1932
|
+
spinner2.fail(t("wizard.step.final.startFailed", { error: daemon.getLastError() ?? "unknown" }));
|
|
1786
1933
|
}
|
|
1787
1934
|
} else {
|
|
1788
|
-
console.log(
|
|
1935
|
+
console.log(pc8.gray(` ${t("wizard.step.final.startTip")}
|
|
1789
1936
|
`));
|
|
1790
1937
|
}
|
|
1791
1938
|
}
|
|
1792
|
-
console.log(
|
|
1793
|
-
${t("wizard.final.done")}
|
|
1939
|
+
console.log(pc8.green(`
|
|
1940
|
+
${t("wizard.step.final.done")}
|
|
1794
1941
|
`));
|
|
1795
1942
|
return { action: "next", next: "done" };
|
|
1796
1943
|
}
|
|
1797
1944
|
|
|
1798
1945
|
// src/cli/wizard/steps/permission-mode.ts
|
|
1799
|
-
import
|
|
1946
|
+
import pc9 from "picocolors";
|
|
1800
1947
|
var STEP7 = "STEP 3";
|
|
1801
1948
|
async function stepPermissionMode(state) {
|
|
1802
|
-
console.log(
|
|
1949
|
+
console.log(pc9.bold(`
|
|
1803
1950
|
${STEP7} \u2014 Permission Mode
|
|
1804
1951
|
`));
|
|
1805
|
-
console.log(
|
|
1952
|
+
console.log(pc9.gray(`${"\u2500".repeat(50)}
|
|
1806
1953
|
`));
|
|
1807
1954
|
const config = loadConfig();
|
|
1808
1955
|
const current = state.pendingChanges?.agent?.claude;
|
|
1809
1956
|
const saved = current?.permission_mode ?? config.agent.claude?.permission_mode ?? null;
|
|
1810
1957
|
const detected = detectPermissionMode();
|
|
1811
1958
|
const initial = saved ?? detected ?? "acceptEdits";
|
|
1812
|
-
console.log(
|
|
1959
|
+
console.log(pc9.gray("Controls how Claude Code handles permission requests.\n"));
|
|
1813
1960
|
if (detected && !saved) {
|
|
1814
|
-
console.log(
|
|
1961
|
+
console.log(pc9.gray(`Detected from ~/.claude/settings.json: ${detected}
|
|
1815
1962
|
`));
|
|
1816
1963
|
}
|
|
1817
1964
|
const selected = await prompts.permissionModeSelect(initial);
|
|
@@ -1888,8 +2035,6 @@ var WizardEngine = class {
|
|
|
1888
2035
|
return await stepCliMenu(this.state);
|
|
1889
2036
|
case "channel-config":
|
|
1890
2037
|
return await stepChannelConfig(this.state);
|
|
1891
|
-
case "channel-notify":
|
|
1892
|
-
return await stepChannelNotify(this.state);
|
|
1893
2038
|
case "final":
|
|
1894
2039
|
return await stepFinal(this.state);
|
|
1895
2040
|
default:
|
|
@@ -2171,29 +2316,29 @@ var statusCommand = new Command4("status").description("Show current status").ac
|
|
|
2171
2316
|
const pidFilePath = join11(homedir10(), ".handsoff", "handsoff.pid");
|
|
2172
2317
|
const daemon = new DaemonManager(pidFilePath, config.general.hook_server_port);
|
|
2173
2318
|
console.log(`
|
|
2174
|
-
${t("status.title")}
|
|
2319
|
+
${t("cli.status.title")}
|
|
2175
2320
|
`);
|
|
2176
|
-
console.log(daemon.isRunning() ? t("status.daemon.running") : t("status.daemon.notRunning"));
|
|
2177
|
-
console.log(t("status.port", { port: config.general.hook_server_port }));
|
|
2178
|
-
console.log(`${t("status.logLevel", { level: config.general.log_level })}
|
|
2321
|
+
console.log(daemon.isRunning() ? t("cli.status.daemon.running") : t("cli.status.daemon.notRunning"));
|
|
2322
|
+
console.log(t("cli.status.port", { port: config.general.hook_server_port }));
|
|
2323
|
+
console.log(`${t("cli.status.logLevel", { level: config.general.log_level })}
|
|
2179
2324
|
`);
|
|
2180
|
-
console.log(t("status.channels"));
|
|
2181
|
-
console.log(` ${config.channel.telegram?.enabled ? t("status.telegram.enabled") : t("status.telegram.disabled")}`);
|
|
2182
|
-
console.log(` ${config.channel.feishu?.enabled ? t("status.feishu.enabled") : t("status.feishu.disabled")}
|
|
2325
|
+
console.log(t("cli.status.channels"));
|
|
2326
|
+
console.log(` ${config.channel.telegram?.enabled ? t("cli.status.telegram.enabled") : t("cli.status.telegram.disabled")}`);
|
|
2327
|
+
console.log(` ${config.channel.feishu?.enabled ? t("cli.status.feishu.enabled") : t("cli.status.feishu.disabled")}
|
|
2183
2328
|
`);
|
|
2184
2329
|
const settingsPath = getSettingsPath2();
|
|
2185
2330
|
const backupPath = settingsPath + ".handsoff-backup";
|
|
2186
2331
|
if (existsSync11(backupPath)) {
|
|
2187
|
-
console.log(t("status.settingsBackup"));
|
|
2332
|
+
console.log(t("cli.status.settingsBackup"));
|
|
2188
2333
|
} else {
|
|
2189
|
-
console.log(t("status.settingsNoBackup"));
|
|
2334
|
+
console.log(t("cli.status.settingsNoBackup"));
|
|
2190
2335
|
}
|
|
2191
2336
|
const home = homedir10();
|
|
2192
2337
|
console.log(`
|
|
2193
|
-
${t("status.paths")}`);
|
|
2194
|
-
console.log(` ${t("status.pathConfig", { path: `${home}/.handsoff/config.toml` })}`);
|
|
2195
|
-
console.log(` ${t("status.pathLogs", { path: `${home}/.handsoff/logs/gateway.log` })}`);
|
|
2196
|
-
console.log(` ${t("status.pathBackup", { path: `${home}/.claude/settings.json.handsoff-backup` })}
|
|
2338
|
+
${t("cli.status.paths")}`);
|
|
2339
|
+
console.log(` ${t("cli.status.pathConfig", { path: `${home}/.handsoff/config.toml` })}`);
|
|
2340
|
+
console.log(` ${t("cli.status.pathLogs", { path: `${home}/.handsoff/logs/gateway.log` })}`);
|
|
2341
|
+
console.log(` ${t("cli.status.pathBackup", { path: `${home}/.claude/settings.json.handsoff-backup` })}
|
|
2197
2342
|
`);
|
|
2198
2343
|
});
|
|
2199
2344
|
|
|
@@ -2229,20 +2374,20 @@ var gatewayCommand = new Command6("gateway").description("Manage handsoff Gatewa
|
|
|
2229
2374
|
const port = options.port ? parseInt(options.port, 10) : config.general.hook_server_port;
|
|
2230
2375
|
const daemon = new DaemonManager(PID_FILE, port);
|
|
2231
2376
|
if (daemon.isRunning()) {
|
|
2232
|
-
console.log(t("gateway.stopping"));
|
|
2377
|
+
console.log(t("cli.gateway.stopping"));
|
|
2233
2378
|
await daemon.stop();
|
|
2234
2379
|
}
|
|
2235
2380
|
const started = await daemon.start();
|
|
2236
2381
|
if (started) {
|
|
2237
|
-
console.log(t("gateway.startedOnPort", { port }));
|
|
2382
|
+
console.log(t("cli.gateway.startedOnPort", { port }));
|
|
2238
2383
|
} else {
|
|
2239
2384
|
const error = daemon.getLastError();
|
|
2240
|
-
console.error(t("gateway.startFailed"));
|
|
2385
|
+
console.error(t("cli.gateway.startFailed"));
|
|
2241
2386
|
if (error) {
|
|
2242
|
-
console.error(t("gateway.error", { error }));
|
|
2387
|
+
console.error(t("cli.gateway.error", { error }));
|
|
2243
2388
|
}
|
|
2244
2389
|
const logPath = join13(homedir12(), ".handsoff", "logs", "gateway.log");
|
|
2245
|
-
console.error(` ${t("gateway.checkLogs", { path: logPath })}`);
|
|
2390
|
+
console.error(` ${t("cli.gateway.checkLogs", { path: logPath })}`);
|
|
2246
2391
|
process.exit(1);
|
|
2247
2392
|
}
|
|
2248
2393
|
})
|
|
@@ -2251,15 +2396,15 @@ var gatewayCommand = new Command6("gateway").description("Manage handsoff Gatewa
|
|
|
2251
2396
|
const config = loadConfig();
|
|
2252
2397
|
const daemon = new DaemonManager(PID_FILE, config.general.hook_server_port);
|
|
2253
2398
|
if (!daemon.isRunning()) {
|
|
2254
|
-
console.log(t("gateway.notRunning"));
|
|
2399
|
+
console.log(t("cli.gateway.notRunning"));
|
|
2255
2400
|
return;
|
|
2256
2401
|
}
|
|
2257
|
-
console.log(t("gateway.stopping"));
|
|
2402
|
+
console.log(t("cli.gateway.stopping"));
|
|
2258
2403
|
const stopped = await daemon.stop();
|
|
2259
2404
|
if (stopped) {
|
|
2260
|
-
console.log(t("gateway.stopped"));
|
|
2405
|
+
console.log(t("cli.gateway.stopped"));
|
|
2261
2406
|
} else {
|
|
2262
|
-
console.error(t("gateway.stopFailed"));
|
|
2407
|
+
console.error(t("cli.gateway.stopFailed"));
|
|
2263
2408
|
process.exit(1);
|
|
2264
2409
|
}
|
|
2265
2410
|
})
|
|
@@ -2271,15 +2416,15 @@ var gatewayCommand = new Command6("gateway").description("Manage handsoff Gatewa
|
|
|
2271
2416
|
await daemon.stop();
|
|
2272
2417
|
const started = await daemon.start();
|
|
2273
2418
|
if (started) {
|
|
2274
|
-
console.log(t("gateway.restartedOnPort", { port }));
|
|
2419
|
+
console.log(t("cli.gateway.restartedOnPort", { port }));
|
|
2275
2420
|
} else {
|
|
2276
2421
|
const error = daemon.getLastError();
|
|
2277
|
-
console.error(t("gateway.restartFailed"));
|
|
2422
|
+
console.error(t("cli.gateway.restartFailed"));
|
|
2278
2423
|
if (error) {
|
|
2279
|
-
console.error(t("gateway.error", { error }));
|
|
2424
|
+
console.error(t("cli.gateway.error", { error }));
|
|
2280
2425
|
}
|
|
2281
2426
|
const logPath = join13(homedir12(), ".handsoff", "logs", "gateway.log");
|
|
2282
|
-
console.error(` ${t("gateway.checkLogs", { path: logPath })}`);
|
|
2427
|
+
console.error(` ${t("cli.gateway.checkLogs", { path: logPath })}`);
|
|
2283
2428
|
process.exit(1);
|
|
2284
2429
|
}
|
|
2285
2430
|
})
|
|
@@ -2290,13 +2435,13 @@ var gatewayCommand = new Command6("gateway").description("Manage handsoff Gatewa
|
|
|
2290
2435
|
const response = await fetch(`http://localhost:${config.general.hook_server_port}/status`);
|
|
2291
2436
|
if (response.ok) {
|
|
2292
2437
|
const status = await response.json();
|
|
2293
|
-
console.log(t("gateway.status.title"));
|
|
2294
|
-
console.log(` ${t("gateway.status.running", { value: status.running ?? true })}`);
|
|
2295
|
-
console.log(` ${t("gateway.status.sessions", { count: status.sessions?.length ?? (typeof status.sessions === "number" ? status.sessions : 0) })}`);
|
|
2296
|
-
console.log(` ${t("gateway.status.clients", { count: status.clients ?? 0 })}`);
|
|
2438
|
+
console.log(t("cli.gateway.status.title"));
|
|
2439
|
+
console.log(` ${t("cli.gateway.status.running", { value: status.running ?? true })}`);
|
|
2440
|
+
console.log(` ${t("cli.gateway.status.sessions", { count: status.sessions?.length ?? (typeof status.sessions === "number" ? status.sessions : 0) })}`);
|
|
2441
|
+
console.log(` ${t("cli.gateway.status.clients", { count: status.clients ?? 0 })}`);
|
|
2297
2442
|
if (status.sessions && status.sessions.length > 0) {
|
|
2298
2443
|
console.log(`
|
|
2299
|
-
${t("gateway.status.activeSessions")}`);
|
|
2444
|
+
${t("cli.gateway.status.activeSessions")}`);
|
|
2300
2445
|
for (const session of status.sessions) {
|
|
2301
2446
|
const agentType = session.agentType || "unknown";
|
|
2302
2447
|
const sessionId = session.sessionId || session.id || "unknown";
|
|
@@ -2304,10 +2449,10 @@ var gatewayCommand = new Command6("gateway").description("Manage handsoff Gatewa
|
|
|
2304
2449
|
}
|
|
2305
2450
|
}
|
|
2306
2451
|
} else {
|
|
2307
|
-
console.log(t("gateway.status.notResponding"));
|
|
2452
|
+
console.log(t("cli.gateway.status.notResponding"));
|
|
2308
2453
|
}
|
|
2309
2454
|
} catch {
|
|
2310
|
-
console.log(t("gateway.status.notRunning"));
|
|
2455
|
+
console.log(t("cli.gateway.status.notRunning"));
|
|
2311
2456
|
}
|
|
2312
2457
|
})
|
|
2313
2458
|
);
|
|
@@ -2344,19 +2489,19 @@ var debugCommand = new Command7("debug").description("Debug tools for Handsoff")
|
|
|
2344
2489
|
import { spawn as spawn3 } from "child_process";
|
|
2345
2490
|
import { homedir as homedir13 } from "os";
|
|
2346
2491
|
import { join as join15, dirname as dirname6 } from "path";
|
|
2347
|
-
import { readFileSync as readFileSync7, existsSync as existsSync12, writeFileSync as
|
|
2492
|
+
import { readFileSync as readFileSync7, existsSync as existsSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6 } from "fs";
|
|
2348
2493
|
function getSettingsPath3() {
|
|
2349
2494
|
return join15(homedir13(), ".claude", "settings.json");
|
|
2350
2495
|
}
|
|
2351
2496
|
function backupSettings2(settingsPath) {
|
|
2352
2497
|
const backupPath = settingsPath + ".handsoff-backup";
|
|
2353
2498
|
if (existsSync12(settingsPath)) {
|
|
2354
|
-
|
|
2499
|
+
writeFileSync7(backupPath, readFileSync7(settingsPath, "utf-8"));
|
|
2355
2500
|
}
|
|
2356
2501
|
}
|
|
2357
2502
|
function writeSettings2(settingsPath, settings) {
|
|
2358
2503
|
mkdirSync6(dirname6(settingsPath), { recursive: true });
|
|
2359
|
-
|
|
2504
|
+
writeFileSync7(settingsPath, JSON.stringify(settings, null, 2));
|
|
2360
2505
|
}
|
|
2361
2506
|
function generateHooksConfig3(port, token) {
|
|
2362
2507
|
const unifiedUrl = `http://localhost:${port}/hook/${token}/event`;
|