handsoff 0.0.1-beta.3 → 0.1.1
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 +204 -320
- package/dist/cli/index.js.map +1 -1
- package/dist/gateway/process.js +7 -5
- package/dist/gateway/process.js.map +1 -1
- package/package.json +4 -3
package/dist/cli/index.js
CHANGED
|
@@ -615,15 +615,15 @@ var attachCommand = new Command("attach").description("Attach an agent to the Ga
|
|
|
615
615
|
|
|
616
616
|
// src/cli/init.ts
|
|
617
617
|
import { Command as Command2 } from "commander";
|
|
618
|
-
import { existsSync as existsSync10 } from "fs";
|
|
619
|
-
import { join as join10 } from "path";
|
|
620
|
-
import { homedir as homedir9 } from "os";
|
|
621
|
-
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
622
|
-
|
|
623
|
-
// src/cli/wizard/engine.ts
|
|
624
618
|
import { existsSync as existsSync9 } from "fs";
|
|
625
619
|
import { join as join9 } from "path";
|
|
626
620
|
import { homedir as homedir8 } from "os";
|
|
621
|
+
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
622
|
+
|
|
623
|
+
// src/cli/wizard/engine.ts
|
|
624
|
+
import { existsSync as existsSync8 } from "fs";
|
|
625
|
+
import { join as join8 } from "path";
|
|
626
|
+
import { homedir as homedir7 } from "os";
|
|
627
627
|
|
|
628
628
|
// src/cli/wizard/state.ts
|
|
629
629
|
function createInitialState(hasExistingConfig) {
|
|
@@ -651,7 +651,10 @@ var ConfigApplicator = class {
|
|
|
651
651
|
general: { ...current.general },
|
|
652
652
|
channel: {
|
|
653
653
|
...current.channel,
|
|
654
|
-
|
|
654
|
+
logger: pending.channel?.logger ? { ...current.channel?.logger ?? {}, ...pending.channel.logger } : current.channel?.logger,
|
|
655
|
+
telegram: pending.channel?.telegram ? { ...current.channel?.telegram ?? {}, ...pending.channel.telegram } : current.channel?.telegram,
|
|
656
|
+
feishu: pending.channel?.feishu ? { ...current.channel?.feishu ?? {}, ...pending.channel.feishu } : current.channel?.feishu,
|
|
657
|
+
permission: pending.channel?.permission ? { ...current.channel?.permission ?? {}, ...pending.channel.permission } : current.channel?.permission
|
|
655
658
|
},
|
|
656
659
|
agent: {
|
|
657
660
|
...current.agent,
|
|
@@ -986,6 +989,7 @@ ${STEP} \u2014 Configure Agent
|
|
|
986
989
|
claude: { adapter: "hooks" }
|
|
987
990
|
};
|
|
988
991
|
state.validationResults.set("claude", { ok: true });
|
|
992
|
+
return { action: "next", next: "channel-select" };
|
|
989
993
|
} catch (err) {
|
|
990
994
|
spinner.fail(t("wizard.cli.failed", { error: String(err) }));
|
|
991
995
|
state.itemStatus.set("claude", "error");
|
|
@@ -1043,6 +1047,7 @@ ${STEP} \u2014 Configure Agent
|
|
|
1043
1047
|
state.validationResults.set("codex", { ok: true, message: t("wizard.codex.enabled") });
|
|
1044
1048
|
console.log(pc.green(` ${t("wizard.codex.enabled")}
|
|
1045
1049
|
`));
|
|
1050
|
+
return { action: "next", next: "channel-select" };
|
|
1046
1051
|
}
|
|
1047
1052
|
return { action: "next", next: "cli-menu" };
|
|
1048
1053
|
}
|
|
@@ -1140,15 +1145,18 @@ async function stepCliMenu(state) {
|
|
|
1140
1145
|
console.log(pc2.bold(`
|
|
1141
1146
|
${t("wizard.section.cli")}
|
|
1142
1147
|
`));
|
|
1143
|
-
const
|
|
1144
|
-
let
|
|
1148
|
+
const claudeStatus = state.itemStatus.get("claude") || "unconfigured";
|
|
1149
|
+
let claudeLabel = getChannelStatusLabel(claudeStatus);
|
|
1145
1150
|
if (state.claudeDetection?.tokenMismatch) {
|
|
1146
|
-
|
|
1147
|
-
}
|
|
1148
|
-
const
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1151
|
+
claudeLabel = pc2.yellow("[update needed]");
|
|
1152
|
+
}
|
|
1153
|
+
const codexStatus = state.itemStatus.get("codex") || "not-found";
|
|
1154
|
+
const codexLabel = getChannelStatusLabel(codexStatus, state.validationResults.get("codex")?.message);
|
|
1155
|
+
const cliOptions = [
|
|
1156
|
+
{ name: "claude", status: claudeLabel },
|
|
1157
|
+
{ name: "codex", status: codexLabel }
|
|
1158
|
+
];
|
|
1159
|
+
const choice = await prompts.channelMenuSelect(cliOptions);
|
|
1152
1160
|
if (choice === "__next__") {
|
|
1153
1161
|
return { action: "next", next: "channel-menu" };
|
|
1154
1162
|
}
|
|
@@ -1197,7 +1205,7 @@ ${STEP2} \u2014 Configure Channel
|
|
|
1197
1205
|
continue;
|
|
1198
1206
|
}
|
|
1199
1207
|
state.itemStatus.set(channelName, "unconfigured");
|
|
1200
|
-
return { action: "next", next: "
|
|
1208
|
+
return { action: "next", next: "channel-select" };
|
|
1201
1209
|
}
|
|
1202
1210
|
const msgSpinner = ora2(t("wizard.channel.telegram.sendingTest")).start();
|
|
1203
1211
|
try {
|
|
@@ -1237,7 +1245,7 @@ ${STEP2} \u2014 Configure Channel
|
|
|
1237
1245
|
console.log(pc3.yellow(`! ${t("wizard.channel.feishu.required")}
|
|
1238
1246
|
`));
|
|
1239
1247
|
state.itemStatus.set("feishu", "unconfigured");
|
|
1240
|
-
return { action: "next", next: "
|
|
1248
|
+
return { action: "next", next: "channel-select" };
|
|
1241
1249
|
}
|
|
1242
1250
|
const allowedUsers = allowedUsersInput.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
1243
1251
|
state.pendingChanges.channel = {
|
|
@@ -1303,6 +1311,9 @@ ${STEP3} \u2014 Select Agent
|
|
|
1303
1311
|
if (result.action === "back") {
|
|
1304
1312
|
return { action: "next", next: "pair-continue" };
|
|
1305
1313
|
}
|
|
1314
|
+
if (result.next === "cli-menu") {
|
|
1315
|
+
return { action: "next", next: "agent-select" };
|
|
1316
|
+
}
|
|
1306
1317
|
return { action: "next", next: "channel-select" };
|
|
1307
1318
|
}
|
|
1308
1319
|
|
|
@@ -1324,17 +1335,26 @@ ${STEP4} \u2014 Select Channel
|
|
|
1324
1335
|
`));
|
|
1325
1336
|
const config = loadConfig();
|
|
1326
1337
|
const currentBindings = config.bindings ?? {};
|
|
1338
|
+
const pendingChannels = state.pendingChanges.channel ?? {};
|
|
1339
|
+
const mergedTelegram = pendingChannels.telegram ? { ...config.channel.telegram, ...pendingChannels.telegram } : config.channel.telegram;
|
|
1340
|
+
const mergedFeishu = pendingChannels.feishu ? { ...config.channel.feishu, ...pendingChannels.feishu } : config.channel.feishu;
|
|
1341
|
+
const mergedLogger = pendingChannels.logger ? { ...config.channel.logger, ...pendingChannels.logger } : config.channel.logger;
|
|
1327
1342
|
const channels = [];
|
|
1328
|
-
|
|
1329
|
-
|
|
1343
|
+
const loggerEnabled = mergedLogger?.enabled === true;
|
|
1344
|
+
const loggerInstanceId = "logger:default";
|
|
1345
|
+
const loggerBoundAgent = Object.entries(currentBindings).find(([, v]) => v === loggerInstanceId)?.[0];
|
|
1346
|
+
const loggerStatus = loggerBoundAgent ? pc6.green(`[bound to ${loggerBoundAgent}]`) : loggerEnabled ? pc6.green("[configured]") : pc6.gray("[not configured]");
|
|
1347
|
+
channels.push({ name: "logger", value: "logger", status: loggerStatus });
|
|
1348
|
+
if (mergedTelegram?.enabled && mergedTelegram.bot_token) {
|
|
1349
|
+
const instanceId = getChannelInstanceId("telegram", mergedTelegram.bot_token);
|
|
1330
1350
|
const boundAgent = Object.entries(currentBindings).find(([, v]) => v === instanceId)?.[0];
|
|
1331
1351
|
const status = boundAgent ? pc6.green(`[bound to ${boundAgent}]`) : pc6.green("[configured]");
|
|
1332
1352
|
channels.push({ name: "telegram", value: "telegram", status });
|
|
1333
1353
|
} else {
|
|
1334
1354
|
channels.push({ name: "telegram", value: "telegram", status: pc6.gray("[not configured]") });
|
|
1335
1355
|
}
|
|
1336
|
-
if (
|
|
1337
|
-
const instanceId = getChannelInstanceId("feishu",
|
|
1356
|
+
if (mergedFeishu?.enabled && mergedFeishu.app_id) {
|
|
1357
|
+
const instanceId = getChannelInstanceId("feishu", mergedFeishu.app_id);
|
|
1338
1358
|
const boundAgent = Object.entries(currentBindings).find(([, v]) => v === instanceId)?.[0];
|
|
1339
1359
|
const status = boundAgent ? pc6.green(`[bound to ${boundAgent}]`) : pc6.green("[configured]");
|
|
1340
1360
|
channels.push({ name: "feishu", value: "feishu", status });
|
|
@@ -1350,223 +1370,6 @@ ${STEP4} \u2014 Select Channel
|
|
|
1350
1370
|
return { action: "next", next: "binding-confirm" };
|
|
1351
1371
|
}
|
|
1352
1372
|
|
|
1353
|
-
// src/shared/logger.ts
|
|
1354
|
-
import pino from "pino";
|
|
1355
|
-
import { mkdirSync as mkdirSync5, existsSync as existsSync6, appendFileSync } from "fs";
|
|
1356
|
-
import { dirname as dirname4 } from "path";
|
|
1357
|
-
import { hostname } from "os";
|
|
1358
|
-
var LOG_LEVEL_MAP = {
|
|
1359
|
-
debug: 20,
|
|
1360
|
-
info: 30,
|
|
1361
|
-
warn: 40,
|
|
1362
|
-
error: 50
|
|
1363
|
-
};
|
|
1364
|
-
var fileLogger = null;
|
|
1365
|
-
var consoleLogger = null;
|
|
1366
|
-
var currentLogLevel = "info" /* INFO */;
|
|
1367
|
-
function createSyncLogger(logFilePath, minLevel = "info" /* INFO */) {
|
|
1368
|
-
const hostnameValue = hostname();
|
|
1369
|
-
const writeLog = (level, msg, obj) => {
|
|
1370
|
-
try {
|
|
1371
|
-
const logDir = dirname4(logFilePath);
|
|
1372
|
-
if (!existsSync6(logDir)) {
|
|
1373
|
-
mkdirSync5(logDir, { recursive: true });
|
|
1374
|
-
}
|
|
1375
|
-
const logEntry = {
|
|
1376
|
-
level,
|
|
1377
|
-
time: Date.now(),
|
|
1378
|
-
msg,
|
|
1379
|
-
pid: process.pid,
|
|
1380
|
-
hostname: hostnameValue,
|
|
1381
|
-
...obj
|
|
1382
|
-
};
|
|
1383
|
-
const line = JSON.stringify(logEntry) + "\n";
|
|
1384
|
-
appendFileSync(logFilePath, line);
|
|
1385
|
-
} catch (err) {
|
|
1386
|
-
console.error(`[Logger] Failed to write to ${logFilePath}:`, err);
|
|
1387
|
-
}
|
|
1388
|
-
};
|
|
1389
|
-
const log = (logLevel) => {
|
|
1390
|
-
const minLevelNum = LOG_LEVEL_MAP[minLevel] ?? LOG_LEVEL_MAP.info;
|
|
1391
|
-
const callLevelNum = LOG_LEVEL_MAP[logLevel] ?? LOG_LEVEL_MAP.info;
|
|
1392
|
-
return (obj, msg) => {
|
|
1393
|
-
if (callLevelNum < minLevelNum) return;
|
|
1394
|
-
if (typeof obj === "string") {
|
|
1395
|
-
writeLog(logLevel, obj);
|
|
1396
|
-
} else {
|
|
1397
|
-
writeLog(logLevel, msg || "", obj);
|
|
1398
|
-
}
|
|
1399
|
-
};
|
|
1400
|
-
};
|
|
1401
|
-
const createChild = (_bindings) => {
|
|
1402
|
-
return {
|
|
1403
|
-
info: log("info"),
|
|
1404
|
-
warn: log("warn"),
|
|
1405
|
-
error: log("error"),
|
|
1406
|
-
debug: log("debug"),
|
|
1407
|
-
child: createChild,
|
|
1408
|
-
level: "debug",
|
|
1409
|
-
levels: LOG_LEVEL_MAP
|
|
1410
|
-
};
|
|
1411
|
-
};
|
|
1412
|
-
return {
|
|
1413
|
-
info: log("info"),
|
|
1414
|
-
warn: log("warn"),
|
|
1415
|
-
error: log("error"),
|
|
1416
|
-
debug: log("debug"),
|
|
1417
|
-
child: createChild,
|
|
1418
|
-
level: "debug",
|
|
1419
|
-
levels: LOG_LEVEL_MAP
|
|
1420
|
-
};
|
|
1421
|
-
}
|
|
1422
|
-
function createAgentLogger(agentType) {
|
|
1423
|
-
const homedir16 = process.env.HOME || process.env.USERPROFILE || "";
|
|
1424
|
-
const logFile = `${homedir16}/.handsoff/logs/agents/${agentType}.log`;
|
|
1425
|
-
return createSyncLogger(logFile, currentLogLevel);
|
|
1426
|
-
}
|
|
1427
|
-
function getLogger(_level) {
|
|
1428
|
-
if (!consoleLogger) {
|
|
1429
|
-
const effectiveLevel = _level ?? (fileLogger ? fileLogger.level : "info" /* INFO */);
|
|
1430
|
-
consoleLogger = pino({
|
|
1431
|
-
level: effectiveLevel
|
|
1432
|
-
});
|
|
1433
|
-
}
|
|
1434
|
-
const delegate = (method) => {
|
|
1435
|
-
return (obj, msg) => {
|
|
1436
|
-
const target = fileLogger || consoleLogger;
|
|
1437
|
-
if (target) {
|
|
1438
|
-
target[method](obj, msg);
|
|
1439
|
-
}
|
|
1440
|
-
};
|
|
1441
|
-
};
|
|
1442
|
-
return {
|
|
1443
|
-
info: delegate("info"),
|
|
1444
|
-
warn: delegate("warn"),
|
|
1445
|
-
error: delegate("error"),
|
|
1446
|
-
debug: delegate("debug"),
|
|
1447
|
-
child: () => getLogger(_level),
|
|
1448
|
-
level: "debug",
|
|
1449
|
-
levels: LOG_LEVEL_MAP
|
|
1450
|
-
};
|
|
1451
|
-
}
|
|
1452
|
-
|
|
1453
|
-
// src/core/binding/BindingService.ts
|
|
1454
|
-
import { writeFileSync as writeFileSync5 } from "fs";
|
|
1455
|
-
import { join as join6 } from "path";
|
|
1456
|
-
import { homedir as homedir6 } from "os";
|
|
1457
|
-
import TOML3 from "@iarna/toml";
|
|
1458
|
-
var BindingService = class {
|
|
1459
|
-
logger;
|
|
1460
|
-
/** Cache the config to avoid repeated disk reads. Refreshed on each bind/unbind since we write to disk. */
|
|
1461
|
-
cachedConfig = null;
|
|
1462
|
-
constructor(logger) {
|
|
1463
|
-
this.logger = logger ?? getLogger();
|
|
1464
|
-
}
|
|
1465
|
-
getConfig() {
|
|
1466
|
-
if (!this.cachedConfig) {
|
|
1467
|
-
this.cachedConfig = loadConfig();
|
|
1468
|
-
}
|
|
1469
|
-
return this.cachedConfig;
|
|
1470
|
-
}
|
|
1471
|
-
/** Invalidate cache after writes so the next read picks up fresh data. */
|
|
1472
|
-
invalidateCache() {
|
|
1473
|
-
this.cachedConfig = null;
|
|
1474
|
-
}
|
|
1475
|
-
/**
|
|
1476
|
-
* Get the current bindings config.
|
|
1477
|
-
*/
|
|
1478
|
-
getBindings() {
|
|
1479
|
-
const config = this.getConfig();
|
|
1480
|
-
const bindings = config.bindings;
|
|
1481
|
-
if (!bindings || Object.keys(bindings).length === 0) {
|
|
1482
|
-
return {};
|
|
1483
|
-
}
|
|
1484
|
-
return bindings;
|
|
1485
|
-
}
|
|
1486
|
-
/**
|
|
1487
|
-
* Get the channel instance ID that a given agent is bound to.
|
|
1488
|
-
*/
|
|
1489
|
-
getBoundChannel(agent) {
|
|
1490
|
-
return this.getBindings()[agent];
|
|
1491
|
-
}
|
|
1492
|
-
/**
|
|
1493
|
-
* Get the agent bound to a given channel instance ID.
|
|
1494
|
-
*/
|
|
1495
|
-
getBoundAgent(channelInstanceId) {
|
|
1496
|
-
const bindings = this.getBindings();
|
|
1497
|
-
for (const [agent, channelId] of Object.entries(bindings)) {
|
|
1498
|
-
if (channelId === channelInstanceId) {
|
|
1499
|
-
return agent;
|
|
1500
|
-
}
|
|
1501
|
-
}
|
|
1502
|
-
return void 0;
|
|
1503
|
-
}
|
|
1504
|
-
/**
|
|
1505
|
-
* Bind an agent to a channel instance.
|
|
1506
|
-
* If the agent is already bound to another channel, it will be migrated.
|
|
1507
|
-
* If the target channel is already bound to another agent, that binding is replaced.
|
|
1508
|
-
* Returns the old channel instance ID that the agent was bound to (if any).
|
|
1509
|
-
*/
|
|
1510
|
-
bindAgent(agent, channelInstanceId) {
|
|
1511
|
-
if (!channelInstanceId.includes(":")) {
|
|
1512
|
-
this.logger.error({ channelInstanceId }, "[Binding] Invalid channel instance ID format");
|
|
1513
|
-
return void 0;
|
|
1514
|
-
}
|
|
1515
|
-
const config = this.getConfig();
|
|
1516
|
-
const bindings = { ...config.bindings ?? {} };
|
|
1517
|
-
const oldChannelId = bindings[agent];
|
|
1518
|
-
for (const [a, cid] of Object.entries(bindings)) {
|
|
1519
|
-
if (cid === channelInstanceId && a !== agent) {
|
|
1520
|
-
this.logger.debug({ agent: a, oldChannel: cid }, "[Binding] Removing conflicting binding");
|
|
1521
|
-
delete bindings[a];
|
|
1522
|
-
}
|
|
1523
|
-
}
|
|
1524
|
-
bindings[agent] = channelInstanceId;
|
|
1525
|
-
this.saveBindings(bindings);
|
|
1526
|
-
return oldChannelId;
|
|
1527
|
-
}
|
|
1528
|
-
/**
|
|
1529
|
-
* Unbind an agent from its current channel.
|
|
1530
|
-
* Returns the old channel instance ID (if any).
|
|
1531
|
-
*/
|
|
1532
|
-
unbindAgent(agent) {
|
|
1533
|
-
const config = this.getConfig();
|
|
1534
|
-
const bindings = { ...config.bindings ?? {} };
|
|
1535
|
-
const oldChannelId = bindings[agent];
|
|
1536
|
-
if (!oldChannelId) {
|
|
1537
|
-
return void 0;
|
|
1538
|
-
}
|
|
1539
|
-
delete bindings[agent];
|
|
1540
|
-
this.saveBindings(bindings);
|
|
1541
|
-
this.logger.info({ agent, oldChannelId }, "[Binding] Agent unbound");
|
|
1542
|
-
return oldChannelId;
|
|
1543
|
-
}
|
|
1544
|
-
/**
|
|
1545
|
-
* Check if an agent is configured (i.e., can be bound).
|
|
1546
|
-
* An agent is configurable if it has a non-empty config entry.
|
|
1547
|
-
*/
|
|
1548
|
-
isAgentConfigurable(agent) {
|
|
1549
|
-
const config = this.getConfig();
|
|
1550
|
-
if (agent === "claude") {
|
|
1551
|
-
return !!config.agent.claude;
|
|
1552
|
-
}
|
|
1553
|
-
if (agent === "codex") {
|
|
1554
|
-
return config.agent.codex?.enabled === true;
|
|
1555
|
-
}
|
|
1556
|
-
return false;
|
|
1557
|
-
}
|
|
1558
|
-
saveBindings(bindings) {
|
|
1559
|
-
const config = this.getConfig();
|
|
1560
|
-
const merged = {
|
|
1561
|
-
...config,
|
|
1562
|
-
bindings
|
|
1563
|
-
};
|
|
1564
|
-
const configPath = join6(homedir6(), ".handsoff", "config.toml");
|
|
1565
|
-
writeFileSync5(configPath, TOML3.stringify(merged));
|
|
1566
|
-
this.invalidateCache();
|
|
1567
|
-
}
|
|
1568
|
-
};
|
|
1569
|
-
|
|
1570
1373
|
// src/cli/wizard/steps/binding-confirm.ts
|
|
1571
1374
|
import pc7 from "picocolors";
|
|
1572
1375
|
var STEP5 = "STEP 5";
|
|
@@ -1579,37 +1382,43 @@ ${STEP5} \u2014 Confirm Binding
|
|
|
1579
1382
|
console.log(pc7.gray(`${"\u2500".repeat(50)}
|
|
1580
1383
|
`));
|
|
1581
1384
|
const config = loadConfig();
|
|
1582
|
-
const
|
|
1583
|
-
const
|
|
1385
|
+
const currentBindings = { ...config.bindings ?? {} };
|
|
1386
|
+
for (const [a, cid] of Object.entries(state.bindings)) {
|
|
1387
|
+
if (cid) currentBindings[a] = cid;
|
|
1388
|
+
}
|
|
1389
|
+
const pendingChannel = state.pendingChanges.channel?.[channelName];
|
|
1390
|
+
const telegramConfig = pendingChannel && channelName === "telegram" ? { ...config.channel.telegram, ...pendingChannel } : config.channel.telegram;
|
|
1391
|
+
const feishuConfig = pendingChannel && channelName === "feishu" ? { ...config.channel.feishu, ...pendingChannel } : config.channel.feishu;
|
|
1584
1392
|
const agentCurrentChannel = currentBindings[agentName];
|
|
1585
1393
|
let channelCurrentAgent;
|
|
1586
|
-
|
|
1587
|
-
|
|
1394
|
+
let instanceId;
|
|
1395
|
+
if (channelName === "telegram" && telegramConfig?.bot_token) {
|
|
1396
|
+
instanceId = getChannelInstanceId("telegram", telegramConfig.bot_token);
|
|
1397
|
+
channelCurrentAgent = Object.entries(currentBindings).find(([, v]) => v === instanceId)?.[0];
|
|
1398
|
+
} else if (channelName === "feishu" && feishuConfig?.app_id) {
|
|
1399
|
+
instanceId = getChannelInstanceId("feishu", feishuConfig.app_id);
|
|
1588
1400
|
channelCurrentAgent = Object.entries(currentBindings).find(([, v]) => v === instanceId)?.[0];
|
|
1589
|
-
} else if (channelName === "
|
|
1590
|
-
|
|
1401
|
+
} else if (channelName === "logger") {
|
|
1402
|
+
instanceId = "logger:default";
|
|
1591
1403
|
channelCurrentAgent = Object.entries(currentBindings).find(([, v]) => v === instanceId)?.[0];
|
|
1592
1404
|
}
|
|
1593
1405
|
console.log(` ${pc7.cyan(agentName)} current binding: ${agentCurrentChannel ? pc7.yellow(agentCurrentChannel) : pc7.gray("none")}`);
|
|
1594
1406
|
console.log(` ${pc7.cyan(channelName)} current binding: ${channelCurrentAgent ? pc7.yellow(channelCurrentAgent) : pc7.gray("none")}`);
|
|
1595
1407
|
console.log("");
|
|
1596
1408
|
const choice = await prompts.bindingConfirm(agentName, channelName);
|
|
1597
|
-
if (choice === "confirm") {
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
instanceId
|
|
1601
|
-
|
|
1602
|
-
instanceId = getChannelInstanceId("feishu", config.channel.feishu.app_id);
|
|
1603
|
-
}
|
|
1604
|
-
if (instanceId) {
|
|
1605
|
-
const oldChannelId = bindingService.bindAgent(agentName, instanceId);
|
|
1606
|
-
if (oldChannelId && oldChannelId !== instanceId) {
|
|
1607
|
-
console.log(pc7.yellow(` \u26A0 ${agentName} migrated from ${oldChannelId} to ${instanceId}`));
|
|
1409
|
+
if (choice === "confirm" && instanceId) {
|
|
1410
|
+
const oldChannelId = currentBindings[agentName];
|
|
1411
|
+
for (const [a, cid] of Object.entries(currentBindings)) {
|
|
1412
|
+
if (cid === instanceId && a !== agentName) {
|
|
1413
|
+
delete state.bindings[a];
|
|
1608
1414
|
}
|
|
1609
|
-
state.bindings[agentName] = instanceId;
|
|
1610
|
-
state.sessionConfigured.add(`binding:${agentName}`);
|
|
1611
|
-
console.log(pc7.green(` \u2713 ${agentName} bound to ${channelName}`));
|
|
1612
1415
|
}
|
|
1416
|
+
if (oldChannelId && oldChannelId !== instanceId) {
|
|
1417
|
+
console.log(pc7.yellow(` \u26A0 ${agentName} migrated from ${oldChannelId} to ${instanceId}`));
|
|
1418
|
+
}
|
|
1419
|
+
state.bindings[agentName] = instanceId;
|
|
1420
|
+
state.sessionConfigured.add(`binding:${agentName}`);
|
|
1421
|
+
console.log(pc7.green(` \u2713 ${agentName} bound to ${channelName}`));
|
|
1613
1422
|
} else {
|
|
1614
1423
|
console.log(pc7.gray(` Binding cancelled`));
|
|
1615
1424
|
}
|
|
@@ -1638,14 +1447,14 @@ ${STEP6} \u2014 Add Another Pair?
|
|
|
1638
1447
|
// src/cli/daemon.ts
|
|
1639
1448
|
import { spawn } from "child_process";
|
|
1640
1449
|
import { exec } from "child_process";
|
|
1641
|
-
import { join as
|
|
1450
|
+
import { join as join6, dirname as dirname4 } from "path";
|
|
1642
1451
|
import { fileURLToPath } from "url";
|
|
1643
|
-
import { mkdirSync as
|
|
1452
|
+
import { mkdirSync as mkdirSync5, existsSync as existsSync7 } from "fs";
|
|
1644
1453
|
|
|
1645
1454
|
// src/shared/pidfile.ts
|
|
1646
|
-
import { writeFileSync as
|
|
1455
|
+
import { writeFileSync as writeFileSync5, readFileSync as readFileSync6, existsSync as existsSync6, unlinkSync } from "fs";
|
|
1647
1456
|
function readPidFile(pidFilePath) {
|
|
1648
|
-
if (!
|
|
1457
|
+
if (!existsSync6(pidFilePath)) {
|
|
1649
1458
|
return null;
|
|
1650
1459
|
}
|
|
1651
1460
|
try {
|
|
@@ -1656,7 +1465,7 @@ function readPidFile(pidFilePath) {
|
|
|
1656
1465
|
}
|
|
1657
1466
|
}
|
|
1658
1467
|
function removePidFile(pidFilePath) {
|
|
1659
|
-
if (
|
|
1468
|
+
if (existsSync6(pidFilePath)) {
|
|
1660
1469
|
unlinkSync(pidFilePath);
|
|
1661
1470
|
}
|
|
1662
1471
|
}
|
|
@@ -1717,23 +1526,23 @@ var DaemonManager = class {
|
|
|
1717
1526
|
}
|
|
1718
1527
|
}
|
|
1719
1528
|
try {
|
|
1720
|
-
const
|
|
1721
|
-
const logDir =
|
|
1722
|
-
|
|
1529
|
+
const homedir15 = process.env.HOME || process.env.USERPROFILE || "";
|
|
1530
|
+
const logDir = join6(homedir15, ".handsoff", "logs");
|
|
1531
|
+
mkdirSync5(logDir, { recursive: true });
|
|
1723
1532
|
removePidFile(this.pidFilePath);
|
|
1724
|
-
const __dirname3 =
|
|
1725
|
-
let daemonScript =
|
|
1726
|
-
if (!
|
|
1727
|
-
daemonScript =
|
|
1533
|
+
const __dirname3 = dirname4(fileURLToPath(import.meta.url));
|
|
1534
|
+
let daemonScript = join6(__dirname3, "..", "gateway", "process.js");
|
|
1535
|
+
if (!existsSync7(daemonScript)) {
|
|
1536
|
+
daemonScript = join6(__dirname3, "..", "..", "src", "gateway", "process.ts");
|
|
1728
1537
|
}
|
|
1729
|
-
if (!
|
|
1538
|
+
if (!existsSync7(daemonScript)) {
|
|
1730
1539
|
this.lastError = t("daemon.scriptNotFound", { path: daemonScript });
|
|
1731
1540
|
console.error(this.lastError);
|
|
1732
1541
|
return false;
|
|
1733
1542
|
}
|
|
1734
|
-
const logFile =
|
|
1543
|
+
const logFile = join6(logDir, "gateway.log");
|
|
1735
1544
|
const isDev = daemonScript.endsWith(".ts");
|
|
1736
|
-
const execPath = isDev ?
|
|
1545
|
+
const execPath = isDev ? join6(__dirname3, "..", "..", "node_modules", ".bin", "tsx") : process.execPath;
|
|
1737
1546
|
const child = spawn(execPath, [daemonScript], {
|
|
1738
1547
|
detached: true,
|
|
1739
1548
|
stdio: "ignore",
|
|
@@ -1804,8 +1613,8 @@ var DaemonManager = class {
|
|
|
1804
1613
|
};
|
|
1805
1614
|
|
|
1806
1615
|
// src/cli/wizard/steps/final.ts
|
|
1807
|
-
import { join as
|
|
1808
|
-
import { homedir as
|
|
1616
|
+
import { join as join7 } from "path";
|
|
1617
|
+
import { homedir as homedir6 } from "os";
|
|
1809
1618
|
import pc9 from "picocolors";
|
|
1810
1619
|
import ora3 from "ora";
|
|
1811
1620
|
async function stepFinal(state) {
|
|
@@ -1824,7 +1633,7 @@ ${t("wizard.section.complete")}
|
|
|
1824
1633
|
}
|
|
1825
1634
|
console.log("");
|
|
1826
1635
|
}
|
|
1827
|
-
const pidFilePath =
|
|
1636
|
+
const pidFilePath = join7(homedir6(), ".handsoff", "handsoff.pid");
|
|
1828
1637
|
const port = loadConfig().general.hook_server_port;
|
|
1829
1638
|
const daemon = new DaemonManager(pidFilePath, port);
|
|
1830
1639
|
const hasChanges = state.sessionConfigured.size > 0 || Object.keys(state.pendingChanges).length > 0 || Object.keys(state.bindings).length > 0;
|
|
@@ -1886,8 +1695,8 @@ var WizardEngine = class {
|
|
|
1886
1695
|
state;
|
|
1887
1696
|
applicator;
|
|
1888
1697
|
constructor() {
|
|
1889
|
-
const configPath =
|
|
1890
|
-
const hasExistingConfig =
|
|
1698
|
+
const configPath = join8(homedir7(), ".handsoff", "config.toml");
|
|
1699
|
+
const hasExistingConfig = existsSync8(configPath);
|
|
1891
1700
|
this.state = createInitialState(hasExistingConfig);
|
|
1892
1701
|
this.applicator = new ConfigApplicator();
|
|
1893
1702
|
}
|
|
@@ -1960,8 +1769,8 @@ var WizardEngine = class {
|
|
|
1960
1769
|
|
|
1961
1770
|
// src/cli/init.ts
|
|
1962
1771
|
var initCommand = new Command2("init").description("Initialize handsoff configuration").option("-f, --force", "Force reinitialize (regenerate token)").action(async (options) => {
|
|
1963
|
-
const configPath =
|
|
1964
|
-
if (
|
|
1772
|
+
const configPath = join9(homedir8(), ".handsoff", "config.toml");
|
|
1773
|
+
if (existsSync9(configPath) && !options.force) {
|
|
1965
1774
|
const overwrite = await confirm2({
|
|
1966
1775
|
message: t("cli.init.configExists"),
|
|
1967
1776
|
default: false
|
|
@@ -1984,10 +1793,10 @@ var initCommand = new Command2("init").description("Initialize handsoff configur
|
|
|
1984
1793
|
import { Command as Command3 } from "commander";
|
|
1985
1794
|
import { createReadStream } from "fs";
|
|
1986
1795
|
import { stat, readFile } from "fs/promises";
|
|
1987
|
-
import { homedir as
|
|
1988
|
-
import { join as
|
|
1989
|
-
import { readdirSync, statSync, existsSync as
|
|
1990
|
-
var SESSIONS_DIR =
|
|
1796
|
+
import { homedir as homedir9 } from "os";
|
|
1797
|
+
import { join as join10 } from "path";
|
|
1798
|
+
import { readdirSync, statSync, existsSync as existsSync10 } from "fs";
|
|
1799
|
+
var SESSIONS_DIR = join10(homedir9(), ".handsoff", "sessions");
|
|
1991
1800
|
function formatSize(bytes) {
|
|
1992
1801
|
if (bytes === 0) return "0B";
|
|
1993
1802
|
const units = ["B", "KB", "MB", "GB"];
|
|
@@ -2000,18 +1809,18 @@ function sleep(ms) {
|
|
|
2000
1809
|
}
|
|
2001
1810
|
function listSessions() {
|
|
2002
1811
|
const sessions = [];
|
|
2003
|
-
if (!
|
|
1812
|
+
if (!existsSync10(SESSIONS_DIR)) {
|
|
2004
1813
|
return sessions;
|
|
2005
1814
|
}
|
|
2006
1815
|
const entries = readdirSync(SESSIONS_DIR, { withFileTypes: true });
|
|
2007
1816
|
for (const entry of entries) {
|
|
2008
1817
|
if (entry.isDirectory() && /^\d{4}-\d{2}-\d{2}$/.test(entry.name)) {
|
|
2009
1818
|
const date = entry.name;
|
|
2010
|
-
const dateDir =
|
|
1819
|
+
const dateDir = join10(SESSIONS_DIR, date);
|
|
2011
1820
|
const files = readdirSync(dateDir);
|
|
2012
1821
|
for (const fileName of files) {
|
|
2013
1822
|
if (fileName.endsWith(".jsonl")) {
|
|
2014
|
-
const filePath =
|
|
1823
|
+
const filePath = join10(dateDir, fileName);
|
|
2015
1824
|
const stats = statSync(filePath);
|
|
2016
1825
|
const match = fileName.match(/^([^-]+)-(.+)\.jsonl$/);
|
|
2017
1826
|
if (match) {
|
|
@@ -2077,7 +1886,7 @@ function printEvent(event) {
|
|
|
2077
1886
|
console.log(`[${timeStr}] ${typeStr} ${content}`);
|
|
2078
1887
|
}
|
|
2079
1888
|
async function* readEvents(filePath) {
|
|
2080
|
-
if (!
|
|
1889
|
+
if (!existsSync10(filePath)) {
|
|
2081
1890
|
return;
|
|
2082
1891
|
}
|
|
2083
1892
|
const content = await readFile(filePath, "utf-8");
|
|
@@ -2217,15 +2026,15 @@ var logsCommand = new Command3("logs").description("View session logs").option("
|
|
|
2217
2026
|
|
|
2218
2027
|
// src/cli/status.ts
|
|
2219
2028
|
import { Command as Command4 } from "commander";
|
|
2220
|
-
import { homedir as
|
|
2221
|
-
import { join as
|
|
2222
|
-
import { existsSync as
|
|
2029
|
+
import { homedir as homedir10 } from "os";
|
|
2030
|
+
import { join as join11 } from "path";
|
|
2031
|
+
import { existsSync as existsSync11 } from "fs";
|
|
2223
2032
|
function getSettingsPath2() {
|
|
2224
|
-
return
|
|
2033
|
+
return join11(homedir10(), ".claude", "settings.json");
|
|
2225
2034
|
}
|
|
2226
2035
|
var statusCommand = new Command4("status").description("Show current status").action(() => {
|
|
2227
2036
|
const config = loadConfig();
|
|
2228
|
-
const pidFilePath =
|
|
2037
|
+
const pidFilePath = join11(homedir10(), ".handsoff", "handsoff.pid");
|
|
2229
2038
|
const daemon = new DaemonManager(pidFilePath, config.general.hook_server_port);
|
|
2230
2039
|
console.log(`
|
|
2231
2040
|
${t("status.title")}
|
|
@@ -2240,12 +2049,12 @@ ${t("status.title")}
|
|
|
2240
2049
|
`);
|
|
2241
2050
|
const settingsPath = getSettingsPath2();
|
|
2242
2051
|
const backupPath = settingsPath + ".handsoff-backup";
|
|
2243
|
-
if (
|
|
2052
|
+
if (existsSync11(backupPath)) {
|
|
2244
2053
|
console.log(t("status.settingsBackup"));
|
|
2245
2054
|
} else {
|
|
2246
2055
|
console.log(t("status.settingsNoBackup"));
|
|
2247
2056
|
}
|
|
2248
|
-
const home =
|
|
2057
|
+
const home = homedir10();
|
|
2249
2058
|
console.log(`
|
|
2250
2059
|
${t("status.paths")}`);
|
|
2251
2060
|
console.log(` ${t("status.pathConfig", { path: `${home}/.handsoff/config.toml` })}`);
|
|
@@ -2256,11 +2065,11 @@ ${t("status.paths")}`);
|
|
|
2256
2065
|
|
|
2257
2066
|
// src/cli/stop.ts
|
|
2258
2067
|
import { Command as Command5 } from "commander";
|
|
2259
|
-
import { homedir as
|
|
2260
|
-
import { join as
|
|
2068
|
+
import { homedir as homedir11 } from "os";
|
|
2069
|
+
import { join as join12 } from "path";
|
|
2261
2070
|
var stopCommand = new Command5("stop").description("Stop the handsoff daemon and restore settings").action(async () => {
|
|
2262
2071
|
const config = loadConfig();
|
|
2263
|
-
const pidFilePath =
|
|
2072
|
+
const pidFilePath = join12(homedir11(), ".handsoff", "handsoff.pid");
|
|
2264
2073
|
const daemon = new DaemonManager(pidFilePath, config.general.hook_server_port);
|
|
2265
2074
|
if (!daemon.isRunning()) {
|
|
2266
2075
|
console.log(t("cli.stop.notRunning"));
|
|
@@ -2277,9 +2086,9 @@ var stopCommand = new Command5("stop").description("Stop the handsoff daemon and
|
|
|
2277
2086
|
|
|
2278
2087
|
// src/cli/gateway.ts
|
|
2279
2088
|
import { Command as Command6 } from "commander";
|
|
2280
|
-
import { join as
|
|
2281
|
-
import { homedir as
|
|
2282
|
-
var PID_FILE =
|
|
2089
|
+
import { join as join13 } from "path";
|
|
2090
|
+
import { homedir as homedir12 } from "os";
|
|
2091
|
+
var PID_FILE = join13(homedir12(), ".handsoff", "handsoff.pid");
|
|
2283
2092
|
var gatewayCommand = new Command6("gateway").description("Manage handsoff Gateway daemon").addCommand(
|
|
2284
2093
|
new Command6("start").description("Start or restart the Gateway daemon").option("-p, --port <port>", "Port to listen on").action(async (options) => {
|
|
2285
2094
|
const config = loadConfig();
|
|
@@ -2298,7 +2107,7 @@ var gatewayCommand = new Command6("gateway").description("Manage handsoff Gatewa
|
|
|
2298
2107
|
if (error) {
|
|
2299
2108
|
console.error(t("gateway.error", { error }));
|
|
2300
2109
|
}
|
|
2301
|
-
const logPath =
|
|
2110
|
+
const logPath = join13(homedir12(), ".handsoff", "logs", "gateway.log");
|
|
2302
2111
|
console.error(` ${t("gateway.checkLogs", { path: logPath })}`);
|
|
2303
2112
|
process.exit(1);
|
|
2304
2113
|
}
|
|
@@ -2335,7 +2144,7 @@ var gatewayCommand = new Command6("gateway").description("Manage handsoff Gatewa
|
|
|
2335
2144
|
if (error) {
|
|
2336
2145
|
console.error(t("gateway.error", { error }));
|
|
2337
2146
|
}
|
|
2338
|
-
const logPath =
|
|
2147
|
+
const logPath = join13(homedir12(), ".handsoff", "logs", "gateway.log");
|
|
2339
2148
|
console.error(` ${t("gateway.checkLogs", { path: logPath })}`);
|
|
2340
2149
|
process.exit(1);
|
|
2341
2150
|
}
|
|
@@ -2373,16 +2182,16 @@ var gatewayCommand = new Command6("gateway").description("Manage handsoff Gatewa
|
|
|
2373
2182
|
import { Command as Command7 } from "commander";
|
|
2374
2183
|
import { spawn as spawn2 } from "child_process";
|
|
2375
2184
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2376
|
-
import { dirname as
|
|
2185
|
+
import { dirname as dirname5, join as join14 } from "path";
|
|
2377
2186
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
2378
|
-
var __dirname2 =
|
|
2187
|
+
var __dirname2 = dirname5(__filename2);
|
|
2379
2188
|
var debugCommand = new Command7("debug").description("Debug tools for Handsoff").addCommand(
|
|
2380
2189
|
new Command7("hooks").description("Capture and log raw hook payloads for debugging").option("-p, --port <port>", "Port to listen on", "16792").action(async (options) => {
|
|
2381
2190
|
const port = parseInt(options.port, 10);
|
|
2382
2191
|
console.log(t("cli.debug.starting", { port }));
|
|
2383
2192
|
console.log(t("cli.debug.capturing"));
|
|
2384
2193
|
console.log(t("cli.debug.pressCtrlC"));
|
|
2385
|
-
const captureScript =
|
|
2194
|
+
const captureScript = join14(__dirname2, "hook-capture.js");
|
|
2386
2195
|
const proc = spawn2("node", [captureScript], {
|
|
2387
2196
|
stdio: "inherit",
|
|
2388
2197
|
env: { ...process.env, HANDSOFF_DEBUG_PORT: String(port) }
|
|
@@ -2399,21 +2208,21 @@ var debugCommand = new Command7("debug").description("Debug tools for Handsoff")
|
|
|
2399
2208
|
|
|
2400
2209
|
// src/cli/agent/claude.ts
|
|
2401
2210
|
import { spawn as spawn3 } from "child_process";
|
|
2402
|
-
import { homedir as
|
|
2403
|
-
import { join as
|
|
2404
|
-
import { readFileSync as readFileSync7, existsSync as
|
|
2211
|
+
import { homedir as homedir13 } from "os";
|
|
2212
|
+
import { join as join15, dirname as dirname6 } from "path";
|
|
2213
|
+
import { readFileSync as readFileSync7, existsSync as existsSync12, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "fs";
|
|
2405
2214
|
function getSettingsPath3() {
|
|
2406
|
-
return
|
|
2215
|
+
return join15(homedir13(), ".claude", "settings.json");
|
|
2407
2216
|
}
|
|
2408
2217
|
function backupSettings2(settingsPath) {
|
|
2409
2218
|
const backupPath = settingsPath + ".handsoff-backup";
|
|
2410
|
-
if (
|
|
2411
|
-
|
|
2219
|
+
if (existsSync12(settingsPath)) {
|
|
2220
|
+
writeFileSync6(backupPath, readFileSync7(settingsPath, "utf-8"));
|
|
2412
2221
|
}
|
|
2413
2222
|
}
|
|
2414
2223
|
function writeSettings2(settingsPath, settings) {
|
|
2415
|
-
|
|
2416
|
-
|
|
2224
|
+
mkdirSync6(dirname6(settingsPath), { recursive: true });
|
|
2225
|
+
writeFileSync6(settingsPath, JSON.stringify(settings, null, 2));
|
|
2417
2226
|
}
|
|
2418
2227
|
function generateHooksConfig3(port, token) {
|
|
2419
2228
|
const unifiedUrl = `http://localhost:${port}/hook/${token}/event`;
|
|
@@ -2439,7 +2248,7 @@ function registerClaudeCommand(program2) {
|
|
|
2439
2248
|
program2.command("claude").description("Start Claude Code with handsoff integration").option("-d, --directory <dir>", "Working directory for Claude").action(async (options) => {
|
|
2440
2249
|
const config = loadConfig();
|
|
2441
2250
|
const port = config.general.hook_server_port;
|
|
2442
|
-
const pidFilePath =
|
|
2251
|
+
const pidFilePath = join15(homedir13(), ".handsoff", "handsoff.pid");
|
|
2443
2252
|
const daemon = new DaemonManager(pidFilePath, port);
|
|
2444
2253
|
let daemonStarted = false;
|
|
2445
2254
|
if (daemon.isRunning()) {
|
|
@@ -2457,7 +2266,7 @@ function registerClaudeCommand(program2) {
|
|
|
2457
2266
|
console.log(t("cli.agent.configuring"));
|
|
2458
2267
|
const settingsPath = getSettingsPath3();
|
|
2459
2268
|
const backupPath = settingsPath + ".handsoff-backup";
|
|
2460
|
-
if (!
|
|
2269
|
+
if (!existsSync12(backupPath)) {
|
|
2461
2270
|
backupSettings2(settingsPath);
|
|
2462
2271
|
console.log(t("cli.agent.settingsBacked"));
|
|
2463
2272
|
}
|
|
@@ -2469,7 +2278,7 @@ function registerClaudeCommand(program2) {
|
|
|
2469
2278
|
}
|
|
2470
2279
|
const hooksConfig = generateHooksConfig3(port, token);
|
|
2471
2280
|
let currentSettings = {};
|
|
2472
|
-
if (
|
|
2281
|
+
if (existsSync12(settingsPath)) {
|
|
2473
2282
|
currentSettings = JSON.parse(readFileSync7(settingsPath, "utf-8"));
|
|
2474
2283
|
}
|
|
2475
2284
|
const mergedSettings = mergeHooks(currentSettings, hooksConfig);
|
|
@@ -2498,9 +2307,84 @@ function registerClaudeCommand(program2) {
|
|
|
2498
2307
|
|
|
2499
2308
|
// src/cli/agent/codex.ts
|
|
2500
2309
|
import { spawn as spawn4 } from "child_process";
|
|
2501
|
-
import { homedir as
|
|
2502
|
-
import { join as
|
|
2310
|
+
import { homedir as homedir14 } from "os";
|
|
2311
|
+
import { join as join16 } from "path";
|
|
2503
2312
|
import { execSync as execSync3 } from "child_process";
|
|
2313
|
+
|
|
2314
|
+
// src/shared/logger.ts
|
|
2315
|
+
import pino from "pino";
|
|
2316
|
+
import { mkdirSync as mkdirSync7, existsSync as existsSync13, appendFileSync } from "fs";
|
|
2317
|
+
import { dirname as dirname7 } from "path";
|
|
2318
|
+
import { hostname } from "os";
|
|
2319
|
+
var LOG_LEVEL_MAP = {
|
|
2320
|
+
debug: 20,
|
|
2321
|
+
info: 30,
|
|
2322
|
+
warn: 40,
|
|
2323
|
+
error: 50
|
|
2324
|
+
};
|
|
2325
|
+
var currentLogLevel = "info" /* INFO */;
|
|
2326
|
+
function createSyncLogger(logFilePath, minLevel = "info" /* INFO */) {
|
|
2327
|
+
const hostnameValue = hostname();
|
|
2328
|
+
const writeLog = (level, msg, obj) => {
|
|
2329
|
+
try {
|
|
2330
|
+
const logDir = dirname7(logFilePath);
|
|
2331
|
+
if (!existsSync13(logDir)) {
|
|
2332
|
+
mkdirSync7(logDir, { recursive: true });
|
|
2333
|
+
}
|
|
2334
|
+
const logEntry = {
|
|
2335
|
+
level,
|
|
2336
|
+
time: Date.now(),
|
|
2337
|
+
msg,
|
|
2338
|
+
pid: process.pid,
|
|
2339
|
+
hostname: hostnameValue,
|
|
2340
|
+
...obj
|
|
2341
|
+
};
|
|
2342
|
+
const line = JSON.stringify(logEntry) + "\n";
|
|
2343
|
+
appendFileSync(logFilePath, line);
|
|
2344
|
+
} catch (err) {
|
|
2345
|
+
console.error(`[Logger] Failed to write to ${logFilePath}:`, err);
|
|
2346
|
+
}
|
|
2347
|
+
};
|
|
2348
|
+
const log = (logLevel) => {
|
|
2349
|
+
const minLevelNum = LOG_LEVEL_MAP[minLevel] ?? LOG_LEVEL_MAP.info;
|
|
2350
|
+
const callLevelNum = LOG_LEVEL_MAP[logLevel] ?? LOG_LEVEL_MAP.info;
|
|
2351
|
+
return (obj, msg) => {
|
|
2352
|
+
if (callLevelNum < minLevelNum) return;
|
|
2353
|
+
if (typeof obj === "string") {
|
|
2354
|
+
writeLog(logLevel, obj);
|
|
2355
|
+
} else {
|
|
2356
|
+
writeLog(logLevel, msg || "", obj);
|
|
2357
|
+
}
|
|
2358
|
+
};
|
|
2359
|
+
};
|
|
2360
|
+
const createChild = (_bindings) => {
|
|
2361
|
+
return {
|
|
2362
|
+
info: log("info"),
|
|
2363
|
+
warn: log("warn"),
|
|
2364
|
+
error: log("error"),
|
|
2365
|
+
debug: log("debug"),
|
|
2366
|
+
child: createChild,
|
|
2367
|
+
level: "debug",
|
|
2368
|
+
levels: LOG_LEVEL_MAP
|
|
2369
|
+
};
|
|
2370
|
+
};
|
|
2371
|
+
return {
|
|
2372
|
+
info: log("info"),
|
|
2373
|
+
warn: log("warn"),
|
|
2374
|
+
error: log("error"),
|
|
2375
|
+
debug: log("debug"),
|
|
2376
|
+
child: createChild,
|
|
2377
|
+
level: "debug",
|
|
2378
|
+
levels: LOG_LEVEL_MAP
|
|
2379
|
+
};
|
|
2380
|
+
}
|
|
2381
|
+
function createAgentLogger(agentType) {
|
|
2382
|
+
const homedir15 = process.env.HOME || process.env.USERPROFILE || "";
|
|
2383
|
+
const logFile = `${homedir15}/.handsoff/logs/agents/${agentType}.log`;
|
|
2384
|
+
return createSyncLogger(logFile, currentLogLevel);
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
// src/cli/agent/codex.ts
|
|
2504
2388
|
function isCodexInstalled() {
|
|
2505
2389
|
try {
|
|
2506
2390
|
execSync3("codex --version", { encoding: "utf8", stdio: "pipe", windowsHide: true });
|
|
@@ -2529,7 +2413,7 @@ function registerCodexCommand(program2) {
|
|
|
2529
2413
|
}
|
|
2530
2414
|
const config = loadConfig();
|
|
2531
2415
|
const port = config.general.hook_server_port;
|
|
2532
|
-
const pidFilePath =
|
|
2416
|
+
const pidFilePath = join16(homedir14(), ".handsoff", "handsoff.pid");
|
|
2533
2417
|
const daemon = new DaemonManager(pidFilePath, port);
|
|
2534
2418
|
let daemonStarted = false;
|
|
2535
2419
|
if (daemon.isRunning()) {
|