march-control-cli 0.1.3 → 0.1.5

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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # March Control
1
+ # FHL Control
2
2
 
3
3
  `march` is a provider configuration manager for:
4
4
 
@@ -8,7 +8,7 @@
8
8
 
9
9
  It provides both:
10
10
 
11
- - CLI (`march` / `mch`)
11
+ - CLI (`march` / `mch` / `ccman`)
12
12
  - Desktop/web workspace in this repo (`src/`, `desktop/`, `src-tauri/`)
13
13
 
14
14
  ## CLI Features
@@ -27,6 +27,10 @@ It provides both:
27
27
  - `preset add`
28
28
  - `preset use`
29
29
  - `preset remove`
30
+ - FHL quick shortcuts:
31
+ - `fhl`
32
+ - `fhl1`
33
+ - `fhlcode`
30
34
  - URL probe before apply
31
35
  - Safe backup/rollback before writing config files
32
36
 
@@ -36,36 +40,42 @@ It provides both:
36
40
  npm install -g march-control-cli
37
41
  ```
38
42
 
43
+ If `ccman` command already exists from another package, uninstall it first:
44
+
45
+ ```bash
46
+ npm uninstall -g ccman
47
+ ```
48
+
39
49
  Install now includes:
40
50
 
41
51
  - Chinese postinstall banner with quick-start commands
42
52
  - Branded setup flow with step-by-step output (`[1/4] ... [4/4]`)
43
- - Dual command entrypoints: `march` and `mch`
53
+ - Dual command entrypoints: `march`, `mch`, and `ccman`
44
54
 
45
55
  ## Quick Start
46
56
 
47
57
  Interactive quick setup:
48
58
 
49
59
  ```bash
50
- march
60
+ ccman
51
61
  ```
52
62
 
53
63
  Non-interactive setup:
54
64
 
55
65
  ```bash
56
- march setup -k sk-your-key
66
+ ccman setup -k sk-your-key
57
67
 
58
68
  # Setup using a preset
59
- march setup --preset march -k sk-your-key
69
+ ccman setup --preset march -k sk-your-key
60
70
  ```
61
71
 
62
72
  Open full interactive menu:
63
73
 
64
74
  ```bash
65
- march menu
75
+ ccman menu
66
76
 
67
77
  # same command alias
68
- mch menu
78
+ march menu
69
79
  ```
70
80
 
71
81
  ## Command Examples
@@ -89,6 +99,12 @@ march preset list
89
99
  march preset add -n team-a --provider-name teama --base-url https://example.com
90
100
  march preset use team-a -k sk-xxx -p codex,opencode
91
101
  march preset remove team-a
102
+
103
+ # FHL quick shortcuts (ccman fhl-like)
104
+ ccman fhl sk-xxx
105
+ ccman fhl --platform codex,openclaw
106
+ ccman fhlcode -k sk-xxx --no-probe
107
+ march fhl sk-xxx
92
108
  ```
93
109
 
94
110
  Probe endpoints:
@@ -149,6 +165,7 @@ node scripts/postinstall.mjs
149
165
  node core/index.js --help
150
166
  node core/index.js preset --help
151
167
  node core/index.js cx --help
168
+ node core/index.js fhl --help
152
169
  ```
153
170
 
154
171
  隔离目录实测(不污染当前用户目录):
@@ -168,13 +185,14 @@ npm pack
168
185
  npm uninstall -g march-control-cli
169
186
 
170
187
  # 安装你刚打出来的 tgz 包(把版本号替换成当前文件名)
171
- npm install -g .\\march-control-cli-0.1.3.tgz
188
+ npm install -g .\\march-control-cli-0.1.4.tgz
172
189
 
173
190
  # 客户侧常用验证命令
191
+ ccman --help
174
192
  march --help
175
- mch --help
176
- march
177
- march setup --preset march -k sk-demo-1234 --platforms codex --no-probe
193
+ ccman fhl --help
194
+ ccman
195
+ ccman setup --preset march -k sk-demo-1234 --platforms codex --no-probe
178
196
  ```
179
197
 
180
198
  ## Desktop
package/core/constants.js CHANGED
@@ -1,8 +1,21 @@
1
- export const APP_NAME = "march";
2
- export const DEFAULT_PROVIDER_NAME = "march";
1
+ export const APP_NAME = "ccman";
2
+ export const DEFAULT_PROVIDER_NAME = "fhl";
3
3
  export const DEFAULT_BASE_URL = "https://gmncode.cn";
4
4
  export const DEFAULT_OPENCLAW_BASE_URL = "https://gmncode.cn/v1";
5
5
  export const DEFAULT_PRIMARY_MODEL = "gpt-5.4";
6
+ export const FHL_PROVIDER_NAME = "fhl";
7
+ export const FHL_BASE_URL = "https://gmn.chuangzuoli.com";
8
+ export const FHL_OPENCLAW_BASE_URL = "https://gmn.chuangzuoli.com";
9
+ export const FHL_CANDIDATE_BASE_URLS = [
10
+ "https://gmn.chuangzuoli.com",
11
+ "https://gmn.chuangzuoli.com/v1",
12
+ "https://gmn.chuangzuoli.com/api"
13
+ ];
14
+ // Backward compatibility with previous naming.
15
+ export const GMN_PROVIDER_NAME = FHL_PROVIDER_NAME;
16
+ export const GMN_BASE_URL = FHL_BASE_URL;
17
+ export const GMN_OPENCLAW_BASE_URL = FHL_OPENCLAW_BASE_URL;
18
+ export const GMN_CANDIDATE_BASE_URLS = FHL_CANDIDATE_BASE_URLS;
6
19
 
7
20
  export const PLATFORM_META = {
8
21
  codex: {
@@ -29,7 +42,7 @@ export const DEFAULT_CANDIDATE_BASE_URLS = [DEFAULT_BASE_URL];
29
42
  export const BUILTIN_PRESETS = [
30
43
  {
31
44
  name: "march",
32
- providerName: "march",
45
+ providerName: "fhl",
33
46
  commonBaseUrl: "https://gmncode.cn",
34
47
  openclawBaseUrl: "https://gmncode.cn/v1",
35
48
  model: DEFAULT_PRIMARY_MODEL
@@ -40,6 +53,20 @@ export const BUILTIN_PRESETS = [
40
53
  commonBaseUrl: "https://api.openai.com/v1",
41
54
  openclawBaseUrl: "https://api.openai.com/v1",
42
55
  model: DEFAULT_PRIMARY_MODEL
56
+ },
57
+ {
58
+ name: "gmn",
59
+ providerName: FHL_PROVIDER_NAME,
60
+ commonBaseUrl: FHL_BASE_URL,
61
+ openclawBaseUrl: FHL_OPENCLAW_BASE_URL,
62
+ model: DEFAULT_PRIMARY_MODEL
63
+ },
64
+ {
65
+ name: "fhl",
66
+ providerName: FHL_PROVIDER_NAME,
67
+ commonBaseUrl: FHL_BASE_URL,
68
+ openclawBaseUrl: FHL_OPENCLAW_BASE_URL,
69
+ model: DEFAULT_PRIMARY_MODEL
43
70
  }
44
71
  ];
45
72
 
@@ -85,7 +85,7 @@ export function getDesktopSnapshot() {
85
85
  const desktopState = getDesktopState();
86
86
 
87
87
  return {
88
- appName: "March 控制台",
88
+ appName: "FHL 控制台",
89
89
  version: "0.3.0-alpha",
90
90
  generatedAt: new Date().toISOString(),
91
91
  models: MODEL_DEFINITIONS,
package/core/index.js CHANGED
@@ -17,6 +17,10 @@ import {
17
17
  DEFAULT_PLATFORM_SELECTION,
18
18
  DEFAULT_PRIMARY_MODEL,
19
19
  DEFAULT_PROVIDER_NAME,
20
+ FHL_BASE_URL,
21
+ FHL_CANDIDATE_BASE_URLS,
22
+ FHL_OPENCLAW_BASE_URL,
23
+ FHL_PROVIDER_NAME,
20
24
  PLATFORM_META,
21
25
  SUPPORTED_PLATFORMS
22
26
  } from "./constants.js";
@@ -32,6 +36,17 @@ import {
32
36
  import { getBestProbeResult, probeBaseUrls } from "./probe.js";
33
37
  import { getPreset, listPresets, removePreset, upsertPreset } from "./presets.js";
34
38
 
39
+ const CLI_ENTRY_NAME = (() => {
40
+ const script = `${process.argv[1] || ""}`.toLowerCase();
41
+ if (script.includes("ccman")) {
42
+ return "ccman";
43
+ }
44
+ if (script.includes("mch")) {
45
+ return "mch";
46
+ }
47
+ return APP_NAME;
48
+ })();
49
+
35
50
  function collect(value, previous) {
36
51
  previous.push(value);
37
52
  return previous;
@@ -51,7 +66,7 @@ function printBrandBanner() {
51
66
  for (const line of logo) {
52
67
  console.log(chalk.cyanBright(`│ ${line.padEnd(58, " ")}│`));
53
68
  }
54
- console.log(chalk.yellow("│ March 配置管理工具 │"));
69
+ console.log(chalk.yellow("│ FHL 配置管理工具 │"));
55
70
  console.log(chalk.gray("│ Codex / OpenCode / OpenClaw 一键管理 │"));
56
71
  console.log(chalk.gray("└────────────────────────────────────────────────────────────┘"));
57
72
  }
@@ -105,9 +120,9 @@ function printSetupPlan({
105
120
 
106
121
  function printNextActions() {
107
122
  printHeader("下一步建议");
108
- console.log("1) march menu");
109
- console.log("2) march cx list");
110
- console.log("3) march preset list");
123
+ console.log(`1) ${CLI_ENTRY_NAME} menu`);
124
+ console.log(`2) ${CLI_ENTRY_NAME} cx list`);
125
+ console.log(`3) ${CLI_ENTRY_NAME} preset list`);
111
126
  }
112
127
 
113
128
  function printProviderSummary(platform, provider) {
@@ -200,6 +215,19 @@ function parsePlatforms(platformsArg) {
200
215
  return resolved;
201
216
  }
202
217
 
218
+ function parseShortcutPlatforms(platformsArg) {
219
+ if (!platformsArg) {
220
+ return [...DEFAULT_PLATFORM_SELECTION];
221
+ }
222
+
223
+ const values = parseCommaList(platformsArg).map((value) => value.toLowerCase());
224
+ if (values.includes("all")) {
225
+ return [...DEFAULT_PLATFORM_SELECTION];
226
+ }
227
+
228
+ return parsePlatforms(values.join(","));
229
+ }
230
+
203
231
  async function resolveBaseUrl(label, rawOptions) {
204
232
  const shouldProbe = rawOptions.probe !== false;
205
233
  const confirmChoice = rawOptions.confirmChoice === true;
@@ -341,7 +369,7 @@ async function runSetup(rawOptions = {}) {
341
369
  console.log(chalk.gray(`使用预设: ${options._resolvedPreset.name}`));
342
370
  }
343
371
  } else {
344
- printHeader("March 命令行安装");
372
+ printHeader("FHL 命令行安装");
345
373
  if (options._resolvedPreset) {
346
374
  console.log(chalk.gray(`使用预设: ${options._resolvedPreset.name}`));
347
375
  }
@@ -375,6 +403,8 @@ async function runSetup(rawOptions = {}) {
375
403
  : parsePlatforms(options.platforms);
376
404
 
377
405
  const apiKey = await promptForApiKey(options.apiKey);
406
+ const defaultCandidateBaseUrls = options.defaultCandidateBaseUrls ?? DEFAULT_CANDIDATE_BASE_URLS;
407
+ const defaultOpenclawCandidateBaseUrls = options.defaultOpenclawCandidateBaseUrls ?? [];
378
408
  printSetupStep(
379
409
  2,
380
410
  "检测可用地址",
@@ -384,7 +414,7 @@ async function runSetup(rawOptions = {}) {
384
414
  interactive,
385
415
  confirmChoice: advanced,
386
416
  probe: options.probe,
387
- candidates: [providerMeta.commonBaseUrl, ...DEFAULT_CANDIDATE_BASE_URLS, ...(options.candidateBaseUrls || [])]
417
+ candidates: [providerMeta.commonBaseUrl, ...defaultCandidateBaseUrls, ...(options.candidateBaseUrls || [])]
388
418
  });
389
419
  const openclawBaseUrl = platforms.includes("openclaw")
390
420
  ? await resolveBaseUrl("OpenClaw", {
@@ -393,6 +423,7 @@ async function runSetup(rawOptions = {}) {
393
423
  probe: options.probe,
394
424
  candidates: [
395
425
  providerMeta.openclawBaseUrl || buildOpenClawBaseUrl(commonBaseUrl),
426
+ ...defaultOpenclawCandidateBaseUrls,
396
427
  ...(options.openclawCandidateBaseUrls || [])
397
428
  ]
398
429
  })
@@ -450,6 +481,41 @@ async function runSetup(rawOptions = {}) {
450
481
  printNextActions();
451
482
  }
452
483
 
484
+ async function runFhlShortcut(apiKeyArg, rawOptions = {}) {
485
+ const apiKey = `${apiKeyArg || rawOptions.apiKey || ""}`.trim();
486
+ const providerName = `${rawOptions.name || rawOptions.providerName || FHL_PROVIDER_NAME}`.trim() || FHL_PROVIDER_NAME;
487
+ const platforms = parseShortcutPlatforms(rawOptions.platform || "all");
488
+ const commonBaseUrl = normalizeBaseUrl(rawOptions.baseUrl || FHL_BASE_URL);
489
+ const openclawBaseUrl = normalizeBaseUrl(rawOptions.openclawBaseUrl || rawOptions.baseUrl || FHL_OPENCLAW_BASE_URL);
490
+ const commonCandidates = dedupeStrings([
491
+ commonBaseUrl,
492
+ ...(rawOptions.baseUrl ? [] : FHL_CANDIDATE_BASE_URLS),
493
+ ...(rawOptions.candidateBaseUrl || [])
494
+ ]);
495
+ const openclawCandidates = dedupeStrings([
496
+ openclawBaseUrl,
497
+ ...(rawOptions.openclawBaseUrl ? [] : FHL_CANDIDATE_BASE_URLS),
498
+ ...(rawOptions.openclawCandidateBaseUrl || [])
499
+ ]);
500
+
501
+ await runSetup({
502
+ interactive: false,
503
+ apiKey,
504
+ platforms: platforms.join(","),
505
+ providerName,
506
+ baseUrl: commonBaseUrl,
507
+ openclawBaseUrl,
508
+ model: rawOptions.model || DEFAULT_PRIMARY_MODEL,
509
+ probe: rawOptions.probe,
510
+ backup: rawOptions.backup,
511
+ overwrite: rawOptions.merge === true ? false : true,
512
+ candidateBaseUrls: commonCandidates,
513
+ openclawCandidateBaseUrls: openclawCandidates,
514
+ defaultCandidateBaseUrls: [],
515
+ defaultOpenclawCandidateBaseUrls: []
516
+ });
517
+ }
518
+
453
519
  async function runAdd(platform, rawOptions = {}) {
454
520
  const interactive = rawOptions.interactive !== false;
455
521
  const meta = PLATFORM_META[platform];
@@ -993,7 +1059,7 @@ async function runMainMenu() {
993
1059
  {
994
1060
  type: "list",
995
1061
  name: "action",
996
- message: "March 主菜单",
1062
+ message: "FHL 主菜单",
997
1063
  choices: [
998
1064
  { name: "快速安装", value: "setup" },
999
1065
  { name: "自定义安装", value: "advanced-setup" },
@@ -1032,9 +1098,9 @@ async function runMainMenu() {
1032
1098
  }
1033
1099
  const program = new Command();
1034
1100
  program
1035
- .name(APP_NAME)
1036
- .description("March 服务商配置管理工具")
1037
- .version("0.1.2");
1101
+ .name(CLI_ENTRY_NAME)
1102
+ .description("FHL 服务商配置管理工具")
1103
+ .version("0.1.5");
1038
1104
 
1039
1105
  program
1040
1106
  .command("menu")
@@ -1076,6 +1142,34 @@ program
1076
1142
  });
1077
1143
  });
1078
1144
 
1145
+ function registerFhlShortcutCommand(commandName) {
1146
+ program
1147
+ .command(`${commandName} [apiKey]`)
1148
+ .description("快捷配置 FHL 到 Codex / OpenCode / OpenClaw(默认覆盖写入)")
1149
+ .option("-k, --api-key <apiKey>", "API Key(可与位置参数二选一)")
1150
+ .option("-p, --platform <platforms>", "指定平台:codex,opencode,openclaw,all", "all")
1151
+ .option("-n, --name <providerName>", "服务商名称", FHL_PROVIDER_NAME)
1152
+ .option("-b, --base-url <baseUrl>", "指定 Codex/OpenCode Base URL(默认自动测速)")
1153
+ .option("--openclaw-base-url <baseUrl>", "指定 OpenClaw Base URL")
1154
+ .option("--model <model>", "模型", DEFAULT_PRIMARY_MODEL)
1155
+ .option("--candidate-base-url <url>", "额外 Codex/OpenCode 备选地址", collect, [])
1156
+ .option("--openclaw-candidate-base-url <url>", "额外 OpenClaw 备选地址", collect, [])
1157
+ .option("--merge", "改为合并写入(默认覆盖写入)")
1158
+ .option("--no-probe", "跳过地址测速")
1159
+ .option("--no-backup", "跳过自动备份")
1160
+ .action(async (apiKey, options) => {
1161
+ await runFhlShortcut(apiKey, options);
1162
+ });
1163
+ }
1164
+
1165
+ registerFhlShortcutCommand("fhl");
1166
+ registerFhlShortcutCommand("fhl1");
1167
+ registerFhlShortcutCommand("fhlcode");
1168
+ // Backward compatibility
1169
+ registerFhlShortcutCommand("gmn");
1170
+ registerFhlShortcutCommand("gmn1");
1171
+ registerFhlShortcutCommand("gmncode");
1172
+
1079
1173
  program
1080
1174
  .command("probe")
1081
1175
  .description("测速一个或多个地址")
package/desktop/main.js CHANGED
@@ -69,6 +69,12 @@ async function loadRenderer(window) {
69
69
  return;
70
70
  }
71
71
 
72
+ const externalDistIndex = path.join(path.dirname(process.execPath), "dist", "index.html");
73
+ if (await exists(externalDistIndex)) {
74
+ await window.loadFile(externalDistIndex);
75
+ return;
76
+ }
77
+
72
78
  const appRoot = app.getAppPath();
73
79
  const distIndex = path.join(appRoot, "dist", "index.html");
74
80
 
@@ -90,7 +96,7 @@ function createWindow() {
90
96
  autoHideMenuBar: true,
91
97
  title: "March 控制台",
92
98
  webPreferences: {
93
- preload: path.join(__dirname, "preload.js"),
99
+ preload: path.join(__dirname, "preload.cjs"),
94
100
  contextIsolation: true,
95
101
  nodeIntegration: false
96
102
  }
@@ -0,0 +1,49 @@
1
+ const { contextBridge, ipcRenderer } = require("electron");
2
+
3
+ contextBridge.exposeInMainWorld("marchDesktop", {
4
+ getSnapshot() {
5
+ return ipcRenderer.invoke("desktop:getSnapshot");
6
+ },
7
+ saveProvider(payload) {
8
+ return ipcRenderer.invoke("desktop:saveProvider", payload);
9
+ },
10
+ activateProvider(payload) {
11
+ return ipcRenderer.invoke("desktop:activateProvider", payload);
12
+ },
13
+ probePlatform(payload) {
14
+ return ipcRenderer.invoke("desktop:probePlatform", payload);
15
+ },
16
+ probeCandidate(payload) {
17
+ return ipcRenderer.invoke("desktop:probeCandidate", payload);
18
+ },
19
+ toggleMcp(payload) {
20
+ return ipcRenderer.invoke("desktop:toggleMcp", payload);
21
+ },
22
+ upsertMcp(payload) {
23
+ return ipcRenderer.invoke("desktop:upsertMcp", payload);
24
+ },
25
+ deleteMcp(payload) {
26
+ return ipcRenderer.invoke("desktop:deleteMcp", payload);
27
+ },
28
+ togglePrompt(payload) {
29
+ return ipcRenderer.invoke("desktop:togglePrompt", payload);
30
+ },
31
+ upsertPrompt(payload) {
32
+ return ipcRenderer.invoke("desktop:upsertPrompt", payload);
33
+ },
34
+ deletePrompt(payload) {
35
+ return ipcRenderer.invoke("desktop:deletePrompt", payload);
36
+ },
37
+ upsertSkill(payload) {
38
+ return ipcRenderer.invoke("desktop:upsertSkill", payload);
39
+ },
40
+ deleteSkill(payload) {
41
+ return ipcRenderer.invoke("desktop:deleteSkill", payload);
42
+ },
43
+ toggleSkillRepo(payload) {
44
+ return ipcRenderer.invoke("desktop:toggleSkillRepo", payload);
45
+ },
46
+ openPath(targetPath) {
47
+ return ipcRenderer.invoke("desktop:openPath", targetPath);
48
+ }
49
+ });
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "march-control-cli",
3
- "version": "0.1.3",
4
- "description": "March Control desktop-style manager for Codex, OpenCode, and OpenClaw.",
3
+ "version": "0.1.5",
4
+ "description": "FHL Control desktop-style manager for Codex, OpenCode, and OpenClaw.",
5
5
  "type": "module",
6
6
  "main": "desktop/main.js",
7
7
  "bin": {
8
8
  "march": "core/index.js",
9
- "mch": "core/index.js"
9
+ "mch": "core/index.js",
10
+ "ccman": "core/index.js"
10
11
  },
11
12
  "files": [
12
13
  "core",
@@ -25,7 +26,7 @@
25
26
  "start": "node ./core/index.js",
26
27
  "check": "node --check ./core/index.js",
27
28
  "desktop:dev": "node ./scripts/desktop-dev.mjs",
28
- "desktop:check": "node --check ./desktop/main.js && node --check ./desktop/preload.js && node --check ./desktop/renderer/app.js && node --check ./core/desktop-service.js",
29
+ "desktop:check": "node --check ./desktop/main.js && node --check ./desktop/preload.cjs && node --check ./desktop/renderer/app.js && node --check ./core/desktop-service.js",
29
30
  "desktop:install-runtime": "node ./node_modules/electron/install.js",
30
31
  "desktop:pack": "npm run build:web && electron-builder --win nsis portable",
31
32
  "site:dev": "node ./scripts/serve-site.mjs",
@@ -45,7 +46,7 @@
45
46
  "config",
46
47
  "cli"
47
48
  ],
48
- "author": "March Team",
49
+ "author": "FHL Team",
49
50
  "license": "MIT",
50
51
  "engines": {
51
52
  "node": ">=18.0.0"
@@ -76,7 +77,7 @@
76
77
  },
77
78
  "build": {
78
79
  "appId": "cn.gmn.march.desktop",
79
- "productName": "March-Control",
80
+ "productName": "FHL-Control",
80
81
  "npmRebuild": false,
81
82
  "directories": {
82
83
  "output": "release"
@@ -95,7 +96,7 @@
95
96
  "nsis",
96
97
  "portable"
97
98
  ],
98
- "artifactName": "March-Control-${version}-${arch}.${ext}"
99
+ "artifactName": "FHL-Control-${version}-${arch}.${ext}"
99
100
  },
100
101
  "nsis": {
101
102
  "oneClick": false,
@@ -1,7 +1,7 @@
1
1
  const lines = [
2
2
  "",
3
3
  "============================================================",
4
- " MARCH 安装完成",
4
+ " FHL 安装完成",
5
5
  "============================================================",
6
6
  "",
7
7
  " __ __ _ ____ ____ _ _ ",
@@ -10,15 +10,15 @@ const lines = [
10
10
  "| | | |/ ___ \\| _ <| |___| _ |",
11
11
  "|_| |_/_/ \\_\\_| \\_\\\\____|_| |_|",
12
12
  "",
13
- "欢迎使用 March 配置管理工具",
13
+ "欢迎使用 FHL 配置管理工具",
14
14
  "",
15
15
  "快速开始:",
16
16
  " march",
17
- " march setup --preset march -k sk-xxxx",
17
+ " ccman fhl sk-xxxx",
18
18
  "",
19
19
  "常用命令:",
20
20
  " march --help",
21
- " march menu",
21
+ " ccman fhl --help",
22
22
  " march preset --help",
23
23
  ""
24
24
  ];
@@ -47,5 +47,5 @@ const server = http.createServer((req, res) => {
47
47
  });
48
48
 
49
49
  server.listen(port, () => {
50
- console.log(`March site preview: http://localhost:${port}`);
50
+ console.log(`FHL site preview: http://localhost:${port}`);
51
51
  });
package/src/App.tsx CHANGED
@@ -18,6 +18,8 @@ import type { PlatformId, Provider } from "./types";
18
18
 
19
19
  const emptyForm = { name: "", baseUrl: "", apiKey: "", model: "gpt-5.4" };
20
20
  const allPlatforms: PlatformId[] = ["codex", "opencode", "openclaw"];
21
+ const OPENAI_ASSET = `${import.meta.env.BASE_URL}assets/openai-small.svg`;
22
+ const OPENCLAW_ASSET = `${import.meta.env.BASE_URL}assets/pixel-lobster.svg`;
21
23
 
22
24
  type McpEditorForm = {
23
25
  kind: "mcp";
@@ -84,16 +86,16 @@ function shortUrl(value: string) {
84
86
  function providerArt(provider: Provider, platformId: PlatformId) {
85
87
  const name = provider.name.toLowerCase();
86
88
  const url = provider.baseUrl.toLowerCase();
87
- if (name.includes("openai") || url.includes("openai.com")) return { src: "/assets/openai-small.svg", alt: "OpenAI" };
89
+ if (name.includes("openai") || url.includes("openai.com")) return { src: OPENAI_ASSET, alt: "OpenAI" };
88
90
  if (platformId === "openclaw" || name.includes("openclaw") || name.includes("lobster")) {
89
- return { src: "/assets/pixel-lobster.svg", alt: "OpenClaw" };
91
+ return { src: OPENCLAW_ASSET, alt: "OpenClaw" };
90
92
  }
91
93
  return null;
92
94
  }
93
95
 
94
96
  function platformArt(platformId: PlatformId) {
95
- if (platformId === "codex") return <img src="/assets/openai-small.svg" alt="Codex" className="h-5 w-5 object-contain" />;
96
- if (platformId === "openclaw") return <img src="/assets/pixel-lobster.svg" alt="OpenClaw" className="h-5 w-5 object-contain" />;
97
+ if (platformId === "codex") return <img src={OPENAI_ASSET} alt="Codex" className="h-5 w-5 object-contain" />;
98
+ if (platformId === "openclaw") return <img src={OPENCLAW_ASSET} alt="OpenClaw" className="h-5 w-5 object-contain" />;
97
99
  return <Bot className="h-4 w-4 text-slate-600" />;
98
100
  }
99
101