fanluc 2.0.0 → 2.4.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 (75) hide show
  1. package/README.md +83 -0
  2. package/dist/assets/{abnfDiagram-O67JEVCF-hiYMdxpV.js → abnfDiagram-O67JEVCF-Di2iDQfl.js} +1 -1
  3. package/dist/assets/{arc-DokRg_wy.js → arc-wXq_yD5s.js} +1 -1
  4. package/dist/assets/{architectureDiagram-NJMV4G6O-UzHYe9Bx.js → architectureDiagram-NJMV4G6O-CTXbg-3W.js} +1 -1
  5. package/dist/assets/{blockDiagram-BEXU5L5S-DqM5UILT.js → blockDiagram-BEXU5L5S-DSDoL3RP.js} +1 -1
  6. package/dist/assets/{c4Diagram-YGBWAQC7-Cxkdm7sV.js → c4Diagram-YGBWAQC7-3sFM3iyw.js} +1 -1
  7. package/dist/assets/channel-atnI5OIR.js +1 -0
  8. package/dist/assets/{chunk-2Q5K7J3B-Nl4zmLlG.js → chunk-2Q5K7J3B-DUOKiJNI.js} +1 -1
  9. package/dist/assets/{chunk-3FUC2YCW-BCol78O2.js → chunk-3FUC2YCW-ablWb8_c.js} +2 -2
  10. package/dist/assets/{chunk-5VM5RSS4-1u4YRv2Y.js → chunk-5VM5RSS4-qmlSVZ_1.js} +1 -1
  11. package/dist/assets/{chunk-GTNCS2PH-BG9YIV2h.js → chunk-GTNCS2PH-PLlC7cNS.js} +1 -1
  12. package/dist/assets/{chunk-GWA4HPMP-BTKfkJAH.js → chunk-GWA4HPMP-DK-1a6YJ.js} +1 -1
  13. package/dist/assets/{chunk-JWPE2WC7-DCjElfA7.js → chunk-JWPE2WC7-D9zynFKs.js} +1 -1
  14. package/dist/assets/{chunk-NETBCI7D-QoJORlVn.js → chunk-NETBCI7D-BvzPMYyE.js} +1 -1
  15. package/dist/assets/{chunk-WEXAMYUT-Da-T8ct9.js → chunk-WEXAMYUT-CzcfZFWB.js} +1 -1
  16. package/dist/assets/{chunk-XXDRQBXY-CLu69Uei.js → chunk-XXDRQBXY-DznN-rwG.js} +1 -1
  17. package/dist/assets/{classDiagram-v2-NBCMYWYE-DIXavbI9.js → classDiagram-v2-NBCMYWYE-D4jem0hV.js} +1 -1
  18. package/dist/assets/{cose-bilkent-JH36ORCC-DuWclG8-.js → cose-bilkent-JH36ORCC-VALsUQJS.js} +1 -1
  19. package/dist/assets/{cynefin-OW5HDTMX-D2CG5-xd.js → cynefin-OW5HDTMX-DK77QSI8.js} +1 -1
  20. package/dist/assets/{cynefinDiagram-VND7K2PF-goI7_Qfg.js → cynefinDiagram-VND7K2PF-CdYXfGNR.js} +1 -1
  21. package/dist/assets/{dagre-6A5THRUB-BmOaZ0cm.js → dagre-6A5THRUB-DeeUQzDV.js} +1 -1
  22. package/dist/assets/{diagram-22UHCM2B-DjDJFMw3.js → diagram-22UHCM2B-B9shZ914.js} +1 -1
  23. package/dist/assets/{diagram-3UASUU5V-J6z81NH3.js → diagram-3UASUU5V-gSHvaXBI.js} +1 -1
  24. package/dist/assets/{diagram-ATOU4E4O-B1MPU8ai.js → diagram-ATOU4E4O-BrCI5HzS.js} +1 -1
  25. package/dist/assets/{diagram-CDSNMT55-X5Uss0Yv.js → diagram-CDSNMT55-XtVlMRq0.js} +1 -1
  26. package/dist/assets/{diagram-MLGK6HIB-CRDqfYy1.js → diagram-MLGK6HIB-BVa6KsvA.js} +1 -1
  27. package/dist/assets/{diagram-MPIPVDR6-bhJtyiOq.js → diagram-MPIPVDR6-C8P23P2T.js} +1 -1
  28. package/dist/assets/{ebnfDiagram-ZINNZB2B-Dn1mZAV8.js → ebnfDiagram-ZINNZB2B-BrooaQj5.js} +1 -1
  29. package/dist/assets/{elk-276RUBZZ-C93BlBEU.js → elk-276RUBZZ-B-qiKlkF.js} +1 -1
  30. package/dist/assets/{erDiagram-OPXOYQCR-PuzjAvhD.js → erDiagram-OPXOYQCR-CL1bp2Mf.js} +1 -1
  31. package/dist/assets/{flowDiagram-KWPJA3E3-oRhFgO0p.js → flowDiagram-KWPJA3E3-BMQvaQ-5.js} +1 -1
  32. package/dist/assets/{ganttDiagram-FUAMR5RP-DKiU1XjU.js → ganttDiagram-FUAMR5RP-PLnTGRI8.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-X574FWY7-CWBMI0vb.js → gitGraphDiagram-X574FWY7-CiTN3Joc.js} +1 -1
  34. package/dist/assets/index-BEKLwzQT.css +1 -0
  35. package/dist/assets/index-C8gDJtDK.js +2841 -0
  36. package/dist/assets/{infoDiagram-VRGFBTTK-C5Hd4Afz.js → infoDiagram-VRGFBTTK-ComkS_96.js} +1 -1
  37. package/dist/assets/{ishikawaDiagram-OU5B5YK6-BdD6ulRK.js → ishikawaDiagram-OU5B5YK6-D-dNoDFJ.js} +1 -1
  38. package/dist/assets/{journeyDiagram-ZHPQQLJL-CzT-uvks.js → journeyDiagram-ZHPQQLJL-BMm9BQM8.js} +1 -1
  39. package/dist/assets/{kanban-definition-PNTS6WVX-BkrazFMR.js → kanban-definition-PNTS6WVX-D5If-l_D.js} +1 -1
  40. package/dist/assets/{linear-B0wSM1Dr.js → linear-DXAHb0Ag.js} +1 -1
  41. package/dist/assets/{mindmap-definition-NLK3R4M7-Df1Acqkf.js → mindmap-definition-NLK3R4M7-CIdwCXTJ.js} +1 -1
  42. package/dist/assets/{pegDiagram-GJSIUBJH-DK_F7h_0.js → pegDiagram-GJSIUBJH-MY7FoVQh.js} +1 -1
  43. package/dist/assets/{pieDiagram-5QR66LMP-BMXyNb9R.js → pieDiagram-5QR66LMP-C7lWWAtM.js} +1 -1
  44. package/dist/assets/{quadrantDiagram-O4NWA36T-Dfu7Z8so.js → quadrantDiagram-O4NWA36T-jHqCwvi8.js} +1 -1
  45. package/dist/assets/{railroadDiagram-XR7U4H2S-CfnBD-aQ.js → railroadDiagram-XR7U4H2S-BwoIA8Ln.js} +1 -1
  46. package/dist/assets/{requirementDiagram-PLB6GJNP-DrvsZJPP.js → requirementDiagram-PLB6GJNP-Cw8w0Rwb.js} +1 -1
  47. package/dist/assets/{sankeyDiagram-IPEJSGJF-DvdVDpT1.js → sankeyDiagram-IPEJSGJF-DTRo1Yxy.js} +1 -1
  48. package/dist/assets/{sequenceDiagram-PO4LG4MO-D1UTrvZH.js → sequenceDiagram-PO4LG4MO-DqZddXsc.js} +1 -1
  49. package/dist/assets/{sizeCapture-INFHLROL-CJrOBcai.js → sizeCapture-INFHLROL-BAEQ4XP5.js} +1 -1
  50. package/dist/assets/{stateDiagram-v2-GCMORJYK-CX8VmtqD.js → stateDiagram-v2-GCMORJYK-CYB4CouD.js} +1 -1
  51. package/dist/assets/{swimlanes-2SLR337P-DUWmhiK6.js → swimlanes-2SLR337P-Dw53LU13.js} +1 -1
  52. package/dist/assets/swimlanesDiagram-TC7HE7FX-Dzkep6h_.js +8 -0
  53. package/dist/assets/{timeline-definition-EJHVYXUP-CBovVdOU.js → timeline-definition-EJHVYXUP-Chk-unGo.js} +1 -1
  54. package/dist/assets/{usecaseDiagram-POWQR4AR-LD5N5BAG.js → usecaseDiagram-POWQR4AR-D_JpD-4l.js} +1 -1
  55. package/dist/assets/{vennDiagram-UO4OBE2U-DdwfzB3A.js → vennDiagram-UO4OBE2U-BnPO8gk8.js} +1 -1
  56. package/dist/assets/{wardleyDiagram-VNRHLVJA-BMHK4bav.js → wardleyDiagram-VNRHLVJA-CD0z1l8R.js} +1 -1
  57. package/dist/assets/{xychartDiagram-PMCCYNJV-q1BNzMC-.js → xychartDiagram-PMCCYNJV-CHEqj1Pe.js} +1 -1
  58. package/dist/cli.js +52 -57
  59. package/dist/index.html +4 -4
  60. package/dist/server.cjs +2144 -841
  61. package/dist/server.cjs.map +4 -4
  62. package/dist/tui.js +3220 -3310
  63. package/docs/fanluc_ui.md +120 -40
  64. package/docs/guide-connect.md +233 -4
  65. package/docs/run-cmd-security.md +26 -0
  66. package/index.html +2 -2
  67. package/mcp.json +3 -0
  68. package/package.json +88 -92
  69. package/searxng/docker-compose.yml +3 -3
  70. package/vite.config.ts +2 -2
  71. package/dist/assets/channel-SPfNTvmj.js +0 -1
  72. package/dist/assets/index-BylpTP-E.css +0 -1
  73. package/dist/assets/index-DcpVteR3.js +0 -3237
  74. package/dist/assets/permissionHelper--88tJJlz.js +0 -3
  75. package/dist/assets/swimlanesDiagram-TC7HE7FX-DMQy1553.js +0 -8
package/dist/cli.js CHANGED
@@ -11,22 +11,21 @@ import path from "path";
11
11
  import fs from "fs";
12
12
  import os from "os";
13
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"
14
+ ask: "Ask every time before writing files or running shell commands",
15
+ autoEdit: "Auto-allow file writes, ask only for shell commands",
16
+ readOnly: "Read only - block all file writes and shell commands"
17
17
  };
18
18
  var DEFAULT_SETTINGS = {
19
19
  permission: "ask",
20
20
  port: 3e3
21
21
  };
22
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") {
23
+ if (process.env.FANLUC_CONFIG_DIR && process.env.FANLUC_CONFIG_DIR.trim()) {
24
+ return process.env.FANLUC_CONFIG_DIR.trim();
25
+ }
26
+ if (process.platform === "win32") {
26
27
  const appData = process.env.APPDATA;
27
- if (appData && appData.trim()) {
28
- return path.join(appData, "fanluc");
29
- }
28
+ if (appData && appData.trim()) return path.join(appData, "fanluc");
30
29
  return path.join(os.homedir(), "AppData", "Roaming", "fanluc");
31
30
  }
32
31
  const xdg = process.env.XDG_CONFIG_HOME;
@@ -60,9 +59,8 @@ function loadSettings(configDir) {
60
59
  } catch (e) {
61
60
  console.warn(`[fanluc] Failed to parse settings.json:`, e);
62
61
  }
63
- const perm = parsed.permission;
64
62
  const valid = ["ask", "autoEdit", "readOnly"];
65
- const permission = valid.includes(perm) ? perm : DEFAULT_SETTINGS.permission;
63
+ const permission = valid.includes(parsed.permission) ? parsed.permission : DEFAULT_SETTINGS.permission;
66
64
  return { ...DEFAULT_SETTINGS, ...parsed, permission };
67
65
  }
68
66
  function saveSettings(settings, configDir) {
@@ -80,7 +78,7 @@ function ensureMcpJson(configDir) {
80
78
  if (fs.existsSync(target)) return target;
81
79
  try {
82
80
  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)`);
81
+ console.log(`[fanluc] Created empty mcp.json at ${target}`);
84
82
  } catch {
85
83
  }
86
84
  return target;
@@ -107,18 +105,17 @@ function findFanlucMd(workspaceDir) {
107
105
  }
108
106
  return null;
109
107
  }
108
+ var MAX_FANLUC_MD = 6e3;
110
109
  function loadFanlucMd(workspaceDir) {
111
110
  const found = findFanlucMd(workspaceDir);
112
111
  if (!found) return null;
113
112
  try {
114
113
  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 ...]" };
114
+ if (raw.length > MAX_FANLUC_MD) {
115
+ console.warn(`[fanluc] FANLUC.md too large (${raw.length} chars), truncating to ${MAX_FANLUC_MD}`);
116
+ return { path: found, content: raw.slice(0, MAX_FANLUC_MD) + "\n\n[... truncated ...]" };
120
117
  }
121
- return { path: found, content };
118
+ return { path: found, content: raw };
122
119
  } catch (e) {
123
120
  console.warn(`[fanluc] Failed to read ${found}:`, e);
124
121
  return null;
@@ -128,7 +125,8 @@ function loadFanlucMd(workspaceDir) {
128
125
  // src/cli/index.ts
129
126
  var PKG = (() => {
130
127
  try {
131
- const pkgPath = path2.join(path2.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, "$1")), "..", "..", "package.json");
128
+ const here = path2.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, "$1"));
129
+ const pkgPath = path2.join(here, "..", "..", "package.json");
132
130
  for (const p of [pkgPath, path2.join(process.cwd(), "package.json"), path2.join(path2.dirname(process.argv[1] ?? ""), "..", "package.json")]) {
133
131
  if (fs2.existsSync(p)) {
134
132
  const j = JSON.parse(fs2.readFileSync(p, "utf-8"));
@@ -141,7 +139,7 @@ var PKG = (() => {
141
139
  })();
142
140
  function printHelp() {
143
141
  console.log(`
144
- fanluc \u2014 terminal tool gi\u1ED1ng Claude Code/opencode (web v\u1EABn ch\u1EA1y song song)
142
+ fanluc - terminal tool like Claude Code / opencode (web UI runs alongside)
145
143
 
146
144
  Usage:
147
145
  fanluc [options] [workspace]
@@ -149,24 +147,26 @@ Usage:
149
147
  fanluc --version
150
148
 
151
149
  Options:
152
- -p, --port <n> Port to listen (default from settings or 3000)
150
+ -p, --port <n> Port to listen on (default from settings or 3000)
153
151
  --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)
152
+ --no-open Don't auto-open the browser
153
+ --tui Force TUI (default: auto when attached to a TTY)
154
+ --no-tui, --web No TUI, server + browser only
157
155
  --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)
156
+ (ask: confirm every time, autoEdit: auto-allow file
157
+ writes, readOnly: read only)
159
158
 
160
159
  Config:
161
- Windows: %APPDATA%\\fanluc\\ (v\xED d\u1EE5 C:\\Users\\${os2.userInfo().username}\\AppData\\Roaming\\fanluc)
160
+ Windows: %APPDATA%\\fanluc\\ (e.g. C:\\Users\\${os2.userInfo().username}\\AppData\\Roaming\\fanluc)
162
161
  Linux/mac: ~/.config/fanluc/
163
162
  - settings.json { permission, port, ... }
164
- - mcp.json { mcpServers: {...} } (copy t\u1EEB ./mcp.json n\u1EBFu c\xF3)
163
+ - mcp.json { mcpServers: {...} }
165
164
 
166
165
  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.
166
+ The current directory is the workspace. A FANLUC.md file in the
167
+ workspace is auto-loaded into the system prompt.
168
168
 
169
- Permissions (3 ch\u1EBF \u0111\u1ED9):
169
+ Permissions (3 modes):
170
170
  ask = ${PERMISSION_DESCRIPTIONS.ask}
171
171
  autoEdit = ${PERMISSION_DESCRIPTIONS.autoEdit}
172
172
  readOnly = ${PERMISSION_DESCRIPTIONS.readOnly}
@@ -177,7 +177,7 @@ Examples:
177
177
  fanluc D:\\my-project
178
178
  fanluc --no-open
179
179
 
180
- Web v\u1EABn ch\u1EA1y: http://localhost:<port>
180
+ Web UI: http://localhost:<port>
181
181
  `);
182
182
  }
183
183
  function printVersion() {
@@ -191,8 +191,7 @@ function parseArgs(argv) {
191
191
  else if (a === "--version" || a === "-v") out.version = true;
192
192
  else if (a === "--no-open") out.noOpen = true;
193
193
  else if (a === "--port" || a === "-p") {
194
- const v = argv[++i];
195
- const n = parseInt(v, 10);
194
+ const n = parseInt(argv[++i], 10);
196
195
  if (!isNaN(n)) out.port = n;
197
196
  } else if (a.startsWith("--port=")) {
198
197
  const n = parseInt(a.split("=")[1], 10);
@@ -220,21 +219,18 @@ function parseArgs(argv) {
220
219
  return out;
221
220
  }
222
221
  function openBrowser(url) {
223
- const platform = process.platform;
224
- let cmd;
225
- if (platform === "win32") {
226
- cmd = `start "" "${url}"`;
227
- exec(cmd, (err) => {
222
+ if (process.platform === "win32") {
223
+ exec(`start "" "${url}"`, (err) => {
228
224
  if (err) {
229
225
  exec(`powershell -NoProfile -Command "Start-Process '${url}'"`, (e2) => {
230
- if (e2) console.warn(`[fanluc] Failed to open browser: ${e2.message}. Please open manually: ${url}`);
226
+ if (e2) console.warn(`[fanluc] Failed to open browser: ${e2.message}. Open manually: ${url}`);
231
227
  else console.log(`[fanluc] Opened ${url} via PowerShell`);
232
228
  });
233
229
  } else {
234
230
  console.log(`[fanluc] Opened ${url}`);
235
231
  }
236
232
  });
237
- } else if (platform === "darwin") {
233
+ } else if (process.platform === "darwin") {
238
234
  exec(`open "${url}"`, (err) => {
239
235
  if (err) console.warn(`[fanluc] Failed to open browser: ${err.message}. Open manually: ${url}`);
240
236
  else console.log(`[fanluc] Opened ${url}`);
@@ -258,7 +254,7 @@ async function main() {
258
254
  }
259
255
  const configDir = getConfigDir();
260
256
  ensureConfigDir(configDir);
261
- let settings = loadSettings(configDir);
257
+ const settings = loadSettings(configDir);
262
258
  if (args.permission) {
263
259
  settings.permission = args.permission;
264
260
  saveSettings(settings, configDir);
@@ -304,7 +300,8 @@ async function main() {
304
300
  }
305
301
  workspace = resolved;
306
302
  } catch (e) {
307
- console.error(`[fanluc] Workspace not found: ${resolved} \u2014 ${e?.message}`);
303
+ const msg = e instanceof Error ? e.message : String(e);
304
+ console.error(`[fanluc] Workspace not found: ${resolved} - ${msg}`);
308
305
  process.exit(1);
309
306
  }
310
307
  } else {
@@ -314,20 +311,19 @@ async function main() {
314
311
  }
315
312
  }
316
313
  const fanlucMd = loadFanlucMd(workspace);
317
- const configDirDisplay = configDir;
318
314
  const mcpPath = path2.join(configDir, "mcp.json");
319
315
  const settingsPath = path2.join(configDir, "settings.json");
320
316
  console.log(`[fanluc] Install dir: ${installDir}`);
321
- console.log(`[fanluc] Config dir : ${configDirDisplay}`);
317
+ console.log(`[fanluc] Config dir : ${configDir}`);
322
318
  console.log(`[fanluc] - settings: ${settingsPath} (permission=${settings.permission})`);
323
319
  console.log(`[fanluc] - mcp : ${mcpPath}`);
324
320
  console.log(`[fanluc] Workspace : ${workspace}`);
325
321
  if (fanlucMd) {
326
322
  console.log(`[fanluc] FANLUC.md : ${fanlucMd.path} (${fanlucMd.content.length} chars, injected into system prompt)`);
327
323
  } else {
328
- console.log(`[fanluc] FANLUC.md : (none) \u2014 create ${path2.join(workspace, "FANLUC.md")} to auto-inject`);
324
+ console.log(`[fanluc] FANLUC.md : (none) - create ${path2.join(workspace, "FANLUC.md")} to auto-inject`);
329
325
  }
330
- console.log(`[fanluc] Permission: ${settings.permission} \u2014 ${PERMISSION_DESCRIPTIONS[settings.permission]}`);
326
+ console.log(`[fanluc] Permission: ${settings.permission} - ${PERMISSION_DESCRIPTIONS[settings.permission]}`);
331
327
  const port = settings.port || 3e3;
332
328
  const host = settings.host || "127.0.0.1";
333
329
  const url = `http://${host === "0.0.0.0" ? "localhost" : host}:${port}`;
@@ -347,7 +343,7 @@ async function main() {
347
343
  serverEntry = serverCjs;
348
344
  } else if (fs2.existsSync(serverTs)) {
349
345
  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).`);
346
+ console.error(`[fanluc] Run 'npm run build:all' in ${installDir} first.`);
351
347
  process.exit(1);
352
348
  }
353
349
  if (!serverEntry) {
@@ -358,12 +354,11 @@ async function main() {
358
354
  console.log(`[fanluc] (installDir=${installDir}, workspace=${workspace})`);
359
355
  const shouldUseTui = !args.noTui && (args.tui || !!process.stdout.isTTY);
360
356
  let serverStdio = "inherit";
361
- let serverLogPath = "";
362
357
  if (shouldUseTui) {
363
358
  try {
364
359
  const cfgDir = getConfigDir();
365
360
  fs2.mkdirSync(cfgDir, { recursive: true });
366
- serverLogPath = path2.join(cfgDir, "server.log");
361
+ const serverLogPath = path2.join(cfgDir, "server.log");
367
362
  const fd = fs2.openSync(serverLogPath, "w");
368
363
  serverStdio = ["ignore", fd, fd];
369
364
  process.env.FANLUC_SERVER_LOG = serverLogPath;
@@ -387,12 +382,11 @@ async function main() {
387
382
  openBrowser(url);
388
383
  }, 1500);
389
384
  } 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}`);
385
+ console.log(`[fanluc] TUI enabled - browser not auto-opened (use --web or --no-tui to open it). Web UI: ${url}`);
391
386
  } else {
392
387
  console.log(`[fanluc] Auto-open disabled (--no-open). Open manually: ${url}`);
393
388
  }
394
- const signals = ["SIGINT", "SIGTERM"];
395
- for (const sig of signals) {
389
+ for (const sig of ["SIGINT", "SIGTERM"]) {
396
390
  process.on(sig, () => {
397
391
  console.log(`
398
392
  [fanluc] Received ${sig}, shutting down...`);
@@ -419,7 +413,7 @@ async function main() {
419
413
  }
420
414
  console.log(`[fanluc] Starting TUI... (workspace: ${workspace}, FANLUC.md: ${fanlucMd ? fanlucMd.path : "none"})`);
421
415
  try {
422
- const tuiPath = path2.join(installDir, "dist/tui.js");
416
+ const tuiPath = path2.join(installDir, "dist", "tui.js");
423
417
  if (!fs2.existsSync(tuiPath)) {
424
418
  console.error(`[fanluc] TUI not built at ${tuiPath}. Run 'npm run build:tui' or 'npm run build:all' first.`);
425
419
  console.error(`[fanluc] Falling back to browser. Open ${url} manually.`);
@@ -429,12 +423,12 @@ async function main() {
429
423
  const fileUrl = `file://${tuiPath.replace(/\\/g, "/").replace(/^([A-Z]):/, "/$1:")}`;
430
424
  const normalizedUrl = fileUrl.startsWith("file:////") ? fileUrl.replace("file:////", "file:///") : fileUrl;
431
425
  const tuiMod = await import(normalizedUrl);
432
- const startTui = tuiMod.startTui || tuiMod.default?.startTui || tuiMod.default;
426
+ const startTui = tuiMod.startTui || (typeof tuiMod.default === "function" ? tuiMod.default : tuiMod.default?.startTui);
433
427
  if (typeof startTui !== "function") throw new Error("startTui not found in dist/tui.js");
434
428
  const tuiConfig = {
435
429
  apiUrl: settings.apiUrl || process.env.OPENAI_API_URL || process.env.API_URL || "",
436
430
  apiKey: settings.apiKey || process.env.OPENAI_API_KEY || process.env.API_KEY || "none",
437
- model: settings.model || settings.model || "",
431
+ model: settings.model || "",
438
432
  systemPrompt: settings.systemPrompt || "",
439
433
  temperature: settings.temperature ?? 0.7,
440
434
  maxTokens: settings.maxTokens ?? 8e3,
@@ -462,9 +456,10 @@ async function main() {
462
456
  }
463
457
  });
464
458
  } 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}`);
459
+ const err = e;
460
+ console.error(`[fanluc] TUI failed to start: ${err?.message || e}`);
461
+ console.error(err?.stack);
462
+ console.error(`[fanluc] Web UI: ${url}`);
468
463
  }
469
464
  }
470
465
  }
package/dist/index.html CHANGED
@@ -3,13 +3,13 @@
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
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." />
6
+ <title>FANLUC</title>
7
+ <meta name="description" content="Self-hosted AI chat with a classic black-and-white terminal style. Custom REST API endpoint." />
8
8
  <link rel="preconnect" href="https://fonts.googleapis.com">
9
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
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">
11
+ <script type="module" crossorigin src="/assets/index-C8gDJtDK.js"></script>
12
+ <link rel="stylesheet" crossorigin href="/assets/index-BEKLwzQT.css">
13
13
  </head>
14
14
  <body>
15
15
  <div id="root"></div>