hocai-connect 0.4.0 → 0.6.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 +235 -11
  2. package/package.json +4 -1
package/dist/cli.js CHANGED
@@ -29147,6 +29147,96 @@ 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
+
29150
29240
  // apps/mcp/src/server.ts
29151
29241
  var import_promises = require("node:fs/promises");
29152
29242
  var import_node_os = require("node:os");
@@ -39587,7 +39677,7 @@ var TOOLS = {
39587
39677
  ],
39588
39678
  WORK: ["Read", "Write", "Edit", "Glob", "Grep"]
39589
39679
  };
39590
- var VERSION = "0.4.0";
39680
+ var VERSION = "0.6.0";
39591
39681
  function claudeArgs(job, o, port) {
39592
39682
  const mcp = {
39593
39683
  mcpServers: {
@@ -40027,10 +40117,56 @@ function absoluteApiUrl(raw, webUrl, fallback) {
40027
40117
  return fallback;
40028
40118
  }
40029
40119
  }
40120
+ function diagnosePortBusy(info, meId) {
40121
+ if (!info || info.ten !== "hocai-connect") {
40122
+ return {
40123
+ loai: "OTHER_PROGRAM",
40124
+ noi: "C\u1ED5ng 4100 \u0111ang b\u1ECB m\u1ED9t ch\u01B0\u01A1ng tr\xECnh kh\xE1c chi\u1EBFm (kh\xF4ng ph\u1EA3i hocai-connect).\n\u0110\xF3ng ch\u01B0\u01A1ng tr\xECnh \u0111\xF3, ho\u1EB7c ch\u1EA1y c\u1ED5ng kh\xE1c: MCP_HTTP_PORT=4101 npx hocai-connect\n\u2014 nh\u01B0ng nh\u1EDB trang hocAI ch\u1EC9 g\u1ECDi c\u1ED5ng 4100, n\xEAn \u0111\u1ED5i c\u1ED5ng th\xEC trang s\u1EBD kh\xF4ng th\u1EA5y m\xE1y b\u1EA1n.",
40125
+ thoat: 1
40126
+ };
40127
+ }
40128
+ if (info.user === void 0) {
40129
+ return {
40130
+ loai: "UNKNOWN_VERSION",
40131
+ noi: `C\u1ED5ng 4100 \u0111ang c\xF3 m\u1ED9t hocai-connect b\u1EA3n c\u0169 (${info.phienBan ?? "kh\xF4ng r\xF5"}) ch\u1EA1y \u2014 b\u1EA3n \u0111\xF3 ch\u01B0a cho bi\u1EBFt n\xF3 n\u1ED1i b\u1EB1ng t\xE0i kho\u1EA3n n\xE0o.
40132
+ \u0110\xF3ng c\u1EEDa s\u1ED5 Terminal \u0111\xF3 (Ctrl+C) r\u1ED3i ch\u1EA1y l\u1EA1i l\u1EC7nh n\xE0y.`,
40133
+ thoat: 1
40134
+ };
40135
+ }
40136
+ if (info.user && info.user.id === meId) {
40137
+ return {
40138
+ loai: "SAME_USER",
40139
+ noi: `M\xE1y n\xE0y \u0111ang ch\u1EA1y r\u1ED3i \u2014 hocai-connect c\u1EE7a ${info.user.ten} \u0111\xE3 m\u1EDF \u1EDF m\u1ED9t c\u1EEDa s\u1ED5 Terminal kh\xE1c.
40140
+ Kh\xF4ng c\u1EA7n l\xE0m g\xEC th\xEAm. C\u1EE9 quay l\u1EA1i trang hocAI v\xE0 l\xE0m b\xE0i.`,
40141
+ thoat: 0
40142
+ };
40143
+ }
40144
+ return {
40145
+ loai: "OTHER_USER",
40146
+ noi: info.user ? `C\u1ED5ng 4100 \u0111ang c\xF3 hocai-connect ch\u1EA1y d\u01B0\u1EDBi t\xE0i kho\u1EA3n ${info.user.ten}, kh\xF4ng ph\u1EA3i t\xE0i kho\u1EA3n b\u1EA1n.
40147
+ \u0110\xF3ng c\u1EEDa s\u1ED5 Terminal \u0111\xF3 (Ctrl+C) r\u1ED3i ch\u1EA1y l\u1EA1i l\u1EC7nh n\xE0y.` : "C\u1ED5ng 4100 \u0111ang c\xF3 m\u1ED9t hocai-connect ch\u1EA1y b\u1EB1ng ch\xECa kho\xE1 \u0111\xE3 b\u1ECB thu h\u1ED3i.\n\u0110\xF3ng c\u1EEDa s\u1ED5 Terminal \u0111\xF3 (Ctrl+C) r\u1ED3i ch\u1EA1y l\u1EA1i l\u1EC7nh n\xE0y.",
40148
+ thoat: 1
40149
+ };
40150
+ }
40151
+ async function askPortOwner(port, origin) {
40152
+ try {
40153
+ const res = await fetch(`http://127.0.0.1:${port}/`, {
40154
+ headers: { origin },
40155
+ signal: AbortSignal.timeout(1500)
40156
+ });
40157
+ if (!res.ok) return null;
40158
+ return await res.json();
40159
+ } catch {
40160
+ return null;
40161
+ }
40162
+ }
40030
40163
 
40031
40164
  // apps/mcp/src/cli.ts
40032
40165
  var HOCAI_DIR = (0, import_node_path5.join)((0, import_node_os3.homedir)(), ".hocai");
40033
40166
  var CREDS = (0, import_node_path5.join)(HOCAI_DIR, "config.json");
40167
+ var APP_DIR = (0, import_node_path5.join)(HOCAI_DIR, "app");
40168
+ var APP_CLI = (0, import_node_path5.join)(APP_DIR, "cli.js");
40169
+ var LOG_DIR = (0, import_node_path5.join)(HOCAI_DIR, "log");
40034
40170
  var WEB = () => process.env.HOCAI_WEB_URL ?? "http://103.154.62.225:8090";
40035
40171
  var API_DEFAULT = () => process.env.HOCAI_API_URL ?? `${WEB()}/api`;
40036
40172
  var PORT = Number(process.env.MCP_HTTP_PORT ?? 4100);
@@ -40124,6 +40260,37 @@ function claudeBin() {
40124
40260
  return "claude";
40125
40261
  }
40126
40262
  }
40263
+ var plan = () => daemonPlan((0, import_node_os3.platform)(), { node: process.execPath, script: APP_CLI, home: (0, import_node_os3.homedir)() });
40264
+ function chay(cmd, im = false) {
40265
+ try {
40266
+ (0, import_node_child_process3.execFileSync)(cmd[0], cmd.slice(1), { stdio: "ignore", shell: (0, import_node_os3.platform)() === "win32" });
40267
+ return true;
40268
+ } catch {
40269
+ if (!im) return false;
40270
+ return false;
40271
+ }
40272
+ }
40273
+ function caiDichVu() {
40274
+ const p = plan();
40275
+ if (p.install.length === 0) {
40276
+ 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.");
40277
+ return false;
40278
+ }
40279
+ (0, import_node_fs3.mkdirSync)(APP_DIR, { recursive: true });
40280
+ (0, import_node_fs3.mkdirSync)(LOG_DIR, { recursive: true });
40281
+ (0, import_node_fs3.copyFileSync)(process.argv[1], APP_CLI);
40282
+ if (p.file && p.content) {
40283
+ (0, import_node_fs3.mkdirSync)((0, import_node_path5.dirname)(p.file), { recursive: true });
40284
+ (0, import_node_fs3.writeFileSync)(p.file, p.content);
40285
+ }
40286
+ p.install.forEach((c, i) => chay(c, i === 0));
40287
+ return true;
40288
+ }
40289
+ function goDichVu() {
40290
+ const p = plan();
40291
+ for (const c of p.uninstall) chay(c, true);
40292
+ if (p.file) (0, import_node_fs3.rmSync)(p.file, { force: true });
40293
+ }
40127
40294
  async function main() {
40128
40295
  const cmd = process.argv[2];
40129
40296
  if (cmd === "mcp") {
@@ -40131,8 +40298,39 @@ async function main() {
40131
40298
  return;
40132
40299
  }
40133
40300
  if (cmd === "quen") {
40301
+ goDichVu();
40134
40302
  (0, import_node_fs3.rmSync)(CREDS, { force: true });
40135
- 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.");
40303
+ 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.");
40304
+ say("Ch\u1EA1y l\u1EA1i `npx hocai-connect` \u0111\u1EC3 k\u1EBFt n\u1ED1i l\u1EA1i.");
40305
+ return;
40306
+ }
40307
+ if (cmd === "stop") {
40308
+ goDichVu();
40309
+ 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.");
40310
+ return;
40311
+ }
40312
+ if (cmd === "status") {
40313
+ const c = loadCreds();
40314
+ if (!c) {
40315
+ say("M\xE1y n\xE0y ch\u01B0a n\u1ED1i. Ch\u1EA1y `npx hocai-connect`.");
40316
+ process.exit(1);
40317
+ }
40318
+ const info = await askPortOwner(PORT, c.api.replace(/\/api$/, ""));
40319
+ if (!info) {
40320
+ 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.`);
40321
+ process.exit(1);
40322
+ }
40323
+ say(`\u2713 \u0110ang ch\u1EA1y \xB7 ${info.user?.ten ?? "(ch\xECa \u0111\xE3 b\u1ECB thu h\u1ED3i)"} \xB7 b\u1EA3n ${info.phienBan}`);
40324
+ say(` Nh\u1EADt k\xFD: ${plan().log}`);
40325
+ return;
40326
+ }
40327
+ if (cmd === "logs") {
40328
+ const f = plan().log;
40329
+ if (!(0, import_node_fs3.existsSync)(f)) {
40330
+ say(`Ch\u01B0a c\xF3 nh\u1EADt k\xFD n\xE0o \u1EDF ${f}`);
40331
+ return;
40332
+ }
40333
+ say((0, import_node_fs3.readFileSync)(f, "utf8").split("\n").slice(-40).join("\n"));
40136
40334
  return;
40137
40335
  }
40138
40336
  const creds = loadCreds() ?? await login();
@@ -40155,6 +40353,31 @@ async function main() {
40155
40353
  });
40156
40354
  writeDesktop(creds);
40157
40355
  addClaudeCode(creds);
40356
+ if (cmd !== "run") {
40357
+ const xong = caiDichVu();
40358
+ say("");
40359
+ 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");
40360
+ say(` \u2713 \u0110\xE3 n\u1ED1i m\xE1y \xB7 ${me.ten}`);
40361
+ if (xong) {
40362
+ say(" M\xE1y n\xE0y s\u1EBD T\u1EF0 n\u1ED1i l\u1EA1i m\u1ED7i l\u1EA7n b\u1EA1n b\u1EADt m\xE1y.");
40363
+ say(" Kh\xF4ng c\u1EA7n m\u1EDF Terminal n\u1EEFa, kh\xF4ng c\u1EA7n gi\u1EEF c\u1EEDa s\u1ED5 n\xE0o.");
40364
+ say("");
40365
+ say(" Xem tr\u1EA1ng th\xE1i: npx hocai-connect status");
40366
+ say(" Ng\u1EAFt k\u1EBFt n\u1ED1i: npx hocai-connect stop");
40367
+ }
40368
+ 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");
40369
+ if (xong) {
40370
+ await new Promise((r) => setTimeout(r, 2500));
40371
+ const info = await askPortOwner(PORT, WEB());
40372
+ if (!info) {
40373
+ say("");
40374
+ say("\u26A0 D\u1ECBch v\u1EE5 \u0111\xE3 c\xE0i nh\u01B0ng ch\u01B0a th\u1EA5y tr\u1EA3 l\u1EDDi \u1EDF c\u1ED5ng " + PORT + ".");
40375
+ say(` Xem nh\u1EADt k\xFD: npx hocai-connect logs`);
40376
+ process.exit(1);
40377
+ }
40378
+ }
40379
+ return;
40380
+ }
40158
40381
  const srv = buildHttpServer({
40159
40382
  token: creds.token,
40160
40383
  origins: (process.env.HOCAI_WEB_ORIGINS ?? `${WEB()},http://localhost:5173,http://localhost:5174`).split(","),
@@ -40162,19 +40385,20 @@ async function main() {
40162
40385
  me: identity.current
40163
40386
  });
40164
40387
  srv.on("error", (e) => {
40165
- if (e.code === "EADDRINUSE") {
40166
- say(`C\u1ED5ng ${PORT} \u0111ang b\u1EADn \u2014 c\xF3 m\u1ED9t hocai-connect kh\xE1c \u0111ang ch\u1EA1y? \u0110\xF3ng n\xF3 r\u1ED3i th\u1EED l\u1EA1i.`);
40167
- process.exit(1);
40168
- }
40169
- throw e;
40388
+ if (e.code !== "EADDRINUSE") throw e;
40389
+ void askPortOwner(PORT, WEB()).then((info) => {
40390
+ const d = diagnosePortBusy(info, me.id);
40391
+ say("");
40392
+ say(d.noi);
40393
+ process.exit(d.thoat);
40394
+ });
40170
40395
  });
40171
40396
  srv.listen(PORT, "127.0.0.1", () => {
40172
40397
  say("");
40173
40398
  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");
40174
- say(` \u2713 \u0110\xE3 k\u1EBFt n\u1ED1i \xB7 ${me.ten}`);
40175
- say(" \u0110\u1EC2 C\u1EECA S\u1ED4 N\xC0Y M\u1EDE trong l\xFAc l\xE0m b\xE0i.");
40176
- say(` Trang hocAI s\u1EBD g\u1ECDi Claude tr\xEAn m\xE1y b\u1EA1n qua c\u1ED5ng ${PORT}.`);
40177
- say(" \u0110\xF3ng c\u1EEDa s\u1ED5 n\xE0y (Ctrl+C) l\xE0 ng\u1EAFt k\u1EBFt n\u1ED1i.");
40399
+ say(` \u2713 \u0110ang ch\u1EA1y \xB7 ${me.ten}`);
40400
+ say(` Trang hocAI g\u1ECDi Claude tr\xEAn m\xE1y b\u1EA1n qua c\u1ED5ng ${PORT}.`);
40401
+ 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.)");
40178
40402
  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");
40179
40403
  });
40180
40404
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hocai-connect",
3
- "version": "0.4.0",
3
+ "version": "0.6.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"
@@ -9,6 +9,9 @@
9
9
  "bin.js",
10
10
  "dist/"
11
11
  ],
12
+ "scripts": {
13
+ "prepublishOnly": "cd ../.. && npm run build:connect"
14
+ },
12
15
  "engines": {
13
16
  "node": ">=20"
14
17
  },