pilotswarm-cli 0.1.12 → 0.1.13

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 CHANGED
@@ -1,282 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- /**
4
- * pilotswarm CLI for the pilotswarm.
5
- *
6
- * Modes:
7
- * local Embedded workers + TUI in one process (default)
8
- * remote Client-only TUI, workers run elsewhere (AKS, separate process)
9
- *
10
- * Usage:
11
- * npx pilotswarm local --plugin ./plugin
12
- * npx pilotswarm remote --store postgresql://... --context toygres-aks
13
- * npx pilotswarm --env .env --plugin ./my-plugin --workers 4
14
- *
15
- * All flags can also be set via environment variables (CLI flags take precedence).
16
- */
3
+ import { parseCliIntoEnv } from "../src/bootstrap-env.js";
4
+ import { startTuiApp } from "../src/index.js";
17
5
 
18
- import { parseArgs } from "node:util";
19
- import fs from "node:fs";
20
- import path from "node:path";
21
- import { fileURLToPath } from "node:url";
22
-
23
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
24
- const pkgRoot = path.resolve(__dirname, "..");
25
- const defaultTuiSplashPath = path.join(pkgRoot, "cli", "tui-splash.txt");
26
-
27
- function readPluginMetadata(pluginDir) {
28
- if (!pluginDir) return null;
29
- const pluginJsonPath = path.join(pluginDir, "plugin.json");
30
- if (!fs.existsSync(pluginJsonPath)) return null;
31
- try {
32
- return JSON.parse(fs.readFileSync(pluginJsonPath, "utf-8"));
33
- } catch (err) {
34
- console.error(`Failed to parse plugin metadata: ${pluginJsonPath}: ${err.message}`);
35
- process.exit(1);
36
- }
37
- }
38
-
39
- // ─── Parse CLI args ──────────────────────────────────────────────
40
-
41
- const { values: flags, positionals } = parseArgs({
42
- options: {
43
- // Connection
44
- store: { type: "string", short: "s" },
45
- env: { type: "string", short: "e" },
46
-
47
- // Local mode
48
- plugin: { type: "string", short: "p" },
49
- worker: { type: "string", short: "w" },
50
- workers: { type: "string", short: "n" },
51
- model: { type: "string", short: "m" },
52
- system: { type: "string" },
53
-
54
- // Remote mode
55
- context: { type: "string", short: "c" },
56
- namespace: { type: "string" },
57
- label: { type: "string" },
58
-
59
- // General
60
- "log-level": { type: "string" },
61
- help: { type: "boolean", short: "h" },
62
- },
63
- allowPositionals: true,
64
- strict: false,
65
- });
66
-
67
- if (flags.help) {
68
- console.log(`
69
- pilotswarm — TUI for pilotswarm apps
70
-
71
- USAGE
72
- npx pilotswarm [local|remote] [flags]
73
-
74
- MODES
75
- local Embed workers in the TUI process (default)
76
- remote Client-only — connect to remote workers via kubectl logs
77
-
78
- FLAGS ENV VAR EQUIVALENT
79
- -s, --store <url> Database URL DATABASE_URL
80
- -e, --env <file> Env file (default: .env / .env.remote)
81
-
82
- -p, --plugin <dir> Plugin dir PLUGIN_DIRS
83
- -w, --worker <module> Tool module WORKER_MODULE
84
- -n, --workers <count> Worker count WORKERS (default: 4)
85
- -m, --model <name> LLM model COPILOT_MODEL
86
- --system <msg|file> System msg SYSTEM_MESSAGE (or plugin/system.md)
87
-
88
- -c, --context <ctx> K8s context K8S_CONTEXT
89
- --namespace <ns> K8s namespace K8S_NAMESPACE (default: copilot-runtime)
90
- --label <selector> Pod label K8S_POD_LABEL
91
- --log-level <level> Trace level LOG_LEVEL
92
- -h, --help Show help
93
-
94
- All flags can be set via the corresponding env var (in .env or exported).
95
- CLI flags take precedence over env vars.
96
-
97
- EXAMPLES
98
- # Plugin-only (no code), env from file
99
- npx pilotswarm --env .env --plugin ./my-plugin
100
-
101
- # Custom tools via worker module
102
- npx pilotswarm --env .env --plugin ./plugin --worker ./tools.js
103
-
104
- # All config in .env (zero flags)
105
- echo "DATABASE_URL=postgresql://..." >> .env
106
- echo "GITHUB_TOKEN=ghu_..." >> .env
107
- echo "PLUGIN_DIRS=./plugin" >> .env
108
- npx pilotswarm
109
-
110
- # Client-only, workers on AKS
111
- npx pilotswarm remote --store postgresql://... --namespace my-app
112
- `.trim());
113
- process.exit(0);
114
- }
115
-
116
- // ─── Determine mode ─────────────────────────────────────────────
117
-
118
- const mode = positionals[0] === "remote" ? "remote" : "local";
119
-
120
- // ─── Load env file ───────────────────────────────────────────────
121
-
122
- const envFile = flags.env
123
- || (mode === "remote" ? ".env.remote" : ".env");
124
- if (fs.existsSync(envFile)) {
125
- // Parse env file manually (KEY=VALUE lines)
126
- const envContent = fs.readFileSync(envFile, "utf-8");
127
- for (const line of envContent.split("\n")) {
128
- const trimmed = line.trim();
129
- if (!trimmed || trimmed.startsWith("#")) continue;
130
- const eqIdx = trimmed.indexOf("=");
131
- if (eqIdx === -1) continue;
132
- const key = trimmed.slice(0, eqIdx).trim();
133
- const value = trimmed.slice(eqIdx + 1).trim();
134
- // CLI flags take precedence, env file fills in gaps
135
- if (!process.env[key]) {
136
- process.env[key] = value;
137
- }
138
- }
139
- }
140
-
141
- // ─── Resolve system message ──────────────────────────────────────
142
-
143
- const DEFAULT_SYSTEM_MESSAGE = `You are a helpful assistant running in a durable execution environment. Be concise.
144
-
145
- CRITICAL RULES:
146
- 1. You have 'wait', 'wait_on_worker', and 'cron' tools. Use 'cron' for recurring or periodic schedules, and use 'wait'/'wait_on_worker' for one-shot delays.
147
- 2. NEVER say you cannot wait or set timers. You CAN — use the 'wait' tool.
148
- 3. NEVER use bash sleep, setTimeout, setInterval, cron, or any other timing mechanism.
149
- 4. The 'wait' and 'cron' tools enable durable timers that survive process restarts and node migrations.
150
- 5. For recurring tasks, call cron(seconds=<N>, reason="...") once. The orchestration handles future wake-ups automatically.
151
- 6. Use wait(seconds=<N>) only for one-shot delays within a turn.
152
- 7. Use cron(action="cancel") to stop a recurring schedule.
153
- 8. If facts tools are available, use them for durable memory, checkpoints, and resumable task state when helpful.`;
154
-
155
- function resolveSystemMessage() {
156
- // 1. CLI flag (string or file path)
157
- if (flags.system) {
158
- if (fs.existsSync(flags.system)) {
159
- return fs.readFileSync(flags.system, "utf-8").trim();
160
- }
161
- return flags.system;
162
- }
163
-
164
- // 2. plugin/system.md (convention)
165
- const pluginDir = resolvePluginDir();
166
- if (pluginDir) {
167
- const systemMd = path.join(pluginDir, "system.md");
168
- if (fs.existsSync(systemMd)) {
169
- return fs.readFileSync(systemMd, "utf-8").trim();
170
- }
171
- }
172
-
173
- // 3. SYSTEM_MESSAGE env var
174
- if (process.env.SYSTEM_MESSAGE) {
175
- return process.env.SYSTEM_MESSAGE;
176
- }
177
-
178
- // 4. Default
179
- return DEFAULT_SYSTEM_MESSAGE;
180
- }
181
-
182
- function resolvePluginDir() {
183
- if (flags.plugin) return path.resolve(flags.plugin);
184
- if (process.env.PLUGIN_DIRS) {
185
- const dirs = process.env.PLUGIN_DIRS.split(",").map(d => d.trim()).filter(Boolean);
186
- return dirs[0] || null;
187
- }
188
- // Auto-detect: ./plugins in cwd, then bundled plugins in CLI package
189
- const cwdPlugin = path.resolve("plugins");
190
- if (fs.existsSync(cwdPlugin)) return cwdPlugin;
191
- const bundledPlugin = path.join(pkgRoot, "plugins");
192
- if (fs.existsSync(bundledPlugin)) return bundledPlugin;
193
- return null;
194
- }
195
-
196
- function resolveTuiBranding(pluginDir) {
197
- const pluginMeta = readPluginMetadata(pluginDir);
198
- const tui = pluginMeta?.tui;
199
- let defaultSplash = "";
200
- if (fs.existsSync(defaultTuiSplashPath)) {
201
- defaultSplash = fs.readFileSync(defaultTuiSplashPath, "utf-8").trimEnd();
202
- }
203
- if (!tui || typeof tui !== "object") {
204
- return {
205
- title: "PilotSwarm",
206
- splash: defaultSplash,
207
- };
208
- }
209
-
210
- const title = typeof tui.title === "string" && tui.title.trim()
211
- ? tui.title.trim()
212
- : "PilotSwarm";
213
-
214
- let splash = defaultSplash;
215
- if (typeof tui.splash === "string" && tui.splash.trim()) {
216
- splash = tui.splash;
217
- } else if (typeof tui.splashFile === "string" && tui.splashFile.trim()) {
218
- const splashPath = path.resolve(pluginDir, tui.splashFile);
219
- if (!fs.existsSync(splashPath)) {
220
- console.error(`TUI splash file not found: ${splashPath}`);
221
- process.exit(1);
222
- }
223
- splash = fs.readFileSync(splashPath, "utf-8").trimEnd();
224
- }
225
-
226
- return { title, splash };
227
- }
228
-
229
- // ─── Build TUI config and set env vars ───────────────────────────
230
-
231
- // Store
232
- const store = flags.store || process.env.DATABASE_URL || "sqlite::memory:";
233
- process.env.DATABASE_URL = store;
234
-
235
- // Workers
236
- if (mode === "remote") {
237
- process.env.WORKERS = "0";
238
- } else {
239
- process.env.WORKERS = flags.workers ?? process.env.WORKERS ?? "4";
240
- }
241
-
242
- // Plugin dirs
243
- const pluginDir = resolvePluginDir();
244
- if (pluginDir) {
245
- process.env.PLUGIN_DIRS = pluginDir;
246
- }
247
- const tuiBranding = resolveTuiBranding(pluginDir);
248
-
249
- // Model
250
- process.env.COPILOT_MODEL = flags.model || process.env.COPILOT_MODEL || "";
251
-
252
- // Log level
253
- process.env.LOG_LEVEL = flags["log-level"] || process.env.LOG_LEVEL || "";
254
-
255
- // Context, namespace and label for remote mode (kubectl log streaming)
256
- process.env.K8S_CONTEXT = flags.context || process.env.K8S_CONTEXT || "";
257
- process.env.K8S_NAMESPACE = flags.namespace || process.env.K8S_NAMESPACE || "copilot-runtime";
258
- process.env.K8S_POD_LABEL = flags.label || process.env.K8S_POD_LABEL || "app.kubernetes.io/component=worker";
259
-
260
- // System message
261
- process.env._TUI_SYSTEM_MESSAGE = resolveSystemMessage();
262
- process.env._TUI_TITLE = tuiBranding.title;
263
- process.env._TUI_SPLASH = tuiBranding.splash;
264
-
265
- // ─── Load custom worker module (local mode only) ─────────────────
266
-
267
- const workerModulePath = flags.worker || process.env.WORKER_MODULE || "";
268
- if (mode === "local" && workerModulePath) {
269
- const resolved = path.resolve(workerModulePath);
270
- if (!fs.existsSync(resolved)) {
271
- console.error(`Worker module not found: ${resolved}`);
272
- process.exit(1);
273
- }
274
- process.env._TUI_WORKER_MODULE = resolved;
275
- }
276
-
277
- // ─── Launch TUI ──────────────────────────────────────────────────
278
-
279
- // The TUI is the same file, but now reads config from env vars set above
280
- // instead of relying on the user to set them manually.
281
- const tuiPath = path.join(pkgRoot, "cli", "tui.js");
282
- await import(tuiPath);
6
+ const config = parseCliIntoEnv(process.argv.slice(2));
7
+ await startTuiApp(config);
package/package.json CHANGED
@@ -1,16 +1,21 @@
1
1
  {
2
2
  "name": "pilotswarm-cli",
3
- "version": "0.1.12",
4
- "description": "Terminal UI for pilotswarm — interactive durable agent orchestration.",
3
+ "version": "0.1.13",
4
+ "description": "Terminal UI for PilotSwarm.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "pilotswarm": "./bin/tui.js",
8
8
  "pilotswarm-cli": "./bin/tui.js"
9
9
  },
10
+ "main": "./src/index.js",
11
+ "exports": {
12
+ ".": "./src/index.js"
13
+ },
10
14
  "scripts": {
11
15
  "build": "echo 'pilotswarm-cli: no build step (plain JS)'",
12
- "tui": "node bin/tui.js local --env ../../.env.remote",
13
- "tui:remote": "node bin/tui.js remote --env ../../.env.remote"
16
+ "start": "node ./bin/tui.js local --env ../../.env.remote",
17
+ "start:remote": "node ./bin/tui.js remote --env ../../.env.remote",
18
+ "clean": "true"
14
19
  },
15
20
  "keywords": [
16
21
  "copilot",
@@ -30,20 +35,19 @@
30
35
  "url": "https://github.com/affandar/PilotSwarm/issues"
31
36
  },
32
37
  "dependencies": {
33
- "pilotswarm-sdk": "^0.1.9",
34
- "@bradygaster/squad-cli": "^0.8.25",
35
- "chalk": "^5.6.2",
36
- "marked": "^15.0.12",
37
- "marked-terminal": "^7.3.0",
38
- "neo-blessed": "^0.1.81"
38
+ "ink": "^6.8.0",
39
+ "pilotswarm-sdk": "^0.1.12",
40
+ "pilotswarm-ui-core": "0.1.0",
41
+ "pilotswarm-ui-react": "0.1.0",
42
+ "react": "^19.2.4"
39
43
  },
40
44
  "engines": {
41
45
  "node": ">=24.0.0"
42
46
  },
43
47
  "files": [
44
48
  "bin/**/*",
45
- "cli/tui.js",
46
- "cli/context-usage.js",
49
+ "src/**/*",
50
+ "tui-splash.txt",
47
51
  "plugins/**/*",
48
52
  "README.md"
49
53
  ]