shioricode 0.5.1 → 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 (57) hide show
  1. package/README.md +22 -1
  2. package/dist/bin.cjs +386 -142
  3. package/dist/bin.mjs +386 -142
  4. package/dist/client/assets/{ArtifactPanel-1Tg5ZLm5.js → ArtifactPanel-ClA97FO8.js} +2 -2
  5. package/dist/client/assets/{ChatMarkdown-BvwoXC6z.js → ChatMarkdown-DkldD1_B.js} +2 -2
  6. package/dist/client/assets/{DiffPanel-D-xp36wD.js → DiffPanel-BYSH-G7M.js} +3 -3
  7. package/dist/client/assets/{DiffPanelShell-DdBr4p9c.js → DiffPanelShell-Ce2wOE9L.js} +2 -2
  8. package/dist/client/assets/{DiffWorkerPoolProvider--93ReMXi.js → DiffWorkerPoolProvider-DjSdGkRQ.js} +2 -2
  9. package/dist/client/assets/{NoActiveThreadState-B_C5Y_aO.js → NoActiveThreadState-CVDfDuF2.js} +2 -2
  10. package/dist/client/assets/{ProviderModelPicker-BWTkQW8f.js → ProviderModelPicker-BFlX8lZL.js} +2 -2
  11. package/dist/client/assets/SettingsPanels-_8_YaXgc.js +4 -0
  12. package/dist/client/assets/{_chat-BhqvWJHj.js → _chat-CQ5UewiP.js} +2 -2
  13. package/dist/client/assets/_chat._threadId-5846z-_l.js +289 -0
  14. package/dist/client/assets/{_chat.index-oZTnMcB3.js → _chat.index-DI7egWLc.js} +2 -2
  15. package/dist/client/assets/automations-D6SQKJ83.js +2 -0
  16. package/dist/client/assets/{badge-BkzSFcY-.js → badge-iReKY3O4.js} +2 -2
  17. package/dist/client/assets/{clientSettings--1UP2ayD.js → clientSettings-DFgmWajw.js} +2 -2
  18. package/dist/client/assets/{collapsible-CqRg_DyH.js → collapsible-BEW6Xu4N.js} +2 -2
  19. package/dist/client/assets/{dialog-C7Ppq2qI.js → dialog-CZ2IjObB.js} +2 -2
  20. package/dist/client/assets/{gitReactQuery-BBOGuHot.js → gitReactQuery-BxToLb49.js} +2 -2
  21. package/dist/client/assets/{index-DvBh1mNh.js → index-B42nGfCf.js} +5 -5
  22. package/dist/client/assets/{input-emuaAuIe.js → input-CthhcRBE.js} +2 -2
  23. package/dist/client/assets/pull-requests-DGa0NhIZ.js +46 -0
  24. package/dist/client/assets/{queuedTurnsStore-I4LoXHLF.js → queuedTurnsStore-CYufNsaQ.js} +2 -2
  25. package/dist/client/assets/{scroll-area-kK2XF1gm.js → scroll-area-D6P_hAXV.js} +2 -2
  26. package/dist/client/assets/{separator-CSqq2xHm.js → separator-ByukCTaZ.js} +2 -2
  27. package/dist/client/assets/{settings-C9R1j3GG.js → settings-BwPioqTL.js} +2 -2
  28. package/dist/client/assets/{settings.appearance-Cac_fbGk.js → settings.appearance-DxQzPuf5.js} +2 -2
  29. package/dist/client/assets/settings.archived-BSqIxOKY.js +2 -0
  30. package/dist/client/assets/{settings.computer-use-BL801Zu5.js → settings.computer-use-DuuUffvc.js} +2 -2
  31. package/dist/client/assets/{settings.credits-Brx-cGXe.js → settings.credits-DiXAbJQa.js} +2 -2
  32. package/dist/client/assets/settings.general-DjIbwGSI.js +2 -0
  33. package/dist/client/assets/{settings.mcp-DEMm4v5q.js → settings.mcp-CHZOA1L-.js} +2 -2
  34. package/dist/client/assets/{settings.mobile-DF1raoba.js → settings.mobile-D3UiSzxp.js} +2 -2
  35. package/dist/client/assets/{settings.plugins-CewrpoKP.js → settings.plugins-qo7Fls2x.js} +2 -2
  36. package/dist/client/assets/{settings.remote-BufK_lG_.js → settings.remote-CmdhJhKY.js} +2 -2
  37. package/dist/client/assets/{settings.skills-Cz9xy1ba.js → settings.skills-UB5Wtkl5.js} +2 -2
  38. package/dist/client/assets/{settings.theme-builder-BU_DRAvo.js → settings.theme-builder-CnwNYRga.js} +2 -2
  39. package/dist/client/assets/{skillsMcpShared-DxxY1mEt.js → skillsMcpShared-CiunLdEL.js} +2 -2
  40. package/dist/client/assets/{store-BQMb1nBV.js → store-LT9wKN7R.js} +3 -3
  41. package/dist/client/assets/{terminalStateStore-C38k-Eaw.js → terminalStateStore-B-V7Awi_.js} +2 -2
  42. package/dist/client/assets/{textarea-DXuuOfmM.js → textarea-DAzODx1v.js} +2 -2
  43. package/dist/client/assets/{toggle-group-CvAnsecf.js → toggle-group-CMWeXyeK.js} +2 -2
  44. package/dist/client/assets/{toggle-nUm5N7vp.js → toggle-uzFsauJb.js} +2 -2
  45. package/dist/client/assets/{uiStateStore-Di3ieZaJ.js → uiStateStore-CgvPrTF8.js} +2 -2
  46. package/dist/client/assets/{useHandleNewThread-WYMZPumx.js → useHandleNewThread-Chs2blac.js} +2 -2
  47. package/dist/client/assets/{useSettings-DOuHg9jg.js → useSettings-CaigvboL.js} +2 -2
  48. package/dist/client/assets/{useTheme-Dhz9U3on.js → useTheme-7QjQMsSZ.js} +2 -2
  49. package/dist/client/assets/{vendor-ui-DmvKhMzf.js → vendor-ui-Ds9VgrYb.js} +2 -2
  50. package/dist/client/index.html +12 -12
  51. package/package.json +1 -1
  52. package/dist/client/assets/SettingsPanels-DGYlfoWv.js +0 -4
  53. package/dist/client/assets/_chat._threadId-Blwb3CRP.js +0 -289
  54. package/dist/client/assets/automations-ChGelqHO.js +0 -2
  55. package/dist/client/assets/pull-requests-xKFO2G81.js +0 -46
  56. package/dist/client/assets/settings.archived-KRU4XARw.js +0 -2
  57. package/dist/client/assets/settings.general-BXcV60tY.js +0 -2
package/dist/bin.cjs CHANGED
@@ -72458,42 +72458,112 @@ var RemoteStateStore = class {
72458
72458
  const execFile$10 = (0, node_util.promisify)(node_child_process.execFile);
72459
72459
  const SERVICE_PORT = 3773;
72460
72460
  const RECOVERY_USERNAME = "recovery";
72461
+ const SERVICE_LAYOUT_VERSION = 1;
72462
+ function invokingUsername() {
72463
+ const sudoUser = process.env.SUDO_USER?.trim();
72464
+ if (sudoUser && sudoUser !== "root") return sudoUser;
72465
+ return node_os.default.userInfo().username;
72466
+ }
72467
+ function invokingUid() {
72468
+ const sudoUid = Number(process.env.SUDO_UID);
72469
+ if (Number.isInteger(sudoUid) && sudoUid >= 0) return sudoUid;
72470
+ return typeof process.getuid === "function" ? process.getuid() : 0;
72471
+ }
72472
+ function defaultAccount(platform) {
72473
+ if (platform === "darwin") return "_shioricode";
72474
+ if (platform === "linux") return "shioricode";
72475
+ return "ShioriCode";
72476
+ }
72477
+ function defaultExistingHomeDir(platform, account) {
72478
+ if (account === node_os.default.userInfo().username) return node_os.default.homedir();
72479
+ if (platform === "darwin") return node_path.default.join("/Users", account);
72480
+ if (platform === "linux") return account === "root" ? "/root" : node_path.default.join("/home", account);
72481
+ return process.env.USERPROFILE || node_os.default.homedir();
72482
+ }
72483
+ function defaultServicePath(platform) {
72484
+ if (platform === "darwin") return "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
72485
+ if (platform === "linux") return "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
72486
+ return process.env.Path ?? process.env.PATH ?? "";
72487
+ }
72488
+ function validateServiceAccount(account) {
72489
+ if (!/^[A-Za-z_][A-Za-z0-9._-]*\$?$/.test(account)) throw new Error(`Invalid service username: ${account}`);
72490
+ }
72491
+ function validateServiceLayout(layout) {
72492
+ const isAbsolute = layout.platform === "win32" ? node_path.default.win32.isAbsolute : node_path.default.isAbsolute;
72493
+ for (const [label, value] of [
72494
+ ["home directory", layout.homeDir],
72495
+ ["state directory", layout.stateDir],
72496
+ ["workspace directory", layout.workspaceDir],
72497
+ ["service definition", layout.definitionPath],
72498
+ ["log file", layout.logPath]
72499
+ ]) if (!isAbsolute(value) || /[\0\r\n]/.test(value)) throw new Error(`The ${label} must be an absolute path without control characters`);
72500
+ if (/\0|\r|\n/.test(layout.servicePath)) throw new Error("The service PATH cannot contain control characters");
72501
+ if (!Number.isInteger(layout.port) || layout.port < 1 || layout.port > 65535) throw new Error(`Invalid service port: ${layout.port}`);
72502
+ return layout;
72503
+ }
72461
72504
  function supportedPlatform(platform) {
72462
72505
  if (platform === "linux" || platform === "darwin" || platform === "win32") return platform;
72463
72506
  throw new Error(`ShioriCode services are not supported on ${platform} yet`);
72464
72507
  }
72465
- function serviceLayout(platform) {
72508
+ function serviceLayout(platform, options = {}) {
72509
+ const accountMode = options.accountMode ?? "dedicated";
72510
+ const account = options.account?.trim() || (accountMode === "current" ? invokingUsername() : defaultAccount(platform));
72511
+ validateServiceAccount(account);
72466
72512
  switch (platform) {
72467
- case "linux": return {
72468
- platform,
72469
- account: "shioricode",
72470
- stateDir: "/var/lib/shioricode",
72471
- workspaceDir: "/var/lib/shioricode/workspaces",
72472
- definitionPath: "/etc/systemd/system/shioricode.service",
72473
- logPath: "/var/log/shioricode/server.log",
72474
- serviceId: "shioricode.service"
72475
- };
72476
- case "darwin": return {
72477
- platform,
72478
- account: "_shioricode",
72479
- stateDir: "/Library/Application Support/ShioriCode",
72480
- workspaceDir: "/Users/Shared/ShioriCode/Workspaces",
72481
- definitionPath: "/Library/LaunchDaemons/codes.shiori.shioricode.plist",
72482
- logPath: "/Library/Logs/ShioriCode/server.log",
72483
- serviceId: "codes.shiori.shioricode"
72484
- };
72513
+ case "linux": {
72514
+ const dedicatedStateDir = "/var/lib/shioricode";
72515
+ const homeDir = options.homeDir ?? (accountMode === "dedicated" ? dedicatedStateDir : defaultExistingHomeDir(platform, account));
72516
+ const stateDir = options.stateDir ?? (accountMode === "dedicated" ? dedicatedStateDir : node_path.default.join(homeDir, ".shioricode-service"));
72517
+ return validateServiceLayout({
72518
+ platform,
72519
+ accountMode,
72520
+ account,
72521
+ homeDir,
72522
+ stateDir,
72523
+ workspaceDir: options.workspaceDir ?? node_path.default.join(stateDir, "workspaces"),
72524
+ definitionPath: accountMode === "dedicated" ? "/etc/systemd/system/shioricode.service" : node_path.default.join(homeDir, ".config/systemd/user/shioricode.service"),
72525
+ logPath: options.logPath ?? (accountMode === "dedicated" ? "/var/log/shioricode/server.log" : node_path.default.join(stateDir, "server.log")),
72526
+ servicePath: options.servicePath ?? defaultServicePath(platform),
72527
+ serviceId: "shioricode.service",
72528
+ port: options.port ?? SERVICE_PORT
72529
+ });
72530
+ }
72531
+ case "darwin": {
72532
+ const dedicatedStateDir = "/Library/Application Support/ShioriCode";
72533
+ const homeDir = options.homeDir ?? (accountMode === "dedicated" ? dedicatedStateDir : defaultExistingHomeDir(platform, account));
72534
+ const stateDir = options.stateDir ?? (accountMode === "dedicated" ? dedicatedStateDir : node_path.default.join(homeDir, ".shioricode-service"));
72535
+ return validateServiceLayout({
72536
+ platform,
72537
+ accountMode,
72538
+ account,
72539
+ homeDir,
72540
+ stateDir,
72541
+ workspaceDir: options.workspaceDir ?? (accountMode === "dedicated" ? "/Users/Shared/ShioriCode/Workspaces" : node_path.default.join(stateDir, "workspaces")),
72542
+ definitionPath: accountMode === "dedicated" ? "/Library/LaunchDaemons/codes.shiori.shioricode.plist" : node_path.default.join(homeDir, "Library/LaunchAgents/codes.shiori.shioricode.plist"),
72543
+ logPath: options.logPath ?? (accountMode === "dedicated" ? "/Library/Logs/ShioriCode/server.log" : node_path.default.join(stateDir, "server.log")),
72544
+ servicePath: options.servicePath ?? defaultServicePath(platform),
72545
+ serviceId: "codes.shiori.shioricode",
72546
+ port: options.port ?? SERVICE_PORT
72547
+ });
72548
+ }
72485
72549
  case "win32": {
72486
72550
  const programData = process.env.ProgramData || "C:\\ProgramData";
72487
- const stateDir = node_path.default.win32.join(programData, "ShioriCode");
72488
- return {
72551
+ const dedicatedStateDir = node_path.default.win32.join(programData, "ShioriCode");
72552
+ const homeDir = options.homeDir ?? (accountMode === "dedicated" ? dedicatedStateDir : defaultExistingHomeDir(platform, account));
72553
+ const stateDir = options.stateDir ?? (accountMode === "dedicated" ? dedicatedStateDir : node_path.default.win32.join(homeDir, ".shioricode-service"));
72554
+ return validateServiceLayout({
72489
72555
  platform,
72490
- account: "ShioriCode",
72556
+ accountMode,
72557
+ account,
72558
+ homeDir,
72491
72559
  stateDir,
72492
- workspaceDir: node_path.default.win32.join(stateDir, "workspaces"),
72560
+ workspaceDir: options.workspaceDir ?? node_path.default.win32.join(stateDir, "workspaces"),
72493
72561
  definitionPath: node_path.default.win32.join(stateDir, "service.cmd"),
72494
- logPath: node_path.default.win32.join(stateDir, "server.log"),
72495
- serviceId: "ShioriCode"
72496
- };
72562
+ logPath: options.logPath ?? node_path.default.win32.join(stateDir, "server.log"),
72563
+ servicePath: options.servicePath ?? defaultServicePath(platform),
72564
+ serviceId: "ShioriCode",
72565
+ port: options.port ?? SERVICE_PORT
72566
+ });
72497
72567
  }
72498
72568
  }
72499
72569
  }
@@ -72506,20 +72576,21 @@ function xmlEscape(value) {
72506
72576
  function windowsBatchQuote(value) {
72507
72577
  return `"${value.replaceAll("\"", "\"\"")}"`;
72508
72578
  }
72509
- function serviceArguments(layout) {
72510
- return [
72579
+ function serviceArguments(layout, requireAuth) {
72580
+ const args = [
72511
72581
  "serve",
72512
72582
  "--mode",
72513
72583
  "web",
72514
72584
  "--host",
72515
72585
  "127.0.0.1",
72516
72586
  "--port",
72517
- String(SERVICE_PORT),
72587
+ String(layout.port),
72518
72588
  "--base-dir",
72519
72589
  layout.stateDir,
72520
- "--remote",
72521
72590
  "--no-browser"
72522
72591
  ];
72592
+ if (requireAuth) args.splice(-1, 0, "--remote");
72593
+ return args;
72523
72594
  }
72524
72595
  function serviceRuntimeRoot(layout) {
72525
72596
  return node_path.default.join(layout.stateDir, "runtime");
@@ -72532,7 +72603,6 @@ function stageServiceRuntime(deps, layout) {
72532
72603
  const runtimeRoot = serviceRuntimeRoot(layout);
72533
72604
  const runtimeDir = node_path.default.join(runtimeRoot, `${Date.now()}-${(0, node_crypto.randomBytes)(6).toString("hex")}`);
72534
72605
  const packageTarget = node_path.default.join(runtimeDir, "package");
72535
- const nodeTarget = node_path.default.join(runtimeDir, layout.platform === "win32" ? "node.exe" : "node");
72536
72606
  node_fs.default.mkdirSync(runtimeDir, {
72537
72607
  recursive: true,
72538
72608
  mode: 488
@@ -72542,8 +72612,6 @@ function stageServiceRuntime(deps, layout) {
72542
72612
  recursive: true,
72543
72613
  dereference: true
72544
72614
  });
72545
- node_fs.default.copyFileSync(deps.execPath, nodeTarget, node_fs.default.constants.COPYFILE_FICLONE);
72546
- if (layout.platform !== "win32") node_fs.default.chmodSync(nodeTarget, 493);
72547
72615
  } catch (error) {
72548
72616
  node_fs.default.rmSync(runtimeDir, {
72549
72617
  recursive: true,
@@ -72555,7 +72623,6 @@ function stageServiceRuntime(deps, layout) {
72555
72623
  runtimeDir,
72556
72624
  dependencies: {
72557
72625
  ...deps,
72558
- execPath: nodeTarget,
72559
72626
  cliPath: node_path.default.join(packageTarget, "dist", "bin.mjs")
72560
72627
  }
72561
72628
  };
@@ -72580,6 +72647,14 @@ function pruneStagedServiceRuntimes(layout, activeRuntimeDir) {
72580
72647
  }
72581
72648
  }
72582
72649
  async function protectStagedServiceRuntime(deps, layout) {
72650
+ if (layout.accountMode === "current") {
72651
+ if (layout.platform !== "win32") await deps.run("chmod", [
72652
+ "-R",
72653
+ "u+rX",
72654
+ serviceRuntimeRoot(layout)
72655
+ ]);
72656
+ return;
72657
+ }
72583
72658
  const runtimeRoot = serviceRuntimeRoot(layout);
72584
72659
  if (layout.platform === "win32") {
72585
72660
  await deps.run("icacls.exe", [
@@ -72605,11 +72680,14 @@ async function protectStagedServiceRuntime(deps, layout) {
72605
72680
  }
72606
72681
  function renderSystemdUnit(input) {
72607
72682
  const { layout } = input;
72683
+ const hasRecoveryLogin = input.recoveryUsername !== null && input.recoveryPassword !== null;
72608
72684
  const command = [
72609
72685
  input.execPath,
72610
72686
  input.cliPath,
72611
- ...serviceArguments(layout)
72687
+ ...serviceArguments(layout, hasRecoveryLogin)
72612
72688
  ].map(systemdQuote).join(" ");
72689
+ const account = layout.accountMode === "dedicated" ? `User=${layout.account}\nGroup=${layout.account}\n` : "";
72690
+ const recoveryLogin = hasRecoveryLogin ? `Environment=${systemdQuote(`SHIORICODE_USERNAME=${input.recoveryUsername}`)}\nEnvironment=${systemdQuote(`SHIORICODE_PASSWORD=${input.recoveryPassword}`)}\n` : "";
72613
72691
  return `[Unit]
72614
72692
  Description=ShioriCode headless server
72615
72693
  After=network-online.target
@@ -72617,45 +72695,40 @@ Wants=network-online.target
72617
72695
 
72618
72696
  [Service]
72619
72697
  Type=simple
72620
- User=${layout.account}
72621
- Group=${layout.account}
72622
- WorkingDirectory=${layout.workspaceDir}
72623
- Environment=HOME=${layout.stateDir}
72698
+ ${account}WorkingDirectory=${systemdQuote(layout.workspaceDir)}
72699
+ Environment=${systemdQuote(`HOME=${layout.homeDir}`)}
72624
72700
  Environment=NODE_ENV=production
72625
72701
  Environment=SHIORICODE_SERVICE=1
72626
72702
  Environment=SHIORICODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD=false
72627
- Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
72628
- Environment=SHIORICODE_USERNAME=${RECOVERY_USERNAME}
72629
- Environment=SHIORICODE_PASSWORD=${input.recoveryPassword}
72630
- ExecStart=${command}
72703
+ Environment=${systemdQuote(`PATH=${layout.servicePath}`)}
72704
+ ${recoveryLogin}ExecStart=${command}
72631
72705
  Restart=on-failure
72632
72706
  RestartSec=3
72633
72707
  SuccessExitStatus=130 143
72634
72708
  LimitNOFILE=65535
72635
- StandardOutput=append:${layout.logPath}
72636
- StandardError=append:${layout.logPath}
72709
+ StandardOutput=${systemdQuote(`append:${layout.logPath}`)}
72710
+ StandardError=${systemdQuote(`append:${layout.logPath}`)}
72637
72711
 
72638
72712
  [Install]
72639
72713
  WantedBy=multi-user.target
72640
72714
  `;
72641
72715
  }
72642
72716
  function renderLaunchDaemon(input) {
72717
+ const hasRecoveryLogin = input.recoveryUsername !== null && input.recoveryPassword !== null;
72643
72718
  const args = [
72644
72719
  input.execPath,
72645
72720
  input.cliPath,
72646
- ...serviceArguments(input.layout)
72721
+ ...serviceArguments(input.layout, hasRecoveryLogin)
72647
72722
  ].map((value) => ` <string>${xmlEscape(value)}</string>`).join("\n");
72723
+ 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` : "";
72724
+ const recoveryLogin = hasRecoveryLogin ? ` <key>SHIORICODE_USERNAME</key><string>${xmlEscape(input.recoveryUsername)}</string>\n <key>SHIORICODE_PASSWORD</key><string>${xmlEscape(input.recoveryPassword)}</string>\n` : "";
72648
72725
  return `<?xml version="1.0" encoding="UTF-8"?>
72649
72726
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
72650
72727
  <plist version="1.0">
72651
72728
  <dict>
72652
72729
  <key>Label</key>
72653
72730
  <string>${input.layout.serviceId}</string>
72654
- <key>UserName</key>
72655
- <string>${input.layout.account}</string>
72656
- <key>GroupName</key>
72657
- <string>${input.layout.account}</string>
72658
- <key>WorkingDirectory</key>
72731
+ ${account} <key>WorkingDirectory</key>
72659
72732
  <string>${xmlEscape(input.layout.workspaceDir)}</string>
72660
72733
  <key>ProgramArguments</key>
72661
72734
  <array>
@@ -72663,14 +72736,12 @@ ${args}
72663
72736
  </array>
72664
72737
  <key>EnvironmentVariables</key>
72665
72738
  <dict>
72666
- <key>HOME</key><string>${xmlEscape(input.layout.stateDir)}</string>
72739
+ <key>HOME</key><string>${xmlEscape(input.layout.homeDir)}</string>
72667
72740
  <key>NODE_ENV</key><string>production</string>
72668
72741
  <key>SHIORICODE_SERVICE</key><string>1</string>
72669
72742
  <key>SHIORICODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD</key><string>false</string>
72670
- <key>PATH</key><string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
72671
- <key>SHIORICODE_USERNAME</key><string>${RECOVERY_USERNAME}</string>
72672
- <key>SHIORICODE_PASSWORD</key><string>${xmlEscape(input.recoveryPassword)}</string>
72673
- </dict>
72743
+ <key>PATH</key><string>${xmlEscape(input.layout.servicePath)}</string>
72744
+ ${recoveryLogin} </dict>
72674
72745
  <key>RunAtLoad</key><true/>
72675
72746
  <key>KeepAlive</key><true/>
72676
72747
  <key>ThrottleInterval</key><integer>3</integer>
@@ -72681,19 +72752,20 @@ ${args}
72681
72752
  `;
72682
72753
  }
72683
72754
  function renderWindowsServiceScript(input) {
72755
+ const hasRecoveryLogin = input.recoveryUsername !== null && input.recoveryPassword !== null;
72684
72756
  const command = [
72685
72757
  input.execPath,
72686
72758
  input.cliPath,
72687
- ...serviceArguments(input.layout)
72759
+ ...serviceArguments(input.layout, hasRecoveryLogin)
72688
72760
  ].map(windowsBatchQuote).join(" ");
72761
+ const recoveryLogin = hasRecoveryLogin ? `set "SHIORICODE_USERNAME=${input.recoveryUsername}"\r\nset "SHIORICODE_PASSWORD=${input.recoveryPassword}"\r\n` : "";
72689
72762
  return `@echo off\r
72690
- set "HOME=${input.layout.stateDir}"\r
72763
+ set "HOME=${input.layout.homeDir}"\r
72691
72764
  set "NODE_ENV=production"\r
72692
72765
  set "SHIORICODE_SERVICE=1"\r
72693
72766
  set "SHIORICODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD=false"\r
72694
- set "SHIORICODE_USERNAME=${RECOVERY_USERNAME}"\r
72695
- set "SHIORICODE_PASSWORD=${input.recoveryPassword}"\r
72696
- cd /d ${windowsBatchQuote(input.layout.workspaceDir)}\r
72767
+ set "PATH=${input.layout.servicePath}"\r
72768
+ ${recoveryLogin}cd /d ${windowsBatchQuote(input.layout.workspaceDir)}\r
72697
72769
  ${command} >> ${windowsBatchQuote(input.layout.logPath)} 2>&1\r
72698
72770
  `;
72699
72771
  }
@@ -72709,11 +72781,24 @@ function defaultDependencies$2() {
72709
72781
  if (!cliArg) throw new Error("Could not resolve the ShioriCode executable path");
72710
72782
  return {
72711
72783
  platform: supportedPlatform(process.platform),
72712
- execPath: node_fs.default.realpathSync(process.execPath),
72784
+ execPath: stableNodeExecutable(),
72713
72785
  cliPath: node_fs.default.realpathSync(cliArg),
72714
72786
  run: runDefault
72715
72787
  };
72716
72788
  }
72789
+ function stableNodeExecutable() {
72790
+ if (process.platform === "win32") return process.execPath;
72791
+ const pathEntries = (process.env.PATH ?? "").split(node_path.default.delimiter);
72792
+ for (const entry of pathEntries) {
72793
+ if (!entry) continue;
72794
+ const candidate = node_path.default.join(entry, "node");
72795
+ try {
72796
+ node_fs.default.accessSync(candidate, node_fs.default.constants.X_OK);
72797
+ return candidate;
72798
+ } catch {}
72799
+ }
72800
+ return process.execPath;
72801
+ }
72717
72802
  function requireAdministrator(platform) {
72718
72803
  if (platform !== "win32" && typeof process.getuid === "function" && process.getuid() !== 0) throw new Error("Service installation requires administrator privileges. Re-run with sudo.");
72719
72804
  }
@@ -72726,7 +72811,9 @@ async function commandExists$1(run, file, args) {
72726
72811
  }
72727
72812
  }
72728
72813
  async function ensureLinuxAccount(deps, layout) {
72729
- if (!await commandExists$1(deps.run, "id", ["-u", layout.account])) await deps.run("useradd", [
72814
+ const accountExists = await commandExists$1(deps.run, "id", ["-u", layout.account]);
72815
+ if (!accountExists && layout.accountMode === "current") throw new Error(`The requested existing service user ${layout.account} does not exist`);
72816
+ if (!accountExists) await deps.run("useradd", [
72730
72817
  "--system",
72731
72818
  "--home-dir",
72732
72819
  layout.stateDir,
@@ -72735,6 +72822,10 @@ async function ensureLinuxAccount(deps, layout) {
72735
72822
  "/usr/sbin/nologin",
72736
72823
  layout.account
72737
72824
  ]);
72825
+ node_fs.default.mkdirSync(layout.stateDir, {
72826
+ recursive: true,
72827
+ mode: 488
72828
+ });
72738
72829
  node_fs.default.mkdirSync(layout.workspaceDir, {
72739
72830
  recursive: true,
72740
72831
  mode: 488
@@ -72743,14 +72834,15 @@ async function ensureLinuxAccount(deps, layout) {
72743
72834
  recursive: true,
72744
72835
  mode: 488
72745
72836
  });
72837
+ if (layout.accountMode === "current") return;
72746
72838
  await deps.run("chown", [
72747
72839
  "-R",
72748
- `${layout.account}:${layout.account}`,
72840
+ layout.account,
72749
72841
  layout.stateDir
72750
72842
  ]);
72751
72843
  await deps.run("chown", [
72752
72844
  "-R",
72753
- `${layout.account}:${layout.account}`,
72845
+ layout.account,
72754
72846
  node_path.default.dirname(layout.logPath)
72755
72847
  ]);
72756
72848
  }
@@ -72785,6 +72877,22 @@ async function readMacNumericAttribute(deps, record, attribute) {
72785
72877
  }
72786
72878
  }
72787
72879
  async function ensureMacAccount(deps, layout) {
72880
+ if (layout.accountMode === "current") {
72881
+ if (!await commandExists$1(deps.run, "id", ["-u", layout.account])) throw new Error(`The requested existing service user ${layout.account} does not exist`);
72882
+ node_fs.default.mkdirSync(layout.stateDir, {
72883
+ recursive: true,
72884
+ mode: 488
72885
+ });
72886
+ node_fs.default.mkdirSync(layout.workspaceDir, {
72887
+ recursive: true,
72888
+ mode: 488
72889
+ });
72890
+ node_fs.default.mkdirSync(node_path.default.dirname(layout.logPath), {
72891
+ recursive: true,
72892
+ mode: 488
72893
+ });
72894
+ return;
72895
+ }
72788
72896
  const userRecord = `/Users/${layout.account}`;
72789
72897
  const groupRecord = `/Groups/${layout.account}`;
72790
72898
  const existingUid = await readMacNumericAttribute(deps, userRecord, "UniqueID");
@@ -72894,58 +73002,80 @@ async function ensureMacAccount(deps, layout) {
72894
73002
  node_path.default.dirname(layout.logPath)
72895
73003
  ]);
72896
73004
  }
72897
- async function installLinux(deps, layout, recoveryPassword) {
73005
+ async function installLinux(deps, layout, recoveryUsername, recoveryPassword) {
72898
73006
  await ensureLinuxAccount(deps, layout);
72899
73007
  const staged = stageServiceRuntime(deps, layout);
72900
73008
  await protectStagedServiceRuntime(deps, layout);
73009
+ node_fs.default.mkdirSync(node_path.default.dirname(layout.definitionPath), {
73010
+ recursive: true,
73011
+ mode: 493
73012
+ });
72901
73013
  node_fs.default.writeFileSync(layout.definitionPath, renderSystemdUnit({
72902
73014
  ...staged.dependencies,
72903
73015
  layout,
73016
+ recoveryUsername,
72904
73017
  recoveryPassword
72905
73018
  }), { mode: 384 });
72906
- await deps.run("systemctl", ["daemon-reload"]);
72907
- await deps.run("systemctl", ["enable", layout.serviceId]);
72908
- await deps.run("systemctl", ["restart", layout.serviceId]);
73019
+ const systemctl = layout.accountMode === "current" ? ["--user"] : [];
73020
+ await deps.run("systemctl", [...systemctl, "daemon-reload"]);
73021
+ await deps.run("systemctl", [
73022
+ ...systemctl,
73023
+ "enable",
73024
+ layout.serviceId
73025
+ ]);
73026
+ await deps.run("systemctl", [
73027
+ ...systemctl,
73028
+ "restart",
73029
+ layout.serviceId
73030
+ ]);
72909
73031
  pruneStagedServiceRuntimes(layout, staged.runtimeDir);
72910
73032
  }
72911
- async function installMac(deps, layout, recoveryPassword) {
73033
+ async function installMac(deps, layout, recoveryUsername, recoveryPassword) {
72912
73034
  await ensureMacAccount(deps, layout);
72913
73035
  const staged = stageServiceRuntime(deps, layout);
72914
73036
  await protectStagedServiceRuntime(deps, layout);
73037
+ node_fs.default.mkdirSync(node_path.default.dirname(layout.definitionPath), {
73038
+ recursive: true,
73039
+ mode: 493
73040
+ });
72915
73041
  node_fs.default.writeFileSync(layout.definitionPath, renderLaunchDaemon({
72916
73042
  ...staged.dependencies,
72917
73043
  layout,
73044
+ recoveryUsername,
72918
73045
  recoveryPassword
72919
73046
  }), { mode: 384 });
73047
+ const domain = layout.accountMode === "dedicated" ? "system" : `gui/${invokingUid()}`;
72920
73048
  await deps.run("launchctl", [
72921
73049
  "bootout",
72922
- "system",
73050
+ domain,
72923
73051
  layout.definitionPath
72924
73052
  ]).catch(() => {});
72925
73053
  await deps.run("launchctl", [
72926
73054
  "bootstrap",
72927
- "system",
73055
+ domain,
72928
73056
  layout.definitionPath
72929
73057
  ]);
72930
- await deps.run("launchctl", ["enable", `system/${layout.serviceId}`]);
73058
+ await deps.run("launchctl", ["enable", `${domain}/${layout.serviceId}`]);
72931
73059
  await deps.run("launchctl", [
72932
73060
  "kickstart",
72933
73061
  "-k",
72934
- `system/${layout.serviceId}`
73062
+ `${domain}/${layout.serviceId}`
72935
73063
  ]);
72936
73064
  pruneStagedServiceRuntimes(layout, staged.runtimeDir);
72937
73065
  }
72938
- async function installWindows(deps, layout, recoveryPassword) {
72939
- const accountPassword = (0, node_crypto.randomBytes)(32).toString("base64url");
72940
- const ps = renderWindowsAccountScript(layout.account, accountPassword);
72941
- await deps.run("powershell.exe", [
72942
- "-NoProfile",
72943
- "-NonInteractive",
72944
- "-Command",
72945
- ps
72946
- ]);
73066
+ async function installWindows(deps, layout, recoveryUsername, recoveryPassword) {
73067
+ const accountPassword = layout.accountMode === "dedicated" ? (0, node_crypto.randomBytes)(32).toString("base64url") : null;
73068
+ if (accountPassword) {
73069
+ const ps = renderWindowsAccountScript(layout.account, accountPassword);
73070
+ await deps.run("powershell.exe", [
73071
+ "-NoProfile",
73072
+ "-NonInteractive",
73073
+ "-Command",
73074
+ ps
73075
+ ]);
73076
+ }
72947
73077
  node_fs.default.mkdirSync(layout.workspaceDir, { recursive: true });
72948
- await deps.run("icacls.exe", [
73078
+ if (layout.accountMode === "dedicated") await deps.run("icacls.exe", [
72949
73079
  layout.stateDir,
72950
73080
  "/inheritance:r",
72951
73081
  "/grant:r",
@@ -72958,6 +73088,7 @@ async function installWindows(deps, layout, recoveryPassword) {
72958
73088
  node_fs.default.writeFileSync(layout.definitionPath, renderWindowsServiceScript({
72959
73089
  ...staged.dependencies,
72960
73090
  layout,
73091
+ recoveryUsername,
72961
73092
  recoveryPassword
72962
73093
  }), "utf8");
72963
73094
  await deps.run("schtasks.exe", [
@@ -72965,16 +73096,23 @@ async function installWindows(deps, layout, recoveryPassword) {
72965
73096
  "/TN",
72966
73097
  layout.serviceId
72967
73098
  ]).catch(() => {});
73099
+ const accountArguments = accountPassword ? [
73100
+ "/RU",
73101
+ `.\\${layout.account}`,
73102
+ "/RP",
73103
+ accountPassword
73104
+ ] : [
73105
+ "/RU",
73106
+ layout.account,
73107
+ "/NP"
73108
+ ];
72968
73109
  await deps.run("schtasks.exe", [
72969
73110
  "/Create",
72970
73111
  "/TN",
72971
73112
  layout.serviceId,
72972
73113
  "/SC",
72973
- "ONSTART",
72974
- "/RU",
72975
- `.\\${layout.account}`,
72976
- "/RP",
72977
- accountPassword,
73114
+ layout.accountMode === "dedicated" ? "ONSTART" : "ONLOGON",
73115
+ ...accountArguments,
72978
73116
  "/TR",
72979
73117
  layout.definitionPath,
72980
73118
  "/F"
@@ -72998,21 +73136,90 @@ $userList='HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\Spe
72998
73136
  New-Item -Path $userList -Force | Out-Null
72999
73137
  New-ItemProperty -Path $userList -Name $name -PropertyType DWord -Value 0 -Force | Out-Null`;
73000
73138
  }
73001
- async function installService(overrides = {}) {
73139
+ function systemServiceLayoutMetadataPath(platform) {
73140
+ if (platform === "linux") return "/etc/shioricode/service-layout.json";
73141
+ if (platform === "darwin") return "/Library/Preferences/codes.shiori.shioricode.service-layout.json";
73142
+ const programData = process.env.ProgramData || "C:\\ProgramData";
73143
+ return node_path.default.win32.join(programData, "ShioriCodeService", "service-layout.json");
73144
+ }
73145
+ function serviceLayoutMetadataPath(layout) {
73146
+ if (layout.accountMode === "dedicated") return systemServiceLayoutMetadataPath(layout.platform);
73147
+ const metadataHome = defaultExistingHomeDir(layout.platform, invokingUsername());
73148
+ if (layout.platform === "linux") return node_path.default.join(metadataHome, ".config/shioricode/service-layout.json");
73149
+ if (layout.platform === "darwin") return node_path.default.join(metadataHome, "Library/Application Support/ShioriCode Service/service-layout.json");
73150
+ return node_path.default.win32.join(process.env.LOCALAPPDATA || node_path.default.win32.join(metadataHome, "AppData", "Local"), "ShioriCodeService", "service-layout.json");
73151
+ }
73152
+ function writeServiceLayout(layout) {
73153
+ const metadataPath = serviceLayoutMetadataPath(layout);
73154
+ node_fs.default.mkdirSync(node_path.default.dirname(metadataPath), {
73155
+ recursive: true,
73156
+ mode: 493
73157
+ });
73158
+ node_fs.default.writeFileSync(metadataPath, `${JSON.stringify({
73159
+ version: SERVICE_LAYOUT_VERSION,
73160
+ layout
73161
+ }, null, 2)}\n`, { mode: 384 });
73162
+ if (layout.accountMode === "dedicated" && layout.platform !== "win32") {
73163
+ node_fs.default.chownSync(metadataPath, 0, 0);
73164
+ node_fs.default.chmodSync(metadataPath, 384);
73165
+ }
73166
+ }
73167
+ function readServiceLayout(metadataPath, platform) {
73168
+ try {
73169
+ const parsed = JSON.parse(node_fs.default.readFileSync(metadataPath, "utf8"));
73170
+ const layout = parsed.layout;
73171
+ 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;
73172
+ return layout;
73173
+ } catch {
73174
+ return null;
73175
+ }
73176
+ }
73177
+ function readInstalledServiceLayout(platform) {
73178
+ const currentLayout = readServiceLayout(serviceLayoutMetadataPath(serviceLayout(platform, { accountMode: "current" })), platform);
73179
+ if (currentLayout && node_fs.default.existsSync(currentLayout.definitionPath)) return currentLayout;
73180
+ const systemLayout = readServiceLayout(systemServiceLayoutMetadataPath(platform), platform);
73181
+ if (systemLayout && node_fs.default.existsSync(systemLayout.definitionPath)) return systemLayout;
73182
+ const legacy = serviceLayout(platform);
73183
+ return node_fs.default.existsSync(legacy.definitionPath) ? legacy : null;
73184
+ }
73185
+ function recoveryPasswordFromOptions(options) {
73186
+ if (!options.recoveryPasswordFile) return {
73187
+ password: (0, node_crypto.randomBytes)(32).toString("base64url"),
73188
+ generated: true
73189
+ };
73190
+ const password = node_fs.default.readFileSync(options.recoveryPasswordFile, "utf8").replace(/\r?\n$/, "");
73191
+ if (!password) throw new Error("The recovery password file is empty");
73192
+ if (/[\0\r\n]/.test(password)) throw new Error("The recovery password file must contain exactly one password line");
73193
+ return {
73194
+ password,
73195
+ generated: false
73196
+ };
73197
+ }
73198
+ async function installService(options = {}, overrides = {}) {
73002
73199
  const deps = {
73003
73200
  ...defaultDependencies$2(),
73004
73201
  ...overrides
73005
73202
  };
73006
- const layout = serviceLayout(deps.platform);
73007
- requireAdministrator(deps.platform);
73008
- const recoveryPassword = (0, node_crypto.randomBytes)(32).toString("base64url");
73009
- if (deps.platform === "linux") await installLinux(deps, layout, recoveryPassword);
73010
- if (deps.platform === "darwin") await installMac(deps, layout, recoveryPassword);
73011
- if (deps.platform === "win32") await installWindows(deps, layout, recoveryPassword);
73203
+ const layout = serviceLayout(deps.platform, options);
73204
+ if (layout.accountMode === "current" && layout.account !== invokingUsername()) throw new Error("Current-account services must run as the invoking OS user");
73205
+ if (layout.accountMode === "dedicated") requireAdministrator(deps.platform);
73206
+ if (options.disableRecoveryLogin && (options.recoveryUsername || options.recoveryPasswordFile)) throw new Error("--no-recovery-login cannot be combined with recovery username/password options");
73207
+ const recoveryUsername = options.disableRecoveryLogin ? null : options.recoveryUsername?.trim() || RECOVERY_USERNAME;
73208
+ if (recoveryUsername !== null && !/^[A-Za-z0-9._@-]{1,128}$/.test(recoveryUsername)) throw new Error("The recovery username contains unsupported characters");
73209
+ const recoveryPassword = options.disableRecoveryLogin ? {
73210
+ password: null,
73211
+ generated: false
73212
+ } : recoveryPasswordFromOptions(options);
73213
+ if (deps.platform === "win32" && recoveryPassword.password !== null && /[%"]/.test(recoveryPassword.password)) throw new Error("Windows recovery passwords cannot contain `%` or `\"`");
73214
+ if (deps.platform === "linux") await installLinux(deps, layout, recoveryUsername, recoveryPassword.password);
73215
+ if (deps.platform === "darwin") await installMac(deps, layout, recoveryUsername, recoveryPassword.password);
73216
+ if (deps.platform === "win32") await installWindows(deps, layout, recoveryUsername, recoveryPassword.password);
73217
+ writeServiceLayout(layout);
73012
73218
  return {
73013
73219
  layout,
73014
- recoveryUsername: RECOVERY_USERNAME,
73015
- recoveryPassword
73220
+ recoveryUsername,
73221
+ recoveryPassword: recoveryPassword.password,
73222
+ recoveryPasswordGenerated: recoveryPassword.generated
73016
73223
  };
73017
73224
  }
73018
73225
  async function readLog(logPath) {
@@ -73028,37 +73235,47 @@ async function controlService(action, overrides = {}) {
73028
73235
  ...defaultDependencies$2(),
73029
73236
  ...overrides
73030
73237
  };
73031
- const layout = serviceLayout(deps.platform);
73032
- requireAdministrator(deps.platform);
73238
+ const layout = readInstalledServiceLayout(deps.platform) ?? serviceLayout(deps.platform);
73239
+ if (layout.accountMode === "dedicated") requireAdministrator(deps.platform);
73033
73240
  if (action === "logs") return await readLog(layout.logPath);
73034
73241
  if (deps.platform === "linux") {
73242
+ const systemctl = layout.accountMode === "current" ? ["--user"] : [];
73035
73243
  if (action === "uninstall") {
73036
73244
  await deps.run("systemctl", [
73245
+ ...systemctl,
73037
73246
  "disable",
73038
73247
  "--now",
73039
73248
  layout.serviceId
73040
73249
  ]).catch(() => {});
73041
73250
  node_fs.default.rmSync(layout.definitionPath, { force: true });
73042
- await deps.run("systemctl", ["daemon-reload"]);
73251
+ await deps.run("systemctl", [...systemctl, "daemon-reload"]);
73252
+ node_fs.default.rmSync(serviceLayoutMetadataPath(layout), { force: true });
73043
73253
  return "Removed the systemd service. ShioriCode data and the service account were preserved.";
73044
73254
  }
73045
73255
  if (action === "status") return (await deps.run("systemctl", [
73256
+ ...systemctl,
73046
73257
  "show",
73047
73258
  layout.serviceId,
73048
73259
  "--no-pager",
73049
73260
  "--property=LoadState,UnitFileState,ActiveState,SubState,MainPID,ExecMainStatus"
73050
73261
  ])).stdout.trim();
73051
- return (await deps.run("systemctl", [action, layout.serviceId])).stdout.trim() || `Service ${action} complete.`;
73262
+ return (await deps.run("systemctl", [
73263
+ ...systemctl,
73264
+ action,
73265
+ layout.serviceId
73266
+ ])).stdout.trim() || `Service ${action} complete.`;
73052
73267
  }
73053
73268
  if (deps.platform === "darwin") {
73054
- const target = `system/${layout.serviceId}`;
73269
+ const domain = layout.accountMode === "dedicated" ? "system" : `gui/${invokingUid()}`;
73270
+ const target = `${domain}/${layout.serviceId}`;
73055
73271
  if (action === "uninstall") {
73056
73272
  await deps.run("launchctl", [
73057
73273
  "bootout",
73058
- "system",
73274
+ domain,
73059
73275
  layout.definitionPath
73060
73276
  ]).catch(() => {});
73061
73277
  node_fs.default.rmSync(layout.definitionPath, { force: true });
73278
+ node_fs.default.rmSync(serviceLayoutMetadataPath(layout), { force: true });
73062
73279
  return "Removed the launch daemon. ShioriCode data and the service account were preserved.";
73063
73280
  }
73064
73281
  if (action === "status") try {
@@ -73069,20 +73286,20 @@ async function controlService(action, overrides = {}) {
73069
73286
  if (action === "stop") {
73070
73287
  await deps.run("launchctl", [
73071
73288
  "bootout",
73072
- "system",
73289
+ domain,
73073
73290
  layout.definitionPath
73074
73291
  ]);
73075
73292
  return "Service stop complete.";
73076
73293
  }
73077
73294
  if (action === "restart") await deps.run("launchctl", [
73078
73295
  "bootout",
73079
- "system",
73296
+ domain,
73080
73297
  layout.definitionPath
73081
73298
  ]).catch(() => {});
73082
73299
  if (action === "start" || action === "restart") {
73083
73300
  await deps.run("launchctl", [
73084
73301
  "bootstrap",
73085
- "system",
73302
+ domain,
73086
73303
  layout.definitionPath
73087
73304
  ]).catch(() => {});
73088
73305
  await deps.run("launchctl", ["enable", target]);
@@ -73106,6 +73323,7 @@ async function controlService(action, overrides = {}) {
73106
73323
  layout.serviceId,
73107
73324
  "/F"
73108
73325
  ]);
73326
+ node_fs.default.rmSync(serviceLayoutMetadataPath(layout), { force: true });
73109
73327
  return "Removed the Windows background task. ShioriCode data and the service account were preserved.";
73110
73328
  }
73111
73329
  if (action === "status") return (await deps.run("schtasks.exe", [
@@ -73144,11 +73362,13 @@ function serviceSummary(layout = serviceLayout(supportedPlatform(process.platfor
73144
73362
  return [
73145
73363
  `Platform: ${layout.platform}`,
73146
73364
  `Service: ${layout.serviceId}`,
73147
- `Account: ${layout.account}`,
73365
+ `Account: ${layout.account} (${layout.accountMode})`,
73366
+ `Home: ${layout.homeDir}`,
73148
73367
  `Data: ${layout.stateDir}`,
73149
73368
  `Workspaces: ${layout.workspaceDir}`,
73150
73369
  `Logs: ${layout.logPath}`,
73151
- `Host: http://127.0.0.1:${SERVICE_PORT}`
73370
+ `PATH: ${layout.servicePath}`,
73371
+ `Host: http://127.0.0.1:${layout.port}`
73152
73372
  ].join("\n");
73153
73373
  }
73154
73374
  const currentServicePlatform = () => supportedPlatform(node_os.default.platform());
@@ -73157,8 +73377,7 @@ function requireServiceAdministrator(platform = currentServicePlatform()) {
73157
73377
  return platform;
73158
73378
  }
73159
73379
  function findInstalledServiceLayout() {
73160
- const layout = serviceLayout(currentServicePlatform());
73161
- return node_fs.default.existsSync(layout.definitionPath) ? layout : null;
73380
+ return readInstalledServiceLayout(currentServicePlatform());
73162
73381
  }
73163
73382
  function installedServiceLayout() {
73164
73383
  const layout = findInstalledServiceLayout();
@@ -73169,14 +73388,13 @@ function linkServiceStateDir(layout = installedServiceLayout()) {
73169
73388
  return node_path.default.join(layout.stateDir, "userdata");
73170
73389
  }
73171
73390
  async function repairServiceStateOwnership(layout = installedServiceLayout()) {
73172
- if (layout.platform === "win32") return;
73173
- const owner = `${layout.account}:${layout.account}`;
73174
- await runDefault("chown", [owner, layout.stateDir]);
73391
+ if (layout.platform === "win32" || layout.accountMode === "current") return;
73392
+ await runDefault("chown", [layout.account, layout.stateDir]);
73175
73393
  for (const entry of node_fs.default.readdirSync(layout.stateDir, { withFileTypes: true })) {
73176
73394
  if (entry.name === "runtime") continue;
73177
73395
  await runDefault("chown", [
73178
73396
  "-R",
73179
- owner,
73397
+ layout.account,
73180
73398
  node_path.default.join(layout.stateDir, entry.name)
73181
73399
  ]);
73182
73400
  }
@@ -73257,9 +73475,8 @@ async function waitForDeviceAuthorization(input) {
73257
73475
  throw new Error("The GitHub authorization code expired");
73258
73476
  }
73259
73477
  function serviceLinkContext() {
73260
- const platform = requireServiceAdministrator();
73261
73478
  const layout = installedServiceLayout();
73262
- if (layout.platform !== platform) throw new Error("Service platform changed unexpectedly");
73479
+ if (layout.accountMode === "dedicated") requireServiceAdministrator(layout.platform);
73263
73480
  const store = new LinkRemoteStore({ stateDir: linkServiceStateDir(layout) });
73264
73481
  return {
73265
73482
  layout,
@@ -73425,18 +73642,13 @@ const PROVIDERS = [
73425
73642
  credentialPaths: [".kimi"]
73426
73643
  }
73427
73644
  ];
73428
- function servicePath(platform) {
73429
- if (platform === "darwin") return "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
73430
- if (platform === "linux") return "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
73431
- return process.env.Path ?? process.env.PATH ?? "";
73432
- }
73433
- async function binaryVersion(binary, home) {
73645
+ async function binaryVersion(binary, home, configuredPath) {
73434
73646
  try {
73435
73647
  const result = await execFile$8(binary, ["--version"], {
73436
73648
  env: {
73437
73649
  ...process.env,
73438
73650
  HOME: home,
73439
- PATH: servicePath(process.platform)
73651
+ PATH: configuredPath
73440
73652
  },
73441
73653
  timeout: 5e3,
73442
73654
  windowsHide: true
@@ -73450,26 +73662,27 @@ function hasCredentials(home, candidates) {
73450
73662
  return candidates.some((candidate) => node_fs.default.existsSync(node_path.default.join(home, candidate)));
73451
73663
  }
73452
73664
  function serviceAccountCommand(command) {
73453
- const layout = serviceLayout(currentServicePlatform());
73665
+ const layout = findInstalledServiceLayout() ?? serviceLayout(currentServicePlatform());
73666
+ if (layout.accountMode === "current") return command;
73454
73667
  if (layout.platform === "linux") return `sudo -u ${layout.account} -H ${command}`;
73455
73668
  if (layout.platform === "darwin") return `sudo -u ${layout.account} -H env PATH=/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin ${command}`;
73456
73669
  return `${command} (run it for the ${layout.account} service account)`;
73457
73670
  }
73458
73671
  async function providerDoctor() {
73459
- const layout = serviceLayout(currentServicePlatform());
73672
+ const layout = findInstalledServiceLayout() ?? serviceLayout(currentServicePlatform());
73460
73673
  const lines = [
73461
73674
  `Service account: ${layout.account}`,
73462
- `Service home: ${layout.stateDir}`,
73675
+ `Service home: ${layout.homeDir}`,
73463
73676
  ""
73464
73677
  ];
73465
73678
  for (const provider of PROVIDERS) {
73466
- const version = await binaryVersion(provider.binary, layout.stateDir);
73679
+ const version = await binaryVersion(provider.binary, layout.homeDir, layout.servicePath);
73467
73680
  if (!version) {
73468
73681
  lines.push(`✗ ${provider.name}: not found in the service PATH`);
73469
73682
  lines.push(` Install: ${provider.installHint}`);
73470
73683
  continue;
73471
73684
  }
73472
- const authenticated = hasCredentials(layout.stateDir, provider.credentialPaths);
73685
+ const authenticated = hasCredentials(layout.homeDir, provider.credentialPaths);
73473
73686
  lines.push(`✓ ${provider.name}: ${version}`);
73474
73687
  lines.push(authenticated ? " Credentials detected in the service home" : ` Sign in: ${serviceAccountCommand(provider.authHint)}`);
73475
73688
  }
@@ -83766,7 +83979,7 @@ function resolveTarget(baseDir, deps) {
83766
83979
  }
83767
83980
  async function withRemoteRpc(target, deps, run) {
83768
83981
  if (!target.service) return await deps.withLocalRpc(target.baseDir, run);
83769
- deps.requireAdministrator(target.service.platform);
83982
+ if (target.service.accountMode === "dedicated") deps.requireAdministrator(target.service.platform);
83770
83983
  const connection = await deps.connectService(target.service.stateDir);
83771
83984
  if (!connection) throw new Error("The ShioriCode service is installed but not reachable. Run `shioricode service start`, then retry.");
83772
83985
  try {
@@ -216801,18 +217014,21 @@ function builtInShioriMcpServers(input) {
216801
217014
  return servers.map((server) => Object.assign({}, server, { providers: [provider] }));
216802
217015
  }
216803
217016
  function makeBuiltInStdioMcpServerEntry(name, subcommand, env) {
216804
- const entry = {
217017
+ return {
216805
217018
  name,
216806
217019
  transport: "stdio",
216807
217020
  command: process.execPath,
216808
217021
  args: [...serverEntrypointArgs(), subcommand],
216809
217022
  enabled: true,
216810
- providers: []
217023
+ providers: [],
217024
+ env: makeBuiltInStdioMcpEnvironment(env)
217025
+ };
217026
+ }
217027
+ function makeBuiltInStdioMcpEnvironment(env) {
217028
+ return {
217029
+ ...env,
217030
+ ELECTRON_RUN_AS_NODE: "1"
216811
217031
  };
216812
- return env ? {
216813
- ...entry,
216814
- env
216815
- } : entry;
216816
217032
  }
216817
217033
  function threadGoalMcpServerEntry(input) {
216818
217034
  return makeBuiltInStdioMcpServerEntry(THREAD_GOAL_MCP_SERVER_NAME, "thread-goal-mcp", {
@@ -219726,7 +219942,7 @@ const getTelemetryIdentifier = gen$5(function* () {
219726
219942
 
219727
219943
  //#endregion
219728
219944
  //#region package.json
219729
- var version$1 = "0.5.1";
219945
+ var version$1 = "0.5.3";
219730
219946
 
219731
219947
  //#endregion
219732
219948
  //#region src/telemetry/Layers/AnalyticsService.ts
@@ -703212,14 +703428,42 @@ const serveCommand = make$166("serve", commandFlags).pipe(withDescription$3("Run
703212
703428
  const serviceActionCommand = (action, description) => make$166(action).pipe(withDescription$3(description), withHandler(() => promise$5(async () => {
703213
703429
  console.log(await controlService(action));
703214
703430
  })));
703431
+ const serviceInstallFlags = {
703432
+ 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")),
703433
+ 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),
703434
+ homeDir: string$10("home-dir").pipe(withDescription$4("HOME exposed to provider CLIs in the service process."), optional$3),
703435
+ stateDir: string$10("state-dir").pipe(withDescription$4("ShioriCode service data directory."), optional$3),
703436
+ workspaceDir: string$10("workspace-dir").pipe(withDescription$4("Default working directory for the service."), optional$3),
703437
+ logPath: string$10("log-file").pipe(withDescription$4("Log file for stdout and stderr from the background service."), optional$3),
703438
+ 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),
703439
+ port: integer$4("port").pipe(withSchema(PortSchema), withDescription$4("Loopback port for the background service."), optional$3),
703440
+ recoveryUsername: string$10("recovery-username").pipe(withDescription$4("Username for direct recovery login. Defaults to `recovery`."), optional$3),
703441
+ 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),
703442
+ disableRecoveryLogin: boolean$8("no-recovery-login").pipe(withDescription$4("Disable direct username/password recovery login. ShioriCode Link remains GitHub-only."), withDefault$2(false))
703443
+ };
703215
703444
  const serviceCommand = make$166("service").pipe(withDescription$3("Install and control the OS background service."), withSubcommands([
703216
- make$166("install").pipe(withDescription$3("Install and start ShioriCode under a dedicated OS account."), withHandler(() => promise$5(async () => {
703217
- const result = await installService();
703445
+ make$166("install", serviceInstallFlags).pipe(withDescription$3("Install and start ShioriCode as a dedicated or existing OS user."), withHandler((flags) => promise$5(async () => {
703446
+ const result = await installService({
703447
+ accountMode: flags.accountMode,
703448
+ account: getOrUndefined$3(flags.account),
703449
+ homeDir: getOrUndefined$3(flags.homeDir),
703450
+ stateDir: getOrUndefined$3(flags.stateDir),
703451
+ workspaceDir: getOrUndefined$3(flags.workspaceDir),
703452
+ logPath: getOrUndefined$3(flags.logPath),
703453
+ servicePath: getOrUndefined$3(flags.servicePath),
703454
+ port: getOrUndefined$3(flags.port),
703455
+ recoveryUsername: getOrUndefined$3(flags.recoveryUsername),
703456
+ recoveryPasswordFile: getOrUndefined$3(flags.recoveryPasswordFile),
703457
+ disableRecoveryLogin: flags.disableRecoveryLogin
703458
+ });
703218
703459
  console.log("ShioriCode service installed and started.\n");
703219
703460
  console.log(serviceSummary(result.layout));
703220
- console.log("\nLocal recovery credentials (store these securely):");
703221
- console.log(`Username: ${result.recoveryUsername}`);
703222
- console.log(`Password: ${result.recoveryPassword}`);
703461
+ console.log("\nLocal recovery login:");
703462
+ if (result.recoveryUsername === null || result.recoveryPassword === null) console.log("Disabled. Hosted access uses GitHub through ShioriCode Link.");
703463
+ else {
703464
+ console.log(`Username: ${result.recoveryUsername}`);
703465
+ console.log(result.recoveryPasswordGenerated ? `Generated password: ${result.recoveryPassword}\nStore it securely; it is shown once.` : "Password: loaded from --recovery-password-file (not echoed)");
703466
+ }
703223
703467
  console.log("\nNext: shioricode link connect");
703224
703468
  }))),
703225
703469
  serviceActionCommand("start", "Start the ShioriCode service."),