pilotswarm-cli 0.1.15 → 0.1.17

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 (44) hide show
  1. package/bin/tui.js +3 -0
  2. package/node_modules/pilotswarm-ui-core/README.md +6 -0
  3. package/node_modules/pilotswarm-ui-core/package.json +32 -0
  4. package/node_modules/pilotswarm-ui-core/src/commands.js +72 -0
  5. package/node_modules/pilotswarm-ui-core/src/context-usage.js +212 -0
  6. package/node_modules/pilotswarm-ui-core/src/controller.js +3676 -0
  7. package/node_modules/pilotswarm-ui-core/src/formatting.js +872 -0
  8. package/node_modules/pilotswarm-ui-core/src/history.js +589 -0
  9. package/node_modules/pilotswarm-ui-core/src/index.js +13 -0
  10. package/node_modules/pilotswarm-ui-core/src/layout.js +196 -0
  11. package/node_modules/pilotswarm-ui-core/src/reducer.js +1030 -0
  12. package/node_modules/pilotswarm-ui-core/src/selectors.js +2921 -0
  13. package/node_modules/pilotswarm-ui-core/src/session-tree.js +109 -0
  14. package/node_modules/pilotswarm-ui-core/src/state.js +80 -0
  15. package/node_modules/pilotswarm-ui-core/src/store.js +23 -0
  16. package/node_modules/pilotswarm-ui-core/src/system-titles.js +24 -0
  17. package/node_modules/pilotswarm-ui-core/src/themes/catppuccin-mocha.js +56 -0
  18. package/node_modules/pilotswarm-ui-core/src/themes/cobalt2.js +56 -0
  19. package/node_modules/pilotswarm-ui-core/src/themes/dark-high-contrast.js +56 -0
  20. package/node_modules/pilotswarm-ui-core/src/themes/dracula.js +56 -0
  21. package/node_modules/pilotswarm-ui-core/src/themes/github-dark.js +56 -0
  22. package/node_modules/pilotswarm-ui-core/src/themes/gruvbox-dark.js +56 -0
  23. package/node_modules/pilotswarm-ui-core/src/themes/hacker-x-matrix.js +56 -0
  24. package/node_modules/pilotswarm-ui-core/src/themes/hacker-x-orion-prime.js +56 -0
  25. package/node_modules/pilotswarm-ui-core/src/themes/helpers.js +77 -0
  26. package/node_modules/pilotswarm-ui-core/src/themes/index.js +44 -0
  27. package/node_modules/pilotswarm-ui-core/src/themes/noctis-obscuro.js +56 -0
  28. package/node_modules/pilotswarm-ui-core/src/themes/noctis-viola.js +56 -0
  29. package/node_modules/pilotswarm-ui-core/src/themes/noctis.js +56 -0
  30. package/node_modules/pilotswarm-ui-core/src/themes/nord.js +56 -0
  31. package/node_modules/pilotswarm-ui-core/src/themes/solarized-dark.js +56 -0
  32. package/node_modules/pilotswarm-ui-core/src/themes/tokyo-night.js +56 -0
  33. package/node_modules/pilotswarm-ui-react/README.md +5 -0
  34. package/node_modules/pilotswarm-ui-react/package.json +36 -0
  35. package/node_modules/pilotswarm-ui-react/src/components.js +1430 -0
  36. package/node_modules/pilotswarm-ui-react/src/index.js +4 -0
  37. package/node_modules/pilotswarm-ui-react/src/platform.js +15 -0
  38. package/node_modules/pilotswarm-ui-react/src/use-controller-state.js +38 -0
  39. package/node_modules/pilotswarm-ui-react/src/web-app.js +2776 -0
  40. package/package.json +7 -2
  41. package/src/app.js +16 -1
  42. package/src/node-sdk-transport.js +174 -1
  43. package/src/sync-workspace-ui.js +53 -0
  44. package/src/version.js +7 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pilotswarm-cli",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "Terminal UI for PilotSwarm.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,6 +14,7 @@
14
14
  },
15
15
  "scripts": {
16
16
  "build": "echo 'pilotswarm-cli: no build step (plain JS)'",
17
+ "prepack": "node ./src/sync-workspace-ui.js",
17
18
  "start": "node ./bin/tui.js local --env ../../.env.remote",
18
19
  "start:remote": "node ./bin/tui.js remote --env ../../.env.remote",
19
20
  "clean": "true"
@@ -37,7 +38,7 @@
37
38
  },
38
39
  "dependencies": {
39
40
  "ink": "^6.8.0",
40
- "pilotswarm-sdk": "^0.1.15",
41
+ "pilotswarm-sdk": "^0.1.17",
41
42
  "pilotswarm-ui-core": "0.1.0",
42
43
  "pilotswarm-ui-react": "0.1.0",
43
44
  "react": "^19.2.4"
@@ -45,6 +46,10 @@
45
46
  "engines": {
46
47
  "node": ">=24.0.0"
47
48
  },
49
+ "bundledDependencies": [
50
+ "pilotswarm-ui-core",
51
+ "pilotswarm-ui-react"
52
+ ],
48
53
  "files": [
49
54
  "bin/**/*",
50
55
  "src/**/*",
package/src/app.js CHANGED
@@ -2,6 +2,7 @@ import React from "react";
2
2
  import { useInput, useStdin } from "ink";
3
3
  import { UiPlatformProvider, SharedPilotSwarmApp } from "pilotswarm-ui-react";
4
4
  import { UI_COMMANDS } from "pilotswarm-ui-core";
5
+ import { PILOTSWARM_CLI_VERSION_LABEL } from "./version.js";
5
6
 
6
7
  const MOUSE_INPUT_PATTERN = /\u001b\[<(\d+);(\d+);(\d+)([mM])/gu;
7
8
  const MOUSE_INPUT_FRAGMENT_PATTERN = /(?:\u001b)?\[<\d+;\d+;\d+[mM]/u;
@@ -281,6 +282,17 @@ export function PilotSwarmTuiApp({ controller, platform, onRequestExit }) {
281
282
  controller.handleCommand(UI_COMMANDS.CLOSE_MODAL).catch(() => {});
282
283
  return;
283
284
  }
285
+ if (modal.type === "confirm") {
286
+ if (key.escape || input === "n" || input === "q") {
287
+ controller.handleCommand(UI_COMMANDS.CLOSE_MODAL).catch(() => {});
288
+ return;
289
+ }
290
+ if (key.return || input === "y") {
291
+ controller.handleCommand(UI_COMMANDS.MODAL_CONFIRM).catch(() => {});
292
+ return;
293
+ }
294
+ return;
295
+ }
284
296
  if (modal.type === "renameSession" || modal.type === "artifactUpload") {
285
297
  if (key.escape) {
286
298
  controller.handleCommand(UI_COMMANDS.CLOSE_MODAL).catch(() => {});
@@ -646,5 +658,8 @@ export function PilotSwarmTuiApp({ controller, platform, onRequestExit }) {
646
658
  });
647
659
 
648
660
  return React.createElement(UiPlatformProvider, { platform },
649
- React.createElement(SharedPilotSwarmApp, { controller }));
661
+ React.createElement(SharedPilotSwarmApp, {
662
+ controller,
663
+ versionLabel: PILOTSWARM_CLI_VERSION_LABEL,
664
+ }));
650
665
  }
@@ -13,7 +13,9 @@ import {
13
13
  import { startEmbeddedWorkers, stopEmbeddedWorkers } from "./embedded-workers.js";
14
14
  import { getPluginDirsFromEnv } from "./plugin-config.js";
15
15
 
16
- const EXPORTS_DIR = path.join(os.homedir(), "pilotswarm-exports");
16
+ const EXPORTS_DIR = path.resolve(
17
+ expandUserPath(process.env.PILOTSWARM_EXPORT_DIR || path.join(os.homedir(), "pilotswarm-exports")),
18
+ );
17
19
  fs.mkdirSync(EXPORTS_DIR, { recursive: true });
18
20
  const K8S_SERVICE_ACCOUNT_DIR = "/var/run/secrets/kubernetes.io/serviceaccount";
19
21
 
@@ -218,6 +220,73 @@ function expandUserPath(filePath) {
218
220
  : value;
219
221
  }
220
222
 
223
+ function getLocalLogDir() {
224
+ const configured = expandUserPath(process.env.PILOTSWARM_LOG_DIR || "");
225
+ return configured ? path.resolve(configured) : "";
226
+ }
227
+
228
+ function listLocalLogFiles(logDir) {
229
+ if (!logDir || !fileExists(logDir)) return [];
230
+ try {
231
+ return fs.readdirSync(logDir, { withFileTypes: true })
232
+ .filter((entry) => entry.isFile() && entry.name.endsWith(".log"))
233
+ .map((entry) => path.join(logDir, entry.name))
234
+ .sort();
235
+ } catch {
236
+ return [];
237
+ }
238
+ }
239
+
240
+ function readRecentLogLines(filePath, maxBytes = 128 * 1024, maxLines = 200) {
241
+ try {
242
+ const stats = fs.statSync(filePath);
243
+ if (!stats.isFile() || stats.size <= 0) return [];
244
+ const fd = fs.openSync(filePath, "r");
245
+ try {
246
+ const bytesToRead = Math.min(stats.size, maxBytes);
247
+ const buffer = Buffer.alloc(bytesToRead);
248
+ fs.readSync(fd, buffer, 0, bytesToRead, stats.size - bytesToRead);
249
+ let text = buffer.toString("utf8");
250
+ if (bytesToRead < stats.size) {
251
+ const newlineIndex = text.indexOf("\n");
252
+ text = newlineIndex >= 0 ? text.slice(newlineIndex + 1) : "";
253
+ }
254
+ return text
255
+ .split(/\r?\n/u)
256
+ .map((line) => line.trimEnd())
257
+ .filter(Boolean)
258
+ .slice(-maxLines);
259
+ } finally {
260
+ fs.closeSync(fd);
261
+ }
262
+ } catch {
263
+ return [];
264
+ }
265
+ }
266
+
267
+ function readLogChunk(filePath, start, end) {
268
+ if (!Number.isFinite(start) || !Number.isFinite(end) || end <= start) return "";
269
+ try {
270
+ const fd = fs.openSync(filePath, "r");
271
+ try {
272
+ const length = end - start;
273
+ const buffer = Buffer.alloc(length);
274
+ const bytesRead = fs.readSync(fd, buffer, 0, length, start);
275
+ return buffer.toString("utf8", 0, bytesRead);
276
+ } finally {
277
+ fs.closeSync(fd);
278
+ }
279
+ } catch {
280
+ return "";
281
+ }
282
+ }
283
+
284
+ function getLocalLogPollIntervalMs() {
285
+ const value = Number.parseInt(process.env.PILOTSWARM_LOG_POLL_INTERVAL_MS || "", 10);
286
+ if (Number.isFinite(value) && value >= 50) return value;
287
+ return 500;
288
+ }
289
+
221
290
  function guessArtifactContentType(filename) {
222
291
  const ext = path.extname(String(filename || "")).toLowerCase();
223
292
  if (ext === ".md" || ext === ".markdown" || ext === ".mdx") return "text/markdown";
@@ -388,6 +457,17 @@ export class NodeSdkTransport {
388
457
  }
389
458
 
390
459
  getLogConfig() {
460
+ const localLogDir = getLocalLogDir();
461
+ if (localLogDir) {
462
+ const exists = fileExists(localLogDir);
463
+ return {
464
+ available: exists,
465
+ availabilityReason: exists
466
+ ? ""
467
+ : `Log tailing disabled: local log directory ${JSON.stringify(localLogDir)} does not exist.`,
468
+ };
469
+ }
470
+
391
471
  const hasInClusterConfig = hasInClusterK8sAccess();
392
472
  const hasKubectlConfig = hasExplicitKubectlConfig();
393
473
  if (hasInClusterConfig) {
@@ -970,10 +1050,103 @@ export class NodeSdkTransport {
970
1050
  });
971
1051
  }
972
1052
 
1053
+ startLocalLogProcess() {
1054
+ const logDir = getLocalLogDir();
1055
+ if (!logDir || this.logTailHandle) return;
1056
+
1057
+ const handle = {
1058
+ stopped: false,
1059
+ files: new Map(),
1060
+ interval: null,
1061
+ stop: () => {
1062
+ if (handle.stopped) return;
1063
+ handle.stopped = true;
1064
+ if (handle.interval) {
1065
+ clearInterval(handle.interval);
1066
+ handle.interval = null;
1067
+ }
1068
+ handle.files.clear();
1069
+ },
1070
+ };
1071
+ this.logTailHandle = handle;
1072
+
1073
+ const emitLine = (filePath, line) => {
1074
+ const text = String(line || "").trim();
1075
+ if (!text) return;
1076
+ const pseudoPod = path.basename(filePath, path.extname(filePath));
1077
+ this.logEntryCounter += 1;
1078
+ this.emitLogEntry(buildLogEntry(`[pod/${pseudoPod}] ${text}`, this.logEntryCounter));
1079
+ };
1080
+
1081
+ const refresh = () => {
1082
+ if (handle.stopped || this.logTailHandle !== handle) return;
1083
+ for (const filePath of listLocalLogFiles(logDir)) {
1084
+ let state = handle.files.get(filePath);
1085
+ let stats;
1086
+ try {
1087
+ stats = fs.statSync(filePath);
1088
+ } catch {
1089
+ continue;
1090
+ }
1091
+ if (!stats.isFile()) continue;
1092
+
1093
+ if (!state) {
1094
+ state = {
1095
+ position: stats.size,
1096
+ inode: stats.ino,
1097
+ buffer: "",
1098
+ };
1099
+ handle.files.set(filePath, state);
1100
+ for (const line of readRecentLogLines(filePath)) {
1101
+ emitLine(filePath, line);
1102
+ }
1103
+ state.position = stats.size;
1104
+ state.inode = stats.ino;
1105
+ continue;
1106
+ }
1107
+
1108
+ if (state.inode !== stats.ino || stats.size < state.position) {
1109
+ state.position = 0;
1110
+ state.buffer = "";
1111
+ state.inode = stats.ino;
1112
+ }
1113
+
1114
+ if (stats.size <= state.position) continue;
1115
+
1116
+ const chunk = readLogChunk(filePath, state.position, stats.size);
1117
+ state.position = stats.size;
1118
+ if (!chunk) continue;
1119
+ const combined = state.buffer + chunk;
1120
+ const lines = combined.split(/\r?\n/u);
1121
+ state.buffer = lines.pop() || "";
1122
+ for (const line of lines) {
1123
+ emitLine(filePath, line);
1124
+ }
1125
+ }
1126
+ };
1127
+
1128
+ try {
1129
+ refresh();
1130
+ handle.interval = setInterval(refresh, getLocalLogPollIntervalMs());
1131
+ if (typeof handle.interval.unref === "function") {
1132
+ handle.interval.unref();
1133
+ }
1134
+ } catch (error) {
1135
+ this.logTailHandle = null;
1136
+ handle.stop();
1137
+ this.emitSyntheticLogMessage(error?.message || String(error), "error", "local-log");
1138
+ }
1139
+ }
1140
+
973
1141
  startLogProcess() {
974
1142
  const config = this.getLogConfig();
975
1143
  if (!config.available || this.logProc || this.logTailHandle) return;
976
1144
 
1145
+ if (getLocalLogDir()) {
1146
+ this.startLocalLogProcess();
1147
+ return;
1148
+ }
1149
+
977
1150
  if (hasInClusterK8sAccess()) {
978
1151
  this.startInClusterLogProcess();
979
1152
  return;
@@ -0,0 +1,53 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ const BUNDLED_UI_PACKAGES = ["pilotswarm-ui-core", "pilotswarm-ui-react"];
7
+
8
+ function copyTree(sourceDir, targetDir) {
9
+ fs.mkdirSync(targetDir, { recursive: true });
10
+ for (const entry of fs.readdirSync(sourceDir, { withFileTypes: true })) {
11
+ const sourcePath = path.join(sourceDir, entry.name);
12
+ const targetPath = path.join(targetDir, entry.name);
13
+ if (entry.isDirectory()) {
14
+ copyTree(sourcePath, targetPath);
15
+ continue;
16
+ }
17
+ fs.copyFileSync(sourcePath, targetPath);
18
+ }
19
+ }
20
+
21
+ export function syncBundledWorkspaceUiPackages({ cliPackageDir = path.resolve(__dirname, "..") } = {}) {
22
+ const packagesDir = path.resolve(cliPackageDir, "..");
23
+ const syncedPackages = [];
24
+
25
+ for (const packageName of BUNDLED_UI_PACKAGES) {
26
+ const workspaceDir = path.join(packagesDir, packageName.replace("pilotswarm-", ""));
27
+ const sourcePackageJson = path.join(workspaceDir, "package.json");
28
+ const sourceReadme = path.join(workspaceDir, "README.md");
29
+ const sourceSrcDir = path.join(workspaceDir, "src");
30
+ if (!fs.existsSync(sourcePackageJson) || !fs.existsSync(sourceSrcDir)) {
31
+ continue;
32
+ }
33
+
34
+ const targetDir = path.join(cliPackageDir, "node_modules", packageName);
35
+ fs.rmSync(targetDir, { recursive: true, force: true });
36
+ fs.mkdirSync(targetDir, { recursive: true });
37
+ fs.copyFileSync(sourcePackageJson, path.join(targetDir, "package.json"));
38
+ if (fs.existsSync(sourceReadme)) {
39
+ fs.copyFileSync(sourceReadme, path.join(targetDir, "README.md"));
40
+ }
41
+ copyTree(sourceSrcDir, path.join(targetDir, "src"));
42
+ syncedPackages.push(packageName);
43
+ }
44
+
45
+ return syncedPackages;
46
+ }
47
+
48
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
49
+ const syncedPackages = syncBundledWorkspaceUiPackages();
50
+ if (syncedPackages.length > 0) {
51
+ console.log(`[sync-workspace-ui] synced ${syncedPackages.join(", ")}`);
52
+ }
53
+ }
package/src/version.js ADDED
@@ -0,0 +1,7 @@
1
+ import { createRequire } from "node:module";
2
+
3
+ const require = createRequire(import.meta.url);
4
+ const cliPackageJson = require("../package.json");
5
+
6
+ export const PILOTSWARM_CLI_VERSION = String(cliPackageJson?.version || "0.0.0");
7
+ export const PILOTSWARM_CLI_VERSION_LABEL = `v${PILOTSWARM_CLI_VERSION}`;