pilotswarm 0.0.1 → 0.4.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 (149) hide show
  1. package/README.md +37 -1
  2. package/mcp/README.md +484 -0
  3. package/mcp/dist/bin/pilotswarm-mcp.d.ts +3 -0
  4. package/mcp/dist/bin/pilotswarm-mcp.d.ts.map +1 -0
  5. package/mcp/dist/bin/pilotswarm-mcp.js +367 -0
  6. package/mcp/dist/bin/pilotswarm-mcp.js.map +1 -0
  7. package/mcp/dist/src/auth.d.ts +7 -0
  8. package/mcp/dist/src/auth.d.ts.map +1 -0
  9. package/mcp/dist/src/auth.js +99 -0
  10. package/mcp/dist/src/auth.js.map +1 -0
  11. package/mcp/dist/src/context.d.ts +48 -0
  12. package/mcp/dist/src/context.d.ts.map +1 -0
  13. package/mcp/dist/src/context.js +83 -0
  14. package/mcp/dist/src/context.js.map +1 -0
  15. package/mcp/dist/src/index.d.ts +4 -0
  16. package/mcp/dist/src/index.d.ts.map +1 -0
  17. package/mcp/dist/src/index.js +3 -0
  18. package/mcp/dist/src/index.js.map +1 -0
  19. package/mcp/dist/src/prompts/skills.d.ts +4 -0
  20. package/mcp/dist/src/prompts/skills.d.ts.map +1 -0
  21. package/mcp/dist/src/prompts/skills.js +11 -0
  22. package/mcp/dist/src/prompts/skills.js.map +1 -0
  23. package/mcp/dist/src/resources/agents.d.ts +4 -0
  24. package/mcp/dist/src/resources/agents.d.ts.map +1 -0
  25. package/mcp/dist/src/resources/agents.js +64 -0
  26. package/mcp/dist/src/resources/agents.js.map +1 -0
  27. package/mcp/dist/src/resources/facts.d.ts +4 -0
  28. package/mcp/dist/src/resources/facts.d.ts.map +1 -0
  29. package/mcp/dist/src/resources/facts.js +125 -0
  30. package/mcp/dist/src/resources/facts.js.map +1 -0
  31. package/mcp/dist/src/resources/models.d.ts +4 -0
  32. package/mcp/dist/src/resources/models.d.ts.map +1 -0
  33. package/mcp/dist/src/resources/models.js +43 -0
  34. package/mcp/dist/src/resources/models.js.map +1 -0
  35. package/mcp/dist/src/resources/sessions.d.ts +4 -0
  36. package/mcp/dist/src/resources/sessions.d.ts.map +1 -0
  37. package/mcp/dist/src/resources/sessions.js +190 -0
  38. package/mcp/dist/src/resources/sessions.js.map +1 -0
  39. package/mcp/dist/src/resources/subscriptions.d.ts +9 -0
  40. package/mcp/dist/src/resources/subscriptions.d.ts.map +1 -0
  41. package/mcp/dist/src/resources/subscriptions.js +157 -0
  42. package/mcp/dist/src/resources/subscriptions.js.map +1 -0
  43. package/mcp/dist/src/server.d.ts +4 -0
  44. package/mcp/dist/src/server.d.ts.map +1 -0
  45. package/mcp/dist/src/server.js +59 -0
  46. package/mcp/dist/src/server.js.map +1 -0
  47. package/mcp/dist/src/tools/agents.d.ts +4 -0
  48. package/mcp/dist/src/tools/agents.d.ts.map +1 -0
  49. package/mcp/dist/src/tools/agents.js +317 -0
  50. package/mcp/dist/src/tools/agents.js.map +1 -0
  51. package/mcp/dist/src/tools/facts.d.ts +4 -0
  52. package/mcp/dist/src/tools/facts.d.ts.map +1 -0
  53. package/mcp/dist/src/tools/facts.js +151 -0
  54. package/mcp/dist/src/tools/facts.js.map +1 -0
  55. package/mcp/dist/src/tools/models.d.ts +4 -0
  56. package/mcp/dist/src/tools/models.d.ts.map +1 -0
  57. package/mcp/dist/src/tools/models.js +256 -0
  58. package/mcp/dist/src/tools/models.js.map +1 -0
  59. package/mcp/dist/src/tools/sessions.d.ts +4 -0
  60. package/mcp/dist/src/tools/sessions.d.ts.map +1 -0
  61. package/mcp/dist/src/tools/sessions.js +606 -0
  62. package/mcp/dist/src/tools/sessions.js.map +1 -0
  63. package/mcp/dist/src/util/command.d.ts +52 -0
  64. package/mcp/dist/src/util/command.d.ts.map +1 -0
  65. package/mcp/dist/src/util/command.js +78 -0
  66. package/mcp/dist/src/util/command.js.map +1 -0
  67. package/package.json +81 -6
  68. package/tui/README.md +35 -0
  69. package/tui/bin/tui.js +30 -0
  70. package/tui/plugins/.mcp.json +7 -0
  71. package/tui/plugins/plugin.json +13 -0
  72. package/tui/plugins/session-policy.json +8 -0
  73. package/tui/src/app.js +850 -0
  74. package/tui/src/auth/cli.js +111 -0
  75. package/tui/src/auth/entra-auth.js +218 -0
  76. package/tui/src/bootstrap-env.js +176 -0
  77. package/tui/src/embedded-workers.js +79 -0
  78. package/tui/src/http-transport-host.js +106 -0
  79. package/tui/src/index.js +340 -0
  80. package/tui/src/node-sdk-transport.js +1793 -0
  81. package/tui/src/platform.js +984 -0
  82. package/tui/src/plugin-config.js +239 -0
  83. package/tui/src/portal.js +7 -0
  84. package/tui/src/version.js +7 -0
  85. package/tui/tui-splash.txt +11 -0
  86. package/ui/core/README.md +6 -0
  87. package/ui/core/src/commands.js +93 -0
  88. package/ui/core/src/context-usage.js +212 -0
  89. package/ui/core/src/controller.js +6104 -0
  90. package/ui/core/src/formatting.js +1036 -0
  91. package/ui/core/src/history.js +932 -0
  92. package/ui/core/src/index.js +13 -0
  93. package/ui/core/src/layout.js +332 -0
  94. package/ui/core/src/reducer.js +1935 -0
  95. package/ui/core/src/selectors.js +5409 -0
  96. package/ui/core/src/session-errors.js +14 -0
  97. package/ui/core/src/session-tree.js +151 -0
  98. package/ui/core/src/state.js +248 -0
  99. package/ui/core/src/store.js +23 -0
  100. package/ui/core/src/system-titles.js +24 -0
  101. package/ui/core/src/themes/catppuccin-mocha.js +56 -0
  102. package/ui/core/src/themes/cobalt2.js +56 -0
  103. package/ui/core/src/themes/dark-high-contrast.js +56 -0
  104. package/ui/core/src/themes/daylight.js +62 -0
  105. package/ui/core/src/themes/dracula.js +56 -0
  106. package/ui/core/src/themes/github-dark.js +56 -0
  107. package/ui/core/src/themes/github-light.js +59 -0
  108. package/ui/core/src/themes/gruvbox-dark.js +56 -0
  109. package/ui/core/src/themes/hacker-x-matrix.js +56 -0
  110. package/ui/core/src/themes/hacker-x-orion-prime.js +56 -0
  111. package/ui/core/src/themes/helpers.js +79 -0
  112. package/ui/core/src/themes/high-contrast-mono.js +59 -0
  113. package/ui/core/src/themes/index.js +52 -0
  114. package/ui/core/src/themes/light-high-contrast.js +62 -0
  115. package/ui/core/src/themes/noctis-obscuro.js +56 -0
  116. package/ui/core/src/themes/noctis.js +56 -0
  117. package/ui/core/src/themes/paper-ink.js +62 -0
  118. package/ui/core/src/themes/solarized-ops.js +59 -0
  119. package/ui/core/src/themes/terminal-green.js +59 -0
  120. package/ui/core/src/themes/tokyo-night.js +56 -0
  121. package/ui/react/README.md +5 -0
  122. package/ui/react/src/chat-status.js +39 -0
  123. package/ui/react/src/components.js +1989 -0
  124. package/ui/react/src/index.js +4 -0
  125. package/ui/react/src/platform.js +15 -0
  126. package/ui/react/src/use-controller-state.js +38 -0
  127. package/ui/react/src/web-app.js +4390 -0
  128. package/web/README.md +198 -0
  129. package/web/api/router.js +196 -0
  130. package/web/api/ws.js +152 -0
  131. package/web/auth/authz/engine.js +204 -0
  132. package/web/auth/config.js +115 -0
  133. package/web/auth/index.js +175 -0
  134. package/web/auth/normalize/entra.js +22 -0
  135. package/web/auth/providers/entra.js +76 -0
  136. package/web/auth/providers/none.js +24 -0
  137. package/web/auth.js +10 -0
  138. package/web/bin/serve.js +53 -0
  139. package/web/config.js +20 -0
  140. package/web/dist/app.js +469 -0
  141. package/web/dist/assets/index-CBgQQk-j.css +1 -0
  142. package/web/dist/assets/index-DMefB7Wb.js +24 -0
  143. package/web/dist/assets/msal-CytV9RFv.js +7 -0
  144. package/web/dist/assets/pilotswarm-DyBlqVTY.js +87 -0
  145. package/web/dist/assets/react-BZwsW8pe.js +1 -0
  146. package/web/dist/index.html +16 -0
  147. package/web/runtime.js +454 -0
  148. package/web/server.js +276 -0
  149. package/index.js +0 -1
@@ -0,0 +1,340 @@
1
+ import React from "react";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import os from "node:os";
5
+ import { createRequire } from "node:module";
6
+ import { render } from "ink";
7
+ import {
8
+ PilotSwarmUiController,
9
+ appReducer,
10
+ createInitialState,
11
+ createStore,
12
+ } from "pilotswarm/ui-core";
13
+ import { PilotSwarmTuiApp } from "./app.js";
14
+ import { createTuiPlatform } from "./platform.js";
15
+ import { NodeSdkTransport } from "./node-sdk-transport.js";
16
+
17
+ const require = createRequire(import.meta.url);
18
+
19
+ const CONFIG_DIR = path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config"), "pilotswarm");
20
+ const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
21
+
22
+ function readConfig() {
23
+ try {
24
+ return JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"));
25
+ } catch {
26
+ return {};
27
+ }
28
+ }
29
+
30
+ function writeConfig(patch) {
31
+ try {
32
+ const existing = readConfig();
33
+ const merged = { ...existing, ...patch };
34
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
35
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2) + "\n", "utf8");
36
+ } catch {}
37
+ }
38
+
39
+ function collapsedSessionIdsToArray(collapsedIds) {
40
+ if (!(collapsedIds instanceof Set)) return [];
41
+ return Array.from(collapsedIds)
42
+ .map((id) => String(id || "").trim())
43
+ .filter(Boolean)
44
+ .sort();
45
+ }
46
+
47
+ function setupTuiHostRuntime() {
48
+ const logFile = "/tmp/duroxide-tui.log";
49
+ const originalConsole = {
50
+ log: console.log.bind(console),
51
+ warn: console.warn.bind(console),
52
+ error: console.error.bind(console),
53
+ };
54
+ const originalStderrWrite = process.stderr.write.bind(process.stderr);
55
+ const originalEmitWarning = process.emitWarning.bind(process);
56
+
57
+ try {
58
+ fs.writeFileSync(logFile, "");
59
+ } catch {}
60
+
61
+ try {
62
+ const { initTracing } = require("duroxide");
63
+ initTracing({
64
+ logFile,
65
+ logLevel: process.env.LOG_LEVEL || "info",
66
+ logFormat: "compact",
67
+ });
68
+ } catch {}
69
+
70
+ const appendLog = (...parts) => {
71
+ try {
72
+ const text = parts
73
+ .map((part) => {
74
+ if (typeof part === "string") return part;
75
+ if (part instanceof Error) return part.stack || part.message;
76
+ try { return JSON.stringify(part); } catch { return String(part); }
77
+ })
78
+ .join(" ");
79
+ fs.appendFileSync(logFile, `${text}\n`);
80
+ } catch {}
81
+ };
82
+
83
+ console.log = (...args) => appendLog(...args);
84
+ console.warn = (...args) => appendLog(...args);
85
+ console.error = (...args) => appendLog(...args);
86
+
87
+ process.stderr.write = (chunk, encoding, cb) => {
88
+ try {
89
+ const text = typeof chunk === "string" ? chunk : chunk?.toString?.(encoding || "utf8");
90
+ if (text) appendLog(text.trimEnd());
91
+ } catch {}
92
+ if (typeof cb === "function") cb();
93
+ return true;
94
+ };
95
+
96
+ process.emitWarning = (warning, ...args) => {
97
+ appendLog("[warning]", warning, ...args);
98
+ };
99
+
100
+ return () => {
101
+ console.log = originalConsole.log;
102
+ console.warn = originalConsole.warn;
103
+ console.error = originalConsole.error;
104
+ process.stderr.write = originalStderrWrite;
105
+ process.emitWarning = originalEmitWarning;
106
+ };
107
+ }
108
+
109
+ function clearTerminalScreen() {
110
+ if (!process.stdout?.isTTY) return;
111
+ try {
112
+ process.stdout.write("\x1b[2J\x1b[3J\x1b[H");
113
+ } catch {}
114
+ }
115
+
116
+ async function createTransport(config, authNotify) {
117
+ if (config.apiUrl) {
118
+ // Web API mode: discover the auth mode, sign in if the deployment
119
+ // requires it (device code, before the Ink app renders), then run
120
+ // the whole TUI over /api/v1 — no backend credentials in-process.
121
+ const { bootstrapApiAuth } = await import("./auth/cli.js");
122
+ const { createHttpTransportHost } = await import("./http-transport-host.js");
123
+ const { getAccessToken } = await bootstrapApiAuth(config.apiUrl, { useDeviceCode: config.deviceCode });
124
+ // Auth failures fire mid-session, once the Ink app owns the screen and
125
+ // setupTuiHostRuntime has redirected console/stderr to the log file.
126
+ // Route guidance through the status line (bound after the controller
127
+ // exists) rather than writing to a terminal Ink is drawing on.
128
+ return createHttpTransportHost({
129
+ apiUrl: config.apiUrl,
130
+ getAccessToken,
131
+ onUnauthorized: () => {
132
+ authNotify.emit(`Session expired. Run: pilotswarm auth login --api-url ${config.apiUrl}`);
133
+ },
134
+ onForbidden: (message) => {
135
+ authNotify.emit(`Access denied: ${message}`);
136
+ },
137
+ });
138
+ }
139
+ return new NodeSdkTransport({
140
+ store: config.store,
141
+ mode: config.mode,
142
+ });
143
+ }
144
+
145
+ export async function startTuiApp(config) {
146
+ // Auth-failure guidance is buffered until the controller exists, then
147
+ // shown on the status line (see createTransport).
148
+ const authNotify = {
149
+ emit(message) {
150
+ if (this.dispatch) this.dispatch(message);
151
+ },
152
+ };
153
+ const transport = await createTransport(config, authNotify);
154
+ const restoreHostRuntime = setupTuiHostRuntime();
155
+ const platform = createTuiPlatform();
156
+ const userConfig = readConfig();
157
+ const store = createStore(appReducer, createInitialState({
158
+ mode: config.mode,
159
+ branding: config.branding,
160
+ themeId: userConfig.themeId,
161
+ chatViewMode: userConfig.chatViewMode,
162
+ sessionOwnerFilter: userConfig.sessionOwnerFilter,
163
+ layoutAdjustments: userConfig.layoutAdjustments,
164
+ pinnedSessionIds: userConfig.pinnedSessionIds,
165
+ collapsedSessionIds: userConfig.collapsedSessionIds,
166
+ activeSessionId: userConfig.activeSessionId,
167
+ }));
168
+ const controller = new PilotSwarmUiController({ store, transport });
169
+ authNotify.dispatch = (message) => {
170
+ try {
171
+ store.dispatch({ type: "ui/status", text: message });
172
+ } catch {}
173
+ };
174
+ let tuiApp;
175
+ let finalized = false;
176
+ let resolveExit;
177
+ const exitPromise = new Promise((resolve) => {
178
+ resolveExit = resolve;
179
+ });
180
+
181
+ const finalizeHost = () => {
182
+ if (finalized) return;
183
+ finalized = true;
184
+ try {
185
+ tuiApp?.cleanup?.();
186
+ } catch {}
187
+ restoreHostRuntime();
188
+ clearTerminalScreen();
189
+ resolveExit?.();
190
+ };
191
+
192
+ const requestExit = async () => {
193
+ const forceExitTimer = setTimeout(() => {
194
+ finalizeHost();
195
+ process.exit(0);
196
+ }, 5000);
197
+ forceExitTimer.unref?.();
198
+
199
+ try {
200
+ await controller.stop();
201
+ } catch {}
202
+ finally {
203
+ clearTimeout(forceExitTimer);
204
+ try {
205
+ tuiApp?.clear?.();
206
+ } catch {}
207
+ try {
208
+ tuiApp?.unmount();
209
+ } catch {}
210
+ finalizeHost();
211
+ process.exit(0);
212
+ }
213
+ };
214
+
215
+ tuiApp = render(React.createElement(PilotSwarmTuiApp, {
216
+ controller,
217
+ platform,
218
+ onRequestExit: requestExit,
219
+ }), {
220
+ exitOnCtrlC: false,
221
+ });
222
+
223
+ // Listen for portal theme-change OSC sequences on stdin:
224
+ // \x1b]777;theme;<themeId>\x07
225
+ let oscBuffer = "";
226
+ const OSC_PREFIX = "\x1b]777;theme;";
227
+ const OSC_SUFFIX = "\x07";
228
+ const stdinThemeHandler = (data) => {
229
+ const str = typeof data === "string" ? data : data.toString("utf8");
230
+ oscBuffer += str;
231
+ while (oscBuffer.includes(OSC_PREFIX) && oscBuffer.includes(OSC_SUFFIX)) {
232
+ const start = oscBuffer.indexOf(OSC_PREFIX);
233
+ const end = oscBuffer.indexOf(OSC_SUFFIX, start);
234
+ if (end < 0) break;
235
+ const themeId = oscBuffer.slice(start + OSC_PREFIX.length, end);
236
+ oscBuffer = oscBuffer.slice(end + OSC_SUFFIX.length);
237
+ if (themeId) {
238
+ store.dispatch({ type: "ui/theme", themeId });
239
+ }
240
+ }
241
+ // Prevent buffer from growing unbounded
242
+ if (oscBuffer.length > 1024) oscBuffer = oscBuffer.slice(-256);
243
+ };
244
+ process.stdin.on("data", stdinThemeHandler);
245
+
246
+ // Sync viewport on terminal resize (SIGWINCH)
247
+ const syncViewport = () => {
248
+ controller.setViewport({
249
+ width: process.stdout.columns || 120,
250
+ height: process.stdout.rows || 40,
251
+ });
252
+ };
253
+ syncViewport();
254
+ process.stdout.on("resize", syncViewport);
255
+
256
+ // Persist theme changes to config file
257
+ let lastPersistedThemeId = store.getState().ui.themeId;
258
+ let lastPersistedChatViewMode = store.getState().ui.chatViewMode;
259
+ let lastPersistedSessionOwnerFilter = JSON.stringify(store.getState().sessions.ownerFilter || null);
260
+ let lastPersistedLayoutAdjustments = JSON.stringify({
261
+ paneAdjust: store.getState().ui.layout?.paneAdjust || 0,
262
+ sessionPaneAdjust: store.getState().ui.layout?.sessionPaneAdjust || 0,
263
+ activityPaneAdjust: store.getState().ui.layout?.activityPaneAdjust || 0,
264
+ });
265
+ let lastPersistedPinnedSessionIds = JSON.stringify(store.getState().sessions.pinnedIds || []);
266
+ let lastPersistedCollapsedSessionIds = JSON.stringify(collapsedSessionIdsToArray(store.getState().sessions.collapsedIds));
267
+ let lastPersistedActiveSessionId = store.getState().sessions.activeSessionId || null;
268
+ store.subscribe(() => {
269
+ const state = store.getState();
270
+ const currentThemeId = state.ui.themeId;
271
+ const currentChatViewMode = state.ui.chatViewMode;
272
+ const currentSessionOwnerFilter = state.sessions.ownerFilter || null;
273
+ const currentSessionOwnerFilterJson = JSON.stringify(currentSessionOwnerFilter);
274
+ const currentLayoutAdjustments = {
275
+ paneAdjust: state.ui.layout?.paneAdjust || 0,
276
+ sessionPaneAdjust: state.ui.layout?.sessionPaneAdjust || 0,
277
+ activityPaneAdjust: state.ui.layout?.activityPaneAdjust || 0,
278
+ };
279
+ const currentLayoutAdjustmentsJson = JSON.stringify(currentLayoutAdjustments);
280
+ const currentPinnedSessionIds = Array.isArray(state.sessions.pinnedIds) ? state.sessions.pinnedIds : [];
281
+ const currentPinnedSessionIdsJson = JSON.stringify(currentPinnedSessionIds);
282
+ const currentCollapsedSessionIds = collapsedSessionIdsToArray(state.sessions.collapsedIds);
283
+ const currentCollapsedSessionIdsJson = JSON.stringify(currentCollapsedSessionIds);
284
+ const currentActiveSessionId = state.sessions.activeSessionId || null;
285
+ const patch = {};
286
+ let changed = false;
287
+
288
+ if (currentThemeId && currentThemeId !== lastPersistedThemeId) {
289
+ lastPersistedThemeId = currentThemeId;
290
+ patch.themeId = currentThemeId;
291
+ changed = true;
292
+ }
293
+
294
+ if ((currentChatViewMode === "summary" || currentChatViewMode === "transcript") && currentChatViewMode !== lastPersistedChatViewMode) {
295
+ lastPersistedChatViewMode = currentChatViewMode;
296
+ patch.chatViewMode = currentChatViewMode;
297
+ changed = true;
298
+ }
299
+
300
+ if (currentSessionOwnerFilterJson !== lastPersistedSessionOwnerFilter) {
301
+ lastPersistedSessionOwnerFilter = currentSessionOwnerFilterJson;
302
+ patch.sessionOwnerFilter = currentSessionOwnerFilter;
303
+ changed = true;
304
+ }
305
+
306
+ if (currentLayoutAdjustmentsJson !== lastPersistedLayoutAdjustments) {
307
+ lastPersistedLayoutAdjustments = currentLayoutAdjustmentsJson;
308
+ patch.layoutAdjustments = currentLayoutAdjustments;
309
+ changed = true;
310
+ }
311
+
312
+ if (currentPinnedSessionIdsJson !== lastPersistedPinnedSessionIds) {
313
+ lastPersistedPinnedSessionIds = currentPinnedSessionIdsJson;
314
+ patch.pinnedSessionIds = currentPinnedSessionIds;
315
+ changed = true;
316
+ }
317
+
318
+ if (currentCollapsedSessionIdsJson !== lastPersistedCollapsedSessionIds) {
319
+ lastPersistedCollapsedSessionIds = currentCollapsedSessionIdsJson;
320
+ patch.collapsedSessionIds = currentCollapsedSessionIds;
321
+ changed = true;
322
+ }
323
+
324
+ if (currentActiveSessionId !== lastPersistedActiveSessionId) {
325
+ lastPersistedActiveSessionId = currentActiveSessionId;
326
+ patch.activeSessionId = currentActiveSessionId;
327
+ changed = true;
328
+ }
329
+
330
+ if (changed) {
331
+ writeConfig(patch);
332
+ }
333
+ });
334
+
335
+ try {
336
+ await exitPromise;
337
+ } finally {
338
+ finalizeHost();
339
+ }
340
+ }