swixter 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli/index.js +307 -71
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -13999,7 +13999,7 @@ var CONFIG_VERSION = "2.0.0", EXPORT_VERSION = "1.0.0";
13999
13999
  var init_versions2 = () => {};
14000
14000
 
14001
14001
  // src/constants/meta.ts
14002
- var APP_VERSION = "0.1.4";
14002
+ var APP_VERSION = "0.1.6";
14003
14003
  var init_meta = () => {};
14004
14004
 
14005
14005
  // src/constants/install.ts
@@ -14359,7 +14359,8 @@ var init_presets = __esm(() => {
14359
14359
  id: "deepseek",
14360
14360
  name: "DeepSeek",
14361
14361
  displayName: "DeepSeek",
14362
- baseURL: "https://api.deepseek.com",
14362
+ baseURL: "https://api.deepseek.com/anthropic",
14363
+ baseURLChat: "https://api.deepseek.com",
14363
14364
  defaultModels: [
14364
14365
  "deepseek-chat",
14365
14366
  "deepseek-coder"
@@ -18914,7 +18915,7 @@ var init_env_key_helper = __esm(() => {
18914
18915
  });
18915
18916
 
18916
18917
  // src/adapters/codex.ts
18917
- import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
18918
+ import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4, unlink } from "node:fs/promises";
18918
18919
  import { existsSync as existsSync4 } from "node:fs";
18919
18920
  import { homedir as homedir2 } from "node:os";
18920
18921
  import { join as join3, dirname as dirname4 } from "node:path";
@@ -18922,8 +18923,10 @@ import { join as join3, dirname as dirname4 } from "node:path";
18922
18923
  class CodexAdapter {
18923
18924
  name = "codex";
18924
18925
  configPath;
18926
+ authPath;
18925
18927
  constructor() {
18926
18928
  this.configPath = join3(homedir2(), ".codex", "config.toml");
18929
+ this.authPath = join3(homedir2(), ".codex", "auth.json");
18927
18930
  }
18928
18931
  async apply(profile) {
18929
18932
  try {
@@ -18961,6 +18964,7 @@ class CodexAdapter {
18961
18964
  config2.model_provider = providerName;
18962
18965
  const tomlContent = stringify(config2);
18963
18966
  await writeFile4(this.configPath, tomlContent, "utf-8");
18967
+ await this.writeAuthJson(profile);
18964
18968
  } catch (error46) {
18965
18969
  throw new Error(`Failed to apply Codex configuration: ${error46 instanceof Error ? error46.message : String(error46)}`);
18966
18970
  }
@@ -18983,6 +18987,22 @@ class CodexAdapter {
18983
18987
  if (!config2.model_providers || !config2.model_providers[providerName]) {
18984
18988
  return false;
18985
18989
  }
18990
+ if (profile.apiKey) {
18991
+ const envKey = await getEnvKey(profile);
18992
+ if (existsSync4(this.authPath)) {
18993
+ try {
18994
+ const authContent = await readFile4(this.authPath, "utf-8");
18995
+ const auth = JSON.parse(authContent);
18996
+ if (auth[envKey] !== profile.apiKey) {
18997
+ return false;
18998
+ }
18999
+ } catch {
19000
+ return false;
19001
+ }
19002
+ } else {
19003
+ return false;
19004
+ }
19005
+ }
18986
19006
  return true;
18987
19007
  } catch (error46) {
18988
19008
  return false;
@@ -18993,7 +19013,8 @@ class CodexAdapter {
18993
19013
  const providerTable = {
18994
19014
  name: preset.displayName,
18995
19015
  base_url: profile.baseURL || baseUrl,
18996
- wire_api: preset.wire_api || "chat"
19016
+ wire_api: "responses",
19017
+ requires_openai_auth: true
18997
19018
  };
18998
19019
  providerTable.env_key = await getEnvKey(profile);
18999
19020
  if (preset.headers) {
@@ -19016,6 +19037,31 @@ class CodexAdapter {
19016
19037
  }
19017
19038
  return profileTable;
19018
19039
  }
19040
+ async writeAuthJson(profile) {
19041
+ const envKey = await getEnvKey(profile);
19042
+ let auth = {};
19043
+ if (existsSync4(this.authPath)) {
19044
+ try {
19045
+ const content = await readFile4(this.authPath, "utf-8");
19046
+ const parsed = JSON.parse(content);
19047
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
19048
+ auth = parsed;
19049
+ }
19050
+ } catch {
19051
+ auth = {};
19052
+ }
19053
+ }
19054
+ if (profile.apiKey) {
19055
+ auth[envKey] = profile.apiKey;
19056
+ } else {
19057
+ delete auth[envKey];
19058
+ }
19059
+ if (Object.keys(auth).length > 0) {
19060
+ await writeFile4(this.authPath, JSON.stringify(auth, null, 2), "utf-8");
19061
+ } else if (existsSync4(this.authPath)) {
19062
+ await unlink(this.authPath);
19063
+ }
19064
+ }
19019
19065
  async getEnvExportCommands(profile) {
19020
19066
  const commands = await getEnvExportCommands(profile);
19021
19067
  const modelValue = getOpenAIModel(profile);
@@ -19034,7 +19080,9 @@ class CodexAdapter {
19034
19080
  const providerKey = `swixter-${profileName}`;
19035
19081
  const profileKey = `swixter-${profileName}`;
19036
19082
  let modified = false;
19083
+ let envKeyToRemove;
19037
19084
  if (config2.model_providers && config2.model_providers[providerKey]) {
19085
+ envKeyToRemove = config2.model_providers[providerKey].env_key;
19038
19086
  delete config2.model_providers[providerKey];
19039
19087
  modified = true;
19040
19088
  }
@@ -19051,6 +19099,14 @@ class CodexAdapter {
19051
19099
  const tomlContent = stringify(config2);
19052
19100
  await writeFile4(this.configPath, tomlContent, "utf-8");
19053
19101
  }
19102
+ if (envKeyToRemove && existsSync4(this.authPath)) {
19103
+ try {
19104
+ const authContent = await readFile4(this.authPath, "utf-8");
19105
+ const auth = JSON.parse(authContent);
19106
+ delete auth[envKeyToRemove];
19107
+ await writeFile4(this.authPath, JSON.stringify(auth, null, 2), "utf-8");
19108
+ } catch {}
19109
+ }
19054
19110
  } catch (error46) {
19055
19111
  console.warn(`Failed to remove profile from Codex config: ${error46}`);
19056
19112
  }
@@ -26527,18 +26583,18 @@ var init_client = __esm(() => {
26527
26583
  });
26528
26584
 
26529
26585
  // src/auth/token.ts
26530
- import { existsSync as existsSync12 } from "node:fs";
26531
- import { readFile as readFile9, writeFile as writeFile8, unlink as unlink2 } from "node:fs/promises";
26532
- import { join as join10 } from "node:path";
26586
+ import { existsSync as existsSync13 } from "node:fs";
26587
+ import { readFile as readFile10, writeFile as writeFile9, unlink as unlink4 } from "node:fs/promises";
26588
+ import { join as join11 } from "node:path";
26533
26589
  function getAuthFilePath() {
26534
- return join10(getConfigDir("swixter"), AUTH_FILE);
26590
+ return join11(getConfigDir("swixter"), AUTH_FILE);
26535
26591
  }
26536
26592
  async function loadAuthState() {
26537
26593
  const authPath = getAuthFilePath();
26538
- if (!existsSync12(authPath))
26594
+ if (!existsSync13(authPath))
26539
26595
  return null;
26540
26596
  try {
26541
- const content = await readFile9(authPath, "utf-8");
26597
+ const content = await readFile10(authPath, "utf-8");
26542
26598
  return JSON.parse(content);
26543
26599
  } catch {
26544
26600
  return null;
@@ -26546,12 +26602,12 @@ async function loadAuthState() {
26546
26602
  }
26547
26603
  async function saveAuthState(state) {
26548
26604
  const authPath = getAuthFilePath();
26549
- await writeFile8(authPath, JSON.stringify(state, null, 2), { mode: 384, encoding: "utf-8" });
26605
+ await writeFile9(authPath, JSON.stringify(state, null, 2), { mode: 384, encoding: "utf-8" });
26550
26606
  }
26551
26607
  async function clearAuthState() {
26552
26608
  const authPath = getAuthFilePath();
26553
- if (existsSync12(authPath)) {
26554
- await unlink2(authPath);
26609
+ if (existsSync13(authPath)) {
26610
+ await unlink4(authPath);
26555
26611
  }
26556
26612
  }
26557
26613
  function isExpired(expiresAt) {
@@ -27364,7 +27420,7 @@ __export(exports_proxy, {
27364
27420
  buildCoderProxyEnv: () => buildCoderProxyEnv,
27365
27421
  buildClaudeProxyEnv: () => buildClaudeProxyEnv
27366
27422
  });
27367
- import { spawn as spawn3 } from "node:child_process";
27423
+ import { spawn as spawn4 } from "node:child_process";
27368
27424
  function resolveProxyRuntimeBinding(input) {
27369
27425
  const occupiedPorts = new Set(input.allInstances.filter((s) => s.running).map((s) => s.port));
27370
27426
  if (input.requestedPort) {
@@ -27558,7 +27614,7 @@ async function cmdStartDaemon(config2) {
27558
27614
  if (config2.groupName) {
27559
27615
  args.push("--group", config2.groupName);
27560
27616
  }
27561
- const child = spawn3(process.execPath, [process.argv[1], ...args], {
27617
+ const child = spawn4(process.execPath, [process.argv[1], ...args], {
27562
27618
  detached: true,
27563
27619
  stdio: "ignore"
27564
27620
  });
@@ -27670,7 +27726,7 @@ async function cmdRun4(args) {
27670
27726
  });
27671
27727
  return;
27672
27728
  }
27673
- const child = spawn3(coder, coderArgs.slice(1), {
27729
+ const child = spawn4(coder, coderArgs.slice(1), {
27674
27730
  env,
27675
27731
  stdio: "inherit"
27676
27732
  });
@@ -28654,8 +28710,8 @@ async function cmdCreateInteractive3() {
28654
28710
  console.log();
28655
28711
  console.log(import_picocolors10.default.bold(import_picocolors10.default.cyan(PROMPTS.createProfile(CODER_CONFIG3.displayName))));
28656
28712
  console.log();
28657
- const { getProvidersByWireApi: getProvidersByWireApi2 } = await Promise.resolve().then(() => (init_presets(), exports_presets));
28658
- const presets = await getProvidersByWireApi2("chat");
28713
+ const { getAllPresets: getAllPresets2 } = await Promise.resolve().then(() => (init_presets(), exports_presets));
28714
+ const presets = await getAllPresets2();
28659
28715
  const name = await he({
28660
28716
  message: PROMPTS.configName,
28661
28717
  placeholder: DEFAULT_PLACEHOLDERS.configName,
@@ -28823,12 +28879,6 @@ async function cmdCreateQuiet3(params) {
28823
28879
  console.log(import_picocolors10.default.dim("Run 'swixter providers' to see all supported providers"));
28824
28880
  process.exit(1);
28825
28881
  }
28826
- if (preset.wire_api !== "chat") {
28827
- console.log(import_picocolors10.default.red(`Error: Provider "${preset.displayName}" is not compatible with ${CODER_CONFIG3.displayName}`));
28828
- console.log(import_picocolors10.default.dim(`${CODER_CONFIG3.displayName} only supports OpenAI-compatible providers (chat API).`));
28829
- console.log(import_picocolors10.default.dim("Use 'ollama' or 'custom' provider instead."));
28830
- process.exit(1);
28831
- }
28832
28882
  if (params.provider !== "ollama" && !params["api-key"]) {
28833
28883
  console.log(import_picocolors10.default.red("Error: This provider requires --api-key parameter"));
28834
28884
  process.exit(1);
@@ -28992,8 +29042,8 @@ async function cmdEdit3(profileName) {
28992
29042
  console.log();
28993
29043
  console.log(import_picocolors10.default.bold(import_picocolors10.default.cyan(`Edit profile: ${profileName}`)));
28994
29044
  console.log();
28995
- const { getProvidersByWireApi: getProvidersByWireApi2 } = await Promise.resolve().then(() => (init_presets(), exports_presets));
28996
- const presets = await getProvidersByWireApi2("chat");
29045
+ const { getAllPresets: getAllPresets2 } = await Promise.resolve().then(() => (init_presets(), exports_presets));
29046
+ const presets = await getAllPresets2();
28997
29047
  const currentPreset = await getPresetByIdAsync(profile.providerId);
28998
29048
  const shouldChangeProvider = await ye({
28999
29049
  message: `Change provider? Current: ${currentPreset?.displayName}`,
@@ -29183,19 +29233,10 @@ async function cmdApply3() {
29183
29233
  console.log(` Provider: ${import_picocolors10.default.yellow(preset?.displayName)}`);
29184
29234
  console.log(` Config file: ${import_picocolors10.default.dim(adapter.configPath)}`);
29185
29235
  console.log();
29186
- if (adapter.name === "codex" && "getEnvExportCommands" in adapter) {
29187
- const envCommands = await adapter.getEnvExportCommands(profile);
29188
- if (envCommands.length > 0) {
29189
- console.log(import_picocolors10.default.bold("To use this profile, set environment variables:"));
29190
- console.log();
29191
- envCommands.forEach((cmd) => {
29192
- console.log(` ${import_picocolors10.default.green(cmd)}`);
29193
- });
29194
- console.log();
29195
- console.log(import_picocolors10.default.dim(`Then run: ${import_picocolors10.default.cyan("codex")}`));
29196
- console.log();
29197
- }
29198
- }
29236
+ console.log(import_picocolors10.default.bold("Run Codex now: ") + import_picocolors10.default.cyan("codex"));
29237
+ console.log();
29238
+ console.log(import_picocolors10.default.dim("Environment variables are automatically managed via auth.json."));
29239
+ console.log();
29199
29240
  } else {
29200
29241
  console.log(import_picocolors10.default.yellow("⚠ Profile written, but verification failed"));
29201
29242
  console.log(import_picocolors10.default.dim("Please check config file format"));
@@ -29426,6 +29467,8 @@ async function cmdUpdate3(args) {
29426
29467
 
29427
29468
  // src/cli/ui.ts
29428
29469
  var import_picocolors12 = __toESM(require_picocolors(), 1);
29470
+ import { spawn as spawn3 } from "node:child_process";
29471
+ import { open } from "node:fs/promises";
29429
29472
 
29430
29473
  // src/server/index.ts
29431
29474
  import http2 from "node:http";
@@ -30109,7 +30152,7 @@ init_versions2();
30109
30152
  init_paths();
30110
30153
  init_export();
30111
30154
  import { existsSync as existsSync9, statSync as statSync2 } from "node:fs";
30112
- import { readFile as readFile8, writeFile as writeFile7, unlink } from "node:fs/promises";
30155
+ import { readFile as readFile8, writeFile as writeFile7, unlink as unlink2 } from "node:fs/promises";
30113
30156
  import { join as join8 } from "node:path";
30114
30157
  async function getVersion(req, res) {
30115
30158
  sendJson(res, {
@@ -30172,7 +30215,7 @@ async function exportConfigFile(req, res) {
30172
30215
  res.end(content);
30173
30216
  } finally {
30174
30217
  try {
30175
- await unlink(tempPath);
30218
+ await unlink2(tempPath);
30176
30219
  } catch {}
30177
30220
  }
30178
30221
  } catch (error46) {
@@ -30194,7 +30237,7 @@ async function importConfigFile(req, res) {
30194
30237
  sendJson(res, { success: true, ...result });
30195
30238
  } finally {
30196
30239
  try {
30197
- await unlink(tempPath);
30240
+ await unlink2(tempPath);
30198
30241
  } catch {}
30199
30242
  }
30200
30243
  } catch (error46) {
@@ -30453,7 +30496,7 @@ function getUiDir() {
30453
30496
  }
30454
30497
  return join9(__dirname2, "..", "..", "ui", "dist");
30455
30498
  }
30456
- async function startServer(portArg) {
30499
+ async function startServer(portArg, options) {
30457
30500
  const port = portArg || await findAvailablePort(3141);
30458
30501
  const host = "127.0.0.1";
30459
30502
  const router = new Router;
@@ -30521,7 +30564,9 @@ async function startServer(portArg) {
30521
30564
  console.log(` Server: ${import_picocolors11.default.cyan(url2)}`);
30522
30565
  console.log(` Press ${import_picocolors11.default.bold("Ctrl+C")} to stop`);
30523
30566
  console.log();
30524
- openBrowser(url2);
30567
+ if (!options?.noBrowser) {
30568
+ openBrowser(url2);
30569
+ }
30525
30570
  const handle = {
30526
30571
  host,
30527
30572
  port,
@@ -30538,45 +30583,230 @@ async function startServer(portArg) {
30538
30583
  return handle;
30539
30584
  }
30540
30585
 
30541
- // src/cli/ui.ts
30542
- async function handleUiCommand(args) {
30543
- const port = getPortFromArgs(args);
30586
+ // src/utils/daemon.ts
30587
+ init_paths();
30588
+ import { existsSync as existsSync12 } from "node:fs";
30589
+ import { readFile as readFile9, writeFile as writeFile8, unlink as unlink3 } from "node:fs/promises";
30590
+ import { join as join10 } from "node:path";
30591
+ function getPidFilePath() {
30592
+ return join10(getConfigDir("swixter"), "ui.pid");
30593
+ }
30594
+ function getLogFilePath() {
30595
+ return join10(getConfigDir("swixter"), "ui.log");
30596
+ }
30597
+ async function readPidFile() {
30598
+ const path = getPidFilePath();
30599
+ if (!existsSync12(path))
30600
+ return null;
30544
30601
  try {
30545
- const server = await startServer(port);
30546
- process.on("SIGINT", () => {
30547
- console.log();
30548
- console.log(import_picocolors12.default.dim("Shutting down..."));
30549
- server.close(() => {
30550
- process.exit(0);
30551
- });
30602
+ const content = await readFile9(path, "utf-8");
30603
+ return JSON.parse(content);
30604
+ } catch {
30605
+ return null;
30606
+ }
30607
+ }
30608
+ async function writePidFile(pid, port) {
30609
+ const path = getPidFilePath();
30610
+ const data = { pid, port, startTime: new Date().toISOString() };
30611
+ await writeFile8(path, JSON.stringify(data, null, 2), "utf-8");
30612
+ }
30613
+ async function removePidFile() {
30614
+ const path = getPidFilePath();
30615
+ if (existsSync12(path)) {
30616
+ await unlink3(path).catch(() => {});
30617
+ }
30618
+ }
30619
+ function isProcessAlive2(pid) {
30620
+ try {
30621
+ process.kill(pid, 0);
30622
+ return true;
30623
+ } catch {
30624
+ return false;
30625
+ }
30626
+ }
30627
+ async function isSwixterUiRunning(pid, port) {
30628
+ if (!isProcessAlive2(pid))
30629
+ return false;
30630
+ try {
30631
+ const res = await fetch(`http://127.0.0.1:${port}/api/version`, {
30632
+ signal: AbortSignal.timeout(3000)
30552
30633
  });
30553
- process.stdin.resume();
30554
- } catch (error46) {
30555
- console.error(import_picocolors12.default.red(`Failed to start server: ${error46}`));
30634
+ return res.ok;
30635
+ } catch {
30636
+ return false;
30637
+ }
30638
+ }
30639
+ async function cleanupStalePidFile() {
30640
+ const data = await readPidFile();
30641
+ if (data && !isProcessAlive2(data.pid)) {
30642
+ await removePidFile();
30643
+ }
30644
+ }
30645
+ async function stopDaemon() {
30646
+ const data = await readPidFile();
30647
+ if (!data) {
30648
+ return { success: false, message: "No daemon process is running." };
30649
+ }
30650
+ if (!isProcessAlive2(data.pid)) {
30651
+ await removePidFile();
30652
+ return { success: false, message: "Daemon process is not running (stale PID file removed)." };
30653
+ }
30654
+ try {
30655
+ process.kill(data.pid, "SIGTERM");
30656
+ await removePidFile();
30657
+ return { success: true, message: `Daemon process ${data.pid} stopped.` };
30658
+ } catch {
30659
+ await removePidFile();
30660
+ return { success: false, message: "Failed to stop daemon process (PID file removed)." };
30661
+ }
30662
+ }
30663
+
30664
+ // src/cli/ui.ts
30665
+ async function handleUiCommand(args) {
30666
+ const flags = parseFlags2(args);
30667
+ if (flags.stop) {
30668
+ const result = await stopDaemon();
30669
+ console.log();
30670
+ console.log(result.success ? import_picocolors12.default.green("✓") + " " + result.message : import_picocolors12.default.yellow("⚠") + " " + result.message);
30671
+ console.log();
30672
+ process.exit(result.success ? 0 : 1);
30673
+ }
30674
+ if (flags.status) {
30675
+ await showStatus();
30676
+ return;
30677
+ }
30678
+ if (flags.daemon) {
30679
+ await startDaemon(flags.port);
30680
+ return;
30681
+ }
30682
+ await runForeground(flags.port);
30683
+ }
30684
+ async function showStatus() {
30685
+ await cleanupStalePidFile();
30686
+ const data = await readPidFile();
30687
+ console.log();
30688
+ if (!data) {
30689
+ console.log(import_picocolors12.default.yellow("Swixter UI is not running."));
30690
+ console.log(import_picocolors12.default.dim("Run 'swixter ui --daemon' to start in background."));
30691
+ console.log();
30692
+ return;
30693
+ }
30694
+ const isRunning = await isSwixterUiRunning(data.pid, data.port);
30695
+ if (isRunning) {
30696
+ console.log(import_picocolors12.default.green("✓ Swixter UI is running"));
30697
+ console.log(` PID: ${import_picocolors12.default.cyan(String(data.pid))}`);
30698
+ console.log(` URL: ${import_picocolors12.default.cyan(`http://127.0.0.1:${data.port}`)}`);
30699
+ console.log(` Started: ${import_picocolors12.default.dim(data.startTime)}`);
30700
+ } else {
30701
+ console.log(import_picocolors12.default.yellow("⚠ Swixter UI is not running (stale PID file removed)."));
30702
+ }
30703
+ console.log();
30704
+ }
30705
+ async function startDaemon(portArg) {
30706
+ await cleanupStalePidFile();
30707
+ const existing = await readPidFile();
30708
+ if (existing && await isSwixterUiRunning(existing.pid, existing.port)) {
30709
+ console.log();
30710
+ console.log(import_picocolors12.default.yellow("Swixter UI is already running."));
30711
+ console.log(` PID: ${import_picocolors12.default.cyan(String(existing.pid))}`);
30712
+ console.log(` URL: ${import_picocolors12.default.cyan(`http://127.0.0.1:${existing.port}`)}`);
30713
+ console.log();
30714
+ openBrowser(`http://127.0.0.1:${existing.port}`);
30715
+ return;
30716
+ }
30717
+ const port = portArg || await findAvailablePort(3141);
30718
+ const childArgs = process.argv.slice(1).filter((arg) => arg !== "--daemon" && arg !== "--stop" && arg !== "--status");
30719
+ const logPath = getLogFilePath();
30720
+ const logFile = await open(logPath, "a");
30721
+ const child = spawn3(process.argv0, childArgs, {
30722
+ detached: true,
30723
+ stdio: ["ignore", logFile.fd, logFile.fd],
30724
+ env: { ...process.env, SWIXTER_UI_DAEMON: "1" }
30725
+ });
30726
+ child.unref();
30727
+ const url2 = `http://127.0.0.1:${port}`;
30728
+ const started = await waitForServer(url2, 1e4);
30729
+ if (!started) {
30730
+ try {
30731
+ process.kill(child.pid, "SIGTERM");
30732
+ } catch {}
30733
+ console.log();
30734
+ console.log(import_picocolors12.default.red("✗ Failed to start daemon (timed out waiting for server)."));
30735
+ console.log();
30556
30736
  process.exit(1);
30557
30737
  }
30738
+ await writePidFile(child.pid, port);
30739
+ console.log();
30740
+ console.log(import_picocolors12.default.green("✓ Swixter UI daemon started"));
30741
+ console.log(` PID: ${import_picocolors12.default.cyan(String(child.pid))}`);
30742
+ console.log(` URL: ${import_picocolors12.default.cyan(url2)}`);
30743
+ console.log(` Log: ${import_picocolors12.default.dim(logPath)}`);
30744
+ console.log();
30745
+ console.log(import_picocolors12.default.dim("Run 'swixter ui --stop' to stop."));
30746
+ console.log();
30558
30747
  }
30559
- function getPortFromArgs(args) {
30560
- if (!args || args.length === 0) {
30748
+ async function runForeground(portArg) {
30749
+ await cleanupStalePidFile();
30750
+ const existing = await readPidFile();
30751
+ if (existing && await isSwixterUiRunning(existing.pid, existing.port)) {
30752
+ const url2 = `http://127.0.0.1:${existing.port}`;
30753
+ console.log();
30754
+ console.log(import_picocolors12.default.green("✓ Swixter UI is already running"));
30755
+ console.log(` URL: ${import_picocolors12.default.cyan(url2)}`);
30756
+ console.log();
30757
+ openBrowser(url2);
30561
30758
  return;
30562
30759
  }
30760
+ const port = portArg || await findAvailablePort(3141);
30761
+ const noBrowser = process.env.SWIXTER_UI_DAEMON === "1";
30762
+ const server = await startServer(port, { noBrowser });
30763
+ await writePidFile(process.pid, port);
30764
+ const shutdown = () => {
30765
+ console.log();
30766
+ console.log(import_picocolors12.default.dim("Shutting down..."));
30767
+ server.close(() => {
30768
+ removePidFile().catch(() => {});
30769
+ process.exit(0);
30770
+ });
30771
+ };
30772
+ process.on("SIGINT", shutdown);
30773
+ process.on("SIGTERM", shutdown);
30774
+ process.stdin.resume();
30775
+ }
30776
+ async function waitForServer(url2, timeoutMs) {
30777
+ const start = Date.now();
30778
+ const interval = 200;
30779
+ while (Date.now() - start < timeoutMs) {
30780
+ try {
30781
+ const res = await fetch(`${url2}/api/version`);
30782
+ if (res.ok)
30783
+ return true;
30784
+ } catch {}
30785
+ await new Promise((r2) => setTimeout(r2, interval));
30786
+ }
30787
+ return false;
30788
+ }
30789
+ function parseFlags2(args) {
30790
+ const result = { daemon: false, stop: false, status: false, port: undefined };
30791
+ if (!args)
30792
+ return result;
30563
30793
  for (let i2 = 0;i2 < args.length; i2++) {
30564
30794
  const arg = args[i2];
30565
- if (arg === "--port" || arg === "-p") {
30566
- const portStr = args[i2 + 1];
30567
- if (!portStr) {
30568
- console.error(import_picocolors12.default.red("Error: --port requires a value"));
30569
- process.exit(1);
30570
- }
30571
- const port = parseInt(portStr, 10);
30572
- if (isNaN(port) || port < 1 || port > 65535) {
30573
- console.error(import_picocolors12.default.red("Error: Invalid port number"));
30574
- process.exit(1);
30795
+ if (arg === "--daemon") {
30796
+ result.daemon = true;
30797
+ } else if (arg === "--stop") {
30798
+ result.stop = true;
30799
+ } else if (arg === "--status") {
30800
+ result.status = true;
30801
+ } else if ((arg === "--port" || arg === "-p") && args[i2 + 1]) {
30802
+ const port = parseInt(args[i2 + 1], 10);
30803
+ if (!isNaN(port) && port >= 1 && port <= 65535) {
30804
+ result.port = port;
30575
30805
  }
30576
- return port;
30806
+ i2++;
30577
30807
  }
30578
30808
  }
30579
- return;
30809
+ return result;
30580
30810
  }
30581
30811
 
30582
30812
  // src/cli/index.ts
@@ -30601,6 +30831,12 @@ ${import_picocolors13.default.bold("Web UI:")}
30601
30831
  ${import_picocolors13.default.cyan("ui")} ${import_picocolors13.default.dim("Launch local Web UI")}
30602
30832
  ${import_picocolors13.default.dim("swixter ui [--port <port>]")}
30603
30833
  ${import_picocolors13.default.dim("Start local HTTP server and open browser")}
30834
+ ${import_picocolors13.default.dim("swixter ui --daemon [--port <port>]")}
30835
+ ${import_picocolors13.default.dim("Start server in background")}
30836
+ ${import_picocolors13.default.dim("swixter ui --stop")}
30837
+ ${import_picocolors13.default.dim("Stop background server")}
30838
+ ${import_picocolors13.default.dim("swixter ui --status")}
30839
+ ${import_picocolors13.default.dim("Show background server status")}
30604
30840
 
30605
30841
  ${import_picocolors13.default.bold("Proxy Commands:")}
30606
30842
  ${import_picocolors13.default.cyan("proxy")} ${import_picocolors13.default.dim("Start proxy server or run with proxy")}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swixter",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "CLI tool for managing AI coding assistant configurations - easily switch between providers (Claude Code, Codex, Continue) with Anthropic, Ollama, or custom APIs",
5
5
  "main": "dist/cli/index.js",
6
6
  "module": "dist/cli/index.js",