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.
- package/bin/tui.js +3 -0
- package/node_modules/pilotswarm-ui-core/README.md +6 -0
- package/node_modules/pilotswarm-ui-core/package.json +32 -0
- package/node_modules/pilotswarm-ui-core/src/commands.js +72 -0
- package/node_modules/pilotswarm-ui-core/src/context-usage.js +212 -0
- package/node_modules/pilotswarm-ui-core/src/controller.js +3676 -0
- package/node_modules/pilotswarm-ui-core/src/formatting.js +872 -0
- package/node_modules/pilotswarm-ui-core/src/history.js +589 -0
- package/node_modules/pilotswarm-ui-core/src/index.js +13 -0
- package/node_modules/pilotswarm-ui-core/src/layout.js +196 -0
- package/node_modules/pilotswarm-ui-core/src/reducer.js +1030 -0
- package/node_modules/pilotswarm-ui-core/src/selectors.js +2921 -0
- package/node_modules/pilotswarm-ui-core/src/session-tree.js +109 -0
- package/node_modules/pilotswarm-ui-core/src/state.js +80 -0
- package/node_modules/pilotswarm-ui-core/src/store.js +23 -0
- package/node_modules/pilotswarm-ui-core/src/system-titles.js +24 -0
- package/node_modules/pilotswarm-ui-core/src/themes/catppuccin-mocha.js +56 -0
- package/node_modules/pilotswarm-ui-core/src/themes/cobalt2.js +56 -0
- package/node_modules/pilotswarm-ui-core/src/themes/dark-high-contrast.js +56 -0
- package/node_modules/pilotswarm-ui-core/src/themes/dracula.js +56 -0
- package/node_modules/pilotswarm-ui-core/src/themes/github-dark.js +56 -0
- package/node_modules/pilotswarm-ui-core/src/themes/gruvbox-dark.js +56 -0
- package/node_modules/pilotswarm-ui-core/src/themes/hacker-x-matrix.js +56 -0
- package/node_modules/pilotswarm-ui-core/src/themes/hacker-x-orion-prime.js +56 -0
- package/node_modules/pilotswarm-ui-core/src/themes/helpers.js +77 -0
- package/node_modules/pilotswarm-ui-core/src/themes/index.js +44 -0
- package/node_modules/pilotswarm-ui-core/src/themes/noctis-obscuro.js +56 -0
- package/node_modules/pilotswarm-ui-core/src/themes/noctis-viola.js +56 -0
- package/node_modules/pilotswarm-ui-core/src/themes/noctis.js +56 -0
- package/node_modules/pilotswarm-ui-core/src/themes/nord.js +56 -0
- package/node_modules/pilotswarm-ui-core/src/themes/solarized-dark.js +56 -0
- package/node_modules/pilotswarm-ui-core/src/themes/tokyo-night.js +56 -0
- package/node_modules/pilotswarm-ui-react/README.md +5 -0
- package/node_modules/pilotswarm-ui-react/package.json +36 -0
- package/node_modules/pilotswarm-ui-react/src/components.js +1430 -0
- package/node_modules/pilotswarm-ui-react/src/index.js +4 -0
- package/node_modules/pilotswarm-ui-react/src/platform.js +15 -0
- package/node_modules/pilotswarm-ui-react/src/use-controller-state.js +38 -0
- package/node_modules/pilotswarm-ui-react/src/web-app.js +2776 -0
- package/package.json +7 -2
- package/src/app.js +16 -1
- package/src/node-sdk-transport.js +174 -1
- package/src/sync-workspace-ui.js +53 -0
- 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.
|
|
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.
|
|
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, {
|
|
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.
|
|
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}`;
|