tide-commander 0.83.0 → 0.84.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/README.md +30 -1
- package/dist/assets/main-C6IAMrFB.css +1 -0
- package/dist/assets/main-dSfKHXvf.js +305 -0
- package/dist/assets/{web-BPwrdMBS.js → web-fkUxxBLP.js} +1 -1
- package/dist/index.html +2 -2
- package/dist/locales/de/terminal.json +4 -0
- package/dist/locales/en/common.json +12 -0
- package/dist/locales/en/config.json +2 -1
- package/dist/locales/en/terminal.json +5 -0
- package/dist/locales/es/terminal.json +4 -0
- package/dist/locales/fr/terminal.json +4 -0
- package/dist/locales/hi/terminal.json +4 -0
- package/dist/locales/it/terminal.json +4 -0
- package/dist/locales/ja/terminal.json +4 -0
- package/dist/locales/pt/terminal.json +4 -0
- package/dist/locales/ru/terminal.json +4 -0
- package/dist/locales/zh-CN/terminal.json +4 -0
- package/dist/src/packages/server/cli.js +158 -5
- package/dist/src/packages/server/data/builtin-skills/index.js +2 -0
- package/dist/src/packages/server/data/builtin-skills/task-label.js +29 -0
- package/dist/src/packages/server/data/index.js +1 -0
- package/dist/src/packages/server/index.js +22 -4
- package/dist/src/packages/server/services/runtime-command-execution.js +2 -0
- package/dist/src/packages/server/services/skill-service.js +4 -4
- package/package.json +1 -1
- package/dist/assets/main-BefR5p6u.css +0 -1
- package/dist/assets/main-Bm_HUW5H.js +0 -303
|
@@ -1 +1 @@
|
|
|
1
|
-
import{W as s}from"./main-
|
|
1
|
+
import{W as s}from"./main-dSfKHXvf.js";import"./modulepreload-polyfill-B5Qt9EMX.js";import"./vendor-react-uS-d4TUT.js";import"./vendor-three-DJ4p3FLF.js";class f 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{f as LocalNotificationsWeb};
|
package/dist/index.html
CHANGED
|
@@ -22,11 +22,11 @@
|
|
|
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-
|
|
25
|
+
<script type="module" crossorigin src="/assets/main-dSfKHXvf.js"></script>
|
|
26
26
|
<link rel="modulepreload" crossorigin href="/assets/modulepreload-polyfill-B5Qt9EMX.js">
|
|
27
27
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-uS-d4TUT.js">
|
|
28
28
|
<link rel="modulepreload" crossorigin href="/assets/vendor-three-DJ4p3FLF.js">
|
|
29
|
-
<link rel="stylesheet" crossorigin href="/assets/main-
|
|
29
|
+
<link rel="stylesheet" crossorigin href="/assets/main-C6IAMrFB.css">
|
|
30
30
|
</head>
|
|
31
31
|
<body>
|
|
32
32
|
<div id="app"></div>
|
|
@@ -393,7 +393,11 @@
|
|
|
393
393
|
"acceptCurrent": "Aktuellen akzeptieren",
|
|
394
394
|
"acceptIncoming": "Eingehenden akzeptieren",
|
|
395
395
|
"acceptAllCurrent": "Alle aktuellen akzeptieren",
|
|
396
|
+
"prevConflict": "Vorheriger Konflikt",
|
|
397
|
+
"nextConflict": "Nächster Konflikt",
|
|
398
|
+
"acceptBoth": "Beide akzeptieren",
|
|
396
399
|
"acceptAllIncoming": "Alle eingehenden akzeptieren",
|
|
400
|
+
"acceptAllBoth": "Alle beide akzeptieren",
|
|
397
401
|
"saving": "Wird gespeichert...",
|
|
398
402
|
"saveAndResolve": "Speichern & als gelöst markieren",
|
|
399
403
|
"closeMiddleClick": "Schließen (Mittelklick)",
|
|
@@ -312,6 +312,18 @@
|
|
|
312
312
|
"clickToOpen": "Click to open",
|
|
313
313
|
"updated": "(updated)"
|
|
314
314
|
},
|
|
315
|
+
"onboarding": {
|
|
316
|
+
"title": "Welcome to Tide Commander",
|
|
317
|
+
"subtitle": "Your AI team is ready to deploy",
|
|
318
|
+
"step1Title": "Deploy an Agent",
|
|
319
|
+
"step1Desc": "Spawn a Claude Code or Codex agent pointed at any project directory.",
|
|
320
|
+
"step2Title": "Give it a Task",
|
|
321
|
+
"step2Desc": "Type a prompt and the agent works autonomously in its own terminal.",
|
|
322
|
+
"step3Title": "Scale Your Team",
|
|
323
|
+
"step3Desc": "Add more agents, assign bosses, and monitor everything in real time.",
|
|
324
|
+
"createFirst": "Create Your First Agent",
|
|
325
|
+
"explore": "Explore First"
|
|
326
|
+
},
|
|
315
327
|
"floatingButtons": {
|
|
316
328
|
"settingsAndTools": "Settings & Tools",
|
|
317
329
|
"commanderView": "Commander View",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"send": "Send",
|
|
43
43
|
"attach": "Attach file",
|
|
44
44
|
"attachOrPaste": "Attach file (or paste image)",
|
|
45
|
+
"dropToAttach": "Drop files to attach",
|
|
45
46
|
"attachedFiles": "{{count}} file(s) attached",
|
|
46
47
|
"removeAttachment": "Remove attachment",
|
|
47
48
|
"pasteFilePath": "Paste a file path to attach",
|
|
@@ -393,7 +394,11 @@
|
|
|
393
394
|
"acceptCurrent": "Accept Current",
|
|
394
395
|
"acceptIncoming": "Accept Incoming",
|
|
395
396
|
"acceptAllCurrent": "Accept All Current",
|
|
397
|
+
"prevConflict": "Previous Conflict",
|
|
398
|
+
"nextConflict": "Next Conflict",
|
|
399
|
+
"acceptBoth": "Accept Both",
|
|
396
400
|
"acceptAllIncoming": "Accept All Incoming",
|
|
401
|
+
"acceptAllBoth": "Accept All Both",
|
|
397
402
|
"saving": "Saving...",
|
|
398
403
|
"saveAndResolve": "Save & Mark Resolved",
|
|
399
404
|
"closeMiddleClick": "Close (Middle-click)",
|
|
@@ -393,7 +393,11 @@
|
|
|
393
393
|
"acceptCurrent": "Aceptar actual",
|
|
394
394
|
"acceptIncoming": "Aceptar entrante",
|
|
395
395
|
"acceptAllCurrent": "Aceptar todo actual",
|
|
396
|
+
"prevConflict": "Conflicto anterior",
|
|
397
|
+
"nextConflict": "Conflicto siguiente",
|
|
398
|
+
"acceptBoth": "Aceptar ambos",
|
|
396
399
|
"acceptAllIncoming": "Aceptar todo entrante",
|
|
400
|
+
"acceptAllBoth": "Aceptar todos ambos",
|
|
397
401
|
"saving": "Guardando...",
|
|
398
402
|
"saveAndResolve": "Guardar y marcar como resuelto",
|
|
399
403
|
"closeMiddleClick": "Cerrar (clic medio)",
|
|
@@ -393,7 +393,11 @@
|
|
|
393
393
|
"acceptCurrent": "Accepter l'actuel",
|
|
394
394
|
"acceptIncoming": "Accepter l'entrant",
|
|
395
395
|
"acceptAllCurrent": "Tout accepter (actuel)",
|
|
396
|
+
"prevConflict": "Conflit précédent",
|
|
397
|
+
"nextConflict": "Conflit suivant",
|
|
398
|
+
"acceptBoth": "Accepter les deux",
|
|
396
399
|
"acceptAllIncoming": "Tout accepter (entrant)",
|
|
400
|
+
"acceptAllBoth": "Tout accepter (les deux)",
|
|
397
401
|
"saving": "Enregistrement...",
|
|
398
402
|
"saveAndResolve": "Enregistrer et marquer comme résolu",
|
|
399
403
|
"closeMiddleClick": "Fermer (clic molette)",
|
|
@@ -393,7 +393,11 @@
|
|
|
393
393
|
"acceptCurrent": "वर्तमान स्वीकार करें",
|
|
394
394
|
"acceptIncoming": "आने वाली स्वीकार करें",
|
|
395
395
|
"acceptAllCurrent": "सभी वर्तमान स्वीकार करें",
|
|
396
|
+
"prevConflict": "पिछला विवाद",
|
|
397
|
+
"nextConflict": "अगला विवाद",
|
|
398
|
+
"acceptBoth": "दोनों स्वीकार करें",
|
|
396
399
|
"acceptAllIncoming": "सभी आने वाली स्वीकार करें",
|
|
400
|
+
"acceptAllBoth": "सभी दोनों स्वीकार करें",
|
|
397
401
|
"saving": "सहेजा जा रहा है...",
|
|
398
402
|
"saveAndResolve": "सहेजें और हल करें",
|
|
399
403
|
"closeMiddleClick": "बंद करें (मिडिल-क्लिक)",
|
|
@@ -393,7 +393,11 @@
|
|
|
393
393
|
"acceptCurrent": "Accetta corrente",
|
|
394
394
|
"acceptIncoming": "Accetta in arrivo",
|
|
395
395
|
"acceptAllCurrent": "Accetta tutti corrente",
|
|
396
|
+
"prevConflict": "Conflitto precedente",
|
|
397
|
+
"nextConflict": "Conflitto successivo",
|
|
398
|
+
"acceptBoth": "Accetta entrambi",
|
|
396
399
|
"acceptAllIncoming": "Accetta tutti in arrivo",
|
|
400
|
+
"acceptAllBoth": "Accetta tutti entrambi",
|
|
397
401
|
"saving": "Salvataggio...",
|
|
398
402
|
"saveAndResolve": "Salva e segna come risolto",
|
|
399
403
|
"closeMiddleClick": "Chiudi (clic centrale)",
|
|
@@ -393,7 +393,11 @@
|
|
|
393
393
|
"acceptCurrent": "現在を採用",
|
|
394
394
|
"acceptIncoming": "受信を採用",
|
|
395
395
|
"acceptAllCurrent": "すべて現在を採用",
|
|
396
|
+
"prevConflict": "前の競合",
|
|
397
|
+
"nextConflict": "次の競合",
|
|
398
|
+
"acceptBoth": "両方を採用",
|
|
396
399
|
"acceptAllIncoming": "すべて受信を採用",
|
|
400
|
+
"acceptAllBoth": "すべて両方を採用",
|
|
397
401
|
"saving": "保存中...",
|
|
398
402
|
"saveAndResolve": "保存して解決済みにする",
|
|
399
403
|
"closeMiddleClick": "閉じる (中クリック)",
|
|
@@ -393,7 +393,11 @@
|
|
|
393
393
|
"acceptCurrent": "Aceitar Atual",
|
|
394
394
|
"acceptIncoming": "Aceitar Recebida",
|
|
395
395
|
"acceptAllCurrent": "Aceitar Todas Atuais",
|
|
396
|
+
"prevConflict": "Conflito anterior",
|
|
397
|
+
"nextConflict": "Conflito seguinte",
|
|
398
|
+
"acceptBoth": "Aceitar Ambas",
|
|
396
399
|
"acceptAllIncoming": "Aceitar Todas Recebidas",
|
|
400
|
+
"acceptAllBoth": "Aceitar Todas Ambas",
|
|
397
401
|
"saving": "Salvando...",
|
|
398
402
|
"saveAndResolve": "Salvar e Marcar como Resolvido",
|
|
399
403
|
"closeMiddleClick": "Fechar (Clique do meio)",
|
|
@@ -393,7 +393,11 @@
|
|
|
393
393
|
"acceptCurrent": "Принять текущую",
|
|
394
394
|
"acceptIncoming": "Принять входящую",
|
|
395
395
|
"acceptAllCurrent": "Принять все текущие",
|
|
396
|
+
"prevConflict": "Предыдущий конфликт",
|
|
397
|
+
"nextConflict": "Следующий конфликт",
|
|
398
|
+
"acceptBoth": "Принять оба",
|
|
396
399
|
"acceptAllIncoming": "Принять все входящие",
|
|
400
|
+
"acceptAllBoth": "Принять все оба",
|
|
397
401
|
"saving": "Сохранение...",
|
|
398
402
|
"saveAndResolve": "Сохранить и отметить решённым",
|
|
399
403
|
"closeMiddleClick": "Закрыть (средний клик)",
|
|
@@ -393,7 +393,11 @@
|
|
|
393
393
|
"acceptCurrent": "接受当前",
|
|
394
394
|
"acceptIncoming": "接受传入",
|
|
395
395
|
"acceptAllCurrent": "接受所有当前",
|
|
396
|
+
"prevConflict": "上一个冲突",
|
|
397
|
+
"nextConflict": "下一个冲突",
|
|
398
|
+
"acceptBoth": "接受两者",
|
|
396
399
|
"acceptAllIncoming": "接受所有传入",
|
|
400
|
+
"acceptAllBoth": "接受所有两者",
|
|
397
401
|
"saving": "保存中...",
|
|
398
402
|
"saveAndResolve": "保存并标记为已解决",
|
|
399
403
|
"closeMiddleClick": "关闭(中键点击)",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { spawn } from 'node:child_process';
|
|
2
|
+
import { execSync, spawn } from 'node:child_process';
|
|
3
|
+
import { randomBytes } from 'node:crypto';
|
|
3
4
|
import fs from 'node:fs';
|
|
4
5
|
import os from 'node:os';
|
|
5
6
|
import path from 'node:path';
|
|
@@ -11,6 +12,9 @@ const PID_FILE = path.join(PID_DIR, 'server.pid');
|
|
|
11
12
|
const META_FILE = path.join(PID_DIR, 'server-meta.json');
|
|
12
13
|
const LOG_FILE = path.join(process.cwd(), 'logs', 'server.log');
|
|
13
14
|
const PACKAGE_NAME = 'tide-commander';
|
|
15
|
+
const TLS_DIR = path.join(os.homedir(), '.tide-commander', 'certs');
|
|
16
|
+
const DEFAULT_TLS_KEY_FILE = path.join(TLS_DIR, 'localhost-key.pem');
|
|
17
|
+
const DEFAULT_TLS_CERT_FILE = path.join(TLS_DIR, 'localhost.pem');
|
|
14
18
|
function findProjectRoot(startDir) {
|
|
15
19
|
let current = startDir;
|
|
16
20
|
while (current !== path.dirname(current)) {
|
|
@@ -35,6 +39,15 @@ Options:
|
|
|
35
39
|
-p, --port <port> Set server port (default: 6200)
|
|
36
40
|
-H, --host <host> Set server host (default: 127.0.0.1)
|
|
37
41
|
-l, --listen-all Listen on all network interfaces
|
|
42
|
+
--https Enable HTTPS/WSS server mode
|
|
43
|
+
--tls-key <path> TLS private key path (PEM)
|
|
44
|
+
--tls-cert <path> TLS certificate path (PEM)
|
|
45
|
+
--install-local-cert
|
|
46
|
+
Install local trusted cert with mkcert
|
|
47
|
+
--auth-token <token>
|
|
48
|
+
Set AUTH_TOKEN for this server run
|
|
49
|
+
--generate-auth-token
|
|
50
|
+
Generate a secure AUTH_TOKEN automatically
|
|
38
51
|
-f, --foreground Run in foreground (default is background)
|
|
39
52
|
--lines <n> Number of log lines for logs command (default: 100)
|
|
40
53
|
--follow Follow logs stream (like tail -f)
|
|
@@ -79,6 +92,42 @@ function parseArgs(argv) {
|
|
|
79
92
|
case '--listen-all':
|
|
80
93
|
options.listenAll = true;
|
|
81
94
|
break;
|
|
95
|
+
case '--https':
|
|
96
|
+
options.https = true;
|
|
97
|
+
break;
|
|
98
|
+
case '--tls-key': {
|
|
99
|
+
const value = argv[i + 1];
|
|
100
|
+
if (!value || value.startsWith('-')) {
|
|
101
|
+
throw new Error(`Missing value for ${arg}`);
|
|
102
|
+
}
|
|
103
|
+
options.tlsKey = value;
|
|
104
|
+
i += 1;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case '--tls-cert': {
|
|
108
|
+
const value = argv[i + 1];
|
|
109
|
+
if (!value || value.startsWith('-')) {
|
|
110
|
+
throw new Error(`Missing value for ${arg}`);
|
|
111
|
+
}
|
|
112
|
+
options.tlsCert = value;
|
|
113
|
+
i += 1;
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
case '--install-local-cert':
|
|
117
|
+
options.installLocalCert = true;
|
|
118
|
+
break;
|
|
119
|
+
case '--auth-token': {
|
|
120
|
+
const value = argv[i + 1];
|
|
121
|
+
if (!value || value.startsWith('-')) {
|
|
122
|
+
throw new Error(`Missing value for ${arg}`);
|
|
123
|
+
}
|
|
124
|
+
options.authToken = value;
|
|
125
|
+
i += 1;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
case '--generate-auth-token':
|
|
129
|
+
options.generateAuthToken = true;
|
|
130
|
+
break;
|
|
82
131
|
case '-f':
|
|
83
132
|
case '--foreground':
|
|
84
133
|
if (options.command === 'logs') {
|
|
@@ -149,7 +198,9 @@ function readServerMeta() {
|
|
|
149
198
|
const parsed = JSON.parse(raw);
|
|
150
199
|
if (typeof parsed.pid === 'number'
|
|
151
200
|
&& typeof parsed.host === 'string'
|
|
152
|
-
&& typeof parsed.port === 'string'
|
|
201
|
+
&& typeof parsed.port === 'string'
|
|
202
|
+
&& (parsed.https === undefined || typeof parsed.https === 'boolean')
|
|
203
|
+
&& (parsed.authEnabled === undefined || typeof parsed.authEnabled === 'boolean')) {
|
|
153
204
|
return parsed;
|
|
154
205
|
}
|
|
155
206
|
return null;
|
|
@@ -166,6 +217,42 @@ function clearServerMeta() {
|
|
|
166
217
|
// no-op
|
|
167
218
|
}
|
|
168
219
|
}
|
|
220
|
+
function resolveFromCwd(filePath) {
|
|
221
|
+
if (filePath.startsWith('~/')) {
|
|
222
|
+
return path.join(os.homedir(), filePath.slice(2));
|
|
223
|
+
}
|
|
224
|
+
if (path.isAbsolute(filePath)) {
|
|
225
|
+
return filePath;
|
|
226
|
+
}
|
|
227
|
+
return path.resolve(process.cwd(), filePath);
|
|
228
|
+
}
|
|
229
|
+
function ensureFileExists(filePath, label) {
|
|
230
|
+
if (!fs.existsSync(filePath)) {
|
|
231
|
+
throw new Error(`${label} not found: ${filePath}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function installLocalCert(host) {
|
|
235
|
+
fs.mkdirSync(TLS_DIR, { recursive: true });
|
|
236
|
+
const mkcertCmd = 'mkcert -install';
|
|
237
|
+
try {
|
|
238
|
+
spawnSyncOrThrow(mkcertCmd);
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
throw new Error('mkcert is required for --install-local-cert');
|
|
242
|
+
}
|
|
243
|
+
const hostArgs = ['localhost', '127.0.0.1', '::1'];
|
|
244
|
+
if (host !== 'localhost' && host !== '127.0.0.1' && host !== '::1' && host !== '0.0.0.0' && host !== '::') {
|
|
245
|
+
hostArgs.push(host);
|
|
246
|
+
}
|
|
247
|
+
const mkcertGenCmd = `mkcert -cert-file "${DEFAULT_TLS_CERT_FILE}" -key-file "${DEFAULT_TLS_KEY_FILE}" ${hostArgs.join(' ')}`;
|
|
248
|
+
spawnSyncOrThrow(mkcertGenCmd);
|
|
249
|
+
ensureFileExists(DEFAULT_TLS_CERT_FILE, 'TLS cert');
|
|
250
|
+
ensureFileExists(DEFAULT_TLS_KEY_FILE, 'TLS key');
|
|
251
|
+
return { keyPath: DEFAULT_TLS_KEY_FILE, certPath: DEFAULT_TLS_CERT_FILE };
|
|
252
|
+
}
|
|
253
|
+
function spawnSyncOrThrow(command) {
|
|
254
|
+
execSync(command, { stdio: 'ignore' });
|
|
255
|
+
}
|
|
169
256
|
function readPidFile() {
|
|
170
257
|
try {
|
|
171
258
|
const raw = fs.readFileSync(PID_FILE, 'utf8').trim();
|
|
@@ -277,13 +364,16 @@ async function statusCommand() {
|
|
|
277
364
|
const meta = readServerMeta();
|
|
278
365
|
const port = meta?.port ?? process.env.PORT ?? '6200';
|
|
279
366
|
const host = meta?.host ?? process.env.HOST ?? 'localhost';
|
|
280
|
-
const
|
|
367
|
+
const protocol = meta?.https ? 'https' : 'http';
|
|
368
|
+
const url = `${protocol}://${host}:${port}`;
|
|
369
|
+
const authEnabled = meta?.authEnabled === true;
|
|
281
370
|
const uptime = getProcessUptime(pid);
|
|
282
371
|
const version = getPackageVersion();
|
|
283
372
|
console.log(`\n${cyan}${bright}🌊 Tide Commander Status${reset}`);
|
|
284
373
|
console.log(`${cyan}${'═'.repeat(60)}${reset}`);
|
|
285
374
|
console.log(`${green}✓ Running${reset} (PID: ${pid})`);
|
|
286
375
|
console.log(`${blue}${bright}🚀 Access: ${url}${reset}`);
|
|
376
|
+
console.log(` Auth: ${authEnabled ? 'enabled' : 'disabled'}`);
|
|
287
377
|
console.log(` Version: ${version}`);
|
|
288
378
|
const npmVersion = await checkNpmVersion(PACKAGE_NAME, version);
|
|
289
379
|
if (npmVersion.relation === 'behind' && npmVersion.latestVersion) {
|
|
@@ -415,6 +505,9 @@ function printUpdateNotice(latestVersion) {
|
|
|
415
505
|
const dim = '\x1b[2m';
|
|
416
506
|
console.log(`${yellow}${bright}⬆ Update available: v${latestVersion}${reset} ${dim}(run: bunx tide-commander@latest)${reset}`);
|
|
417
507
|
}
|
|
508
|
+
function generateAuthToken() {
|
|
509
|
+
return randomBytes(32).toString('hex');
|
|
510
|
+
}
|
|
418
511
|
function versionCommand() {
|
|
419
512
|
try {
|
|
420
513
|
const version = getPackageVersion();
|
|
@@ -454,6 +547,47 @@ async function main() {
|
|
|
454
547
|
process.env.HOST = '0.0.0.0';
|
|
455
548
|
process.env.LISTEN_ALL_INTERFACES = '1';
|
|
456
549
|
}
|
|
550
|
+
if (options.tlsKey && !options.tlsCert) {
|
|
551
|
+
throw new Error('--tls-key requires --tls-cert');
|
|
552
|
+
}
|
|
553
|
+
if (options.tlsCert && !options.tlsKey) {
|
|
554
|
+
throw new Error('--tls-cert requires --tls-key');
|
|
555
|
+
}
|
|
556
|
+
if (options.generateAuthToken && options.authToken) {
|
|
557
|
+
throw new Error('--generate-auth-token cannot be used with --auth-token');
|
|
558
|
+
}
|
|
559
|
+
const shouldEnableHttps = options.https === true
|
|
560
|
+
|| options.installLocalCert === true
|
|
561
|
+
|| options.tlsKey !== undefined
|
|
562
|
+
|| options.tlsCert !== undefined
|
|
563
|
+
|| process.env.HTTPS === '1';
|
|
564
|
+
if (shouldEnableHttps) {
|
|
565
|
+
process.env.HTTPS = '1';
|
|
566
|
+
}
|
|
567
|
+
if (options.installLocalCert) {
|
|
568
|
+
const host = process.env.HOST || 'localhost';
|
|
569
|
+
const generated = installLocalCert(host);
|
|
570
|
+
process.env.TLS_KEY_PATH = generated.keyPath;
|
|
571
|
+
process.env.TLS_CERT_PATH = generated.certPath;
|
|
572
|
+
}
|
|
573
|
+
if (options.tlsKey && options.tlsCert) {
|
|
574
|
+
process.env.TLS_KEY_PATH = resolveFromCwd(options.tlsKey);
|
|
575
|
+
process.env.TLS_CERT_PATH = resolveFromCwd(options.tlsCert);
|
|
576
|
+
}
|
|
577
|
+
if (process.env.HTTPS === '1') {
|
|
578
|
+
const tlsKeyPath = resolveFromCwd(process.env.TLS_KEY_PATH || DEFAULT_TLS_KEY_FILE);
|
|
579
|
+
const tlsCertPath = resolveFromCwd(process.env.TLS_CERT_PATH || DEFAULT_TLS_CERT_FILE);
|
|
580
|
+
ensureFileExists(tlsKeyPath, 'TLS key');
|
|
581
|
+
ensureFileExists(tlsCertPath, 'TLS cert');
|
|
582
|
+
process.env.TLS_KEY_PATH = tlsKeyPath;
|
|
583
|
+
process.env.TLS_CERT_PATH = tlsCertPath;
|
|
584
|
+
}
|
|
585
|
+
if (options.generateAuthToken) {
|
|
586
|
+
process.env.AUTH_TOKEN = generateAuthToken();
|
|
587
|
+
}
|
|
588
|
+
else if (options.authToken) {
|
|
589
|
+
process.env.AUTH_TOKEN = options.authToken;
|
|
590
|
+
}
|
|
457
591
|
const cliDir = path.dirname(fileURLToPath(import.meta.url));
|
|
458
592
|
const serverLaunch = resolveServerLaunch(cliDir);
|
|
459
593
|
const runInForeground = options.foreground === true || process.env.TIDE_COMMANDER_FOREGROUND === '1';
|
|
@@ -461,6 +595,12 @@ async function main() {
|
|
|
461
595
|
const hasStartupOverrides = options.port !== undefined
|
|
462
596
|
|| options.host !== undefined
|
|
463
597
|
|| options.listenAll === true
|
|
598
|
+
|| options.https === true
|
|
599
|
+
|| options.tlsKey !== undefined
|
|
600
|
+
|| options.tlsCert !== undefined
|
|
601
|
+
|| options.installLocalCert === true
|
|
602
|
+
|| options.authToken !== undefined
|
|
603
|
+
|| options.generateAuthToken === true
|
|
464
604
|
|| options.foreground === true;
|
|
465
605
|
if (existingPid && isRunning(existingPid)) {
|
|
466
606
|
if (hasStartupOverrides) {
|
|
@@ -483,7 +623,9 @@ async function main() {
|
|
|
483
623
|
const meta = readServerMeta();
|
|
484
624
|
const port = meta?.port ?? process.env.PORT ?? '6200';
|
|
485
625
|
const host = meta?.host ?? process.env.HOST ?? 'localhost';
|
|
486
|
-
const
|
|
626
|
+
const protocol = meta?.https ? 'https' : 'http';
|
|
627
|
+
const url = `${protocol}://${host}:${port}`;
|
|
628
|
+
const authEnabled = meta?.authEnabled === true;
|
|
487
629
|
const dim = '\x1b[2m';
|
|
488
630
|
const yellow = '\x1b[33m';
|
|
489
631
|
const cyan = '\x1b[36m';
|
|
@@ -495,6 +637,7 @@ async function main() {
|
|
|
495
637
|
console.log(`\n${cyan}${bright}🌊 Tide Commander${reset} ${dim}(already running, PID: ${existingPid})${reset}`);
|
|
496
638
|
console.log(`${cyan}${'═'.repeat(60)}${reset}`);
|
|
497
639
|
console.log(`${blue}${bright}🚀 Open: ${url}${reset}`);
|
|
640
|
+
console.log(` Auth: ${authEnabled ? 'enabled' : 'disabled'}`);
|
|
498
641
|
console.log(` Version: ${currentVer}`);
|
|
499
642
|
const npmVersion = await checkNpmVersion(PACKAGE_NAME, currentVer);
|
|
500
643
|
if (npmVersion.relation === 'behind' && npmVersion.latestVersion) {
|
|
@@ -537,12 +680,16 @@ async function main() {
|
|
|
537
680
|
pid: child.pid,
|
|
538
681
|
host: process.env.HOST || 'localhost',
|
|
539
682
|
port: process.env.PORT || '6200',
|
|
683
|
+
https: process.env.HTTPS === '1',
|
|
684
|
+
authEnabled: Boolean(process.env.AUTH_TOKEN),
|
|
540
685
|
});
|
|
541
686
|
}
|
|
542
687
|
child.unref();
|
|
543
688
|
const port = process.env.PORT || '6200';
|
|
544
689
|
const host = process.env.HOST || 'localhost';
|
|
545
|
-
const
|
|
690
|
+
const protocol = process.env.HTTPS === '1' ? 'https' : 'http';
|
|
691
|
+
const url = `${protocol}://${host}:${port}`;
|
|
692
|
+
const authEnabled = Boolean(process.env.AUTH_TOKEN);
|
|
546
693
|
// ANSI color codes for beautiful output
|
|
547
694
|
const cyan = '\x1b[36m';
|
|
548
695
|
const green = '\x1b[32m';
|
|
@@ -556,7 +703,11 @@ async function main() {
|
|
|
556
703
|
const currentVersion = getPackageVersion();
|
|
557
704
|
console.log(`${green}✓${reset} Started in background (PID: ${child.pid ?? 'unknown'})`);
|
|
558
705
|
console.log(`${blue}${bright}🚀 Open: ${url}${reset}`);
|
|
706
|
+
console.log(` Auth: ${authEnabled ? 'enabled' : 'disabled'}`);
|
|
559
707
|
console.log(` Version: ${currentVersion}`);
|
|
708
|
+
if (options.generateAuthToken && process.env.AUTH_TOKEN) {
|
|
709
|
+
console.log(` Generated AUTH_TOKEN: ${process.env.AUTH_TOKEN}`);
|
|
710
|
+
}
|
|
560
711
|
const npmVersion = await checkNpmVersion(PACKAGE_NAME, currentVersion);
|
|
561
712
|
if (npmVersion.relation === 'behind' && npmVersion.latestVersion) {
|
|
562
713
|
printUpdateNotice(npmVersion.latestVersion);
|
|
@@ -578,6 +729,8 @@ async function main() {
|
|
|
578
729
|
pid: child.pid,
|
|
579
730
|
host: process.env.HOST || 'localhost',
|
|
580
731
|
port: process.env.PORT || '6200',
|
|
732
|
+
https: process.env.HTTPS === '1',
|
|
733
|
+
authEnabled: Boolean(process.env.AUTH_TOKEN),
|
|
581
734
|
});
|
|
582
735
|
}
|
|
583
736
|
child.on('exit', (code, signal) => {
|
|
@@ -14,6 +14,7 @@ import { bitbucketPR } from './bitbucket-pr.js';
|
|
|
14
14
|
import { pm2Logs } from './pm2-logs.js';
|
|
15
15
|
import { createBuilding } from './create-building.js';
|
|
16
16
|
import { releasePipeline } from './release-pipeline.js';
|
|
17
|
+
import { taskLabel } from './task-label.js';
|
|
17
18
|
/**
|
|
18
19
|
* All built-in skills that ship with Tide Commander
|
|
19
20
|
*/
|
|
@@ -27,6 +28,7 @@ export const BUILTIN_SKILLS = [
|
|
|
27
28
|
pm2Logs,
|
|
28
29
|
createBuilding,
|
|
29
30
|
releasePipeline,
|
|
31
|
+
taskLabel,
|
|
30
32
|
];
|
|
31
33
|
/**
|
|
32
34
|
* Get the ID for a built-in skill based on its slug
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export const taskLabel = {
|
|
2
|
+
slug: 'task-label',
|
|
3
|
+
name: 'Task Label',
|
|
4
|
+
description: 'Generate a brief task label for scene display',
|
|
5
|
+
allowedTools: ['Bash(curl:*)'],
|
|
6
|
+
assignedAgentClasses: ['*'],
|
|
7
|
+
content: `# Task Label (MANDATORY - Execute FIRST)
|
|
8
|
+
|
|
9
|
+
**IMPORTANT: Before doing ANY other work, you MUST generate a brief task label describing your current task.**
|
|
10
|
+
|
|
11
|
+
## Steps (do this FIRST before anything else):
|
|
12
|
+
1. Read the user's request
|
|
13
|
+
2. Generate a 1-5 word summary of the task (e.g., "Fix auth bug", "Add dark mode", "Refactor API calls", "Update tests")
|
|
14
|
+
3. Call the API to set your task label:
|
|
15
|
+
|
|
16
|
+
\`\`\`bash
|
|
17
|
+
curl -s -X PATCH http://localhost:5174/api/agents/YOUR_AGENT_ID -H "Content-Type: application/json" -d '{"taskLabel":"YOUR 1-5 WORD LABEL"}'
|
|
18
|
+
\`\`\`
|
|
19
|
+
|
|
20
|
+
4. Then proceed with the actual task
|
|
21
|
+
|
|
22
|
+
## Rules
|
|
23
|
+
- Keep labels between 1-5 words maximum
|
|
24
|
+
- Use action verbs: Fix, Add, Update, Refactor, Debug, Implement, Remove, etc.
|
|
25
|
+
- Be specific but concise (e.g., "Fix login redirect" not "Work on stuff")
|
|
26
|
+
- Do this BEFORE any other work - it should be your very first action
|
|
27
|
+
- Do NOT use exclamation marks (!) in the label - they cause bash errors
|
|
28
|
+
- Replace YOUR_AGENT_ID with your actual agent ID from the system prompt`,
|
|
29
|
+
};
|
|
@@ -132,6 +132,7 @@ function toStoredAgents(agents) {
|
|
|
132
132
|
currentTask: agent.currentTask,
|
|
133
133
|
lastAssignedTask: agent.lastAssignedTask,
|
|
134
134
|
lastAssignedTaskTime: agent.lastAssignedTaskTime,
|
|
135
|
+
taskLabel: agent.taskLabel,
|
|
135
136
|
isBoss: agent.isBoss,
|
|
136
137
|
subordinateIds: agent.subordinateIds,
|
|
137
138
|
bossId: agent.bossId,
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import 'dotenv/config';
|
|
6
6
|
import { createServer } from 'http';
|
|
7
|
+
import { createServer as createHttpsServer } from 'https';
|
|
8
|
+
import fs from 'node:fs';
|
|
7
9
|
import { createApp } from './app.js';
|
|
8
10
|
import { agentService, runtimeService, supervisorService, bossService, skillService, customClassService, secretsService, buildingService } from './services/index.js';
|
|
9
11
|
import * as websocket from './websocket/handler.js';
|
|
@@ -12,6 +14,9 @@ import { logger, closeFileLogging, getLogFilePath } from './utils/logger.js';
|
|
|
12
14
|
// Configuration
|
|
13
15
|
const PORT = process.env.PORT || 6200;
|
|
14
16
|
const HOST = process.env.HOST || (process.env.LISTEN_ALL_INTERFACES ? '::' : '127.0.0.1');
|
|
17
|
+
const HTTPS_ENABLED = process.env.HTTPS === '1';
|
|
18
|
+
const TLS_KEY_PATH = process.env.TLS_KEY_PATH;
|
|
19
|
+
const TLS_CERT_PATH = process.env.TLS_CERT_PATH;
|
|
15
20
|
const FORCE_SHUTDOWN_TIMEOUT_MS = 4500;
|
|
16
21
|
// ============================================================================
|
|
17
22
|
// Global Error Handlers
|
|
@@ -57,7 +62,12 @@ async function main() {
|
|
|
57
62
|
logger.server.log(`Log file: ${getLogFilePath()}`);
|
|
58
63
|
// Create Express app and HTTP server
|
|
59
64
|
const app = createApp();
|
|
60
|
-
const server =
|
|
65
|
+
const server = HTTPS_ENABLED
|
|
66
|
+
? createHttpsServer({
|
|
67
|
+
key: fs.readFileSync(assertTlsPath(TLS_KEY_PATH, 'TLS_KEY_PATH')),
|
|
68
|
+
cert: fs.readFileSync(assertTlsPath(TLS_CERT_PATH, 'TLS_CERT_PATH')),
|
|
69
|
+
}, app)
|
|
70
|
+
: createServer(app);
|
|
61
71
|
const sockets = new Set();
|
|
62
72
|
server.on('connection', (socket) => {
|
|
63
73
|
sockets.add(socket);
|
|
@@ -83,9 +93,11 @@ async function main() {
|
|
|
83
93
|
}
|
|
84
94
|
});
|
|
85
95
|
server.listen(Number(PORT), HOST, () => {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
logger.server.log(`
|
|
96
|
+
const protocol = HTTPS_ENABLED ? 'https' : 'http';
|
|
97
|
+
const wsProtocol = HTTPS_ENABLED ? 'wss' : 'ws';
|
|
98
|
+
logger.server.log(`Server running on ${protocol}://${HOST}:${PORT}`);
|
|
99
|
+
logger.server.log(`WebSocket available at ${wsProtocol}://${HOST}:${PORT}/ws`);
|
|
100
|
+
logger.server.log(`API available at ${protocol}://${HOST}:${PORT}/api`);
|
|
89
101
|
});
|
|
90
102
|
let isShuttingDown = false;
|
|
91
103
|
const gracefulShutdown = async (signal) => {
|
|
@@ -126,4 +138,10 @@ async function main() {
|
|
|
126
138
|
process.on('SIGINT', () => { void gracefulShutdown('SIGINT'); });
|
|
127
139
|
process.on('SIGTERM', () => { void gracefulShutdown('SIGTERM'); });
|
|
128
140
|
}
|
|
141
|
+
function assertTlsPath(value, envName) {
|
|
142
|
+
if (!value) {
|
|
143
|
+
throw new Error(`${envName} is required when HTTPS=1`);
|
|
144
|
+
}
|
|
145
|
+
return value;
|
|
146
|
+
}
|
|
129
147
|
main().catch(console.error);
|
|
@@ -24,6 +24,7 @@ export function createRuntimeCommandExecution(deps) {
|
|
|
24
24
|
if (!isSystemMessage) {
|
|
25
25
|
updateData.lastAssignedTask = command;
|
|
26
26
|
updateData.lastAssignedTaskTime = Date.now();
|
|
27
|
+
updateData.taskLabel = undefined; // Clear for agent to regenerate via skill
|
|
27
28
|
}
|
|
28
29
|
if (Object.keys(updateData).length > 0) {
|
|
29
30
|
agentService.updateAgent(agentId, updateData);
|
|
@@ -75,6 +76,7 @@ export function createRuntimeCommandExecution(deps) {
|
|
|
75
76
|
if (!isSystemMessage) {
|
|
76
77
|
updateData.lastAssignedTask = command;
|
|
77
78
|
updateData.lastAssignedTaskTime = Date.now();
|
|
79
|
+
updateData.taskLabel = undefined; // Clear for agent to regenerate via skill
|
|
78
80
|
}
|
|
79
81
|
agentService.updateAgent(agentId, updateData);
|
|
80
82
|
startStdinWatchdog({
|
|
@@ -250,8 +250,8 @@ export function getSkillsForAgent(agentId, agentClass) {
|
|
|
250
250
|
// Check direct assignment
|
|
251
251
|
if (skill.assignedAgentIds.includes(agentId))
|
|
252
252
|
return true;
|
|
253
|
-
// Check class assignment
|
|
254
|
-
if (skill.assignedAgentClasses.includes(agentClass))
|
|
253
|
+
// Check class assignment (supports '*' wildcard for all classes)
|
|
254
|
+
if (skill.assignedAgentClasses.includes('*') || skill.assignedAgentClasses.includes(agentClass))
|
|
255
255
|
return true;
|
|
256
256
|
return false;
|
|
257
257
|
});
|
|
@@ -432,8 +432,8 @@ async function handleSkillContentUpdate(skill) {
|
|
|
432
432
|
// Check direct assignment
|
|
433
433
|
if (skill.assignedAgentIds.includes(agent.id))
|
|
434
434
|
return true;
|
|
435
|
-
// Check class assignment
|
|
436
|
-
if (skill.assignedAgentClasses.includes(agent.class))
|
|
435
|
+
// Check class assignment (supports '*' wildcard for all classes)
|
|
436
|
+
if (skill.assignedAgentClasses.includes('*') || skill.assignedAgentClasses.includes(agent.class))
|
|
437
437
|
return true;
|
|
438
438
|
return false;
|
|
439
439
|
});
|