hocai-connect 0.14.1 → 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 +292 -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.1";
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
@@ -44152,7 +44262,7 @@ function handleServerMessage(msg, deps) {
44152
44262
  }
44153
44263
 
44154
44264
  // apps/mcp/src/cli.ts
44155
- 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");
44156
44266
  var CREDS = (0, import_node_path6.join)(HOCAI_DIR, "config.json");
44157
44267
  var APP_DIR = (0, import_node_path6.join)(HOCAI_DIR, "app");
44158
44268
  var APP_CLI = (0, import_node_path6.join)(APP_DIR, "cli.js");
@@ -44163,25 +44273,25 @@ var PORT = Number(process.env.MCP_HTTP_PORT ?? 4100);
44163
44273
  var say = (s) => process.stderr.write(s + "\n");
44164
44274
  function loadCreds() {
44165
44275
  try {
44166
- const c = JSON.parse((0, import_node_fs5.readFileSync)(CREDS, "utf8"));
44276
+ const c = JSON.parse((0, import_node_fs6.readFileSync)(CREDS, "utf8"));
44167
44277
  return c.token && c.api ? { token: c.token, api: c.api } : null;
44168
44278
  } catch {
44169
44279
  return null;
44170
44280
  }
44171
44281
  }
44172
44282
  function saveCreds(c) {
44173
- (0, import_node_fs5.mkdirSync)(HOCAI_DIR, { recursive: true, mode: 448 });
44174
- (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));
44175
44285
  try {
44176
- (0, import_node_fs5.chmodSync)(CREDS, 384);
44286
+ (0, import_node_fs6.chmodSync)(CREDS, 384);
44177
44287
  } catch {
44178
44288
  }
44179
44289
  }
44180
44290
  function openBrowser(url2) {
44181
- const p = (0, import_node_os4.platform)();
44291
+ const p = (0, import_node_os5.platform)();
44182
44292
  const cmd = p === "darwin" ? ["open", url2] : p === "win32" ? ["cmd", "/c", "start", "", url2] : ["xdg-open", url2];
44183
44293
  try {
44184
- (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();
44185
44295
  } catch {
44186
44296
  }
44187
44297
  }
@@ -44202,28 +44312,28 @@ async function login() {
44202
44312
  return loadCreds();
44203
44313
  }
44204
44314
  function writeDesktop(creds) {
44205
- const path = claudeDesktopConfigPath((0, import_node_os4.platform)(), (0, import_node_os4.homedir)(), process.env.APPDATA);
44206
- 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))) {
44207
44317
  say("\xB7 Claude Desktop: kh\xF4ng th\u1EA5y tr\xEAn m\xE1y, b\u1ECF qua");
44208
44318
  return;
44209
44319
  }
44210
44320
  let prev;
44211
44321
  try {
44212
- prev = JSON.parse((0, import_node_fs5.readFileSync)(path, "utf8"));
44322
+ prev = JSON.parse((0, import_node_fs6.readFileSync)(path, "utf8"));
44213
44323
  } catch {
44214
44324
  prev = void 0;
44215
44325
  }
44216
- (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));
44217
44327
  say(`\u2713 Claude Desktop: \u0111\xE3 ghi ${path} (m\u1EDF l\u1EA1i Claude Desktop \u0111\u1EC3 n\xF3 nh\u1EADn)`);
44218
44328
  }
44219
44329
  function addClaudeCode(creds) {
44220
- const win = (0, import_node_os4.platform)() === "win32";
44330
+ const win = (0, import_node_os5.platform)() === "win32";
44221
44331
  try {
44222
- (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 });
44223
44333
  } catch {
44224
44334
  }
44225
44335
  try {
44226
- (0, import_node_child_process3.execFileSync)("claude", [
44336
+ (0, import_node_child_process4.execFileSync)("claude", [
44227
44337
  "mcp",
44228
44338
  "add",
44229
44339
  "hocai",
@@ -44245,28 +44355,13 @@ function addClaudeCode(creds) {
44245
44355
  function claudeBin() {
44246
44356
  if (process.env.CLAUDE_BIN) return process.env.CLAUDE_BIN;
44247
44357
  try {
44248
- 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";
44249
44359
  } catch {
44250
44360
  return "claude";
44251
44361
  }
44252
44362
  }
44253
- function chayLay(cmd, args) {
44254
- try {
44255
- const dong = [cmd, ...args].join(" ");
44256
- const o = {
44257
- encoding: "utf8",
44258
- timeout: 8e3,
44259
- stdio: ["ignore", "pipe", "ignore"]
44260
- };
44261
- 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" });
44262
- } catch (e) {
44263
- const ra = e.stdout;
44264
- const chu = typeof ra === "string" ? ra : ra?.toString("utf8");
44265
- return chu && chu.trim() ? chu : null;
44266
- }
44267
- }
44268
44363
  function doTroLy() {
44269
- const r = summarizeAgents(probeAgents(chayLay));
44364
+ const r = summarizeAgents(probeAgents(realProbeIO()));
44270
44365
  if (r.dong.length) {
44271
44366
  say("");
44272
44367
  for (const d of r.dong) say(" " + d);
@@ -44277,13 +44372,75 @@ function doTroLy() {
44277
44372
  }
44278
44373
  return r.chayDuocBai;
44279
44374
  }
44280
- 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)() });
44281
44438
  function nghi(ms) {
44282
44439
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
44283
44440
  }
44284
44441
  function chay(cmd, im = false) {
44285
44442
  try {
44286
- (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" });
44287
44444
  return true;
44288
44445
  } catch {
44289
44446
  if (!im) return false;
@@ -44296,17 +44453,17 @@ function caiDichVu() {
44296
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.");
44297
44454
  return false;
44298
44455
  }
44299
- (0, import_node_fs5.mkdirSync)(APP_DIR, { recursive: true });
44300
- (0, import_node_fs5.mkdirSync)(LOG_DIR, { recursive: true });
44301
- (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);
44302
44459
  if (p.file && p.content) {
44303
- (0, import_node_fs5.mkdirSync)((0, import_node_path6.dirname)(p.file), { recursive: true });
44304
- (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);
44305
44462
  }
44306
44463
  p.install.forEach((c, i) => {
44307
44464
  if (i === 0) {
44308
44465
  chay(c, true);
44309
- if ((0, import_node_os4.platform)() === "darwin") {
44466
+ if ((0, import_node_os5.platform)() === "darwin") {
44310
44467
  const nhan = `gui/${process.getuid?.() ?? 501}/${p.label}`;
44311
44468
  for (let lan = 0; lan < 20; lan++) {
44312
44469
  if (!chay(["launchctl", "print", nhan], true)) break;
@@ -44325,7 +44482,7 @@ function caiDichVu() {
44325
44482
  function goDichVu() {
44326
44483
  const p = plan();
44327
44484
  for (const c of p.uninstall) chay(c, true);
44328
- 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 });
44329
44486
  }
44330
44487
  var LENH = ["mcp", "run", "update", "status", "stop", "logs", "quen", "help", "--help", "-h", "entry"];
44331
44488
  var TRO_GIUP = `hocai-connect \u2014 n\u1ED1i Claude tr\xEAn m\xE1y b\u1EA1n v\xE0o hocAI
@@ -44360,7 +44517,7 @@ async function main() {
44360
44517
  }
44361
44518
  if (cmd === "quen") {
44362
44519
  goDichVu();
44363
- (0, import_node_fs5.rmSync)(CREDS, { force: true });
44520
+ (0, import_node_fs6.rmSync)(CREDS, { force: true });
44364
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.");
44365
44522
  say("Ch\u1EA1y l\u1EA1i `npx hocai-connect` \u0111\u1EC3 k\u1EBFt n\u1ED1i l\u1EA1i.");
44366
44523
  return;
@@ -44377,9 +44534,10 @@ async function main() {
44377
44534
  }
44378
44535
  say(`C\xF3 b\u1EA3n m\u1EDBi: ${VERSION} \u2192 ${moi}. \u0110ang c\u1EADp nh\u1EADt\u2026`);
44379
44536
  say("");
44380
- 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), {
44381
44539
  stdio: "inherit",
44382
- shell: (0, import_node_os4.platform)() === "win32"
44540
+ shell: (0, import_node_os5.platform)() === "win32"
44383
44541
  });
44384
44542
  process.exit(r.status ?? 1);
44385
44543
  }
@@ -44401,6 +44559,7 @@ async function main() {
44401
44559
  }
44402
44560
  say(`\u2713 \u0110ang ch\u1EA1y \xB7 ${info.user?.ten ?? "(ch\xECa \u0111\xE3 b\u1ECB thu h\u1ED3i)"} \xB7 b\u1EA3n ${info.phienBan}`);
44403
44561
  say(` Nh\u1EADt k\xFD: ${plan().log}`);
44562
+ warnLines(doPerms());
44404
44563
  const moi = await latestVersion();
44405
44564
  if (moi && info.phienBan && newerVersion(moi, info.phienBan)) {
44406
44565
  say("");
@@ -44410,13 +44569,24 @@ async function main() {
44410
44569
  }
44411
44570
  if (cmd === "logs") {
44412
44571
  const f = plan().log;
44413
- if (!(0, import_node_fs5.existsSync)(f)) {
44572
+ if (!(0, import_node_fs6.existsSync)(f)) {
44414
44573
  say(`Ch\u01B0a c\xF3 nh\u1EADt k\xFD n\xE0o \u1EDF ${f}`);
44415
44574
  return;
44416
44575
  }
44417
- 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"));
44418
44577
  return;
44419
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
+ }
44420
44590
  const creds = loadCreds() ?? await login();
44421
44591
  process.env.HOCAI_API_URL = creds.api;
44422
44592
  const me = await fetchIdentity(creds.api, creds.token).catch(() => {
@@ -44461,10 +44631,34 @@ async function main() {
44461
44631
  say(` Xem nh\u1EADt k\xFD: npx hocai-connect logs`);
44462
44632
  process.exit(1);
44463
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
+ }
44464
44645
  }
44465
44646
  return;
44466
44647
  }
44467
- 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
+ }
44468
44662
  const folders = createFolderRegistry();
44469
44663
  const srv = buildHttpServer({
44470
44664
  // MỘT sổ dùng chung cho cả đường trình duyệt lẫn đường lên socket. Hai sổ
@@ -44477,7 +44671,11 @@ async function main() {
44477
44671
  // chối trang mới trong im lặng. Xem `defaultOrigins`.
44478
44672
  origins: defaultOrigins(process.env.HOCAI_WEB_ORIGINS),
44479
44673
  claudeBin: claudeBin(),
44480
- 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)
44481
44679
  });
44482
44680
  srv.on("error", (e) => {
44483
44681
  if (e.code !== "EADDRINUSE") throw e;
@@ -44495,8 +44693,8 @@ async function main() {
44495
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.
44496
44694
  hello: () => ({
44497
44695
  t: "hello",
44498
- name: (0, import_node_os4.hostname)(),
44499
- os: (0, import_node_os4.platform)(),
44696
+ name: (0, import_node_os5.hostname)(),
44697
+ os: (0, import_node_os5.platform)(),
44500
44698
  folders: folders.list()
44501
44699
  }),
44502
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.1",
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"