tide-commander 0.52.0 → 0.53.2

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/dist/index.html CHANGED
@@ -19,7 +19,7 @@
19
19
  <meta name="apple-mobile-web-app-title" content="Tide CMD" />
20
20
  <link rel="apple-touch-icon" href="/assets/icons/icon-192.png" />
21
21
  <title>Tide Commander</title>
22
- <script type="module" crossorigin src="/assets/main-DMTRw3br.js"></script>
22
+ <script type="module" crossorigin src="/assets/main-rd_RRZq_.js"></script>
23
23
  <link rel="modulepreload" crossorigin href="/assets/vendor-react-uS-d4TUT.js">
24
24
  <link rel="modulepreload" crossorigin href="/assets/vendor-three-4iQNXcoo.js">
25
25
  <link rel="stylesheet" crossorigin href="/assets/main-BIpLsrUu.css">
@@ -1,25 +1,44 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn } from 'node:child_process';
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
3
5
  import path from 'node:path';
4
6
  import { fileURLToPath } from 'node:url';
7
+ const PID_DIR = path.join(os.homedir(), '.local', 'share', 'tide-commander');
8
+ const PID_FILE = path.join(PID_DIR, 'server.pid');
9
+ const LOG_FILE = path.join(process.cwd(), 'logs', 'server.log');
5
10
  function printHelp() {
6
11
  console.log(`Tide Commander
7
12
 
8
13
  Usage:
9
- tide-commander [options]
14
+ tide-commander [start] [options]
15
+ tide-commander stop
16
+ tide-commander status
17
+ tide-commander logs [--lines <n>] [--follow]
10
18
 
11
19
  Options:
12
20
  -p, --port <port> Set server port (default: 5174)
13
21
  -H, --host <host> Set server host (default: 127.0.0.1)
14
22
  -l, --listen-all Listen on all network interfaces
15
23
  -f, --foreground Run in foreground (default is background)
24
+ --lines <n> Number of log lines for logs command (default: 100)
25
+ --follow Follow logs stream (like tail -f)
16
26
  -h, --help Show this help message
17
27
  `);
18
28
  }
19
29
  function parseArgs(argv) {
20
- const options = {};
30
+ const options = { command: 'start' };
31
+ let commandParsed = false;
21
32
  for (let i = 0; i < argv.length; i += 1) {
22
33
  const arg = argv[i];
34
+ if (!arg.startsWith('-') && !commandParsed) {
35
+ if (arg === 'start' || arg === 'stop' || arg === 'status' || arg === 'logs') {
36
+ options.command = arg;
37
+ commandParsed = true;
38
+ continue;
39
+ }
40
+ throw new Error(`Unknown command: ${arg}`);
41
+ }
23
42
  switch (arg) {
24
43
  case '-p':
25
44
  case '--port': {
@@ -47,8 +66,29 @@ function parseArgs(argv) {
47
66
  break;
48
67
  case '-f':
49
68
  case '--foreground':
50
- options.foreground = true;
69
+ if (options.command === 'logs') {
70
+ options.follow = true;
71
+ }
72
+ else {
73
+ options.foreground = true;
74
+ }
75
+ break;
76
+ case '--follow':
77
+ options.follow = true;
51
78
  break;
79
+ case '--lines': {
80
+ const value = argv[i + 1];
81
+ if (!value || value.startsWith('-')) {
82
+ throw new Error(`Missing value for ${arg}`);
83
+ }
84
+ const lines = Number(value);
85
+ if (!Number.isInteger(lines) || lines < 1) {
86
+ throw new Error(`Invalid lines value: ${value}`);
87
+ }
88
+ options.lines = lines;
89
+ i += 1;
90
+ break;
91
+ }
52
92
  case '-h':
53
93
  case '--help':
54
94
  options.help = true;
@@ -65,12 +105,106 @@ function validatePort(value) {
65
105
  throw new Error(`Invalid port: ${value}`);
66
106
  }
67
107
  }
108
+ function ensurePidDir() {
109
+ fs.mkdirSync(PID_DIR, { recursive: true });
110
+ }
111
+ function writePidFile(pid) {
112
+ ensurePidDir();
113
+ fs.writeFileSync(PID_FILE, `${pid}\n`, 'utf8');
114
+ }
115
+ function clearPidFile() {
116
+ try {
117
+ fs.rmSync(PID_FILE, { force: true });
118
+ }
119
+ catch {
120
+ // no-op
121
+ }
122
+ }
123
+ function readPidFile() {
124
+ try {
125
+ const raw = fs.readFileSync(PID_FILE, 'utf8').trim();
126
+ const pid = Number(raw);
127
+ return Number.isInteger(pid) && pid > 0 ? pid : null;
128
+ }
129
+ catch {
130
+ return null;
131
+ }
132
+ }
133
+ function isRunning(pid) {
134
+ try {
135
+ process.kill(pid, 0);
136
+ return true;
137
+ }
138
+ catch {
139
+ return false;
140
+ }
141
+ }
142
+ function stopCommand() {
143
+ const pid = readPidFile();
144
+ if (!pid) {
145
+ console.log('Tide Commander is not running');
146
+ return 0;
147
+ }
148
+ if (!isRunning(pid)) {
149
+ clearPidFile();
150
+ console.log('Removed stale PID file');
151
+ return 0;
152
+ }
153
+ process.kill(pid, 'SIGTERM');
154
+ console.log(`Sent SIGTERM to Tide Commander (PID: ${pid})`);
155
+ return 0;
156
+ }
157
+ function statusCommand() {
158
+ const pid = readPidFile();
159
+ if (!pid) {
160
+ console.log('Tide Commander is stopped');
161
+ return 1;
162
+ }
163
+ if (!isRunning(pid)) {
164
+ clearPidFile();
165
+ console.log('Tide Commander is stopped (stale PID file removed)');
166
+ return 1;
167
+ }
168
+ console.log(`Tide Commander is running (PID: ${pid})`);
169
+ return 0;
170
+ }
171
+ async function logsCommand(options) {
172
+ if (!fs.existsSync(LOG_FILE)) {
173
+ console.error(`Log file not found: ${LOG_FILE}`);
174
+ return 1;
175
+ }
176
+ const lines = options.lines ?? 100;
177
+ const args = ['-n', String(lines)];
178
+ if (options.follow) {
179
+ args.push('-f');
180
+ }
181
+ args.push(LOG_FILE);
182
+ const tail = spawn('tail', args, { stdio: 'inherit' });
183
+ return await new Promise((resolve) => {
184
+ tail.on('error', (error) => {
185
+ console.error(`Failed to read logs: ${error.message}`);
186
+ resolve(1);
187
+ });
188
+ tail.on('exit', (code) => {
189
+ resolve(code ?? 0);
190
+ });
191
+ });
192
+ }
68
193
  async function main() {
69
194
  const options = parseArgs(process.argv.slice(2));
70
195
  if (options.help) {
71
196
  printHelp();
72
197
  return;
73
198
  }
199
+ if (options.command === 'stop') {
200
+ process.exit(stopCommand());
201
+ }
202
+ if (options.command === 'status') {
203
+ process.exit(statusCommand());
204
+ }
205
+ if (options.command === 'logs') {
206
+ process.exit(await logsCommand(options));
207
+ }
74
208
  if (options.port) {
75
209
  validatePort(options.port);
76
210
  process.env.PORT = options.port;
@@ -85,6 +219,12 @@ async function main() {
85
219
  const cliDir = path.dirname(fileURLToPath(import.meta.url));
86
220
  const serverEntry = path.join(cliDir, 'index.js');
87
221
  const runInForeground = options.foreground === true || process.env.TIDE_COMMANDER_FOREGROUND === '1';
222
+ const existingPid = readPidFile();
223
+ if (existingPid && isRunning(existingPid)) {
224
+ console.log(`Tide Commander is already running (PID: ${existingPid})`);
225
+ return;
226
+ }
227
+ clearPidFile();
88
228
  const child = spawn(process.execPath, ['--experimental-specifier-resolution=node', serverEntry], {
89
229
  stdio: runInForeground ? 'inherit' : 'ignore',
90
230
  detached: !runInForeground,
@@ -95,11 +235,26 @@ async function main() {
95
235
  process.exit(1);
96
236
  });
97
237
  if (!runInForeground) {
238
+ if (child.pid) {
239
+ writePidFile(child.pid);
240
+ }
98
241
  child.unref();
99
- console.log(`Tide Commander started in background (PID: ${child.pid ?? 'unknown'})`);
242
+ const port = process.env.PORT || '5174';
243
+ const host = process.env.HOST || 'localhost';
244
+ const url = `http://${host}:${port}`;
245
+ console.log('\n🌊 Tide Commander');
246
+ console.log('═══════════════════════════════════════════════════════════');
247
+ console.log(`āœ“ Started in background (PID: ${child.pid ?? 'unknown'})`);
248
+ console.log(`šŸš€ Open: ${url}`);
249
+ console.log(`šŸ“ Logs: tail -f logs/server.log`);
250
+ console.log('═══════════════════════════════════════════════════════════\n');
100
251
  return;
101
252
  }
253
+ if (child.pid) {
254
+ writePidFile(child.pid);
255
+ }
102
256
  child.on('exit', (code, signal) => {
257
+ clearPidFile();
103
258
  if (signal) {
104
259
  process.kill(process.pid, signal);
105
260
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tide-commander",
3
- "version": "0.52.0",
3
+ "version": "0.53.2",
4
4
  "description": "Visual multi-agent orchestrator and manager for Claude Code with 3D/2D interface",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,20 +27,10 @@
27
27
  "test:ci": "vitest run --exclude src/packages/client/hooks/__tests__/useSnapshots.test.ts --exclude src/packages/client/hooks/__tests__/useKeyboardShortcuts.test.ts",
28
28
  "test:watch": "vitest",
29
29
  "test:coverage": "vitest run --coverage",
30
- "prepack": "npm run build",
31
- "cap:sync": "npx cap sync android",
32
- "cap:open": "npx cap open android",
33
- "cap:build": "npm run build && npx cap sync android",
34
- "android": "npm run build && npx cap sync android && npx cap open android"
30
+ "prepack": "npm run build"
35
31
  },
36
32
  "dependencies": {
37
33
  "@anthropic-ai/sdk": "^0.71.2",
38
- "@capacitor/android": "^8.0.1",
39
- "@capacitor/cli": "^8.0.1",
40
- "@capacitor/core": "^8.0.1",
41
- "@capacitor/haptics": "^8.0.0",
42
- "@capacitor/local-notifications": "^8.0.0",
43
- "@capawesome/capacitor-background-task": "^8.0.0",
44
34
  "@tanstack/react-virtual": "^3.13.18",
45
35
  "@types/oracledb": "^6.10.1",
46
36
  "archiver": "^7.0.1",
@@ -1 +0,0 @@
1
- import{W as a,I as i,N as r}from"./main-DMTRw3br.js";import"./vendor-react-uS-d4TUT.js";import"./vendor-three-4iQNXcoo.js";class l 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{l as HapticsWeb};
@@ -1 +0,0 @@
1
- import{W as s}from"./main-DMTRw3br.js";import"./vendor-react-uS-d4TUT.js";import"./vendor-three-4iQNXcoo.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};