happy-coder 0.5.0 → 0.6.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/index.cjs +162 -33
- package/dist/index.mjs +162 -33
- package/dist/lib.cjs +1 -1
- package/dist/lib.mjs +1 -1
- package/dist/{types-BBpJNhIN.cjs → types-Bkw2UUhb.cjs} +0 -1
- package/dist/{types-DDjn6Ovv.mjs → types-Cqy5Dx2C.mjs} +0 -1
- package/package.json +2 -2
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-Bkw2UUhb.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');
|
|
@@ -28,6 +28,7 @@ var child_process = require('child_process');
|
|
|
28
28
|
var util = require('util');
|
|
29
29
|
var crypto = require('crypto');
|
|
30
30
|
var qrcode = require('qrcode-terminal');
|
|
31
|
+
var open = require('open');
|
|
31
32
|
var fs = require('fs');
|
|
32
33
|
var os$1 = require('os');
|
|
33
34
|
|
|
@@ -2261,7 +2262,7 @@ async function loop(opts) {
|
|
|
2261
2262
|
}
|
|
2262
2263
|
|
|
2263
2264
|
var name = "happy-coder";
|
|
2264
|
-
var version = "0.
|
|
2265
|
+
var version = "0.6.0";
|
|
2265
2266
|
var description = "Claude Code session sharing CLI";
|
|
2266
2267
|
var author = "Kirill Dubovitskiy";
|
|
2267
2268
|
var license = "MIT";
|
|
@@ -2329,7 +2330,7 @@ var dependencies = {
|
|
|
2329
2330
|
"http-proxy": "^1.18.1",
|
|
2330
2331
|
"http-proxy-middleware": "^3.0.5",
|
|
2331
2332
|
ink: "^6.1.0",
|
|
2332
|
-
|
|
2333
|
+
open: "^10.2.0",
|
|
2333
2334
|
"qrcode-terminal": "^0.12.0",
|
|
2334
2335
|
react: "^19.1.1",
|
|
2335
2336
|
"socket.io-client": "^4.8.1",
|
|
@@ -3214,8 +3215,69 @@ function displayQRCode(url) {
|
|
|
3214
3215
|
console.log("=".repeat(80));
|
|
3215
3216
|
}
|
|
3216
3217
|
|
|
3218
|
+
function generateWebAuthUrl(publicKey) {
|
|
3219
|
+
const publicKeyBase64 = types$1.encodeBase64(publicKey, "base64url");
|
|
3220
|
+
return `https://app.happy.engineering/terminal/connect#key=${publicKeyBase64}`;
|
|
3221
|
+
}
|
|
3222
|
+
|
|
3223
|
+
async function openBrowser(url) {
|
|
3224
|
+
try {
|
|
3225
|
+
if (!process.stdout.isTTY || process.env.CI || process.env.HEADLESS) {
|
|
3226
|
+
types$1.logger.debug("[browser] Headless environment detected, skipping browser open");
|
|
3227
|
+
return false;
|
|
3228
|
+
}
|
|
3229
|
+
types$1.logger.debug(`[browser] Attempting to open URL: ${url}`);
|
|
3230
|
+
await open(url);
|
|
3231
|
+
types$1.logger.debug("[browser] Browser opened successfully");
|
|
3232
|
+
return true;
|
|
3233
|
+
} catch (error) {
|
|
3234
|
+
types$1.logger.debug("[browser] Failed to open browser:", error);
|
|
3235
|
+
return false;
|
|
3236
|
+
}
|
|
3237
|
+
}
|
|
3238
|
+
|
|
3239
|
+
const AuthSelector = ({ onSelect, onCancel }) => {
|
|
3240
|
+
const [selectedIndex, setSelectedIndex] = React.useState(0);
|
|
3241
|
+
const options = [
|
|
3242
|
+
{
|
|
3243
|
+
method: "mobile",
|
|
3244
|
+
label: "Mobile App"
|
|
3245
|
+
},
|
|
3246
|
+
{
|
|
3247
|
+
method: "web",
|
|
3248
|
+
label: "Web Browser"
|
|
3249
|
+
}
|
|
3250
|
+
];
|
|
3251
|
+
ink.useInput((input, key) => {
|
|
3252
|
+
if (key.upArrow) {
|
|
3253
|
+
setSelectedIndex((prev) => Math.max(0, prev - 1));
|
|
3254
|
+
} else if (key.downArrow) {
|
|
3255
|
+
setSelectedIndex((prev) => Math.min(options.length - 1, prev + 1));
|
|
3256
|
+
} else if (key.return) {
|
|
3257
|
+
onSelect(options[selectedIndex].method);
|
|
3258
|
+
} else if (key.escape || key.ctrl && input === "c") {
|
|
3259
|
+
onCancel();
|
|
3260
|
+
} else if (input === "1") {
|
|
3261
|
+
setSelectedIndex(0);
|
|
3262
|
+
onSelect("mobile");
|
|
3263
|
+
} else if (input === "2") {
|
|
3264
|
+
setSelectedIndex(1);
|
|
3265
|
+
onSelect("web");
|
|
3266
|
+
}
|
|
3267
|
+
});
|
|
3268
|
+
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) => {
|
|
3269
|
+
const isSelected = selectedIndex === index;
|
|
3270
|
+
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));
|
|
3271
|
+
})), /* @__PURE__ */ React.createElement(ink.Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(ink.Text, { dimColor: true }, "Use arrows or 1-2 to select, Enter to confirm")));
|
|
3272
|
+
};
|
|
3273
|
+
|
|
3217
3274
|
async function doAuth() {
|
|
3218
|
-
console.
|
|
3275
|
+
console.clear();
|
|
3276
|
+
const authMethod = await selectAuthenticationMethod();
|
|
3277
|
+
if (!authMethod) {
|
|
3278
|
+
console.log("\nAuthentication cancelled.\n");
|
|
3279
|
+
return null;
|
|
3280
|
+
}
|
|
3219
3281
|
const secret = new Uint8Array(node_crypto.randomBytes(32));
|
|
3220
3282
|
const keypair = tweetnacl.box.keyPair.fromSecretKey(secret);
|
|
3221
3283
|
try {
|
|
@@ -3226,38 +3288,106 @@ async function doAuth() {
|
|
|
3226
3288
|
console.log("Failed to create authentication request, please try again later.");
|
|
3227
3289
|
return null;
|
|
3228
3290
|
}
|
|
3229
|
-
|
|
3291
|
+
if (authMethod === "mobile") {
|
|
3292
|
+
return await doMobileAuth(keypair);
|
|
3293
|
+
} else {
|
|
3294
|
+
return await doWebAuth(keypair);
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
function selectAuthenticationMethod() {
|
|
3298
|
+
return new Promise((resolve) => {
|
|
3299
|
+
let hasResolved = false;
|
|
3300
|
+
const onSelect = (method) => {
|
|
3301
|
+
if (!hasResolved) {
|
|
3302
|
+
hasResolved = true;
|
|
3303
|
+
app.unmount();
|
|
3304
|
+
resolve(method);
|
|
3305
|
+
}
|
|
3306
|
+
};
|
|
3307
|
+
const onCancel = () => {
|
|
3308
|
+
if (!hasResolved) {
|
|
3309
|
+
hasResolved = true;
|
|
3310
|
+
app.unmount();
|
|
3311
|
+
resolve(null);
|
|
3312
|
+
}
|
|
3313
|
+
};
|
|
3314
|
+
const app = ink.render(React.createElement(AuthSelector, { onSelect, onCancel }), {
|
|
3315
|
+
exitOnCtrlC: false,
|
|
3316
|
+
patchConsole: false
|
|
3317
|
+
});
|
|
3318
|
+
});
|
|
3319
|
+
}
|
|
3320
|
+
async function doMobileAuth(keypair) {
|
|
3321
|
+
console.clear();
|
|
3322
|
+
console.log("\nMobile Authentication\n");
|
|
3323
|
+
console.log("Scan this QR code with your Happy mobile app:\n");
|
|
3230
3324
|
const authUrl = "happy://terminal?" + types$1.encodeBase64Url(keypair.publicKey);
|
|
3231
3325
|
displayQRCode(authUrl);
|
|
3232
|
-
console.log("\
|
|
3326
|
+
console.log("\nOr manually enter this URL:");
|
|
3233
3327
|
console.log(authUrl);
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3328
|
+
console.log("");
|
|
3329
|
+
return await waitForAuthentication(keypair);
|
|
3330
|
+
}
|
|
3331
|
+
async function doWebAuth(keypair) {
|
|
3332
|
+
console.clear();
|
|
3333
|
+
console.log("\nWeb Authentication\n");
|
|
3334
|
+
const webUrl = generateWebAuthUrl(keypair.publicKey);
|
|
3335
|
+
console.log("Opening your browser...");
|
|
3336
|
+
const browserOpened = await openBrowser(webUrl);
|
|
3337
|
+
if (browserOpened) {
|
|
3338
|
+
console.log("\u2713 Browser opened\n");
|
|
3339
|
+
console.log("Complete authentication in your browser window.");
|
|
3340
|
+
} else {
|
|
3341
|
+
console.log("Could not open browser automatically.\n");
|
|
3342
|
+
console.log("Please open this URL manually:");
|
|
3343
|
+
console.log(webUrl);
|
|
3344
|
+
}
|
|
3345
|
+
console.log("");
|
|
3346
|
+
return await waitForAuthentication(keypair);
|
|
3347
|
+
}
|
|
3348
|
+
async function waitForAuthentication(keypair) {
|
|
3349
|
+
process.stdout.write("Waiting for authentication");
|
|
3350
|
+
let dots = 0;
|
|
3351
|
+
let cancelled = false;
|
|
3352
|
+
const handleInterrupt = () => {
|
|
3353
|
+
cancelled = true;
|
|
3354
|
+
console.log("\n\nAuthentication cancelled.");
|
|
3355
|
+
process.exit(0);
|
|
3356
|
+
};
|
|
3357
|
+
process.on("SIGINT", handleInterrupt);
|
|
3358
|
+
try {
|
|
3359
|
+
while (!cancelled) {
|
|
3360
|
+
try {
|
|
3361
|
+
const response = await axios.post(`${types$1.configuration.serverUrl}/v1/auth/request`, {
|
|
3362
|
+
publicKey: types$1.encodeBase64(keypair.publicKey)
|
|
3363
|
+
});
|
|
3364
|
+
if (response.data.state === "authorized") {
|
|
3365
|
+
let token = response.data.token;
|
|
3366
|
+
let r = types$1.decodeBase64(response.data.response);
|
|
3367
|
+
let decrypted = decryptWithEphemeralKey(r, keypair.secretKey);
|
|
3368
|
+
if (decrypted) {
|
|
3369
|
+
const credentials = {
|
|
3370
|
+
secret: decrypted,
|
|
3371
|
+
token
|
|
3372
|
+
};
|
|
3373
|
+
await writeCredentials(credentials);
|
|
3374
|
+
console.log("\n\n\u2713 Authentication successful\n");
|
|
3375
|
+
return credentials;
|
|
3376
|
+
} else {
|
|
3377
|
+
console.log("\n\nFailed to decrypt response. Please try again.");
|
|
3378
|
+
return null;
|
|
3379
|
+
}
|
|
3254
3380
|
}
|
|
3381
|
+
} catch (error) {
|
|
3382
|
+
console.log("\n\nFailed to check authentication status. Please try again.");
|
|
3383
|
+
return null;
|
|
3255
3384
|
}
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3385
|
+
process.stdout.write("\rWaiting for authentication" + ".".repeat(dots % 3 + 1) + " ");
|
|
3386
|
+
dots++;
|
|
3387
|
+
await types$1.delay(1e3);
|
|
3259
3388
|
}
|
|
3260
|
-
|
|
3389
|
+
} finally {
|
|
3390
|
+
process.off("SIGINT", handleInterrupt);
|
|
3261
3391
|
}
|
|
3262
3392
|
return null;
|
|
3263
3393
|
}
|
|
@@ -3488,12 +3618,11 @@ class ApiDaemonSession extends node_events.EventEmitter {
|
|
|
3488
3618
|
this.stopKeepAlive();
|
|
3489
3619
|
this.keepAliveInterval = setInterval(() => {
|
|
3490
3620
|
const payload = {
|
|
3491
|
-
type: "machine-scoped",
|
|
3492
3621
|
machineId: this.machineIdentity.machineId,
|
|
3493
3622
|
time: Date.now()
|
|
3494
3623
|
};
|
|
3495
|
-
types$1.logger.debugLargeJson(`[DAEMON SESSION] Emitting
|
|
3496
|
-
this.socket.emit("
|
|
3624
|
+
types$1.logger.debugLargeJson(`[DAEMON SESSION] Emitting machine-alive`, payload);
|
|
3625
|
+
this.socket.emit("machine-alive", payload);
|
|
3497
3626
|
}, 2e4);
|
|
3498
3627
|
}
|
|
3499
3628
|
stopKeepAlive() {
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { l as logger, d as backoff, e as delay, R as RawJSONLinesSchema, c as configuration, f as encodeBase64, A as ApiClient, g as encodeBase64Url, h as decodeBase64, j as encrypt, b as initializeConfiguration, i as initLoggerWithGlobalConfiguration } from './types-
|
|
2
|
+
import { l as logger, d as backoff, e as delay, R as RawJSONLinesSchema, c as configuration, f as encodeBase64, A as ApiClient, g as encodeBase64Url, h as decodeBase64, j as encrypt, b as initializeConfiguration, i as initLoggerWithGlobalConfiguration } from './types-Cqy5Dx2C.mjs';
|
|
3
3
|
import { randomUUID, randomBytes } from 'node:crypto';
|
|
4
4
|
import { spawn, execSync } from 'node:child_process';
|
|
5
5
|
import { resolve, join, dirname as dirname$1 } from 'node:path';
|
|
@@ -27,6 +27,7 @@ import { spawn as spawn$1, exec, execSync as execSync$1 } from 'child_process';
|
|
|
27
27
|
import { promisify } from 'util';
|
|
28
28
|
import crypto, { createHash } from 'crypto';
|
|
29
29
|
import qrcode from 'qrcode-terminal';
|
|
30
|
+
import open from 'open';
|
|
30
31
|
import { existsSync as existsSync$1, readFileSync as readFileSync$1, writeFileSync, unlinkSync, mkdirSync as mkdirSync$1, chmodSync } from 'fs';
|
|
31
32
|
import { hostname, homedir as homedir$1 } from 'os';
|
|
32
33
|
|
|
@@ -2240,7 +2241,7 @@ async function loop(opts) {
|
|
|
2240
2241
|
}
|
|
2241
2242
|
|
|
2242
2243
|
var name = "happy-coder";
|
|
2243
|
-
var version = "0.
|
|
2244
|
+
var version = "0.6.0";
|
|
2244
2245
|
var description = "Claude Code session sharing CLI";
|
|
2245
2246
|
var author = "Kirill Dubovitskiy";
|
|
2246
2247
|
var license = "MIT";
|
|
@@ -2308,7 +2309,7 @@ var dependencies = {
|
|
|
2308
2309
|
"http-proxy": "^1.18.1",
|
|
2309
2310
|
"http-proxy-middleware": "^3.0.5",
|
|
2310
2311
|
ink: "^6.1.0",
|
|
2311
|
-
|
|
2312
|
+
open: "^10.2.0",
|
|
2312
2313
|
"qrcode-terminal": "^0.12.0",
|
|
2313
2314
|
react: "^19.1.1",
|
|
2314
2315
|
"socket.io-client": "^4.8.1",
|
|
@@ -3193,8 +3194,69 @@ function displayQRCode(url) {
|
|
|
3193
3194
|
console.log("=".repeat(80));
|
|
3194
3195
|
}
|
|
3195
3196
|
|
|
3197
|
+
function generateWebAuthUrl(publicKey) {
|
|
3198
|
+
const publicKeyBase64 = encodeBase64(publicKey, "base64url");
|
|
3199
|
+
return `https://app.happy.engineering/terminal/connect#key=${publicKeyBase64}`;
|
|
3200
|
+
}
|
|
3201
|
+
|
|
3202
|
+
async function openBrowser(url) {
|
|
3203
|
+
try {
|
|
3204
|
+
if (!process.stdout.isTTY || process.env.CI || process.env.HEADLESS) {
|
|
3205
|
+
logger.debug("[browser] Headless environment detected, skipping browser open");
|
|
3206
|
+
return false;
|
|
3207
|
+
}
|
|
3208
|
+
logger.debug(`[browser] Attempting to open URL: ${url}`);
|
|
3209
|
+
await open(url);
|
|
3210
|
+
logger.debug("[browser] Browser opened successfully");
|
|
3211
|
+
return true;
|
|
3212
|
+
} catch (error) {
|
|
3213
|
+
logger.debug("[browser] Failed to open browser:", error);
|
|
3214
|
+
return false;
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3217
|
+
|
|
3218
|
+
const AuthSelector = ({ onSelect, onCancel }) => {
|
|
3219
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
3220
|
+
const options = [
|
|
3221
|
+
{
|
|
3222
|
+
method: "mobile",
|
|
3223
|
+
label: "Mobile App"
|
|
3224
|
+
},
|
|
3225
|
+
{
|
|
3226
|
+
method: "web",
|
|
3227
|
+
label: "Web Browser"
|
|
3228
|
+
}
|
|
3229
|
+
];
|
|
3230
|
+
useInput((input, key) => {
|
|
3231
|
+
if (key.upArrow) {
|
|
3232
|
+
setSelectedIndex((prev) => Math.max(0, prev - 1));
|
|
3233
|
+
} else if (key.downArrow) {
|
|
3234
|
+
setSelectedIndex((prev) => Math.min(options.length - 1, prev + 1));
|
|
3235
|
+
} else if (key.return) {
|
|
3236
|
+
onSelect(options[selectedIndex].method);
|
|
3237
|
+
} else if (key.escape || key.ctrl && input === "c") {
|
|
3238
|
+
onCancel();
|
|
3239
|
+
} else if (input === "1") {
|
|
3240
|
+
setSelectedIndex(0);
|
|
3241
|
+
onSelect("mobile");
|
|
3242
|
+
} else if (input === "2") {
|
|
3243
|
+
setSelectedIndex(1);
|
|
3244
|
+
onSelect("web");
|
|
3245
|
+
}
|
|
3246
|
+
});
|
|
3247
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", paddingY: 1 }, /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, null, "How would you like to authenticate?")), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, options.map((option, index) => {
|
|
3248
|
+
const isSelected = selectedIndex === index;
|
|
3249
|
+
return /* @__PURE__ */ React.createElement(Box, { key: option.method, marginY: 0 }, /* @__PURE__ */ React.createElement(Text, { color: isSelected ? "cyan" : "gray" }, isSelected ? "\u203A " : " ", index + 1, ". ", option.label));
|
|
3250
|
+
})), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Use arrows or 1-2 to select, Enter to confirm")));
|
|
3251
|
+
};
|
|
3252
|
+
|
|
3196
3253
|
async function doAuth() {
|
|
3197
|
-
console.
|
|
3254
|
+
console.clear();
|
|
3255
|
+
const authMethod = await selectAuthenticationMethod();
|
|
3256
|
+
if (!authMethod) {
|
|
3257
|
+
console.log("\nAuthentication cancelled.\n");
|
|
3258
|
+
return null;
|
|
3259
|
+
}
|
|
3198
3260
|
const secret = new Uint8Array(randomBytes(32));
|
|
3199
3261
|
const keypair = tweetnacl.box.keyPair.fromSecretKey(secret);
|
|
3200
3262
|
try {
|
|
@@ -3205,38 +3267,106 @@ async function doAuth() {
|
|
|
3205
3267
|
console.log("Failed to create authentication request, please try again later.");
|
|
3206
3268
|
return null;
|
|
3207
3269
|
}
|
|
3208
|
-
|
|
3270
|
+
if (authMethod === "mobile") {
|
|
3271
|
+
return await doMobileAuth(keypair);
|
|
3272
|
+
} else {
|
|
3273
|
+
return await doWebAuth(keypair);
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
function selectAuthenticationMethod() {
|
|
3277
|
+
return new Promise((resolve) => {
|
|
3278
|
+
let hasResolved = false;
|
|
3279
|
+
const onSelect = (method) => {
|
|
3280
|
+
if (!hasResolved) {
|
|
3281
|
+
hasResolved = true;
|
|
3282
|
+
app.unmount();
|
|
3283
|
+
resolve(method);
|
|
3284
|
+
}
|
|
3285
|
+
};
|
|
3286
|
+
const onCancel = () => {
|
|
3287
|
+
if (!hasResolved) {
|
|
3288
|
+
hasResolved = true;
|
|
3289
|
+
app.unmount();
|
|
3290
|
+
resolve(null);
|
|
3291
|
+
}
|
|
3292
|
+
};
|
|
3293
|
+
const app = render(React.createElement(AuthSelector, { onSelect, onCancel }), {
|
|
3294
|
+
exitOnCtrlC: false,
|
|
3295
|
+
patchConsole: false
|
|
3296
|
+
});
|
|
3297
|
+
});
|
|
3298
|
+
}
|
|
3299
|
+
async function doMobileAuth(keypair) {
|
|
3300
|
+
console.clear();
|
|
3301
|
+
console.log("\nMobile Authentication\n");
|
|
3302
|
+
console.log("Scan this QR code with your Happy mobile app:\n");
|
|
3209
3303
|
const authUrl = "happy://terminal?" + encodeBase64Url(keypair.publicKey);
|
|
3210
3304
|
displayQRCode(authUrl);
|
|
3211
|
-
console.log("\
|
|
3305
|
+
console.log("\nOr manually enter this URL:");
|
|
3212
3306
|
console.log(authUrl);
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3307
|
+
console.log("");
|
|
3308
|
+
return await waitForAuthentication(keypair);
|
|
3309
|
+
}
|
|
3310
|
+
async function doWebAuth(keypair) {
|
|
3311
|
+
console.clear();
|
|
3312
|
+
console.log("\nWeb Authentication\n");
|
|
3313
|
+
const webUrl = generateWebAuthUrl(keypair.publicKey);
|
|
3314
|
+
console.log("Opening your browser...");
|
|
3315
|
+
const browserOpened = await openBrowser(webUrl);
|
|
3316
|
+
if (browserOpened) {
|
|
3317
|
+
console.log("\u2713 Browser opened\n");
|
|
3318
|
+
console.log("Complete authentication in your browser window.");
|
|
3319
|
+
} else {
|
|
3320
|
+
console.log("Could not open browser automatically.\n");
|
|
3321
|
+
console.log("Please open this URL manually:");
|
|
3322
|
+
console.log(webUrl);
|
|
3323
|
+
}
|
|
3324
|
+
console.log("");
|
|
3325
|
+
return await waitForAuthentication(keypair);
|
|
3326
|
+
}
|
|
3327
|
+
async function waitForAuthentication(keypair) {
|
|
3328
|
+
process.stdout.write("Waiting for authentication");
|
|
3329
|
+
let dots = 0;
|
|
3330
|
+
let cancelled = false;
|
|
3331
|
+
const handleInterrupt = () => {
|
|
3332
|
+
cancelled = true;
|
|
3333
|
+
console.log("\n\nAuthentication cancelled.");
|
|
3334
|
+
process.exit(0);
|
|
3335
|
+
};
|
|
3336
|
+
process.on("SIGINT", handleInterrupt);
|
|
3337
|
+
try {
|
|
3338
|
+
while (!cancelled) {
|
|
3339
|
+
try {
|
|
3340
|
+
const response = await axios.post(`${configuration.serverUrl}/v1/auth/request`, {
|
|
3341
|
+
publicKey: encodeBase64(keypair.publicKey)
|
|
3342
|
+
});
|
|
3343
|
+
if (response.data.state === "authorized") {
|
|
3344
|
+
let token = response.data.token;
|
|
3345
|
+
let r = decodeBase64(response.data.response);
|
|
3346
|
+
let decrypted = decryptWithEphemeralKey(r, keypair.secretKey);
|
|
3347
|
+
if (decrypted) {
|
|
3348
|
+
const credentials = {
|
|
3349
|
+
secret: decrypted,
|
|
3350
|
+
token
|
|
3351
|
+
};
|
|
3352
|
+
await writeCredentials(credentials);
|
|
3353
|
+
console.log("\n\n\u2713 Authentication successful\n");
|
|
3354
|
+
return credentials;
|
|
3355
|
+
} else {
|
|
3356
|
+
console.log("\n\nFailed to decrypt response. Please try again.");
|
|
3357
|
+
return null;
|
|
3358
|
+
}
|
|
3233
3359
|
}
|
|
3360
|
+
} catch (error) {
|
|
3361
|
+
console.log("\n\nFailed to check authentication status. Please try again.");
|
|
3362
|
+
return null;
|
|
3234
3363
|
}
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3364
|
+
process.stdout.write("\rWaiting for authentication" + ".".repeat(dots % 3 + 1) + " ");
|
|
3365
|
+
dots++;
|
|
3366
|
+
await delay(1e3);
|
|
3238
3367
|
}
|
|
3239
|
-
|
|
3368
|
+
} finally {
|
|
3369
|
+
process.off("SIGINT", handleInterrupt);
|
|
3240
3370
|
}
|
|
3241
3371
|
return null;
|
|
3242
3372
|
}
|
|
@@ -3467,12 +3597,11 @@ class ApiDaemonSession extends EventEmitter {
|
|
|
3467
3597
|
this.stopKeepAlive();
|
|
3468
3598
|
this.keepAliveInterval = setInterval(() => {
|
|
3469
3599
|
const payload = {
|
|
3470
|
-
type: "machine-scoped",
|
|
3471
3600
|
machineId: this.machineIdentity.machineId,
|
|
3472
3601
|
time: Date.now()
|
|
3473
3602
|
};
|
|
3474
|
-
logger.debugLargeJson(`[DAEMON SESSION] Emitting
|
|
3475
|
-
this.socket.emit("
|
|
3603
|
+
logger.debugLargeJson(`[DAEMON SESSION] Emitting machine-alive`, payload);
|
|
3604
|
+
this.socket.emit("machine-alive", payload);
|
|
3476
3605
|
}, 2e4);
|
|
3477
3606
|
}
|
|
3478
3607
|
stopKeepAlive() {
|
package/dist/lib.cjs
CHANGED
package/dist/lib.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema, c as configuration, i as initLoggerWithGlobalConfiguration, b as initializeConfiguration, l as logger } from './types-
|
|
1
|
+
export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema, c as configuration, i as initLoggerWithGlobalConfiguration, b as initializeConfiguration, l as logger } from './types-Cqy5Dx2C.mjs';
|
|
2
2
|
import 'axios';
|
|
3
3
|
import 'chalk';
|
|
4
4
|
import 'fs';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "happy-coder",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Claude Code session sharing CLI",
|
|
5
5
|
"author": "Kirill Dubovitskiy",
|
|
6
6
|
"license": "MIT",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"http-proxy": "^1.18.1",
|
|
69
69
|
"http-proxy-middleware": "^3.0.5",
|
|
70
70
|
"ink": "^6.1.0",
|
|
71
|
-
"
|
|
71
|
+
"open": "^10.2.0",
|
|
72
72
|
"qrcode-terminal": "^0.12.0",
|
|
73
73
|
"react": "^19.1.1",
|
|
74
74
|
"socket.io-client": "^4.8.1",
|