shioricode 0.5.2 → 0.5.3

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 (43) hide show
  1. package/README.md +22 -1
  2. package/dist/BunHttpServer-BvXfeDdm.cjs +375 -0
  3. package/dist/BunPTY-l5TwbZzE.cjs +91 -0
  4. package/dist/BunServices-07vLsX26.cjs +74 -0
  5. package/dist/NodeHttpServer-OQzpZjfj.cjs +4142 -0
  6. package/dist/NodePTY-BNeKL1sW.cjs +86 -0
  7. package/dist/NodeSqliteClient-K1HOOjni.cjs +147 -0
  8. package/dist/SqlError-UTW5yuSG.cjs +262 -0
  9. package/dist/SqliteClient-CVOnk2wf.cjs +125 -0
  10. package/dist/bin.cjs +703700 -0
  11. package/dist/bin.mjs +386 -142
  12. package/dist/client/apple-touch-icon.png +0 -0
  13. package/dist/client/assets/{ArtifactPanel-BPQ8FX7l.js → ArtifactPanel-ClA97FO8.js} +2 -2
  14. package/dist/client/assets/{ChatMarkdown-jcE8Pq97.js → ChatMarkdown-DkldD1_B.js} +2 -2
  15. package/dist/client/assets/{DiffPanel-CPmbtIC0.js → DiffPanel-BYSH-G7M.js} +2 -2
  16. package/dist/client/assets/{DiffPanelShell-CqS9jPrk.js → DiffPanelShell-Ce2wOE9L.js} +2 -2
  17. package/dist/client/assets/{DiffWorkerPoolProvider-CEnxoS9v.js → DiffWorkerPoolProvider-DjSdGkRQ.js} +2 -2
  18. package/dist/client/assets/{NoActiveThreadState-Bt_jFQ4-.js → NoActiveThreadState-CVDfDuF2.js} +2 -2
  19. package/dist/client/assets/{ProviderModelPicker-BRDu3BzU.js → ProviderModelPicker-BFlX8lZL.js} +2 -2
  20. package/dist/client/assets/{SettingsPanels-BX2dAbn4.js → SettingsPanels-_8_YaXgc.js} +2 -2
  21. package/dist/client/assets/{_chat-C_aCgqFw.js → _chat-CQ5UewiP.js} +2 -2
  22. package/dist/client/assets/{_chat._threadId-DLIOcnXQ.js → _chat._threadId-5846z-_l.js} +5 -5
  23. package/dist/client/assets/{_chat.index-B9u7xlCv.js → _chat.index-DI7egWLc.js} +2 -2
  24. package/dist/client/assets/{automations-CJVFqqLv.js → automations-D6SQKJ83.js} +2 -2
  25. package/dist/client/assets/{dialog-BwIZs3EF.js → dialog-CZ2IjObB.js} +2 -2
  26. package/dist/client/assets/{index-DKZPXV4a.js → index-B42nGfCf.js} +4 -4
  27. package/dist/client/assets/{pull-requests-CQHYo-fV.js → pull-requests-DGa0NhIZ.js} +2 -2
  28. package/dist/client/assets/{settings-i7-rb7s9.js → settings-BwPioqTL.js} +2 -2
  29. package/dist/client/assets/{settings.appearance-Dwkl1cOq.js → settings.appearance-DxQzPuf5.js} +2 -2
  30. package/dist/client/assets/{settings.archived-DZPSjNZJ.js → settings.archived-BSqIxOKY.js} +2 -2
  31. package/dist/client/assets/{settings.computer-use-CLy6GP7W.js → settings.computer-use-DuuUffvc.js} +2 -2
  32. package/dist/client/assets/{settings.general-CuNCor2d.js → settings.general-DjIbwGSI.js} +2 -2
  33. package/dist/client/assets/{settings.mcp-DwWQDGSB.js → settings.mcp-CHZOA1L-.js} +2 -2
  34. package/dist/client/assets/{settings.mobile-B73fICiz.js → settings.mobile-D3UiSzxp.js} +2 -2
  35. package/dist/client/assets/{settings.plugins-BjW05gj4.js → settings.plugins-qo7Fls2x.js} +2 -2
  36. package/dist/client/assets/{settings.remote-D_iWhzEN.js → settings.remote-CmdhJhKY.js} +2 -2
  37. package/dist/client/assets/{settings.skills-BCB4uvnE.js → settings.skills-UB5Wtkl5.js} +2 -2
  38. package/dist/client/assets/{settings.theme-builder-CS2XB4es.js → settings.theme-builder-CnwNYRga.js} +2 -2
  39. package/dist/client/favicon-16x16.png +0 -0
  40. package/dist/client/favicon-32x32.png +0 -0
  41. package/dist/client/favicon.ico +0 -0
  42. package/dist/client/index.html +1 -1
  43. package/package.json +38 -5
package/dist/bin.mjs CHANGED
@@ -72447,42 +72447,112 @@ var RemoteStateStore = class {
72447
72447
  const execFile$5 = promisify(execFile);
72448
72448
  const SERVICE_PORT = 3773;
72449
72449
  const RECOVERY_USERNAME = "recovery";
72450
+ const SERVICE_LAYOUT_VERSION = 1;
72451
+ function invokingUsername() {
72452
+ const sudoUser = process.env.SUDO_USER?.trim();
72453
+ if (sudoUser && sudoUser !== "root") return sudoUser;
72454
+ return os.userInfo().username;
72455
+ }
72456
+ function invokingUid() {
72457
+ const sudoUid = Number(process.env.SUDO_UID);
72458
+ if (Number.isInteger(sudoUid) && sudoUid >= 0) return sudoUid;
72459
+ return typeof process.getuid === "function" ? process.getuid() : 0;
72460
+ }
72461
+ function defaultAccount(platform) {
72462
+ if (platform === "darwin") return "_shioricode";
72463
+ if (platform === "linux") return "shioricode";
72464
+ return "ShioriCode";
72465
+ }
72466
+ function defaultExistingHomeDir(platform, account) {
72467
+ if (account === os.userInfo().username) return os.homedir();
72468
+ if (platform === "darwin") return path.join("/Users", account);
72469
+ if (platform === "linux") return account === "root" ? "/root" : path.join("/home", account);
72470
+ return process.env.USERPROFILE || os.homedir();
72471
+ }
72472
+ function defaultServicePath(platform) {
72473
+ if (platform === "darwin") return "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
72474
+ if (platform === "linux") return "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
72475
+ return process.env.Path ?? process.env.PATH ?? "";
72476
+ }
72477
+ function validateServiceAccount(account) {
72478
+ if (!/^[A-Za-z_][A-Za-z0-9._-]*\$?$/.test(account)) throw new Error(`Invalid service username: ${account}`);
72479
+ }
72480
+ function validateServiceLayout(layout) {
72481
+ const isAbsolute = layout.platform === "win32" ? path.win32.isAbsolute : path.isAbsolute;
72482
+ for (const [label, value] of [
72483
+ ["home directory", layout.homeDir],
72484
+ ["state directory", layout.stateDir],
72485
+ ["workspace directory", layout.workspaceDir],
72486
+ ["service definition", layout.definitionPath],
72487
+ ["log file", layout.logPath]
72488
+ ]) if (!isAbsolute(value) || /[\0\r\n]/.test(value)) throw new Error(`The ${label} must be an absolute path without control characters`);
72489
+ if (/\0|\r|\n/.test(layout.servicePath)) throw new Error("The service PATH cannot contain control characters");
72490
+ if (!Number.isInteger(layout.port) || layout.port < 1 || layout.port > 65535) throw new Error(`Invalid service port: ${layout.port}`);
72491
+ return layout;
72492
+ }
72450
72493
  function supportedPlatform(platform) {
72451
72494
  if (platform === "linux" || platform === "darwin" || platform === "win32") return platform;
72452
72495
  throw new Error(`ShioriCode services are not supported on ${platform} yet`);
72453
72496
  }
72454
- function serviceLayout(platform) {
72497
+ function serviceLayout(platform, options = {}) {
72498
+ const accountMode = options.accountMode ?? "dedicated";
72499
+ const account = options.account?.trim() || (accountMode === "current" ? invokingUsername() : defaultAccount(platform));
72500
+ validateServiceAccount(account);
72455
72501
  switch (platform) {
72456
- case "linux": return {
72457
- platform,
72458
- account: "shioricode",
72459
- stateDir: "/var/lib/shioricode",
72460
- workspaceDir: "/var/lib/shioricode/workspaces",
72461
- definitionPath: "/etc/systemd/system/shioricode.service",
72462
- logPath: "/var/log/shioricode/server.log",
72463
- serviceId: "shioricode.service"
72464
- };
72465
- case "darwin": return {
72466
- platform,
72467
- account: "_shioricode",
72468
- stateDir: "/Library/Application Support/ShioriCode",
72469
- workspaceDir: "/Users/Shared/ShioriCode/Workspaces",
72470
- definitionPath: "/Library/LaunchDaemons/codes.shiori.shioricode.plist",
72471
- logPath: "/Library/Logs/ShioriCode/server.log",
72472
- serviceId: "codes.shiori.shioricode"
72473
- };
72502
+ case "linux": {
72503
+ const dedicatedStateDir = "/var/lib/shioricode";
72504
+ const homeDir = options.homeDir ?? (accountMode === "dedicated" ? dedicatedStateDir : defaultExistingHomeDir(platform, account));
72505
+ const stateDir = options.stateDir ?? (accountMode === "dedicated" ? dedicatedStateDir : path.join(homeDir, ".shioricode-service"));
72506
+ return validateServiceLayout({
72507
+ platform,
72508
+ accountMode,
72509
+ account,
72510
+ homeDir,
72511
+ stateDir,
72512
+ workspaceDir: options.workspaceDir ?? path.join(stateDir, "workspaces"),
72513
+ definitionPath: accountMode === "dedicated" ? "/etc/systemd/system/shioricode.service" : path.join(homeDir, ".config/systemd/user/shioricode.service"),
72514
+ logPath: options.logPath ?? (accountMode === "dedicated" ? "/var/log/shioricode/server.log" : path.join(stateDir, "server.log")),
72515
+ servicePath: options.servicePath ?? defaultServicePath(platform),
72516
+ serviceId: "shioricode.service",
72517
+ port: options.port ?? SERVICE_PORT
72518
+ });
72519
+ }
72520
+ case "darwin": {
72521
+ const dedicatedStateDir = "/Library/Application Support/ShioriCode";
72522
+ const homeDir = options.homeDir ?? (accountMode === "dedicated" ? dedicatedStateDir : defaultExistingHomeDir(platform, account));
72523
+ const stateDir = options.stateDir ?? (accountMode === "dedicated" ? dedicatedStateDir : path.join(homeDir, ".shioricode-service"));
72524
+ return validateServiceLayout({
72525
+ platform,
72526
+ accountMode,
72527
+ account,
72528
+ homeDir,
72529
+ stateDir,
72530
+ workspaceDir: options.workspaceDir ?? (accountMode === "dedicated" ? "/Users/Shared/ShioriCode/Workspaces" : path.join(stateDir, "workspaces")),
72531
+ definitionPath: accountMode === "dedicated" ? "/Library/LaunchDaemons/codes.shiori.shioricode.plist" : path.join(homeDir, "Library/LaunchAgents/codes.shiori.shioricode.plist"),
72532
+ logPath: options.logPath ?? (accountMode === "dedicated" ? "/Library/Logs/ShioriCode/server.log" : path.join(stateDir, "server.log")),
72533
+ servicePath: options.servicePath ?? defaultServicePath(platform),
72534
+ serviceId: "codes.shiori.shioricode",
72535
+ port: options.port ?? SERVICE_PORT
72536
+ });
72537
+ }
72474
72538
  case "win32": {
72475
72539
  const programData = process.env.ProgramData || "C:\\ProgramData";
72476
- const stateDir = path.win32.join(programData, "ShioriCode");
72477
- return {
72540
+ const dedicatedStateDir = path.win32.join(programData, "ShioriCode");
72541
+ const homeDir = options.homeDir ?? (accountMode === "dedicated" ? dedicatedStateDir : defaultExistingHomeDir(platform, account));
72542
+ const stateDir = options.stateDir ?? (accountMode === "dedicated" ? dedicatedStateDir : path.win32.join(homeDir, ".shioricode-service"));
72543
+ return validateServiceLayout({
72478
72544
  platform,
72479
- account: "ShioriCode",
72545
+ accountMode,
72546
+ account,
72547
+ homeDir,
72480
72548
  stateDir,
72481
- workspaceDir: path.win32.join(stateDir, "workspaces"),
72549
+ workspaceDir: options.workspaceDir ?? path.win32.join(stateDir, "workspaces"),
72482
72550
  definitionPath: path.win32.join(stateDir, "service.cmd"),
72483
- logPath: path.win32.join(stateDir, "server.log"),
72484
- serviceId: "ShioriCode"
72485
- };
72551
+ logPath: options.logPath ?? path.win32.join(stateDir, "server.log"),
72552
+ servicePath: options.servicePath ?? defaultServicePath(platform),
72553
+ serviceId: "ShioriCode",
72554
+ port: options.port ?? SERVICE_PORT
72555
+ });
72486
72556
  }
72487
72557
  }
72488
72558
  }
@@ -72495,20 +72565,21 @@ function xmlEscape(value) {
72495
72565
  function windowsBatchQuote(value) {
72496
72566
  return `"${value.replaceAll("\"", "\"\"")}"`;
72497
72567
  }
72498
- function serviceArguments(layout) {
72499
- return [
72568
+ function serviceArguments(layout, requireAuth) {
72569
+ const args = [
72500
72570
  "serve",
72501
72571
  "--mode",
72502
72572
  "web",
72503
72573
  "--host",
72504
72574
  "127.0.0.1",
72505
72575
  "--port",
72506
- String(SERVICE_PORT),
72576
+ String(layout.port),
72507
72577
  "--base-dir",
72508
72578
  layout.stateDir,
72509
- "--remote",
72510
72579
  "--no-browser"
72511
72580
  ];
72581
+ if (requireAuth) args.splice(-1, 0, "--remote");
72582
+ return args;
72512
72583
  }
72513
72584
  function serviceRuntimeRoot(layout) {
72514
72585
  return path.join(layout.stateDir, "runtime");
@@ -72521,7 +72592,6 @@ function stageServiceRuntime(deps, layout) {
72521
72592
  const runtimeRoot = serviceRuntimeRoot(layout);
72522
72593
  const runtimeDir = path.join(runtimeRoot, `${Date.now()}-${randomBytes(6).toString("hex")}`);
72523
72594
  const packageTarget = path.join(runtimeDir, "package");
72524
- const nodeTarget = path.join(runtimeDir, layout.platform === "win32" ? "node.exe" : "node");
72525
72595
  fs.mkdirSync(runtimeDir, {
72526
72596
  recursive: true,
72527
72597
  mode: 488
@@ -72531,8 +72601,6 @@ function stageServiceRuntime(deps, layout) {
72531
72601
  recursive: true,
72532
72602
  dereference: true
72533
72603
  });
72534
- fs.copyFileSync(deps.execPath, nodeTarget, fs.constants.COPYFILE_FICLONE);
72535
- if (layout.platform !== "win32") fs.chmodSync(nodeTarget, 493);
72536
72604
  } catch (error) {
72537
72605
  fs.rmSync(runtimeDir, {
72538
72606
  recursive: true,
@@ -72544,7 +72612,6 @@ function stageServiceRuntime(deps, layout) {
72544
72612
  runtimeDir,
72545
72613
  dependencies: {
72546
72614
  ...deps,
72547
- execPath: nodeTarget,
72548
72615
  cliPath: path.join(packageTarget, "dist", "bin.mjs")
72549
72616
  }
72550
72617
  };
@@ -72569,6 +72636,14 @@ function pruneStagedServiceRuntimes(layout, activeRuntimeDir) {
72569
72636
  }
72570
72637
  }
72571
72638
  async function protectStagedServiceRuntime(deps, layout) {
72639
+ if (layout.accountMode === "current") {
72640
+ if (layout.platform !== "win32") await deps.run("chmod", [
72641
+ "-R",
72642
+ "u+rX",
72643
+ serviceRuntimeRoot(layout)
72644
+ ]);
72645
+ return;
72646
+ }
72572
72647
  const runtimeRoot = serviceRuntimeRoot(layout);
72573
72648
  if (layout.platform === "win32") {
72574
72649
  await deps.run("icacls.exe", [
@@ -72594,11 +72669,14 @@ async function protectStagedServiceRuntime(deps, layout) {
72594
72669
  }
72595
72670
  function renderSystemdUnit(input) {
72596
72671
  const { layout } = input;
72672
+ const hasRecoveryLogin = input.recoveryUsername !== null && input.recoveryPassword !== null;
72597
72673
  const command = [
72598
72674
  input.execPath,
72599
72675
  input.cliPath,
72600
- ...serviceArguments(layout)
72676
+ ...serviceArguments(layout, hasRecoveryLogin)
72601
72677
  ].map(systemdQuote).join(" ");
72678
+ const account = layout.accountMode === "dedicated" ? `User=${layout.account}\nGroup=${layout.account}\n` : "";
72679
+ const recoveryLogin = hasRecoveryLogin ? `Environment=${systemdQuote(`SHIORICODE_USERNAME=${input.recoveryUsername}`)}\nEnvironment=${systemdQuote(`SHIORICODE_PASSWORD=${input.recoveryPassword}`)}\n` : "";
72602
72680
  return `[Unit]
72603
72681
  Description=ShioriCode headless server
72604
72682
  After=network-online.target
@@ -72606,45 +72684,40 @@ Wants=network-online.target
72606
72684
 
72607
72685
  [Service]
72608
72686
  Type=simple
72609
- User=${layout.account}
72610
- Group=${layout.account}
72611
- WorkingDirectory=${layout.workspaceDir}
72612
- Environment=HOME=${layout.stateDir}
72687
+ ${account}WorkingDirectory=${systemdQuote(layout.workspaceDir)}
72688
+ Environment=${systemdQuote(`HOME=${layout.homeDir}`)}
72613
72689
  Environment=NODE_ENV=production
72614
72690
  Environment=SHIORICODE_SERVICE=1
72615
72691
  Environment=SHIORICODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD=false
72616
- Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
72617
- Environment=SHIORICODE_USERNAME=${RECOVERY_USERNAME}
72618
- Environment=SHIORICODE_PASSWORD=${input.recoveryPassword}
72619
- ExecStart=${command}
72692
+ Environment=${systemdQuote(`PATH=${layout.servicePath}`)}
72693
+ ${recoveryLogin}ExecStart=${command}
72620
72694
  Restart=on-failure
72621
72695
  RestartSec=3
72622
72696
  SuccessExitStatus=130 143
72623
72697
  LimitNOFILE=65535
72624
- StandardOutput=append:${layout.logPath}
72625
- StandardError=append:${layout.logPath}
72698
+ StandardOutput=${systemdQuote(`append:${layout.logPath}`)}
72699
+ StandardError=${systemdQuote(`append:${layout.logPath}`)}
72626
72700
 
72627
72701
  [Install]
72628
72702
  WantedBy=multi-user.target
72629
72703
  `;
72630
72704
  }
72631
72705
  function renderLaunchDaemon(input) {
72706
+ const hasRecoveryLogin = input.recoveryUsername !== null && input.recoveryPassword !== null;
72632
72707
  const args = [
72633
72708
  input.execPath,
72634
72709
  input.cliPath,
72635
- ...serviceArguments(input.layout)
72710
+ ...serviceArguments(input.layout, hasRecoveryLogin)
72636
72711
  ].map((value) => ` <string>${xmlEscape(value)}</string>`).join("\n");
72712
+ const account = input.layout.accountMode === "dedicated" ? ` <key>UserName</key>\n <string>${input.layout.account}</string>\n <key>GroupName</key>\n <string>${input.layout.account}</string>\n` : "";
72713
+ const recoveryLogin = hasRecoveryLogin ? ` <key>SHIORICODE_USERNAME</key><string>${xmlEscape(input.recoveryUsername)}</string>\n <key>SHIORICODE_PASSWORD</key><string>${xmlEscape(input.recoveryPassword)}</string>\n` : "";
72637
72714
  return `<?xml version="1.0" encoding="UTF-8"?>
72638
72715
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
72639
72716
  <plist version="1.0">
72640
72717
  <dict>
72641
72718
  <key>Label</key>
72642
72719
  <string>${input.layout.serviceId}</string>
72643
- <key>UserName</key>
72644
- <string>${input.layout.account}</string>
72645
- <key>GroupName</key>
72646
- <string>${input.layout.account}</string>
72647
- <key>WorkingDirectory</key>
72720
+ ${account} <key>WorkingDirectory</key>
72648
72721
  <string>${xmlEscape(input.layout.workspaceDir)}</string>
72649
72722
  <key>ProgramArguments</key>
72650
72723
  <array>
@@ -72652,14 +72725,12 @@ ${args}
72652
72725
  </array>
72653
72726
  <key>EnvironmentVariables</key>
72654
72727
  <dict>
72655
- <key>HOME</key><string>${xmlEscape(input.layout.stateDir)}</string>
72728
+ <key>HOME</key><string>${xmlEscape(input.layout.homeDir)}</string>
72656
72729
  <key>NODE_ENV</key><string>production</string>
72657
72730
  <key>SHIORICODE_SERVICE</key><string>1</string>
72658
72731
  <key>SHIORICODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD</key><string>false</string>
72659
- <key>PATH</key><string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
72660
- <key>SHIORICODE_USERNAME</key><string>${RECOVERY_USERNAME}</string>
72661
- <key>SHIORICODE_PASSWORD</key><string>${xmlEscape(input.recoveryPassword)}</string>
72662
- </dict>
72732
+ <key>PATH</key><string>${xmlEscape(input.layout.servicePath)}</string>
72733
+ ${recoveryLogin} </dict>
72663
72734
  <key>RunAtLoad</key><true/>
72664
72735
  <key>KeepAlive</key><true/>
72665
72736
  <key>ThrottleInterval</key><integer>3</integer>
@@ -72670,19 +72741,20 @@ ${args}
72670
72741
  `;
72671
72742
  }
72672
72743
  function renderWindowsServiceScript(input) {
72744
+ const hasRecoveryLogin = input.recoveryUsername !== null && input.recoveryPassword !== null;
72673
72745
  const command = [
72674
72746
  input.execPath,
72675
72747
  input.cliPath,
72676
- ...serviceArguments(input.layout)
72748
+ ...serviceArguments(input.layout, hasRecoveryLogin)
72677
72749
  ].map(windowsBatchQuote).join(" ");
72750
+ const recoveryLogin = hasRecoveryLogin ? `set "SHIORICODE_USERNAME=${input.recoveryUsername}"\r\nset "SHIORICODE_PASSWORD=${input.recoveryPassword}"\r\n` : "";
72678
72751
  return `@echo off\r
72679
- set "HOME=${input.layout.stateDir}"\r
72752
+ set "HOME=${input.layout.homeDir}"\r
72680
72753
  set "NODE_ENV=production"\r
72681
72754
  set "SHIORICODE_SERVICE=1"\r
72682
72755
  set "SHIORICODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD=false"\r
72683
- set "SHIORICODE_USERNAME=${RECOVERY_USERNAME}"\r
72684
- set "SHIORICODE_PASSWORD=${input.recoveryPassword}"\r
72685
- cd /d ${windowsBatchQuote(input.layout.workspaceDir)}\r
72756
+ set "PATH=${input.layout.servicePath}"\r
72757
+ ${recoveryLogin}cd /d ${windowsBatchQuote(input.layout.workspaceDir)}\r
72686
72758
  ${command} >> ${windowsBatchQuote(input.layout.logPath)} 2>&1\r
72687
72759
  `;
72688
72760
  }
@@ -72698,11 +72770,24 @@ function defaultDependencies$2() {
72698
72770
  if (!cliArg) throw new Error("Could not resolve the ShioriCode executable path");
72699
72771
  return {
72700
72772
  platform: supportedPlatform(process.platform),
72701
- execPath: fs.realpathSync(process.execPath),
72773
+ execPath: stableNodeExecutable(),
72702
72774
  cliPath: fs.realpathSync(cliArg),
72703
72775
  run: runDefault
72704
72776
  };
72705
72777
  }
72778
+ function stableNodeExecutable() {
72779
+ if (process.platform === "win32") return process.execPath;
72780
+ const pathEntries = (process.env.PATH ?? "").split(path.delimiter);
72781
+ for (const entry of pathEntries) {
72782
+ if (!entry) continue;
72783
+ const candidate = path.join(entry, "node");
72784
+ try {
72785
+ fs.accessSync(candidate, fs.constants.X_OK);
72786
+ return candidate;
72787
+ } catch {}
72788
+ }
72789
+ return process.execPath;
72790
+ }
72706
72791
  function requireAdministrator(platform) {
72707
72792
  if (platform !== "win32" && typeof process.getuid === "function" && process.getuid() !== 0) throw new Error("Service installation requires administrator privileges. Re-run with sudo.");
72708
72793
  }
@@ -72715,7 +72800,9 @@ async function commandExists$1(run, file, args) {
72715
72800
  }
72716
72801
  }
72717
72802
  async function ensureLinuxAccount(deps, layout) {
72718
- if (!await commandExists$1(deps.run, "id", ["-u", layout.account])) await deps.run("useradd", [
72803
+ const accountExists = await commandExists$1(deps.run, "id", ["-u", layout.account]);
72804
+ if (!accountExists && layout.accountMode === "current") throw new Error(`The requested existing service user ${layout.account} does not exist`);
72805
+ if (!accountExists) await deps.run("useradd", [
72719
72806
  "--system",
72720
72807
  "--home-dir",
72721
72808
  layout.stateDir,
@@ -72724,6 +72811,10 @@ async function ensureLinuxAccount(deps, layout) {
72724
72811
  "/usr/sbin/nologin",
72725
72812
  layout.account
72726
72813
  ]);
72814
+ fs.mkdirSync(layout.stateDir, {
72815
+ recursive: true,
72816
+ mode: 488
72817
+ });
72727
72818
  fs.mkdirSync(layout.workspaceDir, {
72728
72819
  recursive: true,
72729
72820
  mode: 488
@@ -72732,14 +72823,15 @@ async function ensureLinuxAccount(deps, layout) {
72732
72823
  recursive: true,
72733
72824
  mode: 488
72734
72825
  });
72826
+ if (layout.accountMode === "current") return;
72735
72827
  await deps.run("chown", [
72736
72828
  "-R",
72737
- `${layout.account}:${layout.account}`,
72829
+ layout.account,
72738
72830
  layout.stateDir
72739
72831
  ]);
72740
72832
  await deps.run("chown", [
72741
72833
  "-R",
72742
- `${layout.account}:${layout.account}`,
72834
+ layout.account,
72743
72835
  path.dirname(layout.logPath)
72744
72836
  ]);
72745
72837
  }
@@ -72774,6 +72866,22 @@ async function readMacNumericAttribute(deps, record, attribute) {
72774
72866
  }
72775
72867
  }
72776
72868
  async function ensureMacAccount(deps, layout) {
72869
+ if (layout.accountMode === "current") {
72870
+ if (!await commandExists$1(deps.run, "id", ["-u", layout.account])) throw new Error(`The requested existing service user ${layout.account} does not exist`);
72871
+ fs.mkdirSync(layout.stateDir, {
72872
+ recursive: true,
72873
+ mode: 488
72874
+ });
72875
+ fs.mkdirSync(layout.workspaceDir, {
72876
+ recursive: true,
72877
+ mode: 488
72878
+ });
72879
+ fs.mkdirSync(path.dirname(layout.logPath), {
72880
+ recursive: true,
72881
+ mode: 488
72882
+ });
72883
+ return;
72884
+ }
72777
72885
  const userRecord = `/Users/${layout.account}`;
72778
72886
  const groupRecord = `/Groups/${layout.account}`;
72779
72887
  const existingUid = await readMacNumericAttribute(deps, userRecord, "UniqueID");
@@ -72883,58 +72991,80 @@ async function ensureMacAccount(deps, layout) {
72883
72991
  path.dirname(layout.logPath)
72884
72992
  ]);
72885
72993
  }
72886
- async function installLinux(deps, layout, recoveryPassword) {
72994
+ async function installLinux(deps, layout, recoveryUsername, recoveryPassword) {
72887
72995
  await ensureLinuxAccount(deps, layout);
72888
72996
  const staged = stageServiceRuntime(deps, layout);
72889
72997
  await protectStagedServiceRuntime(deps, layout);
72998
+ fs.mkdirSync(path.dirname(layout.definitionPath), {
72999
+ recursive: true,
73000
+ mode: 493
73001
+ });
72890
73002
  fs.writeFileSync(layout.definitionPath, renderSystemdUnit({
72891
73003
  ...staged.dependencies,
72892
73004
  layout,
73005
+ recoveryUsername,
72893
73006
  recoveryPassword
72894
73007
  }), { mode: 384 });
72895
- await deps.run("systemctl", ["daemon-reload"]);
72896
- await deps.run("systemctl", ["enable", layout.serviceId]);
72897
- await deps.run("systemctl", ["restart", layout.serviceId]);
73008
+ const systemctl = layout.accountMode === "current" ? ["--user"] : [];
73009
+ await deps.run("systemctl", [...systemctl, "daemon-reload"]);
73010
+ await deps.run("systemctl", [
73011
+ ...systemctl,
73012
+ "enable",
73013
+ layout.serviceId
73014
+ ]);
73015
+ await deps.run("systemctl", [
73016
+ ...systemctl,
73017
+ "restart",
73018
+ layout.serviceId
73019
+ ]);
72898
73020
  pruneStagedServiceRuntimes(layout, staged.runtimeDir);
72899
73021
  }
72900
- async function installMac(deps, layout, recoveryPassword) {
73022
+ async function installMac(deps, layout, recoveryUsername, recoveryPassword) {
72901
73023
  await ensureMacAccount(deps, layout);
72902
73024
  const staged = stageServiceRuntime(deps, layout);
72903
73025
  await protectStagedServiceRuntime(deps, layout);
73026
+ fs.mkdirSync(path.dirname(layout.definitionPath), {
73027
+ recursive: true,
73028
+ mode: 493
73029
+ });
72904
73030
  fs.writeFileSync(layout.definitionPath, renderLaunchDaemon({
72905
73031
  ...staged.dependencies,
72906
73032
  layout,
73033
+ recoveryUsername,
72907
73034
  recoveryPassword
72908
73035
  }), { mode: 384 });
73036
+ const domain = layout.accountMode === "dedicated" ? "system" : `gui/${invokingUid()}`;
72909
73037
  await deps.run("launchctl", [
72910
73038
  "bootout",
72911
- "system",
73039
+ domain,
72912
73040
  layout.definitionPath
72913
73041
  ]).catch(() => {});
72914
73042
  await deps.run("launchctl", [
72915
73043
  "bootstrap",
72916
- "system",
73044
+ domain,
72917
73045
  layout.definitionPath
72918
73046
  ]);
72919
- await deps.run("launchctl", ["enable", `system/${layout.serviceId}`]);
73047
+ await deps.run("launchctl", ["enable", `${domain}/${layout.serviceId}`]);
72920
73048
  await deps.run("launchctl", [
72921
73049
  "kickstart",
72922
73050
  "-k",
72923
- `system/${layout.serviceId}`
73051
+ `${domain}/${layout.serviceId}`
72924
73052
  ]);
72925
73053
  pruneStagedServiceRuntimes(layout, staged.runtimeDir);
72926
73054
  }
72927
- async function installWindows(deps, layout, recoveryPassword) {
72928
- const accountPassword = randomBytes(32).toString("base64url");
72929
- const ps = renderWindowsAccountScript(layout.account, accountPassword);
72930
- await deps.run("powershell.exe", [
72931
- "-NoProfile",
72932
- "-NonInteractive",
72933
- "-Command",
72934
- ps
72935
- ]);
73055
+ async function installWindows(deps, layout, recoveryUsername, recoveryPassword) {
73056
+ const accountPassword = layout.accountMode === "dedicated" ? randomBytes(32).toString("base64url") : null;
73057
+ if (accountPassword) {
73058
+ const ps = renderWindowsAccountScript(layout.account, accountPassword);
73059
+ await deps.run("powershell.exe", [
73060
+ "-NoProfile",
73061
+ "-NonInteractive",
73062
+ "-Command",
73063
+ ps
73064
+ ]);
73065
+ }
72936
73066
  fs.mkdirSync(layout.workspaceDir, { recursive: true });
72937
- await deps.run("icacls.exe", [
73067
+ if (layout.accountMode === "dedicated") await deps.run("icacls.exe", [
72938
73068
  layout.stateDir,
72939
73069
  "/inheritance:r",
72940
73070
  "/grant:r",
@@ -72947,6 +73077,7 @@ async function installWindows(deps, layout, recoveryPassword) {
72947
73077
  fs.writeFileSync(layout.definitionPath, renderWindowsServiceScript({
72948
73078
  ...staged.dependencies,
72949
73079
  layout,
73080
+ recoveryUsername,
72950
73081
  recoveryPassword
72951
73082
  }), "utf8");
72952
73083
  await deps.run("schtasks.exe", [
@@ -72954,16 +73085,23 @@ async function installWindows(deps, layout, recoveryPassword) {
72954
73085
  "/TN",
72955
73086
  layout.serviceId
72956
73087
  ]).catch(() => {});
73088
+ const accountArguments = accountPassword ? [
73089
+ "/RU",
73090
+ `.\\${layout.account}`,
73091
+ "/RP",
73092
+ accountPassword
73093
+ ] : [
73094
+ "/RU",
73095
+ layout.account,
73096
+ "/NP"
73097
+ ];
72957
73098
  await deps.run("schtasks.exe", [
72958
73099
  "/Create",
72959
73100
  "/TN",
72960
73101
  layout.serviceId,
72961
73102
  "/SC",
72962
- "ONSTART",
72963
- "/RU",
72964
- `.\\${layout.account}`,
72965
- "/RP",
72966
- accountPassword,
73103
+ layout.accountMode === "dedicated" ? "ONSTART" : "ONLOGON",
73104
+ ...accountArguments,
72967
73105
  "/TR",
72968
73106
  layout.definitionPath,
72969
73107
  "/F"
@@ -72987,21 +73125,90 @@ $userList='HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\Spe
72987
73125
  New-Item -Path $userList -Force | Out-Null
72988
73126
  New-ItemProperty -Path $userList -Name $name -PropertyType DWord -Value 0 -Force | Out-Null`;
72989
73127
  }
72990
- async function installService(overrides = {}) {
73128
+ function systemServiceLayoutMetadataPath(platform) {
73129
+ if (platform === "linux") return "/etc/shioricode/service-layout.json";
73130
+ if (platform === "darwin") return "/Library/Preferences/codes.shiori.shioricode.service-layout.json";
73131
+ const programData = process.env.ProgramData || "C:\\ProgramData";
73132
+ return path.win32.join(programData, "ShioriCodeService", "service-layout.json");
73133
+ }
73134
+ function serviceLayoutMetadataPath(layout) {
73135
+ if (layout.accountMode === "dedicated") return systemServiceLayoutMetadataPath(layout.platform);
73136
+ const metadataHome = defaultExistingHomeDir(layout.platform, invokingUsername());
73137
+ if (layout.platform === "linux") return path.join(metadataHome, ".config/shioricode/service-layout.json");
73138
+ if (layout.platform === "darwin") return path.join(metadataHome, "Library/Application Support/ShioriCode Service/service-layout.json");
73139
+ return path.win32.join(process.env.LOCALAPPDATA || path.win32.join(metadataHome, "AppData", "Local"), "ShioriCodeService", "service-layout.json");
73140
+ }
73141
+ function writeServiceLayout(layout) {
73142
+ const metadataPath = serviceLayoutMetadataPath(layout);
73143
+ fs.mkdirSync(path.dirname(metadataPath), {
73144
+ recursive: true,
73145
+ mode: 493
73146
+ });
73147
+ fs.writeFileSync(metadataPath, `${JSON.stringify({
73148
+ version: SERVICE_LAYOUT_VERSION,
73149
+ layout
73150
+ }, null, 2)}\n`, { mode: 384 });
73151
+ if (layout.accountMode === "dedicated" && layout.platform !== "win32") {
73152
+ fs.chownSync(metadataPath, 0, 0);
73153
+ fs.chmodSync(metadataPath, 384);
73154
+ }
73155
+ }
73156
+ function readServiceLayout(metadataPath, platform) {
73157
+ try {
73158
+ const parsed = JSON.parse(fs.readFileSync(metadataPath, "utf8"));
73159
+ const layout = parsed.layout;
73160
+ if (parsed.version !== SERVICE_LAYOUT_VERSION || layout?.platform !== platform || layout.accountMode !== "dedicated" && layout.accountMode !== "current" || typeof layout.account !== "string" || typeof layout.homeDir !== "string" || typeof layout.stateDir !== "string" || typeof layout.workspaceDir !== "string" || typeof layout.definitionPath !== "string" || typeof layout.logPath !== "string" || typeof layout.servicePath !== "string" || typeof layout.serviceId !== "string" || typeof layout.port !== "number") return null;
73161
+ return layout;
73162
+ } catch {
73163
+ return null;
73164
+ }
73165
+ }
73166
+ function readInstalledServiceLayout(platform) {
73167
+ const currentLayout = readServiceLayout(serviceLayoutMetadataPath(serviceLayout(platform, { accountMode: "current" })), platform);
73168
+ if (currentLayout && fs.existsSync(currentLayout.definitionPath)) return currentLayout;
73169
+ const systemLayout = readServiceLayout(systemServiceLayoutMetadataPath(platform), platform);
73170
+ if (systemLayout && fs.existsSync(systemLayout.definitionPath)) return systemLayout;
73171
+ const legacy = serviceLayout(platform);
73172
+ return fs.existsSync(legacy.definitionPath) ? legacy : null;
73173
+ }
73174
+ function recoveryPasswordFromOptions(options) {
73175
+ if (!options.recoveryPasswordFile) return {
73176
+ password: randomBytes(32).toString("base64url"),
73177
+ generated: true
73178
+ };
73179
+ const password = fs.readFileSync(options.recoveryPasswordFile, "utf8").replace(/\r?\n$/, "");
73180
+ if (!password) throw new Error("The recovery password file is empty");
73181
+ if (/[\0\r\n]/.test(password)) throw new Error("The recovery password file must contain exactly one password line");
73182
+ return {
73183
+ password,
73184
+ generated: false
73185
+ };
73186
+ }
73187
+ async function installService(options = {}, overrides = {}) {
72991
73188
  const deps = {
72992
73189
  ...defaultDependencies$2(),
72993
73190
  ...overrides
72994
73191
  };
72995
- const layout = serviceLayout(deps.platform);
72996
- requireAdministrator(deps.platform);
72997
- const recoveryPassword = randomBytes(32).toString("base64url");
72998
- if (deps.platform === "linux") await installLinux(deps, layout, recoveryPassword);
72999
- if (deps.platform === "darwin") await installMac(deps, layout, recoveryPassword);
73000
- if (deps.platform === "win32") await installWindows(deps, layout, recoveryPassword);
73192
+ const layout = serviceLayout(deps.platform, options);
73193
+ if (layout.accountMode === "current" && layout.account !== invokingUsername()) throw new Error("Current-account services must run as the invoking OS user");
73194
+ if (layout.accountMode === "dedicated") requireAdministrator(deps.platform);
73195
+ if (options.disableRecoveryLogin && (options.recoveryUsername || options.recoveryPasswordFile)) throw new Error("--no-recovery-login cannot be combined with recovery username/password options");
73196
+ const recoveryUsername = options.disableRecoveryLogin ? null : options.recoveryUsername?.trim() || RECOVERY_USERNAME;
73197
+ if (recoveryUsername !== null && !/^[A-Za-z0-9._@-]{1,128}$/.test(recoveryUsername)) throw new Error("The recovery username contains unsupported characters");
73198
+ const recoveryPassword = options.disableRecoveryLogin ? {
73199
+ password: null,
73200
+ generated: false
73201
+ } : recoveryPasswordFromOptions(options);
73202
+ if (deps.platform === "win32" && recoveryPassword.password !== null && /[%"]/.test(recoveryPassword.password)) throw new Error("Windows recovery passwords cannot contain `%` or `\"`");
73203
+ if (deps.platform === "linux") await installLinux(deps, layout, recoveryUsername, recoveryPassword.password);
73204
+ if (deps.platform === "darwin") await installMac(deps, layout, recoveryUsername, recoveryPassword.password);
73205
+ if (deps.platform === "win32") await installWindows(deps, layout, recoveryUsername, recoveryPassword.password);
73206
+ writeServiceLayout(layout);
73001
73207
  return {
73002
73208
  layout,
73003
- recoveryUsername: RECOVERY_USERNAME,
73004
- recoveryPassword
73209
+ recoveryUsername,
73210
+ recoveryPassword: recoveryPassword.password,
73211
+ recoveryPasswordGenerated: recoveryPassword.generated
73005
73212
  };
73006
73213
  }
73007
73214
  async function readLog(logPath) {
@@ -73017,37 +73224,47 @@ async function controlService(action, overrides = {}) {
73017
73224
  ...defaultDependencies$2(),
73018
73225
  ...overrides
73019
73226
  };
73020
- const layout = serviceLayout(deps.platform);
73021
- requireAdministrator(deps.platform);
73227
+ const layout = readInstalledServiceLayout(deps.platform) ?? serviceLayout(deps.platform);
73228
+ if (layout.accountMode === "dedicated") requireAdministrator(deps.platform);
73022
73229
  if (action === "logs") return await readLog(layout.logPath);
73023
73230
  if (deps.platform === "linux") {
73231
+ const systemctl = layout.accountMode === "current" ? ["--user"] : [];
73024
73232
  if (action === "uninstall") {
73025
73233
  await deps.run("systemctl", [
73234
+ ...systemctl,
73026
73235
  "disable",
73027
73236
  "--now",
73028
73237
  layout.serviceId
73029
73238
  ]).catch(() => {});
73030
73239
  fs.rmSync(layout.definitionPath, { force: true });
73031
- await deps.run("systemctl", ["daemon-reload"]);
73240
+ await deps.run("systemctl", [...systemctl, "daemon-reload"]);
73241
+ fs.rmSync(serviceLayoutMetadataPath(layout), { force: true });
73032
73242
  return "Removed the systemd service. ShioriCode data and the service account were preserved.";
73033
73243
  }
73034
73244
  if (action === "status") return (await deps.run("systemctl", [
73245
+ ...systemctl,
73035
73246
  "show",
73036
73247
  layout.serviceId,
73037
73248
  "--no-pager",
73038
73249
  "--property=LoadState,UnitFileState,ActiveState,SubState,MainPID,ExecMainStatus"
73039
73250
  ])).stdout.trim();
73040
- return (await deps.run("systemctl", [action, layout.serviceId])).stdout.trim() || `Service ${action} complete.`;
73251
+ return (await deps.run("systemctl", [
73252
+ ...systemctl,
73253
+ action,
73254
+ layout.serviceId
73255
+ ])).stdout.trim() || `Service ${action} complete.`;
73041
73256
  }
73042
73257
  if (deps.platform === "darwin") {
73043
- const target = `system/${layout.serviceId}`;
73258
+ const domain = layout.accountMode === "dedicated" ? "system" : `gui/${invokingUid()}`;
73259
+ const target = `${domain}/${layout.serviceId}`;
73044
73260
  if (action === "uninstall") {
73045
73261
  await deps.run("launchctl", [
73046
73262
  "bootout",
73047
- "system",
73263
+ domain,
73048
73264
  layout.definitionPath
73049
73265
  ]).catch(() => {});
73050
73266
  fs.rmSync(layout.definitionPath, { force: true });
73267
+ fs.rmSync(serviceLayoutMetadataPath(layout), { force: true });
73051
73268
  return "Removed the launch daemon. ShioriCode data and the service account were preserved.";
73052
73269
  }
73053
73270
  if (action === "status") try {
@@ -73058,20 +73275,20 @@ async function controlService(action, overrides = {}) {
73058
73275
  if (action === "stop") {
73059
73276
  await deps.run("launchctl", [
73060
73277
  "bootout",
73061
- "system",
73278
+ domain,
73062
73279
  layout.definitionPath
73063
73280
  ]);
73064
73281
  return "Service stop complete.";
73065
73282
  }
73066
73283
  if (action === "restart") await deps.run("launchctl", [
73067
73284
  "bootout",
73068
- "system",
73285
+ domain,
73069
73286
  layout.definitionPath
73070
73287
  ]).catch(() => {});
73071
73288
  if (action === "start" || action === "restart") {
73072
73289
  await deps.run("launchctl", [
73073
73290
  "bootstrap",
73074
- "system",
73291
+ domain,
73075
73292
  layout.definitionPath
73076
73293
  ]).catch(() => {});
73077
73294
  await deps.run("launchctl", ["enable", target]);
@@ -73095,6 +73312,7 @@ async function controlService(action, overrides = {}) {
73095
73312
  layout.serviceId,
73096
73313
  "/F"
73097
73314
  ]);
73315
+ fs.rmSync(serviceLayoutMetadataPath(layout), { force: true });
73098
73316
  return "Removed the Windows background task. ShioriCode data and the service account were preserved.";
73099
73317
  }
73100
73318
  if (action === "status") return (await deps.run("schtasks.exe", [
@@ -73133,11 +73351,13 @@ function serviceSummary(layout = serviceLayout(supportedPlatform(process.platfor
73133
73351
  return [
73134
73352
  `Platform: ${layout.platform}`,
73135
73353
  `Service: ${layout.serviceId}`,
73136
- `Account: ${layout.account}`,
73354
+ `Account: ${layout.account} (${layout.accountMode})`,
73355
+ `Home: ${layout.homeDir}`,
73137
73356
  `Data: ${layout.stateDir}`,
73138
73357
  `Workspaces: ${layout.workspaceDir}`,
73139
73358
  `Logs: ${layout.logPath}`,
73140
- `Host: http://127.0.0.1:${SERVICE_PORT}`
73359
+ `PATH: ${layout.servicePath}`,
73360
+ `Host: http://127.0.0.1:${layout.port}`
73141
73361
  ].join("\n");
73142
73362
  }
73143
73363
  const currentServicePlatform = () => supportedPlatform(os.platform());
@@ -73146,8 +73366,7 @@ function requireServiceAdministrator(platform = currentServicePlatform()) {
73146
73366
  return platform;
73147
73367
  }
73148
73368
  function findInstalledServiceLayout() {
73149
- const layout = serviceLayout(currentServicePlatform());
73150
- return fs.existsSync(layout.definitionPath) ? layout : null;
73369
+ return readInstalledServiceLayout(currentServicePlatform());
73151
73370
  }
73152
73371
  function installedServiceLayout() {
73153
73372
  const layout = findInstalledServiceLayout();
@@ -73158,14 +73377,13 @@ function linkServiceStateDir(layout = installedServiceLayout()) {
73158
73377
  return path.join(layout.stateDir, "userdata");
73159
73378
  }
73160
73379
  async function repairServiceStateOwnership(layout = installedServiceLayout()) {
73161
- if (layout.platform === "win32") return;
73162
- const owner = `${layout.account}:${layout.account}`;
73163
- await runDefault("chown", [owner, layout.stateDir]);
73380
+ if (layout.platform === "win32" || layout.accountMode === "current") return;
73381
+ await runDefault("chown", [layout.account, layout.stateDir]);
73164
73382
  for (const entry of fs.readdirSync(layout.stateDir, { withFileTypes: true })) {
73165
73383
  if (entry.name === "runtime") continue;
73166
73384
  await runDefault("chown", [
73167
73385
  "-R",
73168
- owner,
73386
+ layout.account,
73169
73387
  path.join(layout.stateDir, entry.name)
73170
73388
  ]);
73171
73389
  }
@@ -73246,9 +73464,8 @@ async function waitForDeviceAuthorization(input) {
73246
73464
  throw new Error("The GitHub authorization code expired");
73247
73465
  }
73248
73466
  function serviceLinkContext() {
73249
- const platform = requireServiceAdministrator();
73250
73467
  const layout = installedServiceLayout();
73251
- if (layout.platform !== platform) throw new Error("Service platform changed unexpectedly");
73468
+ if (layout.accountMode === "dedicated") requireServiceAdministrator(layout.platform);
73252
73469
  const store = new LinkRemoteStore({ stateDir: linkServiceStateDir(layout) });
73253
73470
  return {
73254
73471
  layout,
@@ -73414,18 +73631,13 @@ const PROVIDERS = [
73414
73631
  credentialPaths: [".kimi"]
73415
73632
  }
73416
73633
  ];
73417
- function servicePath(platform) {
73418
- if (platform === "darwin") return "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
73419
- if (platform === "linux") return "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
73420
- return process.env.Path ?? process.env.PATH ?? "";
73421
- }
73422
- async function binaryVersion(binary, home) {
73634
+ async function binaryVersion(binary, home, configuredPath) {
73423
73635
  try {
73424
73636
  const result = await execFile$4(binary, ["--version"], {
73425
73637
  env: {
73426
73638
  ...process.env,
73427
73639
  HOME: home,
73428
- PATH: servicePath(process.platform)
73640
+ PATH: configuredPath
73429
73641
  },
73430
73642
  timeout: 5e3,
73431
73643
  windowsHide: true
@@ -73439,26 +73651,27 @@ function hasCredentials(home, candidates) {
73439
73651
  return candidates.some((candidate) => fs.existsSync(path.join(home, candidate)));
73440
73652
  }
73441
73653
  function serviceAccountCommand(command) {
73442
- const layout = serviceLayout(currentServicePlatform());
73654
+ const layout = findInstalledServiceLayout() ?? serviceLayout(currentServicePlatform());
73655
+ if (layout.accountMode === "current") return command;
73443
73656
  if (layout.platform === "linux") return `sudo -u ${layout.account} -H ${command}`;
73444
73657
  if (layout.platform === "darwin") return `sudo -u ${layout.account} -H env PATH=/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin ${command}`;
73445
73658
  return `${command} (run it for the ${layout.account} service account)`;
73446
73659
  }
73447
73660
  async function providerDoctor() {
73448
- const layout = serviceLayout(currentServicePlatform());
73661
+ const layout = findInstalledServiceLayout() ?? serviceLayout(currentServicePlatform());
73449
73662
  const lines = [
73450
73663
  `Service account: ${layout.account}`,
73451
- `Service home: ${layout.stateDir}`,
73664
+ `Service home: ${layout.homeDir}`,
73452
73665
  ""
73453
73666
  ];
73454
73667
  for (const provider of PROVIDERS) {
73455
- const version = await binaryVersion(provider.binary, layout.stateDir);
73668
+ const version = await binaryVersion(provider.binary, layout.homeDir, layout.servicePath);
73456
73669
  if (!version) {
73457
73670
  lines.push(`✗ ${provider.name}: not found in the service PATH`);
73458
73671
  lines.push(` Install: ${provider.installHint}`);
73459
73672
  continue;
73460
73673
  }
73461
- const authenticated = hasCredentials(layout.stateDir, provider.credentialPaths);
73674
+ const authenticated = hasCredentials(layout.homeDir, provider.credentialPaths);
73462
73675
  lines.push(`✓ ${provider.name}: ${version}`);
73463
73676
  lines.push(authenticated ? " Credentials detected in the service home" : ` Sign in: ${serviceAccountCommand(provider.authHint)}`);
73464
73677
  }
@@ -83765,7 +83978,7 @@ function resolveTarget(baseDir, deps) {
83765
83978
  }
83766
83979
  async function withRemoteRpc(target, deps, run) {
83767
83980
  if (!target.service) return await deps.withLocalRpc(target.baseDir, run);
83768
- deps.requireAdministrator(target.service.platform);
83981
+ if (target.service.accountMode === "dedicated") deps.requireAdministrator(target.service.platform);
83769
83982
  const connection = await deps.connectService(target.service.stateDir);
83770
83983
  if (!connection) throw new Error("The ShioriCode service is installed but not reachable. Run `shioricode service start`, then retry.");
83771
83984
  try {
@@ -216800,18 +217013,21 @@ function builtInShioriMcpServers(input) {
216800
217013
  return servers.map((server) => Object.assign({}, server, { providers: [provider] }));
216801
217014
  }
216802
217015
  function makeBuiltInStdioMcpServerEntry(name, subcommand, env) {
216803
- const entry = {
217016
+ return {
216804
217017
  name,
216805
217018
  transport: "stdio",
216806
217019
  command: process.execPath,
216807
217020
  args: [...serverEntrypointArgs(), subcommand],
216808
217021
  enabled: true,
216809
- providers: []
217022
+ providers: [],
217023
+ env: makeBuiltInStdioMcpEnvironment(env)
217024
+ };
217025
+ }
217026
+ function makeBuiltInStdioMcpEnvironment(env) {
217027
+ return {
217028
+ ...env,
217029
+ ELECTRON_RUN_AS_NODE: "1"
216810
217030
  };
216811
- return env ? {
216812
- ...entry,
216813
- env
216814
- } : entry;
216815
217031
  }
216816
217032
  function threadGoalMcpServerEntry(input) {
216817
217033
  return makeBuiltInStdioMcpServerEntry(THREAD_GOAL_MCP_SERVER_NAME, "thread-goal-mcp", {
@@ -219725,7 +219941,7 @@ const getTelemetryIdentifier = gen$5(function* () {
219725
219941
 
219726
219942
  //#endregion
219727
219943
  //#region package.json
219728
- var version$1 = "0.5.2";
219944
+ var version$1 = "0.5.3";
219729
219945
 
219730
219946
  //#endregion
219731
219947
  //#region src/telemetry/Layers/AnalyticsService.ts
@@ -703211,14 +703427,42 @@ const serveCommand = make$166("serve", commandFlags).pipe(withDescription$3("Run
703211
703427
  const serviceActionCommand = (action, description) => make$166(action).pipe(withDescription$3(description), withHandler(() => promise$5(async () => {
703212
703428
  console.log(await controlService(action));
703213
703429
  })));
703430
+ const serviceInstallFlags = {
703431
+ accountMode: choice$2("account", ["dedicated", "current"]).pipe(withDescription$4("OS account strategy: create/reuse a dedicated service user, or run as the invoking user."), withDefault$2("dedicated")),
703432
+ account: string$10("user").pipe(withDescription$4("Override the OS username. Dedicated mode creates it when missing; current mode requires it to exist."), optional$3),
703433
+ homeDir: string$10("home-dir").pipe(withDescription$4("HOME exposed to provider CLIs in the service process."), optional$3),
703434
+ stateDir: string$10("state-dir").pipe(withDescription$4("ShioriCode service data directory."), optional$3),
703435
+ workspaceDir: string$10("workspace-dir").pipe(withDescription$4("Default working directory for the service."), optional$3),
703436
+ logPath: string$10("log-file").pipe(withDescription$4("Log file for stdout and stderr from the background service."), optional$3),
703437
+ servicePath: string$10("service-path").pipe(withDescription$4("PATH used to discover provider CLIs such as codex, claude, and kimi in the service."), optional$3),
703438
+ port: integer$4("port").pipe(withSchema(PortSchema), withDescription$4("Loopback port for the background service."), optional$3),
703439
+ recoveryUsername: string$10("recovery-username").pipe(withDescription$4("Username for direct recovery login. Defaults to `recovery`."), optional$3),
703440
+ recoveryPasswordFile: string$10("recovery-password-file").pipe(withDescription$4("Read the direct recovery password from a local file instead of generating one. The file must not be empty."), optional$3),
703441
+ disableRecoveryLogin: boolean$8("no-recovery-login").pipe(withDescription$4("Disable direct username/password recovery login. ShioriCode Link remains GitHub-only."), withDefault$2(false))
703442
+ };
703214
703443
  const serviceCommand = make$166("service").pipe(withDescription$3("Install and control the OS background service."), withSubcommands([
703215
- make$166("install").pipe(withDescription$3("Install and start ShioriCode under a dedicated OS account."), withHandler(() => promise$5(async () => {
703216
- const result = await installService();
703444
+ make$166("install", serviceInstallFlags).pipe(withDescription$3("Install and start ShioriCode as a dedicated or existing OS user."), withHandler((flags) => promise$5(async () => {
703445
+ const result = await installService({
703446
+ accountMode: flags.accountMode,
703447
+ account: getOrUndefined$3(flags.account),
703448
+ homeDir: getOrUndefined$3(flags.homeDir),
703449
+ stateDir: getOrUndefined$3(flags.stateDir),
703450
+ workspaceDir: getOrUndefined$3(flags.workspaceDir),
703451
+ logPath: getOrUndefined$3(flags.logPath),
703452
+ servicePath: getOrUndefined$3(flags.servicePath),
703453
+ port: getOrUndefined$3(flags.port),
703454
+ recoveryUsername: getOrUndefined$3(flags.recoveryUsername),
703455
+ recoveryPasswordFile: getOrUndefined$3(flags.recoveryPasswordFile),
703456
+ disableRecoveryLogin: flags.disableRecoveryLogin
703457
+ });
703217
703458
  console.log("ShioriCode service installed and started.\n");
703218
703459
  console.log(serviceSummary(result.layout));
703219
- console.log("\nLocal recovery credentials (store these securely):");
703220
- console.log(`Username: ${result.recoveryUsername}`);
703221
- console.log(`Password: ${result.recoveryPassword}`);
703460
+ console.log("\nLocal recovery login:");
703461
+ if (result.recoveryUsername === null || result.recoveryPassword === null) console.log("Disabled. Hosted access uses GitHub through ShioriCode Link.");
703462
+ else {
703463
+ console.log(`Username: ${result.recoveryUsername}`);
703464
+ console.log(result.recoveryPasswordGenerated ? `Generated password: ${result.recoveryPassword}\nStore it securely; it is shown once.` : "Password: loaded from --recovery-password-file (not echoed)");
703465
+ }
703222
703466
  console.log("\nNext: shioricode link connect");
703223
703467
  }))),
703224
703468
  serviceActionCommand("start", "Start the ShioriCode service."),