happy-coder 0.7.1-beta.2 → 0.7.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/README.md +4 -1
- package/bin/happy.mjs +35 -2
- package/dist/index.cjs +502 -434
- package/dist/index.mjs +511 -443
- package/dist/lib.cjs +1 -1
- package/dist/lib.d.cts +2 -2
- package/dist/lib.d.mts +2 -2
- package/dist/lib.mjs +1 -1
- package/dist/{types-CzvFvJwf.cjs → types-B4GgojGc.cjs} +1 -1
- package/dist/{types-BZC9-exR.mjs → types-CnqIfv9n.mjs} +1 -1
- package/package.json +5 -4
- package/bin/happy.cmd +0 -3
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var chalk = require('chalk');
|
|
4
|
-
var types$1 = require('./types-
|
|
4
|
+
var types$1 = require('./types-B4GgojGc.cjs');
|
|
5
5
|
var node_crypto = require('node:crypto');
|
|
6
6
|
var node_child_process = require('node:child_process');
|
|
7
7
|
var node_path = require('node:path');
|
|
@@ -27,11 +27,11 @@ var z = require('zod');
|
|
|
27
27
|
var child_process = require('child_process');
|
|
28
28
|
var util = require('util');
|
|
29
29
|
var crypto = require('crypto');
|
|
30
|
-
var qrcode = require('qrcode-terminal');
|
|
31
|
-
var open = require('open');
|
|
32
30
|
var fastify = require('fastify');
|
|
33
31
|
var fastifyTypeProviderZod = require('fastify-type-provider-zod');
|
|
34
32
|
var os$1 = require('os');
|
|
33
|
+
var qrcode = require('qrcode-terminal');
|
|
34
|
+
var open = require('open');
|
|
35
35
|
var fs = require('fs');
|
|
36
36
|
|
|
37
37
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
@@ -2364,7 +2364,7 @@ async function loop(opts) {
|
|
|
2364
2364
|
}
|
|
2365
2365
|
|
|
2366
2366
|
var name = "happy-coder";
|
|
2367
|
-
var version = "0.7.1
|
|
2367
|
+
var version = "0.7.1";
|
|
2368
2368
|
var description = "Claude Code session sharing CLI";
|
|
2369
2369
|
var author = "Kirill Dubovitskiy";
|
|
2370
2370
|
var license = "MIT";
|
|
@@ -2449,7 +2449,7 @@ var dependencies = {
|
|
|
2449
2449
|
};
|
|
2450
2450
|
var devDependencies = {
|
|
2451
2451
|
"@eslint/compat": "^1",
|
|
2452
|
-
"@types/node": ">=
|
|
2452
|
+
"@types/node": ">=20",
|
|
2453
2453
|
"cross-env": "^10.0.0",
|
|
2454
2454
|
eslint: "^9",
|
|
2455
2455
|
"eslint-config-prettier": "^10",
|
|
@@ -2460,9 +2460,10 @@ var devDependencies = {
|
|
|
2460
2460
|
typescript: "^5",
|
|
2461
2461
|
vitest: "^3.2.4"
|
|
2462
2462
|
};
|
|
2463
|
-
var
|
|
2463
|
+
var resolutions = {
|
|
2464
2464
|
"whatwg-url": "14.2.0"
|
|
2465
2465
|
};
|
|
2466
|
+
var packageManager = "yarn@1.22.22";
|
|
2466
2467
|
var packageJson = {
|
|
2467
2468
|
name: name,
|
|
2468
2469
|
version: version,
|
|
@@ -2482,7 +2483,8 @@ var packageJson = {
|
|
|
2482
2483
|
scripts: scripts,
|
|
2483
2484
|
dependencies: dependencies,
|
|
2484
2485
|
devDependencies: devDependencies,
|
|
2485
|
-
|
|
2486
|
+
resolutions: resolutions,
|
|
2487
|
+
packageManager: packageManager
|
|
2486
2488
|
};
|
|
2487
2489
|
|
|
2488
2490
|
function run(args, options) {
|
|
@@ -3546,6 +3548,16 @@ async function runDoctorCommand() {
|
|
|
3546
3548
|
console.log(`Platform: ${chalk.green(process.platform)} ${process.arch}`);
|
|
3547
3549
|
console.log(`Node.js Version: ${chalk.green(process.version)}`);
|
|
3548
3550
|
console.log("");
|
|
3551
|
+
console.log(chalk.bold("\u{1F527} Daemon Spawn Diagnostics"));
|
|
3552
|
+
const projectRoot = projectPath();
|
|
3553
|
+
const wrapperPath = node_path.join(projectRoot, "bin", "happy.mjs");
|
|
3554
|
+
const cliEntrypoint = node_path.join(projectRoot, "dist", "index.mjs");
|
|
3555
|
+
console.log(`Project Root: ${chalk.blue(projectRoot)}`);
|
|
3556
|
+
console.log(`Wrapper Script: ${chalk.blue(wrapperPath)}`);
|
|
3557
|
+
console.log(`CLI Entrypoint: ${chalk.blue(cliEntrypoint)}`);
|
|
3558
|
+
console.log(`Wrapper Exists: ${node_fs.existsSync(wrapperPath) ? chalk.green("\u2713 Yes") : chalk.red("\u274C No")}`);
|
|
3559
|
+
console.log(`CLI Exists: ${node_fs.existsSync(cliEntrypoint) ? chalk.green("\u2713 Yes") : chalk.red("\u274C No")}`);
|
|
3560
|
+
console.log("");
|
|
3549
3561
|
console.log(chalk.bold("\u2699\uFE0F Configuration"));
|
|
3550
3562
|
console.log(`Happy Home: ${chalk.blue(types$1.configuration.happyHomeDir)}`);
|
|
3551
3563
|
console.log(`Server URL: ${chalk.blue(types$1.configuration.serverUrl)}`);
|
|
@@ -3717,313 +3729,175 @@ var controlClient = /*#__PURE__*/Object.freeze({
|
|
|
3717
3729
|
stopDaemonSession: stopDaemonSession
|
|
3718
3730
|
});
|
|
3719
3731
|
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3732
|
+
function startDaemonControlServer({
|
|
3733
|
+
getChildren,
|
|
3734
|
+
stopSession,
|
|
3735
|
+
spawnSession,
|
|
3736
|
+
requestShutdown,
|
|
3737
|
+
onHappySessionWebhook
|
|
3738
|
+
}) {
|
|
3739
|
+
return new Promise((resolve) => {
|
|
3740
|
+
const app = fastify({
|
|
3741
|
+
logger: false
|
|
3742
|
+
// We use our own logger
|
|
3743
|
+
});
|
|
3744
|
+
app.setValidatorCompiler(fastifyTypeProviderZod.validatorCompiler);
|
|
3745
|
+
app.setSerializerCompiler(fastifyTypeProviderZod.serializerCompiler);
|
|
3746
|
+
const typed = app.withTypeProvider();
|
|
3747
|
+
typed.post("/session-started", {
|
|
3748
|
+
schema: {
|
|
3749
|
+
body: z.z.object({
|
|
3750
|
+
sessionId: z.z.string(),
|
|
3751
|
+
metadata: z.z.any()
|
|
3752
|
+
// Metadata type from API
|
|
3753
|
+
})
|
|
3754
|
+
}
|
|
3755
|
+
}, async (request, reply) => {
|
|
3756
|
+
const { sessionId, metadata } = request.body;
|
|
3757
|
+
types$1.logger.debug(`[CONTROL SERVER] Session started: ${sessionId}`);
|
|
3758
|
+
onHappySessionWebhook(sessionId, metadata);
|
|
3759
|
+
return { status: "ok" };
|
|
3760
|
+
});
|
|
3761
|
+
typed.post("/list", async (request, reply) => {
|
|
3762
|
+
const children = getChildren();
|
|
3763
|
+
types$1.logger.debug(`[CONTROL SERVER] Listing ${children.length} sessions`);
|
|
3764
|
+
return { children };
|
|
3765
|
+
});
|
|
3766
|
+
typed.post("/stop-session", {
|
|
3767
|
+
schema: {
|
|
3768
|
+
body: z.z.object({
|
|
3769
|
+
sessionId: z.z.string()
|
|
3770
|
+
})
|
|
3771
|
+
}
|
|
3772
|
+
}, async (request, reply) => {
|
|
3773
|
+
const { sessionId } = request.body;
|
|
3774
|
+
types$1.logger.debug(`[CONTROL SERVER] Stop session request: ${sessionId}`);
|
|
3775
|
+
const success = stopSession(sessionId);
|
|
3776
|
+
return { success };
|
|
3777
|
+
});
|
|
3778
|
+
typed.post("/spawn-session", {
|
|
3779
|
+
schema: {
|
|
3780
|
+
body: z.z.object({
|
|
3781
|
+
directory: z.z.string(),
|
|
3782
|
+
sessionId: z.z.string().optional()
|
|
3783
|
+
})
|
|
3784
|
+
}
|
|
3785
|
+
}, async (request, reply) => {
|
|
3786
|
+
const { directory, sessionId } = request.body;
|
|
3787
|
+
types$1.logger.debug(`[CONTROL SERVER] Spawn session request: dir=${directory}, sessionId=${sessionId || "new"}`);
|
|
3788
|
+
const session = await spawnSession(directory, sessionId);
|
|
3789
|
+
if (session) {
|
|
3790
|
+
return {
|
|
3791
|
+
success: true,
|
|
3792
|
+
pid: session.pid,
|
|
3793
|
+
sessionId: session.happySessionId || "pending"
|
|
3794
|
+
};
|
|
3795
|
+
} else {
|
|
3796
|
+
reply.code(500);
|
|
3797
|
+
return { error: "Failed to spawn session" };
|
|
3798
|
+
}
|
|
3799
|
+
});
|
|
3800
|
+
typed.post("/stop", async (request, reply) => {
|
|
3801
|
+
types$1.logger.debug("[CONTROL SERVER] Stop daemon request received");
|
|
3802
|
+
setTimeout(() => {
|
|
3803
|
+
types$1.logger.debug("[CONTROL SERVER] Triggering daemon shutdown");
|
|
3804
|
+
requestShutdown();
|
|
3805
|
+
}, 50);
|
|
3806
|
+
return { status: "stopping" };
|
|
3807
|
+
});
|
|
3808
|
+
app.listen({ port: 0, host: "127.0.0.1" }, (err, address) => {
|
|
3809
|
+
if (err) {
|
|
3810
|
+
types$1.logger.debug("[CONTROL SERVER] Failed to start:", err);
|
|
3811
|
+
throw err;
|
|
3812
|
+
}
|
|
3813
|
+
const port = parseInt(address.split(":").pop());
|
|
3814
|
+
types$1.logger.debug(`[CONTROL SERVER] Started on port ${port}`);
|
|
3815
|
+
resolve({
|
|
3816
|
+
port,
|
|
3817
|
+
stop: async () => {
|
|
3818
|
+
types$1.logger.debug("[CONTROL SERVER] Stopping server");
|
|
3819
|
+
await app.close();
|
|
3820
|
+
types$1.logger.debug("[CONTROL SERVER] Server stopped");
|
|
3821
|
+
}
|
|
3822
|
+
});
|
|
3823
|
+
});
|
|
3824
|
+
});
|
|
3825
|
+
}
|
|
3826
|
+
|
|
3827
|
+
function displayQRCode(url) {
|
|
3828
|
+
console.log("=".repeat(80));
|
|
3829
|
+
console.log("\u{1F4F1} To authenticate, scan this QR code with your mobile device:");
|
|
3830
|
+
console.log("=".repeat(80));
|
|
3831
|
+
qrcode.generate(url, { small: true }, (qr) => {
|
|
3832
|
+
for (let l of qr.split("\n")) {
|
|
3833
|
+
console.log(" ".repeat(10) + l);
|
|
3834
|
+
}
|
|
3835
|
+
});
|
|
3836
|
+
console.log("=".repeat(80));
|
|
3837
|
+
}
|
|
3838
|
+
|
|
3839
|
+
function generateWebAuthUrl(publicKey) {
|
|
3840
|
+
const publicKeyBase64 = types$1.encodeBase64(publicKey, "base64url");
|
|
3841
|
+
return `https://app.happy.engineering/terminal/connect#key=${publicKeyBase64}`;
|
|
3842
|
+
}
|
|
3843
|
+
|
|
3844
|
+
async function openBrowser(url) {
|
|
3748
3845
|
try {
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
types$1.logger.debug(`[START] Reported session ${response.id} to daemon`);
|
|
3846
|
+
if (!process.stdout.isTTY || process.env.CI || process.env.HEADLESS) {
|
|
3847
|
+
types$1.logger.debug("[browser] Headless environment detected, skipping browser open");
|
|
3848
|
+
return false;
|
|
3753
3849
|
}
|
|
3850
|
+
types$1.logger.debug(`[browser] Attempting to open URL: ${url}`);
|
|
3851
|
+
await open(url);
|
|
3852
|
+
types$1.logger.debug("[browser] Browser opened successfully");
|
|
3853
|
+
return true;
|
|
3754
3854
|
} catch (error) {
|
|
3755
|
-
types$1.logger.debug("[
|
|
3855
|
+
types$1.logger.debug("[browser] Failed to open browser:", error);
|
|
3856
|
+
return false;
|
|
3756
3857
|
}
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3858
|
+
}
|
|
3859
|
+
|
|
3860
|
+
const AuthSelector = ({ onSelect, onCancel }) => {
|
|
3861
|
+
const [selectedIndex, setSelectedIndex] = React.useState(0);
|
|
3862
|
+
const options = [
|
|
3863
|
+
{
|
|
3864
|
+
method: "mobile",
|
|
3865
|
+
label: "Mobile App"
|
|
3866
|
+
},
|
|
3867
|
+
{
|
|
3868
|
+
method: "web",
|
|
3869
|
+
label: "Web Browser"
|
|
3870
|
+
}
|
|
3871
|
+
];
|
|
3872
|
+
ink.useInput((input, key) => {
|
|
3873
|
+
if (key.upArrow) {
|
|
3874
|
+
setSelectedIndex((prev) => Math.max(0, prev - 1));
|
|
3875
|
+
} else if (key.downArrow) {
|
|
3876
|
+
setSelectedIndex((prev) => Math.min(options.length - 1, prev + 1));
|
|
3877
|
+
} else if (key.return) {
|
|
3878
|
+
onSelect(options[selectedIndex].method);
|
|
3879
|
+
} else if (key.escape || key.ctrl && input === "c") {
|
|
3880
|
+
onCancel();
|
|
3881
|
+
} else if (input === "1") {
|
|
3882
|
+
setSelectedIndex(0);
|
|
3883
|
+
onSelect("mobile");
|
|
3884
|
+
} else if (input === "2") {
|
|
3885
|
+
setSelectedIndex(1);
|
|
3886
|
+
onSelect("web");
|
|
3768
3887
|
}
|
|
3769
3888
|
});
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
const
|
|
3779
|
-
if (
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
const messageQueue = new MessageQueue2((mode) => hashObject(mode));
|
|
3783
|
-
registerHandlers(session);
|
|
3784
|
-
let currentPermissionMode = options.permissionMode;
|
|
3785
|
-
let currentModel = options.model;
|
|
3786
|
-
let currentFallbackModel = void 0;
|
|
3787
|
-
let currentCustomSystemPrompt = void 0;
|
|
3788
|
-
let currentAppendSystemPrompt = void 0;
|
|
3789
|
-
let currentAllowedTools = void 0;
|
|
3790
|
-
let currentDisallowedTools = void 0;
|
|
3791
|
-
session.onUserMessage((message) => {
|
|
3792
|
-
let messagePermissionMode = currentPermissionMode;
|
|
3793
|
-
if (message.meta?.permissionMode) {
|
|
3794
|
-
const validModes = ["default", "acceptEdits", "bypassPermissions", "plan"];
|
|
3795
|
-
if (validModes.includes(message.meta.permissionMode)) {
|
|
3796
|
-
messagePermissionMode = message.meta.permissionMode;
|
|
3797
|
-
currentPermissionMode = messagePermissionMode;
|
|
3798
|
-
types$1.logger.debug(`[loop] Permission mode updated from user message to: ${currentPermissionMode}`);
|
|
3799
|
-
} else {
|
|
3800
|
-
types$1.logger.debug(`[loop] Invalid permission mode received: ${message.meta.permissionMode}`);
|
|
3801
|
-
}
|
|
3802
|
-
} else {
|
|
3803
|
-
types$1.logger.debug(`[loop] User message received with no permission mode override, using current: ${currentPermissionMode}`);
|
|
3804
|
-
}
|
|
3805
|
-
let messageModel = currentModel;
|
|
3806
|
-
if (message.meta?.hasOwnProperty("model")) {
|
|
3807
|
-
messageModel = message.meta.model || void 0;
|
|
3808
|
-
currentModel = messageModel;
|
|
3809
|
-
types$1.logger.debug(`[loop] Model updated from user message: ${messageModel || "reset to default"}`);
|
|
3810
|
-
} else {
|
|
3811
|
-
types$1.logger.debug(`[loop] User message received with no model override, using current: ${currentModel || "default"}`);
|
|
3812
|
-
}
|
|
3813
|
-
let messageCustomSystemPrompt = currentCustomSystemPrompt;
|
|
3814
|
-
if (message.meta?.hasOwnProperty("customSystemPrompt")) {
|
|
3815
|
-
messageCustomSystemPrompt = message.meta.customSystemPrompt || void 0;
|
|
3816
|
-
currentCustomSystemPrompt = messageCustomSystemPrompt;
|
|
3817
|
-
types$1.logger.debug(`[loop] Custom system prompt updated from user message: ${messageCustomSystemPrompt ? "set" : "reset to none"}`);
|
|
3818
|
-
} else {
|
|
3819
|
-
types$1.logger.debug(`[loop] User message received with no custom system prompt override, using current: ${currentCustomSystemPrompt ? "set" : "none"}`);
|
|
3820
|
-
}
|
|
3821
|
-
let messageFallbackModel = currentFallbackModel;
|
|
3822
|
-
if (message.meta?.hasOwnProperty("fallbackModel")) {
|
|
3823
|
-
messageFallbackModel = message.meta.fallbackModel || void 0;
|
|
3824
|
-
currentFallbackModel = messageFallbackModel;
|
|
3825
|
-
types$1.logger.debug(`[loop] Fallback model updated from user message: ${messageFallbackModel || "reset to none"}`);
|
|
3826
|
-
} else {
|
|
3827
|
-
types$1.logger.debug(`[loop] User message received with no fallback model override, using current: ${currentFallbackModel || "none"}`);
|
|
3828
|
-
}
|
|
3829
|
-
let messageAppendSystemPrompt = currentAppendSystemPrompt;
|
|
3830
|
-
if (message.meta?.hasOwnProperty("appendSystemPrompt")) {
|
|
3831
|
-
messageAppendSystemPrompt = message.meta.appendSystemPrompt || void 0;
|
|
3832
|
-
currentAppendSystemPrompt = messageAppendSystemPrompt;
|
|
3833
|
-
types$1.logger.debug(`[loop] Append system prompt updated from user message: ${messageAppendSystemPrompt ? "set" : "reset to none"}`);
|
|
3834
|
-
} else {
|
|
3835
|
-
types$1.logger.debug(`[loop] User message received with no append system prompt override, using current: ${currentAppendSystemPrompt ? "set" : "none"}`);
|
|
3836
|
-
}
|
|
3837
|
-
let messageAllowedTools = currentAllowedTools;
|
|
3838
|
-
if (message.meta?.hasOwnProperty("allowedTools")) {
|
|
3839
|
-
messageAllowedTools = message.meta.allowedTools || void 0;
|
|
3840
|
-
currentAllowedTools = messageAllowedTools;
|
|
3841
|
-
types$1.logger.debug(`[loop] Allowed tools updated from user message: ${messageAllowedTools ? messageAllowedTools.join(", ") : "reset to none"}`);
|
|
3842
|
-
} else {
|
|
3843
|
-
types$1.logger.debug(`[loop] User message received with no allowed tools override, using current: ${currentAllowedTools ? currentAllowedTools.join(", ") : "none"}`);
|
|
3844
|
-
}
|
|
3845
|
-
let messageDisallowedTools = currentDisallowedTools;
|
|
3846
|
-
if (message.meta?.hasOwnProperty("disallowedTools")) {
|
|
3847
|
-
messageDisallowedTools = message.meta.disallowedTools || void 0;
|
|
3848
|
-
currentDisallowedTools = messageDisallowedTools;
|
|
3849
|
-
types$1.logger.debug(`[loop] Disallowed tools updated from user message: ${messageDisallowedTools ? messageDisallowedTools.join(", ") : "reset to none"}`);
|
|
3850
|
-
} else {
|
|
3851
|
-
types$1.logger.debug(`[loop] User message received with no disallowed tools override, using current: ${currentDisallowedTools ? currentDisallowedTools.join(", ") : "none"}`);
|
|
3852
|
-
}
|
|
3853
|
-
const specialCommand = parseSpecialCommand(message.content.text);
|
|
3854
|
-
if (specialCommand.type === "compact") {
|
|
3855
|
-
types$1.logger.debug("[start] Detected /compact command");
|
|
3856
|
-
const enhancedMode2 = {
|
|
3857
|
-
permissionMode: messagePermissionMode || "default",
|
|
3858
|
-
model: messageModel,
|
|
3859
|
-
fallbackModel: messageFallbackModel,
|
|
3860
|
-
customSystemPrompt: messageCustomSystemPrompt,
|
|
3861
|
-
appendSystemPrompt: messageAppendSystemPrompt,
|
|
3862
|
-
allowedTools: messageAllowedTools,
|
|
3863
|
-
disallowedTools: messageDisallowedTools
|
|
3864
|
-
};
|
|
3865
|
-
messageQueue.pushIsolateAndClear(specialCommand.originalMessage || message.content.text, enhancedMode2);
|
|
3866
|
-
types$1.logger.debugLargeJson("[start] /compact command pushed to queue:", message);
|
|
3867
|
-
return;
|
|
3868
|
-
}
|
|
3869
|
-
if (specialCommand.type === "clear") {
|
|
3870
|
-
types$1.logger.debug("[start] Detected /clear command");
|
|
3871
|
-
const enhancedMode2 = {
|
|
3872
|
-
permissionMode: messagePermissionMode || "default",
|
|
3873
|
-
model: messageModel,
|
|
3874
|
-
fallbackModel: messageFallbackModel,
|
|
3875
|
-
customSystemPrompt: messageCustomSystemPrompt,
|
|
3876
|
-
appendSystemPrompt: messageAppendSystemPrompt,
|
|
3877
|
-
allowedTools: messageAllowedTools,
|
|
3878
|
-
disallowedTools: messageDisallowedTools
|
|
3879
|
-
};
|
|
3880
|
-
messageQueue.pushIsolateAndClear(specialCommand.originalMessage || message.content.text, enhancedMode2);
|
|
3881
|
-
types$1.logger.debugLargeJson("[start] /compact command pushed to queue:", message);
|
|
3882
|
-
return;
|
|
3883
|
-
}
|
|
3884
|
-
const enhancedMode = {
|
|
3885
|
-
permissionMode: messagePermissionMode || "default",
|
|
3886
|
-
model: messageModel,
|
|
3887
|
-
fallbackModel: messageFallbackModel,
|
|
3888
|
-
customSystemPrompt: messageCustomSystemPrompt,
|
|
3889
|
-
appendSystemPrompt: messageAppendSystemPrompt,
|
|
3890
|
-
allowedTools: messageAllowedTools,
|
|
3891
|
-
disallowedTools: messageDisallowedTools
|
|
3892
|
-
};
|
|
3893
|
-
messageQueue.push(message.content.text, enhancedMode);
|
|
3894
|
-
types$1.logger.debugLargeJson("User message pushed to queue:", message);
|
|
3895
|
-
});
|
|
3896
|
-
const cleanup = async () => {
|
|
3897
|
-
types$1.logger.debug("[START] Received termination signal, cleaning up...");
|
|
3898
|
-
try {
|
|
3899
|
-
if (session) {
|
|
3900
|
-
session.sendSessionDeath();
|
|
3901
|
-
await session.flush();
|
|
3902
|
-
await session.close();
|
|
3903
|
-
}
|
|
3904
|
-
stopCaffeinate();
|
|
3905
|
-
types$1.logger.debug("[START] Cleanup complete, exiting");
|
|
3906
|
-
process.exit(0);
|
|
3907
|
-
} catch (error) {
|
|
3908
|
-
types$1.logger.debug("[START] Error during cleanup:", error);
|
|
3909
|
-
process.exit(1);
|
|
3910
|
-
}
|
|
3911
|
-
};
|
|
3912
|
-
process.on("SIGTERM", cleanup);
|
|
3913
|
-
process.on("SIGINT", cleanup);
|
|
3914
|
-
process.on("uncaughtException", (error) => {
|
|
3915
|
-
types$1.logger.debug("[START] Uncaught exception:", error);
|
|
3916
|
-
cleanup();
|
|
3917
|
-
});
|
|
3918
|
-
process.on("unhandledRejection", (reason) => {
|
|
3919
|
-
types$1.logger.debug("[START] Unhandled rejection:", reason);
|
|
3920
|
-
cleanup();
|
|
3921
|
-
});
|
|
3922
|
-
await loop({
|
|
3923
|
-
path: workingDirectory,
|
|
3924
|
-
model: options.model,
|
|
3925
|
-
permissionMode: options.permissionMode,
|
|
3926
|
-
startingMode: options.startingMode,
|
|
3927
|
-
messageQueue,
|
|
3928
|
-
api,
|
|
3929
|
-
onModeChange: (newMode) => {
|
|
3930
|
-
session.sendSessionEvent({ type: "switch", mode: newMode });
|
|
3931
|
-
session.updateAgentState((currentState) => ({
|
|
3932
|
-
...currentState,
|
|
3933
|
-
controlledByUser: newMode === "local"
|
|
3934
|
-
}));
|
|
3935
|
-
},
|
|
3936
|
-
onSessionReady: (sessionInstance) => {
|
|
3937
|
-
},
|
|
3938
|
-
mcpServers: {},
|
|
3939
|
-
session,
|
|
3940
|
-
claudeEnvVars: options.claudeEnvVars,
|
|
3941
|
-
claudeArgs: options.claudeArgs
|
|
3942
|
-
});
|
|
3943
|
-
session.sendSessionDeath();
|
|
3944
|
-
types$1.logger.debug("Waiting for socket to flush...");
|
|
3945
|
-
await session.flush();
|
|
3946
|
-
types$1.logger.debug("Closing session...");
|
|
3947
|
-
await session.close();
|
|
3948
|
-
stopCaffeinate();
|
|
3949
|
-
types$1.logger.debug("Stopped sleep prevention");
|
|
3950
|
-
process.exit(0);
|
|
3951
|
-
}
|
|
3952
|
-
|
|
3953
|
-
function displayQRCode(url) {
|
|
3954
|
-
console.log("=".repeat(80));
|
|
3955
|
-
console.log("\u{1F4F1} To authenticate, scan this QR code with your mobile device:");
|
|
3956
|
-
console.log("=".repeat(80));
|
|
3957
|
-
qrcode.generate(url, { small: true }, (qr) => {
|
|
3958
|
-
for (let l of qr.split("\n")) {
|
|
3959
|
-
console.log(" ".repeat(10) + l);
|
|
3960
|
-
}
|
|
3961
|
-
});
|
|
3962
|
-
console.log("=".repeat(80));
|
|
3963
|
-
}
|
|
3964
|
-
|
|
3965
|
-
function generateWebAuthUrl(publicKey) {
|
|
3966
|
-
const publicKeyBase64 = types$1.encodeBase64(publicKey, "base64url");
|
|
3967
|
-
return `https://app.happy.engineering/terminal/connect#key=${publicKeyBase64}`;
|
|
3968
|
-
}
|
|
3969
|
-
|
|
3970
|
-
async function openBrowser(url) {
|
|
3971
|
-
try {
|
|
3972
|
-
if (!process.stdout.isTTY || process.env.CI || process.env.HEADLESS) {
|
|
3973
|
-
types$1.logger.debug("[browser] Headless environment detected, skipping browser open");
|
|
3974
|
-
return false;
|
|
3975
|
-
}
|
|
3976
|
-
types$1.logger.debug(`[browser] Attempting to open URL: ${url}`);
|
|
3977
|
-
await open(url);
|
|
3978
|
-
types$1.logger.debug("[browser] Browser opened successfully");
|
|
3979
|
-
return true;
|
|
3980
|
-
} catch (error) {
|
|
3981
|
-
types$1.logger.debug("[browser] Failed to open browser:", error);
|
|
3982
|
-
return false;
|
|
3983
|
-
}
|
|
3984
|
-
}
|
|
3985
|
-
|
|
3986
|
-
const AuthSelector = ({ onSelect, onCancel }) => {
|
|
3987
|
-
const [selectedIndex, setSelectedIndex] = React.useState(0);
|
|
3988
|
-
const options = [
|
|
3989
|
-
{
|
|
3990
|
-
method: "mobile",
|
|
3991
|
-
label: "Mobile App"
|
|
3992
|
-
},
|
|
3993
|
-
{
|
|
3994
|
-
method: "web",
|
|
3995
|
-
label: "Web Browser"
|
|
3996
|
-
}
|
|
3997
|
-
];
|
|
3998
|
-
ink.useInput((input, key) => {
|
|
3999
|
-
if (key.upArrow) {
|
|
4000
|
-
setSelectedIndex((prev) => Math.max(0, prev - 1));
|
|
4001
|
-
} else if (key.downArrow) {
|
|
4002
|
-
setSelectedIndex((prev) => Math.min(options.length - 1, prev + 1));
|
|
4003
|
-
} else if (key.return) {
|
|
4004
|
-
onSelect(options[selectedIndex].method);
|
|
4005
|
-
} else if (key.escape || key.ctrl && input === "c") {
|
|
4006
|
-
onCancel();
|
|
4007
|
-
} else if (input === "1") {
|
|
4008
|
-
setSelectedIndex(0);
|
|
4009
|
-
onSelect("mobile");
|
|
4010
|
-
} else if (input === "2") {
|
|
4011
|
-
setSelectedIndex(1);
|
|
4012
|
-
onSelect("web");
|
|
4013
|
-
}
|
|
4014
|
-
});
|
|
4015
|
-
return /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", paddingY: 1 }, /* @__PURE__ */ React.createElement(ink.Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(ink.Text, null, "How would you like to authenticate?")), /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column" }, options.map((option, index) => {
|
|
4016
|
-
const isSelected = selectedIndex === index;
|
|
4017
|
-
return /* @__PURE__ */ React.createElement(ink.Box, { key: option.method, marginY: 0 }, /* @__PURE__ */ React.createElement(ink.Text, { color: isSelected ? "cyan" : "gray" }, isSelected ? "\u203A " : " ", index + 1, ". ", option.label));
|
|
4018
|
-
})), /* @__PURE__ */ React.createElement(ink.Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(ink.Text, { dimColor: true }, "Use arrows or 1-2 to select, Enter to confirm")));
|
|
4019
|
-
};
|
|
4020
|
-
|
|
4021
|
-
async function doAuth() {
|
|
4022
|
-
console.clear();
|
|
4023
|
-
const authMethod = await selectAuthenticationMethod();
|
|
4024
|
-
if (!authMethod) {
|
|
4025
|
-
console.log("\nAuthentication cancelled.\n");
|
|
4026
|
-
return null;
|
|
3889
|
+
return /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", paddingY: 1 }, /* @__PURE__ */ React.createElement(ink.Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(ink.Text, null, "How would you like to authenticate?")), /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column" }, options.map((option, index) => {
|
|
3890
|
+
const isSelected = selectedIndex === index;
|
|
3891
|
+
return /* @__PURE__ */ React.createElement(ink.Box, { key: option.method, marginY: 0 }, /* @__PURE__ */ React.createElement(ink.Text, { color: isSelected ? "cyan" : "gray" }, isSelected ? "\u203A " : " ", index + 1, ". ", option.label));
|
|
3892
|
+
})), /* @__PURE__ */ React.createElement(ink.Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(ink.Text, { dimColor: true }, "Use arrows or 1-2 to select, Enter to confirm")));
|
|
3893
|
+
};
|
|
3894
|
+
|
|
3895
|
+
async function doAuth() {
|
|
3896
|
+
console.clear();
|
|
3897
|
+
const authMethod = await selectAuthenticationMethod();
|
|
3898
|
+
if (!authMethod) {
|
|
3899
|
+
console.log("\nAuthentication cancelled.\n");
|
|
3900
|
+
return null;
|
|
4027
3901
|
}
|
|
4028
3902
|
const secret = new Uint8Array(node_crypto.randomBytes(32));
|
|
4029
3903
|
const keypair = tweetnacl.box.keyPair.fromSecretKey(secret);
|
|
@@ -4167,6 +4041,8 @@ async function authAndSetupMachineIfNeeded() {
|
|
|
4167
4041
|
}
|
|
4168
4042
|
const settings = await updateSettings(async (s) => {
|
|
4169
4043
|
if (!s.machineId) {
|
|
4044
|
+
const newMachineId = node_crypto.randomUUID();
|
|
4045
|
+
console.log(`[AUTH] No machine ID found, generating new one: ${newMachineId}; We will not create machine on startup since we don't have api client intialized`);
|
|
4170
4046
|
return {
|
|
4171
4047
|
...s,
|
|
4172
4048
|
machineId: node_crypto.randomUUID()
|
|
@@ -4178,101 +4054,33 @@ async function authAndSetupMachineIfNeeded() {
|
|
|
4178
4054
|
return { credentials, machineId: settings.machineId };
|
|
4179
4055
|
}
|
|
4180
4056
|
|
|
4181
|
-
function
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
}
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
sessionId: z.z.string(),
|
|
4200
|
-
metadata: z.z.any()
|
|
4201
|
-
// Metadata type from API
|
|
4202
|
-
})
|
|
4203
|
-
}
|
|
4204
|
-
}, async (request, reply) => {
|
|
4205
|
-
const { sessionId, metadata } = request.body;
|
|
4206
|
-
types$1.logger.debug(`[CONTROL SERVER] Session started: ${sessionId}`);
|
|
4207
|
-
onHappySessionWebhook(sessionId, metadata);
|
|
4208
|
-
return { status: "ok" };
|
|
4209
|
-
});
|
|
4210
|
-
typed.post("/list", async (request, reply) => {
|
|
4211
|
-
const children = getChildren();
|
|
4212
|
-
types$1.logger.debug(`[CONTROL SERVER] Listing ${children.length} sessions`);
|
|
4213
|
-
return { children };
|
|
4214
|
-
});
|
|
4215
|
-
typed.post("/stop-session", {
|
|
4216
|
-
schema: {
|
|
4217
|
-
body: z.z.object({
|
|
4218
|
-
sessionId: z.z.string()
|
|
4219
|
-
})
|
|
4220
|
-
}
|
|
4221
|
-
}, async (request, reply) => {
|
|
4222
|
-
const { sessionId } = request.body;
|
|
4223
|
-
types$1.logger.debug(`[CONTROL SERVER] Stop session request: ${sessionId}`);
|
|
4224
|
-
const success = stopSession(sessionId);
|
|
4225
|
-
return { success };
|
|
4226
|
-
});
|
|
4227
|
-
typed.post("/spawn-session", {
|
|
4228
|
-
schema: {
|
|
4229
|
-
body: z.z.object({
|
|
4230
|
-
directory: z.z.string(),
|
|
4231
|
-
sessionId: z.z.string().optional()
|
|
4232
|
-
})
|
|
4233
|
-
}
|
|
4234
|
-
}, async (request, reply) => {
|
|
4235
|
-
const { directory, sessionId } = request.body;
|
|
4236
|
-
types$1.logger.debug(`[CONTROL SERVER] Spawn session request: dir=${directory}, sessionId=${sessionId || "new"}`);
|
|
4237
|
-
const session = await spawnSession(directory, sessionId);
|
|
4238
|
-
if (session) {
|
|
4239
|
-
return {
|
|
4240
|
-
success: true,
|
|
4241
|
-
pid: session.pid,
|
|
4242
|
-
sessionId: session.happySessionId || "pending"
|
|
4243
|
-
};
|
|
4244
|
-
} else {
|
|
4245
|
-
reply.code(500);
|
|
4246
|
-
return { error: "Failed to spawn session" };
|
|
4247
|
-
}
|
|
4248
|
-
});
|
|
4249
|
-
typed.post("/stop", async (request, reply) => {
|
|
4250
|
-
types$1.logger.debug("[CONTROL SERVER] Stop daemon request received");
|
|
4251
|
-
setTimeout(() => {
|
|
4252
|
-
types$1.logger.debug("[CONTROL SERVER] Triggering daemon shutdown");
|
|
4253
|
-
requestShutdown();
|
|
4254
|
-
}, 50);
|
|
4255
|
-
return { status: "stopping" };
|
|
4256
|
-
});
|
|
4257
|
-
app.listen({ port: 0, host: "127.0.0.1" }, (err, address) => {
|
|
4258
|
-
if (err) {
|
|
4259
|
-
types$1.logger.debug("[CONTROL SERVER] Failed to start:", err);
|
|
4260
|
-
throw err;
|
|
4261
|
-
}
|
|
4262
|
-
const port = parseInt(address.split(":").pop());
|
|
4263
|
-
types$1.logger.debug(`[CONTROL SERVER] Started on port ${port}`);
|
|
4264
|
-
resolve({
|
|
4265
|
-
port,
|
|
4266
|
-
stop: async () => {
|
|
4267
|
-
types$1.logger.debug("[CONTROL SERVER] Stopping server");
|
|
4268
|
-
await app.close();
|
|
4269
|
-
types$1.logger.debug("[CONTROL SERVER] Server stopped");
|
|
4270
|
-
}
|
|
4271
|
-
});
|
|
4272
|
-
});
|
|
4273
|
-
});
|
|
4057
|
+
function spawnHappyCLI(args, options = {}) {
|
|
4058
|
+
const projectRoot = projectPath();
|
|
4059
|
+
const entrypoint = node_path.join(projectRoot, "dist", "index.mjs");
|
|
4060
|
+
let directory;
|
|
4061
|
+
if ("cwd" in options) {
|
|
4062
|
+
directory = options.cwd;
|
|
4063
|
+
} else {
|
|
4064
|
+
directory = process.cwd();
|
|
4065
|
+
}
|
|
4066
|
+
const fullCommand = `happy ${args.join(" ")}`;
|
|
4067
|
+
types$1.logger.debug(`[DAEMON RUN] Spawning: ${fullCommand} in ${directory}`);
|
|
4068
|
+
const nodeArgs = [
|
|
4069
|
+
"--no-warnings",
|
|
4070
|
+
"--no-deprecation",
|
|
4071
|
+
entrypoint,
|
|
4072
|
+
...args
|
|
4073
|
+
];
|
|
4074
|
+
return child_process.spawn("node", nodeArgs, options);
|
|
4274
4075
|
}
|
|
4275
4076
|
|
|
4077
|
+
const initialMachineMetadata = {
|
|
4078
|
+
host: os$1.hostname(),
|
|
4079
|
+
platform: os$1.platform(),
|
|
4080
|
+
happyCliVersion: packageJson.version,
|
|
4081
|
+
homeDir: os$1.homedir(),
|
|
4082
|
+
happyHomeDir: types$1.configuration.happyHomeDir
|
|
4083
|
+
};
|
|
4276
4084
|
async function startDaemon() {
|
|
4277
4085
|
types$1.logger.debug("[DAEMON RUN] Starting daemon process...");
|
|
4278
4086
|
types$1.logger.debugLargeJson("[DAEMON RUN] Environment", getEnvironmentInfo());
|
|
@@ -4281,6 +4089,7 @@ async function startDaemon() {
|
|
|
4281
4089
|
try {
|
|
4282
4090
|
process.kill(runningDaemon.pid, 0);
|
|
4283
4091
|
types$1.logger.debug("[DAEMON RUN] Daemon already running");
|
|
4092
|
+
console.log(`Daemon already running (PID: ${runningDaemon.pid})`);
|
|
4284
4093
|
process.exit(0);
|
|
4285
4094
|
} catch {
|
|
4286
4095
|
types$1.logger.debug("[DAEMON RUN] Stale state found, cleaning up");
|
|
@@ -4335,9 +4144,7 @@ async function startDaemon() {
|
|
|
4335
4144
|
"--started-by",
|
|
4336
4145
|
"daemon"
|
|
4337
4146
|
];
|
|
4338
|
-
const
|
|
4339
|
-
types$1.logger.debug(`[DAEMON RUN] Spawning: ${fullCommand} in ${directory}`);
|
|
4340
|
-
const happyProcess = child_process.spawn(happyBinPath, args, {
|
|
4147
|
+
const happyProcess = spawnHappyCLI(args, {
|
|
4341
4148
|
cwd: directory,
|
|
4342
4149
|
detached: true,
|
|
4343
4150
|
// Sessions stay alive when daemon stops
|
|
@@ -4442,13 +4249,6 @@ async function startDaemon() {
|
|
|
4442
4249
|
};
|
|
4443
4250
|
await writeDaemonState(fileState);
|
|
4444
4251
|
types$1.logger.debug("[DAEMON RUN] Daemon state written");
|
|
4445
|
-
const initialMetadata = {
|
|
4446
|
-
host: os$1.hostname(),
|
|
4447
|
-
platform: os$1.platform(),
|
|
4448
|
-
happyCliVersion: packageJson.version,
|
|
4449
|
-
homeDir: os$1.homedir(),
|
|
4450
|
-
happyHomeDir: types$1.configuration.happyHomeDir
|
|
4451
|
-
};
|
|
4452
4252
|
const initialDaemonState = {
|
|
4453
4253
|
status: "offline",
|
|
4454
4254
|
pid: process.pid,
|
|
@@ -4456,9 +4256,9 @@ async function startDaemon() {
|
|
|
4456
4256
|
startedAt: Date.now()
|
|
4457
4257
|
};
|
|
4458
4258
|
const api = new types$1.ApiClient(credentials.token, credentials.secret);
|
|
4459
|
-
const machine = await api.
|
|
4259
|
+
const machine = await api.createMachineOrGetExistingAsIs({
|
|
4460
4260
|
machineId,
|
|
4461
|
-
metadata:
|
|
4261
|
+
metadata: initialMachineMetadata,
|
|
4462
4262
|
daemonState: initialDaemonState
|
|
4463
4263
|
});
|
|
4464
4264
|
types$1.logger.debug(`[DAEMON RUN] Machine registered: ${machine.id}`);
|
|
@@ -4523,6 +4323,247 @@ async function startDaemon() {
|
|
|
4523
4323
|
}
|
|
4524
4324
|
}
|
|
4525
4325
|
|
|
4326
|
+
async function start(credentials, options = {}) {
|
|
4327
|
+
const workingDirectory = process.cwd();
|
|
4328
|
+
const sessionTag = node_crypto.randomUUID();
|
|
4329
|
+
types$1.logger.debugLargeJson("[START] Happy process started", getEnvironmentInfo());
|
|
4330
|
+
types$1.logger.debug(`[START] Options: startedBy=${options.startedBy}, startingMode=${options.startingMode}`);
|
|
4331
|
+
if (options.startedBy === "daemon" && options.startingMode === "local") {
|
|
4332
|
+
types$1.logger.debug("Daemon spawn requested with local mode - forcing remote mode");
|
|
4333
|
+
options.startingMode = "remote";
|
|
4334
|
+
}
|
|
4335
|
+
const api = new types$1.ApiClient(credentials.token, credentials.secret);
|
|
4336
|
+
let state = {};
|
|
4337
|
+
const settings = await readSettings();
|
|
4338
|
+
let machineId = settings?.machineId;
|
|
4339
|
+
if (!machineId) {
|
|
4340
|
+
console.error(`[START] No machine ID found in settings, which is unexepcted since authAndSetupMachineIfNeeded should have created it, using 'unknown' id instead`);
|
|
4341
|
+
machineId = "unknown";
|
|
4342
|
+
}
|
|
4343
|
+
types$1.logger.debug(`Using machineId: ${machineId}`);
|
|
4344
|
+
let metadata = {
|
|
4345
|
+
path: workingDirectory,
|
|
4346
|
+
host: os.hostname(),
|
|
4347
|
+
version: packageJson.version,
|
|
4348
|
+
os: os.platform(),
|
|
4349
|
+
machineId,
|
|
4350
|
+
homeDir: os.homedir(),
|
|
4351
|
+
happyHomeDir: types$1.configuration.happyHomeDir,
|
|
4352
|
+
startedFromDaemon: options.startedBy === "daemon",
|
|
4353
|
+
hostPid: process.pid,
|
|
4354
|
+
startedBy: options.startedBy || "terminal"
|
|
4355
|
+
};
|
|
4356
|
+
const response = await api.getOrCreateSession({ tag: sessionTag, metadata, state });
|
|
4357
|
+
types$1.logger.debug(`Session created: ${response.id}`);
|
|
4358
|
+
await api.createMachineOrGetExistingAsIs({
|
|
4359
|
+
machineId,
|
|
4360
|
+
metadata: initialMachineMetadata
|
|
4361
|
+
});
|
|
4362
|
+
try {
|
|
4363
|
+
const daemonState = await getDaemonState();
|
|
4364
|
+
if (daemonState?.httpPort) {
|
|
4365
|
+
await notifyDaemonSessionStarted(response.id, metadata);
|
|
4366
|
+
types$1.logger.debug(`[START] Reported session ${response.id} to daemon`);
|
|
4367
|
+
}
|
|
4368
|
+
} catch (error) {
|
|
4369
|
+
types$1.logger.debug("[START] Failed to report to daemon (may not be running):", error);
|
|
4370
|
+
}
|
|
4371
|
+
extractSDKMetadataAsync(async (sdkMetadata) => {
|
|
4372
|
+
types$1.logger.debug("[start] SDK metadata extracted, updating session:", sdkMetadata);
|
|
4373
|
+
try {
|
|
4374
|
+
api.sessionSyncClient(response).updateMetadata((currentMetadata) => ({
|
|
4375
|
+
...currentMetadata,
|
|
4376
|
+
tools: sdkMetadata.tools,
|
|
4377
|
+
slashCommands: sdkMetadata.slashCommands
|
|
4378
|
+
}));
|
|
4379
|
+
types$1.logger.debug("[start] Session metadata updated with SDK capabilities");
|
|
4380
|
+
} catch (error) {
|
|
4381
|
+
types$1.logger.debug("[start] Failed to update session metadata:", error);
|
|
4382
|
+
}
|
|
4383
|
+
});
|
|
4384
|
+
const session = api.sessionSyncClient(response);
|
|
4385
|
+
const logPath = await types$1.logger.logFilePathPromise;
|
|
4386
|
+
types$1.logger.infoDeveloper(`Session: ${response.id}`);
|
|
4387
|
+
types$1.logger.infoDeveloper(`Logs: ${logPath}`);
|
|
4388
|
+
session.updateAgentState((currentState) => ({
|
|
4389
|
+
...currentState,
|
|
4390
|
+
controlledByUser: options.startingMode !== "remote"
|
|
4391
|
+
}));
|
|
4392
|
+
const caffeinateStarted = startCaffeinate();
|
|
4393
|
+
if (caffeinateStarted) {
|
|
4394
|
+
types$1.logger.infoDeveloper("Sleep prevention enabled (macOS)");
|
|
4395
|
+
}
|
|
4396
|
+
const messageQueue = new MessageQueue2((mode) => hashObject(mode));
|
|
4397
|
+
registerHandlers(session);
|
|
4398
|
+
let currentPermissionMode = options.permissionMode;
|
|
4399
|
+
let currentModel = options.model;
|
|
4400
|
+
let currentFallbackModel = void 0;
|
|
4401
|
+
let currentCustomSystemPrompt = void 0;
|
|
4402
|
+
let currentAppendSystemPrompt = void 0;
|
|
4403
|
+
let currentAllowedTools = void 0;
|
|
4404
|
+
let currentDisallowedTools = void 0;
|
|
4405
|
+
session.onUserMessage((message) => {
|
|
4406
|
+
let messagePermissionMode = currentPermissionMode;
|
|
4407
|
+
if (message.meta?.permissionMode) {
|
|
4408
|
+
const validModes = ["default", "acceptEdits", "bypassPermissions", "plan"];
|
|
4409
|
+
if (validModes.includes(message.meta.permissionMode)) {
|
|
4410
|
+
messagePermissionMode = message.meta.permissionMode;
|
|
4411
|
+
currentPermissionMode = messagePermissionMode;
|
|
4412
|
+
types$1.logger.debug(`[loop] Permission mode updated from user message to: ${currentPermissionMode}`);
|
|
4413
|
+
} else {
|
|
4414
|
+
types$1.logger.debug(`[loop] Invalid permission mode received: ${message.meta.permissionMode}`);
|
|
4415
|
+
}
|
|
4416
|
+
} else {
|
|
4417
|
+
types$1.logger.debug(`[loop] User message received with no permission mode override, using current: ${currentPermissionMode}`);
|
|
4418
|
+
}
|
|
4419
|
+
let messageModel = currentModel;
|
|
4420
|
+
if (message.meta?.hasOwnProperty("model")) {
|
|
4421
|
+
messageModel = message.meta.model || void 0;
|
|
4422
|
+
currentModel = messageModel;
|
|
4423
|
+
types$1.logger.debug(`[loop] Model updated from user message: ${messageModel || "reset to default"}`);
|
|
4424
|
+
} else {
|
|
4425
|
+
types$1.logger.debug(`[loop] User message received with no model override, using current: ${currentModel || "default"}`);
|
|
4426
|
+
}
|
|
4427
|
+
let messageCustomSystemPrompt = currentCustomSystemPrompt;
|
|
4428
|
+
if (message.meta?.hasOwnProperty("customSystemPrompt")) {
|
|
4429
|
+
messageCustomSystemPrompt = message.meta.customSystemPrompt || void 0;
|
|
4430
|
+
currentCustomSystemPrompt = messageCustomSystemPrompt;
|
|
4431
|
+
types$1.logger.debug(`[loop] Custom system prompt updated from user message: ${messageCustomSystemPrompt ? "set" : "reset to none"}`);
|
|
4432
|
+
} else {
|
|
4433
|
+
types$1.logger.debug(`[loop] User message received with no custom system prompt override, using current: ${currentCustomSystemPrompt ? "set" : "none"}`);
|
|
4434
|
+
}
|
|
4435
|
+
let messageFallbackModel = currentFallbackModel;
|
|
4436
|
+
if (message.meta?.hasOwnProperty("fallbackModel")) {
|
|
4437
|
+
messageFallbackModel = message.meta.fallbackModel || void 0;
|
|
4438
|
+
currentFallbackModel = messageFallbackModel;
|
|
4439
|
+
types$1.logger.debug(`[loop] Fallback model updated from user message: ${messageFallbackModel || "reset to none"}`);
|
|
4440
|
+
} else {
|
|
4441
|
+
types$1.logger.debug(`[loop] User message received with no fallback model override, using current: ${currentFallbackModel || "none"}`);
|
|
4442
|
+
}
|
|
4443
|
+
let messageAppendSystemPrompt = currentAppendSystemPrompt;
|
|
4444
|
+
if (message.meta?.hasOwnProperty("appendSystemPrompt")) {
|
|
4445
|
+
messageAppendSystemPrompt = message.meta.appendSystemPrompt || void 0;
|
|
4446
|
+
currentAppendSystemPrompt = messageAppendSystemPrompt;
|
|
4447
|
+
types$1.logger.debug(`[loop] Append system prompt updated from user message: ${messageAppendSystemPrompt ? "set" : "reset to none"}`);
|
|
4448
|
+
} else {
|
|
4449
|
+
types$1.logger.debug(`[loop] User message received with no append system prompt override, using current: ${currentAppendSystemPrompt ? "set" : "none"}`);
|
|
4450
|
+
}
|
|
4451
|
+
let messageAllowedTools = currentAllowedTools;
|
|
4452
|
+
if (message.meta?.hasOwnProperty("allowedTools")) {
|
|
4453
|
+
messageAllowedTools = message.meta.allowedTools || void 0;
|
|
4454
|
+
currentAllowedTools = messageAllowedTools;
|
|
4455
|
+
types$1.logger.debug(`[loop] Allowed tools updated from user message: ${messageAllowedTools ? messageAllowedTools.join(", ") : "reset to none"}`);
|
|
4456
|
+
} else {
|
|
4457
|
+
types$1.logger.debug(`[loop] User message received with no allowed tools override, using current: ${currentAllowedTools ? currentAllowedTools.join(", ") : "none"}`);
|
|
4458
|
+
}
|
|
4459
|
+
let messageDisallowedTools = currentDisallowedTools;
|
|
4460
|
+
if (message.meta?.hasOwnProperty("disallowedTools")) {
|
|
4461
|
+
messageDisallowedTools = message.meta.disallowedTools || void 0;
|
|
4462
|
+
currentDisallowedTools = messageDisallowedTools;
|
|
4463
|
+
types$1.logger.debug(`[loop] Disallowed tools updated from user message: ${messageDisallowedTools ? messageDisallowedTools.join(", ") : "reset to none"}`);
|
|
4464
|
+
} else {
|
|
4465
|
+
types$1.logger.debug(`[loop] User message received with no disallowed tools override, using current: ${currentDisallowedTools ? currentDisallowedTools.join(", ") : "none"}`);
|
|
4466
|
+
}
|
|
4467
|
+
const specialCommand = parseSpecialCommand(message.content.text);
|
|
4468
|
+
if (specialCommand.type === "compact") {
|
|
4469
|
+
types$1.logger.debug("[start] Detected /compact command");
|
|
4470
|
+
const enhancedMode2 = {
|
|
4471
|
+
permissionMode: messagePermissionMode || "default",
|
|
4472
|
+
model: messageModel,
|
|
4473
|
+
fallbackModel: messageFallbackModel,
|
|
4474
|
+
customSystemPrompt: messageCustomSystemPrompt,
|
|
4475
|
+
appendSystemPrompt: messageAppendSystemPrompt,
|
|
4476
|
+
allowedTools: messageAllowedTools,
|
|
4477
|
+
disallowedTools: messageDisallowedTools
|
|
4478
|
+
};
|
|
4479
|
+
messageQueue.pushIsolateAndClear(specialCommand.originalMessage || message.content.text, enhancedMode2);
|
|
4480
|
+
types$1.logger.debugLargeJson("[start] /compact command pushed to queue:", message);
|
|
4481
|
+
return;
|
|
4482
|
+
}
|
|
4483
|
+
if (specialCommand.type === "clear") {
|
|
4484
|
+
types$1.logger.debug("[start] Detected /clear command");
|
|
4485
|
+
const enhancedMode2 = {
|
|
4486
|
+
permissionMode: messagePermissionMode || "default",
|
|
4487
|
+
model: messageModel,
|
|
4488
|
+
fallbackModel: messageFallbackModel,
|
|
4489
|
+
customSystemPrompt: messageCustomSystemPrompt,
|
|
4490
|
+
appendSystemPrompt: messageAppendSystemPrompt,
|
|
4491
|
+
allowedTools: messageAllowedTools,
|
|
4492
|
+
disallowedTools: messageDisallowedTools
|
|
4493
|
+
};
|
|
4494
|
+
messageQueue.pushIsolateAndClear(specialCommand.originalMessage || message.content.text, enhancedMode2);
|
|
4495
|
+
types$1.logger.debugLargeJson("[start] /compact command pushed to queue:", message);
|
|
4496
|
+
return;
|
|
4497
|
+
}
|
|
4498
|
+
const enhancedMode = {
|
|
4499
|
+
permissionMode: messagePermissionMode || "default",
|
|
4500
|
+
model: messageModel,
|
|
4501
|
+
fallbackModel: messageFallbackModel,
|
|
4502
|
+
customSystemPrompt: messageCustomSystemPrompt,
|
|
4503
|
+
appendSystemPrompt: messageAppendSystemPrompt,
|
|
4504
|
+
allowedTools: messageAllowedTools,
|
|
4505
|
+
disallowedTools: messageDisallowedTools
|
|
4506
|
+
};
|
|
4507
|
+
messageQueue.push(message.content.text, enhancedMode);
|
|
4508
|
+
types$1.logger.debugLargeJson("User message pushed to queue:", message);
|
|
4509
|
+
});
|
|
4510
|
+
const cleanup = async () => {
|
|
4511
|
+
types$1.logger.debug("[START] Received termination signal, cleaning up...");
|
|
4512
|
+
try {
|
|
4513
|
+
if (session) {
|
|
4514
|
+
session.sendSessionDeath();
|
|
4515
|
+
await session.flush();
|
|
4516
|
+
await session.close();
|
|
4517
|
+
}
|
|
4518
|
+
stopCaffeinate();
|
|
4519
|
+
types$1.logger.debug("[START] Cleanup complete, exiting");
|
|
4520
|
+
process.exit(0);
|
|
4521
|
+
} catch (error) {
|
|
4522
|
+
types$1.logger.debug("[START] Error during cleanup:", error);
|
|
4523
|
+
process.exit(1);
|
|
4524
|
+
}
|
|
4525
|
+
};
|
|
4526
|
+
process.on("SIGTERM", cleanup);
|
|
4527
|
+
process.on("SIGINT", cleanup);
|
|
4528
|
+
process.on("uncaughtException", (error) => {
|
|
4529
|
+
types$1.logger.debug("[START] Uncaught exception:", error);
|
|
4530
|
+
cleanup();
|
|
4531
|
+
});
|
|
4532
|
+
process.on("unhandledRejection", (reason) => {
|
|
4533
|
+
types$1.logger.debug("[START] Unhandled rejection:", reason);
|
|
4534
|
+
cleanup();
|
|
4535
|
+
});
|
|
4536
|
+
await loop({
|
|
4537
|
+
path: workingDirectory,
|
|
4538
|
+
model: options.model,
|
|
4539
|
+
permissionMode: options.permissionMode,
|
|
4540
|
+
startingMode: options.startingMode,
|
|
4541
|
+
messageQueue,
|
|
4542
|
+
api,
|
|
4543
|
+
onModeChange: (newMode) => {
|
|
4544
|
+
session.sendSessionEvent({ type: "switch", mode: newMode });
|
|
4545
|
+
session.updateAgentState((currentState) => ({
|
|
4546
|
+
...currentState,
|
|
4547
|
+
controlledByUser: newMode === "local"
|
|
4548
|
+
}));
|
|
4549
|
+
},
|
|
4550
|
+
onSessionReady: (sessionInstance) => {
|
|
4551
|
+
},
|
|
4552
|
+
mcpServers: {},
|
|
4553
|
+
session,
|
|
4554
|
+
claudeEnvVars: options.claudeEnvVars,
|
|
4555
|
+
claudeArgs: options.claudeArgs
|
|
4556
|
+
});
|
|
4557
|
+
session.sendSessionDeath();
|
|
4558
|
+
types$1.logger.debug("Waiting for socket to flush...");
|
|
4559
|
+
await session.flush();
|
|
4560
|
+
types$1.logger.debug("Closing session...");
|
|
4561
|
+
await session.close();
|
|
4562
|
+
stopCaffeinate();
|
|
4563
|
+
types$1.logger.debug("Stopped sleep prevention");
|
|
4564
|
+
process.exit(0);
|
|
4565
|
+
}
|
|
4566
|
+
|
|
4526
4567
|
function trimIdent(text) {
|
|
4527
4568
|
const lines = text.split("\n");
|
|
4528
4569
|
while (lines.length > 0 && lines[0].trim() === "") {
|
|
@@ -4868,6 +4909,34 @@ async function handleAuthStatus() {
|
|
|
4868
4909
|
}
|
|
4869
4910
|
}
|
|
4870
4911
|
|
|
4912
|
+
const DaemonPrompt = ({ onSelect }) => {
|
|
4913
|
+
const [selectedIndex, setSelectedIndex] = React.useState(0);
|
|
4914
|
+
const options = [
|
|
4915
|
+
{ value: true, label: "Yes (recommended)", key: "Y" },
|
|
4916
|
+
{ value: false, label: "No", key: "N" }
|
|
4917
|
+
];
|
|
4918
|
+
ink.useInput((input, key) => {
|
|
4919
|
+
const upperInput = input.toUpperCase();
|
|
4920
|
+
if (key.upArrow || key.leftArrow) {
|
|
4921
|
+
setSelectedIndex(0);
|
|
4922
|
+
} else if (key.downArrow || key.rightArrow) {
|
|
4923
|
+
setSelectedIndex(1);
|
|
4924
|
+
} else if (key.return) {
|
|
4925
|
+
onSelect(options[selectedIndex].value);
|
|
4926
|
+
} else if (upperInput === "Y") {
|
|
4927
|
+
onSelect(true);
|
|
4928
|
+
} else if (upperInput === "N") {
|
|
4929
|
+
onSelect(false);
|
|
4930
|
+
} else if (key.escape || key.ctrl && input === "c") {
|
|
4931
|
+
onSelect(false);
|
|
4932
|
+
}
|
|
4933
|
+
});
|
|
4934
|
+
return /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(ink.Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(ink.Text, { bold: true, color: "cyan" }, "\u{1F680} Happy Daemon Setup")), /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(ink.Text, null, "\u{1F4F1} Happy can run a background service that allows you to:"), /* @__PURE__ */ React.createElement(ink.Text, { color: "cyan" }, " \u2022 Spawn new conversations from your phone"), /* @__PURE__ */ React.createElement(ink.Text, { color: "cyan" }, " \u2022 Continue closed conversations remotely"), /* @__PURE__ */ React.createElement(ink.Text, { color: "cyan" }, " \u2022 Work with Claude while your computer has internet")), /* @__PURE__ */ React.createElement(ink.Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(ink.Text, null, "Would you like Happy to start this service automatically?")), /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column" }, options.map((option, index) => {
|
|
4935
|
+
const isSelected = selectedIndex === index;
|
|
4936
|
+
return /* @__PURE__ */ React.createElement(ink.Box, { key: option.key }, /* @__PURE__ */ React.createElement(ink.Text, { color: isSelected ? "green" : "gray" }, isSelected ? "\u203A " : " ", "[", option.key, "] ", option.label));
|
|
4937
|
+
})), /* @__PURE__ */ React.createElement(ink.Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(ink.Text, { dimColor: true }, "Press Y/N or use arrows + Enter to select")));
|
|
4938
|
+
};
|
|
4939
|
+
|
|
4871
4940
|
(async () => {
|
|
4872
4941
|
const args = process.argv.slice(2);
|
|
4873
4942
|
types$1.logger.debug("Starting happy CLI with args: ", process.argv);
|
|
@@ -4944,8 +5013,7 @@ async function handleAuthStatus() {
|
|
|
4944
5013
|
}
|
|
4945
5014
|
return;
|
|
4946
5015
|
} else if (daemonSubcommand === "start") {
|
|
4947
|
-
const
|
|
4948
|
-
const child = child_process.spawn(happyBinPath, ["daemon", "start-sync"], {
|
|
5016
|
+
const child = spawnHappyCLI(["daemon", "start-sync"], {
|
|
4949
5017
|
detached: true,
|
|
4950
5018
|
stdio: "ignore",
|
|
4951
5019
|
env: process.env
|
|
@@ -5133,38 +5201,38 @@ ${chalk.bold.cyan("Claude Code Options (from `claude --help`):")}
|
|
|
5133
5201
|
const result = await authAndSetupMachineIfNeeded();
|
|
5134
5202
|
credentials = result.credentials;
|
|
5135
5203
|
}
|
|
5204
|
+
const isExperimentalEnabled = ["true", "1", "yes"].includes(process.env.HAPPY_EXPERIMENTAL?.toLowerCase() || "");
|
|
5136
5205
|
let settings = await readSettings();
|
|
5137
|
-
if (settings && settings.daemonAutoStartWhenRunningHappy === void 0) {
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5206
|
+
if (isExperimentalEnabled && settings && settings.daemonAutoStartWhenRunningHappy === void 0) {
|
|
5207
|
+
const shouldAutoStart = await new Promise((resolve) => {
|
|
5208
|
+
let hasResolved = false;
|
|
5209
|
+
const onSelect = (autoStart) => {
|
|
5210
|
+
if (!hasResolved) {
|
|
5211
|
+
hasResolved = true;
|
|
5212
|
+
app.unmount();
|
|
5213
|
+
resolve(autoStart);
|
|
5214
|
+
}
|
|
5215
|
+
};
|
|
5216
|
+
const app = ink.render(React.createElement(DaemonPrompt, { onSelect }), {
|
|
5217
|
+
exitOnCtrlC: false,
|
|
5218
|
+
patchConsole: false
|
|
5219
|
+
});
|
|
5149
5220
|
});
|
|
5150
|
-
rl.close();
|
|
5151
|
-
const shouldAutoStart = answer.toLowerCase() !== "n";
|
|
5152
5221
|
settings = await updateSettings((settings2) => ({
|
|
5153
5222
|
...settings2,
|
|
5154
5223
|
daemonAutoStartWhenRunningHappy: shouldAutoStart
|
|
5155
5224
|
}));
|
|
5156
5225
|
if (shouldAutoStart) {
|
|
5157
|
-
console.log(chalk.green("\u2713 Happy will start the background service automatically"));
|
|
5226
|
+
console.log(chalk.green("\n\u2713 Happy will start the background service automatically"));
|
|
5158
5227
|
console.log(chalk.gray(" The service will run whenever you use the happy command"));
|
|
5159
5228
|
} else {
|
|
5160
|
-
console.log(chalk.yellow(" You can enable this later by running: happy daemon install"));
|
|
5229
|
+
console.log(chalk.yellow("\n You can enable this later by running: happy daemon install"));
|
|
5161
5230
|
}
|
|
5162
5231
|
}
|
|
5163
|
-
if (settings && settings.daemonAutoStartWhenRunningHappy) {
|
|
5232
|
+
if (isExperimentalEnabled && settings && settings.daemonAutoStartWhenRunningHappy) {
|
|
5164
5233
|
types$1.logger.debug("Starting Happy background service...");
|
|
5165
5234
|
if (!await isDaemonRunning()) {
|
|
5166
|
-
const
|
|
5167
|
-
const daemonProcess = child_process.spawn(happyBinPath, ["daemon", "start-sync"], {
|
|
5235
|
+
const daemonProcess = spawnHappyCLI(["daemon", "start-sync"], {
|
|
5168
5236
|
detached: true,
|
|
5169
5237
|
stdio: "ignore",
|
|
5170
5238
|
env: process.env
|