@juspay/shooter 1.7.1 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/hooks/notifier.cjs +481 -134
- package/bin/shooter.cjs +570 -12
- package/build/client/_app/immutable/assets/{0.B5tfAY8Y.css → 0.BZLcOr5z.css} +1 -1
- package/build/client/_app/immutable/assets/0.BZLcOr5z.css.br +0 -0
- package/build/client/_app/immutable/assets/0.BZLcOr5z.css.gz +0 -0
- package/build/client/_app/immutable/chunks/3EfvnCrr.js +1 -0
- package/build/client/_app/immutable/chunks/3EfvnCrr.js.br +0 -0
- package/build/client/_app/immutable/chunks/3EfvnCrr.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{CBvZimn-.js → C2Qh_9aV.js} +1 -1
- package/build/client/_app/immutable/chunks/C2Qh_9aV.js.br +0 -0
- package/build/client/_app/immutable/chunks/C2Qh_9aV.js.gz +0 -0
- package/build/client/_app/immutable/chunks/C2yx8lo8.js +3 -0
- package/build/client/_app/immutable/chunks/C2yx8lo8.js.br +0 -0
- package/build/client/_app/immutable/chunks/C2yx8lo8.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DdpA9n1s.js +5 -0
- package/build/client/_app/immutable/chunks/DdpA9n1s.js.br +4 -0
- package/build/client/_app/immutable/chunks/DdpA9n1s.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{D347PuJK.js → klpn9j-A.js} +1 -1
- package/build/client/_app/immutable/chunks/klpn9j-A.js.br +0 -0
- package/build/client/_app/immutable/chunks/klpn9j-A.js.gz +0 -0
- package/build/client/_app/immutable/chunks/xs1Xl3_e.js +1 -0
- package/build/client/_app/immutable/chunks/xs1Xl3_e.js.br +0 -0
- package/build/client/_app/immutable/chunks/xs1Xl3_e.js.gz +0 -0
- package/build/client/_app/immutable/entry/{app.C_IPOstn.js → app.Bfisx3a0.js} +2 -2
- package/build/client/_app/immutable/entry/app.Bfisx3a0.js.br +0 -0
- package/build/client/_app/immutable/entry/app.Bfisx3a0.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.BfgeeQYG.js +1 -0
- package/build/client/_app/immutable/entry/start.BfgeeQYG.js.br +2 -0
- package/build/client/_app/immutable/entry/start.BfgeeQYG.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{0.DRy3hSaJ.js → 0.Vg8bq-s8.js} +1 -1
- package/build/client/_app/immutable/nodes/0.Vg8bq-s8.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.Vg8bq-s8.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{1.BL16K8Rb.js → 1.B7kXtFIl.js} +1 -1
- package/build/client/_app/immutable/nodes/1.B7kXtFIl.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.B7kXtFIl.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{2.iOKTlmTJ.js → 2.DVFe_SN2.js} +2 -2
- package/build/client/_app/immutable/nodes/2.DVFe_SN2.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.DVFe_SN2.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{3.GuF9Fr8V.js → 3.Deb3vtJl.js} +3 -3
- package/build/client/_app/immutable/nodes/3.Deb3vtJl.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.Deb3vtJl.js.gz +0 -0
- package/build/client/_app/immutable/nodes/5.BN2SM61w.js +1 -0
- package/build/client/_app/immutable/nodes/5.BN2SM61w.js.br +0 -0
- package/build/client/_app/immutable/nodes/5.BN2SM61w.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{6.DSC53pZZ.js → 6.CS_KYbQ7.js} +1 -1
- package/build/client/_app/immutable/nodes/6.CS_KYbQ7.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.CS_KYbQ7.js.gz +0 -0
- package/build/client/_app/immutable/nodes/7.CEiUUm74.js +4 -0
- package/build/client/_app/immutable/nodes/7.CEiUUm74.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.CEiUUm74.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{8.Dn1iVaxc.js → 8.DGStHrkF.js} +2 -2
- package/build/client/_app/immutable/nodes/8.DGStHrkF.js.br +0 -0
- package/build/client/_app/immutable/nodes/8.DGStHrkF.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{9.D3nURtQJ.js → 9.CbIw97FV.js} +1 -1
- package/build/client/_app/immutable/nodes/9.CbIw97FV.js.br +0 -0
- package/build/client/_app/immutable/nodes/9.CbIw97FV.js.gz +0 -0
- package/build/client/_app/version.json +1 -1
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/server/chunks/{0-D82zFKZh.js → 0-ikRVFZQS.js} +3 -3
- package/build/server/chunks/{0-D82zFKZh.js.map → 0-ikRVFZQS.js.map} +1 -1
- package/build/server/chunks/{1-BmaGmZUH.js → 1-CG-fUUbt.js} +2 -2
- package/build/server/chunks/{1-BmaGmZUH.js.map → 1-CG-fUUbt.js.map} +1 -1
- package/build/server/chunks/{2-T5uLjTgt.js → 2-BaV1q6GP.js} +2 -2
- package/build/server/chunks/{2-T5uLjTgt.js.map → 2-BaV1q6GP.js.map} +1 -1
- package/build/server/chunks/{3-D6FHwoMf.js → 3-BpzQzk4m.js} +2 -2
- package/build/server/chunks/{3-D6FHwoMf.js.map → 3-BpzQzk4m.js.map} +1 -1
- package/build/server/chunks/{5-Bj49x3to.js → 5-DRhcUdp_.js} +2 -2
- package/build/server/chunks/{5-Bj49x3to.js.map → 5-DRhcUdp_.js.map} +1 -1
- package/build/server/chunks/{6-Yh_tXC7x.js → 6-Bx-SE48t.js} +2 -2
- package/build/server/chunks/{6-Yh_tXC7x.js.map → 6-Bx-SE48t.js.map} +1 -1
- package/build/server/chunks/{7-Dmz1unig.js → 7-LeGA4bt3.js} +3 -3
- package/build/server/chunks/{7-Dmz1unig.js.map → 7-LeGA4bt3.js.map} +1 -1
- package/build/server/chunks/{8-Bf6s5ssE.js → 8-CXwtG-B-.js} +2 -2
- package/build/server/chunks/{8-Bf6s5ssE.js.map → 8-CXwtG-B-.js.map} +1 -1
- package/build/server/chunks/{9-AHy0sACd.js → 9-DBztKl9o.js} +2 -2
- package/build/server/chunks/{9-AHy0sACd.js.map → 9-DBztKl9o.js.map} +1 -1
- package/build/server/chunks/{_page.svelte-D1Iaycsm.js → _page.svelte-DpUVuiqQ.js} +3 -13
- package/build/server/chunks/_page.svelte-DpUVuiqQ.js.map +1 -0
- package/build/server/chunks/{_server.ts-A9_tRR-K.js → _server.ts-9P1PrkiL.js} +2 -2
- package/build/server/chunks/{_server.ts-A9_tRR-K.js.map → _server.ts-9P1PrkiL.js.map} +1 -1
- package/build/server/chunks/{_server.ts-G8OeADGj.js → _server.ts-BAPg2K8u.js} +5 -2
- package/build/server/chunks/_server.ts-BAPg2K8u.js.map +1 -0
- package/build/server/chunks/{_server.ts-BQarRaho.js → _server.ts-CilRds58.js} +14 -11
- package/build/server/chunks/_server.ts-CilRds58.js.map +1 -0
- package/build/server/chunks/{library-apns-Cf-E-DhM.js → library-apns-CUDtjHQk.js} +3 -2
- package/build/server/chunks/library-apns-CUDtjHQk.js.map +1 -0
- package/build/server/index.js +1 -1
- package/build/server/index.js.map +1 -1
- package/build/server/manifest.js +13 -13
- package/build/server/manifest.js.map +1 -1
- package/package.json +1 -1
- package/scripts/update-checker.cjs +196 -0
- package/scripts/update-state.cjs +235 -0
- package/server.ts +55 -3
- package/src/lib/modules/client/activity/summarizer.ts +1 -1
- package/src/lib/modules/client/dashboard/summarizer.ts +1 -2
- package/src/lib/modules/client/neurolink/cdn.ts +6 -0
- package/src/lib/modules/client/terminal/LaunchSheet.svelte +5 -12
- package/src/lib/modules/server/apn/library-apns.ts +1 -0
- package/src/lib/modules/server/fcm/fcm-service.ts +1 -0
- package/src/lib/theme.css +33 -1
- package/src/lib/types/apn.ts +1 -0
- package/src/routes/api/notify/+server.ts +2 -0
- package/src/routes/api/sessions/+server.ts +16 -10
- package/src/routes/neurolink/+page.svelte +4 -6
- package/src/routes/session/[id]/+page.svelte +25 -14
- package/build/client/_app/immutable/assets/0.B5tfAY8Y.css.br +0 -0
- package/build/client/_app/immutable/assets/0.B5tfAY8Y.css.gz +0 -0
- package/build/client/_app/immutable/chunks/Bkcn25gz.js +0 -3
- package/build/client/_app/immutable/chunks/Bkcn25gz.js.br +0 -0
- package/build/client/_app/immutable/chunks/Bkcn25gz.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CBvZimn-.js.br +0 -0
- package/build/client/_app/immutable/chunks/CBvZimn-.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CRXYOhph.js +0 -5
- package/build/client/_app/immutable/chunks/CRXYOhph.js.br +0 -0
- package/build/client/_app/immutable/chunks/CRXYOhph.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D347PuJK.js.br +0 -0
- package/build/client/_app/immutable/chunks/D347PuJK.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DQM017d5.js +0 -1
- package/build/client/_app/immutable/chunks/DQM017d5.js.br +0 -0
- package/build/client/_app/immutable/chunks/DQM017d5.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Dqgg-a0I.js +0 -1
- package/build/client/_app/immutable/chunks/Dqgg-a0I.js.br +0 -0
- package/build/client/_app/immutable/chunks/Dqgg-a0I.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.C_IPOstn.js.br +0 -0
- package/build/client/_app/immutable/entry/app.C_IPOstn.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.BfrjAeOs.js +0 -1
- package/build/client/_app/immutable/entry/start.BfrjAeOs.js.br +0 -2
- package/build/client/_app/immutable/entry/start.BfrjAeOs.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.DRy3hSaJ.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.DRy3hSaJ.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.BL16K8Rb.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.BL16K8Rb.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.iOKTlmTJ.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.iOKTlmTJ.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.GuF9Fr8V.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.GuF9Fr8V.js.gz +0 -0
- package/build/client/_app/immutable/nodes/5.DRvLQ5NR.js +0 -1
- package/build/client/_app/immutable/nodes/5.DRvLQ5NR.js.br +0 -0
- package/build/client/_app/immutable/nodes/5.DRvLQ5NR.js.gz +0 -0
- package/build/client/_app/immutable/nodes/6.DSC53pZZ.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.DSC53pZZ.js.gz +0 -0
- package/build/client/_app/immutable/nodes/7.C8lL01pY.js +0 -4
- package/build/client/_app/immutable/nodes/7.C8lL01pY.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.C8lL01pY.js.gz +0 -0
- package/build/client/_app/immutable/nodes/8.Dn1iVaxc.js.br +0 -0
- package/build/client/_app/immutable/nodes/8.Dn1iVaxc.js.gz +0 -0
- package/build/client/_app/immutable/nodes/9.D3nURtQJ.js.br +0 -0
- package/build/client/_app/immutable/nodes/9.D3nURtQJ.js.gz +0 -0
- package/build/server/chunks/_page.svelte-D1Iaycsm.js.map +0 -1
- package/build/server/chunks/_server.ts-BQarRaho.js.map +0 -1
- package/build/server/chunks/_server.ts-G8OeADGj.js.map +0 -1
- package/build/server/chunks/library-apns-Cf-E-DhM.js.map +0 -1
package/bin/shooter.cjs
CHANGED
|
@@ -85,6 +85,15 @@ function removePid() {
|
|
|
85
85
|
} catch {}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
// ── Guard / auto-update constants ──────────────────────────────────
|
|
89
|
+
const GUARD_PID_FILE = path.join(SHOOTER_HOME, 'guard.pid');
|
|
90
|
+
const GUARD_LOG_FILE = path.join(LOG_DIR, 'guard.log');
|
|
91
|
+
const UPDATE_CHECK_INTERVAL_MS = 2 * 60 * 60 * 1000; // 2 hours
|
|
92
|
+
const UPDATE_FIRST_CHECK_MS = 30_000; // 30 seconds after start
|
|
93
|
+
const INSTALL_TIMEOUT_MS = 120_000;
|
|
94
|
+
const BUILD_TIMEOUT_MS = 300_000;
|
|
95
|
+
const HEALTH_TIMEOUT_MS = 30_000;
|
|
96
|
+
|
|
88
97
|
// ── CLI argument parsing ────────────────────────────────────────────
|
|
89
98
|
const args = process.argv.slice(2);
|
|
90
99
|
const command = args[0] || 'start';
|
|
@@ -108,6 +117,12 @@ switch (command) {
|
|
|
108
117
|
case 'setup':
|
|
109
118
|
runSetup();
|
|
110
119
|
break;
|
|
120
|
+
case 'update':
|
|
121
|
+
runUpdate(args[1]);
|
|
122
|
+
break;
|
|
123
|
+
case 'guard':
|
|
124
|
+
runGuard();
|
|
125
|
+
break;
|
|
111
126
|
case 'version':
|
|
112
127
|
case '--version':
|
|
113
128
|
case '-v':
|
|
@@ -130,6 +145,35 @@ function hasFlag(flag) {
|
|
|
130
145
|
return args.includes(flag);
|
|
131
146
|
}
|
|
132
147
|
|
|
148
|
+
// Keep in sync with `parsePortArg` in server.ts.
|
|
149
|
+
function parsePortFlag() {
|
|
150
|
+
const isValid = (n) => Number.isInteger(n) && n >= 0 && n < 65536;
|
|
151
|
+
const fail = (raw) => {
|
|
152
|
+
console.error(`Error: invalid --port value "${raw}" — expected an integer in 0-65535.`);
|
|
153
|
+
process.exit(2);
|
|
154
|
+
};
|
|
155
|
+
for (let i = 0; i < args.length; i++) {
|
|
156
|
+
const a = args[i];
|
|
157
|
+
if ((a === '--port' || a === '-p') && args[i + 1] !== undefined) {
|
|
158
|
+
const raw = args[i + 1];
|
|
159
|
+
const n = parseInt(raw, 10);
|
|
160
|
+
if (!isValid(n)) fail(raw);
|
|
161
|
+
return n;
|
|
162
|
+
} else if (a.startsWith('--port=')) {
|
|
163
|
+
const raw = a.slice('--port='.length);
|
|
164
|
+
const n = parseInt(raw, 10);
|
|
165
|
+
if (!isValid(n)) fail(raw);
|
|
166
|
+
return n;
|
|
167
|
+
} else if (a.startsWith('-p=')) {
|
|
168
|
+
const raw = a.slice('-p='.length);
|
|
169
|
+
const n = parseInt(raw, 10);
|
|
170
|
+
if (!isValid(n)) fail(raw);
|
|
171
|
+
return n;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
|
|
133
177
|
function isCloudflaredAvailable() {
|
|
134
178
|
try {
|
|
135
179
|
execSync('which cloudflared', { stdio: 'ignore' });
|
|
@@ -216,9 +260,12 @@ function startServer() {
|
|
|
216
260
|
|
|
217
261
|
const daemon = hasFlag('--daemon') || hasFlag('-d');
|
|
218
262
|
const noTunnel = hasFlag('--no-tunnel');
|
|
219
|
-
const
|
|
263
|
+
const cliPort = parsePortFlag();
|
|
264
|
+
const port = cliPort ?? resolvePort();
|
|
220
265
|
|
|
221
|
-
//
|
|
266
|
+
// Fail-fast pre-flight so a busy port is surfaced before we spawn the
|
|
267
|
+
// server and start the Cloudflare Tunnel — keeping tunnel and listen
|
|
268
|
+
// port in sync.
|
|
222
269
|
try {
|
|
223
270
|
execSync(
|
|
224
271
|
`"${process.execPath}" -e "const s=require('net').createServer();s.listen(${parseInt(port, 10)},()=>s.close());s.on('error',e=>{if(e.code==='EADDRINUSE')process.exit(1)})"`,
|
|
@@ -226,7 +273,7 @@ function startServer() {
|
|
|
226
273
|
);
|
|
227
274
|
} catch {
|
|
228
275
|
console.error(`Error: Port ${port} is already in use.`);
|
|
229
|
-
console.error('Stop the existing process or set
|
|
276
|
+
console.error('Stop the existing process, pass --port <num>, or set PORT in ~/.shooter/.env');
|
|
230
277
|
process.exit(1);
|
|
231
278
|
}
|
|
232
279
|
|
|
@@ -256,6 +303,7 @@ function startServer() {
|
|
|
256
303
|
stdio: ['ignore', logFd, logFd],
|
|
257
304
|
env: {
|
|
258
305
|
...process.env,
|
|
306
|
+
PORT: String(port),
|
|
259
307
|
SHOOTER_PKG_ROOT: PKG_ROOT,
|
|
260
308
|
SHOOTER_HOME,
|
|
261
309
|
},
|
|
@@ -279,6 +327,10 @@ function startServer() {
|
|
|
279
327
|
} else if (!noTunnel && !isCloudflaredAvailable()) {
|
|
280
328
|
console.log(' (cloudflared not found — no tunnel. Install: brew install cloudflared)');
|
|
281
329
|
}
|
|
330
|
+
|
|
331
|
+
// Spawn auto-update guard (detached) — only when launchd is managing
|
|
332
|
+
spawnGuard(child.pid, port);
|
|
333
|
+
|
|
282
334
|
// Exit immediately — daemon is running, tunnel starts async
|
|
283
335
|
process.exit(0);
|
|
284
336
|
} else {
|
|
@@ -292,6 +344,7 @@ function startServer() {
|
|
|
292
344
|
stdio: 'inherit',
|
|
293
345
|
env: {
|
|
294
346
|
...process.env,
|
|
347
|
+
PORT: String(port),
|
|
295
348
|
SHOOTER_PKG_ROOT: PKG_ROOT,
|
|
296
349
|
SHOOTER_HOME,
|
|
297
350
|
},
|
|
@@ -312,9 +365,13 @@ function startServer() {
|
|
|
312
365
|
}, 3000);
|
|
313
366
|
}
|
|
314
367
|
|
|
368
|
+
// Spawn auto-update guard (detached) — only when launchd is managing
|
|
369
|
+
spawnGuard(child.pid, port);
|
|
370
|
+
|
|
315
371
|
child.on('error', (err) => {
|
|
316
372
|
removePid();
|
|
317
373
|
if (tunnelStarted) stopTunnel();
|
|
374
|
+
stopGuard();
|
|
318
375
|
console.error('Failed to start Shooter server:', err.message);
|
|
319
376
|
process.exit(1);
|
|
320
377
|
});
|
|
@@ -322,6 +379,7 @@ function startServer() {
|
|
|
322
379
|
child.on('exit', (code, signal) => {
|
|
323
380
|
removePid();
|
|
324
381
|
stopTunnel();
|
|
382
|
+
stopGuard();
|
|
325
383
|
if (signal) {
|
|
326
384
|
process.exit(128 + (signalCode(signal) || 1));
|
|
327
385
|
}
|
|
@@ -333,6 +391,7 @@ function startServer() {
|
|
|
333
391
|
process.on(sig, () => {
|
|
334
392
|
child.kill(sig);
|
|
335
393
|
stopTunnel();
|
|
394
|
+
stopGuard();
|
|
336
395
|
});
|
|
337
396
|
}
|
|
338
397
|
}
|
|
@@ -351,6 +410,7 @@ function stopServer() {
|
|
|
351
410
|
|
|
352
411
|
console.log(`Stopping Shooter (PID ${pid})...`);
|
|
353
412
|
stopTunnel();
|
|
413
|
+
stopGuard();
|
|
354
414
|
try {
|
|
355
415
|
process.kill(pid, 'SIGTERM');
|
|
356
416
|
// Wait briefly for clean shutdown
|
|
@@ -681,6 +741,493 @@ function runSetup() {
|
|
|
681
741
|
});
|
|
682
742
|
}
|
|
683
743
|
|
|
744
|
+
// ── update ──────────────────────────────────────────────────────────
|
|
745
|
+
|
|
746
|
+
function runUpdate(subcommand) {
|
|
747
|
+
const { checkForUpdate } = require(path.join(PKG_ROOT, 'scripts', 'update-checker.cjs'));
|
|
748
|
+
const { recordCheck, isVersionSuppressed } = require(path.join(PKG_ROOT, 'scripts', 'update-state.cjs'));
|
|
749
|
+
|
|
750
|
+
const result = checkForUpdate(PKG_ROOT);
|
|
751
|
+
if (!result.checkFailed) recordCheck(result.latestVersion);
|
|
752
|
+
|
|
753
|
+
if (subcommand === 'check') {
|
|
754
|
+
// Just check, don't install
|
|
755
|
+
if (result.checkFailed) {
|
|
756
|
+
console.error(`Update check failed: ${result.error}`);
|
|
757
|
+
process.exitCode = 1;
|
|
758
|
+
} else if (result.updateAvailable) {
|
|
759
|
+
console.log(`Update available: ${result.currentVersion} → ${result.latestVersion}`);
|
|
760
|
+
console.log(` Current commit: ${result.currentCommit}`);
|
|
761
|
+
console.log(` Latest commit: ${result.latestCommit}`);
|
|
762
|
+
if (isVersionSuppressed(result.latestVersion)) {
|
|
763
|
+
console.log(` (version ${result.latestVersion} is temporarily suppressed — will retry in <24h)`);
|
|
764
|
+
}
|
|
765
|
+
} else {
|
|
766
|
+
console.log(`Already up to date: v${result.currentVersion} (${result.currentCommit})`);
|
|
767
|
+
}
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// Default: check + install
|
|
772
|
+
if (result.checkFailed) {
|
|
773
|
+
console.error(`Update check failed: ${result.error}`);
|
|
774
|
+
process.exitCode = 1;
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
if (!result.updateAvailable) {
|
|
778
|
+
console.log(`Already up to date: v${result.currentVersion} (${result.currentCommit})`);
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// Only allow installs on the release branch
|
|
783
|
+
const { getCurrentBranch } = require(path.join(PKG_ROOT, 'scripts', 'update-checker.cjs'));
|
|
784
|
+
const branch = getCurrentBranch(PKG_ROOT);
|
|
785
|
+
if (branch !== 'release') {
|
|
786
|
+
console.log(`Cannot update: currently on branch '${branch || 'unknown'}', updates only apply to 'release'.`);
|
|
787
|
+
console.log('Switch to the release branch and try again.');
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if (isVersionSuppressed(result.latestVersion)) {
|
|
792
|
+
console.log(`Update to ${result.latestVersion} is temporarily suppressed (previous failure).`);
|
|
793
|
+
console.log('Suppression expires within 24 hours. Use a fresh git pull to override.');
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
console.log(`Updating: ${result.currentVersion} → ${result.latestVersion}...`);
|
|
798
|
+
const success = performUpdate(result);
|
|
799
|
+
if (success) {
|
|
800
|
+
console.log(`\nUpdate complete! Now running v${result.latestVersion}.`);
|
|
801
|
+
|
|
802
|
+
// Restart if server is running
|
|
803
|
+
const pid = readPid();
|
|
804
|
+
if (pid) {
|
|
805
|
+
if (isLaunchdManaging()) {
|
|
806
|
+
const uid = process.getuid ? process.getuid() : 501;
|
|
807
|
+
try {
|
|
808
|
+
execSync(`launchctl kickstart -k gui/${uid}/${LAUNCHD_LABEL}`, {
|
|
809
|
+
timeout: 10_000,
|
|
810
|
+
stdio: 'pipe',
|
|
811
|
+
});
|
|
812
|
+
console.log('Signaled launchd to restart the server.');
|
|
813
|
+
} catch {
|
|
814
|
+
console.log('launchctl kickstart failed — the server may need a manual restart.');
|
|
815
|
+
}
|
|
816
|
+
} else {
|
|
817
|
+
try {
|
|
818
|
+
process.kill(pid, 'SIGTERM');
|
|
819
|
+
} catch {}
|
|
820
|
+
console.log('Server process terminated. Run "shooter start" to restart.');
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* Perform the actual update: git pull → pnpm install → pnpm build.
|
|
828
|
+
* Returns true on success. On failure, rolls back and returns false.
|
|
829
|
+
*/
|
|
830
|
+
function performUpdate(result) {
|
|
831
|
+
const { suppressVersion, recordSuccessfulUpdate } = require(path.join(PKG_ROOT, 'scripts', 'update-state.cjs'));
|
|
832
|
+
|
|
833
|
+
// Save current HEAD for rollback
|
|
834
|
+
let savedHead = '';
|
|
835
|
+
try {
|
|
836
|
+
savedHead = execSync('git rev-parse HEAD', {
|
|
837
|
+
cwd: PKG_ROOT,
|
|
838
|
+
encoding: 'utf8',
|
|
839
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
840
|
+
}).trim();
|
|
841
|
+
} catch {}
|
|
842
|
+
|
|
843
|
+
// 1. Git pull (fast-forward only)
|
|
844
|
+
try {
|
|
845
|
+
console.log(' Pulling latest changes...');
|
|
846
|
+
execSync('git pull --ff-only origin release', {
|
|
847
|
+
cwd: PKG_ROOT,
|
|
848
|
+
stdio: 'inherit',
|
|
849
|
+
timeout: 30_000,
|
|
850
|
+
});
|
|
851
|
+
} catch (err) {
|
|
852
|
+
console.error(' Git pull failed:', err.message || err);
|
|
853
|
+
suppressVersion(result.latestVersion, 'pull_failed');
|
|
854
|
+
return false;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// 2. Install dependencies
|
|
858
|
+
try {
|
|
859
|
+
console.log(' Installing dependencies...');
|
|
860
|
+
execSync('pnpm install --frozen-lockfile', {
|
|
861
|
+
cwd: PKG_ROOT,
|
|
862
|
+
stdio: 'inherit',
|
|
863
|
+
timeout: INSTALL_TIMEOUT_MS,
|
|
864
|
+
});
|
|
865
|
+
} catch (err) {
|
|
866
|
+
console.error(' pnpm install failed:', err.message || err);
|
|
867
|
+
rollback(savedHead);
|
|
868
|
+
suppressVersion(result.latestVersion, 'install_failed');
|
|
869
|
+
return false;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// 3. Build
|
|
873
|
+
try {
|
|
874
|
+
console.log(' Building...');
|
|
875
|
+
execSync('pnpm build', {
|
|
876
|
+
cwd: PKG_ROOT,
|
|
877
|
+
stdio: 'inherit',
|
|
878
|
+
timeout: BUILD_TIMEOUT_MS,
|
|
879
|
+
});
|
|
880
|
+
} catch (err) {
|
|
881
|
+
console.error(' Build failed:', err.message || err);
|
|
882
|
+
rollback(savedHead);
|
|
883
|
+
suppressVersion(result.latestVersion, 'build_failed');
|
|
884
|
+
return false;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
recordSuccessfulUpdate(result.latestVersion, result.currentVersion);
|
|
888
|
+
return true;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
/**
|
|
892
|
+
* Roll back to a previous commit after a failed update.
|
|
893
|
+
*/
|
|
894
|
+
function rollback(savedHead) {
|
|
895
|
+
if (!savedHead) return;
|
|
896
|
+
console.log(' Rolling back to previous version...');
|
|
897
|
+
try {
|
|
898
|
+
execSync(`git reset --hard ${savedHead}`, {
|
|
899
|
+
cwd: PKG_ROOT,
|
|
900
|
+
stdio: 'inherit',
|
|
901
|
+
timeout: 10_000,
|
|
902
|
+
});
|
|
903
|
+
execSync('pnpm install --frozen-lockfile', {
|
|
904
|
+
cwd: PKG_ROOT,
|
|
905
|
+
stdio: 'inherit',
|
|
906
|
+
timeout: INSTALL_TIMEOUT_MS,
|
|
907
|
+
});
|
|
908
|
+
execSync('pnpm build', {
|
|
909
|
+
cwd: PKG_ROOT,
|
|
910
|
+
stdio: 'inherit',
|
|
911
|
+
timeout: BUILD_TIMEOUT_MS,
|
|
912
|
+
});
|
|
913
|
+
} catch (rollbackErr) {
|
|
914
|
+
console.error(' WARNING: Rollback failed:', rollbackErr.message || rollbackErr);
|
|
915
|
+
console.error(' Manual intervention may be required.');
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// ── guard (hidden — spawned by start) ──────────────────────────────
|
|
920
|
+
|
|
921
|
+
function spawnGuard(parentPid, port) {
|
|
922
|
+
// Only spawn guard when launchd is managing the service
|
|
923
|
+
if (!isLaunchdManaging()) return;
|
|
924
|
+
|
|
925
|
+
const shooterBin = path.join(PKG_ROOT, 'bin', 'shooter.cjs');
|
|
926
|
+
try {
|
|
927
|
+
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
928
|
+
const logFd = fs.openSync(GUARD_LOG_FILE, 'a');
|
|
929
|
+
|
|
930
|
+
const child = spawn(
|
|
931
|
+
process.execPath,
|
|
932
|
+
[shooterBin, 'guard', '--parent-pid', String(parentPid), '--port', String(port)],
|
|
933
|
+
{
|
|
934
|
+
cwd: PKG_ROOT,
|
|
935
|
+
detached: true,
|
|
936
|
+
stdio: ['ignore', logFd, logFd],
|
|
937
|
+
env: {
|
|
938
|
+
...process.env,
|
|
939
|
+
SHOOTER_PKG_ROOT: PKG_ROOT,
|
|
940
|
+
SHOOTER_HOME,
|
|
941
|
+
},
|
|
942
|
+
}
|
|
943
|
+
);
|
|
944
|
+
|
|
945
|
+
if (child.pid) {
|
|
946
|
+
fs.mkdirSync(SHOOTER_HOME, { recursive: true });
|
|
947
|
+
fs.writeFileSync(GUARD_PID_FILE, String(child.pid));
|
|
948
|
+
}
|
|
949
|
+
child.unref();
|
|
950
|
+
fs.closeSync(logFd);
|
|
951
|
+
} catch (err) {
|
|
952
|
+
// Non-fatal — server runs fine without guard
|
|
953
|
+
console.log(` (auto-update guard failed to start: ${err.message})`);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
function stopGuard() {
|
|
958
|
+
try {
|
|
959
|
+
const pid = parseInt(fs.readFileSync(GUARD_PID_FILE, 'utf8').trim(), 10);
|
|
960
|
+
if (!isNaN(pid) && pid > 0) {
|
|
961
|
+
// Validate the PID is actually a guard process (prevent PID reuse attacks)
|
|
962
|
+
try {
|
|
963
|
+
const cmdline = execSync(`ps -p ${pid} -o command= 2>/dev/null`, {
|
|
964
|
+
encoding: 'utf8',
|
|
965
|
+
}).trim();
|
|
966
|
+
if (!cmdline.includes('shooter.cjs') || !cmdline.includes('guard')) {
|
|
967
|
+
// PID reused by an unrelated process — just clean up the stale pidfile
|
|
968
|
+
fs.unlinkSync(GUARD_PID_FILE);
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
} catch {
|
|
972
|
+
// ps failed — process likely already exited
|
|
973
|
+
fs.unlinkSync(GUARD_PID_FILE);
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
try {
|
|
977
|
+
process.kill(pid, 'SIGTERM');
|
|
978
|
+
} catch {}
|
|
979
|
+
}
|
|
980
|
+
} catch {}
|
|
981
|
+
try {
|
|
982
|
+
fs.unlinkSync(GUARD_PID_FILE);
|
|
983
|
+
} catch {}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
function isLaunchdManaging() {
|
|
987
|
+
if (os.platform() !== 'darwin') return false;
|
|
988
|
+
try {
|
|
989
|
+
const uid = process.getuid ? process.getuid() : 501;
|
|
990
|
+
execSync(`launchctl print gui/${uid}/${LAUNCHD_LABEL} 2>/dev/null`, {
|
|
991
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
992
|
+
timeout: 5_000,
|
|
993
|
+
});
|
|
994
|
+
return true;
|
|
995
|
+
} catch {
|
|
996
|
+
return false;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
function runGuard() {
|
|
1001
|
+
// Parse guard-specific args
|
|
1002
|
+
let parentPid = 0;
|
|
1003
|
+
let port = DEFAULT_PORT;
|
|
1004
|
+
for (let i = 1; i < args.length; i++) {
|
|
1005
|
+
if (args[i] === '--parent-pid' && args[i + 1]) {
|
|
1006
|
+
parentPid = parseInt(args[i + 1], 10);
|
|
1007
|
+
i++;
|
|
1008
|
+
} else if (args[i] === '--port' && args[i + 1]) {
|
|
1009
|
+
port = parseInt(args[i + 1], 10);
|
|
1010
|
+
i++;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
if (!parentPid || parentPid <= 0) {
|
|
1015
|
+
console.error('[guard] Invalid --parent-pid');
|
|
1016
|
+
process.exit(1);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
const { checkForUpdate, getCurrentBranch } = require(path.join(PKG_ROOT, 'scripts', 'update-checker.cjs'));
|
|
1020
|
+
const {
|
|
1021
|
+
recordCheck,
|
|
1022
|
+
isVersionSuppressed,
|
|
1023
|
+
suppressVersion,
|
|
1024
|
+
recordSuccessfulUpdate,
|
|
1025
|
+
} = require(path.join(PKG_ROOT, 'scripts', 'update-state.cjs'));
|
|
1026
|
+
|
|
1027
|
+
const log = (msg) => console.log(`[guard] ${new Date().toISOString()} ${msg}`);
|
|
1028
|
+
|
|
1029
|
+
// Check if parent is alive
|
|
1030
|
+
function isParentAlive() {
|
|
1031
|
+
try {
|
|
1032
|
+
process.kill(parentPid, 0);
|
|
1033
|
+
return true;
|
|
1034
|
+
} catch {
|
|
1035
|
+
return false;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
let updateInProgress = false;
|
|
1040
|
+
let updateRestartInProgress = false;
|
|
1041
|
+
|
|
1042
|
+
async function runUpdateCheck() {
|
|
1043
|
+
if (updateInProgress) return;
|
|
1044
|
+
updateInProgress = true;
|
|
1045
|
+
|
|
1046
|
+
try {
|
|
1047
|
+
// Only auto-update on release branch
|
|
1048
|
+
const branch = getCurrentBranch(PKG_ROOT);
|
|
1049
|
+
if (branch !== 'release') {
|
|
1050
|
+
log(`skipping update check — on branch '${branch}', not 'release'`);
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
// 1. Check for update
|
|
1055
|
+
const result = checkForUpdate(PKG_ROOT);
|
|
1056
|
+
if (result.checkFailed) {
|
|
1057
|
+
log(`update check failed: ${result.error}`);
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
recordCheck(result.latestVersion);
|
|
1061
|
+
|
|
1062
|
+
if (!result.updateAvailable) {
|
|
1063
|
+
log(`up to date: v${result.currentVersion}`);
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
if (isVersionSuppressed(result.latestVersion)) {
|
|
1068
|
+
log(`version ${result.latestVersion} is suppressed, skipping`);
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
log(`update available: ${result.currentVersion} → ${result.latestVersion}`);
|
|
1073
|
+
|
|
1074
|
+
// 2. Save current HEAD for rollback
|
|
1075
|
+
let savedHead = '';
|
|
1076
|
+
try {
|
|
1077
|
+
savedHead = execSync('git rev-parse HEAD', {
|
|
1078
|
+
cwd: PKG_ROOT,
|
|
1079
|
+
encoding: 'utf8',
|
|
1080
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
1081
|
+
}).trim();
|
|
1082
|
+
} catch {}
|
|
1083
|
+
|
|
1084
|
+
// 3. Git pull
|
|
1085
|
+
try {
|
|
1086
|
+
execSync('git pull --ff-only origin release', {
|
|
1087
|
+
cwd: PKG_ROOT,
|
|
1088
|
+
encoding: 'utf8',
|
|
1089
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1090
|
+
timeout: 30_000,
|
|
1091
|
+
});
|
|
1092
|
+
} catch (err) {
|
|
1093
|
+
const stderr = err.stderr ? err.stderr.toString().trim().slice(-500) : '';
|
|
1094
|
+
log(`git pull failed: ${err.message}${stderr ? '\n' + stderr : ''}`);
|
|
1095
|
+
suppressVersion(result.latestVersion, 'pull_failed');
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// 4. Install dependencies
|
|
1100
|
+
try {
|
|
1101
|
+
log('installing dependencies...');
|
|
1102
|
+
execSync('pnpm install --frozen-lockfile', {
|
|
1103
|
+
cwd: PKG_ROOT,
|
|
1104
|
+
encoding: 'utf8',
|
|
1105
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1106
|
+
timeout: INSTALL_TIMEOUT_MS,
|
|
1107
|
+
});
|
|
1108
|
+
} catch (err) {
|
|
1109
|
+
const stderr = err.stderr ? err.stderr.toString().trim().slice(-500) : '';
|
|
1110
|
+
log(`pnpm install failed: ${err.message}${stderr ? '\n' + stderr : ''}`);
|
|
1111
|
+
guardRollback(savedHead);
|
|
1112
|
+
suppressVersion(result.latestVersion, 'install_failed');
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// 5. Build
|
|
1117
|
+
try {
|
|
1118
|
+
log('building...');
|
|
1119
|
+
execSync('pnpm build', {
|
|
1120
|
+
cwd: PKG_ROOT,
|
|
1121
|
+
encoding: 'utf8',
|
|
1122
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1123
|
+
timeout: BUILD_TIMEOUT_MS,
|
|
1124
|
+
});
|
|
1125
|
+
} catch (err) {
|
|
1126
|
+
const stderr = err.stderr ? err.stderr.toString().trim().slice(-500) : '';
|
|
1127
|
+
log(`build failed: ${err.message}${stderr ? '\n' + stderr : ''}`);
|
|
1128
|
+
guardRollback(savedHead);
|
|
1129
|
+
suppressVersion(result.latestVersion, 'build_failed');
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
// 6. Restart via launchctl
|
|
1134
|
+
updateRestartInProgress = true;
|
|
1135
|
+
log('restarting via launchctl...');
|
|
1136
|
+
const uid = process.getuid ? process.getuid() : 501;
|
|
1137
|
+
try {
|
|
1138
|
+
execSync(`launchctl kickstart -k gui/${uid}/${LAUNCHD_LABEL}`, {
|
|
1139
|
+
timeout: 10_000,
|
|
1140
|
+
stdio: 'pipe',
|
|
1141
|
+
});
|
|
1142
|
+
} catch {
|
|
1143
|
+
log('WARNING: launchctl kickstart failed');
|
|
1144
|
+
suppressVersion(result.latestVersion, 'restart_failed');
|
|
1145
|
+
updateRestartInProgress = false;
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
// 7. Wait for healthy restart
|
|
1150
|
+
let healthy = false;
|
|
1151
|
+
const restartStart = Date.now();
|
|
1152
|
+
while (Date.now() - restartStart < HEALTH_TIMEOUT_MS) {
|
|
1153
|
+
await sleep(2000);
|
|
1154
|
+
try {
|
|
1155
|
+
const resp = await fetch(`http://localhost:${port}/api/health`, {
|
|
1156
|
+
signal: AbortSignal.timeout(3000),
|
|
1157
|
+
});
|
|
1158
|
+
if (resp.ok) {
|
|
1159
|
+
const data = await resp.json();
|
|
1160
|
+
if (data.version === result.latestVersion) {
|
|
1161
|
+
healthy = true;
|
|
1162
|
+
break;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
} catch {
|
|
1166
|
+
/* retry */
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
if (healthy) {
|
|
1171
|
+
log(`update successful: now running v${result.latestVersion}`);
|
|
1172
|
+
recordSuccessfulUpdate(result.latestVersion, result.currentVersion);
|
|
1173
|
+
// New server will spawn its own guard — exit this one
|
|
1174
|
+
process.exit(0);
|
|
1175
|
+
} else {
|
|
1176
|
+
log(`WARNING: server unhealthy after update to ${result.latestVersion}`);
|
|
1177
|
+
suppressVersion(result.latestVersion, 'unhealthy_after_restart');
|
|
1178
|
+
updateRestartInProgress = false;
|
|
1179
|
+
}
|
|
1180
|
+
} catch (err) {
|
|
1181
|
+
log(`update check error: ${err.message || err}`);
|
|
1182
|
+
} finally {
|
|
1183
|
+
updateInProgress = false;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
function guardRollback(savedHead) {
|
|
1188
|
+
if (!savedHead) return;
|
|
1189
|
+
log('rolling back...');
|
|
1190
|
+
try {
|
|
1191
|
+
execSync(`git reset --hard ${savedHead}`, {
|
|
1192
|
+
cwd: PKG_ROOT,
|
|
1193
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
1194
|
+
timeout: 10_000,
|
|
1195
|
+
});
|
|
1196
|
+
execSync('pnpm install --frozen-lockfile', {
|
|
1197
|
+
cwd: PKG_ROOT,
|
|
1198
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1199
|
+
timeout: INSTALL_TIMEOUT_MS,
|
|
1200
|
+
});
|
|
1201
|
+
execSync('pnpm build', {
|
|
1202
|
+
cwd: PKG_ROOT,
|
|
1203
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1204
|
+
timeout: BUILD_TIMEOUT_MS,
|
|
1205
|
+
});
|
|
1206
|
+
} catch (rollbackErr) {
|
|
1207
|
+
log(`WARNING: rollback failed: ${rollbackErr.message}`);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
// Parent process health monitor
|
|
1212
|
+
const parentCheckInterval = setInterval(() => {
|
|
1213
|
+
if (!isParentAlive() && !updateRestartInProgress) {
|
|
1214
|
+
log('parent process died, exiting guard');
|
|
1215
|
+
clearInterval(parentCheckInterval);
|
|
1216
|
+
process.exit(0);
|
|
1217
|
+
}
|
|
1218
|
+
}, 5_000);
|
|
1219
|
+
|
|
1220
|
+
// Schedule update checks
|
|
1221
|
+
setTimeout(() => void runUpdateCheck(), UPDATE_FIRST_CHECK_MS);
|
|
1222
|
+
setInterval(() => void runUpdateCheck(), UPDATE_CHECK_INTERVAL_MS);
|
|
1223
|
+
|
|
1224
|
+
log(`started (parent PID: ${parentPid}, port: ${port})`);
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
function sleep(ms) {
|
|
1228
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
1229
|
+
}
|
|
1230
|
+
|
|
684
1231
|
// ── help ────────────────────────────────────────────────────────────
|
|
685
1232
|
|
|
686
1233
|
function showHelp() {
|
|
@@ -699,22 +1246,33 @@ Commands:
|
|
|
699
1246
|
logs Tail server logs
|
|
700
1247
|
setup Quick setup (API key + build, ~60 seconds)
|
|
701
1248
|
setup --push Add/reconfigure push notifications
|
|
1249
|
+
update Check for updates and install if available
|
|
1250
|
+
update check Check for updates without installing
|
|
702
1251
|
version Show version number
|
|
703
1252
|
help Show this help message
|
|
704
1253
|
|
|
705
1254
|
Start options:
|
|
706
|
-
-d, --daemon
|
|
707
|
-
--no-tunnel
|
|
1255
|
+
-d, --daemon Run in background (detach from terminal)
|
|
1256
|
+
--no-tunnel Don't start a Cloudflare Tunnel
|
|
1257
|
+
-p, --port <num> Port to listen on (overrides PORT env)
|
|
1258
|
+
|
|
1259
|
+
Auto-update:
|
|
1260
|
+
When running as a LaunchAgent, Shooter automatically checks for updates
|
|
1261
|
+
every 2 hours. Updates are pulled from origin/release, built, and the
|
|
1262
|
+
server is restarted via launchctl. Terminal sessions survive restarts.
|
|
708
1263
|
|
|
709
1264
|
Examples:
|
|
710
|
-
shooter
|
|
711
|
-
shooter start -d
|
|
1265
|
+
shooter Start the server + tunnel (foreground)
|
|
1266
|
+
shooter start -d Start in background (daemon mode)
|
|
712
1267
|
shooter start --no-tunnel Start without Cloudflare Tunnel
|
|
713
|
-
shooter
|
|
714
|
-
shooter
|
|
715
|
-
shooter
|
|
716
|
-
shooter
|
|
717
|
-
shooter setup
|
|
1268
|
+
shooter start --port 3000 Start on port 3000
|
|
1269
|
+
shooter status Check status and tunnel URL
|
|
1270
|
+
shooter autostart on Enable autostart on login
|
|
1271
|
+
shooter logs Follow server logs
|
|
1272
|
+
shooter setup Quick setup (~60s, push deferred)
|
|
1273
|
+
shooter setup --push Add iOS/Android push notifications
|
|
1274
|
+
shooter update Check and install updates
|
|
1275
|
+
shooter update check Check for updates only
|
|
718
1276
|
`.trim()
|
|
719
1277
|
);
|
|
720
1278
|
}
|