happy-coder 0.11.2 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/codex/happyMcpStdioBridge.cjs +2 -3
- package/dist/codex/happyMcpStdioBridge.mjs +2 -3
- package/dist/{index-sSOy3f2x.mjs → index-DMBGazgc.mjs} +144 -48
- package/dist/{index-DmJ8WyYo.cjs → index-IPZkNBoV.cjs} +142 -46
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/lib.cjs +1 -1
- package/dist/lib.d.cts +6 -15
- package/dist/lib.d.mts +6 -15
- package/dist/lib.mjs +1 -1
- package/dist/{runCodex-BnjA1TX6.mjs → runCodex-BgCwl6pc.mjs} +3 -3
- package/dist/{runCodex-DQPZNHzH.cjs → runCodex-DuUtfZ9i.cjs} +3 -3
- package/dist/{types-CjceR-4_.mjs → types-7HcYY6Ao.mjs} +73 -28
- package/dist/{types-Bg43e3vc.cjs → types-CO3A_kFm.cjs} +74 -29
- package/package.json +15 -15
- package/scripts/claude_local_launcher.cjs +5 -30
- package/scripts/claude_remote_launcher.cjs +4 -1
- package/scripts/claude_version_utils.cjs +378 -0
|
@@ -21,7 +21,7 @@ import { platform } from 'os';
|
|
|
21
21
|
import { Expo } from 'expo-server-sdk';
|
|
22
22
|
|
|
23
23
|
var name = "happy-coder";
|
|
24
|
-
var version = "0.
|
|
24
|
+
var version = "0.12.0";
|
|
25
25
|
var description = "Mobile and Web client for Claude Code and Codex";
|
|
26
26
|
var author = "Kirill Dubovitskiy";
|
|
27
27
|
var license = "MIT";
|
|
@@ -80,7 +80,9 @@ var scripts = {
|
|
|
80
80
|
typecheck: "tsc --noEmit",
|
|
81
81
|
build: "shx rm -rf dist && npx tsc --noEmit && pkgroll",
|
|
82
82
|
test: "yarn build && tsx --env-file .env.integration-test node_modules/.bin/vitest run",
|
|
83
|
+
"test:win": "yarn build && npx vitest run",
|
|
83
84
|
start: "yarn build && ./bin/happy.mjs",
|
|
85
|
+
"start:win": "yarn build && node ./bin/happy.mjs",
|
|
84
86
|
dev: "tsx src/index.ts",
|
|
85
87
|
"dev:local-server": "yarn build && tsx --env-file .env.dev-local-server src/index.ts",
|
|
86
88
|
"dev:integration-test-env": "yarn build && tsx --env-file .env.integration-test src/index.ts",
|
|
@@ -89,32 +91,30 @@ var scripts = {
|
|
|
89
91
|
postinstall: "node scripts/unpack-tools.cjs"
|
|
90
92
|
};
|
|
91
93
|
var dependencies = {
|
|
92
|
-
"@
|
|
93
|
-
"@anthropic-ai/sdk": "0.65.0",
|
|
94
|
-
"@modelcontextprotocol/sdk": "^1.15.1",
|
|
94
|
+
"@modelcontextprotocol/sdk": "^1.22.0",
|
|
95
95
|
"@stablelib/base64": "^2.0.1",
|
|
96
96
|
"@stablelib/hex": "^2.0.1",
|
|
97
97
|
"@types/cross-spawn": "^6.0.6",
|
|
98
|
-
"@types/http-proxy": "^1.17.
|
|
98
|
+
"@types/http-proxy": "^1.17.17",
|
|
99
99
|
"@types/ps-list": "^6.2.1",
|
|
100
100
|
"@types/qrcode-terminal": "^0.12.2",
|
|
101
|
-
"@types/react": "^19.
|
|
101
|
+
"@types/react": "^19.2.7",
|
|
102
102
|
"@types/tmp": "^0.2.6",
|
|
103
|
-
axios: "^1.
|
|
104
|
-
chalk: "^5.
|
|
103
|
+
axios: "^1.13.2",
|
|
104
|
+
chalk: "^5.6.2",
|
|
105
105
|
"cross-spawn": "^7.0.6",
|
|
106
106
|
"expo-server-sdk": "^3.15.0",
|
|
107
|
-
fastify: "^5.
|
|
107
|
+
fastify: "^5.6.2",
|
|
108
108
|
"fastify-type-provider-zod": "4.0.2",
|
|
109
109
|
"http-proxy": "^1.18.1",
|
|
110
110
|
"http-proxy-middleware": "^3.0.5",
|
|
111
|
-
ink: "^6.1
|
|
111
|
+
ink: "^6.5.1",
|
|
112
112
|
open: "^10.2.0",
|
|
113
113
|
"ps-list": "^8.1.1",
|
|
114
114
|
"qrcode-terminal": "^0.12.0",
|
|
115
|
-
react: "^19.
|
|
115
|
+
react: "^19.2.0",
|
|
116
116
|
"socket.io-client": "^4.8.1",
|
|
117
|
-
tar: "^7.
|
|
117
|
+
tar: "^7.5.2",
|
|
118
118
|
tmp: "^0.2.5",
|
|
119
119
|
tweetnacl: "^1.0.3",
|
|
120
120
|
zod: "^3.23.8"
|
|
@@ -122,15 +122,15 @@ var dependencies = {
|
|
|
122
122
|
var devDependencies = {
|
|
123
123
|
"@eslint/compat": "^1",
|
|
124
124
|
"@types/node": ">=20",
|
|
125
|
-
"cross-env": "^10.
|
|
125
|
+
"cross-env": "^10.1.0",
|
|
126
126
|
dotenv: "^16.6.1",
|
|
127
127
|
eslint: "^9",
|
|
128
128
|
"eslint-config-prettier": "^10",
|
|
129
129
|
pkgroll: "^2.14.2",
|
|
130
|
-
"release-it": "^19.0.
|
|
130
|
+
"release-it": "^19.0.6",
|
|
131
131
|
shx: "^0.3.3",
|
|
132
132
|
"ts-node": "^10",
|
|
133
|
-
tsx: "^4.20.
|
|
133
|
+
tsx: "^4.20.6",
|
|
134
134
|
typescript: "^5",
|
|
135
135
|
vitest: "^3.2.4"
|
|
136
136
|
};
|
|
@@ -1070,10 +1070,28 @@ function run(args, options) {
|
|
|
1070
1070
|
});
|
|
1071
1071
|
}
|
|
1072
1072
|
|
|
1073
|
+
function validatePath(targetPath, workingDirectory) {
|
|
1074
|
+
const resolvedTarget = resolve(workingDirectory, targetPath);
|
|
1075
|
+
const resolvedWorkingDir = resolve(workingDirectory);
|
|
1076
|
+
if (!resolvedTarget.startsWith(resolvedWorkingDir + "/") && resolvedTarget !== resolvedWorkingDir) {
|
|
1077
|
+
return {
|
|
1078
|
+
valid: false,
|
|
1079
|
+
error: `Access denied: Path '${targetPath}' is outside the working directory`
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
return { valid: true };
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1073
1085
|
const execAsync = promisify(exec);
|
|
1074
|
-
function registerCommonHandlers(rpcHandlerManager) {
|
|
1086
|
+
function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
|
|
1075
1087
|
rpcHandlerManager.registerHandler("bash", async (data) => {
|
|
1076
1088
|
logger.debug("Shell command request:", data.command);
|
|
1089
|
+
if (data.cwd) {
|
|
1090
|
+
const validation = validatePath(data.cwd, workingDirectory);
|
|
1091
|
+
if (!validation.valid) {
|
|
1092
|
+
return { success: false, error: validation.error };
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1077
1095
|
try {
|
|
1078
1096
|
const options = {
|
|
1079
1097
|
cwd: data.cwd,
|
|
@@ -1109,6 +1127,10 @@ function registerCommonHandlers(rpcHandlerManager) {
|
|
|
1109
1127
|
});
|
|
1110
1128
|
rpcHandlerManager.registerHandler("readFile", async (data) => {
|
|
1111
1129
|
logger.debug("Read file request:", data.path);
|
|
1130
|
+
const validation = validatePath(data.path, workingDirectory);
|
|
1131
|
+
if (!validation.valid) {
|
|
1132
|
+
return { success: false, error: validation.error };
|
|
1133
|
+
}
|
|
1112
1134
|
try {
|
|
1113
1135
|
const buffer = await readFile$1(data.path);
|
|
1114
1136
|
const content = buffer.toString("base64");
|
|
@@ -1120,6 +1142,10 @@ function registerCommonHandlers(rpcHandlerManager) {
|
|
|
1120
1142
|
});
|
|
1121
1143
|
rpcHandlerManager.registerHandler("writeFile", async (data) => {
|
|
1122
1144
|
logger.debug("Write file request:", data.path);
|
|
1145
|
+
const validation = validatePath(data.path, workingDirectory);
|
|
1146
|
+
if (!validation.valid) {
|
|
1147
|
+
return { success: false, error: validation.error };
|
|
1148
|
+
}
|
|
1123
1149
|
try {
|
|
1124
1150
|
if (data.expectedHash !== null && data.expectedHash !== void 0) {
|
|
1125
1151
|
try {
|
|
@@ -1166,6 +1192,10 @@ function registerCommonHandlers(rpcHandlerManager) {
|
|
|
1166
1192
|
});
|
|
1167
1193
|
rpcHandlerManager.registerHandler("listDirectory", async (data) => {
|
|
1168
1194
|
logger.debug("List directory request:", data.path);
|
|
1195
|
+
const validation = validatePath(data.path, workingDirectory);
|
|
1196
|
+
if (!validation.valid) {
|
|
1197
|
+
return { success: false, error: validation.error };
|
|
1198
|
+
}
|
|
1169
1199
|
try {
|
|
1170
1200
|
const entries = await readdir(data.path, { withFileTypes: true });
|
|
1171
1201
|
const directoryEntries = await Promise.all(
|
|
@@ -1207,6 +1237,10 @@ function registerCommonHandlers(rpcHandlerManager) {
|
|
|
1207
1237
|
});
|
|
1208
1238
|
rpcHandlerManager.registerHandler("getDirectoryTree", async (data) => {
|
|
1209
1239
|
logger.debug("Get directory tree request:", data.path, "maxDepth:", data.maxDepth);
|
|
1240
|
+
const validation = validatePath(data.path, workingDirectory);
|
|
1241
|
+
if (!validation.valid) {
|
|
1242
|
+
return { success: false, error: validation.error };
|
|
1243
|
+
}
|
|
1210
1244
|
async function buildTree(path, name, currentDepth) {
|
|
1211
1245
|
try {
|
|
1212
1246
|
const stats = await stat$1(path);
|
|
@@ -1263,6 +1297,12 @@ function registerCommonHandlers(rpcHandlerManager) {
|
|
|
1263
1297
|
});
|
|
1264
1298
|
rpcHandlerManager.registerHandler("ripgrep", async (data) => {
|
|
1265
1299
|
logger.debug("Ripgrep request with args:", data.args, "cwd:", data.cwd);
|
|
1300
|
+
if (data.cwd) {
|
|
1301
|
+
const validation = validatePath(data.cwd, workingDirectory);
|
|
1302
|
+
if (!validation.valid) {
|
|
1303
|
+
return { success: false, error: validation.error };
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1266
1306
|
try {
|
|
1267
1307
|
const result = await run$1(data.args, { cwd: data.cwd });
|
|
1268
1308
|
return {
|
|
@@ -1281,6 +1321,12 @@ function registerCommonHandlers(rpcHandlerManager) {
|
|
|
1281
1321
|
});
|
|
1282
1322
|
rpcHandlerManager.registerHandler("difftastic", async (data) => {
|
|
1283
1323
|
logger.debug("Difftastic request with args:", data.args, "cwd:", data.cwd);
|
|
1324
|
+
if (data.cwd) {
|
|
1325
|
+
const validation = validatePath(data.cwd, workingDirectory);
|
|
1326
|
+
if (!validation.valid) {
|
|
1327
|
+
return { success: false, error: validation.error };
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1284
1330
|
try {
|
|
1285
1331
|
const result = await run(data.args, { cwd: data.cwd });
|
|
1286
1332
|
return {
|
|
@@ -1330,7 +1376,7 @@ class ApiSessionClient extends EventEmitter {
|
|
|
1330
1376
|
encryptionVariant: this.encryptionVariant,
|
|
1331
1377
|
logger: (msg, data) => logger.debug(msg, data)
|
|
1332
1378
|
});
|
|
1333
|
-
registerCommonHandlers(this.rpcHandlerManager);
|
|
1379
|
+
registerCommonHandlers(this.rpcHandlerManager, this.metadata.path);
|
|
1334
1380
|
this.socket = io(configuration.serverUrl, {
|
|
1335
1381
|
auth: {
|
|
1336
1382
|
token: this.token,
|
|
@@ -1446,7 +1492,7 @@ class ApiSessionClient extends EventEmitter {
|
|
|
1446
1492
|
sid: this.sessionId,
|
|
1447
1493
|
message: encrypted
|
|
1448
1494
|
});
|
|
1449
|
-
if (body.type === "assistant" && body.message
|
|
1495
|
+
if (body.type === "assistant" && body.message?.usage) {
|
|
1450
1496
|
try {
|
|
1451
1497
|
this.sendUsageData(body.message.usage);
|
|
1452
1498
|
} catch (error) {
|
|
@@ -1620,7 +1666,7 @@ class ApiMachineClient {
|
|
|
1620
1666
|
encryptionVariant: this.machine.encryptionVariant,
|
|
1621
1667
|
logger: (msg, data) => logger.debug(msg, data)
|
|
1622
1668
|
});
|
|
1623
|
-
registerCommonHandlers(this.rpcHandlerManager);
|
|
1669
|
+
registerCommonHandlers(this.rpcHandlerManager, process.cwd());
|
|
1624
1670
|
}
|
|
1625
1671
|
socket;
|
|
1626
1672
|
keepAliveInterval = null;
|
|
@@ -1836,7 +1882,7 @@ class PushNotificationClient {
|
|
|
1836
1882
|
);
|
|
1837
1883
|
logger.debug(`Fetched ${response.data.tokens.length} push tokens`);
|
|
1838
1884
|
response.data.tokens.forEach((token, index) => {
|
|
1839
|
-
logger.debug(`[PUSH] Token ${index + 1}: id=${token.id},
|
|
1885
|
+
logger.debug(`[PUSH] Token ${index + 1}: id=${token.id}, created=${new Date(token.createdAt).toISOString()}, updated=${new Date(token.updatedAt).toISOString()}`);
|
|
1840
1886
|
});
|
|
1841
1887
|
return response.data.tokens;
|
|
1842
1888
|
} catch (error) {
|
|
@@ -1910,14 +1956,14 @@ class PushNotificationClient {
|
|
|
1910
1956
|
const tokens = await this.fetchPushTokens();
|
|
1911
1957
|
logger.debug(`[PUSH] Fetched ${tokens.length} push tokens`);
|
|
1912
1958
|
tokens.forEach((token, index) => {
|
|
1913
|
-
logger.debug(`[PUSH] Using token ${index + 1}: id=${token.id}
|
|
1959
|
+
logger.debug(`[PUSH] Using token ${index + 1}: id=${token.id}`);
|
|
1914
1960
|
});
|
|
1915
1961
|
if (tokens.length === 0) {
|
|
1916
1962
|
logger.debug("No push tokens found for user");
|
|
1917
1963
|
return;
|
|
1918
1964
|
}
|
|
1919
1965
|
const messages = tokens.map((token, index) => {
|
|
1920
|
-
logger.debug(`[PUSH] Creating message ${index + 1} for token
|
|
1966
|
+
logger.debug(`[PUSH] Creating message ${index + 1} for token`);
|
|
1921
1967
|
return {
|
|
1922
1968
|
to: token.token,
|
|
1923
1969
|
title,
|
|
@@ -2114,17 +2160,16 @@ const RawJSONLinesSchema = z$1.discriminatedUnion("type", [
|
|
|
2114
2160
|
// Used in sessionScanner.ts
|
|
2115
2161
|
}).passthrough()
|
|
2116
2162
|
}).passthrough(),
|
|
2117
|
-
// Assistant message - validates
|
|
2163
|
+
// Assistant message - only validates uuid and type
|
|
2164
|
+
// message object is optional to handle synthetic error messages (isApiErrorMessage: true)
|
|
2165
|
+
// which may have different structure than normal assistant messages
|
|
2118
2166
|
z$1.object({
|
|
2119
2167
|
uuid: z$1.string(),
|
|
2120
2168
|
type: z$1.literal("assistant"),
|
|
2121
2169
|
message: z$1.object({
|
|
2122
|
-
|
|
2123
|
-
usage: UsageSchema.optional(),
|
|
2170
|
+
usage: UsageSchema.optional()
|
|
2124
2171
|
// Used in apiSession.ts
|
|
2125
|
-
|
|
2126
|
-
// Used in tests
|
|
2127
|
-
}).passthrough()
|
|
2172
|
+
}).passthrough().optional()
|
|
2128
2173
|
}).passthrough(),
|
|
2129
2174
|
// Summary message - validates summary and leafUuid
|
|
2130
2175
|
z$1.object({
|
|
@@ -42,7 +42,7 @@ function _interopNamespaceDefault(e) {
|
|
|
42
42
|
var z__namespace = /*#__PURE__*/_interopNamespaceDefault(z);
|
|
43
43
|
|
|
44
44
|
var name = "happy-coder";
|
|
45
|
-
var version = "0.
|
|
45
|
+
var version = "0.12.0";
|
|
46
46
|
var description = "Mobile and Web client for Claude Code and Codex";
|
|
47
47
|
var author = "Kirill Dubovitskiy";
|
|
48
48
|
var license = "MIT";
|
|
@@ -101,7 +101,9 @@ var scripts = {
|
|
|
101
101
|
typecheck: "tsc --noEmit",
|
|
102
102
|
build: "shx rm -rf dist && npx tsc --noEmit && pkgroll",
|
|
103
103
|
test: "yarn build && tsx --env-file .env.integration-test node_modules/.bin/vitest run",
|
|
104
|
+
"test:win": "yarn build && npx vitest run",
|
|
104
105
|
start: "yarn build && ./bin/happy.mjs",
|
|
106
|
+
"start:win": "yarn build && node ./bin/happy.mjs",
|
|
105
107
|
dev: "tsx src/index.ts",
|
|
106
108
|
"dev:local-server": "yarn build && tsx --env-file .env.dev-local-server src/index.ts",
|
|
107
109
|
"dev:integration-test-env": "yarn build && tsx --env-file .env.integration-test src/index.ts",
|
|
@@ -110,32 +112,30 @@ var scripts = {
|
|
|
110
112
|
postinstall: "node scripts/unpack-tools.cjs"
|
|
111
113
|
};
|
|
112
114
|
var dependencies = {
|
|
113
|
-
"@
|
|
114
|
-
"@anthropic-ai/sdk": "0.65.0",
|
|
115
|
-
"@modelcontextprotocol/sdk": "^1.15.1",
|
|
115
|
+
"@modelcontextprotocol/sdk": "^1.22.0",
|
|
116
116
|
"@stablelib/base64": "^2.0.1",
|
|
117
117
|
"@stablelib/hex": "^2.0.1",
|
|
118
118
|
"@types/cross-spawn": "^6.0.6",
|
|
119
|
-
"@types/http-proxy": "^1.17.
|
|
119
|
+
"@types/http-proxy": "^1.17.17",
|
|
120
120
|
"@types/ps-list": "^6.2.1",
|
|
121
121
|
"@types/qrcode-terminal": "^0.12.2",
|
|
122
|
-
"@types/react": "^19.
|
|
122
|
+
"@types/react": "^19.2.7",
|
|
123
123
|
"@types/tmp": "^0.2.6",
|
|
124
|
-
axios: "^1.
|
|
125
|
-
chalk: "^5.
|
|
124
|
+
axios: "^1.13.2",
|
|
125
|
+
chalk: "^5.6.2",
|
|
126
126
|
"cross-spawn": "^7.0.6",
|
|
127
127
|
"expo-server-sdk": "^3.15.0",
|
|
128
|
-
fastify: "^5.
|
|
128
|
+
fastify: "^5.6.2",
|
|
129
129
|
"fastify-type-provider-zod": "4.0.2",
|
|
130
130
|
"http-proxy": "^1.18.1",
|
|
131
131
|
"http-proxy-middleware": "^3.0.5",
|
|
132
|
-
ink: "^6.1
|
|
132
|
+
ink: "^6.5.1",
|
|
133
133
|
open: "^10.2.0",
|
|
134
134
|
"ps-list": "^8.1.1",
|
|
135
135
|
"qrcode-terminal": "^0.12.0",
|
|
136
|
-
react: "^19.
|
|
136
|
+
react: "^19.2.0",
|
|
137
137
|
"socket.io-client": "^4.8.1",
|
|
138
|
-
tar: "^7.
|
|
138
|
+
tar: "^7.5.2",
|
|
139
139
|
tmp: "^0.2.5",
|
|
140
140
|
tweetnacl: "^1.0.3",
|
|
141
141
|
zod: "^3.23.8"
|
|
@@ -143,15 +143,15 @@ var dependencies = {
|
|
|
143
143
|
var devDependencies = {
|
|
144
144
|
"@eslint/compat": "^1",
|
|
145
145
|
"@types/node": ">=20",
|
|
146
|
-
"cross-env": "^10.
|
|
146
|
+
"cross-env": "^10.1.0",
|
|
147
147
|
dotenv: "^16.6.1",
|
|
148
148
|
eslint: "^9",
|
|
149
149
|
"eslint-config-prettier": "^10",
|
|
150
150
|
pkgroll: "^2.14.2",
|
|
151
|
-
"release-it": "^19.0.
|
|
151
|
+
"release-it": "^19.0.6",
|
|
152
152
|
shx: "^0.3.3",
|
|
153
153
|
"ts-node": "^10",
|
|
154
|
-
tsx: "^4.20.
|
|
154
|
+
tsx: "^4.20.6",
|
|
155
155
|
typescript: "^5",
|
|
156
156
|
vitest: "^3.2.4"
|
|
157
157
|
};
|
|
@@ -1019,7 +1019,7 @@ class RpcHandlerManager {
|
|
|
1019
1019
|
}
|
|
1020
1020
|
}
|
|
1021
1021
|
|
|
1022
|
-
const __dirname$1 = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-
|
|
1022
|
+
const __dirname$1 = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-CO3A_kFm.cjs', document.baseURI).href))));
|
|
1023
1023
|
function projectPath() {
|
|
1024
1024
|
const path$1 = path.resolve(__dirname$1, "..");
|
|
1025
1025
|
return path$1;
|
|
@@ -1091,10 +1091,28 @@ function run(args, options) {
|
|
|
1091
1091
|
});
|
|
1092
1092
|
}
|
|
1093
1093
|
|
|
1094
|
+
function validatePath(targetPath, workingDirectory) {
|
|
1095
|
+
const resolvedTarget = path.resolve(workingDirectory, targetPath);
|
|
1096
|
+
const resolvedWorkingDir = path.resolve(workingDirectory);
|
|
1097
|
+
if (!resolvedTarget.startsWith(resolvedWorkingDir + "/") && resolvedTarget !== resolvedWorkingDir) {
|
|
1098
|
+
return {
|
|
1099
|
+
valid: false,
|
|
1100
|
+
error: `Access denied: Path '${targetPath}' is outside the working directory`
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
return { valid: true };
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1094
1106
|
const execAsync = util.promisify(child_process.exec);
|
|
1095
|
-
function registerCommonHandlers(rpcHandlerManager) {
|
|
1107
|
+
function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
|
|
1096
1108
|
rpcHandlerManager.registerHandler("bash", async (data) => {
|
|
1097
1109
|
logger.debug("Shell command request:", data.command);
|
|
1110
|
+
if (data.cwd) {
|
|
1111
|
+
const validation = validatePath(data.cwd, workingDirectory);
|
|
1112
|
+
if (!validation.valid) {
|
|
1113
|
+
return { success: false, error: validation.error };
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1098
1116
|
try {
|
|
1099
1117
|
const options = {
|
|
1100
1118
|
cwd: data.cwd,
|
|
@@ -1130,6 +1148,10 @@ function registerCommonHandlers(rpcHandlerManager) {
|
|
|
1130
1148
|
});
|
|
1131
1149
|
rpcHandlerManager.registerHandler("readFile", async (data) => {
|
|
1132
1150
|
logger.debug("Read file request:", data.path);
|
|
1151
|
+
const validation = validatePath(data.path, workingDirectory);
|
|
1152
|
+
if (!validation.valid) {
|
|
1153
|
+
return { success: false, error: validation.error };
|
|
1154
|
+
}
|
|
1133
1155
|
try {
|
|
1134
1156
|
const buffer = await fs$2.readFile(data.path);
|
|
1135
1157
|
const content = buffer.toString("base64");
|
|
@@ -1141,6 +1163,10 @@ function registerCommonHandlers(rpcHandlerManager) {
|
|
|
1141
1163
|
});
|
|
1142
1164
|
rpcHandlerManager.registerHandler("writeFile", async (data) => {
|
|
1143
1165
|
logger.debug("Write file request:", data.path);
|
|
1166
|
+
const validation = validatePath(data.path, workingDirectory);
|
|
1167
|
+
if (!validation.valid) {
|
|
1168
|
+
return { success: false, error: validation.error };
|
|
1169
|
+
}
|
|
1144
1170
|
try {
|
|
1145
1171
|
if (data.expectedHash !== null && data.expectedHash !== void 0) {
|
|
1146
1172
|
try {
|
|
@@ -1187,6 +1213,10 @@ function registerCommonHandlers(rpcHandlerManager) {
|
|
|
1187
1213
|
});
|
|
1188
1214
|
rpcHandlerManager.registerHandler("listDirectory", async (data) => {
|
|
1189
1215
|
logger.debug("List directory request:", data.path);
|
|
1216
|
+
const validation = validatePath(data.path, workingDirectory);
|
|
1217
|
+
if (!validation.valid) {
|
|
1218
|
+
return { success: false, error: validation.error };
|
|
1219
|
+
}
|
|
1190
1220
|
try {
|
|
1191
1221
|
const entries = await fs$2.readdir(data.path, { withFileTypes: true });
|
|
1192
1222
|
const directoryEntries = await Promise.all(
|
|
@@ -1228,6 +1258,10 @@ function registerCommonHandlers(rpcHandlerManager) {
|
|
|
1228
1258
|
});
|
|
1229
1259
|
rpcHandlerManager.registerHandler("getDirectoryTree", async (data) => {
|
|
1230
1260
|
logger.debug("Get directory tree request:", data.path, "maxDepth:", data.maxDepth);
|
|
1261
|
+
const validation = validatePath(data.path, workingDirectory);
|
|
1262
|
+
if (!validation.valid) {
|
|
1263
|
+
return { success: false, error: validation.error };
|
|
1264
|
+
}
|
|
1231
1265
|
async function buildTree(path$1, name, currentDepth) {
|
|
1232
1266
|
try {
|
|
1233
1267
|
const stats = await fs$2.stat(path$1);
|
|
@@ -1284,6 +1318,12 @@ function registerCommonHandlers(rpcHandlerManager) {
|
|
|
1284
1318
|
});
|
|
1285
1319
|
rpcHandlerManager.registerHandler("ripgrep", async (data) => {
|
|
1286
1320
|
logger.debug("Ripgrep request with args:", data.args, "cwd:", data.cwd);
|
|
1321
|
+
if (data.cwd) {
|
|
1322
|
+
const validation = validatePath(data.cwd, workingDirectory);
|
|
1323
|
+
if (!validation.valid) {
|
|
1324
|
+
return { success: false, error: validation.error };
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1287
1327
|
try {
|
|
1288
1328
|
const result = await run$1(data.args, { cwd: data.cwd });
|
|
1289
1329
|
return {
|
|
@@ -1302,6 +1342,12 @@ function registerCommonHandlers(rpcHandlerManager) {
|
|
|
1302
1342
|
});
|
|
1303
1343
|
rpcHandlerManager.registerHandler("difftastic", async (data) => {
|
|
1304
1344
|
logger.debug("Difftastic request with args:", data.args, "cwd:", data.cwd);
|
|
1345
|
+
if (data.cwd) {
|
|
1346
|
+
const validation = validatePath(data.cwd, workingDirectory);
|
|
1347
|
+
if (!validation.valid) {
|
|
1348
|
+
return { success: false, error: validation.error };
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1305
1351
|
try {
|
|
1306
1352
|
const result = await run(data.args, { cwd: data.cwd });
|
|
1307
1353
|
return {
|
|
@@ -1351,7 +1397,7 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
1351
1397
|
encryptionVariant: this.encryptionVariant,
|
|
1352
1398
|
logger: (msg, data) => logger.debug(msg, data)
|
|
1353
1399
|
});
|
|
1354
|
-
registerCommonHandlers(this.rpcHandlerManager);
|
|
1400
|
+
registerCommonHandlers(this.rpcHandlerManager, this.metadata.path);
|
|
1355
1401
|
this.socket = socket_ioClient.io(configuration.serverUrl, {
|
|
1356
1402
|
auth: {
|
|
1357
1403
|
token: this.token,
|
|
@@ -1467,7 +1513,7 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
1467
1513
|
sid: this.sessionId,
|
|
1468
1514
|
message: encrypted
|
|
1469
1515
|
});
|
|
1470
|
-
if (body.type === "assistant" && body.message
|
|
1516
|
+
if (body.type === "assistant" && body.message?.usage) {
|
|
1471
1517
|
try {
|
|
1472
1518
|
this.sendUsageData(body.message.usage);
|
|
1473
1519
|
} catch (error) {
|
|
@@ -1641,7 +1687,7 @@ class ApiMachineClient {
|
|
|
1641
1687
|
encryptionVariant: this.machine.encryptionVariant,
|
|
1642
1688
|
logger: (msg, data) => logger.debug(msg, data)
|
|
1643
1689
|
});
|
|
1644
|
-
registerCommonHandlers(this.rpcHandlerManager);
|
|
1690
|
+
registerCommonHandlers(this.rpcHandlerManager, process.cwd());
|
|
1645
1691
|
}
|
|
1646
1692
|
socket;
|
|
1647
1693
|
keepAliveInterval = null;
|
|
@@ -1857,7 +1903,7 @@ class PushNotificationClient {
|
|
|
1857
1903
|
);
|
|
1858
1904
|
logger.debug(`Fetched ${response.data.tokens.length} push tokens`);
|
|
1859
1905
|
response.data.tokens.forEach((token, index) => {
|
|
1860
|
-
logger.debug(`[PUSH] Token ${index + 1}: id=${token.id},
|
|
1906
|
+
logger.debug(`[PUSH] Token ${index + 1}: id=${token.id}, created=${new Date(token.createdAt).toISOString()}, updated=${new Date(token.updatedAt).toISOString()}`);
|
|
1861
1907
|
});
|
|
1862
1908
|
return response.data.tokens;
|
|
1863
1909
|
} catch (error) {
|
|
@@ -1931,14 +1977,14 @@ class PushNotificationClient {
|
|
|
1931
1977
|
const tokens = await this.fetchPushTokens();
|
|
1932
1978
|
logger.debug(`[PUSH] Fetched ${tokens.length} push tokens`);
|
|
1933
1979
|
tokens.forEach((token, index) => {
|
|
1934
|
-
logger.debug(`[PUSH] Using token ${index + 1}: id=${token.id}
|
|
1980
|
+
logger.debug(`[PUSH] Using token ${index + 1}: id=${token.id}`);
|
|
1935
1981
|
});
|
|
1936
1982
|
if (tokens.length === 0) {
|
|
1937
1983
|
logger.debug("No push tokens found for user");
|
|
1938
1984
|
return;
|
|
1939
1985
|
}
|
|
1940
1986
|
const messages = tokens.map((token, index) => {
|
|
1941
|
-
logger.debug(`[PUSH] Creating message ${index + 1} for token
|
|
1987
|
+
logger.debug(`[PUSH] Creating message ${index + 1} for token`);
|
|
1942
1988
|
return {
|
|
1943
1989
|
to: token.token,
|
|
1944
1990
|
title,
|
|
@@ -2135,17 +2181,16 @@ const RawJSONLinesSchema = z.z.discriminatedUnion("type", [
|
|
|
2135
2181
|
// Used in sessionScanner.ts
|
|
2136
2182
|
}).passthrough()
|
|
2137
2183
|
}).passthrough(),
|
|
2138
|
-
// Assistant message - validates
|
|
2184
|
+
// Assistant message - only validates uuid and type
|
|
2185
|
+
// message object is optional to handle synthetic error messages (isApiErrorMessage: true)
|
|
2186
|
+
// which may have different structure than normal assistant messages
|
|
2139
2187
|
z.z.object({
|
|
2140
2188
|
uuid: z.z.string(),
|
|
2141
2189
|
type: z.z.literal("assistant"),
|
|
2142
2190
|
message: z.z.object({
|
|
2143
|
-
|
|
2144
|
-
usage: UsageSchema.optional(),
|
|
2191
|
+
usage: UsageSchema.optional()
|
|
2145
2192
|
// Used in apiSession.ts
|
|
2146
|
-
|
|
2147
|
-
// Used in tests
|
|
2148
|
-
}).passthrough()
|
|
2193
|
+
}).passthrough().optional()
|
|
2149
2194
|
}).passthrough(),
|
|
2150
2195
|
// Summary message - validates summary and leafUuid
|
|
2151
2196
|
z.z.object({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "happy-coder",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Mobile and Web client for Claude Code and Codex",
|
|
5
5
|
"author": "Kirill Dubovitskiy",
|
|
6
6
|
"license": "MIT",
|
|
@@ -59,7 +59,9 @@
|
|
|
59
59
|
"typecheck": "tsc --noEmit",
|
|
60
60
|
"build": "shx rm -rf dist && npx tsc --noEmit && pkgroll",
|
|
61
61
|
"test": "yarn build && tsx --env-file .env.integration-test node_modules/.bin/vitest run",
|
|
62
|
+
"test:win": "yarn build && npx vitest run",
|
|
62
63
|
"start": "yarn build && ./bin/happy.mjs",
|
|
64
|
+
"start:win": "yarn build && node ./bin/happy.mjs",
|
|
63
65
|
"dev": "tsx src/index.ts",
|
|
64
66
|
"dev:local-server": "yarn build && tsx --env-file .env.dev-local-server src/index.ts",
|
|
65
67
|
"dev:integration-test-env": "yarn build && tsx --env-file .env.integration-test src/index.ts",
|
|
@@ -68,32 +70,30 @@
|
|
|
68
70
|
"postinstall": "node scripts/unpack-tools.cjs"
|
|
69
71
|
},
|
|
70
72
|
"dependencies": {
|
|
71
|
-
"@
|
|
72
|
-
"@anthropic-ai/sdk": "0.65.0",
|
|
73
|
-
"@modelcontextprotocol/sdk": "^1.15.1",
|
|
73
|
+
"@modelcontextprotocol/sdk": "^1.22.0",
|
|
74
74
|
"@stablelib/base64": "^2.0.1",
|
|
75
75
|
"@stablelib/hex": "^2.0.1",
|
|
76
76
|
"@types/cross-spawn": "^6.0.6",
|
|
77
|
-
"@types/http-proxy": "^1.17.
|
|
77
|
+
"@types/http-proxy": "^1.17.17",
|
|
78
78
|
"@types/ps-list": "^6.2.1",
|
|
79
79
|
"@types/qrcode-terminal": "^0.12.2",
|
|
80
|
-
"@types/react": "^19.
|
|
80
|
+
"@types/react": "^19.2.7",
|
|
81
81
|
"@types/tmp": "^0.2.6",
|
|
82
|
-
"axios": "^1.
|
|
83
|
-
"chalk": "^5.
|
|
82
|
+
"axios": "^1.13.2",
|
|
83
|
+
"chalk": "^5.6.2",
|
|
84
84
|
"cross-spawn": "^7.0.6",
|
|
85
85
|
"expo-server-sdk": "^3.15.0",
|
|
86
|
-
"fastify": "^5.
|
|
86
|
+
"fastify": "^5.6.2",
|
|
87
87
|
"fastify-type-provider-zod": "4.0.2",
|
|
88
88
|
"http-proxy": "^1.18.1",
|
|
89
89
|
"http-proxy-middleware": "^3.0.5",
|
|
90
|
-
"ink": "^6.1
|
|
90
|
+
"ink": "^6.5.1",
|
|
91
91
|
"open": "^10.2.0",
|
|
92
92
|
"ps-list": "^8.1.1",
|
|
93
93
|
"qrcode-terminal": "^0.12.0",
|
|
94
|
-
"react": "^19.
|
|
94
|
+
"react": "^19.2.0",
|
|
95
95
|
"socket.io-client": "^4.8.1",
|
|
96
|
-
"tar": "^7.
|
|
96
|
+
"tar": "^7.5.2",
|
|
97
97
|
"tmp": "^0.2.5",
|
|
98
98
|
"tweetnacl": "^1.0.3",
|
|
99
99
|
"zod": "^3.23.8"
|
|
@@ -101,15 +101,15 @@
|
|
|
101
101
|
"devDependencies": {
|
|
102
102
|
"@eslint/compat": "^1",
|
|
103
103
|
"@types/node": ">=20",
|
|
104
|
-
"cross-env": "^10.
|
|
104
|
+
"cross-env": "^10.1.0",
|
|
105
105
|
"dotenv": "^16.6.1",
|
|
106
106
|
"eslint": "^9",
|
|
107
107
|
"eslint-config-prettier": "^10",
|
|
108
108
|
"pkgroll": "^2.14.2",
|
|
109
|
-
"release-it": "^19.0.
|
|
109
|
+
"release-it": "^19.0.6",
|
|
110
110
|
"shx": "^0.3.3",
|
|
111
111
|
"ts-node": "^10",
|
|
112
|
-
"tsx": "^4.20.
|
|
112
|
+
"tsx": "^4.20.6",
|
|
113
113
|
"typescript": "^5",
|
|
114
114
|
"vitest": "^3.2.4"
|
|
115
115
|
},
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const crypto = require('crypto');
|
|
2
1
|
const fs = require('fs');
|
|
3
2
|
|
|
4
3
|
// Disable autoupdater (never works really)
|
|
@@ -13,34 +12,7 @@ function writeMessage(message) {
|
|
|
13
12
|
}
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
// Intercept
|
|
17
|
-
const originalRandomUUID = crypto.randomUUID;
|
|
18
|
-
Object.defineProperty(global, 'crypto', {
|
|
19
|
-
configurable: true,
|
|
20
|
-
enumerable: true,
|
|
21
|
-
get() {
|
|
22
|
-
return {
|
|
23
|
-
randomUUID: () => {
|
|
24
|
-
const uuid = originalRandomUUID();
|
|
25
|
-
writeMessage({ type: 'uuid', value: uuid });
|
|
26
|
-
return uuid;
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
Object.defineProperty(crypto, 'randomUUID', {
|
|
32
|
-
configurable: true,
|
|
33
|
-
enumerable: true,
|
|
34
|
-
get() {
|
|
35
|
-
return () => {
|
|
36
|
-
const uuid = originalRandomUUID();
|
|
37
|
-
writeMessage({ type: 'uuid', value: uuid });
|
|
38
|
-
return uuid;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
// Intercept fetch to track activity
|
|
15
|
+
// Intercept fetch to track thinking state
|
|
44
16
|
const originalFetch = global.fetch;
|
|
45
17
|
let fetchCounter = 0;
|
|
46
18
|
|
|
@@ -95,4 +67,7 @@ global.fetch = function(...args) {
|
|
|
95
67
|
Object.defineProperty(global.fetch, 'name', { value: 'fetch' });
|
|
96
68
|
Object.defineProperty(global.fetch, 'length', { value: originalFetch.length });
|
|
97
69
|
|
|
98
|
-
|
|
70
|
+
// Import global Claude Code CLI
|
|
71
|
+
const { getClaudeCliPath, runClaudeCli } = require('./claude_version_utils.cjs');
|
|
72
|
+
|
|
73
|
+
runClaudeCli(getClaudeCliPath());
|
|
@@ -10,4 +10,7 @@ global.setTimeout = function(callback, delay, ...args) {
|
|
|
10
10
|
Object.defineProperty(global.setTimeout, 'name', { value: 'setTimeout' });
|
|
11
11
|
Object.defineProperty(global.setTimeout, 'length', { value: originalSetTimeout.length });
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
// Import global Claude Code CLI
|
|
14
|
+
const { getClaudeCliPath, runClaudeCli } = require('./claude_version_utils.cjs');
|
|
15
|
+
|
|
16
|
+
runClaudeCli(getClaudeCliPath());
|