hocai-connect 0.5.0 → 0.7.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 +270 -6
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -29147,6 +29147,153 @@ var StdioServerTransport = class {
29147
29147
  }
29148
29148
  };
29149
29149
 
29150
+ // apps/mcp/src/daemon.ts
29151
+ var LABEL_MAC = "vn.hocai.connect";
29152
+ var TASK_NAME = "hocai-connect";
29153
+ function daemonPlan(platform2, o) {
29154
+ const log = `${o.home}/.hocai/log/connect.log`;
29155
+ if (platform2 === "darwin") {
29156
+ return {
29157
+ label: LABEL_MAC,
29158
+ file: `${o.home}/Library/LaunchAgents/${LABEL_MAC}.plist`,
29159
+ // `KeepAlive` để launchd dựng lại khi tiến trình sập; `RunAtLoad` để nó
29160
+ // lên ngay lúc đăng nhập, không đợi ai gọi.
29161
+ content: `<?xml version="1.0" encoding="UTF-8"?>
29162
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
29163
+ <plist version="1.0">
29164
+ <dict>
29165
+ <key>Label</key><string>${LABEL_MAC}</string>
29166
+ <key>ProgramArguments</key>
29167
+ <array>
29168
+ <string>${o.node}</string>
29169
+ <string>${o.script}</string>
29170
+ <string>run</string>
29171
+ </array>
29172
+ <key>RunAtLoad</key><true/>
29173
+ <key>KeepAlive</key><true/>
29174
+ <key>StandardOutPath</key><string>${log}</string>
29175
+ <key>StandardErrorPath</key><string>${log}</string>
29176
+ </dict>
29177
+ </plist>
29178
+ `,
29179
+ // `bootout` trước `bootstrap`: nạp đè lên một nhãn đang chạy thì launchd
29180
+ // từ chối, và người dùng nhận "Load failed: 5: Input/output error".
29181
+ install: [
29182
+ ["launchctl", "bootout", `gui/${process.getuid?.() ?? 501}/${LABEL_MAC}`],
29183
+ ["launchctl", "bootstrap", `gui/${process.getuid?.() ?? 501}`, `${o.home}/Library/LaunchAgents/${LABEL_MAC}.plist`]
29184
+ ],
29185
+ uninstall: [["launchctl", "bootout", `gui/${process.getuid?.() ?? 501}/${LABEL_MAC}`]],
29186
+ log
29187
+ };
29188
+ }
29189
+ if (platform2 === "linux") {
29190
+ return {
29191
+ label: TASK_NAME,
29192
+ file: `${o.home}/.config/systemd/user/${TASK_NAME}.service`,
29193
+ content: `[Unit]
29194
+ Description=hocAI connect \u2014 n\u1ED1i Claude tr\xEAn m\xE1y n\xE0y v\xE0o hocAI
29195
+
29196
+ [Service]
29197
+ ExecStart=${o.node} ${o.script} run
29198
+ Restart=always
29199
+ RestartSec=5
29200
+ StandardOutput=append:${log}
29201
+ StandardError=append:${log}
29202
+
29203
+ [Install]
29204
+ WantedBy=default.target
29205
+ `,
29206
+ install: [
29207
+ ["systemctl", "--user", "daemon-reload"],
29208
+ ["systemctl", "--user", "enable", "--now", `${TASK_NAME}.service`]
29209
+ ],
29210
+ uninstall: [["systemctl", "--user", "disable", "--now", `${TASK_NAME}.service`]],
29211
+ log
29212
+ };
29213
+ }
29214
+ if (platform2 === "win32") {
29215
+ return {
29216
+ label: TASK_NAME,
29217
+ file: null,
29218
+ content: null,
29219
+ install: [
29220
+ [
29221
+ "schtasks",
29222
+ "/Create",
29223
+ "/F",
29224
+ "/SC",
29225
+ "ONLOGON",
29226
+ "/TN",
29227
+ TASK_NAME,
29228
+ "/TR",
29229
+ `"${o.node}" "${o.script}" run`
29230
+ ],
29231
+ ["schtasks", "/Run", "/TN", TASK_NAME]
29232
+ ],
29233
+ uninstall: [["schtasks", "/Delete", "/F", "/TN", TASK_NAME]],
29234
+ log
29235
+ };
29236
+ }
29237
+ return { label: TASK_NAME, file: null, content: null, install: [], uninstall: [], log };
29238
+ }
29239
+
29240
+ // apps/mcp/src/agents.ts
29241
+ var NHAN = { claude: "Claude Code", codex: "Codex" };
29242
+ function summarizeAgents(probes) {
29243
+ const dong = [];
29244
+ const canhBao = [];
29245
+ for (const p of probes) {
29246
+ if (!p.installed) continue;
29247
+ if (!p.runnable) {
29248
+ dong.push(`\xB7 ${NHAN[p.name]}: c\xF3 trong m\xE1y nh\u01B0ng g\u1ECDi kh\xF4ng ch\u1EA1y`);
29249
+ canhBao.push(`${NHAN[p.name]} c\xE0i l\u1ED7i tr\xEAn m\xE1y n\xE0y \u2014 g\u1EE1 r\u1ED3i c\xE0i l\u1EA1i, ho\u1EB7c b\u1ECF qua n\u1EBFu b\u1EA1n kh\xF4ng d\xF9ng n\xF3.`);
29250
+ continue;
29251
+ }
29252
+ if (p.loggedIn === false) {
29253
+ dong.push(`\xB7 ${NHAN[p.name]}: \u0111\xE3 c\xE0i, CH\u01AFA \u0111\u0103ng nh\u1EADp`);
29254
+ canhBao.push(
29255
+ 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."
29256
+ );
29257
+ continue;
29258
+ }
29259
+ dong.push(`\u2713 ${NHAN[p.name]}: s\u1EB5n s\xE0ng${p.loggedIn === null ? " (kh\xF4ng ki\u1EC3m \u0111\u01B0\u1EE3c \u0111\u0103ng nh\u1EADp)" : ""}`);
29260
+ }
29261
+ const claude = probes.find((p) => p.name === "claude");
29262
+ const claudeSanSang = !!claude && claude.installed && claude.runnable && claude.loggedIn !== false;
29263
+ const codexSanSang = probes.some((p) => p.name === "codex" && p.installed && p.runnable && p.loggedIn !== false);
29264
+ if (!probes.some((p) => p.installed)) {
29265
+ canhBao.push(
29266
+ "M\xE1y n\xE0y ch\u01B0a c\xE0i tr\u1EE3 l\xFD AI n\xE0o. hocAI ch\u1EA1y b\xE0i t\u1EADp b\u1EB1ng Claude Code \u2014 c\xE0i n\xF3 r\u1ED3i ch\u1EA1y l\u1EA1i l\u1EC7nh n\xE0y."
29267
+ );
29268
+ } else if (!claudeSanSang && codexSanSang) {
29269
+ canhBao.push(
29270
+ "M\xE1y n\xE0y c\xF3 Codex nh\u01B0ng hocAI ch\u01B0a ch\u1EA1y b\xE0i t\u1EADp b\u1EB1ng Codex \u0111\u01B0\u1EE3c \u2014 ph\u1EA7n \u0111\xF3 g\u1ECDi Claude Code. C\xE0i Claude Code \u0111\u1EC3 l\xE0m b\xE0i."
29271
+ );
29272
+ }
29273
+ return { dong, canhBao, chayDuocBai: claudeSanSang };
29274
+ }
29275
+ function probeAgents(run) {
29276
+ const co = (b) => run("command", ["-v", b]) !== null;
29277
+ const probes = [];
29278
+ if (co("claude")) {
29279
+ const raw = run("claude", ["auth", "status"]);
29280
+ let loggedIn = null;
29281
+ if (raw !== null) {
29282
+ try {
29283
+ const j = JSON.parse(raw);
29284
+ loggedIn = typeof j.loggedIn === "boolean" ? j.loggedIn : null;
29285
+ } catch {
29286
+ }
29287
+ }
29288
+ probes.push({ name: "claude", installed: true, runnable: raw !== null, loggedIn });
29289
+ }
29290
+ if (co("codex")) {
29291
+ const ver = run("codex", ["--version"]);
29292
+ probes.push({ name: "codex", installed: true, runnable: ver !== null, loggedIn: null });
29293
+ }
29294
+ return probes;
29295
+ }
29296
+
29150
29297
  // apps/mcp/src/server.ts
29151
29298
  var import_promises = require("node:fs/promises");
29152
29299
  var import_node_os = require("node:os");
@@ -39587,7 +39734,7 @@ var TOOLS = {
39587
39734
  ],
39588
39735
  WORK: ["Read", "Write", "Edit", "Glob", "Grep"]
39589
39736
  };
39590
- var VERSION = "0.5.0";
39737
+ var VERSION = "0.7.0";
39591
39738
  function claudeArgs(job, o, port) {
39592
39739
  const mcp = {
39593
39740
  mcpServers: {
@@ -40074,6 +40221,9 @@ async function askPortOwner(port, origin) {
40074
40221
  // apps/mcp/src/cli.ts
40075
40222
  var HOCAI_DIR = (0, import_node_path5.join)((0, import_node_os3.homedir)(), ".hocai");
40076
40223
  var CREDS = (0, import_node_path5.join)(HOCAI_DIR, "config.json");
40224
+ var APP_DIR = (0, import_node_path5.join)(HOCAI_DIR, "app");
40225
+ var APP_CLI = (0, import_node_path5.join)(APP_DIR, "cli.js");
40226
+ var LOG_DIR = (0, import_node_path5.join)(HOCAI_DIR, "log");
40077
40227
  var WEB = () => process.env.HOCAI_WEB_URL ?? "http://103.154.62.225:8090";
40078
40228
  var API_DEFAULT = () => process.env.HOCAI_API_URL ?? `${WEB()}/api`;
40079
40229
  var PORT = Number(process.env.MCP_HTTP_PORT ?? 4100);
@@ -40167,6 +40317,64 @@ function claudeBin() {
40167
40317
  return "claude";
40168
40318
  }
40169
40319
  }
40320
+ function chayLay(cmd, args) {
40321
+ try {
40322
+ const dong = [cmd, ...args].join(" ");
40323
+ const o = {
40324
+ encoding: "utf8",
40325
+ timeout: 8e3,
40326
+ stdio: ["ignore", "pipe", "ignore"]
40327
+ };
40328
+ return (0, import_node_os3.platform)() === "win32" ? (0, import_node_child_process3.execSync)(dong, o) : (0, import_node_child_process3.execSync)(dong, { ...o, shell: "/bin/sh" });
40329
+ } catch (e) {
40330
+ const ra = e.stdout;
40331
+ const chu = typeof ra === "string" ? ra : ra?.toString("utf8");
40332
+ return chu && chu.trim() ? chu : null;
40333
+ }
40334
+ }
40335
+ function doTroLy() {
40336
+ const r = summarizeAgents(probeAgents(chayLay));
40337
+ if (r.dong.length) {
40338
+ say("");
40339
+ for (const d of r.dong) say(" " + d);
40340
+ }
40341
+ for (const c of r.canhBao) {
40342
+ say("");
40343
+ say("\u26A0 " + c);
40344
+ }
40345
+ return r.chayDuocBai;
40346
+ }
40347
+ var plan = () => daemonPlan((0, import_node_os3.platform)(), { node: process.execPath, script: APP_CLI, home: (0, import_node_os3.homedir)() });
40348
+ function chay(cmd, im = false) {
40349
+ try {
40350
+ (0, import_node_child_process3.execFileSync)(cmd[0], cmd.slice(1), { stdio: "ignore", shell: (0, import_node_os3.platform)() === "win32" });
40351
+ return true;
40352
+ } catch {
40353
+ if (!im) return false;
40354
+ return false;
40355
+ }
40356
+ }
40357
+ function caiDichVu() {
40358
+ const p = plan();
40359
+ if (p.install.length === 0) {
40360
+ 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.");
40361
+ return false;
40362
+ }
40363
+ (0, import_node_fs3.mkdirSync)(APP_DIR, { recursive: true });
40364
+ (0, import_node_fs3.mkdirSync)(LOG_DIR, { recursive: true });
40365
+ (0, import_node_fs3.copyFileSync)(process.argv[1], APP_CLI);
40366
+ if (p.file && p.content) {
40367
+ (0, import_node_fs3.mkdirSync)((0, import_node_path5.dirname)(p.file), { recursive: true });
40368
+ (0, import_node_fs3.writeFileSync)(p.file, p.content);
40369
+ }
40370
+ p.install.forEach((c, i) => chay(c, i === 0));
40371
+ return true;
40372
+ }
40373
+ function goDichVu() {
40374
+ const p = plan();
40375
+ for (const c of p.uninstall) chay(c, true);
40376
+ if (p.file) (0, import_node_fs3.rmSync)(p.file, { force: true });
40377
+ }
40170
40378
  async function main() {
40171
40379
  const cmd = process.argv[2];
40172
40380
  if (cmd === "mcp") {
@@ -40174,8 +40382,39 @@ async function main() {
40174
40382
  return;
40175
40383
  }
40176
40384
  if (cmd === "quen") {
40385
+ goDichVu();
40177
40386
  (0, import_node_fs3.rmSync)(CREDS, { force: true });
40178
- say("\u0110\xE3 qu\xEAn token tr\xEAn m\xE1y n\xE0y. Ch\u1EA1y l\u1EA1i `npx hocai-connect` \u0111\u1EC3 k\u1EBFt n\u1ED1i l\u1EA1i.");
40387
+ 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.");
40388
+ say("Ch\u1EA1y l\u1EA1i `npx hocai-connect` \u0111\u1EC3 k\u1EBFt n\u1ED1i l\u1EA1i.");
40389
+ return;
40390
+ }
40391
+ if (cmd === "stop") {
40392
+ goDichVu();
40393
+ say("\u0110\xE3 t\u1EAFt v\xE0 g\u1EE1 d\u1ECBch v\u1EE5 n\u1EC1n. Ch\xECa kho\xE1 v\u1EABn gi\u1EEF \u2014 ch\u1EA1y `npx hocai-connect` l\xE0 n\u1ED1i l\u1EA1i ngay.");
40394
+ return;
40395
+ }
40396
+ if (cmd === "status") {
40397
+ const c = loadCreds();
40398
+ if (!c) {
40399
+ say("M\xE1y n\xE0y ch\u01B0a n\u1ED1i. Ch\u1EA1y `npx hocai-connect`.");
40400
+ process.exit(1);
40401
+ }
40402
+ const info = await askPortOwner(PORT, c.api.replace(/\/api$/, ""));
40403
+ if (!info) {
40404
+ say(`Kh\xF4ng c\xF3 g\xEC tr\u1EA3 l\u1EDDi \u1EDF c\u1ED5ng ${PORT} \u2014 d\u1ECBch v\u1EE5 n\u1EC1n ch\u01B0a ch\u1EA1y.`);
40405
+ process.exit(1);
40406
+ }
40407
+ say(`\u2713 \u0110ang ch\u1EA1y \xB7 ${info.user?.ten ?? "(ch\xECa \u0111\xE3 b\u1ECB thu h\u1ED3i)"} \xB7 b\u1EA3n ${info.phienBan}`);
40408
+ say(` Nh\u1EADt k\xFD: ${plan().log}`);
40409
+ return;
40410
+ }
40411
+ if (cmd === "logs") {
40412
+ const f = plan().log;
40413
+ if (!(0, import_node_fs3.existsSync)(f)) {
40414
+ say(`Ch\u01B0a c\xF3 nh\u1EADt k\xFD n\xE0o \u1EDF ${f}`);
40415
+ return;
40416
+ }
40417
+ say((0, import_node_fs3.readFileSync)(f, "utf8").split("\n").slice(-40).join("\n"));
40179
40418
  return;
40180
40419
  }
40181
40420
  const creds = loadCreds() ?? await login();
@@ -40198,6 +40437,32 @@ async function main() {
40198
40437
  });
40199
40438
  writeDesktop(creds);
40200
40439
  addClaudeCode(creds);
40440
+ if (cmd !== "run") {
40441
+ const xong = caiDichVu();
40442
+ const sanSang = doTroLy();
40443
+ say("");
40444
+ say("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
40445
+ say(sanSang ? ` \u2713 \u0110\xE3 n\u1ED1i m\xE1y \xB7 ${me.ten}` : ` \u2713 \u0110\xE3 n\u1ED1i m\xE1y \xB7 ${me.ten} \u2014 nh\u01B0ng xem c\u1EA3nh b\xE1o \u1EDF tr\xEAn`);
40446
+ if (xong) {
40447
+ say(" M\xE1y n\xE0y s\u1EBD T\u1EF0 n\u1ED1i l\u1EA1i m\u1ED7i l\u1EA7n b\u1EA1n b\u1EADt m\xE1y.");
40448
+ say(" Kh\xF4ng c\u1EA7n m\u1EDF Terminal n\u1EEFa, kh\xF4ng c\u1EA7n gi\u1EEF c\u1EEDa s\u1ED5 n\xE0o.");
40449
+ say("");
40450
+ say(" Xem tr\u1EA1ng th\xE1i: npx hocai-connect status");
40451
+ say(" Ng\u1EAFt k\u1EBFt n\u1ED1i: npx hocai-connect stop");
40452
+ }
40453
+ say("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
40454
+ if (xong) {
40455
+ await new Promise((r) => setTimeout(r, 2500));
40456
+ const info = await askPortOwner(PORT, WEB());
40457
+ if (!info) {
40458
+ say("");
40459
+ say("\u26A0 D\u1ECBch v\u1EE5 \u0111\xE3 c\xE0i nh\u01B0ng ch\u01B0a th\u1EA5y tr\u1EA3 l\u1EDDi \u1EDF c\u1ED5ng " + PORT + ".");
40460
+ say(` Xem nh\u1EADt k\xFD: npx hocai-connect logs`);
40461
+ process.exit(1);
40462
+ }
40463
+ }
40464
+ return;
40465
+ }
40201
40466
  const srv = buildHttpServer({
40202
40467
  token: creds.token,
40203
40468
  origins: (process.env.HOCAI_WEB_ORIGINS ?? `${WEB()},http://localhost:5173,http://localhost:5174`).split(","),
@@ -40216,10 +40481,9 @@ async function main() {
40216
40481
  srv.listen(PORT, "127.0.0.1", () => {
40217
40482
  say("");
40218
40483
  say("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
40219
- say(` \u2713 \u0110\xE3 k\u1EBFt n\u1ED1i \xB7 ${me.ten}`);
40220
- say(" \u0110\u1EC2 C\u1EECA S\u1ED4 N\xC0Y M\u1EDE trong l\xFAc l\xE0m b\xE0i.");
40221
- say(` Trang hocAI s\u1EBD g\u1ECDi Claude tr\xEAn m\xE1y b\u1EA1n qua c\u1ED5ng ${PORT}.`);
40222
- say(" \u0110\xF3ng c\u1EEDa s\u1ED5 n\xE0y (Ctrl+C) l\xE0 ng\u1EAFt k\u1EBFt n\u1ED1i.");
40484
+ say(` \u2713 \u0110ang ch\u1EA1y \xB7 ${me.ten}`);
40485
+ say(` Trang hocAI g\u1ECDi Claude tr\xEAn m\xE1y b\u1EA1n qua c\u1ED5ng ${PORT}.`);
40486
+ say(" (\u0110\xE2y l\xE0 ch\u1EBF \u0111\u1ED9 ti\u1EC1n c\u1EA3nh \u2014 d\u1ECBch v\u1EE5 n\u1EC1n g\u1ECDi ch\xEDnh \u0111\u01B0\u1EDDng n\xE0y.)");
40223
40487
  say("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
40224
40488
  });
40225
40489
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hocai-connect",
3
- "version": "0.5.0",
3
+ "version": "0.7.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"