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.
- package/dist/cli.js +270 -6
- 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.
|
|
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
|
|
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 \
|
|
40220
|
-
say(
|
|
40221
|
-
say(
|
|
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
|
}
|