xgen-dex-cli 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -361,15 +361,26 @@ function validateProfileName(input) {
361
361
  return name;
362
362
  }
363
363
  function validateServerUrl(input) {
364
+ const raw = input.trim();
365
+ if (!raw) {
366
+ throw new DexError("config_invalid", "\uC11C\uBC84 \uC8FC\uC18C\uB97C \uC785\uB825\uD558\uC138\uC694.");
367
+ }
368
+ const hasScheme = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(raw);
369
+ const bare = raw.replace(/^\/+/, "");
370
+ const local = /^(localhost|127\.0\.0\.1|\[?::1\]?)(:|$|\/)/i.test(bare);
371
+ const candidate = hasScheme ? raw : `${local ? "http" : "https"}://${bare}`;
364
372
  let url;
365
373
  try {
366
- url = new URL(input.trim());
374
+ url = new URL(candidate);
367
375
  } catch {
368
- throw new DexError("config_invalid", "\uC11C\uBC84 URL\uC740 http:// \uB610\uB294 https://\uB85C \uC2DC\uC791\uD574\uC57C \uD569\uB2C8\uB2E4.");
376
+ throw new DexError("config_invalid", "\uC11C\uBC84 \uC8FC\uC18C\uB97C \uC54C\uC544\uBCFC \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC608: xgen.example.com");
369
377
  }
370
378
  if (url.protocol !== "http:" && url.protocol !== "https:") {
371
379
  throw new DexError("config_invalid", "\uC11C\uBC84 URL\uC740 http:// \uB610\uB294 https://\uB9CC \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
372
380
  }
381
+ if (!url.hostname) {
382
+ throw new DexError("config_invalid", "\uC11C\uBC84 \uC8FC\uC18C\uC5D0 \uD638\uC2A4\uD2B8\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uC608: xgen.example.com");
383
+ }
373
384
  if (url.username || url.password || url.search || url.hash) {
374
385
  throw new DexError("config_invalid", "\uC11C\uBC84 URL\uC5D0\uB294 \uC790\uACA9 \uC99D\uBA85, query, fragment\uB97C \uB123\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
375
386
  }
@@ -4437,22 +4448,104 @@ var DexEngine = class {
4437
4448
  };
4438
4449
 
4439
4450
  // ../../packages/engine/src/credential-store.ts
4451
+ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
4452
+ import { dirname as dirname3, join as join3 } from "node:path";
4440
4453
  var SERVICE = "xgen-dex-cli";
4441
4454
  var ACCOUNT_PREFIX = "profile:";
4455
+ var activeBackend = "file";
4456
+ function credentialBackend() {
4457
+ return activeBackend;
4458
+ }
4459
+ var keytarProbe = null;
4460
+ var KEYCHAIN_TIMEOUT_MS = 2e3;
4461
+ async function withTimeout2(work) {
4462
+ let timer;
4463
+ try {
4464
+ return await Promise.race([
4465
+ work,
4466
+ new Promise((_, reject) => {
4467
+ timer = setTimeout(() => reject(new Error("keychain timeout")), KEYCHAIN_TIMEOUT_MS);
4468
+ timer.unref?.();
4469
+ })
4470
+ ]);
4471
+ } finally {
4472
+ if (timer) clearTimeout(timer);
4473
+ }
4474
+ }
4442
4475
  async function loadKeytar() {
4476
+ keytarProbe ??= (async () => {
4477
+ if (process.env.DEX_NO_KEYCHAIN === "1") return null;
4478
+ try {
4479
+ const loaded = await import("keytar");
4480
+ const keytar2 = loaded.default ?? loaded;
4481
+ if (!keytar2.getPassword || !keytar2.setPassword || !keytar2.deletePassword) return null;
4482
+ await withTimeout2(keytar2.getPassword(SERVICE, "__probe__"));
4483
+ return keytar2;
4484
+ } catch {
4485
+ return null;
4486
+ }
4487
+ })();
4488
+ const keytar = await keytarProbe;
4489
+ activeBackend = keytar ? "keychain" : "file";
4490
+ return keytar;
4491
+ }
4492
+ function filePath() {
4493
+ return join3(dataDirectory(), "credentials.json");
4494
+ }
4495
+ function readFileStore() {
4443
4496
  try {
4444
- const loaded = await import("keytar");
4445
- const keytar = loaded.default ?? loaded;
4446
- if (!keytar.getPassword || !keytar.setPassword || !keytar.deletePassword) throw new Error("invalid keytar module");
4447
- return keytar;
4448
- } catch (error) {
4449
- throw new DexError(
4450
- "credential_store_unavailable",
4451
- "OS \uD0A4\uCCB4\uC778\uC744 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. keytar \uC124\uCE58\uC640 OS \uD0A4\uB9C1 \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694.",
4452
- error
4453
- );
4497
+ const raw = readFileSync(filePath(), "utf8");
4498
+ const parsed = JSON.parse(raw);
4499
+ return parsed && typeof parsed === "object" ? parsed : {};
4500
+ } catch {
4501
+ return {};
4454
4502
  }
4455
4503
  }
4504
+ function writeFileStore(values) {
4505
+ const path = filePath();
4506
+ mkdirSync(dirname3(path), { recursive: true });
4507
+ const tmp = `${path}.tmp`;
4508
+ writeFileSync(tmp, JSON.stringify(values, null, 2), { encoding: "utf8", mode: 384 });
4509
+ try {
4510
+ chmodSync(tmp, 384);
4511
+ } catch {
4512
+ }
4513
+ renameSync(tmp, path);
4514
+ }
4515
+ async function secretGet(account) {
4516
+ const keytar = await loadKeytar();
4517
+ if (keytar) {
4518
+ try {
4519
+ return await withTimeout2(keytar.getPassword(SERVICE, account));
4520
+ } catch {
4521
+ }
4522
+ }
4523
+ return readFileStore()[account] ?? null;
4524
+ }
4525
+ async function secretSet(account, value) {
4526
+ const keytar = await loadKeytar();
4527
+ if (keytar) {
4528
+ try {
4529
+ if (value === null) await withTimeout2(keytar.deletePassword(SERVICE, account));
4530
+ else await withTimeout2(keytar.setPassword(SERVICE, account, value));
4531
+ return;
4532
+ } catch {
4533
+ activeBackend = "file";
4534
+ }
4535
+ }
4536
+ const values = readFileStore();
4537
+ if (value === null) delete values[account];
4538
+ else values[account] = value;
4539
+ if (Object.keys(values).length === 0 && existsSync(filePath())) {
4540
+ try {
4541
+ unlinkSync(filePath());
4542
+ } catch {
4543
+ writeFileStore(values);
4544
+ }
4545
+ return;
4546
+ }
4547
+ writeFileStore(values);
4548
+ }
4456
4549
  function parseSession(raw) {
4457
4550
  if (!raw) return null;
4458
4551
  try {
@@ -4463,7 +4556,7 @@ function parseSession(raw) {
4463
4556
  return null;
4464
4557
  }
4465
4558
  }
4466
- var KeytarCredentialStore = class {
4559
+ var SystemCredentialStore = class {
4467
4560
  /**
4468
4561
  * 엔진의 `SecretPort` 로 내보내는 두 함수 — 프로파일 세션 말고 **임의의 비밀**
4469
4562
  * (MCP 서버 시크릿 · OAuth 상태)을 같은 백엔드에 둔다.
@@ -4472,29 +4565,20 @@ var KeytarCredentialStore = class {
4472
4565
  * 하나의 질문이고, 두 곳에 나뉘면 로그아웃할 때 한쪽이 남는다.
4473
4566
  */
4474
4567
  async getRaw(name) {
4475
- const keytar = await loadKeytar();
4476
- return keytar.getPassword(SERVICE, name);
4568
+ return secretGet(name);
4477
4569
  }
4478
4570
  async setRaw(name, value) {
4479
- const keytar = await loadKeytar();
4480
- if (value === null) {
4481
- await keytar.deletePassword(SERVICE, name);
4482
- return true;
4483
- }
4484
- await keytar.setPassword(SERVICE, name, value);
4571
+ await secretSet(name, value);
4485
4572
  return true;
4486
4573
  }
4487
4574
  async get(profile) {
4488
- const keytar = await loadKeytar();
4489
- return parseSession(await keytar.getPassword(SERVICE, ACCOUNT_PREFIX + profile));
4575
+ return parseSession(await secretGet(ACCOUNT_PREFIX + profile));
4490
4576
  }
4491
4577
  async set(profile, session) {
4492
- const keytar = await loadKeytar();
4493
- await keytar.setPassword(SERVICE, ACCOUNT_PREFIX + profile, JSON.stringify(session));
4578
+ await secretSet(ACCOUNT_PREFIX + profile, JSON.stringify(session));
4494
4579
  }
4495
4580
  async delete(profile) {
4496
- const keytar = await loadKeytar();
4497
- await keytar.deletePassword(SERVICE, ACCOUNT_PREFIX + profile);
4581
+ await secretSet(ACCOUNT_PREFIX + profile, null);
4498
4582
  }
4499
4583
  };
4500
4584
 
@@ -4555,6 +4639,7 @@ export {
4555
4639
  dataDirectory,
4556
4640
  FileConfigStore,
4557
4641
  DexEngine,
4558
- KeytarCredentialStore
4642
+ credentialBackend,
4643
+ SystemCredentialStore
4559
4644
  };
4560
- //# sourceMappingURL=chunk-QXSCKZEG.js.map
4645
+ //# sourceMappingURL=chunk-ULMEXJOR.js.map