tide-commander 1.65.0 → 1.66.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 (47) hide show
  1. package/dist/assets/{BossLogsModal-U4mcLIP3.js → BossLogsModal-D6nnEtFf.js} +1 -1
  2. package/dist/assets/{BossSpawnModal-CiFvgaQ1.js → BossSpawnModal-DmPw2xym.js} +1 -1
  3. package/dist/assets/{ControlsModal-Bz4EQ0ii.js → ControlsModal-OEZddGmt.js} +1 -1
  4. package/dist/assets/{DockerLogsModal-CSMh4uRZ.js → DockerLogsModal-CzpJKdxd.js} +1 -1
  5. package/dist/assets/{EmbeddedEditor-ecjZKKkD.js → EmbeddedEditor-Dh9neVWW.js} +1 -1
  6. package/dist/assets/{GmailOAuthSetup-pFwSGVxa.js → GmailOAuthSetup-FtYHQDDw.js} +1 -1
  7. package/dist/assets/{GoogleOAuthSetup-CPvUA9JE.js → GoogleOAuthSetup-reYSDcFV.js} +1 -1
  8. package/dist/assets/{IframeModal-DcTPyjLy.js → IframeModal-DbW9WCx6.js} +1 -1
  9. package/dist/assets/{IntegrationsPanel-DHdp8HO-.js → IntegrationsPanel-DooKtzpO.js} +2 -2
  10. package/dist/assets/{LogViewerModal-QWGgGmiP.js → LogViewerModal-BamqTQzc.js} +1 -1
  11. package/dist/assets/{MonitoringModal-C8cfJlg5.js → MonitoringModal-DiOT_xpJ.js} +1 -1
  12. package/dist/assets/{PM2LogsModal-uUu_bTeC.js → PM2LogsModal-C4hIfwGk.js} +1 -1
  13. package/dist/assets/{RestoreArchivedAreaModal-C-Zxn6n6.js → RestoreArchivedAreaModal-CQ3NdSvn.js} +1 -1
  14. package/dist/assets/{Scene2DCanvas-0uRu2G4V.js → Scene2DCanvas-BfcwE6aA.js} +1 -1
  15. package/dist/assets/{SceneManager-xyitf6BI.js → SceneManager-D409BuL6.js} +1 -1
  16. package/dist/assets/{SkillsPanel-Bgcx1OI3.js → SkillsPanel-B0V-BFfO.js} +1 -1
  17. package/dist/assets/{SpawnModal-CS3BMuY9.js → SpawnModal-gPT83rW4.js} +1 -1
  18. package/dist/assets/{SubordinateAssignmentModal-DNWrbv9p.js → SubordinateAssignmentModal-Dvvqv0yZ.js} +1 -1
  19. package/dist/assets/{TriggerManagerPanel-D-pLDqne.js → TriggerManagerPanel-BmDBe8xx.js} +1 -1
  20. package/dist/assets/{WorkflowEditorPanel-Cu7zjFdE.js → WorkflowEditorPanel-PS4W8Q3F.js} +1 -1
  21. package/dist/assets/{index-cGfzMto2.js → index-BSdgxlrR.js} +1 -1
  22. package/dist/assets/{index-2BVW4z0_.js → index-BkpkUL3C.js} +1 -1
  23. package/dist/assets/{index-CZl-6UH7.js → index-C7cIg4BE.js} +1 -1
  24. package/dist/assets/{index-DEfCPZTr.js → index-CJYuOBJD.js} +3 -3
  25. package/dist/assets/{index-Yy2LrlI7.js → index-D9NmRir8.js} +2 -2
  26. package/dist/assets/{index-Dd0cfrn9.js → index-DvbZsLxj.js} +1 -1
  27. package/dist/assets/index-baPDjRvq.js +1 -0
  28. package/dist/assets/{index-DM70jqOd.js → index-cCrsvvfk.js} +1 -1
  29. package/dist/assets/main-B3L4mgQ4.css +1 -0
  30. package/dist/assets/{main-BNhrjJHj.js → main-BzSUj-VM.js} +5 -5
  31. package/dist/assets/{web-BnX_BsjB.js → web-DffxvD3t.js} +1 -1
  32. package/dist/assets/{web-CyBgxhK2.js → web-y3e1lmyW.js} +1 -1
  33. package/dist/index.html +2 -2
  34. package/dist/src/packages/server/data/builtin-skills/backup-restore.js +242 -0
  35. package/dist/src/packages/server/data/builtin-skills/index.js +2 -0
  36. package/dist/src/packages/server/index.js +4 -0
  37. package/dist/src/packages/server/routes/agents.js +27 -0
  38. package/dist/src/packages/server/services/backup-service.js +148 -0
  39. package/package.json +2 -1
  40. package/scripts/backup-data.sh +116 -0
  41. package/scripts/krunner/install-krunner-integration.sh +53 -0
  42. package/scripts/krunner/org.riven.tide.krunner.service +3 -0
  43. package/scripts/krunner/plasma-runner-tide-commander.desktop +16 -0
  44. package/scripts/krunner/tide-krunner-runner.js +335 -0
  45. package/scripts/recover-agents.ts +623 -0
  46. package/dist/assets/index-CWyxpmuL.js +0 -1
  47. package/dist/assets/main-4iQEaD98.css +0 -1
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
5
+
6
+ RUNNER_SRC="${REPO_ROOT}/scripts/krunner/tide-krunner-runner.js"
7
+ SERVICE_SRC="${REPO_ROOT}/scripts/krunner/org.riven.tide.krunner.service"
8
+ DESKTOP_SRC="${REPO_ROOT}/scripts/krunner/plasma-runner-tide-commander.desktop"
9
+
10
+ BIN_DIR="${HOME}/.local/bin"
11
+ DBUS_DIR="${HOME}/.local/share/dbus-1/services"
12
+ KRUNNER_DIR="${HOME}/.local/share/krunner/dbusplugins"
13
+
14
+ RUNNER_DST="${BIN_DIR}/tide-krunner-runner"
15
+ SERVICE_DST="${DBUS_DIR}/org.riven.tide.krunner.service"
16
+ DESKTOP_DST="${KRUNNER_DIR}/plasma-runner-tide-commander.desktop"
17
+
18
+ mkdir -p "${BIN_DIR}" "${DBUS_DIR}" "${KRUNNER_DIR}"
19
+
20
+ install -m 0755 "${RUNNER_SRC}" "${RUNNER_DST}"
21
+ install -m 0644 "${SERVICE_SRC}" "${SERVICE_DST}"
22
+ install -m 0644 "${DESKTOP_SRC}" "${DESKTOP_DST}"
23
+
24
+ # Ensure ${HOME} in Exec is expanded when installed.
25
+ sed -i "s#\\\${HOME}#${HOME}#g" "${SERVICE_DST}"
26
+
27
+ echo "Installed:"
28
+ echo " ${RUNNER_DST}"
29
+ echo " ${SERVICE_DST}"
30
+ echo " ${DESKTOP_DST}"
31
+
32
+ echo "Reloading DBus user config..."
33
+ dbus-send --session --dest=org.freedesktop.DBus --type=method_call --print-reply \
34
+ /org/freedesktop/DBus org.freedesktop.DBus.ReloadConfig >/dev/null 2>&1 || true
35
+
36
+ echo "Stopping existing Tide runner process..."
37
+ if pgrep -f "${RUNNER_DST}" >/dev/null 2>&1; then
38
+ pgrep -f "${RUNNER_DST}" | xargs -r kill
39
+ sleep 0.2
40
+ fi
41
+
42
+ echo "Restarting KRunner..."
43
+ kquitapp6 krunner >/dev/null 2>&1 || true
44
+ nohup krunner >/dev/null 2>&1 &
45
+ sleep 1
46
+
47
+ echo
48
+ echo "Done. Test with:"
49
+ echo " 1) Open KRunner (Alt+Space)"
50
+ echo " 2) Type: tc <agent or area name>"
51
+ echo
52
+ echo "Direct API check:"
53
+ echo " curl -s -X POST http://localhost:5174/api/focus-agent -H 'Content-Type: application/json' -d '{\"agentId\":\"g3d1jvlr\",\"openTerminal\":true}'"
@@ -0,0 +1,3 @@
1
+ [D-BUS Service]
2
+ Name=org.riven.tide.krunner
3
+ Exec=${HOME}/.local/bin/tide-krunner-runner
@@ -0,0 +1,16 @@
1
+ [Desktop Entry]
2
+ Name=Tide Commander
3
+ Comment=Find and focus Tide agents and areas
4
+ Type=Service
5
+ Icon=utilities-terminal
6
+ X-KDE-ServiceTypes=Plasma/Runner
7
+ X-KDE-PluginInfo-Name=tide-commander
8
+ X-KDE-PluginInfo-Version=1.0
9
+ X-KDE-PluginInfo-License=MIT
10
+ X-KDE-PluginInfo-EnabledByDefault=true
11
+ X-Plasma-API=DBus
12
+ X-Plasma-DBusRunner-Service=org.riven.tide.krunner
13
+ X-Plasma-DBusRunner-Path=/runner
14
+ X-Plasma-Request-Actions-Once=true
15
+ X-Plasma-Runner-Syntaxes=tc :q:,tide :q:
16
+ X-Plasma-Runner-Syntax-Descriptions=Find Tide agents and areas by name,Find Tide agents and areas by name
@@ -0,0 +1,335 @@
1
+ #!/usr/bin/env gjs
2
+
3
+ /**
4
+ * Tide Commander KRunner DBus Runner
5
+ *
6
+ * Query examples:
7
+ * tc gus
8
+ * tide area backend
9
+ */
10
+
11
+ imports.gi.versions.Gio = '2.0';
12
+ imports.gi.versions.GLib = '2.0';
13
+
14
+ const { Gio, GLib } = imports.gi;
15
+
16
+ const BUS_NAME = 'org.riven.tide.krunner';
17
+ const OBJECT_PATH = '/runner';
18
+ const API_BASE = GLib.getenv('TIDE_COMMANDER_API') || 'http://localhost:5174/api';
19
+ const APP_CAPTION = GLib.getenv('TIDE_COMMANDER_WINDOW_CAPTION') || 'Tide Commander';
20
+ const APP_DESKTOP_FILE = GLib.getenv('TIDE_COMMANDER_DESKTOP_FILE') || 'brave-idemibpphagihbobmgmaojhjfidlfpdl-Default';
21
+ const APP_KWIN_SHORTCUT = GLib.getenv('TIDE_COMMANDER_KWIN_SHORTCUT') || 'AppToggler25TideCommander';
22
+ let lastActivationToken = '';
23
+
24
+ const IFACE_XML = `
25
+ <node>
26
+ <interface name="org.kde.krunner1">
27
+ <method name="Config">
28
+ <arg name="config" type="a{sv}" direction="out"/>
29
+ </method>
30
+ <method name="Actions">
31
+ <arg name="actions" type="a(sss)" direction="out"/>
32
+ </method>
33
+ <method name="SetActivationToken">
34
+ <arg name="token" type="s" direction="in"/>
35
+ </method>
36
+ <method name="Run">
37
+ <arg name="matchId" type="s" direction="in"/>
38
+ <arg name="actionId" type="s" direction="in"/>
39
+ </method>
40
+ <method name="Match">
41
+ <arg name="query" type="s" direction="in"/>
42
+ <arg name="matches" type="a(sssida{sv})" direction="out"/>
43
+ </method>
44
+ </interface>
45
+ </node>`;
46
+
47
+ function shellEscapeSingleQuotes(str) {
48
+ return str.replace(/'/g, `'\\''`);
49
+ }
50
+
51
+ function runShell(command) {
52
+ try {
53
+ const [ok, stdout, stderr] = GLib.spawn_command_line_sync(command);
54
+ const out = (stdout ? ByteArray.toString(stdout) : '').trim();
55
+ const err = (stderr ? ByteArray.toString(stderr) : '').trim();
56
+ return { ok, out, err };
57
+ } catch (_err) {
58
+ return { ok: false, out: '', err: 'spawn failed' };
59
+ }
60
+ }
61
+
62
+ function fetchJson(url) {
63
+ const cmd = `curl -s --max-time 1 '${shellEscapeSingleQuotes(url)}'`;
64
+ const { out } = runShell(cmd);
65
+ if (!out) return null;
66
+ try {
67
+ return JSON.parse(out);
68
+ } catch (_err) {
69
+ return null;
70
+ }
71
+ }
72
+
73
+ function normalize(text) {
74
+ return (text || '').toString().toLowerCase().trim();
75
+ }
76
+
77
+ function contains(haystack, needle) {
78
+ return normalize(haystack).includes(normalize(needle));
79
+ }
80
+
81
+ function splitQuery(query) {
82
+ const trimmed = normalize(query);
83
+ if (!trimmed) return { isTrigger: true, term: '' };
84
+
85
+ if (trimmed === 'tc' || trimmed === 'tide') {
86
+ return { isTrigger: true, term: '' };
87
+ }
88
+
89
+ if (trimmed.startsWith('tc ')) {
90
+ return { isTrigger: true, term: trimmed.slice(3).trim() };
91
+ }
92
+ if (trimmed.startsWith('tide ')) {
93
+ return { isTrigger: true, term: trimmed.slice(5).trim() };
94
+ }
95
+
96
+ // In many KRunner setups, Match receives only the text after trigger words.
97
+ // Accept plain text as a valid term so autocomplete works reliably.
98
+ return { isTrigger: true, term: trimmed };
99
+ }
100
+
101
+ function scoreText(text, term) {
102
+ const t = normalize(text);
103
+ const q = normalize(term);
104
+ if (!q) return 0.7;
105
+ if (t === q) return 1.0;
106
+ if (t.startsWith(q)) return 0.95;
107
+ if (t.includes(q)) return 0.85;
108
+ return 0.0;
109
+ }
110
+
111
+ function isActiveStatus(status) {
112
+ const normalized = normalize(status);
113
+ return normalized === 'working' ||
114
+ normalized === 'waiting' ||
115
+ normalized === 'waiting_permission' ||
116
+ normalized === 'orphaned';
117
+ }
118
+
119
+ function asVariantDict(props) {
120
+ const dict = {};
121
+ for (const [k, v] of Object.entries(props)) {
122
+ if (Array.isArray(v)) {
123
+ dict[k] = GLib.Variant.new_strv(v);
124
+ } else if (typeof v === 'string') {
125
+ dict[k] = new GLib.Variant('s', v);
126
+ } else if (typeof v === 'boolean') {
127
+ dict[k] = new GLib.Variant('b', v);
128
+ } else if (typeof v === 'number') {
129
+ dict[k] = new GLib.Variant('d', v);
130
+ }
131
+ }
132
+ return dict;
133
+ }
134
+
135
+ function makeMatch(id, text, subtext, relevance) {
136
+ return [
137
+ id,
138
+ text,
139
+ 'terminal',
140
+ 100,
141
+ relevance,
142
+ asVariantDict({
143
+ subtext,
144
+ category: 'Tide Commander',
145
+ actions: [],
146
+ }),
147
+ ];
148
+ }
149
+
150
+ function areaMapFromAreas(areas) {
151
+ const map = new Map();
152
+ for (const area of areas) {
153
+ if (!area || !Array.isArray(area.assignedAgentIds)) continue;
154
+ for (const agentId of area.assignedAgentIds) {
155
+ if (!map.has(agentId)) {
156
+ map.set(agentId, area.name || 'No area');
157
+ }
158
+ }
159
+ }
160
+ return map;
161
+ }
162
+
163
+ function focusTideWindow() {
164
+ // Invoke the persistent AppToggler KWin shortcut (from kde-toggle-windows-shortcuts).
165
+ // Ephemeral KWin scripts loaded via loadScript/start/unloadScript don't get
166
+ // proper focus privileges on Wayland. The AppToggler runs inside KWin as a
167
+ // registered plugin, so workspace.activeWindow = client actually works.
168
+ runShell(
169
+ `qdbus org.kde.kglobalaccel /component/kwin org.kde.kglobalaccel.Component.invokeShortcut '${shellEscapeSingleQuotes(APP_KWIN_SHORTCUT)}' >/dev/null 2>&1`
170
+ );
171
+ }
172
+
173
+ function focusAgent(agentId) {
174
+ const payload = JSON.stringify({ agentId, openTerminal: true }).replace(/'/g, `'\\''`);
175
+ const activationHeader = lastActivationToken
176
+ ? ` -H 'X-KDE-ActivationToken: ${shellEscapeSingleQuotes(lastActivationToken)}'`
177
+ : '';
178
+ runShell(
179
+ `curl -s -X POST '${API_BASE}/focus-agent' -H 'Content-Type: application/json'${activationHeader} -d '${payload}' >/dev/null`
180
+ );
181
+ // Retry focus a few times (Wayland can delay activation).
182
+ focusTideWindow();
183
+ }
184
+
185
+ class TideRunner {
186
+ Config() {
187
+ return {
188
+ TriggerWords: new GLib.Variant('as', ['tc', 'tide']),
189
+ MinLetterCount: new GLib.Variant('i', 0),
190
+ };
191
+ }
192
+
193
+ Actions() {
194
+ return [];
195
+ }
196
+
197
+ SetActivationToken(token) {
198
+ lastActivationToken = String(token || '');
199
+ }
200
+
201
+ Run(matchId, _actionId) {
202
+ if (matchId.startsWith('agent:')) {
203
+ focusAgent(matchId.slice(6));
204
+ return;
205
+ }
206
+ if (matchId.startsWith('area:')) {
207
+ const parts = matchId.slice(5).split(':');
208
+ const fallbackAgentId = parts[1] || '';
209
+ if (fallbackAgentId) {
210
+ focusAgent(fallbackAgentId);
211
+ } else {
212
+ focusTideWindow();
213
+ }
214
+ return;
215
+ }
216
+ focusTideWindow();
217
+ }
218
+
219
+ Match(query) {
220
+ const parsed = splitQuery(query);
221
+ if (!parsed.isTrigger) return [];
222
+
223
+ const agents = fetchJson(`${API_BASE}/agents`) || [];
224
+ const areas = fetchJson(`${API_BASE}/areas`) || [];
225
+
226
+ const term = parsed.term;
227
+ const matches = [];
228
+
229
+ const agentsById = new Map();
230
+ for (const agent of agents) {
231
+ agentsById.set(agent.id, agent);
232
+ }
233
+ const areaByAgentId = areaMapFromAreas(areas);
234
+
235
+ const agentCandidates = [];
236
+ const areaCandidates = [];
237
+
238
+ for (const agent of agents) {
239
+ if (
240
+ term &&
241
+ !contains(agent.name, term) &&
242
+ !contains(agent.class, term) &&
243
+ !contains(areaByAgentId.get(agent.id) || '', term) &&
244
+ !contains(agent.cwd, term) &&
245
+ !contains(agent.status, term)
246
+ ) {
247
+ continue;
248
+ }
249
+ const relevance = Math.max(
250
+ scoreText(agent.name, term),
251
+ scoreText(agent.class, term),
252
+ scoreText(areaByAgentId.get(agent.id) || '', term),
253
+ scoreText(agent.cwd, term),
254
+ scoreText(agent.status, term),
255
+ term ? 0.6 : 0.7
256
+ );
257
+ const lastActivity = Number(agent.lastActivity || 0);
258
+ const activeRank = isActiveStatus(agent.status) ? 1 : 0;
259
+ const areaName = areaByAgentId.get(agent.id) || 'No area';
260
+
261
+ agentCandidates.push({
262
+ activeRank,
263
+ lastActivity,
264
+ relevance,
265
+ match: makeMatch(
266
+ `agent:${agent.id}`,
267
+ `${agent.name} [${areaName}]`,
268
+ `${agent.class} • ${agent.status} • Area: ${areaName}`,
269
+ relevance
270
+ ),
271
+ });
272
+ }
273
+
274
+ for (const area of areas) {
275
+ if (!area || !area.name) continue;
276
+ if (term && !contains(area.name, term) && !contains('area', term)) continue;
277
+
278
+ const assigned = Array.isArray(area.assignedAgentIds) ? area.assignedAgentIds : [];
279
+ const fallbackAgentId = assigned.find((id) => agentsById.has(id)) || '';
280
+ const previewAgent = fallbackAgentId ? agentsById.get(fallbackAgentId).name : 'no agents';
281
+
282
+ const relevance = Math.max(scoreText(area.name, term), term ? 0.55 : 0.65);
283
+ areaCandidates.push({
284
+ relevance,
285
+ match: makeMatch(
286
+ `area:${area.id}:${fallbackAgentId}`,
287
+ `Area: ${area.name}`,
288
+ `Open ${previewAgent}`,
289
+ relevance
290
+ ),
291
+ });
292
+ }
293
+
294
+ agentCandidates.sort((a, b) => {
295
+ if (b.activeRank !== a.activeRank) return b.activeRank - a.activeRank;
296
+ if (b.lastActivity !== a.lastActivity) return b.lastActivity - a.lastActivity;
297
+ return b.relevance - a.relevance;
298
+ });
299
+ areaCandidates.sort((a, b) => b.relevance - a.relevance);
300
+
301
+ for (const candidate of agentCandidates) matches.push(candidate.match);
302
+ for (const candidate of areaCandidates) matches.push(candidate.match);
303
+
304
+ if (matches.length === 0) {
305
+ matches.push(
306
+ makeMatch(
307
+ 'tide:open',
308
+ 'Tide Commander',
309
+ 'No agent or area matched',
310
+ 0.6
311
+ )
312
+ );
313
+ }
314
+
315
+ return matches.slice(0, 12);
316
+ }
317
+ }
318
+
319
+ const ByteArray = imports.byteArray;
320
+ const loop = new GLib.MainLoop(null, false);
321
+ const runner = new TideRunner();
322
+ const dbusObject = Gio.DBusExportedObject.wrapJSObject(IFACE_XML, runner);
323
+
324
+ const connection = Gio.bus_get_sync(Gio.BusType.SESSION, null);
325
+ dbusObject.export(connection, OBJECT_PATH);
326
+
327
+ Gio.bus_own_name_on_connection(
328
+ connection,
329
+ BUS_NAME,
330
+ Gio.BusNameOwnerFlags.NONE,
331
+ null,
332
+ null
333
+ );
334
+
335
+ loop.run();