@matelink/cli 2026.4.7 → 2026.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/matecli.mjs +299 -102
  2. package/package.json +1 -1
package/bin/matecli.mjs CHANGED
@@ -30,13 +30,205 @@ const CLI_COMMAND_NAME = "matecli";
30
30
  // compatibility endpoints remain available without per-method scope juggling.
31
31
  const DEFAULT_GATEWAY_SCOPES = "operator.admin";
32
32
  const CLI_ENTRY = fileURLToPath(import.meta.url);
33
+ const CLI_LANGUAGE = detectCliLanguage();
34
+ const CLI_I18N = {
35
+ zh: {
36
+ help_title: `${CLI_COMMAND_NAME} CLI`,
37
+ help_usage: "用法:",
38
+ help_environment: "环境变量:",
39
+ help_package: `包名: ${CLI_PACKAGE_NAME}`,
40
+ help_tip: "提示:",
41
+ help_tip_pair: " pair 命令会自动以后台模式启动 relay bridge worker。",
42
+ help_tip_no_serve: " 如果不希望自动启动 bridge worker,可使用 --no-serve-relay。",
43
+ help_env_home: " OPENCLAW_HOME 覆盖 OpenClaw 主目录(默认: ~/.openclaw)",
44
+ help_env_openim: " OPENIM_RELAY_URL / OPENIM_RELAY_TOKEN",
45
+ help_env_relay_url: ` TESTNEXTIM_RELAY_URL 覆盖 relay 地址(默认: ${DEFAULT_RELAY_URL})`,
46
+ help_env_relay_token: " TESTNEXTIM_RELAY_TOKEN 用于 pair publish/bind 接口的 Bearer Token",
47
+ help_env_gateway_base:
48
+ " OPENCLAW_TESTNEXTIM_GATEWAY_BASE_URL 本地 gateway 地址(优先于 openclaw.json 中的 gateway.port)",
49
+ help_env_gateway_port:
50
+ " OPENCLAW_GATEWAY_PORT / OPENCLAW_GATEWAY_HOST 可显式覆盖本地 gateway host/port",
51
+ config_read_failed: "无法读取配置文件: {path}",
52
+ config_invalid_json: "配置文件 JSON 格式无效: {path}",
53
+ fail_prefix: CLI_COMMAND_NAME,
54
+ pair_usage_missing: `配对码不能为空。用法: ${CLI_COMMAND_NAME} pair <1234|123456|XXXX-XXXX>`,
55
+ relay_url_required: "缺少 relay 地址。",
56
+ relay_url_missing_detail: "已启用 relay,但当前没有可用的 URL。",
57
+ relay_url_hint: `可直接使用内置默认值(${DEFAULT_RELAY_URL}),或通过 --relay / TESTNEXTIM_RELAY_URL 传入。`,
58
+ pair_code_not_found: "配对码不存在: {code}",
59
+ pair_code_generate_in_app: "请先在手机 App 里生成新的配对码。",
60
+ pair_code_consumed: "这个配对码已经被使用过了: {code}",
61
+ pair_missing_client_user_id: "这个配对码缺少客户端标识。",
62
+ pair_regenerate_latest: "请在 App 里重新生成最新的配对码后再试一次。",
63
+ bound_host_exists: "这台主机已经绑定过设备。",
64
+ current_client_user_id: "当前 clientUserId: {value}",
65
+ active_pair_code: "当前配对码: {value}",
66
+ reset_first: `请先执行 \`${CLI_COMMAND_NAME} reset\` 清掉当前绑定,再重新配对。`,
67
+ binding_cleared: "已清除绑定: {value}",
68
+ restart_failed_after_config: "更新配置后,OpenClaw 重启失败。",
69
+ restart_manual_retry: "请手动执行 `openclaw daemon restart` 后重试。",
70
+ gateway_not_ready: "本地 OpenClaw 网关 `/v1/responses` 还没有准备好。",
71
+ gateway_checked: "检查地址: {url}",
72
+ gateway_http_expected: "HTTP {status}(预期不是 401/403/404)",
73
+ gateway_request_failed: "{error}",
74
+ gateway_ready_hint: "请确认 OpenClaw daemon 正在运行,并且 responses endpoint 已启用。",
75
+ pair_success: "配对已就绪,请回到 App 完成确认。",
76
+ pair_worker_failed: "配对初始化失败,relay bridge worker 启动失败。",
77
+ setup_updated: "配置已更新: {path}",
78
+ setup_ready: "配置已准备好: {path}",
79
+ setup_state_file: "状态文件: {path}",
80
+ setup_transport: "聊天传输模式: {mode}",
81
+ setup_restarted: "OpenClaw 已重启。",
82
+ setup_restart_failed: "OpenClaw 重启失败,请手动执行:",
83
+ no_active_binding: "当前没有可清除的绑定。",
84
+ relay_reset_done: "Relay 绑定已重置。",
85
+ relay_reset_empty: "Relay 上没有可清除的绑定。",
86
+ relay_reset_skipped: "已跳过 Relay 绑定重置。",
87
+ stopped_bridge_count: "已停止 {count} 个 bridge 进程。",
88
+ local_state_reset: "本地状态已重置: {path}",
89
+ reset_ready: `现在可以重新执行 \`${CLI_COMMAND_NAME} pair <code>\`。`,
90
+ status_config: "配置文件: {value}",
91
+ status_channel: "通道: {value}",
92
+ status_enabled: "启用: {value}",
93
+ status_bind_base: "绑定地址: {value}",
94
+ status_relay_url: "Relay 地址: {value}",
95
+ status_transport: "传输模式: {value}",
96
+ status_gateway_base: "本地 Gateway: {value}",
97
+ status_webhook_path: "Webhook 路径: {value}",
98
+ status_bind_path: "Bind 路径: {value}",
99
+ status_gateway_id: "Relay Gateway ID: {value}",
100
+ status_client_token: "Relay Client Token: {value}",
101
+ status_gateway_token: "Relay Gateway Token: {value}",
102
+ status_app_secret: "App Secret: {value}",
103
+ status_access_token: "Access Token: {value}",
104
+ status_linked_user: "已绑定用户: {value}",
105
+ yes: "是",
106
+ no: "否",
107
+ set: "已设置",
108
+ missing: "缺失",
109
+ not_set: "未设置",
110
+ not_linked: "未绑定",
111
+ },
112
+ en: {
113
+ help_title: `${CLI_COMMAND_NAME} CLI`,
114
+ help_usage: "Usage:",
115
+ help_environment: "Environment:",
116
+ help_package: `Package: ${CLI_PACKAGE_NAME}`,
117
+ help_tip: "Tip:",
118
+ help_tip_pair: " The pair command auto-starts the relay bridge worker in detached mode.",
119
+ help_tip_no_serve: " Use --no-serve-relay if you do not want the bridge worker to start automatically.",
120
+ help_env_home: " OPENCLAW_HOME Override OpenClaw home directory (default: ~/.openclaw)",
121
+ help_env_openim: " OPENIM_RELAY_URL / OPENIM_RELAY_TOKEN",
122
+ help_env_relay_url: ` TESTNEXTIM_RELAY_URL Override relay base URL (default: ${DEFAULT_RELAY_URL})`,
123
+ help_env_relay_token: " TESTNEXTIM_RELAY_TOKEN Bearer token used for pair publish/bind endpoints",
124
+ help_env_gateway_base:
125
+ " OPENCLAW_TESTNEXTIM_GATEWAY_BASE_URL Local gateway base (overrides openclaw.json gateway.port)",
126
+ help_env_gateway_port:
127
+ " OPENCLAW_GATEWAY_PORT / OPENCLAW_GATEWAY_HOST Optional explicit local gateway host/port override",
128
+ config_read_failed: "Cannot read config file: {path}",
129
+ config_invalid_json: "Invalid JSON in config file: {path}",
130
+ fail_prefix: CLI_COMMAND_NAME,
131
+ pair_usage_missing: `Pair code is required. Usage: ${CLI_COMMAND_NAME} pair <1234|123456|XXXX-XXXX>`,
132
+ relay_url_required: "A relay URL is required.",
133
+ relay_url_missing_detail: "Relay is enabled but no URL is available.",
134
+ relay_url_hint: `Use the built-in default (${DEFAULT_RELAY_URL}) or pass --relay / TESTNEXTIM_RELAY_URL.`,
135
+ pair_code_not_found: "Pair code not found: {code}",
136
+ pair_code_generate_in_app: "Generate a fresh pair code from the mobile app first.",
137
+ pair_code_consumed: "This pair code has already been consumed: {code}",
138
+ pair_missing_client_user_id: "This pair code is missing a client user ID.",
139
+ pair_regenerate_latest: "Generate the latest pair code in the app and try again.",
140
+ bound_host_exists: "This host is already bound to a device.",
141
+ current_client_user_id: "Current clientUserId: {value}",
142
+ active_pair_code: "Active pair code: {value}",
143
+ reset_first: `Run \`${CLI_COMMAND_NAME} reset\` first, then retry pairing.`,
144
+ binding_cleared: "Cleared binding: {value}",
145
+ restart_failed_after_config: "OpenClaw restart failed after updating config.",
146
+ restart_manual_retry: "Run `openclaw daemon restart` manually and try again.",
147
+ gateway_not_ready: "Local OpenClaw gateway `/v1/responses` is not ready.",
148
+ gateway_checked: "Checked: {url}",
149
+ gateway_http_expected: "HTTP {status} (expected non-401/403/404)",
150
+ gateway_request_failed: "{error}",
151
+ gateway_ready_hint:
152
+ "Make sure OpenClaw daemon is running and the responses endpoint is enabled.",
153
+ pair_success: "Pairing is ready. Return to the app to finish confirmation.",
154
+ pair_worker_failed: "Pairing initialization failed because the relay bridge worker could not start.",
155
+ setup_updated: "Updated config: {path}",
156
+ setup_ready: "Config already prepared: {path}",
157
+ setup_state_file: "State file: {path}",
158
+ setup_transport: "Chat transport mode: {mode}",
159
+ setup_restarted: "OpenClaw restarted.",
160
+ setup_restart_failed: "OpenClaw restart failed. Run this manually:",
161
+ no_active_binding: "No active binding found on relay.",
162
+ relay_reset_done: "Relay binding reset: done",
163
+ relay_reset_empty: "Relay binding reset: nothing to clear",
164
+ relay_reset_skipped: "Relay binding reset: skipped",
165
+ stopped_bridge_count: "Stopped relay bridge processes: {count}",
166
+ local_state_reset: "Local state reset: {path}",
167
+ reset_ready: `You can now run \`${CLI_COMMAND_NAME} pair <code>\` again.`,
168
+ status_config: "Config: {value}",
169
+ status_channel: "Channel: {value}",
170
+ status_enabled: "Enabled: {value}",
171
+ status_bind_base: "Bind Base: {value}",
172
+ status_relay_url: "Relay URL: {value}",
173
+ status_transport: "Transport Mode: {value}",
174
+ status_gateway_base: "Local Gateway Base: {value}",
175
+ status_webhook_path: "Webhook Path: {value}",
176
+ status_bind_path: "Bind Path: {value}",
177
+ status_gateway_id: "Relay Gateway ID: {value}",
178
+ status_client_token: "Relay Client Token: {value}",
179
+ status_gateway_token: "Relay Gateway Token: {value}",
180
+ status_app_secret: "App Secret: {value}",
181
+ status_access_token: "Access Token: {value}",
182
+ status_linked_user: "Linked User: {value}",
183
+ yes: "yes",
184
+ no: "no",
185
+ set: "set",
186
+ missing: "missing",
187
+ not_set: "(not set)",
188
+ not_linked: "(not linked)",
189
+ },
190
+ };
191
+
192
+ function detectCliLanguage() {
193
+ const candidates = [
194
+ process.env.LC_ALL,
195
+ process.env.LC_MESSAGES,
196
+ process.env.LANG,
197
+ process.env.LANGUAGE,
198
+ process.env.VSLANG,
199
+ Intl.DateTimeFormat().resolvedOptions().locale,
200
+ ];
201
+ for (const candidate of candidates) {
202
+ const normalized = String(candidate ?? "").trim().toLowerCase();
203
+ if (!normalized) {
204
+ continue;
205
+ }
206
+ if (normalized.startsWith("zh")) {
207
+ return "zh";
208
+ }
209
+ if (normalized.startsWith("en")) {
210
+ return "en";
211
+ }
212
+ }
213
+ return "en";
214
+ }
215
+
216
+ function t(key, args = {}) {
217
+ const table = CLI_I18N[CLI_LANGUAGE] || CLI_I18N.en;
218
+ const fallback = CLI_I18N.en[key] ?? key;
219
+ let message = table[key] ?? fallback;
220
+ for (const [name, value] of Object.entries(args)) {
221
+ message = message.replaceAll(`{${name}}`, String(value));
222
+ }
223
+ return message;
224
+ }
33
225
 
34
226
  function printHelp() {
35
227
  console.log(
36
228
  [
37
- `${CLI_COMMAND_NAME} CLI`,
229
+ t("help_title"),
38
230
  "",
39
- "Usage:",
231
+ t("help_usage"),
40
232
  ` ${CLI_COMMAND_NAME} setup [--relay <url>] [--public-base <url>] [--mode <relay>] [--account <id>] [--restart|--no-restart] [--json]`,
41
233
  ` ${CLI_COMMAND_NAME} pair <1234|123456|XXXX-XXXX> [--mode <relay>]`,
42
234
  ` ${CLI_COMMAND_NAME} reset [--relay <url>] [--gateway <url>] [--json]`,
@@ -44,25 +236,25 @@ function printHelp() {
44
236
  ` ${CLI_COMMAND_NAME} pair-url [<1234|123456|XXXX-XXXX>] [--account <id>] [--code <1234|123456|XXXX-XXXX>] [--json]`,
45
237
  ` ${CLI_COMMAND_NAME} status [--json]`,
46
238
  "",
47
- `Package: ${CLI_PACKAGE_NAME}`,
239
+ t("help_package"),
48
240
  "",
49
- "Environment:",
50
- " OPENCLAW_HOME Override OpenClaw home directory (default: ~/.openclaw)",
51
- " OPENIM_RELAY_URL / OPENIM_RELAY_TOKEN",
52
- ` TESTNEXTIM_RELAY_URL Override relay base URL (default: ${DEFAULT_RELAY_URL})`,
53
- " TESTNEXTIM_RELAY_TOKEN Bearer token used for pair publish/bind endpoints",
54
- " OPENCLAW_TESTNEXTIM_GATEWAY_BASE_URL Local gateway base (overrides openclaw.json gateway.port)",
55
- " OPENCLAW_GATEWAY_PORT / OPENCLAW_GATEWAY_HOST Optional explicit local gateway host/port override",
241
+ t("help_environment"),
242
+ t("help_env_home"),
243
+ t("help_env_openim"),
244
+ t("help_env_relay_url"),
245
+ t("help_env_relay_token"),
246
+ t("help_env_gateway_base"),
247
+ t("help_env_gateway_port"),
56
248
  "",
57
- "Tip:",
58
- " pair command will auto-start relay bridge worker in detached mode.",
59
- " Use --no-serve-relay if you do not want auto bridge worker.",
249
+ t("help_tip"),
250
+ t("help_tip_pair"),
251
+ t("help_tip_no_serve"),
60
252
  ].join("\n"),
61
253
  );
62
254
  }
63
255
 
64
256
  function fail(message, code = 1) {
65
- console.error(`${CLI_COMMAND_NAME}: ${message}`);
257
+ console.error(`${t("fail_prefix")}: ${message}`);
66
258
  process.exit(code);
67
259
  }
68
260
 
@@ -226,12 +418,12 @@ function readJsonFile(filePath) {
226
418
  try {
227
419
  content = fs.readFileSync(filePath, "utf8");
228
420
  } catch {
229
- fail(`cannot read config file: ${filePath}`);
421
+ fail(t("config_read_failed", { path: filePath }));
230
422
  }
231
423
  try {
232
424
  return JSON.parse(content);
233
425
  } catch {
234
- fail(`invalid JSON in config file: ${filePath}`);
426
+ fail(t("config_invalid_json", { path: filePath }));
235
427
  }
236
428
  }
237
429
 
@@ -481,7 +673,7 @@ function buildGatewayWsUrl(base) {
481
673
  } else {
482
674
  parsed.protocol = "ws:";
483
675
  }
484
- parsed.pathname = "/";
676
+ parsed.pathname = "/v1/ws";
485
677
  parsed.search = "";
486
678
  parsed.hash = "";
487
679
  return parsed.toString();
@@ -2392,7 +2584,7 @@ async function runPair({
2392
2584
  serveRelay,
2393
2585
  }) {
2394
2586
  if (!explicitCode || !String(explicitCode).trim()) {
2395
- fail(`pair code is required. Usage: ${CLI_COMMAND_NAME} pair <1234|123456|XXXX-XXXX>`);
2587
+ fail(t("pair_usage_missing"));
2396
2588
  }
2397
2589
  const code = normalizeProvidedCode(explicitCode);
2398
2590
  const configPath = resolveOpenClawConfigPath();
@@ -2402,9 +2594,9 @@ async function runPair({
2402
2594
  if (!relayUrl) {
2403
2595
  fail(
2404
2596
  [
2405
- "relay URL is required.",
2406
- "Relay is enabled but no URL is available.",
2407
- `Use the built-in default (${DEFAULT_RELAY_URL}) or pass --relay <url> / TESTNEXTIM_RELAY_URL.`,
2597
+ t("relay_url_required"),
2598
+ t("relay_url_missing_detail"),
2599
+ t("relay_url_hint"),
2408
2600
  ].join("\n"),
2409
2601
  );
2410
2602
  }
@@ -2413,13 +2605,13 @@ async function runPair({
2413
2605
  if (!currentSession) {
2414
2606
  fail(
2415
2607
  [
2416
- `pair code not found in relay: ${code}`,
2417
- "Please generate the code from mobile app first.",
2608
+ t("pair_code_not_found", { code }),
2609
+ t("pair_code_generate_in_app"),
2418
2610
  ].join("\n"),
2419
2611
  );
2420
2612
  }
2421
2613
  if (currentSession.consumed) {
2422
- fail(`pair code has already been consumed: ${code}`);
2614
+ fail(t("pair_code_consumed", { code }));
2423
2615
  }
2424
2616
 
2425
2617
  const accountId = normalizeAccountId(currentSession.accountId || account);
@@ -2431,8 +2623,8 @@ async function runPair({
2431
2623
  if (!clientUserId) {
2432
2624
  fail(
2433
2625
  [
2434
- `pair code ${code} is missing clientUserId in relay session.`,
2435
- "Please regenerate the pair code from the latest app and try again.",
2626
+ t("pair_missing_client_user_id"),
2627
+ t("pair_regenerate_latest"),
2436
2628
  ].join("\n"),
2437
2629
  );
2438
2630
  }
@@ -2445,9 +2637,6 @@ async function runPair({
2445
2637
  writeJsonFile(configPath, config);
2446
2638
  }
2447
2639
  section = readChannelSection(config);
2448
- if (!json && prepared.changed) {
2449
- console.log("Updated openclaw.json: removed stale testnextim plugin/channel config.");
2450
- }
2451
2640
 
2452
2641
  const bindPath = normalizePath(section.bindPath, DEFAULT_BIND_PATH);
2453
2642
 
@@ -2499,12 +2688,14 @@ async function runPair({
2499
2688
  if (existingBinding && existingCode !== code) {
2500
2689
  fail(
2501
2690
  [
2502
- "This host is already bound to a device.",
2691
+ t("bound_host_exists"),
2503
2692
  existingBinding.clientUserId
2504
- ? `Current clientUserId: ${existingBinding.clientUserId}`
2693
+ ? t("current_client_user_id", { value: existingBinding.clientUserId })
2694
+ : null,
2695
+ existingBinding.code
2696
+ ? t("active_pair_code", { value: existingBinding.code })
2505
2697
  : null,
2506
- existingBinding.code ? `Active pair code: ${existingBinding.code}` : null,
2507
- `Run \`${CLI_COMMAND_NAME} reset\` first to clear the current binding, then retry pairing.`,
2698
+ t("reset_first"),
2508
2699
  ]
2509
2700
  .filter(Boolean)
2510
2701
  .join("\n"),
@@ -2530,8 +2721,8 @@ async function runPair({
2530
2721
  if (!restartResult.ok) {
2531
2722
  fail(
2532
2723
  [
2533
- "OpenClaw restart failed after updating testnextim config.",
2534
- "Run `openclaw daemon restart` and retry.",
2724
+ t("restart_failed_after_config"),
2725
+ t("restart_manual_retry"),
2535
2726
  restartResult.stderr?.trim() || restartResult.stdout?.trim() || "",
2536
2727
  ]
2537
2728
  .filter(Boolean)
@@ -2551,8 +2742,8 @@ async function runPair({
2551
2742
  if (!restartResult.ok) {
2552
2743
  fail(
2553
2744
  [
2554
- "OpenClaw gateway is not ready and restart failed.",
2555
- "Run `openclaw daemon restart` and retry.",
2745
+ t("restart_failed_after_config"),
2746
+ t("restart_manual_retry"),
2556
2747
  restartResult.stderr?.trim() || restartResult.stdout?.trim() || "",
2557
2748
  ]
2558
2749
  .filter(Boolean)
@@ -2569,12 +2760,12 @@ async function runPair({
2569
2760
  if (!gatewayProbe.ok) {
2570
2761
  fail(
2571
2762
  [
2572
- "local OpenClaw gateway `/v1/responses` is not ready for relay bridge.",
2573
- `Checked: ${gatewayProbe.url}`,
2763
+ t("gateway_not_ready"),
2764
+ t("gateway_checked", { url: gatewayProbe.url }),
2574
2765
  gatewayProbe.status
2575
- ? `HTTP ${gatewayProbe.status} (expected non-401/403/404)`
2576
- : gatewayProbe.error || "request failed",
2577
- "Make sure OpenClaw daemon is running and the responses endpoint is enabled.",
2766
+ ? t("gateway_http_expected", { status: gatewayProbe.status })
2767
+ : t("gateway_request_failed", { error: gatewayProbe.error || "request failed" }),
2768
+ t("gateway_ready_hint"),
2578
2769
  ].join("\n"),
2579
2770
  );
2580
2771
  }
@@ -2635,31 +2826,15 @@ async function runPair({
2635
2826
  if (json) {
2636
2827
  console.log(JSON.stringify(output, null, 2));
2637
2828
  } else {
2638
- console.log(`Pair code: ${code}`);
2639
- console.log(`Transport Mode: ${transportMode}`);
2640
- if (publicBindUrl) console.log(`Public Bind URL: ${publicBindUrl}`);
2641
- console.log(`Local Bind URL: ${localBindUrl}`);
2642
- console.log(`Relay Bind URL: ${relayBindUrl}`);
2643
- console.log(`Published Gateway Base: ${publishedGatewayBaseUrl}`);
2644
- if (relayResult?.relayLookupUrl) {
2645
- console.log(`Relay Lookup: ${relayResult.relayLookupUrl}`);
2646
- }
2647
- if (relayCredentials?.gatewayId) {
2648
- console.log(`Relay Gateway ID: ${relayCredentials.gatewayId}`);
2649
- }
2650
- if (clientUserId) {
2651
- console.log(`Client User ID: ${clientUserId}`);
2652
- }
2653
- if (serveRelay) {
2654
- if (output.relayWorkerResult?.started) {
2655
- console.log("Relay bridge worker: started (detached)");
2656
- } else if (output.relayWorkerResult?.alreadyRunning) {
2657
- console.log(`Relay bridge worker: already running (${output.relayWorkerResult.mode})`);
2658
- } else {
2659
- console.log("Relay bridge worker: failed to start");
2660
- }
2829
+ if (
2830
+ serveRelay &&
2831
+ output.relayWorkerResult &&
2832
+ !output.relayWorkerResult.started &&
2833
+ !output.relayWorkerResult.alreadyRunning
2834
+ ) {
2835
+ fail(t("pair_worker_failed"));
2661
2836
  }
2662
- console.log(code);
2837
+ console.log(t("pair_success"));
2663
2838
  }
2664
2839
  }
2665
2840
 
@@ -2718,26 +2893,28 @@ async function runReset({ json, relay, noRelay, gateway }) {
2718
2893
  }
2719
2894
 
2720
2895
  if (activeBinding) {
2721
- console.log(
2722
- `Cleared binding: ${activeBinding.clientUserId || "(unknown client)"}${
2723
- activeBinding.code ? ` · code ${activeBinding.code}` : ""
2724
- }`,
2725
- );
2896
+ const bindingLabel = [
2897
+ activeBinding.clientUserId || "(unknown client)",
2898
+ activeBinding.code ? `code ${activeBinding.code}` : null,
2899
+ ]
2900
+ .filter(Boolean)
2901
+ .join(" · ");
2902
+ console.log(t("binding_cleared", { value: bindingLabel }));
2726
2903
  } else {
2727
- console.log("No active binding found on relay.");
2904
+ console.log(t("no_active_binding"));
2728
2905
  }
2729
2906
  if (relayResetResult?.ok) {
2730
- console.log("Relay binding reset: done");
2907
+ console.log(t("relay_reset_done"));
2731
2908
  } else if (relayResetResult?.notFound) {
2732
- console.log("Relay binding reset: nothing to clear");
2909
+ console.log(t("relay_reset_empty"));
2733
2910
  } else {
2734
- console.log("Relay binding reset: skipped");
2911
+ console.log(t("relay_reset_skipped"));
2735
2912
  }
2736
2913
  if (stoppedBridges.count > 0) {
2737
- console.log(`Stopped relay bridge processes: ${stoppedBridges.count}`);
2914
+ console.log(t("stopped_bridge_count", { count: stoppedBridges.count }));
2738
2915
  }
2739
- console.log(`Local state reset: ${nextState.statePath}`);
2740
- console.log(`You can now run \`${CLI_COMMAND_NAME} pair <code>\` again.`);
2916
+ console.log(t("local_state_reset", { path: nextState.statePath }));
2917
+ console.log(t("reset_ready"));
2741
2918
  }
2742
2919
 
2743
2920
  function runSetup({
@@ -2755,9 +2932,9 @@ function runSetup({
2755
2932
  if (!relayUrl) {
2756
2933
  fail(
2757
2934
  [
2758
- "setup requires relay URL.",
2759
- "Relay is enabled but no URL is available.",
2760
- `Use the built-in default (${DEFAULT_RELAY_URL}) or pass --relay <url> / TESTNEXTIM_RELAY_URL.`,
2935
+ t("relay_url_required"),
2936
+ t("relay_url_missing_detail"),
2937
+ t("relay_url_hint"),
2761
2938
  ].join("\n"),
2762
2939
  );
2763
2940
  }
@@ -2823,20 +3000,24 @@ function runSetup({
2823
3000
  }
2824
3001
 
2825
3002
  if (setupChanges.length > 0) {
2826
- console.log(`Updated config: ${configPath}`);
3003
+ console.log(t("setup_updated", { path: configPath }));
2827
3004
  for (const item of setupChanges) {
2828
3005
  console.log(`- ${item}`);
2829
3006
  }
2830
3007
  } else {
2831
- console.log(`Config already prepared: ${configPath}`);
3008
+ console.log(t("setup_ready", { path: configPath }));
2832
3009
  }
2833
- console.log(`State file: ${persistedSettings.statePath}`);
2834
- console.log(`Chat transport mode: ${normalizeChatTransportMode(section.chatTransportMode, "relay")}`);
3010
+ console.log(t("setup_state_file", { path: persistedSettings.statePath }));
3011
+ console.log(
3012
+ t("setup_transport", {
3013
+ mode: normalizeChatTransportMode(section.chatTransportMode, "relay"),
3014
+ }),
3015
+ );
2835
3016
  if (restart) {
2836
3017
  if (restartResult?.ok) {
2837
- console.log("OpenClaw restarted.");
3018
+ console.log(t("setup_restarted"));
2838
3019
  } else {
2839
- console.log("OpenClaw restart failed. You can run manually:");
3020
+ console.log(t("setup_restart_failed"));
2840
3021
  console.log(" openclaw daemon restart");
2841
3022
  if (restartResult?.stderr?.trim()) {
2842
3023
  console.log(restartResult.stderr.trim());
@@ -3043,21 +3224,37 @@ function runStatus({ json }) {
3043
3224
  return;
3044
3225
  }
3045
3226
 
3046
- console.log(`Config: ${status.configPath}`);
3047
- console.log(`Channel: ${status.channel}`);
3048
- console.log(`Enabled: ${status.enabled ? "yes" : "no"}`);
3049
- console.log(`Bind Base: ${status.bindPublicBaseUrl ?? "(not set)"}`);
3050
- console.log(`Relay URL: ${status.relayUrl ?? "(not set)"}`);
3051
- console.log(`Transport Mode: ${status.transportMode}`);
3052
- console.log(`Local Gateway Base: ${status.gatewayBaseUrl}`);
3053
- console.log(`Webhook Path: ${status.webhookPath}`);
3054
- console.log(`Bind Path: ${status.bindPath}`);
3055
- console.log(`Relay Gateway ID: ${status.relayGatewayId ?? "(missing)"}`);
3056
- console.log(`Relay Client Token: ${status.hasRelayClientToken ? "set" : "missing"}`);
3057
- console.log(`Relay Gateway Token: ${status.hasRelayGatewayToken ? "set" : "missing"}`);
3058
- console.log(`App Secret: ${status.hasAppSecret ? "set" : "missing"}`);
3059
- console.log(`Access Token: ${status.hasAccessToken ? "set" : "missing"}`);
3060
- console.log(`Linked User: ${status.linkedUserId ?? "(not linked)"}`);
3227
+ console.log(t("status_config", { value: status.configPath }));
3228
+ console.log(t("status_channel", { value: status.channel }));
3229
+ console.log(t("status_enabled", { value: status.enabled ? t("yes") : t("no") }));
3230
+ console.log(t("status_bind_base", { value: status.bindPublicBaseUrl ?? t("not_set") }));
3231
+ console.log(t("status_relay_url", { value: status.relayUrl ?? t("not_set") }));
3232
+ console.log(t("status_transport", { value: status.transportMode }));
3233
+ console.log(t("status_gateway_base", { value: status.gatewayBaseUrl }));
3234
+ console.log(t("status_webhook_path", { value: status.webhookPath }));
3235
+ console.log(t("status_bind_path", { value: status.bindPath }));
3236
+ console.log(t("status_gateway_id", { value: status.relayGatewayId ?? t("missing") }));
3237
+ console.log(
3238
+ t("status_client_token", {
3239
+ value: status.hasRelayClientToken ? t("set") : t("missing"),
3240
+ }),
3241
+ );
3242
+ console.log(
3243
+ t("status_gateway_token", {
3244
+ value: status.hasRelayGatewayToken ? t("set") : t("missing"),
3245
+ }),
3246
+ );
3247
+ console.log(
3248
+ t("status_app_secret", {
3249
+ value: status.hasAppSecret ? t("set") : t("missing"),
3250
+ }),
3251
+ );
3252
+ console.log(
3253
+ t("status_access_token", {
3254
+ value: status.hasAccessToken ? t("set") : t("missing"),
3255
+ }),
3256
+ );
3257
+ console.log(t("status_linked_user", { value: status.linkedUserId ?? t("not_linked") }));
3061
3258
  }
3062
3259
 
3063
3260
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matelink/cli",
3
- "version": "2026.4.7",
3
+ "version": "2026.4.9",
4
4
  "private": false,
5
5
  "description": "Relay-first CLI for pairing and bridging OpenClaw gateway traffic",
6
6
  "type": "module",