@dylanpiercey/work 0.0.5 → 0.0.7

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 (157) hide show
  1. package/bin/work.mjs +88 -18
  2. package/dist/index.mjs +1218 -820
  3. package/dist/public/assets/{_DwWKKIbx.js → _B-_0cKbG.js} +2 -2
  4. package/dist/public/assets/{_DwWKKIbx.js.map → _B-_0cKbG.js.map} +1 -1
  5. package/dist/public/assets/{_BbQjjMLb2.js → _B1F2GQHq2.js} +2 -2
  6. package/dist/public/assets/{_BbQjjMLb2.js.map → _B1F2GQHq2.js.map} +1 -1
  7. package/dist/public/assets/{_DThwiCYf.js → _B49hrT7F.js} +2 -2
  8. package/dist/public/assets/{_DThwiCYf.js.map → _B49hrT7F.js.map} +1 -1
  9. package/dist/public/assets/{_L0QEXXRu.js → _B4R8SNvy.js} +2 -2
  10. package/dist/public/assets/{_L0QEXXRu.js.map → _B4R8SNvy.js.map} +1 -1
  11. package/dist/public/assets/{_g24DOTM6.js → _B7K5PePe.js} +2 -2
  12. package/dist/public/assets/{_g24DOTM6.js.map → _B7K5PePe.js.map} +1 -1
  13. package/dist/public/assets/{_DsH6sF8_.js → _B8mEgNvE.js} +2 -2
  14. package/dist/public/assets/{_DsH6sF8_.js.map → _B8mEgNvE.js.map} +1 -1
  15. package/dist/public/assets/{_Dp2I7SvR2.js → _BBbcogWL2.js} +2 -2
  16. package/dist/public/assets/{_Dp2I7SvR2.js.map → _BBbcogWL2.js.map} +1 -1
  17. package/dist/public/assets/{_BQnjw6qe.js → _BDxD__Ca.js} +2 -2
  18. package/dist/public/assets/{_BQnjw6qe.js.map → _BDxD__Ca.js.map} +1 -1
  19. package/dist/public/assets/{_BxHj_AmV.js → _BE5Ltp0D.js} +2 -2
  20. package/dist/public/assets/{_BxHj_AmV.js.map → _BE5Ltp0D.js.map} +1 -1
  21. package/dist/public/assets/{_lZmV1DmL.js → _BE8gA6TO.js} +2 -2
  22. package/dist/public/assets/{_lZmV1DmL.js.map → _BE8gA6TO.js.map} +1 -1
  23. package/dist/public/assets/{_f31sxhc9.js → _BEY9qyEj.js} +2 -2
  24. package/dist/public/assets/{_f31sxhc9.js.map → _BEY9qyEj.js.map} +1 -1
  25. package/dist/public/assets/{_3LSnPXE-2.js → _BFPTVBh82.js} +2 -2
  26. package/dist/public/assets/{_3LSnPXE-2.js.map → _BFPTVBh82.js.map} +1 -1
  27. package/dist/public/assets/{_CPt59YK-2.js → _BKNwgsJI2.js} +2 -2
  28. package/dist/public/assets/{_CPt59YK-2.js.map → _BKNwgsJI2.js.map} +1 -1
  29. package/dist/public/assets/{_CGEJqu1l.js → _BLuo0bJF.js} +2 -2
  30. package/dist/public/assets/{_CGEJqu1l.js.map → _BLuo0bJF.js.map} +1 -1
  31. package/dist/public/assets/{_De1qIUYL2.js → _BMbg6Q_32.js} +2 -2
  32. package/dist/public/assets/{_De1qIUYL2.js.map → _BMbg6Q_32.js.map} +1 -1
  33. package/dist/public/assets/{_DFdBRroy.js → _BQqn5S5j.js} +2 -2
  34. package/dist/public/assets/{_DFdBRroy.js.map → _BQqn5S5j.js.map} +1 -1
  35. package/dist/public/assets/{_C_AWyxBB.js → _BTJ_-CIz.js} +2 -2
  36. package/dist/public/assets/{_C_AWyxBB.js.map → _BTJ_-CIz.js.map} +1 -1
  37. package/dist/public/assets/{_DWh6hcFb.js → _BTnhYWUT.js} +2 -2
  38. package/dist/public/assets/{_DWh6hcFb.js.map → _BTnhYWUT.js.map} +1 -1
  39. package/dist/public/assets/{_C55X2_br.js → _B_U5SPso.js} +2 -2
  40. package/dist/public/assets/{_C55X2_br.js.map → _B_U5SPso.js.map} +1 -1
  41. package/dist/public/assets/{_BmLItN7n.js → _BfxQEfor.js} +2 -2
  42. package/dist/public/assets/{_BmLItN7n.js.map → _BfxQEfor.js.map} +1 -1
  43. package/dist/public/assets/{_Cxre_eVs2.js → _Bhn-MKXG2.js} +2 -2
  44. package/dist/public/assets/{_Cxre_eVs2.js.map → _Bhn-MKXG2.js.map} +1 -1
  45. package/dist/public/assets/{_G2euCJ5s2.js → _BkEOzqHA2.js} +2 -2
  46. package/dist/public/assets/{_G2euCJ5s2.js.map → _BkEOzqHA2.js.map} +1 -1
  47. package/dist/public/assets/{_BtEwCzLt.js → _BqeWVi6c.js} +2 -2
  48. package/dist/public/assets/{_BtEwCzLt.js.map → _BqeWVi6c.js.map} +1 -1
  49. package/dist/public/assets/{_D25eFvus.js → _Bx6coq7v.js} +2 -2
  50. package/dist/public/assets/{_D25eFvus.js.map → _Bx6coq7v.js.map} +1 -1
  51. package/dist/public/assets/{_Dj5K_vdY.js → _BxWguH11.js} +2 -2
  52. package/dist/public/assets/{_Dj5K_vdY.js.map → _BxWguH11.js.map} +1 -1
  53. package/dist/public/assets/{_BO52x4nD2.js → _C2TX6Jrr2.js} +2 -2
  54. package/dist/public/assets/{_BO52x4nD2.js.map → _C2TX6Jrr2.js.map} +1 -1
  55. package/dist/public/assets/{_UUjPxQzk.js → _C8Lw1Tmb.js} +2 -2
  56. package/dist/public/assets/{_UUjPxQzk.js.map → _C8Lw1Tmb.js.map} +1 -1
  57. package/dist/public/assets/{_C_RO1bIU.js → _CDtlFxZX.js} +2 -2
  58. package/dist/public/assets/{_C_RO1bIU.js.map → _CDtlFxZX.js.map} +1 -1
  59. package/dist/public/assets/{_AAkZyuUo2.js → _CF3V7ZyO2.js} +2 -2
  60. package/dist/public/assets/{_AAkZyuUo2.js.map → _CF3V7ZyO2.js.map} +1 -1
  61. package/dist/public/assets/{_BMIj0szP.js → _CIUf_Jmq.js} +2 -2
  62. package/dist/public/assets/{_BMIj0szP.js.map → _CIUf_Jmq.js.map} +1 -1
  63. package/dist/public/assets/{_CZjGo4EJ2.js → _CJlF2h3E2.js} +2 -2
  64. package/dist/public/assets/{_CZjGo4EJ2.js.map → _CJlF2h3E2.js.map} +1 -1
  65. package/dist/public/assets/{_DjF3hk4N.js → _CNDjHC1Q.js} +2 -2
  66. package/dist/public/assets/{_DjF3hk4N.js.map → _CNDjHC1Q.js.map} +1 -1
  67. package/dist/public/assets/{_Dg0ufwJw.js → _CTkR0S8t.js} +2 -2
  68. package/dist/public/assets/{_Dg0ufwJw.js.map → _CTkR0S8t.js.map} +1 -1
  69. package/dist/public/assets/{_DWBu1oTd.js → _C_K-IZx7.js} +2 -2
  70. package/dist/public/assets/{_DWBu1oTd.js.map → _C_K-IZx7.js.map} +1 -1
  71. package/dist/public/assets/{_B7ycuWnF.js → _CawcEo7A.js} +2 -2
  72. package/dist/public/assets/{_B7ycuWnF.js.map → _CawcEo7A.js.map} +1 -1
  73. package/dist/public/assets/{_BF4oeZAs2.js → _Ch0uVmAz2.js} +2 -2
  74. package/dist/public/assets/{_BF4oeZAs2.js.map → _Ch0uVmAz2.js.map} +1 -1
  75. package/dist/public/assets/{_BksA_4pq.js → _Ck30GMoL.js} +2 -2
  76. package/dist/public/assets/{_BksA_4pq.js.map → _Ck30GMoL.js.map} +1 -1
  77. package/dist/public/assets/{_LMf8ZNAk.js → _Cpqbvj5d.js} +2 -2
  78. package/dist/public/assets/{_LMf8ZNAk.js.map → _Cpqbvj5d.js.map} +1 -1
  79. package/dist/public/assets/{_MSZeR7tc.js → _CqNVh0h5.js} +2 -2
  80. package/dist/public/assets/{_MSZeR7tc.js.map → _CqNVh0h5.js.map} +1 -1
  81. package/dist/public/assets/{_CZJ6f1W42.js → _Cx_br0wh2.js} +2 -2
  82. package/dist/public/assets/{_CZJ6f1W42.js.map → _Cx_br0wh2.js.map} +1 -1
  83. package/dist/public/assets/{_dbpNK3ti.js → _CzBadWXq.js} +2 -2
  84. package/dist/public/assets/{_dbpNK3ti.js.map → _CzBadWXq.js.map} +1 -1
  85. package/dist/public/assets/{_BKWC43v9.js → _D0Dk2eMz.js} +2 -2
  86. package/dist/public/assets/{_BKWC43v9.js.map → _D0Dk2eMz.js.map} +1 -1
  87. package/dist/public/assets/{_BWobArGx2.js → _D0PyqJyr2.js} +2 -2
  88. package/dist/public/assets/{_BWobArGx2.js.map → _D0PyqJyr2.js.map} +1 -1
  89. package/dist/public/assets/{_Cp2dVuf7.js → _D409hg3n.js} +2 -2
  90. package/dist/public/assets/{_Cp2dVuf7.js.map → _D409hg3n.js.map} +1 -1
  91. package/dist/public/assets/{_DgS0ehtc.js → _D4uduNrb.js} +2 -2
  92. package/dist/public/assets/{_DgS0ehtc.js.map → _D4uduNrb.js.map} +1 -1
  93. package/dist/public/assets/{_DwvTHP0H.js → _DBj-L0Ww.js} +2 -2
  94. package/dist/public/assets/{_DwvTHP0H.js.map → _DBj-L0Ww.js.map} +1 -1
  95. package/dist/public/assets/{_DkAxFIdj2.js → _DC9uMckf2.js} +2 -2
  96. package/dist/public/assets/{_DkAxFIdj2.js.map → _DC9uMckf2.js.map} +1 -1
  97. package/dist/public/assets/{_m6OqtXq5.js → _DHsfzMP_.js} +2 -2
  98. package/dist/public/assets/{_m6OqtXq5.js.map → _DHsfzMP_.js.map} +1 -1
  99. package/dist/public/assets/{_DR7HMLdt.js → _DPDjFNn3.js} +2 -2
  100. package/dist/public/assets/{_DR7HMLdt.js.map → _DPDjFNn3.js.map} +1 -1
  101. package/dist/public/assets/{_CFYld88W.js → _DS1r4Da7.js} +2 -2
  102. package/dist/public/assets/{_CFYld88W.js.map → _DS1r4Da7.js.map} +1 -1
  103. package/dist/public/assets/{_XySmXsU3.js → _DZp-Xvf0.js} +2 -2
  104. package/dist/public/assets/{_XySmXsU3.js.map → _DZp-Xvf0.js.map} +1 -1
  105. package/dist/public/assets/{_C7WwEVpI.js → _DbP8pES9.js} +2 -2
  106. package/dist/public/assets/{_C7WwEVpI.js.map → _DbP8pES9.js.map} +1 -1
  107. package/dist/public/assets/{_B-CqRNtV.js → _DhqQ2yva.js} +2 -2
  108. package/dist/public/assets/{_B-CqRNtV.js.map → _DhqQ2yva.js.map} +1 -1
  109. package/dist/public/assets/{_qGiUFV1r.js → _Din9HEMs.js} +2 -2
  110. package/dist/public/assets/{_qGiUFV1r.js.map → _Din9HEMs.js.map} +1 -1
  111. package/dist/public/assets/{_MSjbYDCb.js → _Dk-i8IAv.js} +2 -2
  112. package/dist/public/assets/{_MSjbYDCb.js.map → _Dk-i8IAv.js.map} +1 -1
  113. package/dist/public/assets/{_CMH9CsQN.js → _DvdWf5o-.js} +2 -2
  114. package/dist/public/assets/{_CMH9CsQN.js.map → _DvdWf5o-.js.map} +1 -1
  115. package/dist/public/assets/{_DaxEJQAG2.js → _DwmbSeOR2.js} +2 -2
  116. package/dist/public/assets/{_DaxEJQAG2.js.map → _DwmbSeOR2.js.map} +1 -1
  117. package/dist/public/assets/{_DdXxywnu.js → _Dz57V0my.js} +2 -2
  118. package/dist/public/assets/{_DdXxywnu.js.map → _Dz57V0my.js.map} +1 -1
  119. package/dist/public/assets/{_DVqaReFM2.js → _JDPB-L9T2.js} +2 -2
  120. package/dist/public/assets/{_DVqaReFM2.js.map → _JDPB-L9T2.js.map} +1 -1
  121. package/dist/public/assets/{_BacHRrKm2.js → _T3Aahgg12.js} +2 -2
  122. package/dist/public/assets/{_BacHRrKm2.js.map → _T3Aahgg12.js.map} +1 -1
  123. package/dist/public/assets/{_sQkp9ieF.js → _Tbp9N0Pq.js} +2 -2
  124. package/dist/public/assets/{_sQkp9ieF.js.map → _Tbp9N0Pq.js.map} +1 -1
  125. package/dist/public/assets/{_CSxCkWdX2.js → _Tqwd1-8Z2.js} +2 -2
  126. package/dist/public/assets/{_CSxCkWdX2.js.map → _Tqwd1-8Z2.js.map} +1 -1
  127. package/dist/public/assets/{_Dc3WoyCf.js → _UPkRAhR-.js} +2 -2
  128. package/dist/public/assets/{_Dc3WoyCf.js.map → _UPkRAhR-.js.map} +1 -1
  129. package/dist/public/assets/{_D4G4BzdU2.js → _WO2um4ce2.js} +2 -2
  130. package/dist/public/assets/{_D4G4BzdU2.js.map → _WO2um4ce2.js.map} +1 -1
  131. package/dist/public/assets/{_DwB4tost2.js → _WXstblxD2.js} +2 -2
  132. package/dist/public/assets/{_DwB4tost2.js.map → _WXstblxD2.js.map} +1 -1
  133. package/dist/public/assets/{_BPAMsQ-Y2.js → _Wq5Lwig62.js} +2 -2
  134. package/dist/public/assets/{_BPAMsQ-Y2.js.map → _Wq5Lwig62.js.map} +1 -1
  135. package/dist/public/assets/{_B7BQvLFA2.js → _YMDMk2XX2.js} +2 -2
  136. package/dist/public/assets/{_B7BQvLFA2.js.map → _YMDMk2XX2.js.map} +1 -1
  137. package/dist/public/assets/{_A4VtPfAb.js → _b9uzSPzx.js} +2 -2
  138. package/dist/public/assets/{_A4VtPfAb.js.map → _b9uzSPzx.js.map} +1 -1
  139. package/dist/public/assets/{_Bjvy806u2.js → _dfWRmwy82.js} +2 -2
  140. package/dist/public/assets/{_Bjvy806u2.js.map → _dfWRmwy82.js.map} +1 -1
  141. package/dist/public/assets/{_2JdtdC0N.js → _ehtlhcCR.js} +2 -2
  142. package/dist/public/assets/{_2JdtdC0N.js.map → _ehtlhcCR.js.map} +1 -1
  143. package/dist/public/assets/{_Fpix3N3i.js → _fZl_Qerv.js} +2 -2
  144. package/dist/public/assets/{_Fpix3N3i.js.map → _fZl_Qerv.js.map} +1 -1
  145. package/dist/public/assets/{_C6fTVhaS.js → _js4WyaBT.js} +2 -2
  146. package/dist/public/assets/{_C6fTVhaS.js.map → _js4WyaBT.js.map} +1 -1
  147. package/dist/public/assets/{_Ck_58QRD.js → _okHqOb61.js} +2 -2
  148. package/dist/public/assets/{_Ck_58QRD.js.map → _okHqOb61.js.map} +1 -1
  149. package/dist/public/assets/{_5XJqee1K2.js → _rMU5-5Oc2.js} +2 -2
  150. package/dist/public/assets/{_5XJqee1K2.js.map → _rMU5-5Oc2.js.map} +1 -1
  151. package/dist/public/assets/{index-BAGH9WA4.js → index-Bx9UtyrE.js} +63 -61
  152. package/dist/public/assets/index-Bx9UtyrE.js.map +1 -0
  153. package/dist/public/assets/{index-KIy0rCx1.css → index-C_YqjXeW.css} +1 -1
  154. package/install.sh +4 -1
  155. package/package.json +1 -1
  156. package/scripts/migrate.mjs +141 -0
  157. package/dist/public/assets/index-BAGH9WA4.js.map +0 -1
package/bin/work.mjs CHANGED
@@ -107,15 +107,21 @@ async function setup() {
107
107
  }
108
108
  }
109
109
 
110
- // One-shot cleanups between versions.
110
+ // One-shot cleanups between versions. Run them against the settings the
111
+ // service actually uses: migrate.mjs exits quietly when it finds no db, so
112
+ // an ambient environment missing WORK_DATA_DIR skips every sweep.
111
113
  const migrate = path.join(pkgRoot, "scripts", "migrate.mjs");
112
114
  if (fs.existsSync(migrate)) {
113
- spawnSync(process.execPath, [migrate], { stdio: "inherit" });
115
+ spawnSync(process.execPath, [migrate], {
116
+ stdio: "inherit",
117
+ env: { ...process.env, ...serviceEnv() },
118
+ });
114
119
  }
115
120
 
116
121
  installService();
117
- maybeTunnel();
118
- log(`done — http://localhost:${appPort}`);
122
+ const port = serviceEnv().PORT;
123
+ maybeTunnel(port);
124
+ log(`done — http://localhost:${port}`);
119
125
  }
120
126
 
121
127
  async function update() {
@@ -139,18 +145,73 @@ function servicePath() {
139
145
  ].join(":");
140
146
  }
141
147
 
148
+ /** Argv prefix that keeps the host awake while the server runs. A suspended
149
+ * host is a dead host: agents keep running in tmux, but the machine stops
150
+ * answering, so remote sessions look hung until someone wakes it by hand. The
151
+ * inhibitor needs no privileges and lifts when the service stops — masking
152
+ * sleep.target is the stronger machine-wide version, but that wants sudo. */
153
+ function awakePrefix() {
154
+ if (process.platform === "darwin")
155
+ return have("caffeinate") ? ["/usr/bin/caffeinate", "-s"] : [];
156
+ if (process.platform === "linux" && have("systemd-inhibit"))
157
+ return [
158
+ "systemd-inhibit",
159
+ "--what=sleep:idle",
160
+ "--who=work",
161
+ "--why=work server is running",
162
+ "--mode=block",
163
+ ];
164
+ return [];
165
+ }
166
+
167
+ /** Where installService() writes the service definition — also the store we
168
+ * read settings back out of, so there is no second config file to keep. */
169
+ function serviceFile() {
170
+ return process.platform === "darwin"
171
+ ? path.join(os.homedir(), "Library/LaunchAgents/dev.work.app.plist")
172
+ : path.join(
173
+ process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config"),
174
+ "systemd/user/work.service",
175
+ );
176
+ }
177
+
178
+ /** WORK_* already baked into the installed service. `work update` re-runs
179
+ * setup from whatever shell the user happens to be in, so without this the
180
+ * first update silently resets the port, root and data dir to defaults —
181
+ * and a lost WORK_DATA_DIR points the server at an empty database, making
182
+ * every workspace look like it vanished. */
183
+ function installedServiceEnv() {
184
+ const installed = {};
185
+ try {
186
+ const text = fs.readFileSync(serviceFile(), "utf8");
187
+ const re =
188
+ process.platform === "darwin"
189
+ ? /<key>(WORK_[A-Z_]+)<\/key>\s*<string>([^<]*)<\/string>/g
190
+ : /^Environment=(WORK_[A-Z_]+)=(.*)$/gm;
191
+ for (const [, key, value] of text.matchAll(re)) installed[key] = value;
192
+ } catch {}
193
+ return installed;
194
+ }
195
+
142
196
  function serviceEnv() {
143
- const env = { PATH: servicePath(), PORT: appPort };
144
- for (const key of ["WORK_APP_PORT", "WORK_ROOT", "WORK_DATA_DIR", "WORK_ALLOWED_HOSTS", "WORK_BIND"]) {
145
- if (process.env[key]) env[key] = process.env[key];
197
+ const env = { PATH: servicePath() };
198
+ const installed = installedServiceEnv();
199
+ for (const key of ["WORK_APP_PORT", "WORK_ROOT", "WORK_DATA_DIR", "WORK_ALLOWED_HOSTS"]) {
200
+ // `in` rather than a truthy check, so `WORK_ALLOWED_HOSTS= work setup`
201
+ // can deliberately clear a setting the installed service still carries.
202
+ const value = key in process.env ? process.env[key] : installed[key];
203
+ if (value) env[key] = value;
146
204
  }
205
+ // entry.ts prefers PORT, so carrying WORK_APP_PORT forward without this
206
+ // would leave the service on the default port anyway.
207
+ env.PORT = env.WORK_APP_PORT || appPort;
147
208
  return env;
148
209
  }
149
210
 
150
211
  function installService() {
151
212
  const cliPath = fileURLToPath(import.meta.url);
152
213
  if (process.platform === "darwin") {
153
- const plist = path.join(os.homedir(), "Library/LaunchAgents/dev.work.app.plist");
214
+ const plist = serviceFile();
154
215
  fs.mkdirSync(path.dirname(plist), { recursive: true });
155
216
  fs.mkdirSync(path.join(os.homedir(), "Library/Logs"), { recursive: true });
156
217
  const envXml = Object.entries(serviceEnv())
@@ -164,7 +225,7 @@ function installService() {
164
225
  <dict>
165
226
  <key>Label</key><string>dev.work.app</string>
166
227
  <key>ProgramArguments</key>
167
- <array><string>${process.execPath}</string><string>${cliPath}</string><string>start</string></array>
228
+ <array>${[...awakePrefix(), process.execPath, cliPath, "start"].map((a) => `<string>${a}</string>`).join("")}</array>
168
229
  <key>RunAtLoad</key><true/>
169
230
  <key>KeepAlive</key><true/>
170
231
  <key>StandardOutPath</key><string>${os.homedir()}/Library/Logs/work.log</string>
@@ -192,22 +253,31 @@ ${envXml}
192
253
  }
193
254
 
194
255
  if (process.platform === "linux" && have("systemctl")) {
195
- const unitDir = path.join(
196
- process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config"),
197
- "systemd/user",
198
- );
256
+ const unitFile = serviceFile();
257
+ const unitDir = path.dirname(unitFile);
199
258
  fs.mkdirSync(unitDir, { recursive: true });
200
259
  const envLines = Object.entries(serviceEnv())
201
260
  .map(([k, v]) => `Environment=${k}=${v}`)
202
261
  .join("\n");
262
+ // systemd splits ExecStart on whitespace, so quote the inhibitor's --why.
263
+ const execStart = [...awakePrefix(), process.execPath, cliPath, "start"]
264
+ .map((a) => (a.includes(" ") ? `"${a}"` : a))
265
+ .join(" ");
203
266
  fs.writeFileSync(
204
- path.join(unitDir, "work.service"),
267
+ unitFile,
205
268
  `[Unit]
206
269
  Description=work — agentic dev workspace GUI
270
+ # Keep retrying forever: hitting the default start limit parks the unit in
271
+ # failed state, which is the manual-restart hole we are closing.
272
+ StartLimitIntervalSec=0
207
273
 
208
274
  [Service]
209
- ExecStart=${process.execPath} ${cliPath} start
210
- Restart=on-failure
275
+ ExecStart=${execStart}
276
+ # always, not on-failure: a stray SIGTERM (an agent running \`pkill node\`) or a
277
+ # clean exit looks like success to systemd and leaves the service down until
278
+ # someone notices. Matches launchd's KeepAlive on the macOS side.
279
+ Restart=always
280
+ RestartSec=2
211
281
  ${envLines}
212
282
 
213
283
  [Install]
@@ -227,12 +297,12 @@ WantedBy=default.target
227
297
  warn("no service manager found — start manually with: work start");
228
298
  }
229
299
 
230
- function maybeTunnel() {
300
+ function maybeTunnel(port = appPort) {
231
301
  if (yes || have("cloudflared")) return;
232
302
  console.log(`
233
303
  To reach work from your phone or other devices, put it behind something
234
304
  that terminates TLS and authenticates — e.g. a Cloudflare Tunnel with
235
305
  Access (https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/)
236
- pointing at http://localhost:${appPort}, or Tailscale. The server itself has
306
+ pointing at http://localhost:${port}, or Tailscale. The server itself has
237
307
  no auth of its own.`);
238
308
  }