@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.
Files changed (2) hide show
  1. package/bin/holin-cli.js +52 -15
  2. 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
- // MCP streamable-http has a single endpoint; forward directly to upstream path.
272
- // Accio requests http://127.0.0.1:18787/mcpupstream https://api.holin.work/icbu/mcp
273
- const upstreamUrl = new URL(UPSTREAM_URL);
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: upstreamUrl.pathname,
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
- try {
292
- const parsed = JSON.parse(rawBody);
293
- mcpMethod = parsed.method || "";
294
- } catch { /* not JSON */ }
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
- log(`[proxy] ${proxyRes.statusCode} ${elapsed}ms content-type=${proxyRes.headers["content-type"]} method=${mcpMethod}`);
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
- if (!credsMissingSince) {
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holin-work/holin-cli",
3
- "version": "1.3.7",
3
+ "version": "1.3.9",
4
4
  "description": "Holin CLI — Accio plugin authorization tool for ICBU batch listing",
5
5
  "type": "module",
6
6
  "bin": {