hocai-connect 0.14.0 → 0.15.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.
Files changed (2) hide show
  1. package/dist/cli.js +293 -94
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -16657,11 +16657,11 @@ var require_websocket_server = __commonJS({
16657
16657
  });
16658
16658
 
16659
16659
  // apps/mcp/src/cli.ts
16660
- var import_node_fs5 = require("node:fs");
16661
- var import_node_os4 = require("node:os");
16660
+ var import_node_fs6 = require("node:fs");
16661
+ var import_node_os5 = require("node:os");
16662
16662
  var import_node_path6 = require("node:path");
16663
16663
  var import_node_crypto3 = require("node:crypto");
16664
- var import_node_child_process3 = require("node:child_process");
16664
+ var import_node_child_process4 = require("node:child_process");
16665
16665
 
16666
16666
  // node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
16667
16667
  var import_node_process = __toESM(require("node:process"), 1);
@@ -32839,9 +32839,9 @@ function cutLogIfBig(file2, maxBytes = 1e6) {
32839
32839
  } catch {
32840
32840
  }
32841
32841
  }
32842
- function daemonPlan(platform2, o) {
32842
+ function daemonPlan(platform3, o) {
32843
32843
  const log = logPathFor(o.home);
32844
- if (platform2 === "darwin") {
32844
+ if (platform3 === "darwin") {
32845
32845
  return {
32846
32846
  label: LABEL_MAC,
32847
32847
  file: `${o.home}/Library/LaunchAgents/${LABEL_MAC}.plist`,
@@ -32875,7 +32875,7 @@ function daemonPlan(platform2, o) {
32875
32875
  log
32876
32876
  };
32877
32877
  }
32878
- if (platform2 === "linux") {
32878
+ if (platform3 === "linux") {
32879
32879
  return {
32880
32880
  label: TASK_NAME,
32881
32881
  file: `${o.home}/.config/systemd/user/${TASK_NAME}.service`,
@@ -32900,7 +32900,7 @@ WantedBy=default.target
32900
32900
  log
32901
32901
  };
32902
32902
  }
32903
- if (platform2 === "win32") {
32903
+ if (platform3 === "win32") {
32904
32904
  return {
32905
32905
  label: TASK_NAME,
32906
32906
  file: null,
@@ -32927,6 +32927,9 @@ WantedBy=default.target
32927
32927
  }
32928
32928
 
32929
32929
  // apps/mcp/src/agents.ts
32930
+ var import_node_child_process = require("node:child_process");
32931
+ var import_node_fs2 = require("node:fs");
32932
+ var import_node_os = require("node:os");
32930
32933
  var NHAN = { claude: "Claude Code", codex: "Codex" };
32931
32934
  function summarizeAgents(probes) {
32932
32935
  const dong = [];
@@ -32939,6 +32942,13 @@ function summarizeAgents(probes) {
32939
32942
  continue;
32940
32943
  }
32941
32944
  if (p.loggedIn === false) {
32945
+ if (p.name === "claude" && p.envAuth) {
32946
+ dong.push("\xB7 Claude Code: ch\u1EA1y trong Terminal b\u1EB1ng bi\u1EBFn m\xF4i tr\u01B0\u1EDDng, nh\u01B0ng CH\u01AFA \u0111\u0103ng nh\u1EADp");
32947
+ canhBao.push(
32948
+ "Claude Code tr\xEAn m\xE1y n\xE0y l\u1EA5y quy\u1EC1n t\u1EEB bi\u1EBFn m\xF4i tr\u01B0\u1EDDng c\u1EE7a shell (ANTHROPIC_AUTH_TOKEN ho\u1EB7c ANTHROPIC_API_KEY). D\u1ECBch v\u1EE5 n\u1EC1n c\u1EE7a hocAI kh\xF4ng n\u1EA1p h\u1ED3 s\u01A1 shell n\xEAn kh\xF4ng th\u1EA5y bi\u1EBFn \u0111\xF3 \u2014 ch\u1EA1y `claude auth login` \u0111\u1EC3 b\xE0i t\u1EADp ch\u1EA1y \u0111\u01B0\u1EE3c c\u1EA3 khi b\u1EA1n \u0111\xF3ng Terminal."
32949
+ );
32950
+ continue;
32951
+ }
32942
32952
  dong.push(`\xB7 ${NHAN[p.name]}: \u0111\xE3 c\xE0i, CH\u01AFA \u0111\u0103ng nh\u1EADp`);
32943
32953
  canhBao.push(
32944
32954
  p.name === "claude" ? "Claude Code ch\u01B0a \u0111\u0103ng nh\u1EADp. Ch\u1EA1y `claude auth login` r\u1ED3i quay l\u1EA1i \u2014 ch\u01B0a \u0111\u0103ng nh\u1EADp th\xEC b\xE0i t\u1EADp kh\xF4ng ch\u1EA1y \u0111\u01B0\u1EE3c." : "Codex ch\u01B0a \u0111\u0103ng nh\u1EADp. Ch\u1EA1y `codex login` n\u1EBFu b\u1EA1n \u0111\u1ECBnh d\xF9ng n\xF3."
@@ -32961,11 +32971,23 @@ function summarizeAgents(probes) {
32961
32971
  }
32962
32972
  return { dong, canhBao, chayDuocBai: claudeSanSang };
32963
32973
  }
32964
- function probeAgents(run) {
32965
- const co = (b) => run("command", ["-v", b]) !== null;
32974
+ function codexLoggedIn(io) {
32975
+ const tep = `${io.env.CODEX_HOME || `${io.home}/.codex`}/auth.json`;
32976
+ if (!io.exists(tep)) return false;
32977
+ const raw = io.readFile(tep);
32978
+ if (raw === null) return null;
32979
+ try {
32980
+ const j = JSON.parse(raw);
32981
+ return !!(j.tokens?.access_token || j.OPENAI_API_KEY);
32982
+ } catch {
32983
+ return null;
32984
+ }
32985
+ }
32986
+ function probeAgents(io) {
32987
+ const co = (b) => io.run("command", ["-v", b]) !== null;
32966
32988
  const probes = [];
32967
32989
  if (co("claude")) {
32968
- const raw = run("claude", ["auth", "status"]);
32990
+ const raw = io.run("claude", ["auth", "status"]);
32969
32991
  let loggedIn = null;
32970
32992
  if (raw !== null) {
32971
32993
  try {
@@ -32974,18 +32996,102 @@ function probeAgents(run) {
32974
32996
  } catch {
32975
32997
  }
32976
32998
  }
32977
- probes.push({ name: "claude", installed: true, runnable: raw !== null, loggedIn });
32999
+ const envAuth = !!(io.env.ANTHROPIC_AUTH_TOKEN || io.env.ANTHROPIC_API_KEY);
33000
+ probes.push({ name: "claude", installed: true, runnable: raw !== null, loggedIn, envAuth });
32978
33001
  }
32979
33002
  if (co("codex")) {
32980
- const ver = run("codex", ["--version"]);
32981
- probes.push({ name: "codex", installed: true, runnable: ver !== null, loggedIn: null });
33003
+ const ver = io.run("codex", ["--version"]);
33004
+ probes.push({ name: "codex", installed: true, runnable: ver !== null, loggedIn: codexLoggedIn(io) });
32982
33005
  }
32983
33006
  return probes;
32984
33007
  }
33008
+ function cachedProbe(io, now, ttl = { ready: 3e5, notReady: 2e4 }) {
33009
+ let luc = 0;
33010
+ let nho = null;
33011
+ let han = 0;
33012
+ return () => {
33013
+ const gio = now();
33014
+ if (nho && gio - luc <= han) return nho;
33015
+ nho = probeAgents(io);
33016
+ luc = gio;
33017
+ han = summarizeAgents(nho).chayDuocBai ? ttl.ready : ttl.notReady;
33018
+ return nho;
33019
+ };
33020
+ }
33021
+ function shellRun(cmd, args, opts) {
33022
+ try {
33023
+ const dong = [cmd, ...args].join(" ");
33024
+ const o = {
33025
+ encoding: "utf8",
33026
+ timeout: 8e3,
33027
+ stdio: ["ignore", "pipe", "ignore"],
33028
+ cwd: opts?.cwd,
33029
+ env: opts?.env
33030
+ };
33031
+ return (0, import_node_os.platform)() === "win32" ? (0, import_node_child_process.execSync)(dong, o) : (0, import_node_child_process.execSync)(dong, { ...o, shell: "/bin/sh" });
33032
+ } catch (e) {
33033
+ const ra = e.stdout;
33034
+ const chu = typeof ra === "string" ? ra : ra?.toString("utf8");
33035
+ return chu && chu.trim() ? chu : null;
33036
+ }
33037
+ }
33038
+ function realProbeIO() {
33039
+ return {
33040
+ run: shellRun,
33041
+ exists: import_node_fs2.existsSync,
33042
+ readFile: (t) => {
33043
+ try {
33044
+ return (0, import_node_fs2.readFileSync)(t, "utf8");
33045
+ } catch {
33046
+ return null;
33047
+ }
33048
+ },
33049
+ env: process.env,
33050
+ home: (0, import_node_os.homedir)()
33051
+ };
33052
+ }
33053
+
33054
+ // apps/mcp/src/permissions.ts
33055
+ var GUARDED_DIRS = ["Documents", "Desktop", "Downloads"];
33056
+ function permPlan(platform3, o) {
33057
+ const notice = [];
33058
+ const fixCache = [];
33059
+ const coSudo = platform3 !== "win32";
33060
+ if (o.alienCacheFiles > 0 && coSudo) {
33061
+ notice.push(
33062
+ `\xB7 M\u1EADt kh\u1EA9u m\xE1y \u2014 ch\u1EC9 \u0111\u1EC3 s\u1EEDa quy\u1EC1n \xEDt nh\u1EA5t ${o.alienCacheFiles} m\u1EE5c trong ${o.cacheDir}. Kh\xF4ng s\u1EEDa th\xEC l\u1EC7nh c\u1EADp nh\u1EADt sau n\xE0y s\u1EBD h\u1ECFng. B\u1ECF qua \u0111\u01B0\u1EE3c.`
33063
+ );
33064
+ fixCache.push(["sudo", "chown", "-R", o.user, o.cacheDir]);
33065
+ }
33066
+ if (platform3 === "darwin") {
33067
+ notice.push("\xB7 Quy\u1EC1n th\u01B0 m\u1EE5c \u2014 n\u1EBFu macOS h\u1ECFi, b\u1EA5m Cho ph\xE9p. Kh\xF4ng h\u1ECFi th\xEC kh\xF4ng c\u1EA7n l\xE0m g\xEC.");
33068
+ }
33069
+ if (o.needLogin) {
33070
+ notice.push("\xB7 Cho ph\xE9p trong tr\xECnh duy\u1EC7t \u2014 \u0111\u1EC3 nh\u1EADn ch\xECa kho\xE1 v\u1EC1 m\xE1y n\xE0y.");
33071
+ }
33072
+ return { notice, fixCache };
33073
+ }
33074
+ function updateNpxArgs(version2, cacheDir) {
33075
+ return ["--cache", cacheDir, "-y", `hocai-connect@${version2}`];
33076
+ }
33077
+ function cleanNpmEnv(env) {
33078
+ return Object.fromEntries(Object.entries(env).filter(([k]) => !k.startsWith("npm_config_")));
33079
+ }
33080
+ function blockedDirs(platform3, home, canRead) {
33081
+ if (platform3 !== "darwin") return [];
33082
+ return GUARDED_DIRS.filter((d) => !canRead(`${home}/${d}`));
33083
+ }
33084
+ function blockedNotice(dirs) {
33085
+ if (dirs.length === 0) return [];
33086
+ return [
33087
+ `D\u1ECBch v\u1EE5 n\u1EC1n kh\xF4ng \u0111\u1ECDc \u0111\u01B0\u1EE3c ${dirs.join(", ")} tr\xEAn m\xE1y n\xE0y.`,
33088
+ "B\xE0i t\u1EADp \u0111\u1EC3 trong nh\u1EEFng th\u01B0 m\u1EE5c \u0111\xF3 s\u1EBD kh\xF4ng ch\u1EA1y \u2014 ch\u1ECDn th\u01B0 m\u1EE5c kh\xE1c, v\xED d\u1EE5 ~/hocai-bai-tap, l\xE0 xong."
33089
+ ];
33090
+ }
32985
33091
 
32986
33092
  // apps/mcp/src/server.ts
32987
33093
  var import_promises = require("node:fs/promises");
32988
- var import_node_os = require("node:os");
33094
+ var import_node_os2 = require("node:os");
32989
33095
  var import_node_path = require("node:path");
32990
33096
 
32991
33097
  // node_modules/zod/v3/helpers/util.js
@@ -41106,7 +41212,7 @@ function buildServer(token) {
41106
41212
  },
41107
41213
  async ({ slug }) => text(await call(`/lessons/${slug}/homework`))
41108
41214
  );
41109
- const thuMucBai = (maDe) => (0, import_node_path.join)((0, import_node_os.homedir)(), "hocai-bai-tap", maDe);
41215
+ const thuMucBai = (maDe) => (0, import_node_path.join)((0, import_node_os2.homedir)(), "hocai-bai-tap", maDe);
41110
41216
  server.registerTool(
41111
41217
  "tai_file_de",
41112
41218
  {
@@ -41169,9 +41275,9 @@ function buildServer(token) {
41169
41275
  }
41170
41276
 
41171
41277
  // apps/mcp/src/http.ts
41172
- var import_node_fs3 = require("node:fs");
41278
+ var import_node_fs4 = require("node:fs");
41173
41279
  var import_node_http = __toESM(require("node:http"));
41174
- var import_node_child_process2 = require("node:child_process");
41280
+ var import_node_child_process3 = require("node:child_process");
41175
41281
 
41176
41282
  // node_modules/@hono/node-server/dist/constants-BLSFu_RU.mjs
41177
41283
  var X_ALREADY_SENT = "x-hono-already-sent";
@@ -43221,12 +43327,12 @@ var StreamableHTTPServerTransport = class {
43221
43327
  };
43222
43328
 
43223
43329
  // apps/mcp/src/http.ts
43224
- var import_node_os2 = require("node:os");
43330
+ var import_node_os3 = require("node:os");
43225
43331
  var import_node_path3 = require("node:path");
43226
43332
 
43227
43333
  // apps/mcp/src/job.ts
43228
43334
  var import_node_crypto = require("node:crypto");
43229
- var import_node_fs2 = require("node:fs");
43335
+ var import_node_fs3 = require("node:fs");
43230
43336
  var import_node_path2 = require("node:path");
43231
43337
  var MAX_RUNNING = 3;
43232
43338
  var TIMEOUT_MS = 15 * 60 * 1e3;
@@ -43235,7 +43341,7 @@ var XONG = /* @__PURE__ */ new Set(["DONE", "FAILED", "STOPPED"]);
43235
43341
  function createJobStore(o) {
43236
43342
  const maxRunning = o.maxRunning ?? MAX_RUNNING;
43237
43343
  const timeoutMs = o.timeoutMs ?? TIMEOUT_MS;
43238
- (0, import_node_fs2.mkdirSync)(o.dir, { recursive: true });
43344
+ (0, import_node_fs3.mkdirSync)(o.dir, { recursive: true });
43239
43345
  const jobs = /* @__PURE__ */ new Map();
43240
43346
  const handles = /* @__PURE__ */ new Map();
43241
43347
  const listeners = /* @__PURE__ */ new Map();
@@ -43243,7 +43349,7 @@ function createJobStore(o) {
43243
43349
  const file2 = (id) => (0, import_node_path2.join)(o.dir, `${id}.jsonl`);
43244
43350
  const push = (id, line) => {
43245
43351
  try {
43246
- (0, import_node_fs2.appendFileSync)(file2(id), line + "\n");
43352
+ (0, import_node_fs3.appendFileSync)(file2(id), line + "\n");
43247
43353
  } catch {
43248
43354
  }
43249
43355
  for (const fn of listeners.get(id) ?? []) fn(line);
@@ -43305,7 +43411,7 @@ function createJobStore(o) {
43305
43411
  startedAt: Date.now()
43306
43412
  };
43307
43413
  jobs.set(job.id, job);
43308
- (0, import_node_fs2.writeFileSync)(file2(job.id), JSON.stringify(job) + "\n");
43414
+ (0, import_node_fs3.writeFileSync)(file2(job.id), JSON.stringify(job) + "\n");
43309
43415
  if (dangChay() < maxRunning) start(job);
43310
43416
  else queue.push(job.id);
43311
43417
  return job;
@@ -43344,9 +43450,9 @@ function createJobStore(o) {
43344
43450
  }
43345
43451
 
43346
43452
  // apps/mcp/src/folder.ts
43347
- var import_node_child_process = require("node:child_process");
43453
+ var import_node_child_process2 = require("node:child_process");
43348
43454
  var runExec = (cmd, args) => new Promise((resolve, reject) => {
43349
- (0, import_node_child_process.execFile)(cmd, args, (err, stdout) => {
43455
+ (0, import_node_child_process2.execFile)(cmd, args, (err, stdout) => {
43350
43456
  const e = err;
43351
43457
  if (e && e.code === "ENOENT") {
43352
43458
  reject(e);
@@ -43356,14 +43462,14 @@ var runExec = (cmd, args) => new Promise((resolve, reject) => {
43356
43462
  });
43357
43463
  });
43358
43464
  var DIALOG_TITLE = "Ch\u1ECDn th\u01B0 m\u1EE5c cho AI l\xE0m vi\u1EC7c";
43359
- function cmdFor(platform2) {
43360
- if (platform2 === "darwin") {
43465
+ function cmdFor(platform3) {
43466
+ if (platform3 === "darwin") {
43361
43467
  return {
43362
43468
  cmd: "osascript",
43363
43469
  args: ["-e", `POSIX path of (choose folder with prompt "${DIALOG_TITLE}")`]
43364
43470
  };
43365
43471
  }
43366
- if (platform2 === "win32") {
43472
+ if (platform3 === "win32") {
43367
43473
  return {
43368
43474
  cmd: "powershell.exe",
43369
43475
  args: [
@@ -43379,9 +43485,9 @@ function cmdFor(platform2) {
43379
43485
  };
43380
43486
  }
43381
43487
  async function chooseFolder(o = {}) {
43382
- const platform2 = o.platform ?? process.platform;
43488
+ const platform3 = o.platform ?? process.platform;
43383
43489
  const exec = o.exec ?? runExec;
43384
- const { cmd, args } = cmdFor(platform2);
43490
+ const { cmd, args } = cmdFor(platform3);
43385
43491
  let r;
43386
43492
  try {
43387
43493
  r = await exec(cmd, args);
@@ -43423,7 +43529,7 @@ var TOOLS = {
43423
43529
  ],
43424
43530
  WORK: ["Read", "Write", "Edit", "Glob", "Grep"]
43425
43531
  };
43426
- var VERSION = "0.14.0";
43532
+ var VERSION = "0.15.0";
43427
43533
  function claudeArgs(job, o, port) {
43428
43534
  const mcp = {
43429
43535
  mcpServers: {
@@ -43455,7 +43561,7 @@ function claudeArgs(job, o, port) {
43455
43561
  ];
43456
43562
  }
43457
43563
  function buildHttpServer(o) {
43458
- const spawn2 = o.spawn ?? import_node_child_process2.spawn;
43564
+ const spawn2 = o.spawn ?? import_node_child_process3.spawn;
43459
43565
  const dangChay = /* @__PURE__ */ new Set();
43460
43566
  const folders = o.folders;
43461
43567
  const json3 = (res, code, body) => {
@@ -43511,7 +43617,7 @@ function buildHttpServer(o) {
43511
43617
  return typeof a === "object" && a ? a.port : 0;
43512
43618
  };
43513
43619
  const store = createJobStore({
43514
- dir: o.jobsDir ?? (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".hocai", "jobs"),
43620
+ dir: o.jobsDir ?? (0, import_node_path3.join)((0, import_node_os3.homedir)(), ".hocai", "jobs"),
43515
43621
  maxRunning: o.maxRunning,
43516
43622
  run: (job, onLine) => {
43517
43623
  const child = spawn2(o.claudeBin, claudeArgs(job, o, port()), {
@@ -43565,7 +43671,7 @@ function buildHttpServer(o) {
43565
43671
  json3(res, 400, { error: "M\xE3 \u0111\u1EC1 kh\xF4ng h\u1EE3p l\u1EC7" });
43566
43672
  return;
43567
43673
  }
43568
- dir = (0, import_node_path3.join)((0, import_node_os2.homedir)(), "hocai-bai-tap", maDe);
43674
+ dir = (0, import_node_path3.join)((0, import_node_os3.homedir)(), "hocai-bai-tap", maDe);
43569
43675
  } else {
43570
43676
  const d = i.dir ?? "";
43571
43677
  if (!d.startsWith("/") || d.includes("..")) {
@@ -43584,7 +43690,7 @@ function buildHttpServer(o) {
43584
43690
  json3(res, 409, { error: "Ch\u1ED7 n\xE0y \u0111ang c\xF3 m\u1ED9t l\u01B0\u1EE3t ch\u1EA1y \u2014 ch\u1EDD n\xF3 xong \u0111\xE3" });
43585
43691
  return;
43586
43692
  }
43587
- (0, import_node_fs3.mkdirSync)(dir, { recursive: true });
43693
+ (0, import_node_fs4.mkdirSync)(dir, { recursive: true });
43588
43694
  const job = store.create({
43589
43695
  kind: i.kind,
43590
43696
  dir,
@@ -43628,7 +43734,11 @@ function buildHttpServer(o) {
43628
43734
  json3(res, 200, {
43629
43735
  ten: "hocai-connect",
43630
43736
  phienBan: VERSION,
43631
- user: me ? { ten: me.ten, ...hoi ? { khop: hoi === me.id } : {} } : me
43737
+ user: me ? { ten: me.ten, ...hoi ? { khop: hoi === me.id } : {} } : me,
43738
+ // Vắng mặt khoá này = helper bản cũ. Web phân biệt được `undefined` với
43739
+ // `[]`, nên đừng "cho lành" bằng cách trả mảng rỗng: rỗng nghĩa là máy
43740
+ // KHÔNG CÓ trợ lý nào, một câu hoàn toàn khác.
43741
+ ...o.agents ? { agents: o.agents() } : {}
43632
43742
  });
43633
43743
  return;
43634
43744
  }
@@ -43781,11 +43891,11 @@ function mergeClaudeDesktopConfig(existing, creds) {
43781
43891
  };
43782
43892
  return { ...base, mcpServers: { ...servers, hocai } };
43783
43893
  }
43784
- function claudeDesktopConfigPath(platform2, home, appdata) {
43785
- if (platform2 === "win32") {
43894
+ function claudeDesktopConfigPath(platform3, home, appdata) {
43895
+ if (platform3 === "win32") {
43786
43896
  return import_node_path4.win32.join(appdata ?? import_node_path4.win32.join(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
43787
43897
  }
43788
- if (platform2 === "darwin") {
43898
+ if (platform3 === "darwin") {
43789
43899
  return import_node_path4.posix.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
43790
43900
  }
43791
43901
  return import_node_path4.posix.join(home, ".config", "Claude", "claude_desktop_config.json");
@@ -43968,11 +44078,11 @@ function defaultOrigins(env) {
43968
44078
  }
43969
44079
 
43970
44080
  // apps/mcp/src/folder-registry.ts
43971
- var import_node_fs4 = require("node:fs");
43972
- var import_node_os3 = require("node:os");
44081
+ var import_node_fs5 = require("node:fs");
44082
+ var import_node_os4 = require("node:os");
43973
44083
  var import_node_crypto2 = require("node:crypto");
43974
44084
  var import_node_path5 = require("node:path");
43975
- var MAC_DINH = (0, import_node_path5.join)((0, import_node_os3.homedir)(), ".hocai", "folders.json");
44085
+ var MAC_DINH = (0, import_node_path5.join)((0, import_node_os4.homedir)(), ".hocai", "folders.json");
43976
44086
  function createFolderRegistry(o = {}) {
43977
44087
  if (!o.file && process.env.VITEST) {
43978
44088
  throw new Error(
@@ -44011,7 +44121,7 @@ function createFolderRegistry(o = {}) {
44011
44121
  function nap(file2) {
44012
44122
  let raw;
44013
44123
  try {
44014
- raw = (0, import_node_fs4.readFileSync)(file2, "utf8");
44124
+ raw = (0, import_node_fs5.readFileSync)(file2, "utf8");
44015
44125
  } catch {
44016
44126
  return { rows: [], daLoai: false };
44017
44127
  }
@@ -44023,13 +44133,13 @@ function nap(file2) {
44023
44133
  }
44024
44134
  if (!Array.isArray(data)) return { rows: [], daLoai: false };
44025
44135
  const giu = data.filter(
44026
- (r) => !!r && typeof r.id === "string" && typeof r.path === "string" && typeof r.label === "string" && typeof r.createdAt === "string" && (0, import_node_fs4.existsSync)(r.path)
44136
+ (r) => !!r && typeof r.id === "string" && typeof r.path === "string" && typeof r.label === "string" && typeof r.createdAt === "string" && (0, import_node_fs5.existsSync)(r.path)
44027
44137
  );
44028
44138
  return { rows: giu, daLoai: giu.length !== data.length };
44029
44139
  }
44030
44140
  function ghi(file2, rows) {
44031
- (0, import_node_fs4.mkdirSync)((0, import_node_path5.dirname)(file2), { recursive: true, mode: 448 });
44032
- (0, import_node_fs4.writeFileSync)(file2, JSON.stringify([...rows.values()], null, 2), { mode: 384 });
44141
+ (0, import_node_fs5.mkdirSync)((0, import_node_path5.dirname)(file2), { recursive: true, mode: 448 });
44142
+ (0, import_node_fs5.writeFileSync)(file2, JSON.stringify([...rows.values()], null, 2), { mode: 384 });
44033
44143
  }
44034
44144
 
44035
44145
  // node_modules/ws/wrapper.mjs
@@ -44074,6 +44184,7 @@ function createUplink(o) {
44074
44184
  s.send(JSON.stringify(o.hello()));
44075
44185
  canhIm();
44076
44186
  });
44187
+ s.on("ping", canhIm);
44077
44188
  s.on("message", (raw) => {
44078
44189
  canhIm();
44079
44190
  let msg;
@@ -44151,7 +44262,7 @@ function handleServerMessage(msg, deps) {
44151
44262
  }
44152
44263
 
44153
44264
  // apps/mcp/src/cli.ts
44154
- var HOCAI_DIR = (0, import_node_path6.join)((0, import_node_os4.homedir)(), ".hocai");
44265
+ var HOCAI_DIR = (0, import_node_path6.join)((0, import_node_os5.homedir)(), ".hocai");
44155
44266
  var CREDS = (0, import_node_path6.join)(HOCAI_DIR, "config.json");
44156
44267
  var APP_DIR = (0, import_node_path6.join)(HOCAI_DIR, "app");
44157
44268
  var APP_CLI = (0, import_node_path6.join)(APP_DIR, "cli.js");
@@ -44162,25 +44273,25 @@ var PORT = Number(process.env.MCP_HTTP_PORT ?? 4100);
44162
44273
  var say = (s) => process.stderr.write(s + "\n");
44163
44274
  function loadCreds() {
44164
44275
  try {
44165
- const c = JSON.parse((0, import_node_fs5.readFileSync)(CREDS, "utf8"));
44276
+ const c = JSON.parse((0, import_node_fs6.readFileSync)(CREDS, "utf8"));
44166
44277
  return c.token && c.api ? { token: c.token, api: c.api } : null;
44167
44278
  } catch {
44168
44279
  return null;
44169
44280
  }
44170
44281
  }
44171
44282
  function saveCreds(c) {
44172
- (0, import_node_fs5.mkdirSync)(HOCAI_DIR, { recursive: true, mode: 448 });
44173
- (0, import_node_fs5.writeFileSync)(CREDS, JSON.stringify(c, null, 2));
44283
+ (0, import_node_fs6.mkdirSync)(HOCAI_DIR, { recursive: true, mode: 448 });
44284
+ (0, import_node_fs6.writeFileSync)(CREDS, JSON.stringify(c, null, 2));
44174
44285
  try {
44175
- (0, import_node_fs5.chmodSync)(CREDS, 384);
44286
+ (0, import_node_fs6.chmodSync)(CREDS, 384);
44176
44287
  } catch {
44177
44288
  }
44178
44289
  }
44179
44290
  function openBrowser(url2) {
44180
- const p = (0, import_node_os4.platform)();
44291
+ const p = (0, import_node_os5.platform)();
44181
44292
  const cmd = p === "darwin" ? ["open", url2] : p === "win32" ? ["cmd", "/c", "start", "", url2] : ["xdg-open", url2];
44182
44293
  try {
44183
- (0, import_node_child_process3.spawn)(cmd[0], cmd.slice(1), { stdio: "ignore", detached: true }).unref();
44294
+ (0, import_node_child_process4.spawn)(cmd[0], cmd.slice(1), { stdio: "ignore", detached: true }).unref();
44184
44295
  } catch {
44185
44296
  }
44186
44297
  }
@@ -44201,28 +44312,28 @@ async function login() {
44201
44312
  return loadCreds();
44202
44313
  }
44203
44314
  function writeDesktop(creds) {
44204
- const path = claudeDesktopConfigPath((0, import_node_os4.platform)(), (0, import_node_os4.homedir)(), process.env.APPDATA);
44205
- if (!(0, import_node_fs5.existsSync)((0, import_node_path6.dirname)(path))) {
44315
+ const path = claudeDesktopConfigPath((0, import_node_os5.platform)(), (0, import_node_os5.homedir)(), process.env.APPDATA);
44316
+ if (!(0, import_node_fs6.existsSync)((0, import_node_path6.dirname)(path))) {
44206
44317
  say("\xB7 Claude Desktop: kh\xF4ng th\u1EA5y tr\xEAn m\xE1y, b\u1ECF qua");
44207
44318
  return;
44208
44319
  }
44209
44320
  let prev;
44210
44321
  try {
44211
- prev = JSON.parse((0, import_node_fs5.readFileSync)(path, "utf8"));
44322
+ prev = JSON.parse((0, import_node_fs6.readFileSync)(path, "utf8"));
44212
44323
  } catch {
44213
44324
  prev = void 0;
44214
44325
  }
44215
- (0, import_node_fs5.writeFileSync)(path, JSON.stringify(mergeClaudeDesktopConfig(prev, creds), null, 2));
44326
+ (0, import_node_fs6.writeFileSync)(path, JSON.stringify(mergeClaudeDesktopConfig(prev, creds), null, 2));
44216
44327
  say(`\u2713 Claude Desktop: \u0111\xE3 ghi ${path} (m\u1EDF l\u1EA1i Claude Desktop \u0111\u1EC3 n\xF3 nh\u1EADn)`);
44217
44328
  }
44218
44329
  function addClaudeCode(creds) {
44219
- const win = (0, import_node_os4.platform)() === "win32";
44330
+ const win = (0, import_node_os5.platform)() === "win32";
44220
44331
  try {
44221
- (0, import_node_child_process3.execFileSync)("claude", ["mcp", "remove", "hocai"], { stdio: "ignore", shell: win });
44332
+ (0, import_node_child_process4.execFileSync)("claude", ["mcp", "remove", "hocai"], { stdio: "ignore", shell: win });
44222
44333
  } catch {
44223
44334
  }
44224
44335
  try {
44225
- (0, import_node_child_process3.execFileSync)("claude", [
44336
+ (0, import_node_child_process4.execFileSync)("claude", [
44226
44337
  "mcp",
44227
44338
  "add",
44228
44339
  "hocai",
@@ -44244,28 +44355,13 @@ function addClaudeCode(creds) {
44244
44355
  function claudeBin() {
44245
44356
  if (process.env.CLAUDE_BIN) return process.env.CLAUDE_BIN;
44246
44357
  try {
44247
- return (0, import_node_child_process3.execSync)("command -v claude", { shell: "/bin/sh", encoding: "utf8" }).trim() || "claude";
44358
+ return (0, import_node_child_process4.execSync)("command -v claude", { shell: "/bin/sh", encoding: "utf8" }).trim() || "claude";
44248
44359
  } catch {
44249
44360
  return "claude";
44250
44361
  }
44251
44362
  }
44252
- function chayLay(cmd, args) {
44253
- try {
44254
- const dong = [cmd, ...args].join(" ");
44255
- const o = {
44256
- encoding: "utf8",
44257
- timeout: 8e3,
44258
- stdio: ["ignore", "pipe", "ignore"]
44259
- };
44260
- return (0, import_node_os4.platform)() === "win32" ? (0, import_node_child_process3.execSync)(dong, o) : (0, import_node_child_process3.execSync)(dong, { ...o, shell: "/bin/sh" });
44261
- } catch (e) {
44262
- const ra = e.stdout;
44263
- const chu = typeof ra === "string" ? ra : ra?.toString("utf8");
44264
- return chu && chu.trim() ? chu : null;
44265
- }
44266
- }
44267
44363
  function doTroLy() {
44268
- const r = summarizeAgents(probeAgents(chayLay));
44364
+ const r = summarizeAgents(probeAgents(realProbeIO()));
44269
44365
  if (r.dong.length) {
44270
44366
  say("");
44271
44367
  for (const d of r.dong) say(" " + d);
@@ -44276,13 +44372,75 @@ function doTroLy() {
44276
44372
  }
44277
44373
  return r.chayDuocBai;
44278
44374
  }
44279
- var plan = () => daemonPlan((0, import_node_os4.platform)(), { node: process.execPath, script: APP_CLI, home: (0, import_node_os4.homedir)() });
44375
+ function chayHien(cmd) {
44376
+ try {
44377
+ (0, import_node_child_process4.execFileSync)(cmd[0], cmd.slice(1), { stdio: "inherit", shell: (0, import_node_os5.platform)() === "win32" });
44378
+ return true;
44379
+ } catch {
44380
+ return false;
44381
+ }
44382
+ }
44383
+ var PERMS = (0, import_node_path6.join)(HOCAI_DIR, "perms.json");
44384
+ var NPM_CACHE = (0, import_node_path6.join)(HOCAI_DIR, "npm-cache");
44385
+ function npmCacheDir() {
44386
+ const ra = shellRun("npm", ["config", "get", "cache"], {
44387
+ cwd: (0, import_node_os5.homedir)(),
44388
+ env: cleanNpmEnv(process.env)
44389
+ });
44390
+ const chu = ra?.trim();
44391
+ if (chu && chu !== "undefined" && chu !== "null") return chu;
44392
+ return (0, import_node_os5.platform)() === "win32" ? (0, import_node_path6.join)(process.env.LOCALAPPDATA ?? (0, import_node_os5.homedir)(), "npm-cache") : (0, import_node_path6.join)((0, import_node_os5.homedir)(), ".npm");
44393
+ }
44394
+ function countAlienFiles(dir, tran = 500) {
44395
+ const toi = process.getuid?.();
44396
+ if (toi === void 0) return 0;
44397
+ let dem = 0;
44398
+ const di = (d) => {
44399
+ if (dem >= tran) return;
44400
+ let muc;
44401
+ try {
44402
+ muc = (0, import_node_fs6.readdirSync)(d);
44403
+ } catch {
44404
+ return;
44405
+ }
44406
+ for (const m of muc) {
44407
+ if (dem >= tran) return;
44408
+ const duong = (0, import_node_path6.join)(d, m);
44409
+ try {
44410
+ const st = (0, import_node_fs6.statSync)(duong);
44411
+ if (st.uid !== toi) {
44412
+ dem++;
44413
+ continue;
44414
+ }
44415
+ if (st.isDirectory()) di(duong);
44416
+ } catch {
44417
+ }
44418
+ }
44419
+ };
44420
+ di(dir);
44421
+ return dem;
44422
+ }
44423
+ function warnLines(lines) {
44424
+ if (lines.length === 0) return;
44425
+ say("");
44426
+ say("\u26A0 " + lines[0]);
44427
+ for (const d of lines.slice(1)) say(" " + d);
44428
+ }
44429
+ function doPerms() {
44430
+ try {
44431
+ const j = JSON.parse((0, import_node_fs6.readFileSync)(PERMS, "utf8"));
44432
+ return blockedNotice(Array.isArray(j.blocked) ? j.blocked : []);
44433
+ } catch {
44434
+ return [];
44435
+ }
44436
+ }
44437
+ var plan = () => daemonPlan((0, import_node_os5.platform)(), { node: process.execPath, script: APP_CLI, home: (0, import_node_os5.homedir)() });
44280
44438
  function nghi(ms) {
44281
44439
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
44282
44440
  }
44283
44441
  function chay(cmd, im = false) {
44284
44442
  try {
44285
- (0, import_node_child_process3.execFileSync)(cmd[0], cmd.slice(1), { stdio: "ignore", shell: (0, import_node_os4.platform)() === "win32" });
44443
+ (0, import_node_child_process4.execFileSync)(cmd[0], cmd.slice(1), { stdio: "ignore", shell: (0, import_node_os5.platform)() === "win32" });
44286
44444
  return true;
44287
44445
  } catch {
44288
44446
  if (!im) return false;
@@ -44295,17 +44453,17 @@ function caiDichVu() {
44295
44453
  say("\xB7 H\u1EC7 \u0111i\u1EC1u h\xE0nh n\xE0y ch\u01B0a h\u1ED7 tr\u1EE3 ch\u1EA1y \u1EA9n. D\xF9ng `npx hocai-connect run` v\xE0 \u0111\u1EC3 c\u1EEDa s\u1ED5 m\u1EDF.");
44296
44454
  return false;
44297
44455
  }
44298
- (0, import_node_fs5.mkdirSync)(APP_DIR, { recursive: true });
44299
- (0, import_node_fs5.mkdirSync)(LOG_DIR, { recursive: true });
44300
- (0, import_node_fs5.copyFileSync)(__filename, APP_CLI);
44456
+ (0, import_node_fs6.mkdirSync)(APP_DIR, { recursive: true });
44457
+ (0, import_node_fs6.mkdirSync)(LOG_DIR, { recursive: true });
44458
+ (0, import_node_fs6.copyFileSync)(__filename, APP_CLI);
44301
44459
  if (p.file && p.content) {
44302
- (0, import_node_fs5.mkdirSync)((0, import_node_path6.dirname)(p.file), { recursive: true });
44303
- (0, import_node_fs5.writeFileSync)(p.file, p.content);
44460
+ (0, import_node_fs6.mkdirSync)((0, import_node_path6.dirname)(p.file), { recursive: true });
44461
+ (0, import_node_fs6.writeFileSync)(p.file, p.content);
44304
44462
  }
44305
44463
  p.install.forEach((c, i) => {
44306
44464
  if (i === 0) {
44307
44465
  chay(c, true);
44308
- if ((0, import_node_os4.platform)() === "darwin") {
44466
+ if ((0, import_node_os5.platform)() === "darwin") {
44309
44467
  const nhan = `gui/${process.getuid?.() ?? 501}/${p.label}`;
44310
44468
  for (let lan = 0; lan < 20; lan++) {
44311
44469
  if (!chay(["launchctl", "print", nhan], true)) break;
@@ -44324,7 +44482,7 @@ function caiDichVu() {
44324
44482
  function goDichVu() {
44325
44483
  const p = plan();
44326
44484
  for (const c of p.uninstall) chay(c, true);
44327
- if (p.file) (0, import_node_fs5.rmSync)(p.file, { force: true });
44485
+ if (p.file) (0, import_node_fs6.rmSync)(p.file, { force: true });
44328
44486
  }
44329
44487
  var LENH = ["mcp", "run", "update", "status", "stop", "logs", "quen", "help", "--help", "-h", "entry"];
44330
44488
  var TRO_GIUP = `hocai-connect \u2014 n\u1ED1i Claude tr\xEAn m\xE1y b\u1EA1n v\xE0o hocAI
@@ -44359,7 +44517,7 @@ async function main() {
44359
44517
  }
44360
44518
  if (cmd === "quen") {
44361
44519
  goDichVu();
44362
- (0, import_node_fs5.rmSync)(CREDS, { force: true });
44520
+ (0, import_node_fs6.rmSync)(CREDS, { force: true });
44363
44521
  say("\u0110\xE3 ng\u1EAFt k\u1EBFt n\u1ED1i v\xE0 g\u1EE1 d\u1ECBch v\u1EE5 n\u1EC1n tr\xEAn m\xE1y n\xE0y.");
44364
44522
  say("Ch\u1EA1y l\u1EA1i `npx hocai-connect` \u0111\u1EC3 k\u1EBFt n\u1ED1i l\u1EA1i.");
44365
44523
  return;
@@ -44376,9 +44534,10 @@ async function main() {
44376
44534
  }
44377
44535
  say(`C\xF3 b\u1EA3n m\u1EDBi: ${VERSION} \u2192 ${moi}. \u0110ang c\u1EADp nh\u1EADt\u2026`);
44378
44536
  say("");
44379
- const r = (0, import_node_child_process3.spawnSync)("npx", ["-y", `hocai-connect@${moi}`], {
44537
+ (0, import_node_fs6.mkdirSync)(NPM_CACHE, { recursive: true });
44538
+ const r = (0, import_node_child_process4.spawnSync)("npx", updateNpxArgs(moi, NPM_CACHE), {
44380
44539
  stdio: "inherit",
44381
- shell: (0, import_node_os4.platform)() === "win32"
44540
+ shell: (0, import_node_os5.platform)() === "win32"
44382
44541
  });
44383
44542
  process.exit(r.status ?? 1);
44384
44543
  }
@@ -44400,6 +44559,7 @@ async function main() {
44400
44559
  }
44401
44560
  say(`\u2713 \u0110ang ch\u1EA1y \xB7 ${info.user?.ten ?? "(ch\xECa \u0111\xE3 b\u1ECB thu h\u1ED3i)"} \xB7 b\u1EA3n ${info.phienBan}`);
44402
44561
  say(` Nh\u1EADt k\xFD: ${plan().log}`);
44562
+ warnLines(doPerms());
44403
44563
  const moi = await latestVersion();
44404
44564
  if (moi && info.phienBan && newerVersion(moi, info.phienBan)) {
44405
44565
  say("");
@@ -44409,13 +44569,24 @@ async function main() {
44409
44569
  }
44410
44570
  if (cmd === "logs") {
44411
44571
  const f = plan().log;
44412
- if (!(0, import_node_fs5.existsSync)(f)) {
44572
+ if (!(0, import_node_fs6.existsSync)(f)) {
44413
44573
  say(`Ch\u01B0a c\xF3 nh\u1EADt k\xFD n\xE0o \u1EDF ${f}`);
44414
44574
  return;
44415
44575
  }
44416
- say((0, import_node_fs5.readFileSync)(f, "utf8").split("\n").slice(-40).join("\n"));
44576
+ say((0, import_node_fs6.readFileSync)(f, "utf8").split("\n").slice(-40).join("\n"));
44417
44577
  return;
44418
44578
  }
44579
+ const quyen = cmd === "run" ? null : permPlan((0, import_node_os5.platform)(), {
44580
+ alienCacheFiles: countAlienFiles(npmCacheDir()),
44581
+ cacheDir: npmCacheDir(),
44582
+ user: (0, import_node_os5.userInfo)().username,
44583
+ needLogin: !loadCreds()
44584
+ });
44585
+ if (quyen && quyen.notice.length) {
44586
+ say("");
44587
+ say("hocAI s\u1EAFp xin nh\u1EEFng th\u1EE9 sau \u2014 kh\xF4ng c\xF3 g\xEC ch\u1EA1y ng\u1EA7m sau l\u01B0ng b\u1EA1n:");
44588
+ for (const d of quyen.notice) say(" " + d);
44589
+ }
44419
44590
  const creds = loadCreds() ?? await login();
44420
44591
  process.env.HOCAI_API_URL = creds.api;
44421
44592
  const me = await fetchIdentity(creds.api, creds.token).catch(() => {
@@ -44460,10 +44631,34 @@ async function main() {
44460
44631
  say(` Xem nh\u1EADt k\xFD: npx hocai-connect logs`);
44461
44632
  process.exit(1);
44462
44633
  }
44634
+ warnLines(doPerms());
44635
+ }
44636
+ if (quyen && quyen.fixCache.length) {
44637
+ say("");
44638
+ say("C\xF2n m\u1ED9t vi\u1EC7c: cache npm tr\xEAn m\xE1y n\xE0y c\xF3 t\u1EC7p thu\u1ED9c ng\u01B0\u1EDDi d\xF9ng kh\xE1c,");
44639
+ say("n\xEAn l\u1EC7nh `npx hocai-connect update` sau n\xE0y s\u1EBD h\u1ECFng. S\u1EEDa c\u1EA7n m\u1EADt kh\u1EA9u m\xE1y.");
44640
+ say("Kh\xF4ng mu\u1ED1n th\xEC b\u1EA5m Ctrl-C \u2014 m\u1ECDi th\u1EE9 kh\xE1c \u0111\xE3 xong v\xE0 v\u1EABn ch\u1EA1y.");
44641
+ for (const lenh of quyen.fixCache) {
44642
+ const ok = chayHien(lenh);
44643
+ say(ok ? "\u2713 \u0110\xE3 s\u1EEDa quy\u1EC1n cache npm." : "\xB7 Ch\u01B0a s\u1EEDa \u0111\u01B0\u1EE3c. C\u1EADp nh\u1EADt sau n\xE0y v\u1EABn ch\u1EA1y \u0111\u01B0\u1EE3c v\xEC hocAI d\xF9ng cache ri\xEAng.");
44644
+ }
44463
44645
  }
44464
44646
  return;
44465
44647
  }
44466
- cutLogIfBig(logPathFor((0, import_node_os4.homedir)()));
44648
+ cutLogIfBig(logPathFor((0, import_node_os5.homedir)()));
44649
+ try {
44650
+ const chan = blockedDirs((0, import_node_os5.platform)(), (0, import_node_os5.homedir)(), (duong) => {
44651
+ try {
44652
+ (0, import_node_fs6.readdirSync)(duong);
44653
+ return true;
44654
+ } catch {
44655
+ return false;
44656
+ }
44657
+ });
44658
+ (0, import_node_fs6.mkdirSync)(HOCAI_DIR, { recursive: true });
44659
+ (0, import_node_fs6.writeFileSync)(PERMS, JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), blocked: chan }) + "\n");
44660
+ } catch {
44661
+ }
44467
44662
  const folders = createFolderRegistry();
44468
44663
  const srv = buildHttpServer({
44469
44664
  // MỘT sổ dùng chung cho cả đường trình duyệt lẫn đường lên socket. Hai sổ
@@ -44476,7 +44671,11 @@ async function main() {
44476
44671
  // chối trang mới trong im lặng. Xem `defaultOrigins`.
44477
44672
  origins: defaultOrigins(process.env.HOCAI_WEB_ORIGINS),
44478
44673
  claudeBin: claudeBin(),
44479
- me: identity.current
44674
+ me: identity.current,
44675
+ // Nhớ 5 phút. Đủ lâu để tuyến nóng không đốt máy học viên, đủ ngắn để
44676
+ // người vừa `claude auth login` không phải khởi động lại dịch vụ mới thấy
44677
+ // màn hình đổi.
44678
+ agents: cachedProbe(realProbeIO(), Date.now)
44480
44679
  });
44481
44680
  srv.on("error", (e) => {
44482
44681
  if (e.code !== "EADDRINUSE") throw e;
@@ -44494,8 +44693,8 @@ async function main() {
44494
44693
  // hai lượt nối là chuyện thường, và máy chủ phải nhận danh sách MỚI NHẤT.
44495
44694
  hello: () => ({
44496
44695
  t: "hello",
44497
- name: (0, import_node_os4.hostname)(),
44498
- os: (0, import_node_os4.platform)(),
44696
+ name: (0, import_node_os5.hostname)(),
44697
+ os: (0, import_node_os5.platform)(),
44499
44698
  folders: folders.list()
44500
44699
  }),
44501
44700
  onMessage: (msg) => handleServerMessage(msg, { folders, store: srv.store, send: uplink.send })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hocai-connect",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "Nối Claude trên máy học viên vào hocAI, và chạy việc AI ngay tại đó",
5
5
  "bin": {
6
6
  "hocai-connect": "bin.js"