tide-commander 1.136.2 → 1.138.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.
Files changed (45) hide show
  1. package/dist/assets/{BossLogsModal-B4p16n9B.js → BossLogsModal-DK_gRY9J.js} +1 -1
  2. package/dist/assets/{BossSpawnModal-BHQ7sxeu.js → BossSpawnModal-CVeg1ba3.js} +1 -1
  3. package/dist/assets/{ControlsModal-BcP5-m0h.js → ControlsModal-CJpzUh2S.js} +1 -1
  4. package/dist/assets/{DockerLogsModal-C_Gd6YLn.js → DockerLogsModal-PMHHWFgI.js} +1 -1
  5. package/dist/assets/{EmbeddedEditor-GHVeXIn5.js → EmbeddedEditor-D6bkBhBz.js} +1 -1
  6. package/dist/assets/{GmailOAuthSetup-VjAKcJf9.js → GmailOAuthSetup-D8FK1wPP.js} +1 -1
  7. package/dist/assets/{GoogleOAuthSetup-DFbaN0Ry.js → GoogleOAuthSetup-DfS-KTPX.js} +1 -1
  8. package/dist/assets/{IframeModal-BaoVOZTf.js → IframeModal-BD9t2CjJ.js} +1 -1
  9. package/dist/assets/{IntegrationsPanel-Cg4TxOfh.js → IntegrationsPanel-kUFlC88j.js} +2 -2
  10. package/dist/assets/{LogViewerModal-CN2GYpgr.js → LogViewerModal-DzRFl5vE.js} +1 -1
  11. package/dist/assets/{MonitoringModal-oN_JjoC2.js → MonitoringModal-B1NjpT1x.js} +1 -1
  12. package/dist/assets/{PM2LogsModal-BOQ1Im-i.js → PM2LogsModal-CSAyzJg8.js} +1 -1
  13. package/dist/assets/{RestoreArchivedAreaModal-DYcjqjbA.js → RestoreArchivedAreaModal-CoQZj4FX.js} +1 -1
  14. package/dist/assets/{Scene2DCanvas-CsBA5ERQ.js → Scene2DCanvas-CHzKb6s7.js} +1 -1
  15. package/dist/assets/{SceneManager-BqVfBAQc.js → SceneManager-Ce5eSBMz.js} +1 -1
  16. package/dist/assets/{SkillsPanel-CtfixcgQ.js → SkillsPanel-WpSKoJdE.js} +1 -1
  17. package/dist/assets/{SlackMultiInstanceSetup-3nGO_xs6.js → SlackMultiInstanceSetup-hwQKLUTu.js} +1 -1
  18. package/dist/assets/{SpawnModal-BvUHN-Q1.js → SpawnModal-Cy8oM5b_.js} +1 -1
  19. package/dist/assets/{StatisticsModal-DIx8l3F0.js → StatisticsModal-DlGR1kB7.js} +1 -1
  20. package/dist/assets/{SubordinateAssignmentModal-Bw-V4i4-.js → SubordinateAssignmentModal-BGJAkzuP.js} +1 -1
  21. package/dist/assets/{TriggerManagerPanel-C2Mz8wVK.js → TriggerManagerPanel-CNooFcwx.js} +1 -1
  22. package/dist/assets/{WorkflowEditorPanel-C2RHV8Dx.js → WorkflowEditorPanel-hG09npK6.js} +1 -1
  23. package/dist/assets/{area-logos-aaW_jqzI.js → area-logos-C_pyB3G7.js} +1 -1
  24. package/dist/assets/{index-BzCUPOjQ.js → index-B6gdpT3P.js} +1 -1
  25. package/dist/assets/index-BBl0GYIy.js +1 -0
  26. package/dist/assets/{index-D6r1xAEg.js → index-BGiOafvR.js} +1 -1
  27. package/dist/assets/index-CA9Bjg56.js +2 -0
  28. package/dist/assets/{index-DidVGiaJ.js → index-CF_MNOzj.js} +1 -1
  29. package/dist/assets/{index-CmGwjdPh.js → index-CS_wb805.js} +3 -3
  30. package/dist/assets/{index-BGz62M0g.js → index-DMjx_AHN.js} +1 -1
  31. package/dist/assets/{index-BvR0it5l.js → index-DjDuXM2y.js} +3 -3
  32. package/dist/assets/{index-BIUEhCXu.js → index-DtHo9zM8.js} +5 -5
  33. package/dist/assets/{main-rDQDtv_U.js → main-B1AS0Idb.js} +107 -97
  34. package/dist/assets/{main-Ba3wdQK_.css → main-DQ1lnGdW.css} +1 -1
  35. package/dist/assets/{web-DojkIxYY.js → web-BT2I5zMh.js} +1 -1
  36. package/dist/assets/{web-B5Ma_-eu.js → web-DrPcxPKA.js} +1 -1
  37. package/dist/assets/{web-Dy1328BB.js → web-DyJaKm0M.js} +1 -1
  38. package/dist/index.html +2 -2
  39. package/dist/locales/en/config.json +14 -1
  40. package/dist/src/packages/server/cli.js +8 -1
  41. package/dist/src/packages/server/routes/system.js +28 -11
  42. package/dist/src/packages/server/services/self-update-service.js +52 -0
  43. package/package.json +1 -1
  44. package/dist/assets/index-B4JG--zt.js +0 -2
  45. package/dist/assets/index-BT7u2ftY.js +0 -11
@@ -1 +1 @@
1
- import{cO as t}from"./main-rDQDtv_U.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class o extends t{constructor(){super(),this.handleVisibilityChange=()=>{const e={isActive:document.hidden!==!0};this.notifyListeners("appStateChange",e),document.hidden?this.notifyListeners("pause",null):this.notifyListeners("resume",null)},document.addEventListener("visibilitychange",this.handleVisibilityChange,!1)}exitApp(){throw this.unimplemented("Not implemented on web.")}async getInfo(){throw this.unimplemented("Not implemented on web.")}async getLaunchUrl(){return{url:""}}async getState(){return{isActive:document.hidden!==!0}}async minimizeApp(){throw this.unimplemented("Not implemented on web.")}async toggleBackButtonHandler(){throw this.unimplemented("Not implemented on web.")}async getAppLanguage(){return{value:navigator.language.split("-")[0].toLowerCase()}}}export{o as AppWeb};
1
+ import{cP as t}from"./main-B1AS0Idb.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class o extends t{constructor(){super(),this.handleVisibilityChange=()=>{const e={isActive:document.hidden!==!0};this.notifyListeners("appStateChange",e),document.hidden?this.notifyListeners("pause",null):this.notifyListeners("resume",null)},document.addEventListener("visibilitychange",this.handleVisibilityChange,!1)}exitApp(){throw this.unimplemented("Not implemented on web.")}async getInfo(){throw this.unimplemented("Not implemented on web.")}async getLaunchUrl(){return{url:""}}async getState(){return{isActive:document.hidden!==!0}}async minimizeApp(){throw this.unimplemented("Not implemented on web.")}async toggleBackButtonHandler(){throw this.unimplemented("Not implemented on web.")}async getAppLanguage(){return{value:navigator.language.split("-")[0].toLowerCase()}}}export{o as AppWeb};
@@ -1 +1 @@
1
- import{cO as a}from"./main-rDQDtv_U.js";import{ImpactStyle as i,NotificationType as r}from"./index-B4JG--zt.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class h extends a{constructor(){super(...arguments),this.selectionStarted=!1}async impact(t){const e=this.patternForImpact(t==null?void 0:t.style);this.vibrateWithPattern(e)}async notification(t){const e=this.patternForNotification(t==null?void 0:t.type);this.vibrateWithPattern(e)}async vibrate(t){const e=(t==null?void 0:t.duration)||300;this.vibrateWithPattern([e])}async selectionStart(){this.selectionStarted=!0}async selectionChanged(){this.selectionStarted&&this.vibrateWithPattern([70])}async selectionEnd(){this.selectionStarted=!1}patternForImpact(t=i.Heavy){return t===i.Medium?[43]:t===i.Light?[20]:[61]}patternForNotification(t=r.Success){return t===r.Warning?[30,40,30,50,60]:t===r.Error?[27,45,50]:[35,65,21]}vibrateWithPattern(t){if(navigator.vibrate)navigator.vibrate(t);else throw this.unavailable("Browser does not support the vibrate API")}}export{h as HapticsWeb};
1
+ import{cP as a}from"./main-B1AS0Idb.js";import{ImpactStyle as i,NotificationType as r}from"./index-CA9Bjg56.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class h extends a{constructor(){super(...arguments),this.selectionStarted=!1}async impact(t){const e=this.patternForImpact(t==null?void 0:t.style);this.vibrateWithPattern(e)}async notification(t){const e=this.patternForNotification(t==null?void 0:t.type);this.vibrateWithPattern(e)}async vibrate(t){const e=(t==null?void 0:t.duration)||300;this.vibrateWithPattern([e])}async selectionStart(){this.selectionStarted=!0}async selectionChanged(){this.selectionStarted&&this.vibrateWithPattern([70])}async selectionEnd(){this.selectionStarted=!1}patternForImpact(t=i.Heavy){return t===i.Medium?[43]:t===i.Light?[20]:[61]}patternForNotification(t=r.Success){return t===r.Warning?[30,40,30,50,60]:t===r.Error?[27,45,50]:[35,65,21]}vibrateWithPattern(t){if(navigator.vibrate)navigator.vibrate(t);else throw this.unavailable("Browser does not support the vibrate API")}}export{h as HapticsWeb};
@@ -1 +1 @@
1
- import{cO as s}from"./main-rDQDtv_U.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class l extends s{constructor(){super(...arguments),this.pending=[],this.deliveredNotifications=[],this.hasNotificationSupport=()=>{if(!("Notification"in window)||!Notification.requestPermission)return!1;if(Notification.permission!=="granted")try{new Notification("")}catch(i){if(i instanceof Error&&i.name==="TypeError")return!1}return!0}}async getDeliveredNotifications(){const i=[];for(const t of this.deliveredNotifications){const e={title:t.title,id:parseInt(t.tag),body:t.body};i.push(e)}return{notifications:i}}async removeDeliveredNotifications(i){for(const t of i.notifications){const e=this.deliveredNotifications.find(n=>n.tag===String(t.id));e==null||e.close(),this.deliveredNotifications=this.deliveredNotifications.filter(()=>!e)}}async removeAllDeliveredNotifications(){for(const i of this.deliveredNotifications)i.close();this.deliveredNotifications=[]}async createChannel(){throw this.unimplemented("Not implemented on web.")}async deleteChannel(){throw this.unimplemented("Not implemented on web.")}async listChannels(){throw this.unimplemented("Not implemented on web.")}async schedule(i){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");for(const t of i.notifications)this.sendNotification(t);return{notifications:i.notifications.map(t=>({id:t.id}))}}async getPending(){return{notifications:this.pending}}async registerActionTypes(){throw this.unimplemented("Not implemented on web.")}async cancel(i){this.pending=this.pending.filter(t=>!i.notifications.find(e=>e.id===t.id))}async areEnabled(){const{display:i}=await this.checkPermissions();return{value:i==="granted"}}async changeExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async checkExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async requestPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(await Notification.requestPermission())}}async checkPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(Notification.permission)}}transformNotificationPermission(i){switch(i){case"granted":return"granted";case"denied":return"denied";default:return"prompt"}}sendPending(){var i;const t=[],e=new Date().getTime();for(const n of this.pending)!((i=n.schedule)===null||i===void 0)&&i.at&&n.schedule.at.getTime()<=e&&(this.buildNotification(n),t.push(n));this.pending=this.pending.filter(n=>!t.find(o=>o===n))}sendNotification(i){var t;if(!((t=i.schedule)===null||t===void 0)&&t.at){const e=i.schedule.at.getTime()-new Date().getTime();this.pending.push(i),setTimeout(()=>{this.sendPending()},e);return}this.buildNotification(i)}buildNotification(i){const t=new Notification(i.title,{body:i.body,tag:String(i.id)});return t.addEventListener("click",this.onClick.bind(this,i),!1),t.addEventListener("show",this.onShow.bind(this,i),!1),t.addEventListener("close",()=>{this.deliveredNotifications=this.deliveredNotifications.filter(()=>!this)},!1),this.deliveredNotifications.push(t),t}onClick(i){const t={actionId:"tap",notification:i};this.notifyListeners("localNotificationActionPerformed",t)}onShow(i){this.notifyListeners("localNotificationReceived",i)}}export{l as LocalNotificationsWeb};
1
+ import{cP as s}from"./main-B1AS0Idb.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class l extends s{constructor(){super(...arguments),this.pending=[],this.deliveredNotifications=[],this.hasNotificationSupport=()=>{if(!("Notification"in window)||!Notification.requestPermission)return!1;if(Notification.permission!=="granted")try{new Notification("")}catch(i){if(i instanceof Error&&i.name==="TypeError")return!1}return!0}}async getDeliveredNotifications(){const i=[];for(const t of this.deliveredNotifications){const e={title:t.title,id:parseInt(t.tag),body:t.body};i.push(e)}return{notifications:i}}async removeDeliveredNotifications(i){for(const t of i.notifications){const e=this.deliveredNotifications.find(n=>n.tag===String(t.id));e==null||e.close(),this.deliveredNotifications=this.deliveredNotifications.filter(()=>!e)}}async removeAllDeliveredNotifications(){for(const i of this.deliveredNotifications)i.close();this.deliveredNotifications=[]}async createChannel(){throw this.unimplemented("Not implemented on web.")}async deleteChannel(){throw this.unimplemented("Not implemented on web.")}async listChannels(){throw this.unimplemented("Not implemented on web.")}async schedule(i){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");for(const t of i.notifications)this.sendNotification(t);return{notifications:i.notifications.map(t=>({id:t.id}))}}async getPending(){return{notifications:this.pending}}async registerActionTypes(){throw this.unimplemented("Not implemented on web.")}async cancel(i){this.pending=this.pending.filter(t=>!i.notifications.find(e=>e.id===t.id))}async areEnabled(){const{display:i}=await this.checkPermissions();return{value:i==="granted"}}async changeExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async checkExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async requestPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(await Notification.requestPermission())}}async checkPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(Notification.permission)}}transformNotificationPermission(i){switch(i){case"granted":return"granted";case"denied":return"denied";default:return"prompt"}}sendPending(){var i;const t=[],e=new Date().getTime();for(const n of this.pending)!((i=n.schedule)===null||i===void 0)&&i.at&&n.schedule.at.getTime()<=e&&(this.buildNotification(n),t.push(n));this.pending=this.pending.filter(n=>!t.find(o=>o===n))}sendNotification(i){var t;if(!((t=i.schedule)===null||t===void 0)&&t.at){const e=i.schedule.at.getTime()-new Date().getTime();this.pending.push(i),setTimeout(()=>{this.sendPending()},e);return}this.buildNotification(i)}buildNotification(i){const t=new Notification(i.title,{body:i.body,tag:String(i.id)});return t.addEventListener("click",this.onClick.bind(this,i),!1),t.addEventListener("show",this.onShow.bind(this,i),!1),t.addEventListener("close",()=>{this.deliveredNotifications=this.deliveredNotifications.filter(()=>!this)},!1),this.deliveredNotifications.push(t),t}onClick(i){const t={actionId:"tap",notification:i};this.notifyListeners("localNotificationActionPerformed",t)}onShow(i){this.notifyListeners("localNotificationReceived",i)}}export{l as LocalNotificationsWeb};
package/dist/index.html CHANGED
@@ -22,10 +22,10 @@
22
22
  <link rel="icon" type="image/png" sizes="16x16" href="/assets/icons/favicon-16x16.png" />
23
23
  <link rel="apple-touch-icon" sizes="180x180" href="/assets/icons/apple-touch-icon.png" />
24
24
  <title>Tide Commander</title>
25
- <script type="module" crossorigin src="/assets/main-rDQDtv_U.js"></script>
25
+ <script type="module" crossorigin src="/assets/main-B1AS0Idb.js"></script>
26
26
  <link rel="modulepreload" crossorigin href="/assets/vendor-react--Eh9ivFN.js">
27
27
  <link rel="modulepreload" crossorigin href="/assets/vendor-three-Chj50gSY.js">
28
- <link rel="stylesheet" crossorigin href="/assets/main-Ba3wdQK_.css">
28
+ <link rel="stylesheet" crossorigin href="/assets/main-DQ1lnGdW.css">
29
29
  </head>
30
30
  <body>
31
31
  <div id="app"></div>
@@ -554,18 +554,31 @@
554
554
  "autoUpdateButton": "Update now",
555
555
  "autoUpdateRunning": "Installing update...",
556
556
  "autoUpdateConfirmTitle": "Update Tide Commander?",
557
- "autoUpdateConfirmBody": "This will run npm install -g tide-commander@latest and then close the server. You will need to relaunch tide-commander from your terminal.",
557
+ "autoUpdateConfirmBody": "This will run npm install -g tide-commander@latest and then automatically restart the server. The UI will reconnect on its own once it is back up.",
558
558
  "autoUpdateConfirm": "Yes, update",
559
559
  "autoUpdateCancel": "Cancel",
560
560
  "autoUpdateClose": "Close",
561
561
  "autoUpdateOutput": "Install log",
562
562
  "autoUpdateSuccess": "Update installed.",
563
563
  "autoUpdateRestartHint": "Please restart Tide Commander from your terminal (run: tide-commander).",
564
+ "autoUpdateReconnectHint": "Restarting automatically — reconnecting shortly...",
564
565
  "autoUpdateFailed": "Update failed.",
565
566
  "autoUpdateDevMode": "Auto-update is unavailable in dev mode.",
566
567
  "autoUpdateManualCommand": "Run this in your terminal to update manually:",
567
568
  "autoUpdatePackageManagerNotice": "Auto-update only supports npm-installed globals. Detected: {{pm}}."
568
569
  },
570
+ "updateBanner": {
571
+ "newVersion": "New version v{{version}} available",
572
+ "updateBtn": "Update & restart",
573
+ "dismiss": "Ignore",
574
+ "confirmWarning": "Restarting will interrupt agents currently working. Continue?",
575
+ "confirmYes": "Yes, update & restart",
576
+ "confirmCancel": "Cancel",
577
+ "installing": "Installing update…",
578
+ "reconnecting": "Update installed — restarting, reconnecting shortly…",
579
+ "failed": "Update failed",
580
+ "close": "Close"
581
+ },
569
582
  "shortcuts": {
570
583
  "pressKeys": "Press keys...",
571
584
  "notSet": "Not set",
@@ -53,6 +53,7 @@ Options:
53
53
  current terminal so logs print here and Ctrl+C stops it.
54
54
  --lines <n> Number of log lines for logs command (default: 100)
55
55
  --follow Follow logs stream (like tail -f)
56
+ --restart Restart a running server, reusing its current config
56
57
  -h, --help Show this help message
57
58
  `);
58
59
  }
@@ -143,6 +144,9 @@ function parseArgs(argv) {
143
144
  case '--follow':
144
145
  options.follow = true;
145
146
  break;
147
+ case '--restart':
148
+ options.restart = true;
149
+ break;
146
150
  case '--lines': {
147
151
  const value = argv[i + 1];
148
152
  if (!value || value.startsWith('-')) {
@@ -646,6 +650,9 @@ async function main() {
646
650
  || options.authToken !== undefined
647
651
  || options.generateAuthToken === true
648
652
  || options.foreground === true;
653
+ // An explicit --restart (used by the post-update auto-restart) forces a clean
654
+ // restart of the running server even when no config overrides were passed.
655
+ const shouldRestartExisting = hasStartupOverrides || options.restart === true;
649
656
  if (existingPid && isRunning(existingPid)) {
650
657
  if (runInForeground) {
651
658
  // In foreground mode (PM2 managed), the process manager handles lifecycle.
@@ -663,7 +670,7 @@ async function main() {
663
670
  clearPidFile();
664
671
  clearServerMeta();
665
672
  }
666
- else if (hasStartupOverrides) {
673
+ else if (shouldRestartExisting) {
667
674
  try {
668
675
  process.kill(existingPid, 'SIGTERM');
669
676
  }
@@ -5,7 +5,7 @@
5
5
  import { Router } from 'express';
6
6
  import { createLogger } from '../utils/logger.js';
7
7
  import { fetchLatestNpmVersion, getVersionRelation } from '../../shared/version.js';
8
- import { getInstallInfo, isAutoUpdateSupported, runNpmGlobalUpdate, } from '../services/self-update-service.js';
8
+ import { getInstallInfo, isAutoUpdateSupported, runNpmGlobalUpdate, schedulePostUpdateRestart, } from '../services/self-update-service.js';
9
9
  const log = createLogger('SystemRoutes');
10
10
  const router = Router();
11
11
  const PACKAGE_NAME = 'tide-commander';
@@ -56,8 +56,9 @@ router.get('/install-info', async (_req, res) => {
56
56
  * - done { success, exitCode, newVersion, requiresRestart }
57
57
  * - error { message, permissionDenied, suggestedManualCommand }
58
58
  *
59
- * On success the server schedules its own exit so the next launch picks up
60
- * the new binary. The user must relaunch tide-commander manually.
59
+ * On success the server relaunches itself (detached `tide-commander start
60
+ * --restart`) so the new binary comes up automatically; if that can't be
61
+ * scheduled it falls back to exiting for a manual restart.
61
62
  */
62
63
  router.post('/self-update', async (_req, res) => {
63
64
  const info = getInstallInfo();
@@ -107,21 +108,37 @@ router.post('/self-update', async (_req, res) => {
107
108
  clearInterval(keepalive);
108
109
  if (result.exitCode === 0) {
109
110
  const newVersion = (await fetchLatestNpmVersion(PACKAGE_NAME)) ?? null;
111
+ // Try to bring the new binary up automatically. The relauncher will
112
+ // SIGTERM this process, so we must NOT exit ourselves when it's scheduled.
113
+ const autoRestart = schedulePostUpdateRestart();
110
114
  send('done', {
111
115
  success: true,
112
116
  exitCode: 0,
113
117
  newVersion,
114
118
  requiresRestart: true,
115
- message: 'Update installed. Please restart Tide Commander from your terminal.',
119
+ autoRestart,
120
+ message: autoRestart
121
+ ? 'Update installed — Tide Commander is restarting automatically. The UI will reconnect in a few seconds.'
122
+ : 'Update installed. Please restart Tide Commander from your terminal.',
116
123
  });
117
124
  res.end();
118
- // Give the client ~1.5s to render the success message before we exit.
119
- // The user must manually restart with `tide-commander` from the terminal.
120
- log.log('Update succeeded scheduling server exit in 1500ms to release the old binary.');
121
- setTimeout(() => {
122
- log.log('Exiting after successful update. Restart manually with: tide-commander');
123
- process.exit(0);
124
- }, 1500);
125
+ if (autoRestart) {
126
+ log.log('Update succeeded detached relauncher scheduled; awaiting takeover.');
127
+ // Safety net: if the relauncher never takes over, don't stay locked.
128
+ setTimeout(() => {
129
+ log.warn('Relauncher did not take over within 30s — clearing update lock.');
130
+ updateInProgress = false;
131
+ }, 30000).unref?.();
132
+ }
133
+ else {
134
+ // Couldn't schedule an auto-restart — fall back to the old behaviour:
135
+ // exit so the next manual launch picks up the new binary.
136
+ log.log('Auto-restart unavailable — scheduling exit in 1500ms for manual restart.');
137
+ setTimeout(() => {
138
+ log.log('Exiting after successful update. Restart manually with: tide-commander');
139
+ process.exit(0);
140
+ }, 1500);
141
+ }
125
142
  }
126
143
  else {
127
144
  const errMsg = result.permissionDenied
@@ -189,3 +189,55 @@ export function runNpmGlobalUpdate(callbacks = {}) {
189
189
  });
190
190
  });
191
191
  }
192
+ /**
193
+ * After a successful global update, bring up the freshly installed binary
194
+ * without the user having to relaunch by hand.
195
+ *
196
+ * Spawns a fully detached `tide-commander start --restart` that inherits the
197
+ * current server's environment (same PORT/HOST/AUTH_TOKEN/HTTPS). That process
198
+ * SIGTERMs the still-running old server (triggering its graceful shutdown so the
199
+ * port is released), waits for it to exit, then starts the new server and
200
+ * rewrites the PID file. The caller MUST NOT exit — the relauncher drives the
201
+ * handoff.
202
+ *
203
+ * Returns true if the relauncher was scheduled, false if it could not be
204
+ * (in which case the caller should fall back to exiting for a manual restart).
205
+ */
206
+ export function schedulePostUpdateRestart(delayMs = 600) {
207
+ let cliPath;
208
+ try {
209
+ // self-update-service.js lives at dist/.../server/services/, the CLI entry
210
+ // at dist/.../server/cli.js — one level up.
211
+ cliPath = fileURLToPath(new URL('../cli.js', import.meta.url));
212
+ }
213
+ catch (err) {
214
+ log.error(`Cannot resolve CLI path for auto-restart: ${err.message}`);
215
+ return false;
216
+ }
217
+ if (!fs.existsSync(cliPath)) {
218
+ log.error(`CLI entrypoint not found for auto-restart: ${cliPath}`);
219
+ return false;
220
+ }
221
+ // Small delay so the SSE 'done' frame flushes to the client before the
222
+ // relauncher SIGTERMs us.
223
+ const timer = setTimeout(() => {
224
+ try {
225
+ const child = spawn(process.execPath, [cliPath, 'start', '--restart'], {
226
+ detached: true,
227
+ stdio: 'ignore',
228
+ env: process.env,
229
+ cwd: process.cwd(),
230
+ });
231
+ child.on('error', (err) => {
232
+ log.error(`Auto-restart relauncher failed to spawn: ${err.message}`);
233
+ });
234
+ child.unref();
235
+ log.log('Spawned detached relauncher: tide-commander start --restart');
236
+ }
237
+ catch (err) {
238
+ log.error(`Failed to spawn auto-restart relauncher: ${err.message}`);
239
+ }
240
+ }, delayMs);
241
+ timer.unref?.();
242
+ return true;
243
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tide-commander",
3
- "version": "1.136.2",
3
+ "version": "1.138.0",
4
4
  "description": "Visual multi-agent orchestrator and manager for Claude Code with 3D/2D interface",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,2 +0,0 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/web-B5Ma_-eu.js","assets/main-rDQDtv_U.js","assets/vendor-react--Eh9ivFN.js","assets/vendor-three-Chj50gSY.js","assets/main-Ba3wdQK_.css"])))=>i.map(i=>d[i]);
2
- import{cD as e,bL as o}from"./main-rDQDtv_U.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";var i;(function(r){r.Heavy="HEAVY",r.Medium="MEDIUM",r.Light="LIGHT"})(i||(i={}));var t;(function(r){r.Success="SUCCESS",r.Warning="WARNING",r.Error="ERROR"})(t||(t={}));const c=e("Haptics",{web:()=>o(()=>import("./web-B5Ma_-eu.js"),__vite__mapDeps([0,1,2,3,4])).then(r=>new r.HapticsWeb)});export{c as Haptics,i as ImpactStyle,t as NotificationType};