@lakphy/local-router 0.5.6 → 0.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -8708,6 +8708,8 @@ var init_errors = __esm(() => {
8708
8708
  APPLY_FAILED: 8,
8709
8709
  UPSTREAM_UNREACHABLE: 9,
8710
8710
  INTERACTIVE_REQUIRED: 10,
8711
+ TARGET_NOT_FOUND: 3,
8712
+ TARGET_UNREACHABLE: 9,
8711
8713
  UNKNOWN_ERROR: 1
8712
8714
  };
8713
8715
  CliError = class CliError extends Error {
@@ -8750,6 +8752,9 @@ function extractGlobalFlags(args) {
8750
8752
  let yes = false;
8751
8753
  let jsonAlias = false;
8752
8754
  let explain = false;
8755
+ let targetUrl;
8756
+ let targetHost;
8757
+ let targetPortRaw;
8753
8758
  for (let i = 0;i < args.length; i++) {
8754
8759
  const a = args[i];
8755
8760
  if (!a)
@@ -8796,6 +8801,21 @@ function extractGlobalFlags(args) {
8796
8801
  explain = true;
8797
8802
  continue;
8798
8803
  }
8804
+ if (a === "--url" || a.startsWith("--url=")) {
8805
+ targetUrl = a.startsWith("--url=") ? a.slice("--url=".length) : args[i + 1];
8806
+ rest.push(a);
8807
+ continue;
8808
+ }
8809
+ if (a === "--host" || a.startsWith("--host=")) {
8810
+ targetHost = a.startsWith("--host=") ? a.slice("--host=".length) : args[i + 1];
8811
+ rest.push(a);
8812
+ continue;
8813
+ }
8814
+ if (a === "--port" || a.startsWith("--port=")) {
8815
+ targetPortRaw = a.startsWith("--port=") ? a.slice("--port=".length) : args[i + 1];
8816
+ rest.push(a);
8817
+ continue;
8818
+ }
8799
8819
  rest.push(a);
8800
8820
  }
8801
8821
  const envFormat = pickOutput(process.env.LOCAL_ROUTER_FORMAT);
@@ -8813,6 +8833,16 @@ function extractGlobalFlags(args) {
8813
8833
  } else {
8814
8834
  output = envFormat ?? "markdown";
8815
8835
  }
8836
+ let targetPort;
8837
+ if (targetPortRaw !== undefined) {
8838
+ targetPort = Number.parseInt(targetPortRaw, 10);
8839
+ if (!Number.isFinite(targetPort) || targetPort <= 0 || targetPort > 65535) {
8840
+ throw new CliError("USAGE_ERROR", `\u65E0\u6548\u7AEF\u53E3: ${targetPortRaw}`, {
8841
+ hint: "\u7AEF\u53E3\u8303\u56F4 1-65535"
8842
+ });
8843
+ }
8844
+ }
8845
+ const target = targetUrl !== undefined || targetHost !== undefined || targetPort !== undefined ? { url: targetUrl, host: targetHost, port: targetPort } : undefined;
8816
8846
  return {
8817
8847
  flags: {
8818
8848
  output,
@@ -8821,7 +8851,8 @@ function extractGlobalFlags(args) {
8821
8851
  noColor: noColor || !!process.env.NO_COLOR,
8822
8852
  noInteractive: noInteractive || process.env.LOCAL_ROUTER_NO_INTERACTIVE === "1",
8823
8853
  yes,
8824
- explain: explain || process.env.LOCAL_ROUTER_EXPLAIN === "1"
8854
+ explain: explain || process.env.LOCAL_ROUTER_EXPLAIN === "1",
8855
+ target
8825
8856
  },
8826
8857
  rest
8827
8858
  };
@@ -9179,257 +9210,6 @@ var init_output = __esm(() => {
9179
9210
  init_global_flags();
9180
9211
  });
9181
9212
 
9182
- // src/cli/runtime.ts
9183
- import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync3 } from "fs";
9184
- import { homedir as homedir2 } from "os";
9185
- import { join as join3, resolve as resolve2 } from "path";
9186
- function getRuntimeDirs() {
9187
- const override = process.env.LOCAL_ROUTER_RUNTIME_DIR;
9188
- const root = override?.trim() ? override.trim() : join3(homedir2(), ".local-router");
9189
- return {
9190
- root,
9191
- run: join3(root, "run"),
9192
- logs: join3(root, "logs")
9193
- };
9194
- }
9195
- function getRuntimeFiles() {
9196
- const dirs = getRuntimeDirs();
9197
- return {
9198
- pid: join3(dirs.run, "local-router.pid"),
9199
- state: join3(dirs.run, "status.json"),
9200
- daemonLog: join3(dirs.logs, "daemon.log")
9201
- };
9202
- }
9203
- function ensureRuntimeDirs() {
9204
- const dirs = getRuntimeDirs();
9205
- mkdirSync3(dirs.root, { recursive: true });
9206
- mkdirSync3(dirs.run, { recursive: true });
9207
- mkdirSync3(dirs.logs, { recursive: true });
9208
- }
9209
- function writeRuntimeState(state) {
9210
- ensureRuntimeDirs();
9211
- const files = getRuntimeFiles();
9212
- writeFileSync3(files.pid, `${state.pid}
9213
- `, "utf-8");
9214
- writeFileSync3(files.state, JSON.stringify(state, null, 2), "utf-8");
9215
- }
9216
- function readRuntimeState() {
9217
- const files = getRuntimeFiles();
9218
- if (!existsSync2(files.state)) {
9219
- return null;
9220
- }
9221
- try {
9222
- return JSON.parse(readFileSync4(files.state, "utf-8"));
9223
- } catch {
9224
- return null;
9225
- }
9226
- }
9227
- function clearRuntimeFiles() {
9228
- const files = getRuntimeFiles();
9229
- rmSync(files.pid, { force: true });
9230
- rmSync(files.state, { force: true });
9231
- }
9232
- function resolveConfigArgPath(pathValue) {
9233
- return resolve2(pathValue);
9234
- }
9235
- var init_runtime = () => {};
9236
-
9237
- // src/cli/autostart.ts
9238
- import { execSync } from "child_process";
9239
- import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
9240
- import { homedir as homedir3, platform } from "os";
9241
- import { dirname as dirname2, join as join4 } from "path";
9242
- function getDaemonLogPath() {
9243
- return getRuntimeDirs().logs + "/daemon.log";
9244
- }
9245
- function getLaunchAgentPath() {
9246
- return join4(homedir3(), "Library", "LaunchAgents", `${LABEL}.plist`);
9247
- }
9248
- function buildPlist(opts) {
9249
- const logPath = getDaemonLogPath();
9250
- const args = [opts.execPath, ...opts.args].map((a) => ` <string>${escapeXml(a)}</string>`).join(`
9251
- `);
9252
- return `<?xml version="1.0" encoding="UTF-8"?>
9253
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
9254
- <plist version="1.0">
9255
- <dict>
9256
- <key>Label</key>
9257
- <string>${escapeXml(opts.label)}</string>
9258
- <key>ProgramArguments</key>
9259
- <array>
9260
- ${args}
9261
- </array>
9262
- <key>RunAtLoad</key>
9263
- <true/>
9264
- <key>KeepAlive</key>
9265
- <false/>
9266
- <key>StandardOutPath</key>
9267
- <string>${escapeXml(logPath)}</string>
9268
- <key>StandardErrorPath</key>
9269
- <string>${escapeXml(logPath)}</string>
9270
- </dict>
9271
- </plist>
9272
- `;
9273
- }
9274
- function escapeXml(s) {
9275
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
9276
- }
9277
- function createMacosManager() {
9278
- const plistPath = getLaunchAgentPath();
9279
- return {
9280
- platform: "macos",
9281
- async isInstalled() {
9282
- return existsSync3(plistPath);
9283
- },
9284
- async install(opts) {
9285
- const dir = dirname2(plistPath);
9286
- if (!existsSync3(dir))
9287
- mkdirSync4(dir, { recursive: true });
9288
- writeFileSync4(plistPath, buildPlist(opts), "utf-8");
9289
- try {
9290
- execSync(`launchctl bootout gui/$(id -u) ${plistPath} 2>/dev/null`, { stdio: "ignore" });
9291
- } catch {}
9292
- execSync(`launchctl bootstrap gui/$(id -u) ${plistPath}`, { stdio: "ignore" });
9293
- },
9294
- async uninstall() {
9295
- if (!existsSync3(plistPath))
9296
- return;
9297
- try {
9298
- execSync(`launchctl bootout gui/$(id -u) ${plistPath}`, { stdio: "ignore" });
9299
- } catch {}
9300
- rmSync2(plistPath, { force: true });
9301
- },
9302
- getServicePath() {
9303
- return plistPath;
9304
- }
9305
- };
9306
- }
9307
- function getSystemdUnitPath() {
9308
- return join4(homedir3(), ".config", "systemd", "user", "local-router.service");
9309
- }
9310
- function buildUnit(opts) {
9311
- const logPath = getDaemonLogPath();
9312
- const execStart = [opts.execPath, ...opts.args].join(" ");
9313
- return `[Unit]
9314
- Description=Local Router API Gateway
9315
- After=network-online.target
9316
-
9317
- [Service]
9318
- Type=simple
9319
- ExecStart=${execStart}
9320
- Restart=on-failure
9321
- RestartSec=5
9322
- StandardOutput=append:${logPath}
9323
- StandardError=append:${logPath}
9324
-
9325
- [Install]
9326
- WantedBy=default.target
9327
- `;
9328
- }
9329
- function createLinuxManager() {
9330
- const unitPath = getSystemdUnitPath();
9331
- return {
9332
- platform: "linux",
9333
- async isInstalled() {
9334
- if (!existsSync3(unitPath))
9335
- return false;
9336
- try {
9337
- const out = execSync("systemctl --user is-enabled local-router 2>/dev/null", {
9338
- encoding: "utf-8"
9339
- }).trim();
9340
- return out === "enabled";
9341
- } catch {
9342
- return false;
9343
- }
9344
- },
9345
- async install(opts) {
9346
- const dir = dirname2(unitPath);
9347
- if (!existsSync3(dir))
9348
- mkdirSync4(dir, { recursive: true });
9349
- writeFileSync4(unitPath, buildUnit(opts), "utf-8");
9350
- execSync("systemctl --user daemon-reload", { stdio: "ignore" });
9351
- execSync("systemctl --user enable local-router", { stdio: "ignore" });
9352
- },
9353
- async uninstall() {
9354
- try {
9355
- execSync("systemctl --user disable local-router", { stdio: "ignore" });
9356
- } catch {}
9357
- rmSync2(unitPath, { force: true });
9358
- try {
9359
- execSync("systemctl --user daemon-reload", { stdio: "ignore" });
9360
- } catch {}
9361
- },
9362
- getServicePath() {
9363
- return unitPath;
9364
- }
9365
- };
9366
- }
9367
- function createWindowsManager() {
9368
- return {
9369
- platform: "windows",
9370
- async isInstalled() {
9371
- try {
9372
- execSync(`reg query "${WIN_REG_KEY}" /v ${WIN_REG_VALUE}`, { stdio: "ignore" });
9373
- return true;
9374
- } catch {
9375
- return false;
9376
- }
9377
- },
9378
- async install(opts) {
9379
- const cmd = [opts.execPath, ...opts.args].map((a) => `"${a}"`).join(" ");
9380
- execSync(`reg add "${WIN_REG_KEY}" /v ${WIN_REG_VALUE} /t REG_SZ /d "${cmd}" /f`, {
9381
- stdio: "ignore"
9382
- });
9383
- },
9384
- async uninstall() {
9385
- try {
9386
- execSync(`reg delete "${WIN_REG_KEY}" /v ${WIN_REG_VALUE} /f`, { stdio: "ignore" });
9387
- } catch {}
9388
- },
9389
- getServicePath() {
9390
- return `${WIN_REG_KEY}\\${WIN_REG_VALUE}`;
9391
- }
9392
- };
9393
- }
9394
- function createUnsupportedManager() {
9395
- return {
9396
- platform: "unsupported",
9397
- async isInstalled() {
9398
- return false;
9399
- },
9400
- async install() {
9401
- throw new Error("\u5F53\u524D\u5E73\u53F0\u4E0D\u652F\u6301\u81EA\u542F\u52A8");
9402
- },
9403
- async uninstall() {
9404
- throw new Error("\u5F53\u524D\u5E73\u53F0\u4E0D\u652F\u6301\u81EA\u542F\u52A8");
9405
- },
9406
- getServicePath() {
9407
- return "";
9408
- }
9409
- };
9410
- }
9411
- function createAutostartManager() {
9412
- const p = platform();
9413
- if (p === "darwin")
9414
- return createMacosManager();
9415
- if (p === "linux")
9416
- return createLinuxManager();
9417
- if (p === "win32")
9418
- return createWindowsManager();
9419
- return createUnsupportedManager();
9420
- }
9421
- function getAutostartExecArgs() {
9422
- const script = process.argv[1] ?? "dist/cli.js";
9423
- return {
9424
- execPath: process.execPath,
9425
- args: [script, "__run-server", "--mode", "daemon"]
9426
- };
9427
- }
9428
- var LABEL = "com.lakphy.local-router", WIN_REG_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", WIN_REG_VALUE = "LocalRouter";
9429
- var init_autostart = __esm(() => {
9430
- init_runtime();
9431
- });
9432
-
9433
9213
  // node_modules/.bun/@ai-sdk+provider@3.0.8/node_modules/@ai-sdk/provider/dist/index.mjs
9434
9214
  function getErrorMessage(error) {
9435
9215
  if (error == null) {
@@ -27547,14 +27327,14 @@ async function delay(delayInMs, options) {
27547
27327
  return Promise.resolve();
27548
27328
  }
27549
27329
  const signal = options == null ? undefined : options.abortSignal;
27550
- return new Promise((resolve22, reject) => {
27330
+ return new Promise((resolve2, reject) => {
27551
27331
  if (signal == null ? undefined : signal.aborted) {
27552
27332
  reject(createAbortError());
27553
27333
  return;
27554
27334
  }
27555
27335
  const timeoutId = setTimeout(() => {
27556
27336
  cleanup();
27557
- resolve22();
27337
+ resolve2();
27558
27338
  }, delayInMs);
27559
27339
  const cleanup = () => {
27560
27340
  clearTimeout(timeoutId);
@@ -29012,7 +28792,7 @@ function createProviderToolFactoryWithOutputSchema({
29012
28792
  supportsDeferredResults
29013
28793
  });
29014
28794
  }
29015
- async function resolve3(value) {
28795
+ async function resolve2(value) {
29016
28796
  if (typeof value === "function") {
29017
28797
  value = value();
29018
28798
  }
@@ -29051,13 +28831,13 @@ var DelayedPromise = class {
29051
28831
  if (this._promise) {
29052
28832
  return this._promise;
29053
28833
  }
29054
- this._promise = new Promise((resolve22, reject) => {
28834
+ this._promise = new Promise((resolve2, reject) => {
29055
28835
  if (this.status.type === "resolved") {
29056
- resolve22(this.status.value);
28836
+ resolve2(this.status.value);
29057
28837
  } else if (this.status.type === "rejected") {
29058
28838
  reject(this.status.error);
29059
28839
  }
29060
- this._resolve = resolve22;
28840
+ this._resolve = resolve2;
29061
28841
  this._reject = reject;
29062
28842
  });
29063
28843
  return this._promise;
@@ -31515,11 +31295,11 @@ var VERSION2 = "3.0.58", anthropicErrorDataSchema, anthropicFailedResponseHandle
31515
31295
  betas,
31516
31296
  headers
31517
31297
  }) {
31518
- return combineHeaders(await resolve3(this.config.headers), headers, betas.size > 0 ? { "anthropic-beta": Array.from(betas).join(",") } : {});
31298
+ return combineHeaders(await resolve2(this.config.headers), headers, betas.size > 0 ? { "anthropic-beta": Array.from(betas).join(",") } : {});
31519
31299
  }
31520
31300
  async getBetasFromHeaders(requestHeaders) {
31521
31301
  var _a16, _b16;
31522
- const configHeaders = await resolve3(this.config.headers);
31302
+ const configHeaders = await resolve2(this.config.headers);
31523
31303
  const configBetaHeader = (_a16 = configHeaders["anthropic-beta"]) != null ? _a16 : "";
31524
31304
  const requestBetaHeader = (_b16 = requestHeaders == null ? undefined : requestHeaders["anthropic-beta"]) != null ? _b16 : "";
31525
31305
  return new Set([
@@ -42350,7 +42130,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
42350
42130
  try {
42351
42131
  const { value } = await getFromApi({
42352
42132
  url: `${this.config.baseURL}/config`,
42353
- headers: await resolve3(this.config.headers()),
42133
+ headers: await resolve2(this.config.headers()),
42354
42134
  successfulResponseHandler: createJsonResponseHandler(gatewayAvailableModelsResponseSchema),
42355
42135
  failedResponseHandler: createJsonErrorResponseHandler({
42356
42136
  errorSchema: exports_external.any(),
@@ -42368,7 +42148,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
42368
42148
  const baseUrl = new URL(this.config.baseURL);
42369
42149
  const { value } = await getFromApi({
42370
42150
  url: `${baseUrl.origin}/v1/credits`,
42371
- headers: await resolve3(this.config.headers()),
42151
+ headers: await resolve2(this.config.headers()),
42372
42152
  successfulResponseHandler: createJsonResponseHandler(gatewayCreditsResponseSchema),
42373
42153
  failedResponseHandler: createJsonErrorResponseHandler({
42374
42154
  errorSchema: exports_external.any(),
@@ -42401,7 +42181,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
42401
42181
  async doGenerate(options) {
42402
42182
  const { args, warnings } = await this.getArgs(options);
42403
42183
  const { abortSignal } = options;
42404
- const resolvedHeaders = await resolve3(this.config.headers());
42184
+ const resolvedHeaders = await resolve2(this.config.headers());
42405
42185
  try {
42406
42186
  const {
42407
42187
  responseHeaders,
@@ -42409,7 +42189,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
42409
42189
  rawValue: rawResponse
42410
42190
  } = await postJsonToApi({
42411
42191
  url: this.getUrl(),
42412
- headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, false), await resolve3(this.config.o11yHeaders)),
42192
+ headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, false), await resolve2(this.config.o11yHeaders)),
42413
42193
  body: args,
42414
42194
  successfulResponseHandler: createJsonResponseHandler(exports_external.any()),
42415
42195
  failedResponseHandler: createJsonErrorResponseHandler({
@@ -42432,11 +42212,11 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
42432
42212
  async doStream(options) {
42433
42213
  const { args, warnings } = await this.getArgs(options);
42434
42214
  const { abortSignal } = options;
42435
- const resolvedHeaders = await resolve3(this.config.headers());
42215
+ const resolvedHeaders = await resolve2(this.config.headers());
42436
42216
  try {
42437
42217
  const { value: response, responseHeaders } = await postJsonToApi({
42438
42218
  url: this.getUrl(),
42439
- headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, true), await resolve3(this.config.o11yHeaders)),
42219
+ headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, true), await resolve2(this.config.o11yHeaders)),
42440
42220
  body: args,
42441
42221
  successfulResponseHandler: createEventSourceResponseHandler(exports_external.any()),
42442
42222
  failedResponseHandler: createJsonErrorResponseHandler({
@@ -42521,7 +42301,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
42521
42301
  providerOptions
42522
42302
  }) {
42523
42303
  var _a92;
42524
- const resolvedHeaders = await resolve3(this.config.headers());
42304
+ const resolvedHeaders = await resolve2(this.config.headers());
42525
42305
  try {
42526
42306
  const {
42527
42307
  responseHeaders,
@@ -42529,7 +42309,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
42529
42309
  rawValue
42530
42310
  } = await postJsonToApi({
42531
42311
  url: this.getUrl(),
42532
- headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve3(this.config.o11yHeaders)),
42312
+ headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve2(this.config.o11yHeaders)),
42533
42313
  body: {
42534
42314
  values,
42535
42315
  ...providerOptions ? { providerOptions } : {}
@@ -42585,7 +42365,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
42585
42365
  abortSignal
42586
42366
  }) {
42587
42367
  var _a92, _b92, _c, _d;
42588
- const resolvedHeaders = await resolve3(this.config.headers());
42368
+ const resolvedHeaders = await resolve2(this.config.headers());
42589
42369
  try {
42590
42370
  const {
42591
42371
  responseHeaders,
@@ -42593,7 +42373,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
42593
42373
  rawValue
42594
42374
  } = await postJsonToApi({
42595
42375
  url: this.getUrl(),
42596
- headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve3(this.config.o11yHeaders)),
42376
+ headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve2(this.config.o11yHeaders)),
42597
42377
  body: {
42598
42378
  prompt,
42599
42379
  n,
@@ -42668,11 +42448,11 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
42668
42448
  abortSignal
42669
42449
  }) {
42670
42450
  var _a92;
42671
- const resolvedHeaders = await resolve3(this.config.headers());
42451
+ const resolvedHeaders = await resolve2(this.config.headers());
42672
42452
  try {
42673
42453
  const { responseHeaders, value: responseBody } = await postJsonToApi({
42674
42454
  url: this.getUrl(),
42675
- headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve3(this.config.o11yHeaders), { accept: "text/event-stream" }),
42455
+ headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve2(this.config.o11yHeaders), { accept: "text/event-stream" }),
42676
42456
  body: {
42677
42457
  prompt,
42678
42458
  n,
@@ -46662,8 +46442,8 @@ function writeToServerResponse({
46662
46442
  break;
46663
46443
  const canContinue = response.write(value);
46664
46444
  if (!canContinue) {
46665
- await new Promise((resolve32) => {
46666
- response.once("drain", resolve32);
46445
+ await new Promise((resolve3) => {
46446
+ response.once("drain", resolve3);
46667
46447
  });
46668
46448
  }
46669
46449
  }
@@ -47424,15 +47204,15 @@ async function consumeStream({
47424
47204
  }
47425
47205
  }
47426
47206
  function createResolvablePromise() {
47427
- let resolve32;
47207
+ let resolve3;
47428
47208
  let reject;
47429
47209
  const promise2 = new Promise((res, rej) => {
47430
- resolve32 = res;
47210
+ resolve3 = res;
47431
47211
  reject = rej;
47432
47212
  });
47433
47213
  return {
47434
47214
  promise: promise2,
47435
- resolve: resolve32,
47215
+ resolve: resolve3,
47436
47216
  reject
47437
47217
  };
47438
47218
  }
@@ -48014,7 +47794,7 @@ var import_api, import_api2, __defProp2, __export2 = (target, all) => {
48014
47794
  const schema = asSchema(inputSchema);
48015
47795
  return {
48016
47796
  name: "object",
48017
- responseFormat: resolve3(schema.jsonSchema).then((jsonSchema2) => ({
47797
+ responseFormat: resolve2(schema.jsonSchema).then((jsonSchema2) => ({
48018
47798
  type: "json",
48019
47799
  schema: jsonSchema2,
48020
47800
  ...name21 != null && { name: name21 },
@@ -48075,7 +47855,7 @@ var import_api, import_api2, __defProp2, __export2 = (target, all) => {
48075
47855
  const elementSchema = asSchema(inputElementSchema);
48076
47856
  return {
48077
47857
  name: "array",
48078
- responseFormat: resolve3(elementSchema.jsonSchema).then((jsonSchema2) => {
47858
+ responseFormat: resolve2(elementSchema.jsonSchema).then((jsonSchema2) => {
48079
47859
  const { $schema, ...itemSchema } = jsonSchema2;
48080
47860
  return {
48081
47861
  type: "json",
@@ -52573,7 +52353,7 @@ var init_path = () => {};
52573
52353
  var ENCODINGS, ENCODINGS_ORDERED_KEYS, DEFAULT_DOCUMENT = "index.html", serveStatic = (options) => {
52574
52354
  const root = options.root ?? "./";
52575
52355
  const optionPath = options.path;
52576
- const join5 = options.join ?? defaultJoin;
52356
+ const join3 = options.join ?? defaultJoin;
52577
52357
  return async (c, next) => {
52578
52358
  if (c.finalized) {
52579
52359
  return next();
@@ -52592,9 +52372,9 @@ var ENCODINGS, ENCODINGS_ORDERED_KEYS, DEFAULT_DOCUMENT = "index.html", serveSta
52592
52372
  return next();
52593
52373
  }
52594
52374
  }
52595
- let path = join5(root, !optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename) : filename);
52375
+ let path = join3(root, !optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename) : filename);
52596
52376
  if (options.isDir && await options.isDir(path)) {
52597
- path = join5(path, DEFAULT_DOCUMENT);
52377
+ path = join3(path, DEFAULT_DOCUMENT);
52598
52378
  }
52599
52379
  const getContent = options.getContent;
52600
52380
  let content = await getContent(path, c);
@@ -52642,7 +52422,7 @@ var init_serve_static = __esm(() => {
52642
52422
 
52643
52423
  // node_modules/.bun/hono@4.12.5/node_modules/hono/dist/adapter/bun/serve-static.js
52644
52424
  import { stat } from "fs/promises";
52645
- import { join as join5 } from "path";
52425
+ import { join as join3 } from "path";
52646
52426
  var serveStatic2 = (options) => {
52647
52427
  return async function serveStatic22(c, next) {
52648
52428
  const getContent = async (path) => {
@@ -52660,7 +52440,7 @@ var serveStatic2 = (options) => {
52660
52440
  return serveStatic({
52661
52441
  ...options,
52662
52442
  getContent,
52663
- join: join5,
52443
+ join: join3,
52664
52444
  isDir
52665
52445
  })(c, next);
52666
52446
  };
@@ -52826,6 +52606,286 @@ var init_bun = __esm(() => {
52826
52606
  init_server();
52827
52607
  });
52828
52608
 
52609
+ // src/cli/asset-paths.ts
52610
+ async function findExisting(...candidates) {
52611
+ for (const url2 of candidates) {
52612
+ try {
52613
+ const exists = await Bun.file(url2).exists();
52614
+ if (exists)
52615
+ return url2;
52616
+ } catch {}
52617
+ }
52618
+ return null;
52619
+ }
52620
+ async function readPackageJson() {
52621
+ const url2 = await findExisting(new URL("../../package.json", import.meta.url), new URL("../package.json", import.meta.url), new URL("../../../package.json", import.meta.url));
52622
+ if (!url2)
52623
+ return null;
52624
+ try {
52625
+ return await Bun.file(url2).json();
52626
+ } catch {
52627
+ return null;
52628
+ }
52629
+ }
52630
+ async function findConfigSchemaUrl() {
52631
+ return findExisting(new URL("../../config.schema.json", import.meta.url), new URL("../config.schema.json", import.meta.url), new URL("../../../config.schema.json", import.meta.url));
52632
+ }
52633
+ async function readVersionString() {
52634
+ const pkg = await readPackageJson();
52635
+ return typeof pkg?.version === "string" ? pkg.version : "unknown";
52636
+ }
52637
+
52638
+ // src/cli/runtime.ts
52639
+ import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync3 } from "fs";
52640
+ import { homedir as homedir2 } from "os";
52641
+ import { join as join4, resolve as resolve3 } from "path";
52642
+ function getRuntimeDirs() {
52643
+ const override = process.env.LOCAL_ROUTER_RUNTIME_DIR;
52644
+ const root = override?.trim() ? override.trim() : join4(homedir2(), ".local-router");
52645
+ return {
52646
+ root,
52647
+ run: join4(root, "run"),
52648
+ logs: join4(root, "logs")
52649
+ };
52650
+ }
52651
+ function getRuntimeFiles() {
52652
+ const dirs = getRuntimeDirs();
52653
+ return {
52654
+ pid: join4(dirs.run, "local-router.pid"),
52655
+ state: join4(dirs.run, "status.json"),
52656
+ daemonLog: join4(dirs.logs, "daemon.log")
52657
+ };
52658
+ }
52659
+ function ensureRuntimeDirs() {
52660
+ const dirs = getRuntimeDirs();
52661
+ mkdirSync3(dirs.root, { recursive: true });
52662
+ mkdirSync3(dirs.run, { recursive: true });
52663
+ mkdirSync3(dirs.logs, { recursive: true });
52664
+ }
52665
+ function writeRuntimeState(state) {
52666
+ ensureRuntimeDirs();
52667
+ const files = getRuntimeFiles();
52668
+ writeFileSync3(files.pid, `${state.pid}
52669
+ `, "utf-8");
52670
+ writeFileSync3(files.state, JSON.stringify(state, null, 2), "utf-8");
52671
+ }
52672
+ function readRuntimeState() {
52673
+ const files = getRuntimeFiles();
52674
+ if (!existsSync2(files.state)) {
52675
+ return null;
52676
+ }
52677
+ try {
52678
+ return JSON.parse(readFileSync4(files.state, "utf-8"));
52679
+ } catch {
52680
+ return null;
52681
+ }
52682
+ }
52683
+ function clearRuntimeFiles() {
52684
+ const files = getRuntimeFiles();
52685
+ rmSync(files.pid, { force: true });
52686
+ rmSync(files.state, { force: true });
52687
+ }
52688
+ function resolveConfigArgPath(pathValue) {
52689
+ return resolve3(pathValue);
52690
+ }
52691
+ var init_runtime = () => {};
52692
+
52693
+ // src/cli/autostart.ts
52694
+ import { execSync } from "child_process";
52695
+ import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
52696
+ import { homedir as homedir3, platform } from "os";
52697
+ import { dirname as dirname3, join as join5 } from "path";
52698
+ function getDaemonLogPath() {
52699
+ return getRuntimeDirs().logs + "/daemon.log";
52700
+ }
52701
+ function getLaunchAgentPath() {
52702
+ return join5(homedir3(), "Library", "LaunchAgents", `${LABEL}.plist`);
52703
+ }
52704
+ function buildPlist(opts) {
52705
+ const logPath = getDaemonLogPath();
52706
+ const args = [opts.execPath, ...opts.args].map((a) => ` <string>${escapeXml(a)}</string>`).join(`
52707
+ `);
52708
+ return `<?xml version="1.0" encoding="UTF-8"?>
52709
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
52710
+ <plist version="1.0">
52711
+ <dict>
52712
+ <key>Label</key>
52713
+ <string>${escapeXml(opts.label)}</string>
52714
+ <key>ProgramArguments</key>
52715
+ <array>
52716
+ ${args}
52717
+ </array>
52718
+ <key>RunAtLoad</key>
52719
+ <true/>
52720
+ <key>KeepAlive</key>
52721
+ <false/>
52722
+ <key>StandardOutPath</key>
52723
+ <string>${escapeXml(logPath)}</string>
52724
+ <key>StandardErrorPath</key>
52725
+ <string>${escapeXml(logPath)}</string>
52726
+ </dict>
52727
+ </plist>
52728
+ `;
52729
+ }
52730
+ function escapeXml(s) {
52731
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
52732
+ }
52733
+ function createMacosManager() {
52734
+ const plistPath = getLaunchAgentPath();
52735
+ return {
52736
+ platform: "macos",
52737
+ async isInstalled() {
52738
+ return existsSync3(plistPath);
52739
+ },
52740
+ async install(opts) {
52741
+ const dir = dirname3(plistPath);
52742
+ if (!existsSync3(dir))
52743
+ mkdirSync4(dir, { recursive: true });
52744
+ writeFileSync4(plistPath, buildPlist(opts), "utf-8");
52745
+ try {
52746
+ execSync(`launchctl bootout gui/$(id -u) ${plistPath} 2>/dev/null`, { stdio: "ignore" });
52747
+ } catch {}
52748
+ execSync(`launchctl bootstrap gui/$(id -u) ${plistPath}`, { stdio: "ignore" });
52749
+ },
52750
+ async uninstall() {
52751
+ if (!existsSync3(plistPath))
52752
+ return;
52753
+ try {
52754
+ execSync(`launchctl bootout gui/$(id -u) ${plistPath}`, { stdio: "ignore" });
52755
+ } catch {}
52756
+ rmSync2(plistPath, { force: true });
52757
+ },
52758
+ getServicePath() {
52759
+ return plistPath;
52760
+ }
52761
+ };
52762
+ }
52763
+ function getSystemdUnitPath() {
52764
+ return join5(homedir3(), ".config", "systemd", "user", "local-router.service");
52765
+ }
52766
+ function buildUnit(opts) {
52767
+ const logPath = getDaemonLogPath();
52768
+ const execStart = [opts.execPath, ...opts.args].join(" ");
52769
+ return `[Unit]
52770
+ Description=Local Router API Gateway
52771
+ After=network-online.target
52772
+
52773
+ [Service]
52774
+ Type=simple
52775
+ ExecStart=${execStart}
52776
+ Restart=on-failure
52777
+ RestartSec=5
52778
+ StandardOutput=append:${logPath}
52779
+ StandardError=append:${logPath}
52780
+
52781
+ [Install]
52782
+ WantedBy=default.target
52783
+ `;
52784
+ }
52785
+ function createLinuxManager() {
52786
+ const unitPath = getSystemdUnitPath();
52787
+ return {
52788
+ platform: "linux",
52789
+ async isInstalled() {
52790
+ if (!existsSync3(unitPath))
52791
+ return false;
52792
+ try {
52793
+ const out = execSync("systemctl --user is-enabled local-router 2>/dev/null", {
52794
+ encoding: "utf-8"
52795
+ }).trim();
52796
+ return out === "enabled";
52797
+ } catch {
52798
+ return false;
52799
+ }
52800
+ },
52801
+ async install(opts) {
52802
+ const dir = dirname3(unitPath);
52803
+ if (!existsSync3(dir))
52804
+ mkdirSync4(dir, { recursive: true });
52805
+ writeFileSync4(unitPath, buildUnit(opts), "utf-8");
52806
+ execSync("systemctl --user daemon-reload", { stdio: "ignore" });
52807
+ execSync("systemctl --user enable local-router", { stdio: "ignore" });
52808
+ },
52809
+ async uninstall() {
52810
+ try {
52811
+ execSync("systemctl --user disable local-router", { stdio: "ignore" });
52812
+ } catch {}
52813
+ rmSync2(unitPath, { force: true });
52814
+ try {
52815
+ execSync("systemctl --user daemon-reload", { stdio: "ignore" });
52816
+ } catch {}
52817
+ },
52818
+ getServicePath() {
52819
+ return unitPath;
52820
+ }
52821
+ };
52822
+ }
52823
+ function createWindowsManager() {
52824
+ return {
52825
+ platform: "windows",
52826
+ async isInstalled() {
52827
+ try {
52828
+ execSync(`reg query "${WIN_REG_KEY}" /v ${WIN_REG_VALUE}`, { stdio: "ignore" });
52829
+ return true;
52830
+ } catch {
52831
+ return false;
52832
+ }
52833
+ },
52834
+ async install(opts) {
52835
+ const cmd = [opts.execPath, ...opts.args].map((a) => `"${a}"`).join(" ");
52836
+ execSync(`reg add "${WIN_REG_KEY}" /v ${WIN_REG_VALUE} /t REG_SZ /d "${cmd}" /f`, {
52837
+ stdio: "ignore"
52838
+ });
52839
+ },
52840
+ async uninstall() {
52841
+ try {
52842
+ execSync(`reg delete "${WIN_REG_KEY}" /v ${WIN_REG_VALUE} /f`, { stdio: "ignore" });
52843
+ } catch {}
52844
+ },
52845
+ getServicePath() {
52846
+ return `${WIN_REG_KEY}\\${WIN_REG_VALUE}`;
52847
+ }
52848
+ };
52849
+ }
52850
+ function createUnsupportedManager() {
52851
+ return {
52852
+ platform: "unsupported",
52853
+ async isInstalled() {
52854
+ return false;
52855
+ },
52856
+ async install() {
52857
+ throw new Error("\u5F53\u524D\u5E73\u53F0\u4E0D\u652F\u6301\u81EA\u542F\u52A8");
52858
+ },
52859
+ async uninstall() {
52860
+ throw new Error("\u5F53\u524D\u5E73\u53F0\u4E0D\u652F\u6301\u81EA\u542F\u52A8");
52861
+ },
52862
+ getServicePath() {
52863
+ return "";
52864
+ }
52865
+ };
52866
+ }
52867
+ function createAutostartManager() {
52868
+ const p = platform();
52869
+ if (p === "darwin")
52870
+ return createMacosManager();
52871
+ if (p === "linux")
52872
+ return createLinuxManager();
52873
+ if (p === "win32")
52874
+ return createWindowsManager();
52875
+ return createUnsupportedManager();
52876
+ }
52877
+ function getAutostartExecArgs() {
52878
+ const script = process.argv[1] ?? "dist/cli.js";
52879
+ return {
52880
+ execPath: process.execPath,
52881
+ args: [script, "__run-server", "--mode", "daemon"]
52882
+ };
52883
+ }
52884
+ var LABEL = "com.lakphy.local-router", WIN_REG_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", WIN_REG_VALUE = "LocalRouter";
52885
+ var init_autostart = __esm(() => {
52886
+ init_runtime();
52887
+ });
52888
+
52829
52889
  // src/config-store.ts
52830
52890
  import { writeFileSync as writeFileSync5 } from "fs";
52831
52891
  import { resolve as resolve4 } from "path";
@@ -57455,6 +57515,97 @@ var init_openapi = __esm(() => {
57455
57515
  }
57456
57516
  }
57457
57517
  },
57518
+ "/api/models": {
57519
+ get: {
57520
+ tags: ["Health"],
57521
+ summary: "\u55C5\u63A2\u53EF\u7528\u6A21\u578B\u8DEF\u7531",
57522
+ description: "\u8FD4\u56DE\u672C\u673A\u6307\u5B9A\u534F\u8BAE\u4E0B\u53EF\u7528\u7684\u6A21\u578B\u8DEF\u7531\u522B\u540D\uFF08routes[protocol] \u7684 key\uFF0C\u6392\u9664 * \u901A\u914D\uFF09\u3002\u4F9B\u5C40\u57DF\u7F51\u5185\u5176\u4ED6 local-router \u63A2\u6D4B\u4F7F\u7528\u3002",
57523
+ parameters: [
57524
+ {
57525
+ name: "protocol",
57526
+ in: "query",
57527
+ required: true,
57528
+ schema: {
57529
+ type: "string",
57530
+ enum: ["openai-completions", "openai-responses", "anthropic-messages"]
57531
+ },
57532
+ description: "\u534F\u8BAE\u7C7B\u578B"
57533
+ }
57534
+ ],
57535
+ responses: {
57536
+ "200": {
57537
+ description: "\u53EF\u7528\u6A21\u578B\u8DEF\u7531\u5217\u8868",
57538
+ content: {
57539
+ "application/json": {
57540
+ schema: {
57541
+ type: "object",
57542
+ properties: {
57543
+ protocol: { type: "string", example: "anthropic-messages" },
57544
+ models: {
57545
+ type: "array",
57546
+ items: { type: "string" },
57547
+ example: ["claude-3-5-sonnet", "claude-3-opus"]
57548
+ }
57549
+ }
57550
+ }
57551
+ }
57552
+ }
57553
+ },
57554
+ "400": { description: "\u534F\u8BAE\u53C2\u6570\u7F3A\u5931\u6216\u65E0\u6548" }
57555
+ }
57556
+ }
57557
+ },
57558
+ "/api/providers/discover": {
57559
+ get: {
57560
+ tags: ["Health"],
57561
+ summary: "\u53D1\u73B0\u5C40\u57DF\u7F51 local-router \u7684\u6A21\u578B",
57562
+ description: "\u7531\u672C\u673A server \u4EE3\u4E3A\u8BF7\u6C42\u5BF9\u7AEF local-router \u7684 /api/models\uFF08\u89C4\u907F\u6D4F\u89C8\u5668\u8DE8\u57DF\uFF09\uFF0C\u8FD4\u56DE\u5BF9\u7AEF\u67D0\u534F\u8BAE\u4E0B\u7684\u53EF\u7528\u6A21\u578B\u8DEF\u7531\u3002",
57563
+ parameters: [
57564
+ {
57565
+ name: "ip",
57566
+ in: "query",
57567
+ required: true,
57568
+ schema: { type: "string" },
57569
+ description: "\u5BF9\u7AEF IP"
57570
+ },
57571
+ {
57572
+ name: "port",
57573
+ in: "query",
57574
+ required: false,
57575
+ schema: { type: "string", default: "4099" },
57576
+ description: "\u5BF9\u7AEF\u7AEF\u53E3\uFF0C\u9ED8\u8BA4 4099"
57577
+ },
57578
+ {
57579
+ name: "protocol",
57580
+ in: "query",
57581
+ required: true,
57582
+ schema: {
57583
+ type: "string",
57584
+ enum: ["openai-completions", "openai-responses", "anthropic-messages"]
57585
+ },
57586
+ description: "\u534F\u8BAE\u7C7B\u578B"
57587
+ }
57588
+ ],
57589
+ responses: {
57590
+ "200": {
57591
+ description: "\u5BF9\u7AEF\u53EF\u7528\u6A21\u578B\u8DEF\u7531\u5217\u8868",
57592
+ content: {
57593
+ "application/json": {
57594
+ schema: {
57595
+ type: "object",
57596
+ properties: {
57597
+ protocol: { type: "string", example: "anthropic-messages" },
57598
+ models: { type: "array", items: { type: "string" } }
57599
+ }
57600
+ }
57601
+ }
57602
+ }
57603
+ },
57604
+ "400": { description: "\u53C2\u6570\u7F3A\u5931" },
57605
+ "502": { description: "\u65E0\u6CD5\u8FDE\u63A5\u5BF9\u7AEF\u6216\u5BF9\u7AEF\u8FD4\u56DE\u9519\u8BEF" }
57606
+ }
57607
+ }
57608
+ },
57458
57609
  "/api/metrics/logs": {
57459
57610
  get: {
57460
57611
  tags: ["Health"],
@@ -59234,6 +59385,13 @@ function createServerAddressInfo(listenHost, port) {
59234
59385
  // src/index.ts
59235
59386
  import { readFileSync as readFileSync8 } from "fs";
59236
59387
  import { dirname as dirname4, resolve as resolve8 } from "path";
59388
+ function readRestartCriticalServerFields(config2) {
59389
+ return {
59390
+ host: config2.server?.host ?? "0.0.0.0",
59391
+ port: config2.server?.port ?? 4099,
59392
+ idleTimeout: config2.server?.idleTimeout ?? 0
59393
+ };
59394
+ }
59237
59395
  function printIntegrationGuide(config2, listen = {}) {
59238
59396
  const host = listen.host ?? process.env.HOST ?? "0.0.0.0";
59239
59397
  const parsedPort = listen.port ?? Number.parseInt(process.env.PORT ?? "4099", 10);
@@ -59313,7 +59471,7 @@ function createChatProxyModel(providerName, providerConfig, model) {
59313
59471
  throw new Error(`\u6682\u4E0D\u652F\u6301\u7684 provider \u7C7B\u578B: ${providerConfig.type}`);
59314
59472
  }
59315
59473
  }
59316
- function createAdminApiRoutes(store, pluginManager, registerCleanup) {
59474
+ function createAdminApiRoutes(store, pluginManager, registerCleanup, serverControl, restartLogStorageTask, serviceVersion) {
59317
59475
  const api2 = new Hono2;
59318
59476
  const cryptoSessions = new Map;
59319
59477
  const CRYPTO_SESSION_TTL_MS = 2 * 60 * 1000;
@@ -59353,7 +59511,16 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
59353
59511
  }
59354
59512
  cryptoSessions.clear();
59355
59513
  });
59356
- api2.get("/health", (c) => c.json({ status: "ok", service: "local-router" }));
59514
+ api2.get("/health", (c) => {
59515
+ const addr = serverControl?.current ?? readRestartCriticalServerFields(store.get());
59516
+ return c.json({
59517
+ status: "ok",
59518
+ service: "local-router",
59519
+ version: serviceVersion ?? "unknown",
59520
+ host: addr.host,
59521
+ port: addr.port
59522
+ });
59523
+ });
59357
59524
  api2.post("/crypto/handshake", async (c) => {
59358
59525
  pruneExpiredCryptoSessions();
59359
59526
  if (cryptoSessions.size >= CRYPTO_SESSION_MAX) {
@@ -59426,18 +59593,30 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
59426
59593
  });
59427
59594
  api2.post("/config/apply", async (_c) => {
59428
59595
  try {
59596
+ const fallbackBefore = readRestartCriticalServerFields(store.get());
59597
+ const before = serverControl?.current ?? fallbackBefore;
59429
59598
  const config2 = store.reload();
59599
+ const after = readRestartCriticalServerFields(config2);
59430
59600
  if (config2.log) {
59431
59601
  const logBaseDir = resolveLogBaseDir(config2.log);
59432
59602
  initLogger(logBaseDir, config2.log);
59603
+ } else {
59604
+ resetLogger();
59433
59605
  }
59606
+ restartLogStorageTask?.(config2.log);
59434
59607
  const pluginResult = await pluginManager.reloadAll(config2.providers);
59608
+ const restartRequired = before.host !== after.host || before.port !== after.port || before.idleTimeout !== after.idleTimeout;
59435
59609
  return _c.json({
59436
59610
  ok: true,
59437
59611
  summary: {
59438
59612
  providers: Object.keys(config2.providers).length,
59439
59613
  routes: Object.keys(config2.routes).length
59440
59614
  },
59615
+ restartRequired,
59616
+ ...restartRequired && {
59617
+ listen: { host: after.host, port: after.port },
59618
+ canRestart: Boolean(serverControl)
59619
+ },
59441
59620
  ...pluginResult.failures.length > 0 && {
59442
59621
  pluginWarnings: pluginResult.failures
59443
59622
  }
@@ -59446,12 +59625,57 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
59446
59625
  return _c.json({ error: `\u5E94\u7528\u914D\u7F6E\u5931\u8D25: ${err instanceof Error ? err.message : err}` }, 500);
59447
59626
  }
59448
59627
  });
59628
+ api2.post("/restart", (c) => {
59629
+ if (!serverControl) {
59630
+ return c.json({ error: "\u5F53\u524D\u8FD0\u884C\u65B9\u5F0F\u4E0D\u652F\u6301\u81EA\u52A8\u91CD\u542F\uFF0C\u8BF7\u624B\u52A8\u6267\u884C local-router restart" }, 501);
59631
+ }
59632
+ const { host, port } = readRestartCriticalServerFields(store.get());
59633
+ serverControl.requestRestart();
59634
+ return c.json({ ok: true, listen: { host, port } });
59635
+ });
59449
59636
  api2.get("/config/meta", (c) => {
59450
59637
  return c.json({
59451
59638
  configPath: store.getPath(),
59452
59639
  routeTypes: Object.keys(ROUTE_REGISTRY)
59453
59640
  });
59454
59641
  });
59642
+ api2.get("/models", (c) => {
59643
+ const protocol = c.req.query("protocol");
59644
+ const routeTypes = Object.keys(ROUTE_REGISTRY);
59645
+ if (!protocol || !routeTypes.includes(protocol)) {
59646
+ return c.json({ error: "invalid or missing protocol", routeTypes }, 400);
59647
+ }
59648
+ const routes = store.get().routes[protocol] ?? {};
59649
+ const models = Object.keys(routes).filter((k) => k !== "*");
59650
+ return c.json({ protocol, models });
59651
+ });
59652
+ api2.get("/providers/discover", async (c) => {
59653
+ const ip = c.req.query("ip");
59654
+ const portStr = c.req.query("port") ?? "4099";
59655
+ const protocol = c.req.query("protocol");
59656
+ if (!ip || !protocol) {
59657
+ return c.json({ error: "ip and protocol are required" }, 400);
59658
+ }
59659
+ const port = Number(portStr);
59660
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
59661
+ return c.json({ error: "\u7AEF\u53E3\u65E0\u6548\uFF0C\u5FC5\u987B\u662F 1-65535 \u7684\u6574\u6570" }, 400);
59662
+ }
59663
+ if (!isLoopbackAddress(ip) && !isLanAddress(ip)) {
59664
+ return c.json({ error: "\u4EC5\u652F\u6301\u5C40\u57DF\u7F51\u6216\u672C\u673A IP \u5730\u5740" }, 400);
59665
+ }
59666
+ const url2 = `http://${ip}:${port}/api/models?protocol=${encodeURIComponent(protocol)}`;
59667
+ try {
59668
+ const res = await fetch(url2, { signal: AbortSignal.timeout(5000) });
59669
+ if (!res.ok) {
59670
+ const body = await res.json().catch(() => ({}));
59671
+ return c.json({ error: body.error ?? `remote returned ${res.status}` }, 502);
59672
+ }
59673
+ const data = await res.json();
59674
+ return c.json(data);
59675
+ } catch (err) {
59676
+ return c.json({ error: `\u65E0\u6CD5\u8FDE\u63A5\u5BF9\u7AEF local-router: ${err instanceof Error ? err.message : err}` }, 502);
59677
+ }
59678
+ });
59455
59679
  api2.get("/config/schema", (c) => {
59456
59680
  try {
59457
59681
  return c.json(schemaJson);
@@ -59954,6 +60178,7 @@ async function proxyAdminToDevServer(c, origin) {
59954
60178
  }
59955
60179
  async function createApp(store, options) {
59956
60180
  const config2 = store.get();
60181
+ const serviceVersion = await readVersionString();
59957
60182
  console.log(`\u5DF2\u52A0\u8F7D\u914D\u7F6E: ${store.getPath()}`);
59958
60183
  if (config2.log) {
59959
60184
  const logBaseDir = resolveLogBaseDir(config2.log);
@@ -59961,8 +60186,14 @@ async function createApp(store, options) {
59961
60186
  } else {
59962
60187
  resetLogger();
59963
60188
  }
59964
- const stopLogStorageTask = startLogStorageBackgroundTask(config2.log);
59965
- options?.registerCleanup?.(stopLogStorageTask);
60189
+ let stopLogStorageTask = startLogStorageBackgroundTask(config2.log);
60190
+ const restartLogStorageTask = (logConfig) => {
60191
+ try {
60192
+ stopLogStorageTask();
60193
+ } catch {}
60194
+ stopLogStorageTask = startLogStorageBackgroundTask(logConfig);
60195
+ };
60196
+ options?.registerCleanup?.(() => stopLogStorageTask());
59966
60197
  const configDir = dirname4(resolve8(store.getPath()));
59967
60198
  const pluginManager = new PluginManager(configDir);
59968
60199
  const reloadResult = await pluginManager.reloadAll(config2.providers);
@@ -59981,7 +60212,7 @@ async function createApp(store, options) {
59981
60212
  app.route(entry.mountPrefix, subApp);
59982
60213
  console.log(`\u5DF2\u6CE8\u518C\u8DEF\u7531: ${routeType} -> ${entry.mountPrefix}`);
59983
60214
  }
59984
- app.route("/api", createAdminApiRoutes(store, pluginManager, options?.registerCleanup));
60215
+ app.route("/api", createAdminApiRoutes(store, pluginManager, options?.registerCleanup, options?.serverControl, restartLogStorageTask, serviceVersion));
59985
60216
  console.log("\u5DF2\u6CE8\u518C\u7BA1\u7406 API: /api");
59986
60217
  app.get("/api/docs", middleware({ url: "/api/openapi.json" }));
59987
60218
  app.get("/api/openapi.json", (c) => c.json(openAPISpec));
@@ -60010,11 +60241,12 @@ async function createApp(store, options) {
60010
60241
  }
60011
60242
  return app;
60012
60243
  }
60013
- async function createAppRuntimeFromConfigPath(configPath, listen) {
60244
+ async function createAppRuntimeFromConfigPath(configPath, listen, serverControl) {
60014
60245
  const store = new ConfigStore(configPath);
60015
60246
  const cleanups = [];
60016
60247
  const app = await createApp(store, {
60017
60248
  listen,
60249
+ serverControl,
60018
60250
  registerCleanup: (cleanup) => {
60019
60251
  cleanups.push(cleanup);
60020
60252
  }
@@ -60035,7 +60267,6 @@ async function createAppRuntimeFromConfigPath(configPath, listen) {
60035
60267
  }
60036
60268
  var ROUTE_REGISTRY;
60037
60269
  var init_src = __esm(() => {
60038
- init_autostart();
60039
60270
  init_dist4();
60040
60271
  init_dist5();
60041
60272
  init_dist6();
@@ -60043,6 +60274,7 @@ var init_src = __esm(() => {
60043
60274
  init_dist9();
60044
60275
  init_dist10();
60045
60276
  init_bun();
60277
+ init_autostart();
60046
60278
  init_config();
60047
60279
  init_config_store();
60048
60280
  init_config_validate();
@@ -60116,11 +60348,15 @@ function resolveIdleTimeoutSeconds(explicit) {
60116
60348
  return DEFAULT_IDLE_TIMEOUT_SECONDS;
60117
60349
  }
60118
60350
  async function startServer(options) {
60351
+ const idleTimeout = resolveIdleTimeoutSeconds(options.idleTimeoutSeconds);
60352
+ const requestRestart = options.requestRestart;
60119
60353
  const runtime = await createAppRuntimeFromConfigPath(options.configPath, {
60120
60354
  host: options.host,
60121
60355
  port: options.port
60122
- });
60123
- const idleTimeout = resolveIdleTimeoutSeconds(options.idleTimeoutSeconds);
60356
+ }, requestRestart ? {
60357
+ requestRestart,
60358
+ current: { host: options.host, port: options.port, idleTimeout }
60359
+ } : undefined);
60124
60360
  const server = Bun.serve({
60125
60361
  fetch: (request, server2) => {
60126
60362
  const remoteAddress = server2.requestIP(request)?.address ?? null;
@@ -60163,6 +60399,7 @@ var exports_process = {};
60163
60399
  __export(exports_process, {
60164
60400
  stopProcess: () => stopProcess,
60165
60401
  startDaemon: () => startDaemon,
60402
+ spawnDetachedRestart: () => spawnDetachedRestart,
60166
60403
  runServerProcess: () => runServerProcess,
60167
60404
  readLogDelta: () => readLogDelta,
60168
60405
  parseSharedFlags: () => parseSharedFlags,
@@ -60320,7 +60557,8 @@ async function runServerProcess(opts) {
60320
60557
  configPath: ensured.path,
60321
60558
  host,
60322
60559
  port,
60323
- idleTimeoutSeconds: Number.isFinite(idleTimeoutSeconds) ? idleTimeoutSeconds : undefined
60560
+ idleTimeoutSeconds: Number.isFinite(idleTimeoutSeconds) ? idleTimeoutSeconds : undefined,
60561
+ requestRestart: () => spawnDetachedRestart({ configPath: ensured.path })
60324
60562
  });
60325
60563
  } catch (err) {
60326
60564
  const code = err?.code;
@@ -60424,6 +60662,32 @@ async function startDaemon(flags) {
60424
60662
  throw new Error(`\u540E\u53F0\u542F\u52A8\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u65E5\u5FD7: ${files.daemonLog}
60425
60663
  ${tail}`);
60426
60664
  }
60665
+ function spawnDetachedRestart(opts = {}) {
60666
+ const trigger = () => {
60667
+ try {
60668
+ ensureRuntimeDirs();
60669
+ const files = getRuntimeFiles();
60670
+ const fd = openSync3(files.daemonLog, "a");
60671
+ const cliScript = process.argv[1] ?? "src/cli.ts";
60672
+ const childArgs = [cliScript, "restart"];
60673
+ if (opts.configPath) {
60674
+ childArgs.push("--config", opts.configPath);
60675
+ }
60676
+ const child = Bun.spawn([process.execPath, ...childArgs], {
60677
+ stdin: "ignore",
60678
+ stdout: fd,
60679
+ stderr: fd,
60680
+ detached: true
60681
+ });
60682
+ closeSync3(fd);
60683
+ child.unref();
60684
+ } catch (err) {
60685
+ console.error(`[restart] \u81EA\u91CD\u542F\u89E6\u53D1\u5931\u8D25: ${err instanceof Error ? err.message : err}`);
60686
+ }
60687
+ };
60688
+ const timer = setTimeout(trigger, 400);
60689
+ timer.unref?.();
60690
+ }
60427
60691
  async function stopProcess(graceMs = 8000) {
60428
60692
  await cleanupIfStale();
60429
60693
  const state = readRuntimeState();
@@ -61066,6 +61330,10 @@ function normalizeOne(flag, raw2) {
61066
61330
  }
61067
61331
  function parseCommandArgs(def, args) {
61068
61332
  const options = toParseArgsOptions(def.flags);
61333
+ for (const g of GLOBAL_TARGET_FLAG_NAMES) {
61334
+ if (!(g in options))
61335
+ options[g] = { type: "string" };
61336
+ }
61069
61337
  let parsed;
61070
61338
  try {
61071
61339
  parsed = nodeParseArgs({
@@ -61077,7 +61345,7 @@ function parseCommandArgs(def, args) {
61077
61345
  } catch (err) {
61078
61346
  throw new CliError("USAGE_ERROR", err instanceof Error ? err.message : String(err));
61079
61347
  }
61080
- const known = new Set(def.flags?.map((f) => f.name) ?? []);
61348
+ const known = new Set([...def.flags?.map((f) => f.name) ?? [], ...GLOBAL_TARGET_FLAG_NAMES]);
61081
61349
  for (const a of args) {
61082
61350
  if (!a.startsWith("--"))
61083
61351
  continue;
@@ -61145,8 +61413,10 @@ function levenshtein(a, b) {
61145
61413
  }
61146
61414
  return dp[b.length];
61147
61415
  }
61416
+ var GLOBAL_TARGET_FLAG_NAMES;
61148
61417
  var init_parse_args = __esm(() => {
61149
61418
  init_errors();
61419
+ GLOBAL_TARGET_FLAG_NAMES = ["url", "host", "port"];
61150
61420
  });
61151
61421
 
61152
61422
  // src/cli/registry.ts
@@ -61442,9 +61712,298 @@ init_config_apply();
61442
61712
  init_errors();
61443
61713
  init_output();
61444
61714
  init_process();
61715
+ import { createInterface as createInterface5 } from "readline/promises";
61716
+ import { parseArgs as parseArgs3 } from "util";
61717
+
61718
+ // src/cli/target.ts
61719
+ init_errors();
61720
+ init_output();
61721
+ init_process();
61445
61722
  init_runtime();
61446
61723
  import { createInterface as createInterface4 } from "readline/promises";
61447
- import { parseArgs as parseArgs3 } from "util";
61724
+ var DEFAULT_HOST = "127.0.0.1";
61725
+ var DEFAULT_PORT = 4099;
61726
+ function normalizeHostForUrl(host) {
61727
+ const h = host.trim();
61728
+ if (h === "" || h === "0.0.0.0")
61729
+ return "127.0.0.1";
61730
+ if (h === "::" || h === "::1")
61731
+ return "[::1]";
61732
+ if (h.includes(":") && !h.startsWith("["))
61733
+ return `[${h}]`;
61734
+ return h;
61735
+ }
61736
+ function buildBaseUrl(host, port) {
61737
+ return `http://${normalizeHostForUrl(host)}:${port}`;
61738
+ }
61739
+ async function fingerprint(baseUrl, timeoutMs = 800) {
61740
+ const controller = new AbortController;
61741
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
61742
+ try {
61743
+ const res = await fetch(`${baseUrl}/api/health`, { method: "GET", signal: controller.signal });
61744
+ if (!res.ok)
61745
+ return { ok: false };
61746
+ const body = await res.json();
61747
+ if (body?.service !== "local-router")
61748
+ return { ok: false };
61749
+ return { ok: true, version: body.version, host: body.host, port: body.port };
61750
+ } catch {
61751
+ return { ok: false };
61752
+ } finally {
61753
+ clearTimeout(timer);
61754
+ }
61755
+ }
61756
+ function parseLsofPorts(output) {
61757
+ const ports = new Set;
61758
+ for (const line of output.split(`
61759
+ `)) {
61760
+ if (!line.includes("LISTEN"))
61761
+ continue;
61762
+ const m = line.match(/(?:\*|\[[^\]]+\]|[\d.]+):(\d{2,5})\s*\(LISTEN\)/);
61763
+ if (!m)
61764
+ continue;
61765
+ const port = Number.parseInt(m[1], 10);
61766
+ if (Number.isFinite(port))
61767
+ ports.add(port);
61768
+ }
61769
+ return [...ports];
61770
+ }
61771
+ function parseNetstatPorts(output) {
61772
+ const ports = new Set;
61773
+ for (const line of output.split(`
61774
+ `)) {
61775
+ if (!/LISTEN/i.test(line))
61776
+ continue;
61777
+ const m = line.match(/[.:](\d{2,5})\s+\S+\s+LISTEN/i) ?? line.match(/[.:](\d{2,5})\s+LISTEN/i);
61778
+ if (!m)
61779
+ continue;
61780
+ const port = Number.parseInt(m[1], 10);
61781
+ if (Number.isFinite(port))
61782
+ ports.add(port);
61783
+ }
61784
+ return [...ports];
61785
+ }
61786
+ async function runShellCommand(cmd) {
61787
+ try {
61788
+ const proc = Bun.spawn(cmd, { stdout: "pipe", stderr: "ignore", stdin: "ignore" });
61789
+ const out = await new Response(proc.stdout).text();
61790
+ await proc.exited;
61791
+ return out;
61792
+ } catch {
61793
+ return null;
61794
+ }
61795
+ }
61796
+ async function listListeningPorts() {
61797
+ const ports = new Set;
61798
+ const lsof = await runShellCommand(["lsof", "-nP", "-iTCP", "-sTCP:LISTEN"]);
61799
+ if (lsof) {
61800
+ for (const p of parseLsofPorts(lsof))
61801
+ ports.add(p);
61802
+ }
61803
+ if (ports.size === 0) {
61804
+ const netstat = await runShellCommand(["netstat", "-an"]);
61805
+ if (netstat) {
61806
+ for (const p of parseNetstatPorts(netstat))
61807
+ ports.add(p);
61808
+ }
61809
+ }
61810
+ return [...ports].sort((a, b) => a - b);
61811
+ }
61812
+ async function discoverLocalRouters(timeoutMs = 300) {
61813
+ const ports = await listListeningPorts();
61814
+ const hits = [];
61815
+ const concurrency = 8;
61816
+ let idx = 0;
61817
+ const worker = async () => {
61818
+ while (idx < ports.length) {
61819
+ const port = ports[idx++];
61820
+ const fp = await fingerprint(buildBaseUrl(DEFAULT_HOST, port), timeoutMs);
61821
+ if (fp.ok)
61822
+ hits.push({ port, version: fp.version });
61823
+ }
61824
+ };
61825
+ await Promise.all(Array.from({ length: Math.min(concurrency, ports.length) }, () => worker()));
61826
+ return hits.sort((a, b) => a.port - b.port);
61827
+ }
61828
+ async function fromExplicit(url2, host, port, source) {
61829
+ if (url2) {
61830
+ let u;
61831
+ try {
61832
+ u = new URL(url2);
61833
+ } catch {
61834
+ throw new CliError("USAGE_ERROR", `\u65E0\u6548 URL: ${url2}`, { hint: "\u5F62\u5982 http://127.0.0.1:4099" });
61835
+ }
61836
+ const baseUrl = u.origin;
61837
+ const fp = await fingerprint(baseUrl);
61838
+ if (!fp.ok) {
61839
+ throw new CliError("TARGET_UNREACHABLE", `\u76EE\u6807\u65E0\u6CD5\u8FDE\u901A: ${baseUrl}`, {
61840
+ hint: "\u786E\u8BA4\u5730\u5740\u6B63\u786E\u4E14 local-router \u6B63\u5728\u8BE5\u5730\u5740\u76D1\u542C"
61841
+ });
61842
+ }
61843
+ const portNum = u.port ? Number.parseInt(u.port, 10) : u.protocol === "https:" ? 443 : 80;
61844
+ return { baseUrl, host: u.hostname, port: portNum, version: fp.version, source };
61845
+ }
61846
+ if (port !== undefined || host !== undefined) {
61847
+ const h = host ?? DEFAULT_HOST;
61848
+ const p = port ?? DEFAULT_PORT;
61849
+ const baseUrl = buildBaseUrl(h, p);
61850
+ const fp = await fingerprint(baseUrl);
61851
+ if (!fp.ok) {
61852
+ throw new CliError("TARGET_UNREACHABLE", `\u76EE\u6807\u65E0\u6CD5\u8FDE\u901A: ${baseUrl}`, {
61853
+ hint: "\u786E\u8BA4\u7AEF\u53E3\u6B63\u786E\u4E14 local-router \u6B63\u5728\u76D1\u542C"
61854
+ });
61855
+ }
61856
+ return { baseUrl, host: h, port: p, version: fp.version, source };
61857
+ }
61858
+ return null;
61859
+ }
61860
+ async function promptPort() {
61861
+ const rl = createInterface4({ input: process.stdin, output: process.stdout });
61862
+ try {
61863
+ const answer = (await rl.question(`\u672A\u53D1\u73B0 local-router\uFF0C\u8BF7\u8F93\u5165\u7AEF\u53E3 [${DEFAULT_PORT}]: `)).trim();
61864
+ const port = answer === "" ? DEFAULT_PORT : Number.parseInt(answer, 10);
61865
+ if (!Number.isFinite(port) || port <= 0 || port > 65535) {
61866
+ throw new CliError("USAGE_ERROR", `\u65E0\u6548\u7AEF\u53E3: ${answer}`, { hint: "\u7AEF\u53E3\u8303\u56F4 1-65535" });
61867
+ }
61868
+ return port;
61869
+ } finally {
61870
+ rl.close();
61871
+ }
61872
+ }
61873
+ async function promptSelectPort(found) {
61874
+ const rl = createInterface4({ input: process.stdin, output: process.stdout });
61875
+ try {
61876
+ console.log("\u53D1\u73B0\u591A\u4E2A local-router\uFF1A");
61877
+ found.forEach((f, i) => {
61878
+ console.log(` ${i + 1}) 127.0.0.1:${f.port}${f.version ? ` (v${f.version})` : ""}`);
61879
+ });
61880
+ const answer = (await rl.question("\u8BF7\u8F93\u5165\u5E8F\u53F7: ")).trim();
61881
+ const idx = Number.parseInt(answer, 10) - 1;
61882
+ if (!Number.isFinite(idx) || idx < 0 || idx >= found.length) {
61883
+ throw new CliError("USAGE_ERROR", `\u65E0\u6548\u5E8F\u53F7: ${answer}`);
61884
+ }
61885
+ return found[idx].port;
61886
+ } finally {
61887
+ rl.close();
61888
+ }
61889
+ }
61890
+ function discoveredTarget(found) {
61891
+ return {
61892
+ baseUrl: buildBaseUrl(DEFAULT_HOST, found.port),
61893
+ host: DEFAULT_HOST,
61894
+ port: found.port,
61895
+ version: found.version,
61896
+ source: "discovered"
61897
+ };
61898
+ }
61899
+ async function resolveTarget(flags) {
61900
+ const explicit = await fromExplicit(flags.target?.url, flags.target?.host, flags.target?.port, "flag");
61901
+ if (explicit)
61902
+ return explicit;
61903
+ const envUrl = process.env.LOCAL_ROUTER_URL?.trim() || undefined;
61904
+ const envPortRaw = process.env.LOCAL_ROUTER_PORT?.trim();
61905
+ let envPort;
61906
+ if (envPortRaw) {
61907
+ envPort = Number.parseInt(envPortRaw, 10);
61908
+ if (!Number.isFinite(envPort)) {
61909
+ throw new CliError("USAGE_ERROR", `\u65E0\u6548 LOCAL_ROUTER_PORT: ${envPortRaw}`);
61910
+ }
61911
+ }
61912
+ const env = await fromExplicit(envUrl, undefined, envPort, "env");
61913
+ if (env)
61914
+ return env;
61915
+ const state = readRuntimeState();
61916
+ if (state && isProcessAlive(state.pid)) {
61917
+ const fp = await fingerprint(state.baseUrl);
61918
+ if (fp.ok) {
61919
+ return {
61920
+ baseUrl: state.baseUrl,
61921
+ host: resolveLocalAccessHost(state.host),
61922
+ port: state.port,
61923
+ version: fp.version,
61924
+ source: "runtime"
61925
+ };
61926
+ }
61927
+ }
61928
+ const defaultUrl = buildBaseUrl(DEFAULT_HOST, DEFAULT_PORT);
61929
+ const defFp = await fingerprint(defaultUrl);
61930
+ if (defFp.ok) {
61931
+ return {
61932
+ baseUrl: defaultUrl,
61933
+ host: DEFAULT_HOST,
61934
+ port: DEFAULT_PORT,
61935
+ version: defFp.version,
61936
+ source: "default"
61937
+ };
61938
+ }
61939
+ const found = await discoverLocalRouters();
61940
+ if (found.length === 1) {
61941
+ return discoveredTarget(found[0]);
61942
+ }
61943
+ if (found.length > 1) {
61944
+ if (flags.yes)
61945
+ return discoveredTarget(found[0]);
61946
+ if (flags.noInteractive || !process.stdin.isTTY) {
61947
+ throw new CliError("TARGET_NOT_FOUND", "\u53D1\u73B0\u591A\u4E2A local-router\uFF0C\u8BF7\u7528 --port \u6307\u5B9A", {
61948
+ hint: `\u5019\u9009\u7AEF\u53E3: ${found.map((f) => f.port).join(", ")}`,
61949
+ details: { candidates: found }
61950
+ });
61951
+ }
61952
+ const picked = await promptSelectPort(found);
61953
+ return discoveredTarget(found.find((f) => f.port === picked) ?? { port: picked });
61954
+ }
61955
+ if (!flags.noInteractive && process.stdin.isTTY) {
61956
+ const port = await promptPort();
61957
+ const baseUrl = buildBaseUrl(DEFAULT_HOST, port);
61958
+ const fp = await fingerprint(baseUrl);
61959
+ if (!fp.ok) {
61960
+ throw new CliError("TARGET_UNREACHABLE", `\u7AEF\u53E3 ${port} \u4E0A\u6CA1\u6709 local-router`);
61961
+ }
61962
+ return { baseUrl, host: DEFAULT_HOST, port, version: fp.version, source: "prompt" };
61963
+ }
61964
+ throw new CliError("TARGET_NOT_FOUND", "\u627E\u4E0D\u5230\u53EF\u8FDE\u63A5\u7684 local-router", {
61965
+ hint: "`local-router start` \u542F\u52A8\uFF1B\u6216\u7528 --port <port> / --url <url> \u6307\u5B9A\u76EE\u6807"
61966
+ });
61967
+ }
61968
+ function guessTargetUrl(flags) {
61969
+ if (flags.target?.url) {
61970
+ try {
61971
+ const u = new URL(flags.target.url);
61972
+ const port = u.port ? Number.parseInt(u.port, 10) : u.protocol === "https:" ? 443 : 80;
61973
+ return { baseUrl: u.origin, host: u.hostname, port, running: false };
61974
+ } catch {}
61975
+ }
61976
+ if (flags.target?.port !== undefined || flags.target?.host !== undefined) {
61977
+ const host = flags.target.host ?? DEFAULT_HOST;
61978
+ const port = flags.target.port ?? DEFAULT_PORT;
61979
+ return { baseUrl: buildBaseUrl(host, port), host, port, running: false };
61980
+ }
61981
+ const state = readRuntimeState();
61982
+ if (state && isProcessAlive(state.pid)) {
61983
+ return {
61984
+ baseUrl: state.baseUrl,
61985
+ host: resolveLocalAccessHost(state.host),
61986
+ port: state.port,
61987
+ running: true
61988
+ };
61989
+ }
61990
+ return {
61991
+ baseUrl: buildBaseUrl(DEFAULT_HOST, DEFAULT_PORT),
61992
+ host: DEFAULT_HOST,
61993
+ port: DEFAULT_PORT,
61994
+ running: false
61995
+ };
61996
+ }
61997
+ function targetMetaLine(t) {
61998
+ return `\u2192 ${t.host}:${t.port}${t.version ? ` (v${t.version})` : ""} \xB7 ${t.source}`;
61999
+ }
62000
+ async function requireTarget(ctx) {
62001
+ const t = await resolveTarget(ctx.flags);
62002
+ emitDiagnostic(ctx, targetMetaLine(t));
62003
+ return t;
62004
+ }
62005
+
62006
+ // src/cli/config-command.ts
61448
62007
  function readConfig(configArg) {
61449
62008
  const path = resolveConfigPath(configArg);
61450
62009
  return { path, config: loadConfig(path) };
@@ -61512,7 +62071,7 @@ async function selectFromList(title, items) {
61512
62071
  items.forEach((item, i) => {
61513
62072
  console.log(` ${i + 1}) ${item}`);
61514
62073
  });
61515
- const rl = createInterface4({ input: process.stdin, output: process.stdout });
62074
+ const rl = createInterface5({ input: process.stdin, output: process.stdout });
61516
62075
  try {
61517
62076
  const answer = await rl.question("\u8BF7\u8F93\u5165\u5E8F\u53F7: ");
61518
62077
  const idx = Number.parseInt(answer, 10) - 1;
@@ -61537,6 +62096,7 @@ Commands:
61537
62096
  config provider list [--json] [--config <path>]
61538
62097
  config provider show <name> [--show-secrets] [--config <path>]
61539
62098
  config provider add <name> --type <type> --base <url> --api-key <key> --model <name> [--image-input] [--reasoning] [--proxy <url>] [--dry-run] [--config <path>]
62099
+ config provider add-lan <ip> --type <protocol> [--port 4099] [--dry-run] [--config <path>]
61540
62100
  config provider set <name> [--base <url>] [--api-key <key>] [--proxy <url>] [--dry-run] [--config <path>]
61541
62101
  config provider remove <name> [--force] [--dry-run] [--config <path>]
61542
62102
  config provider model list <provider> [--config <path>]
@@ -61716,6 +62276,89 @@ async function handleProviderAdd(args, flags) {
61716
62276
  }
61717
62277
  });
61718
62278
  }
62279
+ async function handleProviderAddLan(args, flags) {
62280
+ return runCommand({
62281
+ command: "config.provider.add-lan",
62282
+ flags,
62283
+ fn: async (ctx) => {
62284
+ const [ip, ...flagArgs] = args;
62285
+ ensureNoFlag("ip", ip);
62286
+ if (!ip) {
62287
+ throw new CliError("USAGE_ERROR", "ip \u5FC5\u586B", {
62288
+ hint: "\u7528\u6CD5: config provider add-lan <ip> --type <protocol> [--port 4099]"
62289
+ });
62290
+ }
62291
+ const parsed = parseArgs3({
62292
+ args: flagArgs,
62293
+ options: {
62294
+ type: { type: "string" },
62295
+ port: { type: "string", default: "4099" },
62296
+ "dry-run": { type: "boolean", default: false },
62297
+ config: { type: "string" }
62298
+ },
62299
+ allowPositionals: true,
62300
+ strict: false
62301
+ });
62302
+ const type = parsed.values.type;
62303
+ if (!type || !providerTypes().includes(type)) {
62304
+ throw new CliError("USAGE_ERROR", "type \u5FC5\u586B\u4E14\u5FC5\u987B\u662F openai-completions/openai-responses/anthropic-messages", { details: { acceptable: providerTypes() } });
62305
+ }
62306
+ const portStr = parsed.values.port ?? "4099";
62307
+ const port = Number(portStr);
62308
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
62309
+ throw new CliError("USAGE_ERROR", `\u7AEF\u53E3\u65E0\u6548: ${portStr}\uFF08\u5FC5\u987B\u662F 1-65535 \u7684\u6574\u6570\uFF09`);
62310
+ }
62311
+ const name21 = `${ip}-${type}`;
62312
+ const { path, config: config2 } = readConfig(parsed.values.config);
62313
+ if (config2.providers[name21]) {
62314
+ throw new CliError("PROVIDER_EXISTS", `provider \u5DF2\u5B58\u5728: ${name21}`, {
62315
+ hint: "\u4F7F\u7528 `config provider set` \u4FEE\u6539\u5B57\u6BB5"
62316
+ });
62317
+ }
62318
+ const url2 = `http://${ip}:${port}/api/models?protocol=${encodeURIComponent(type)}`;
62319
+ let models;
62320
+ try {
62321
+ const res = await fetch(url2, { signal: AbortSignal.timeout(5000) });
62322
+ if (!res.ok) {
62323
+ const body = await res.json().catch(() => ({}));
62324
+ throw new CliError("UPSTREAM_UNREACHABLE", `\u5BF9\u7AEF\u8FD4\u56DE\u9519\u8BEF: ${body.error ?? res.status}`, {
62325
+ details: { url: url2 }
62326
+ });
62327
+ }
62328
+ const data = await res.json();
62329
+ models = Array.isArray(data.models) ? data.models : [];
62330
+ } catch (err) {
62331
+ if (err instanceof CliError)
62332
+ throw err;
62333
+ throw new CliError("UPSTREAM_UNREACHABLE", `\u65E0\u6CD5\u8FDE\u63A5\u5BF9\u7AEF local-router: ${err instanceof Error ? err.message : err}`, { details: { url: url2 } });
62334
+ }
62335
+ if (models.length === 0) {
62336
+ throw new CliError("USAGE_ERROR", `\u5BF9\u7AEF\u5728\u534F\u8BAE "${type}" \u4E0B\u6CA1\u6709\u53EF\u7528\u7684\u6A21\u578B\u8DEF\u7531\uFF0C\u65E0\u6CD5\u521B\u5EFA provider`, { hint: "\u786E\u8BA4\u5BF9\u7AEF\u5DF2\u4E3A\u8BE5\u534F\u8BAE\u914D\u7F6E\u4E86\u5177\u4F53\u6A21\u578B\u8DEF\u7531\uFF08\u800C\u975E\u4EC5 * \u515C\u5E95\uFF09" });
62337
+ }
62338
+ const modelMap = {};
62339
+ for (const m of models) {
62340
+ modelMap[m] = { "image-input": false, reasoning: false };
62341
+ }
62342
+ config2.providers[name21] = {
62343
+ type,
62344
+ base: `http://${ip}:${port}/${type}`,
62345
+ apiKey: "no_key",
62346
+ models: modelMap
62347
+ };
62348
+ const result = applyConfigChange(path, config2, { dryRun: parsed.values["dry-run"] });
62349
+ emitResult(ctx, {
62350
+ command: "config.provider.add-lan",
62351
+ data: { provider: name21, models: models.length, ...result },
62352
+ md: {
62353
+ heading: `config.provider.add-lan \xB7 ${name21} \xB7 ${result.written ? "\u2713" : "dry-run"}`,
62354
+ data: applyResultToMd(result, `provider ${name21} (${models.length} \u4E2A\u6A21\u578B)`),
62355
+ hints: result.written ? ["\u70ED\u52A0\u8F7D: `local-router config apply`"] : ["\u6267\u884C\u5199\u5165: \u53BB\u6389 `--dry-run`"]
62356
+ },
62357
+ text: result.written ? `\u5DF2\u6DFB\u52A0 provider: ${name21}\uFF08\u55C5\u63A2\u5230 ${models.length} \u4E2A\u6A21\u578B\uFF09` : applyResultToText(result)
62358
+ });
62359
+ }
62360
+ });
62361
+ }
61719
62362
  async function handleProviderSet(args, flags) {
61720
62363
  return runCommand({
61721
62364
  command: "config.provider.set",
@@ -62299,32 +62942,26 @@ async function handleApply(args, flags) {
62299
62942
  command: "config.apply",
62300
62943
  flags,
62301
62944
  fn: async (ctx) => {
62302
- await cleanupIfStale();
62303
- const state = readRuntimeState();
62304
- if (!state) {
62305
- throw new CliError("SERVICE_NOT_RUNNING", "\u670D\u52A1\u672A\u8FD0\u884C\uFF0C\u65E0\u6CD5 apply", {
62306
- hint: "\u542F\u52A8: `local-router start --daemon`"
62307
- });
62308
- }
62309
- const res = await fetch(`${state.baseUrl}/api/config/apply`, { method: "POST" });
62945
+ const target = await requireTarget(ctx);
62946
+ const res = await fetch(`${target.baseUrl}/api/config/apply`, { method: "POST" });
62310
62947
  if (!res.ok) {
62311
62948
  const text2 = await res.text();
62312
62949
  throw new CliError("APPLY_FAILED", `apply \u5931\u8D25: ${res.status} ${text2}`, {
62313
- details: { status: res.status, baseUrl: state.baseUrl }
62950
+ details: { status: res.status, baseUrl: target.baseUrl }
62314
62951
  });
62315
62952
  }
62316
- const healthy = await checkHealth(state.baseUrl);
62953
+ const healthy = await checkHealth(target.baseUrl);
62317
62954
  if (!healthy) {
62318
- throw new CliError("HEALTH_FAILED", `apply \u540E\u5065\u5EB7\u68C0\u67E5\u5931\u8D25: ${state.baseUrl}`);
62955
+ throw new CliError("HEALTH_FAILED", `apply \u540E\u5065\u5EB7\u68C0\u67E5\u5931\u8D25: ${target.baseUrl}`);
62319
62956
  }
62320
62957
  emitResult(ctx, {
62321
62958
  command: "config.apply",
62322
- data: { ok: true, baseUrl: state.baseUrl },
62959
+ data: { ok: true, baseUrl: target.baseUrl },
62323
62960
  md: {
62324
62961
  heading: "config.apply \xB7 \u2713",
62325
- data: `\u5DF2\u5E94\u7528: \`${state.baseUrl}\``
62962
+ data: `\u5DF2\u5E94\u7528: \`${target.baseUrl}\``
62326
62963
  },
62327
- text: `\u914D\u7F6E\u5DF2\u5E94\u7528: ${state.baseUrl}`
62964
+ text: `\u914D\u7F6E\u5DF2\u5E94\u7528: ${target.baseUrl}`
62328
62965
  });
62329
62966
  }
62330
62967
  });
@@ -62338,6 +62975,8 @@ async function handleProvider(args, flags) {
62338
62975
  return handleProviderShow(rest, flags);
62339
62976
  case "add":
62340
62977
  return handleProviderAdd(rest, flags);
62978
+ case "add-lan":
62979
+ return handleProviderAddLan(rest, flags);
62341
62980
  case "set":
62342
62981
  return handleProviderSet(rest, flags);
62343
62982
  case "remove":
@@ -62445,7 +63084,11 @@ async function dispatchConfig(group, rest, flags) {
62445
63084
  init_registry();
62446
63085
  var PROVIDER_TYPE_ENUM = ["openai-completions", "openai-responses", "anthropic-messages"];
62447
63086
  var COMMON_CONFIG_FLAG = { name: "config", type: "string", description: "\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84" };
62448
- var DRY_RUN_FLAG = { name: "dry-run", type: "boolean", description: "\u53EA\u9884\u89C8 diff\uFF0C\u4E0D\u5199\u5165" };
63087
+ var DRY_RUN_FLAG = {
63088
+ name: "dry-run",
63089
+ type: "boolean",
63090
+ description: "\u53EA\u9884\u89C8 diff\uFF0C\u4E0D\u5199\u5165"
63091
+ };
62449
63092
  function forward(prefix) {
62450
63093
  return async (args, flags) => cmdConfig([...prefix, ...args], flags);
62451
63094
  }
@@ -62462,10 +63105,7 @@ defineCommand({
62462
63105
  defineCommand({
62463
63106
  name: "config diff",
62464
63107
  summary: "\u4E0E\u5907\u4EFD\u6216\u6307\u5B9A\u6587\u4EF6\u5BF9\u6BD4",
62465
- flags: [
62466
- { name: "against", type: "string", description: "\u5907\u4EFD id \u6216\u8DEF\u5F84" },
62467
- COMMON_CONFIG_FLAG
62468
- ],
63108
+ flags: [{ name: "against", type: "string", description: "\u5907\u4EFD id \u6216\u8DEF\u5F84" }, COMMON_CONFIG_FLAG],
62469
63109
  supportsJson: true,
62470
63110
  handler: forward(["diff"])
62471
63111
  });
@@ -62579,6 +63219,26 @@ defineCommand({
62579
63219
  supportsJson: true,
62580
63220
  handler: forward(["provider", "add"])
62581
63221
  });
63222
+ defineCommand({
63223
+ name: "config provider add-lan",
63224
+ summary: "\u4ECE\u5C40\u57DF\u7F51\u5185\u5176\u4ED6 local-router \u55C5\u63A2\u5E76\u65B0\u589E provider",
63225
+ positionals: [{ name: "ip", required: true, description: "\u5BF9\u7AEF IP" }],
63226
+ flags: [
63227
+ {
63228
+ name: "type",
63229
+ type: "enum",
63230
+ enum: [...PROVIDER_TYPE_ENUM],
63231
+ required: true,
63232
+ description: "\u534F\u8BAE\u7C7B\u578B"
63233
+ },
63234
+ { name: "port", type: "string", description: "\u5BF9\u7AEF\u7AEF\u53E3\uFF08\u9ED8\u8BA4 4099\uFF09" },
63235
+ DRY_RUN_FLAG,
63236
+ COMMON_CONFIG_FLAG
63237
+ ],
63238
+ mutates: true,
63239
+ supportsJson: true,
63240
+ handler: forward(["provider", "add-lan"])
63241
+ });
62582
63242
  defineCommand({
62583
63243
  name: "config provider set",
62584
63244
  summary: "\u4FEE\u6539 provider \u5B57\u6BB5",
@@ -62664,10 +63324,7 @@ defineCommand({
62664
63324
  defineCommand({
62665
63325
  name: "config route list",
62666
63326
  summary: "\u5217\u51FA\u6240\u6709\u8DEF\u7531",
62667
- flags: [
62668
- { name: "entry", type: "string", description: "\u53EA\u770B\u67D0\u5165\u53E3" },
62669
- COMMON_CONFIG_FLAG
62670
- ],
63327
+ flags: [{ name: "entry", type: "string", description: "\u53EA\u770B\u67D0\u5165\u53E3" }, COMMON_CONFIG_FLAG],
62671
63328
  supportsJson: true,
62672
63329
  handler: forward(["route", "list"])
62673
63330
  });
@@ -62877,10 +63534,8 @@ defineSchemaCommand({
62877
63534
  // src/cli/handlers/chat.ts
62878
63535
  init_errors();
62879
63536
  init_output();
62880
- init_process();
62881
63537
  init_registry();
62882
- init_runtime();
62883
- import { createInterface as createInterface5 } from "readline/promises";
63538
+ import { createInterface as createInterface6 } from "readline/promises";
62884
63539
  defineSchemaCommand({
62885
63540
  name: "chat",
62886
63541
  summary: "\u4EA4\u4E92\u5F0F REPL\uFF08\u9ED8\u8BA4\u8D70 openai-completions\uFF0C\u6D41\u5F0F\uFF09",
@@ -62899,25 +63554,19 @@ defineSchemaCommand({
62899
63554
  { name: "no-stream", type: "boolean", description: "\u7981\u7528\u6D41\u5F0F\uFF08\u4E00\u6B21\u8FD4\u56DE\uFF09" }
62900
63555
  ],
62901
63556
  fn: async ({ values, ctx }) => {
62902
- await cleanupIfStale();
62903
- const state = readRuntimeState();
62904
- if (!state)
62905
- throw new CliError("SERVICE_NOT_RUNNING", "\u670D\u52A1\u672A\u8FD0\u884C");
62906
- if (!await checkHealth(state.baseUrl)) {
62907
- throw new CliError("HEALTH_FAILED", `\u670D\u52A1\u5065\u5EB7\u68C0\u67E5\u5931\u8D25: ${state.baseUrl}`);
62908
- }
62909
63557
  if (!process.stdin.isTTY) {
62910
63558
  throw new CliError("INTERACTIVE_REQUIRED", "chat \u9700\u8981 TTY", {
62911
63559
  hint: "\u7BA1\u9053\u573A\u666F\u8BF7\u7528 `local-router try`"
62912
63560
  });
62913
63561
  }
62914
- const baseUrl = state.baseUrl.replace(/\/+$/, "");
63562
+ const target = await requireTarget(ctx);
63563
+ const baseUrl = target.baseUrl.replace(/\/+$/, "");
62915
63564
  const url2 = `${baseUrl}/openai-completions/v1/chat/completions`;
62916
63565
  const messages = [];
62917
63566
  if (values.system)
62918
63567
  messages.push({ role: "system", content: values.system });
62919
63568
  emitDiagnostic(ctx, `chat \u2192 ${values.entry}/${values.model} \xB7 \u8F93\u5165 \`/exit\` \u6216 Ctrl+C \u9000\u51FA \xB7 /reset \u6E05\u7A7A\u4E0A\u4E0B\u6587`);
62920
- const rl = createInterface5({ input: process.stdin, output: process.stdout });
63569
+ const rl = createInterface6({ input: process.stdin, output: process.stdout });
62921
63570
  try {
62922
63571
  while (true) {
62923
63572
  const input = (await rl.question("\u203A ")).trim();
@@ -63367,7 +64016,6 @@ init_config_apply();
63367
64016
  init_errors();
63368
64017
  init_output();
63369
64018
  init_registry();
63370
- init_runtime();
63371
64019
  import { spawnSync } from "child_process";
63372
64020
  import { copyFileSync, existsSync as existsSync13, mkdtempSync, rmSync as rmSync4 } from "fs";
63373
64021
  import { tmpdir as tmpdir2 } from "os";
@@ -63447,7 +64095,10 @@ function platformOpen(target) {
63447
64095
  args = [target];
63448
64096
  }
63449
64097
  const r = spawnSync(cmd, args, { stdio: "ignore" });
63450
- return { ok: !r.error && (typeof r.status !== "number" || r.status === 0), cmd: `${cmd} ${args.join(" ")}` };
64098
+ return {
64099
+ ok: !r.error && (typeof r.status !== "number" || r.status === 0),
64100
+ cmd: `${cmd} ${args.join(" ")}`
64101
+ };
63451
64102
  }
63452
64103
  defineSchemaCommand({
63453
64104
  name: "open",
@@ -63461,7 +64112,7 @@ defineSchemaCommand({
63461
64112
  }
63462
64113
  ],
63463
64114
  flags: [{ name: "config", type: "string", description: "\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84" }],
63464
- fn: ({ positionals, values, ctx }) => {
64115
+ fn: async ({ positionals, values, ctx }) => {
63465
64116
  const target = positionals[0];
63466
64117
  if (!target) {
63467
64118
  throw new CliError("USAGE_ERROR", "\u7528\u6CD5: open <admin|docs|logs-dir|config>");
@@ -63469,13 +64120,8 @@ defineSchemaCommand({
63469
64120
  let url2;
63470
64121
  let label;
63471
64122
  if (target === "admin") {
63472
- const state = readRuntimeState();
63473
- if (!state) {
63474
- throw new CliError("SERVICE_NOT_RUNNING", "\u670D\u52A1\u672A\u8FD0\u884C\uFF0C\u65E0\u6CD5\u6253\u5F00 admin", {
63475
- hint: "`local-router start --daemon`"
63476
- });
63477
- }
63478
- url2 = `${state.baseUrl}/admin/`;
64123
+ const resolved = await resolveTarget(ctx.flags);
64124
+ url2 = `${resolved.baseUrl}/admin/`;
63479
64125
  label = "Web Admin";
63480
64126
  } else if (target === "docs") {
63481
64127
  url2 = "https://github.com/lakphy/local-router#readme";
@@ -63527,8 +64173,9 @@ defineSchemaCommand({
63527
64173
  { name: "config", type: "string", description: "\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84" }
63528
64174
  ],
63529
64175
  fn: ({ values, ctx }) => {
63530
- const state = readRuntimeState();
63531
- const baseUrl = state?.baseUrl ?? "http://127.0.0.1:4099";
64176
+ const guess = guessTargetUrl(ctx.flags);
64177
+ const baseUrl = guess.baseUrl;
64178
+ const running = guess.running;
63532
64179
  const entries = [
63533
64180
  {
63534
64181
  key: "OPENAI_BASE_URL",
@@ -63572,12 +64219,12 @@ defineSchemaCommand({
63572
64219
  data: {
63573
64220
  baseUrl,
63574
64221
  shell: values.shell,
63575
- running: !!state,
64222
+ running,
63576
64223
  entries,
63577
64224
  script
63578
64225
  },
63579
64226
  md: {
63580
- heading: `env \xB7 ${state ? "\u2713 \u8FD0\u884C\u4E2D" : "\u2717 \u672A\u8FD0\u884C\uFF08\u4F7F\u7528\u9ED8\u8BA4 4099\uFF09"}`,
64227
+ heading: `env \xB7 ${running ? "\u2713 \u8FD0\u884C\u4E2D" : "\u2717 \u672A\u8FD0\u884C\uFF08\u4F7F\u7528\u9ED8\u8BA4 4099\uFF09"}`,
63581
64228
  meta: [`baseUrl: \`${baseUrl}\``],
63582
64229
  data: [
63583
64230
  renderKv(entries.map((e) => ({ key: e.key, value: e.value }))),
@@ -63877,14 +64524,8 @@ defineSchemaCommand({
63877
64524
  ],
63878
64525
  fn: async ({ values, ctx }) => {
63879
64526
  const { entry, model, prompt, stream: streamMode, timeout: timeoutSec } = values;
63880
- await cleanupIfStale();
63881
- const state = readRuntimeState();
63882
- if (!state) {
63883
- throw new CliError("SERVICE_NOT_RUNNING", "\u670D\u52A1\u672A\u8FD0\u884C", {
63884
- hint: "`local-router start --daemon`"
63885
- });
63886
- }
63887
- const payload = buildTryPayload(entry, model, prompt, state.baseUrl);
64527
+ const target = await requireTarget(ctx);
64528
+ const payload = buildTryPayload(entry, model, prompt, target.baseUrl);
63888
64529
  const controller = new AbortController;
63889
64530
  const timer = setTimeout(() => controller.abort(), timeoutSec * 1000);
63890
64531
  const startedAt = Date.now();
@@ -64022,35 +64663,6 @@ defineSchemaCommand({
64022
64663
  import { readFileSync as readFileSync11 } from "fs";
64023
64664
  import { fileURLToPath as fileURLToPath2 } from "url";
64024
64665
 
64025
- // src/cli/asset-paths.ts
64026
- async function findExisting(...candidates) {
64027
- for (const url2 of candidates) {
64028
- try {
64029
- const exists = await Bun.file(url2).exists();
64030
- if (exists)
64031
- return url2;
64032
- } catch {}
64033
- }
64034
- return null;
64035
- }
64036
- async function readPackageJson() {
64037
- const url2 = await findExisting(new URL("../../package.json", import.meta.url), new URL("../package.json", import.meta.url), new URL("../../../package.json", import.meta.url));
64038
- if (!url2)
64039
- return null;
64040
- try {
64041
- return await Bun.file(url2).json();
64042
- } catch {
64043
- return null;
64044
- }
64045
- }
64046
- async function findConfigSchemaUrl() {
64047
- return findExisting(new URL("../../config.schema.json", import.meta.url), new URL("../config.schema.json", import.meta.url), new URL("../../../config.schema.json", import.meta.url));
64048
- }
64049
- async function readVersionString() {
64050
- const pkg = await readPackageJson();
64051
- return typeof pkg?.version === "string" ? pkg.version : "unknown";
64052
- }
64053
-
64054
64666
  // src/cli/error-docs.ts
64055
64667
  var ERROR_DOCS = {
64056
64668
  USAGE_ERROR: {
@@ -64143,6 +64755,16 @@ var ERROR_DOCS = {
64143
64755
  cause: "\u547D\u4EE4\u7F3A\u5C11\u5FC5\u586B flag\uFF0C\u672C\u5E94\u5F39\u51FA\u9009\u62E9\u5668\u4F46\u5F53\u524D stdin \u4E0D\u662F TTY \u6216\u663E\u5F0F `--no-interactive`\u3002",
64144
64756
  fix: "\u5728\u9519\u8BEF\u7684 `details` \u91CC\u67E5\u770B\u5019\u9009\u9879\uFF0C\u663E\u5F0F\u8865 `--provider/--model` \u7B49\u3002"
64145
64757
  },
64758
+ TARGET_NOT_FOUND: {
64759
+ summary: "\u627E\u4E0D\u5230\u53EF\u8FDE\u63A5\u7684 local-router",
64760
+ cause: "\u9ED8\u8BA4\u7AEF\u53E3 4099 \u4E0A\u6CA1\u6709 local-router\uFF0COS \u8FDB\u7A0B\u679A\u4E3E\u4E5F\u672A\u53D1\u73B0\u5176\u5B83\u5B9E\u4F8B\u3002",
64761
+ fix: "`local-router start` \u542F\u52A8\uFF1B\u6216\u7528 `--port <port>` / `--url <url>` \u6307\u5B9A\u76EE\u6807\u3002"
64762
+ },
64763
+ TARGET_UNREACHABLE: {
64764
+ summary: "\u6307\u5B9A\u7684\u76EE\u6807\u65E0\u6CD5\u8FDE\u901A",
64765
+ cause: "`--port` / `--url`\uFF08\u6216\u73AF\u5883\u53D8\u91CF\uFF09\u6307\u5411\u7684\u5730\u5740\u6CA1\u6709\u54CD\u5E94 `/api/health` \u6216\u4E0D\u662F local-router\u3002",
64766
+ fix: "\u786E\u8BA4\u76EE\u6807\u7AEF\u53E3/\u5730\u5740\u6B63\u786E\u4E14 local-router \u6B63\u5728\u8BE5\u5730\u5740\u76D1\u542C\u3002"
64767
+ },
64146
64768
  UNKNOWN_ERROR: {
64147
64769
  summary: "\u672A\u77E5\u9519\u8BEF",
64148
64770
  cause: "\u6CA1\u6709\u5339\u914D\u5230\u4EFB\u4F55 CliError \u7C7B\u578B\u3002",
@@ -64589,13 +65211,13 @@ defineSchemaCommand({
64589
65211
 
64590
65212
  // src/cli/handlers/lifecycle.ts
64591
65213
  init_config();
64592
- import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
64593
- import { setTimeout as sleep3 } from "timers/promises";
64594
65214
  init_errors();
64595
65215
  init_output();
64596
65216
  init_process();
64597
65217
  init_registry();
64598
65218
  init_runtime();
65219
+ import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
65220
+ import { setTimeout as sleep3 } from "timers/promises";
64599
65221
 
64600
65222
  // src/cli/wait.ts
64601
65223
  init_errors();
@@ -64814,34 +65436,34 @@ defineSchemaCommand({
64814
65436
  fn: async ({ values, ctx }) => {
64815
65437
  const retry = Math.max(1, values.retry || 1);
64816
65438
  const interval = Math.max(0, values["retry-interval"] || 1);
64817
- await cleanupIfStale();
64818
- const state = readRuntimeState();
64819
- if (!state) {
64820
- throw new CliError("SERVICE_NOT_RUNNING", "\u670D\u52A1\u672A\u8FD0\u884C", {
64821
- hint: "\u542F\u52A8: `local-router start --daemon`"
64822
- });
64823
- }
65439
+ const target = guessTargetUrl(ctx.flags);
65440
+ const baseUrl = target.baseUrl;
64824
65441
  let ok = false;
64825
65442
  for (let i = 0;i < retry; i++) {
64826
- ok = await checkHealth(state.baseUrl);
65443
+ ok = await checkHealth(baseUrl);
64827
65444
  if (ok)
64828
65445
  break;
64829
65446
  if (i < retry - 1)
64830
65447
  await sleep3(interval * 1000);
64831
65448
  }
64832
65449
  if (!ok) {
64833
- throw new CliError("HEALTH_FAILED", `\u5065\u5EB7\u68C0\u67E5\u5931\u8D25: ${state.baseUrl}/api/health`, {
64834
- details: { baseUrl: state.baseUrl, retries: retry }
65450
+ if (!target.running && !ctx.flags.target) {
65451
+ throw new CliError("SERVICE_NOT_RUNNING", "\u670D\u52A1\u672A\u8FD0\u884C", {
65452
+ hint: "\u542F\u52A8: `local-router start --daemon`"
65453
+ });
65454
+ }
65455
+ throw new CliError("HEALTH_FAILED", `\u5065\u5EB7\u68C0\u67E5\u5931\u8D25: ${baseUrl}/api/health`, {
65456
+ details: { baseUrl, retries: retry }
64835
65457
  });
64836
65458
  }
64837
65459
  emitResult(ctx, {
64838
65460
  command: "health",
64839
- data: { ok: true, baseUrl: state.baseUrl },
65461
+ data: { ok: true, baseUrl },
64840
65462
  md: {
64841
65463
  heading: "health \xB7 \u2713 ok",
64842
- meta: [`\u5730\u5740 ${state.baseUrl}`]
65464
+ meta: [`\u5730\u5740 ${baseUrl}`]
64843
65465
  },
64844
- text: `\u5065\u5EB7\u68C0\u67E5\u901A\u8FC7: ${state.baseUrl}`
65466
+ text: `\u5065\u5EB7\u68C0\u67E5\u901A\u8FC7: ${baseUrl}`
64845
65467
  });
64846
65468
  }
64847
65469
  });
@@ -64955,22 +65577,10 @@ defineSchemaCommand({
64955
65577
  // src/cli/handlers/logs.ts
64956
65578
  init_errors();
64957
65579
  init_output();
64958
- init_process();
64959
65580
  init_registry();
64960
- init_runtime();
64961
- async function requireRunning() {
64962
- await cleanupIfStale();
64963
- const state = readRuntimeState();
64964
- if (!state) {
64965
- throw new CliError("SERVICE_NOT_RUNNING", "\u65E5\u5FD7\u67E5\u8BE2\u9700\u8981\u670D\u52A1\u8FD0\u884C", {
64966
- hint: "`local-router start --daemon`"
64967
- });
64968
- }
64969
- const ok = await checkHealth(state.baseUrl);
64970
- if (!ok) {
64971
- throw new CliError("HEALTH_FAILED", `\u670D\u52A1\u5065\u5EB7\u68C0\u67E5\u5931\u8D25: ${state.baseUrl}`);
64972
- }
64973
- return { baseUrl: state.baseUrl };
65581
+ async function requireRunning(ctx) {
65582
+ const t = await requireTarget(ctx);
65583
+ return { baseUrl: t.baseUrl };
64974
65584
  }
64975
65585
  async function fetchJson(url2, init) {
64976
65586
  const res = await fetch(url2, init);
@@ -65018,7 +65628,7 @@ defineSchemaCommand({
65018
65628
  { name: "cursor", type: "string", description: "\u5206\u9875\u6E38\u6807" }
65019
65629
  ],
65020
65630
  fn: async ({ values, ctx }) => {
65021
- const { baseUrl } = await requireRunning();
65631
+ const { baseUrl } = await requireRunning(ctx);
65022
65632
  const params = new URLSearchParams;
65023
65633
  const set2 = (k, v) => {
65024
65634
  if (v !== undefined && v !== null && v !== "" && v !== false) {
@@ -65083,7 +65693,7 @@ defineSchemaCommand({
65083
65693
  const id = positionals[0];
65084
65694
  if (!id)
65085
65695
  throw new CliError("USAGE_ERROR", "\u7528\u6CD5: logs event <id>");
65086
- const { baseUrl } = await requireRunning();
65696
+ const { baseUrl } = await requireRunning(ctx);
65087
65697
  const params = new URLSearchParams;
65088
65698
  if (values["include-stream"])
65089
65699
  params.set("includeStream", "true");
@@ -65129,7 +65739,7 @@ defineSchemaCommand({
65129
65739
  }
65130
65740
  ],
65131
65741
  fn: async ({ values, ctx }) => {
65132
- const { baseUrl } = await requireRunning();
65742
+ const { baseUrl } = await requireRunning(ctx);
65133
65743
  const w = values.window ?? "24h";
65134
65744
  const { status, json: json3 } = await fetchJson(`${baseUrl}/api/logs/events?window=${w}&hasError=true&limit=1&sort=time_desc`);
65135
65745
  if (status !== 200)
@@ -65181,7 +65791,7 @@ defineSchemaCommand({
65181
65791
  }
65182
65792
  ],
65183
65793
  fn: async ({ values, ctx }) => {
65184
- const { baseUrl } = await requireRunning();
65794
+ const { baseUrl } = await requireRunning(ctx);
65185
65795
  const w = values.window ?? "24h";
65186
65796
  const { status, json: json3 } = await fetchJson(`${baseUrl}/api/metrics/logs?window=${w}`);
65187
65797
  if (status !== 200) {
@@ -65226,7 +65836,7 @@ defineSchemaCommand({
65226
65836
  supportsJson: true,
65227
65837
  requiresRunning: true,
65228
65838
  fn: async ({ ctx }) => {
65229
- const { baseUrl } = await requireRunning();
65839
+ const { baseUrl } = await requireRunning(ctx);
65230
65840
  const { status, json: json3 } = await fetchJson(`${baseUrl}/api/logs/storage`);
65231
65841
  if (status !== 200) {
65232
65842
  throw new CliError("UNKNOWN_ERROR", `\u83B7\u53D6 storage \u5931\u8D25: ${status}`, { details: json3 });
@@ -65255,7 +65865,7 @@ defineSchemaCommand({
65255
65865
  { name: "limit", type: "number", default: 50, description: "\u6700\u5927 session \u6570" }
65256
65866
  ],
65257
65867
  fn: async ({ values, ctx }) => {
65258
- const { baseUrl } = await requireRunning();
65868
+ const { baseUrl } = await requireRunning(ctx);
65259
65869
  const params = new URLSearchParams;
65260
65870
  params.set("window", values.window ?? "24h");
65261
65871
  if (values.user)
@@ -65269,7 +65879,10 @@ defineSchemaCommand({
65269
65879
  emitResult(ctx, {
65270
65880
  command: "logs.sessions",
65271
65881
  data: json3,
65272
- md: { heading: "logs.sessions", data: renderCodeBlock(JSON.stringify(json3, null, 2), "json") }
65882
+ md: {
65883
+ heading: "logs.sessions",
65884
+ data: renderCodeBlock(JSON.stringify(json3, null, 2), "json")
65885
+ }
65273
65886
  });
65274
65887
  }
65275
65888
  });
@@ -65279,7 +65892,7 @@ defineSchemaCommand({
65279
65892
  supportsJson: true,
65280
65893
  requiresRunning: true,
65281
65894
  fn: async ({ ctx }) => {
65282
- const { baseUrl } = await requireRunning();
65895
+ const { baseUrl } = await requireRunning(ctx);
65283
65896
  const stream = startStream(ctx, "logs.tail");
65284
65897
  const res = await fetch(`${baseUrl}/api/logs/tail`, {
65285
65898
  headers: { accept: "text/event-stream" }
@@ -65341,8 +65954,8 @@ defineSchemaCommand({
65341
65954
  { name: "from", type: "string", description: "ISO \u8D77\u70B9" },
65342
65955
  { name: "to", type: "string", description: "ISO \u7EC8\u70B9" }
65343
65956
  ],
65344
- fn: async ({ values }) => {
65345
- const { baseUrl } = await requireRunning();
65957
+ fn: async ({ values, ctx }) => {
65958
+ const { baseUrl } = await requireRunning(ctx);
65346
65959
  const params = new URLSearchParams;
65347
65960
  params.set("format", values.format ?? "jsonl");
65348
65961
  params.set("window", values.window ?? "24h");
@@ -65373,7 +65986,7 @@ defineSchemaCommand({
65373
65986
  const id = positionals[0];
65374
65987
  if (!id)
65375
65988
  throw new CliError("USAGE_ERROR", "\u7528\u6CD5: logs replay <event-id>");
65376
- const { baseUrl } = await requireRunning();
65989
+ const { baseUrl } = await requireRunning(ctx);
65377
65990
  const detailRes = await fetchJson(`${baseUrl}/api/logs/events/${encodeURIComponent(id)}`);
65378
65991
  if (detailRes.status === 404)
65379
65992
  throw new CliError("ROUTE_NOT_FOUND", `\u4E8B\u4EF6\u4E0D\u5B58\u5728: ${id}`);
@@ -65426,7 +66039,6 @@ defineSchemaCommand({
65426
66039
  init_config();
65427
66040
  init_errors();
65428
66041
  init_output();
65429
- init_process();
65430
66042
  init_registry();
65431
66043
  init_runtime();
65432
66044
  import { existsSync as existsSync16, rmSync as rmSync5 } from "fs";
@@ -65441,15 +66053,9 @@ async function fetchJson2(url2) {
65441
66053
  } catch {}
65442
66054
  return { status: res.status, json: json3 };
65443
66055
  }
65444
- async function requireBaseUrl() {
65445
- await cleanupIfStale();
65446
- const state = readRuntimeState();
65447
- if (!state)
65448
- throw new CliError("SERVICE_NOT_RUNNING", "\u670D\u52A1\u672A\u8FD0\u884C");
65449
- if (!await checkHealth(state.baseUrl)) {
65450
- throw new CliError("HEALTH_FAILED", `\u670D\u52A1\u5065\u5EB7\u68C0\u67E5\u5931\u8D25: ${state.baseUrl}`);
65451
- }
65452
- return state.baseUrl;
66056
+ async function requireBaseUrl(ctx) {
66057
+ const t = await requireTarget(ctx);
66058
+ return t.baseUrl;
65453
66059
  }
65454
66060
  defineSchemaCommand({
65455
66061
  name: "logs tokens",
@@ -65466,7 +66072,7 @@ defineSchemaCommand({
65466
66072
  }
65467
66073
  ],
65468
66074
  fn: async ({ values, ctx }) => {
65469
- const baseUrl = await requireBaseUrl();
66075
+ const baseUrl = await requireBaseUrl(ctx);
65470
66076
  const { status, json: json3 } = await fetchJson2(`${baseUrl}/api/logs/events?window=${values.window}&limit=1`);
65471
66077
  if (status !== 200) {
65472
66078
  throw new CliError("UNKNOWN_ERROR", `\u67E5\u8BE2\u5931\u8D25: ${status}`, { details: json3 });
@@ -65531,7 +66137,7 @@ defineSchemaCommand({
65531
66137
  }
65532
66138
  ],
65533
66139
  fn: async ({ values, ctx }) => {
65534
- const baseUrl = await requireBaseUrl();
66140
+ const baseUrl = await requireBaseUrl(ctx);
65535
66141
  let rateTable = {};
65536
66142
  if (values["rate-table"]) {
65537
66143
  try {
@@ -66021,6 +66627,59 @@ defineSchemaCommand({
66021
66627
  }
66022
66628
  });
66023
66629
 
66630
+ // src/cli/handlers/target.ts
66631
+ init_output();
66632
+ init_registry();
66633
+ var SOURCE_LABEL = {
66634
+ flag: "\u547D\u4EE4\u884C --port/--url/--host",
66635
+ env: "\u73AF\u5883\u53D8\u91CF LOCAL_ROUTER_URL/PORT",
66636
+ runtime: "\u672C\u673A daemon (status.json)",
66637
+ default: "\u9ED8\u8BA4\u7AEF\u53E3 4099",
66638
+ discovered: "OS \u8FDB\u7A0B\u679A\u4E3E\u53D1\u73B0",
66639
+ prompt: "\u4EA4\u4E92\u8F93\u5165"
66640
+ };
66641
+ defineSchemaCommand({
66642
+ name: "target",
66643
+ summary: "\u663E\u793A\u5F53\u524D\u89E3\u6790\u5230\u7684 local-router \u76EE\u6807\uFF08\u7AEF\u53E3/\u6765\u6E90/\u7248\u672C\uFF09",
66644
+ supportsJson: true,
66645
+ requiresRunning: false,
66646
+ flags: [],
66647
+ fn: async ({ ctx }) => {
66648
+ const t = await resolveTarget(ctx.flags);
66649
+ const candidates = ctx.flags.verbose ? await discoverLocalRouters() : [];
66650
+ emitResult(ctx, {
66651
+ command: "target",
66652
+ data: {
66653
+ baseUrl: t.baseUrl,
66654
+ host: t.host,
66655
+ port: t.port,
66656
+ version: t.version ?? null,
66657
+ source: t.source,
66658
+ candidates: ctx.flags.verbose ? candidates.map((c) => ({ port: c.port, version: c.version ?? null })) : undefined
66659
+ },
66660
+ md: {
66661
+ heading: `target \xB7 ${t.host}:${t.port}${t.version ? ` (v${t.version})` : ""}`,
66662
+ meta: [`\u6765\u6E90: ${SOURCE_LABEL[t.source] ?? t.source}`],
66663
+ data: [
66664
+ renderKv([
66665
+ { key: "baseUrl", value: t.baseUrl },
66666
+ { key: "host", value: t.host },
66667
+ { key: "port", value: t.port },
66668
+ { key: "version", value: t.version ?? "unknown" },
66669
+ { key: "source", value: t.source }
66670
+ ]),
66671
+ ctx.flags.verbose ? `**\u53D1\u73B0\u7684\u5B9E\u4F8B**
66672
+
66673
+ ${candidates.length > 0 ? renderTable(["port", "version"], candidates.map((c) => [String(c.port), c.version ?? "?"])) : "\uFF08\u672A\u53D1\u73B0\u5176\u5B83\u5B9E\u4F8B\uFF09"}` : ""
66674
+ ].filter(Boolean).join(`
66675
+
66676
+ `)
66677
+ },
66678
+ text: t.baseUrl
66679
+ });
66680
+ }
66681
+ });
66682
+
66024
66683
  // src/cli.ts
66025
66684
  init_output();
66026
66685
  init_parse_args();