fanluc 2.0.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/bin/fanluc.js +54 -0
- package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/_basePickBy-CP3UBwOk.js +1 -0
- package/dist/assets/_baseUniq-CZ8FG28m.js +1 -0
- package/dist/assets/abnfDiagram-O67JEVCF-hiYMdxpV.js +1 -0
- package/dist/assets/arc-DokRg_wy.js +1 -0
- package/dist/assets/architectureDiagram-NJMV4G6O-UzHYe9Bx.js +36 -0
- package/dist/assets/blockDiagram-BEXU5L5S-DqM5UILT.js +140 -0
- package/dist/assets/c4Diagram-YGBWAQC7-Cxkdm7sV.js +38 -0
- package/dist/assets/channel-SPfNTvmj.js +1 -0
- package/dist/assets/chunk-2Q5K7J3B-Nl4zmLlG.js +1 -0
- package/dist/assets/chunk-3FUC2YCW-BCol78O2.js +2 -0
- package/dist/assets/chunk-5VM5RSS4-1u4YRv2Y.js +15 -0
- package/dist/assets/chunk-GTNCS2PH-BG9YIV2h.js +88 -0
- package/dist/assets/chunk-GWA4HPMP-BTKfkJAH.js +1 -0
- package/dist/assets/chunk-JWPE2WC7-DCjElfA7.js +1 -0
- package/dist/assets/chunk-NETBCI7D-QoJORlVn.js +1 -0
- package/dist/assets/chunk-WEXAMYUT-Da-T8ct9.js +1 -0
- package/dist/assets/chunk-XXDRQBXY-CLu69Uei.js +1 -0
- package/dist/assets/classDiagram-v2-NBCMYWYE-DIXavbI9.js +217 -0
- package/dist/assets/cose-bilkent-JH36ORCC-DuWclG8-.js +1 -0
- package/dist/assets/cynefin-OW5HDTMX-D2CG5-xd.js +178 -0
- package/dist/assets/cynefinDiagram-VND7K2PF-goI7_Qfg.js +62 -0
- package/dist/assets/cytoscape.esm-Ix0LnXOy.js +331 -0
- package/dist/assets/dagre-6A5THRUB-BmOaZ0cm.js +4 -0
- package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/assets/diagram-22UHCM2B-DjDJFMw3.js +200 -0
- package/dist/assets/diagram-3UASUU5V-J6z81NH3.js +24 -0
- package/dist/assets/diagram-ATOU4E4O-B1MPU8ai.js +3 -0
- package/dist/assets/diagram-CDSNMT55-X5Uss0Yv.js +30 -0
- package/dist/assets/diagram-MLGK6HIB-CRDqfYy1.js +24 -0
- package/dist/assets/diagram-MPIPVDR6-bhJtyiOq.js +41 -0
- package/dist/assets/ebnfDiagram-ZINNZB2B-Dn1mZAV8.js +1 -0
- package/dist/assets/elk-276RUBZZ-C93BlBEU.js +27 -0
- package/dist/assets/erDiagram-OPXOYQCR-PuzjAvhD.js +99 -0
- package/dist/assets/flowDiagram-KWPJA3E3-oRhFgO0p.js +213 -0
- package/dist/assets/ganttDiagram-FUAMR5RP-DKiU1XjU.js +292 -0
- package/dist/assets/gitGraphDiagram-X574FWY7-CWBMI0vb.js +106 -0
- package/dist/assets/graph-tGV3vi3M.js +1 -0
- package/dist/assets/index-BylpTP-E.css +1 -0
- package/dist/assets/index-DcpVteR3.js +3237 -0
- package/dist/assets/infoDiagram-VRGFBTTK-C5Hd4Afz.js +2 -0
- package/dist/assets/init-Gi6I4Gst.js +1 -0
- package/dist/assets/ishikawaDiagram-OU5B5YK6-BdD6ulRK.js +70 -0
- package/dist/assets/journeyDiagram-ZHPQQLJL-CzT-uvks.js +139 -0
- package/dist/assets/kanban-definition-PNTS6WVX-BkrazFMR.js +89 -0
- package/dist/assets/katex-C5jXJg4s.js +257 -0
- package/dist/assets/linear-B0wSM1Dr.js +1 -0
- package/dist/assets/mindmap-definition-NLK3R4M7-Df1Acqkf.js +96 -0
- package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/assets/pegDiagram-GJSIUBJH-DK_F7h_0.js +1 -0
- package/dist/assets/permissionHelper--88tJJlz.js +3 -0
- package/dist/assets/pieDiagram-5QR66LMP-BMXyNb9R.js +39 -0
- package/dist/assets/quadrantDiagram-O4NWA36T-Dfu7Z8so.js +7 -0
- package/dist/assets/railroadDiagram-XR7U4H2S-CfnBD-aQ.js +1 -0
- package/dist/assets/requirementDiagram-PLB6GJNP-DrvsZJPP.js +84 -0
- package/dist/assets/sankeyDiagram-IPEJSGJF-DvdVDpT1.js +40 -0
- package/dist/assets/sequenceDiagram-PO4LG4MO-D1UTrvZH.js +169 -0
- package/dist/assets/sizeCapture-INFHLROL-CJrOBcai.js +1 -0
- package/dist/assets/stateDiagram-v2-GCMORJYK-CX8VmtqD.js +291 -0
- package/dist/assets/swimlanes-2SLR337P-DUWmhiK6.js +1 -0
- package/dist/assets/swimlanesDiagram-TC7HE7FX-DMQy1553.js +8 -0
- package/dist/assets/timeline-definition-EJHVYXUP-CBovVdOU.js +120 -0
- package/dist/assets/usecaseDiagram-POWQR4AR-LD5N5BAG.js +370 -0
- package/dist/assets/vennDiagram-UO4OBE2U-DdwfzB3A.js +34 -0
- package/dist/assets/wardleyDiagram-VNRHLVJA-BMHK4bav.js +78 -0
- package/dist/assets/xychartDiagram-PMCCYNJV-q1BNzMC-.js +7 -0
- package/dist/cli.js +474 -0
- package/dist/index.html +18 -0
- package/dist/msh_spin_inverted.gif +0 -0
- package/dist/server.cjs +1974 -0
- package/dist/server.cjs.map +7 -0
- package/dist/shrink_rotate.gif +0 -0
- package/dist/tui.js +5949 -0
- package/docs/fanluc_ui.md +48 -0
- package/docs/guide-connect.md +63 -0
- package/docs/mondk.md +6 -0
- package/docs/msh.md +3 -0
- package/index.html +17 -0
- package/mcp.json +0 -0
- package/package.json +92 -0
- package/public/msh_spin_inverted.gif +0 -0
- package/public/shrink_rotate.gif +0 -0
- package/searxng/docker-compose.yml +20 -0
- package/searxng/settings.yml +23 -0
- package/tsconfig.json +26 -0
- package/vite.config.ts +22 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli/index.ts
|
|
4
|
+
import path2 from "path";
|
|
5
|
+
import fs2 from "fs";
|
|
6
|
+
import os2 from "os";
|
|
7
|
+
import { spawn, exec } from "child_process";
|
|
8
|
+
|
|
9
|
+
// src/cli/config.ts
|
|
10
|
+
import path from "path";
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
import os from "os";
|
|
13
|
+
var PERMISSION_DESCRIPTIONS = {
|
|
14
|
+
ask: "H\u1ECFi m\u1ECDi l\u1EA7n tr\u01B0\u1EDBc khi ghi file ho\u1EB7c ch\u1EA1y l\u1EC7nh shell",
|
|
15
|
+
autoEdit: "T\u1EF1 \u0111\u1ED9ng cho ph\xE9p s\u1EEDa/ghi file, ch\u1EC9 h\u1ECFi khi ch\u1EA1y l\u1EC7nh shell",
|
|
16
|
+
readOnly: "Ch\u1EC9 \u0111\u1ECDc \u2014 ch\u1EB7n m\u1ECDi thao t\xE1c ghi file v\xE0 ch\u1EA1y l\u1EC7nh"
|
|
17
|
+
};
|
|
18
|
+
var DEFAULT_SETTINGS = {
|
|
19
|
+
permission: "ask",
|
|
20
|
+
port: 3e3
|
|
21
|
+
};
|
|
22
|
+
function getConfigDir() {
|
|
23
|
+
if (process.env.FANLUC_CONFIG_DIR && process.env.FANLUC_CONFIG_DIR.trim()) return process.env.FANLUC_CONFIG_DIR.trim();
|
|
24
|
+
const platform = process.platform;
|
|
25
|
+
if (platform === "win32") {
|
|
26
|
+
const appData = process.env.APPDATA;
|
|
27
|
+
if (appData && appData.trim()) {
|
|
28
|
+
return path.join(appData, "fanluc");
|
|
29
|
+
}
|
|
30
|
+
return path.join(os.homedir(), "AppData", "Roaming", "fanluc");
|
|
31
|
+
}
|
|
32
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
33
|
+
if (xdg && xdg.trim()) return path.join(xdg, "fanluc");
|
|
34
|
+
return path.join(os.homedir(), ".config", "fanluc");
|
|
35
|
+
}
|
|
36
|
+
function getSettingsPath(configDir) {
|
|
37
|
+
return path.join(configDir || getConfigDir(), "settings.json");
|
|
38
|
+
}
|
|
39
|
+
function getMcpPath(configDir) {
|
|
40
|
+
return path.join(configDir || getConfigDir(), "mcp.json");
|
|
41
|
+
}
|
|
42
|
+
function ensureConfigDir(configDir) {
|
|
43
|
+
const dir = configDir || getConfigDir();
|
|
44
|
+
try {
|
|
45
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
46
|
+
} catch (e) {
|
|
47
|
+
console.warn(`[fanluc] Failed to create config dir ${dir}:`, e);
|
|
48
|
+
}
|
|
49
|
+
return dir;
|
|
50
|
+
}
|
|
51
|
+
function loadSettings(configDir) {
|
|
52
|
+
const dir = ensureConfigDir(configDir);
|
|
53
|
+
const p = getSettingsPath(dir);
|
|
54
|
+
let parsed = {};
|
|
55
|
+
try {
|
|
56
|
+
if (fs.existsSync(p)) {
|
|
57
|
+
const raw = fs.readFileSync(p, "utf-8").replace(/^\uFEFF/, "");
|
|
58
|
+
parsed = JSON.parse(raw);
|
|
59
|
+
}
|
|
60
|
+
} catch (e) {
|
|
61
|
+
console.warn(`[fanluc] Failed to parse settings.json:`, e);
|
|
62
|
+
}
|
|
63
|
+
const perm = parsed.permission;
|
|
64
|
+
const valid = ["ask", "autoEdit", "readOnly"];
|
|
65
|
+
const permission = valid.includes(perm) ? perm : DEFAULT_SETTINGS.permission;
|
|
66
|
+
return { ...DEFAULT_SETTINGS, ...parsed, permission };
|
|
67
|
+
}
|
|
68
|
+
function saveSettings(settings, configDir) {
|
|
69
|
+
const dir = ensureConfigDir(configDir);
|
|
70
|
+
const p = getSettingsPath(dir);
|
|
71
|
+
try {
|
|
72
|
+
fs.writeFileSync(p, JSON.stringify(settings, null, 2), "utf-8");
|
|
73
|
+
} catch (e) {
|
|
74
|
+
console.warn(`[fanluc] Failed to write settings.json:`, e);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function ensureMcpJson(configDir) {
|
|
78
|
+
const dir = ensureConfigDir(configDir);
|
|
79
|
+
const target = getMcpPath(dir);
|
|
80
|
+
if (fs.existsSync(target)) return target;
|
|
81
|
+
try {
|
|
82
|
+
fs.writeFileSync(target, JSON.stringify({ mcpServers: {} }, null, 2), "utf-8");
|
|
83
|
+
console.log(`[fanluc] Created empty mcp.json at ${target} (cwd mcp.json ignored \u2014 copy manually if needed)`);
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
86
|
+
return target;
|
|
87
|
+
}
|
|
88
|
+
function getWorkspaceDir() {
|
|
89
|
+
if (process.env.FANLUC_WORKSPACE && process.env.FANLUC_WORKSPACE.trim()) {
|
|
90
|
+
return path.resolve(process.env.FANLUC_WORKSPACE.trim());
|
|
91
|
+
}
|
|
92
|
+
return process.cwd();
|
|
93
|
+
}
|
|
94
|
+
function findFanlucMd(workspaceDir) {
|
|
95
|
+
const ws = workspaceDir || getWorkspaceDir();
|
|
96
|
+
const candidates = [
|
|
97
|
+
path.join(ws, "FANLUC.md"),
|
|
98
|
+
path.join(ws, "fanluc.md"),
|
|
99
|
+
path.join(ws, ".fanluc", "FANLUC.md"),
|
|
100
|
+
path.join(ws, ".fanluc.md")
|
|
101
|
+
];
|
|
102
|
+
for (const p of candidates) {
|
|
103
|
+
try {
|
|
104
|
+
if (fs.existsSync(p) && fs.statSync(p).isFile()) return p;
|
|
105
|
+
} catch {
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
function loadFanlucMd(workspaceDir) {
|
|
111
|
+
const found = findFanlucMd(workspaceDir);
|
|
112
|
+
if (!found) return null;
|
|
113
|
+
try {
|
|
114
|
+
const raw = fs.readFileSync(found, "utf-8").replace(/^\uFEFF/, "");
|
|
115
|
+
const content = raw;
|
|
116
|
+
const MAX_FANLUC_MD = 6e3;
|
|
117
|
+
if (content.length > MAX_FANLUC_MD) {
|
|
118
|
+
console.warn(`[fanluc] FANLUC.md too large (${content.length} chars), truncating to ${MAX_FANLUC_MD}`);
|
|
119
|
+
return { path: found, content: content.slice(0, MAX_FANLUC_MD) + "\n\n[... truncated ...]" };
|
|
120
|
+
}
|
|
121
|
+
return { path: found, content };
|
|
122
|
+
} catch (e) {
|
|
123
|
+
console.warn(`[fanluc] Failed to read ${found}:`, e);
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// src/cli/index.ts
|
|
129
|
+
var PKG = (() => {
|
|
130
|
+
try {
|
|
131
|
+
const pkgPath = path2.join(path2.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, "$1")), "..", "..", "package.json");
|
|
132
|
+
for (const p of [pkgPath, path2.join(process.cwd(), "package.json"), path2.join(path2.dirname(process.argv[1] ?? ""), "..", "package.json")]) {
|
|
133
|
+
if (fs2.existsSync(p)) {
|
|
134
|
+
const j = JSON.parse(fs2.readFileSync(p, "utf-8"));
|
|
135
|
+
if (j.name) return j;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} catch {
|
|
139
|
+
}
|
|
140
|
+
return { version: "0.0.0", name: "fanluc" };
|
|
141
|
+
})();
|
|
142
|
+
function printHelp() {
|
|
143
|
+
console.log(`
|
|
144
|
+
fanluc \u2014 terminal tool gi\u1ED1ng Claude Code/opencode (web v\u1EABn ch\u1EA1y song song)
|
|
145
|
+
|
|
146
|
+
Usage:
|
|
147
|
+
fanluc [options] [workspace]
|
|
148
|
+
fanluc --help
|
|
149
|
+
fanluc --version
|
|
150
|
+
|
|
151
|
+
Options:
|
|
152
|
+
-p, --port <n> Port to listen (default from settings or 3000)
|
|
153
|
+
--host <host> Host to bind (default 127.0.0.1)
|
|
154
|
+
--no-open Don't auto-open browser
|
|
155
|
+
--tui Force TUI (default: auto if TTY)
|
|
156
|
+
--no-tui, --web No TUI, only server + browser (G\u01101 behavior)
|
|
157
|
+
--permission <mode> Permission mode: ask | autoEdit | readOnly
|
|
158
|
+
(ask: h\u1ECFi m\u1ECDi l\u1EA7n, autoEdit: t\u1EF1 cho s\u1EEDa file, readOnly: ch\u1EC9 \u0111\u1ECDc)
|
|
159
|
+
|
|
160
|
+
Config:
|
|
161
|
+
Windows: %APPDATA%\\fanluc\\ (v\xED d\u1EE5 C:\\Users\\${os2.userInfo().username}\\AppData\\Roaming\\fanluc)
|
|
162
|
+
Linux/mac: ~/.config/fanluc/
|
|
163
|
+
- settings.json { permission, port, ... }
|
|
164
|
+
- mcp.json { mcpServers: {...} } (copy t\u1EEB ./mcp.json n\u1EBFu c\xF3)
|
|
165
|
+
|
|
166
|
+
Workspace:
|
|
167
|
+
Th\u01B0 m\u1EE5c hi\u1EC7n t\u1EA1i l\xE0 workspace. N\u1EBFu c\xF3 FANLUC.md trong workspace th\xEC t\u1EF1 n\u1EA1p v\xE0o system prompt.
|
|
168
|
+
|
|
169
|
+
Permissions (3 ch\u1EBF \u0111\u1ED9):
|
|
170
|
+
ask = ${PERMISSION_DESCRIPTIONS.ask}
|
|
171
|
+
autoEdit = ${PERMISSION_DESCRIPTIONS.autoEdit}
|
|
172
|
+
readOnly = ${PERMISSION_DESCRIPTIONS.readOnly}
|
|
173
|
+
|
|
174
|
+
Examples:
|
|
175
|
+
fanluc
|
|
176
|
+
fanluc --port 3001 --permission autoEdit
|
|
177
|
+
fanluc D:\\my-project
|
|
178
|
+
fanluc --no-open
|
|
179
|
+
|
|
180
|
+
Web v\u1EABn ch\u1EA1y: http://localhost:<port>
|
|
181
|
+
`);
|
|
182
|
+
}
|
|
183
|
+
function printVersion() {
|
|
184
|
+
console.log(PKG.version || "0.0.0");
|
|
185
|
+
}
|
|
186
|
+
function parseArgs(argv) {
|
|
187
|
+
const out = { noOpen: false, noTui: false, tui: false, help: false, version: false, extra: [] };
|
|
188
|
+
for (let i = 0; i < argv.length; i++) {
|
|
189
|
+
const a = argv[i];
|
|
190
|
+
if (a === "--help" || a === "-h") out.help = true;
|
|
191
|
+
else if (a === "--version" || a === "-v") out.version = true;
|
|
192
|
+
else if (a === "--no-open") out.noOpen = true;
|
|
193
|
+
else if (a === "--port" || a === "-p") {
|
|
194
|
+
const v = argv[++i];
|
|
195
|
+
const n = parseInt(v, 10);
|
|
196
|
+
if (!isNaN(n)) out.port = n;
|
|
197
|
+
} else if (a.startsWith("--port=")) {
|
|
198
|
+
const n = parseInt(a.split("=")[1], 10);
|
|
199
|
+
if (!isNaN(n)) out.port = n;
|
|
200
|
+
} else if (a === "--host") {
|
|
201
|
+
out.host = argv[++i];
|
|
202
|
+
} else if (a.startsWith("--host=")) {
|
|
203
|
+
out.host = a.split("=")[1];
|
|
204
|
+
} else if (a === "--permission") {
|
|
205
|
+
const v = argv[++i];
|
|
206
|
+
if (["ask", "autoEdit", "readOnly"].includes(v)) out.permission = v;
|
|
207
|
+
} else if (a.startsWith("--permission=")) {
|
|
208
|
+
const v = a.split("=")[1];
|
|
209
|
+
if (["ask", "autoEdit", "readOnly"].includes(v)) out.permission = v;
|
|
210
|
+
} else if (a === "--tui") out.tui = true;
|
|
211
|
+
else if (a === "--no-tui" || a === "--web") out.noTui = true;
|
|
212
|
+
else if (a.startsWith("-") && a !== "-") {
|
|
213
|
+
out.extra.push(a);
|
|
214
|
+
} else if (!a.startsWith("-") && !out.workspace) {
|
|
215
|
+
out.workspace = a;
|
|
216
|
+
} else {
|
|
217
|
+
out.extra.push(a);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return out;
|
|
221
|
+
}
|
|
222
|
+
function openBrowser(url) {
|
|
223
|
+
const platform = process.platform;
|
|
224
|
+
let cmd;
|
|
225
|
+
if (platform === "win32") {
|
|
226
|
+
cmd = `start "" "${url}"`;
|
|
227
|
+
exec(cmd, (err) => {
|
|
228
|
+
if (err) {
|
|
229
|
+
exec(`powershell -NoProfile -Command "Start-Process '${url}'"`, (e2) => {
|
|
230
|
+
if (e2) console.warn(`[fanluc] Failed to open browser: ${e2.message}. Please open manually: ${url}`);
|
|
231
|
+
else console.log(`[fanluc] Opened ${url} via PowerShell`);
|
|
232
|
+
});
|
|
233
|
+
} else {
|
|
234
|
+
console.log(`[fanluc] Opened ${url}`);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
} else if (platform === "darwin") {
|
|
238
|
+
exec(`open "${url}"`, (err) => {
|
|
239
|
+
if (err) console.warn(`[fanluc] Failed to open browser: ${err.message}. Open manually: ${url}`);
|
|
240
|
+
else console.log(`[fanluc] Opened ${url}`);
|
|
241
|
+
});
|
|
242
|
+
} else {
|
|
243
|
+
exec(`xdg-open "${url}"`, (err) => {
|
|
244
|
+
if (err) console.warn(`[fanluc] Failed to open browser: ${err.message}. Open manually: ${url}`);
|
|
245
|
+
else console.log(`[fanluc] Opened ${url}`);
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
async function main() {
|
|
250
|
+
const args = parseArgs(process.argv.slice(2));
|
|
251
|
+
if (args.help) {
|
|
252
|
+
printHelp();
|
|
253
|
+
process.exit(0);
|
|
254
|
+
}
|
|
255
|
+
if (args.version) {
|
|
256
|
+
printVersion();
|
|
257
|
+
process.exit(0);
|
|
258
|
+
}
|
|
259
|
+
const configDir = getConfigDir();
|
|
260
|
+
ensureConfigDir(configDir);
|
|
261
|
+
let settings = loadSettings(configDir);
|
|
262
|
+
if (args.permission) {
|
|
263
|
+
settings.permission = args.permission;
|
|
264
|
+
saveSettings(settings, configDir);
|
|
265
|
+
console.log(`[fanluc] Permission set to ${settings.permission} (saved to ${path2.join(configDir, "settings.json")})`);
|
|
266
|
+
}
|
|
267
|
+
if (args.port) settings.port = args.port;
|
|
268
|
+
if (args.host) settings.host = args.host;
|
|
269
|
+
ensureMcpJson(configDir);
|
|
270
|
+
function getInstallDir() {
|
|
271
|
+
try {
|
|
272
|
+
const urlPath = new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, "$1");
|
|
273
|
+
let dir = path2.dirname(urlPath);
|
|
274
|
+
try {
|
|
275
|
+
dir = decodeURIComponent(dir);
|
|
276
|
+
} catch {
|
|
277
|
+
}
|
|
278
|
+
if (dir.endsWith(`${path2.sep}dist`)) return path2.dirname(dir);
|
|
279
|
+
if (dir.includes(`${path2.sep}src${path2.sep}cli`) || dir.endsWith("src/cli")) {
|
|
280
|
+
return path2.resolve(dir, "../..");
|
|
281
|
+
}
|
|
282
|
+
const candidate = path2.resolve(dir, "../..");
|
|
283
|
+
if (fs2.existsSync(path2.join(candidate, "package.json")) && fs2.existsSync(path2.join(candidate, "server.ts"))) {
|
|
284
|
+
return candidate;
|
|
285
|
+
}
|
|
286
|
+
const argvDir = path2.dirname(process.argv[1] ?? "");
|
|
287
|
+
if (argvDir && fs2.existsSync(path2.join(argvDir, "../package.json"))) {
|
|
288
|
+
return path2.resolve(argvDir, "..");
|
|
289
|
+
}
|
|
290
|
+
return candidate;
|
|
291
|
+
} catch {
|
|
292
|
+
return process.cwd();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const installDir = getInstallDir();
|
|
296
|
+
let workspace = getWorkspaceDir();
|
|
297
|
+
if (args.workspace) {
|
|
298
|
+
const resolved = path2.resolve(args.workspace);
|
|
299
|
+
try {
|
|
300
|
+
const stat = fs2.statSync(resolved);
|
|
301
|
+
if (!stat.isDirectory()) {
|
|
302
|
+
console.error(`[fanluc] Workspace path is not a directory: ${resolved}`);
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
workspace = resolved;
|
|
306
|
+
} catch (e) {
|
|
307
|
+
console.error(`[fanluc] Workspace not found: ${resolved} \u2014 ${e?.message}`);
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
} else {
|
|
311
|
+
try {
|
|
312
|
+
workspace = process.cwd();
|
|
313
|
+
} catch {
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const fanlucMd = loadFanlucMd(workspace);
|
|
317
|
+
const configDirDisplay = configDir;
|
|
318
|
+
const mcpPath = path2.join(configDir, "mcp.json");
|
|
319
|
+
const settingsPath = path2.join(configDir, "settings.json");
|
|
320
|
+
console.log(`[fanluc] Install dir: ${installDir}`);
|
|
321
|
+
console.log(`[fanluc] Config dir : ${configDirDisplay}`);
|
|
322
|
+
console.log(`[fanluc] - settings: ${settingsPath} (permission=${settings.permission})`);
|
|
323
|
+
console.log(`[fanluc] - mcp : ${mcpPath}`);
|
|
324
|
+
console.log(`[fanluc] Workspace : ${workspace}`);
|
|
325
|
+
if (fanlucMd) {
|
|
326
|
+
console.log(`[fanluc] FANLUC.md : ${fanlucMd.path} (${fanlucMd.content.length} chars, injected into system prompt)`);
|
|
327
|
+
} else {
|
|
328
|
+
console.log(`[fanluc] FANLUC.md : (none) \u2014 create ${path2.join(workspace, "FANLUC.md")} to auto-inject`);
|
|
329
|
+
}
|
|
330
|
+
console.log(`[fanluc] Permission: ${settings.permission} \u2014 ${PERMISSION_DESCRIPTIONS[settings.permission]}`);
|
|
331
|
+
const port = settings.port || 3e3;
|
|
332
|
+
const host = settings.host || "127.0.0.1";
|
|
333
|
+
const url = `http://${host === "0.0.0.0" ? "localhost" : host}:${port}`;
|
|
334
|
+
const env = {
|
|
335
|
+
...process.env,
|
|
336
|
+
PORT: String(port),
|
|
337
|
+
HOST: host,
|
|
338
|
+
FANLUC_WORKSPACE: workspace,
|
|
339
|
+
FANLUC_CONFIG_DIR: configDir,
|
|
340
|
+
FANLUC_PERMISSION: settings.permission,
|
|
341
|
+
FANLUC_MD_PATH: fanlucMd?.path || ""
|
|
342
|
+
};
|
|
343
|
+
const serverCjs = path2.join(installDir, "dist", "server.cjs");
|
|
344
|
+
const serverTs = path2.join(installDir, "server.ts");
|
|
345
|
+
let serverEntry = null;
|
|
346
|
+
if (fs2.existsSync(serverCjs)) {
|
|
347
|
+
serverEntry = serverCjs;
|
|
348
|
+
} else if (fs2.existsSync(serverTs)) {
|
|
349
|
+
console.error(`[fanluc] dist/server.cjs not found at ${serverCjs}`);
|
|
350
|
+
console.error(`[fanluc] Please run 'npm run build:all' in ${installDir} first (b\u1ECF fallback npx tsx theo y\xEAu c\u1EA7u).`);
|
|
351
|
+
process.exit(1);
|
|
352
|
+
}
|
|
353
|
+
if (!serverEntry) {
|
|
354
|
+
console.error(`[fanluc] Cannot locate server entry in installDir ${installDir}`);
|
|
355
|
+
process.exit(1);
|
|
356
|
+
}
|
|
357
|
+
console.log(`[fanluc] Starting server: ${serverEntry} on ${url} ...`);
|
|
358
|
+
console.log(`[fanluc] (installDir=${installDir}, workspace=${workspace})`);
|
|
359
|
+
const shouldUseTui = !args.noTui && (args.tui || !!process.stdout.isTTY);
|
|
360
|
+
let serverStdio = "inherit";
|
|
361
|
+
let serverLogPath = "";
|
|
362
|
+
if (shouldUseTui) {
|
|
363
|
+
try {
|
|
364
|
+
const cfgDir = getConfigDir();
|
|
365
|
+
fs2.mkdirSync(cfgDir, { recursive: true });
|
|
366
|
+
serverLogPath = path2.join(cfgDir, "server.log");
|
|
367
|
+
const fd = fs2.openSync(serverLogPath, "w");
|
|
368
|
+
serverStdio = ["ignore", fd, fd];
|
|
369
|
+
process.env.FANLUC_SERVER_LOG = serverLogPath;
|
|
370
|
+
} catch {
|
|
371
|
+
serverStdio = "inherit";
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
const activeChild = spawn(process.execPath, [serverEntry], {
|
|
375
|
+
env,
|
|
376
|
+
stdio: serverStdio,
|
|
377
|
+
cwd: installDir
|
|
378
|
+
});
|
|
379
|
+
activeChild.on("error", (e) => {
|
|
380
|
+
console.error(`[fanluc] Server spawn error: ${e.message}`);
|
|
381
|
+
process.exit(1);
|
|
382
|
+
});
|
|
383
|
+
const shouldOpenBrowser = !args.noOpen && !shouldUseTui;
|
|
384
|
+
if (shouldOpenBrowser) {
|
|
385
|
+
setTimeout(() => {
|
|
386
|
+
console.log(`[fanluc] Opening ${url} ...`);
|
|
387
|
+
openBrowser(url);
|
|
388
|
+
}, 1500);
|
|
389
|
+
} else if (shouldUseTui) {
|
|
390
|
+
console.log(`[fanluc] TUI enabled \u2014 browser not auto-opened (use --web or --no-tui to open browser). Web still at ${url}`);
|
|
391
|
+
} else {
|
|
392
|
+
console.log(`[fanluc] Auto-open disabled (--no-open). Open manually: ${url}`);
|
|
393
|
+
}
|
|
394
|
+
const signals = ["SIGINT", "SIGTERM"];
|
|
395
|
+
for (const sig of signals) {
|
|
396
|
+
process.on(sig, () => {
|
|
397
|
+
console.log(`
|
|
398
|
+
[fanluc] Received ${sig}, shutting down...`);
|
|
399
|
+
try {
|
|
400
|
+
activeChild.kill(sig);
|
|
401
|
+
} catch {
|
|
402
|
+
}
|
|
403
|
+
setTimeout(() => process.exit(0), 800);
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
activeChild.on("exit", (code, signal) => {
|
|
407
|
+
if (signal) {
|
|
408
|
+
console.log(`[fanluc] Server exited via signal ${signal}`);
|
|
409
|
+
process.exit(1);
|
|
410
|
+
}
|
|
411
|
+
console.log(`[fanluc] Server exited with code ${code}`);
|
|
412
|
+
if (!shouldUseTui) process.exit(code ?? 0);
|
|
413
|
+
});
|
|
414
|
+
if (shouldUseTui) {
|
|
415
|
+
await new Promise((r) => setTimeout(r, 1800));
|
|
416
|
+
if (activeChild.exitCode !== null) {
|
|
417
|
+
console.error("[fanluc] Server failed to start, TUI not launched");
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
console.log(`[fanluc] Starting TUI... (workspace: ${workspace}, FANLUC.md: ${fanlucMd ? fanlucMd.path : "none"})`);
|
|
421
|
+
try {
|
|
422
|
+
const tuiPath = path2.join(installDir, "dist/tui.js");
|
|
423
|
+
if (!fs2.existsSync(tuiPath)) {
|
|
424
|
+
console.error(`[fanluc] TUI not built at ${tuiPath}. Run 'npm run build:tui' or 'npm run build:all' first.`);
|
|
425
|
+
console.error(`[fanluc] Falling back to browser. Open ${url} manually.`);
|
|
426
|
+
if (!args.noOpen) openBrowser(url);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
const fileUrl = `file://${tuiPath.replace(/\\/g, "/").replace(/^([A-Z]):/, "/$1:")}`;
|
|
430
|
+
const normalizedUrl = fileUrl.startsWith("file:////") ? fileUrl.replace("file:////", "file:///") : fileUrl;
|
|
431
|
+
const tuiMod = await import(normalizedUrl);
|
|
432
|
+
const startTui = tuiMod.startTui || tuiMod.default?.startTui || tuiMod.default;
|
|
433
|
+
if (typeof startTui !== "function") throw new Error("startTui not found in dist/tui.js");
|
|
434
|
+
const tuiConfig = {
|
|
435
|
+
apiUrl: settings.apiUrl || process.env.OPENAI_API_URL || process.env.API_URL || "",
|
|
436
|
+
apiKey: settings.apiKey || process.env.OPENAI_API_KEY || process.env.API_KEY || "none",
|
|
437
|
+
model: settings.model || settings.model || "",
|
|
438
|
+
systemPrompt: settings.systemPrompt || "",
|
|
439
|
+
temperature: settings.temperature ?? 0.7,
|
|
440
|
+
maxTokens: settings.maxTokens ?? 8e3,
|
|
441
|
+
topP: settings.topP ?? 0.95,
|
|
442
|
+
topK: settings.topK ?? 40,
|
|
443
|
+
repeatPenalty: settings.repeatPenalty ?? 1.01,
|
|
444
|
+
reasoningEffort: settings.reasoningEffort || "high",
|
|
445
|
+
effort: settings.effort || "fast",
|
|
446
|
+
thinkStartTag: settings.thinkStartTag || "<think>",
|
|
447
|
+
thinkEndTag: settings.thinkEndTag || "</think>",
|
|
448
|
+
skills: settings.skills || []
|
|
449
|
+
};
|
|
450
|
+
process.env.FANLUC_VERSION = String(PKG.version || "0.0.0");
|
|
451
|
+
await startTui({
|
|
452
|
+
workspace,
|
|
453
|
+
port,
|
|
454
|
+
permission: settings.permission,
|
|
455
|
+
config: tuiConfig,
|
|
456
|
+
onExit: () => {
|
|
457
|
+
try {
|
|
458
|
+
activeChild.kill("SIGTERM");
|
|
459
|
+
} catch {
|
|
460
|
+
}
|
|
461
|
+
setTimeout(() => process.exit(0), 500);
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
} catch (e) {
|
|
465
|
+
console.error(`[fanluc] TUI failed to start: ${e?.message || e}`);
|
|
466
|
+
console.error(e?.stack);
|
|
467
|
+
console.error(`[fanluc] Web still at ${url}`);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
main().catch((e) => {
|
|
472
|
+
console.error(`[fanluc] Fatal:`, e);
|
|
473
|
+
process.exit(1);
|
|
474
|
+
});
|
package/dist/index.html
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>TOOL-NGORK</title>
|
|
7
|
+
<meta name="description" content="Ung dung tro chuyen voi AI qua Custom REST API Endpoint phong cach co dien trang den." />
|
|
8
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
10
|
+
<link href="https://fonts.googleapis.com/css2?family=Courier+Prime:ital,wght@0,400;0,700;1,400;1,700&family=Inconsolata:wght@200..900&family=Lora:ital,wght@0,400..700;1,400..700&family=Share+Tech+Mono&family=VT323&display=swap" rel="stylesheet">
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-DcpVteR3.js"></script>
|
|
12
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BylpTP-E.css">
|
|
13
|
+
</head>
|
|
14
|
+
<body>
|
|
15
|
+
<div id="root"></div>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
|
18
|
+
|
|
Binary file
|