@holin-work/holin-cli 1.3.7 → 1.3.9
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/bin/holin-cli.js +52 -15
- package/package.json +1 -1
package/bin/holin-cli.js
CHANGED
|
@@ -34,6 +34,7 @@ const CONFIG_DIR = join(
|
|
|
34
34
|
const CREDENTIALS_FILE = join(CONFIG_DIR, "credentials.json");
|
|
35
35
|
const PID_FILE = join(CONFIG_DIR, "proxy.pid");
|
|
36
36
|
const LOG_FILE = join(CONFIG_DIR, "proxy.log");
|
|
37
|
+
const LOGIN_LOCK_FILE = join(CONFIG_DIR, "login.lock"); // written by auth login, read by proxy healthcheck
|
|
37
38
|
|
|
38
39
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
39
40
|
|
|
@@ -267,10 +268,26 @@ function createProxyServer() {
|
|
|
267
268
|
return;
|
|
268
269
|
}
|
|
269
270
|
|
|
270
|
-
// Build upstream request
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
const
|
|
271
|
+
// Build upstream request based on path
|
|
272
|
+
// /mcp → https://api.holin.work/icbu/mcp (MCP protocol)
|
|
273
|
+
// /api/* → https://api.holin.work/api/* (platform API, e.g. file upload)
|
|
274
|
+
const upstreamBase = "https://api.holin.work";
|
|
275
|
+
let upstreamPath;
|
|
276
|
+
let isMcpRequest = false;
|
|
277
|
+
if (req.url.startsWith("/mcp")) {
|
|
278
|
+
upstreamPath = "/icbu/mcp";
|
|
279
|
+
isMcpRequest = true;
|
|
280
|
+
} else if (req.url.startsWith("/api/")) {
|
|
281
|
+
upstreamPath = req.url;
|
|
282
|
+
} else {
|
|
283
|
+
// Unknown path
|
|
284
|
+
const body = JSON.stringify({ error: "not_found", message: `Unknown path: ${req.url}` });
|
|
285
|
+
res.writeHead(404, { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) });
|
|
286
|
+
res.end(body);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const upstreamUrl = new URL(upstreamBase);
|
|
274
291
|
const bodyBuffer = Buffer.concat(chunks);
|
|
275
292
|
const forwardHeaders = { ...req.headers };
|
|
276
293
|
delete forwardHeaders["transfer-encoding"];
|
|
@@ -281,25 +298,28 @@ function createProxyServer() {
|
|
|
281
298
|
const options = {
|
|
282
299
|
hostname: upstreamUrl.hostname,
|
|
283
300
|
port: upstreamUrl.port || 443,
|
|
284
|
-
path:
|
|
301
|
+
path: upstreamPath,
|
|
285
302
|
method: req.method,
|
|
286
303
|
headers: forwardHeaders,
|
|
287
304
|
};
|
|
288
305
|
|
|
289
|
-
// Parse request body to determine MCP method
|
|
306
|
+
// Parse request body to determine MCP method (only for MCP requests)
|
|
290
307
|
let mcpMethod = "";
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
308
|
+
if (isMcpRequest) {
|
|
309
|
+
try {
|
|
310
|
+
const parsed = JSON.parse(rawBody);
|
|
311
|
+
mcpMethod = parsed.method || "";
|
|
312
|
+
} catch { /* not JSON */ }
|
|
313
|
+
}
|
|
295
314
|
|
|
296
|
-
// Non-streaming methods should return JSON, not SSE
|
|
315
|
+
// Non-streaming methods should return JSON, not SSE (only for MCP requests)
|
|
297
316
|
// Accio expects JSON for initialize and tools/list
|
|
298
|
-
const shouldConvertToJson = mcpMethod === "initialize" || mcpMethod === "tools/list";
|
|
317
|
+
const shouldConvertToJson = isMcpRequest && (mcpMethod === "initialize" || mcpMethod === "tools/list");
|
|
299
318
|
|
|
300
319
|
const proxyReq = https.request(options, (proxyRes) => {
|
|
301
320
|
const elapsed = Date.now() - requestStart;
|
|
302
|
-
|
|
321
|
+
const routeLabel = isMcpRequest ? `mcp/${mcpMethod || "unknown"}` : req.url;
|
|
322
|
+
log(`[proxy] ← ${proxyRes.statusCode} ${elapsed}ms content-type=${proxyRes.headers["content-type"]} route=${routeLabel}`);
|
|
303
323
|
|
|
304
324
|
if (shouldConvertToJson && proxyRes.headers["content-type"]?.includes("text/event-stream")) {
|
|
305
325
|
// Collect SSE response and convert to JSON
|
|
@@ -389,6 +409,14 @@ async function cmdAuthLogin() {
|
|
|
389
409
|
process.exit(1);
|
|
390
410
|
}
|
|
391
411
|
|
|
412
|
+
// Write login lock so the running proxy does not exit during re-auth credential gap.
|
|
413
|
+
ensureConfigDir();
|
|
414
|
+
writeFileSync(LOGIN_LOCK_FILE, String(process.pid), "utf-8");
|
|
415
|
+
const removeLock = () => { try { if (existsSync(LOGIN_LOCK_FILE)) unlinkSync(LOGIN_LOCK_FILE); } catch {} };
|
|
416
|
+
process.on("exit", removeLock);
|
|
417
|
+
process.on("SIGINT", () => { removeLock(); process.exit(1); });
|
|
418
|
+
process.on("SIGTERM", () => { removeLock(); process.exit(1); });
|
|
419
|
+
|
|
392
420
|
console.log("[holin-cli] Verifying API Key...");
|
|
393
421
|
let result;
|
|
394
422
|
try {
|
|
@@ -431,6 +459,7 @@ async function cmdAuthLogin() {
|
|
|
431
459
|
console.error(`[holin-cli] Warning: Failed to start local proxy — ${err.message}`);
|
|
432
460
|
console.error("[holin-cli] You can start it manually with: holin-cli proxy start");
|
|
433
461
|
}
|
|
462
|
+
// Lock removed automatically via process.on("exit")
|
|
434
463
|
}
|
|
435
464
|
|
|
436
465
|
function cmdAuthLogout() {
|
|
@@ -529,12 +558,20 @@ function cmdProxyStart() {
|
|
|
529
558
|
return;
|
|
530
559
|
}
|
|
531
560
|
// Check if credentials file still exists (user may have logged out / uninstalled)
|
|
532
|
-
// Use a 30s grace period to avoid false positives during re-auth (old creds cleared, new ones not yet written)
|
|
561
|
+
// Use a 30s grace period to avoid false positives during re-auth (old creds cleared, new ones not yet written).
|
|
562
|
+
// Skip the grace period countdown entirely while a login.lock file exists — auth login is in progress.
|
|
533
563
|
const credsExist = existsSync(CREDENTIALS_FILE);
|
|
534
564
|
const creds = credsExist ? readCredentials() : null;
|
|
535
565
|
const hasValidKey = !!(creds?.api_key);
|
|
536
566
|
if (!hasValidKey) {
|
|
537
|
-
|
|
567
|
+
const loginInProgress = existsSync(LOGIN_LOCK_FILE);
|
|
568
|
+
if (loginInProgress) {
|
|
569
|
+
// auth login is actively running — reset timer, do not start grace period
|
|
570
|
+
if (credsMissingSince) {
|
|
571
|
+
log("[proxy] Login lock detected, resetting credentials grace period.");
|
|
572
|
+
credsMissingSince = null;
|
|
573
|
+
}
|
|
574
|
+
} else if (!credsMissingSince) {
|
|
538
575
|
credsMissingSince = Date.now();
|
|
539
576
|
log("[proxy] Credentials missing, starting 30s grace period...");
|
|
540
577
|
} else if (Date.now() - credsMissingSince > 30000) {
|