pragma-so 0.1.0 → 0.1.2

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 (95) hide show
  1. package/dist/cli/index.js +705 -0
  2. package/dist/index.js +4 -0
  3. package/dist/server/connectorBinaries.js +136 -0
  4. package/dist/server/connectorRegistry.js +142 -0
  5. package/dist/server/conversation/adapterRegistry.js +23 -0
  6. package/dist/server/conversation/adapters/claudeAdapter.js +118 -0
  7. package/dist/server/conversation/adapters/codexAdapter.js +116 -0
  8. package/dist/server/conversation/adapters.js +181 -0
  9. package/dist/server/conversation/executeRunner.js +837 -0
  10. package/dist/server/conversation/gitWorkflow.js +813 -0
  11. package/dist/server/conversation/models.js +21 -0
  12. package/dist/server/conversation/pragmaCli.js +31 -0
  13. package/dist/server/conversation/prompts.js +264 -0
  14. package/{server/conversation/store.ts → dist/server/conversation/store.js} +191 -409
  15. package/dist/server/conversation/titleGenerator.js +88 -0
  16. package/dist/server/conversation/turnRunner.js +262 -0
  17. package/dist/server/conversation/types.js +24 -0
  18. package/dist/server/db.js +678 -0
  19. package/dist/server/http/middleware.js +19 -0
  20. package/dist/server/http/schemas.js +368 -0
  21. package/dist/server/http/validators.js +33 -0
  22. package/dist/server/index.js +4745 -0
  23. package/dist/server/process/runCommand.js +92 -0
  24. package/dist/server/stores/agentStore.js +77 -0
  25. package/dist/server/stores/connectorStore.js +150 -0
  26. package/dist/server/stores/humanStore.js +21 -0
  27. package/dist/server/stores/skillStore.js +73 -0
  28. package/dist/server/stores/taskStore.js +187 -0
  29. package/dist/shared/net.js +29 -0
  30. package/package.json +8 -3
  31. package/ui/dist/assets/index-DEkzJ5rp.css +1 -0
  32. package/ui/dist/assets/index-Dr1FqdaF.js +1095 -0
  33. package/ui/{index.html → dist/index.html} +2 -1
  34. package/cli/index.ts +0 -882
  35. package/index.ts +0 -3
  36. package/server/connectorBinaries.ts +0 -103
  37. package/server/connectorRegistry.ts +0 -158
  38. package/server/conversation/adapterRegistry.ts +0 -53
  39. package/server/conversation/adapters/claudeAdapter.ts +0 -138
  40. package/server/conversation/adapters/codexAdapter.ts +0 -142
  41. package/server/conversation/adapters.ts +0 -224
  42. package/server/conversation/executeRunner.ts +0 -1191
  43. package/server/conversation/gitWorkflow.ts +0 -1037
  44. package/server/conversation/models.ts +0 -23
  45. package/server/conversation/pragmaCli.ts +0 -34
  46. package/server/conversation/prompts.ts +0 -335
  47. package/server/conversation/titleGenerator.ts +0 -106
  48. package/server/conversation/turnRunner.ts +0 -365
  49. package/server/conversation/types.ts +0 -134
  50. package/server/db.ts +0 -837
  51. package/server/http/middleware.ts +0 -31
  52. package/server/http/schemas.ts +0 -430
  53. package/server/http/validators.ts +0 -38
  54. package/server/index.ts +0 -6560
  55. package/server/process/runCommand.ts +0 -142
  56. package/server/stores/agentStore.ts +0 -167
  57. package/server/stores/connectorStore.ts +0 -299
  58. package/server/stores/humanStore.ts +0 -28
  59. package/server/stores/skillStore.ts +0 -127
  60. package/server/stores/taskStore.ts +0 -371
  61. package/shared/net.ts +0 -24
  62. package/tsconfig.json +0 -14
  63. package/ui/src/App.jsx +0 -1338
  64. package/ui/src/api.js +0 -954
  65. package/ui/src/components/CodeView.jsx +0 -319
  66. package/ui/src/components/ConnectionsView.jsx +0 -1004
  67. package/ui/src/components/ContextView.jsx +0 -315
  68. package/ui/src/components/ConversationDrawer.jsx +0 -963
  69. package/ui/src/components/EmptyPane.jsx +0 -20
  70. package/ui/src/components/FeedView.jsx +0 -773
  71. package/ui/src/components/FilesView.jsx +0 -257
  72. package/ui/src/components/InlineChatView.jsx +0 -158
  73. package/ui/src/components/InputBar.jsx +0 -476
  74. package/ui/src/components/OnboardingModal.jsx +0 -112
  75. package/ui/src/components/OutputPanel.jsx +0 -658
  76. package/ui/src/components/PlanProposalPanel.jsx +0 -177
  77. package/ui/src/components/RightPanel.jsx +0 -951
  78. package/ui/src/components/SettingsView.jsx +0 -186
  79. package/ui/src/components/Sidebar.jsx +0 -247
  80. package/ui/src/components/TestingPane.jsx +0 -198
  81. package/ui/src/components/testing/ApiTesterPanel.jsx +0 -187
  82. package/ui/src/components/testing/LogViewerPanel.jsx +0 -64
  83. package/ui/src/components/testing/TerminalPanel.jsx +0 -104
  84. package/ui/src/components/testing/WebPreviewPanel.jsx +0 -78
  85. package/ui/src/hooks/useAgents.js +0 -81
  86. package/ui/src/hooks/useConversation.js +0 -252
  87. package/ui/src/hooks/useTasks.js +0 -161
  88. package/ui/src/hooks/useWorkspace.js +0 -259
  89. package/ui/src/lib/agentIcon.js +0 -10
  90. package/ui/src/lib/conversationUtils.js +0 -575
  91. package/ui/src/main.jsx +0 -10
  92. package/ui/src/styles.css +0 -6899
  93. package/ui/vite.config.mjs +0 -6
  94. /package/ui/{public → dist}/favicon-32.png +0 -0
  95. /package/ui/{public → dist}/favicon.png +0 -0
@@ -0,0 +1,705 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ var __importDefault = (this && this.__importDefault) || function (mod) {
37
+ return (mod && mod.__esModule) ? mod : { "default": mod };
38
+ };
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ const node_http_1 = require("node:http");
41
+ const promises_1 = require("node:fs/promises");
42
+ const node_path_1 = require("node:path");
43
+ const commander_1 = require("commander");
44
+ const mime_types_1 = require("mime-types");
45
+ const open_1 = __importDefault(require("open"));
46
+ const runCommand_1 = require("../server/process/runCommand");
47
+ const program = new commander_1.Command();
48
+ const DEFAULT_API_URL = process.env.PRAGMA_API_URL ?? "http://127.0.0.1:3000";
49
+ const DEFAULT_UI_URL = process.env.PRAGMA_UI_URL ?? "http://127.0.0.1:5173";
50
+ if (!process.env.PRAGMA_CLI_COMMAND) {
51
+ const entry = process.argv[1] ? quoteShellArg(process.argv[1]) : "pragma-so";
52
+ process.env.PRAGMA_CLI_COMMAND = `${quoteShellArg(process.execPath)} ${entry}`;
53
+ }
54
+ program
55
+ .name("pragma-so")
56
+ .description("Very minimal pragma-so CLI")
57
+ .version("0.1.0")
58
+ .action(async () => {
59
+ await runAll();
60
+ });
61
+ program
62
+ .command("setup")
63
+ .description("Call the API setup endpoint")
64
+ .option("-u, --api-url <url>", "Pragma API base URL", DEFAULT_API_URL)
65
+ .action(async (options) => {
66
+ await apiRequest(options.apiUrl, "/setup", { method: "POST" });
67
+ console.log("Setup complete.");
68
+ });
69
+ program
70
+ .command("create-task")
71
+ .description("Call the API to create a task")
72
+ .argument("<title>", "Task title")
73
+ .option("-a, --assigned-to <agentId>", "Assigned agent id")
74
+ .option("-o, --output-dir <outputDir>", "Output directory")
75
+ .option("-s, --status <status>", "Task status", "queued")
76
+ .option("-u, --api-url <url>", "Pragma API base URL", DEFAULT_API_URL)
77
+ .action(async (title, options) => {
78
+ const result = await apiRequest(options.apiUrl, "/tasks", {
79
+ method: "POST",
80
+ headers: { "content-type": "application/json" },
81
+ body: JSON.stringify({
82
+ title,
83
+ status: options.status,
84
+ assigned_to: options.assignedTo,
85
+ output_dir: options.outputDir,
86
+ }),
87
+ });
88
+ console.log(`Created task ${result.id}`);
89
+ });
90
+ program
91
+ .command("list-tasks")
92
+ .description("Call the API to list tasks")
93
+ .option("-s, --status <status>", "Filter by status")
94
+ .option("-l, --limit <limit>", "Maximum tasks to return", "25")
95
+ .option("-u, --api-url <url>", "Pragma API base URL", DEFAULT_API_URL)
96
+ .action(async (options) => {
97
+ const params = new URLSearchParams();
98
+ if (options.status) {
99
+ params.set("status", options.status);
100
+ }
101
+ params.set("limit", options.limit);
102
+ const result = await apiRequest(options.apiUrl, `/tasks?${params.toString()}`);
103
+ if (result.tasks.length === 0) {
104
+ console.log("No tasks found.");
105
+ return;
106
+ }
107
+ console.table(result.tasks);
108
+ });
109
+ program
110
+ .command("list-agents")
111
+ .description("Call the API to list all agents")
112
+ .option("-u, --api-url <url>", "Pragma API base URL", DEFAULT_API_URL)
113
+ .action(async (options) => {
114
+ const result = await apiRequest(options.apiUrl, "/agents");
115
+ if (result.agents.length === 0) {
116
+ console.log("No agents found.");
117
+ return;
118
+ }
119
+ console.table(result.agents);
120
+ });
121
+ program
122
+ .command("db-query")
123
+ .description("Run a read-only SQL query against the workspace database")
124
+ .requiredOption("--sql <text>", "SQL SELECT statement to execute")
125
+ .option("-u, --api-url <url>", "Pragma API base URL", DEFAULT_API_URL)
126
+ .action(async (options) => {
127
+ const result = await apiRequest(options.apiUrl, "/db/query", {
128
+ method: "POST",
129
+ headers: { "content-type": "application/json" },
130
+ body: JSON.stringify({ sql: options.sql }),
131
+ });
132
+ if (result.rows.length === 0) {
133
+ console.log("No rows returned.");
134
+ return;
135
+ }
136
+ console.table(result.rows);
137
+ });
138
+ const taskCommand = program
139
+ .command("task")
140
+ .description("Agent task-control commands");
141
+ taskCommand
142
+ .command("select-recipient")
143
+ .description("Select a worker recipient for the current orchestrating task")
144
+ .requiredOption("--agent-id <id>", "Worker agent id")
145
+ .requiredOption("--reason <text>", "Selection reason")
146
+ .option("--task-id <id>", "Task id")
147
+ .option("--turn-id <id>", "Turn id")
148
+ .option("--api-url <url>", "Pragma API base URL")
149
+ .action(async (options) => {
150
+ const { apiUrl, taskId, turnId } = resolveTaskCommandContext(options);
151
+ const result = await apiRequest(apiUrl, `/tasks/${encodeURIComponent(taskId)}/agent/select-recipient`, {
152
+ method: "POST",
153
+ headers: { "content-type": "application/json" },
154
+ body: JSON.stringify({
155
+ agent_id: options.agentId,
156
+ reason: options.reason,
157
+ turn_id: turnId,
158
+ }),
159
+ });
160
+ const selected = result.assigned_to || options.agentId;
161
+ console.log(`Selected recipient ${selected} for task ${taskId}.`);
162
+ });
163
+ taskCommand
164
+ .command("plan-select-recipient")
165
+ .description("Select a worker recipient for the current plan turn")
166
+ .requiredOption("--agent-id <id>", "Worker agent id")
167
+ .requiredOption("--reason <text>", "Selection reason")
168
+ .option("--thread-id <id>", "Conversation thread id")
169
+ .option("--turn-id <id>", "Conversation turn id")
170
+ .option("--api-url <url>", "Pragma API base URL")
171
+ .action(async (options) => {
172
+ const { apiUrl, threadId, turnId } = resolveThreadTurnCommandContext(options);
173
+ const result = await apiRequest(apiUrl, `/conversations/${encodeURIComponent(threadId)}/turns/${encodeURIComponent(turnId)}/agent/select-recipient`, {
174
+ method: "POST",
175
+ headers: { "content-type": "application/json" },
176
+ body: JSON.stringify({
177
+ agent_id: options.agentId,
178
+ reason: options.reason,
179
+ }),
180
+ });
181
+ const selected = result.selected_agent_id || options.agentId;
182
+ console.log(`Selected plan recipient ${selected} for turn ${turnId}.`);
183
+ });
184
+ taskCommand
185
+ .command("plan-propose")
186
+ .description("Submit a structured plan proposal with a chain of tasks")
187
+ .option("--task <json>", "Task JSON object (repeatable: {title, prompt, recipient})", (val, acc) => { acc.push(val); return acc; }, [])
188
+ .option("--thread-id <id>", "Conversation thread id")
189
+ .option("--turn-id <id>", "Conversation turn id")
190
+ .option("--api-url <url>", "Pragma API base URL")
191
+ .action(async (options) => {
192
+ const { apiUrl, threadId, turnId } = resolveThreadTurnCommandContext(options);
193
+ if (!options.task || options.task.length === 0) {
194
+ console.error("Error: At least one --task flag is required.");
195
+ process.exit(1);
196
+ }
197
+ const tasks = [];
198
+ for (const raw of options.task) {
199
+ try {
200
+ const parsed = JSON.parse(raw);
201
+ if (!parsed.title || !parsed.prompt || !parsed.recipient) {
202
+ console.error(`Error: Each --task JSON must have title, prompt, and recipient fields. Got: ${raw}`);
203
+ process.exit(1);
204
+ }
205
+ tasks.push({ title: parsed.title, prompt: parsed.prompt, recipient: parsed.recipient });
206
+ }
207
+ catch {
208
+ console.error(`Error: Invalid JSON for --task: ${raw}`);
209
+ process.exit(1);
210
+ }
211
+ }
212
+ const result = await apiRequest(apiUrl, `/conversations/${encodeURIComponent(threadId)}/turns/${encodeURIComponent(turnId)}/agent/plan-propose`, {
213
+ method: "POST",
214
+ headers: { "content-type": "application/json" },
215
+ body: JSON.stringify({ tasks }),
216
+ });
217
+ console.log(`Plan proposal submitted with ${result.task_count ?? tasks.length} task(s) for turn ${turnId}.`);
218
+ });
219
+ taskCommand
220
+ .command("ask-question")
221
+ .description("Pause execution and ask the human a clarification question")
222
+ .requiredOption("--question <text>", "Question for the human")
223
+ .option("--details <text>", "Optional context details")
224
+ .option("--option <text>", "Add a selectable answer option (repeatable)", (val, acc) => { acc.push(val); return acc; }, [])
225
+ .option("--task-id <id>", "Task id")
226
+ .option("--turn-id <id>", "Turn id")
227
+ .option("--api-url <url>", "Pragma API base URL")
228
+ .action(async (options) => {
229
+ const { apiUrl, taskId, turnId } = resolveTaskCommandContext(options);
230
+ const agentId = normalizeOptionalString(process.env.PRAGMA_AGENT_ID);
231
+ await apiRequest(apiUrl, `/tasks/${encodeURIComponent(taskId)}/agent/ask-question`, {
232
+ method: "POST",
233
+ headers: { "content-type": "application/json" },
234
+ body: JSON.stringify({
235
+ question: options.question,
236
+ details: options.details,
237
+ turn_id: turnId,
238
+ agent_id: agentId,
239
+ options: options.option.length > 0 ? options.option : undefined,
240
+ }),
241
+ });
242
+ console.log(`Question submitted for task ${taskId}.`);
243
+ });
244
+ taskCommand
245
+ .command("request-help")
246
+ .description("Pause execution and request human help")
247
+ .requiredOption("--summary <text>", "Help summary")
248
+ .option("--details <text>", "Optional context details")
249
+ .option("--task-id <id>", "Task id")
250
+ .option("--turn-id <id>", "Turn id")
251
+ .option("--api-url <url>", "Pragma API base URL")
252
+ .action(async (options) => {
253
+ const { apiUrl, taskId, turnId } = resolveTaskCommandContext(options);
254
+ const agentId = normalizeOptionalString(process.env.PRAGMA_AGENT_ID);
255
+ await apiRequest(apiUrl, `/tasks/${encodeURIComponent(taskId)}/agent/request-help`, {
256
+ method: "POST",
257
+ headers: { "content-type": "application/json" },
258
+ body: JSON.stringify({
259
+ summary: options.summary,
260
+ details: options.details,
261
+ turn_id: turnId,
262
+ agent_id: agentId,
263
+ }),
264
+ });
265
+ console.log(`Help request submitted for task ${taskId}.`);
266
+ });
267
+ taskCommand
268
+ .command("submit-test-commands")
269
+ .description("Submit runnable test commands for the current task (appends by default)")
270
+ .requiredOption("--command <text>", "Test command (repeat for multiple commands)", (value, prev) => [...prev, value], [])
271
+ .requiredOption("--cwd <path>", "Run directory aligned to --command order (repeatable, relative to task workspace root)", (value, prev) => [...prev, value], [])
272
+ .option("--name <text>", "Optional button label aligned to --command order (repeatable)", (value, prev) => [...prev, value], [])
273
+ .option("--task-id <id>", "Task id")
274
+ .option("--turn-id <id>", "Turn id")
275
+ .option("--replace", "Replace existing commands instead of appending")
276
+ .option("--api-url <url>", "Pragma API base URL")
277
+ .action(async (options) => {
278
+ const { apiUrl, taskId, turnId } = resolveTaskCommandContext(options);
279
+ const cwdByIndex = Array.isArray(options.cwd) ? options.cwd : [];
280
+ const commands = (Array.isArray(options.command) ? options.command : [])
281
+ .map((value, index) => {
282
+ const command = value.trim();
283
+ const cwd = (cwdByIndex[index] ?? "").trim();
284
+ const label = (Array.isArray(options.name) ? options.name[index] : "")?.trim() || command;
285
+ return { label, command, cwd };
286
+ })
287
+ .filter((item) => item.command.length > 0 && item.cwd.length > 0);
288
+ if (commands.length === 0) {
289
+ throw new Error("At least one --command and matching --cwd is required.");
290
+ }
291
+ if (commands.length !== (Array.isArray(options.command) ? options.command.length : 0)) {
292
+ throw new Error("Each --command must include a matching --cwd at the same index.");
293
+ }
294
+ await apiRequest(apiUrl, `/tasks/${encodeURIComponent(taskId)}/agent/test-commands`, {
295
+ method: "POST",
296
+ headers: { "content-type": "application/json" },
297
+ body: JSON.stringify({
298
+ commands,
299
+ turn_id: turnId,
300
+ agent_id: normalizeOptionalString(process.env.PRAGMA_AGENT_ID),
301
+ replace: Boolean(options.replace),
302
+ }),
303
+ });
304
+ console.log(`Submitted ${commands.length} test command(s) for task ${taskId}.`);
305
+ });
306
+ taskCommand
307
+ .command("submit-testing-config")
308
+ .description("Submit a testing config for the current task")
309
+ .requiredOption("--config <json>", "The full testing config as a JSON string")
310
+ .option("--task-id <id>", "Task id")
311
+ .option("--turn-id <id>", "Turn id")
312
+ .option("--api-url <url>", "Pragma API base URL")
313
+ .action(async (options) => {
314
+ const { apiUrl, taskId, turnId } = resolveTaskCommandContext(options);
315
+ let config;
316
+ try {
317
+ config = JSON.parse(options.config);
318
+ }
319
+ catch {
320
+ throw new Error("--config must be valid JSON.");
321
+ }
322
+ await apiRequest(apiUrl, `/tasks/${encodeURIComponent(taskId)}/agent/testing-config`, {
323
+ method: "POST",
324
+ headers: { "content-type": "application/json" },
325
+ body: JSON.stringify({
326
+ config,
327
+ turn_id: turnId,
328
+ agent_id: normalizeOptionalString(process.env.PRAGMA_AGENT_ID),
329
+ }),
330
+ });
331
+ console.log(`Submitted testing config for task ${taskId}.`);
332
+ });
333
+ taskCommand
334
+ .command("plan-summary")
335
+ .description("Submit structured plan summary for the current plan turn")
336
+ .requiredOption("--title <text>", "Plan title")
337
+ .requiredOption("--summary <text>", "Plan summary")
338
+ .option("--step <text>", "Plan step (repeat for multiple steps)", (value, prev) => [...prev, value], [])
339
+ .option("--thread-id <id>", "Conversation thread id")
340
+ .option("--turn-id <id>", "Conversation turn id")
341
+ .option("--api-url <url>", "Pragma API base URL")
342
+ .action(async (options) => {
343
+ const { apiUrl, threadId, turnId } = resolveThreadTurnCommandContext(options);
344
+ const steps = (Array.isArray(options.step) ? options.step : [])
345
+ .map((step) => step.trim())
346
+ .filter(Boolean);
347
+ if (steps.length === 0) {
348
+ throw new Error("At least one --step is required.");
349
+ }
350
+ await apiRequest(apiUrl, `/conversations/${encodeURIComponent(threadId)}/turns/${encodeURIComponent(turnId)}/agent/plan-summary`, {
351
+ method: "POST",
352
+ headers: { "content-type": "application/json" },
353
+ body: JSON.stringify({
354
+ title: options.title.trim(),
355
+ summary: options.summary.trim(),
356
+ steps,
357
+ }),
358
+ });
359
+ console.log(`Plan summary submitted for turn ${turnId}.`);
360
+ });
361
+ const agentCommand = program
362
+ .command("agent")
363
+ .description("Agent skill commands");
364
+ agentCommand
365
+ .command("list-skills")
366
+ .description("List skills assigned to the current agent")
367
+ .option("--agent-id <id>", "Agent id")
368
+ .option("--api-url <url>", "Pragma API base URL")
369
+ .action(async (options) => {
370
+ const apiUrl = resolveRequiredOptionOrEnv(options.apiUrl, "PRAGMA_API_URL", "--api-url");
371
+ const agentId = resolveRequiredOptionOrEnv(options.agentId, "PRAGMA_AGENT_ID", "--agent-id");
372
+ const result = await apiRequest(apiUrl, `/agents/${encodeURIComponent(agentId)}/skills`);
373
+ if (result.skills.length === 0) {
374
+ console.log("No skills assigned.");
375
+ return;
376
+ }
377
+ console.table(result.skills.map((s) => ({ name: s.name, description: s.description ?? "" })));
378
+ });
379
+ agentCommand
380
+ .command("get-skill")
381
+ .description("Print the full content of a skill assigned to the current agent")
382
+ .requiredOption("--name <name>", "Skill name")
383
+ .option("--agent-id <id>", "Agent id")
384
+ .option("--api-url <url>", "Pragma API base URL")
385
+ .action(async (options) => {
386
+ const apiUrl = resolveRequiredOptionOrEnv(options.apiUrl, "PRAGMA_API_URL", "--api-url");
387
+ const agentId = resolveRequiredOptionOrEnv(options.agentId, "PRAGMA_AGENT_ID", "--agent-id");
388
+ const listResult = await apiRequest(apiUrl, `/agents/${encodeURIComponent(agentId)}/skills`);
389
+ const skill = listResult.skills.find((s) => s.name.toLowerCase() === options.name.toLowerCase());
390
+ if (skill) {
391
+ const response = await fetch(`${apiUrl.replace(/\/$/, "")}/agents/${encodeURIComponent(agentId)}/skills/${encodeURIComponent(skill.id)}/content`);
392
+ if (!response.ok) {
393
+ throw new Error(`Failed to fetch skill content: HTTP ${response.status}`);
394
+ }
395
+ const content = await response.text();
396
+ console.log(content);
397
+ return;
398
+ }
399
+ // Fall back to connectors
400
+ const connectorResult = await apiRequest(apiUrl, `/agents/${encodeURIComponent(agentId)}/connectors`);
401
+ const connector = connectorResult.connectors.find((c) => c.name.toLowerCase() === options.name.toLowerCase());
402
+ if (connector) {
403
+ const response = await fetch(`${apiUrl.replace(/\/$/, "")}/agents/${encodeURIComponent(agentId)}/connectors/${encodeURIComponent(connector.id)}/content`);
404
+ if (!response.ok) {
405
+ throw new Error(`Failed to fetch connector content: HTTP ${response.status}`);
406
+ }
407
+ const content = await response.text();
408
+ console.log(content);
409
+ return;
410
+ }
411
+ throw new Error(`Skill not found: ${options.name}`);
412
+ });
413
+ program
414
+ .command("server")
415
+ .description("Start the Pragma API server")
416
+ .option("-p, --port <port>", "Port to listen on", "3000")
417
+ .action(async (options) => {
418
+ const port = parsePort(options.port);
419
+ const { startServer } = await Promise.resolve().then(() => __importStar(require("../server")));
420
+ await startServer({ port });
421
+ });
422
+ program
423
+ .command("ui")
424
+ .description("Start the Pragma UI")
425
+ .option("-p, --port <port>", "UI port", "5173")
426
+ .option("-u, --api-url <url>", "Pragma API base URL", DEFAULT_API_URL)
427
+ .action(async (options) => {
428
+ await startUi({
429
+ port: parsePort(options.port),
430
+ apiUrl: options.apiUrl,
431
+ });
432
+ });
433
+ async function runAll() {
434
+ const apiUrl = DEFAULT_API_URL;
435
+ const uiUrl = DEFAULT_UI_URL;
436
+ const serverPort = parsePort(new URL(apiUrl).port || "3000");
437
+ const uiPort = parsePort(new URL(uiUrl).port || "5173");
438
+ const serverProcess = spawnSelfCommand(["server", "--port", String(serverPort)]);
439
+ const serverExit = waitForExit(serverProcess, "server");
440
+ await waitForHealth(apiUrl);
441
+ const uiProcess = spawnSelfCommand([
442
+ "ui",
443
+ "--port",
444
+ String(uiPort),
445
+ "--api-url",
446
+ apiUrl,
447
+ ]);
448
+ const uiExit = waitForExit(uiProcess, "ui");
449
+ try {
450
+ await (0, open_1.default)(uiUrl, { wait: false });
451
+ }
452
+ catch (error) {
453
+ console.warn(`Unable to open browser automatically: ${errorMessage(error)}`);
454
+ console.warn(`Open ${uiUrl} manually.`);
455
+ }
456
+ let shuttingDown = false;
457
+ const stop = () => {
458
+ if (shuttingDown) {
459
+ return;
460
+ }
461
+ shuttingDown = true;
462
+ serverProcess.kill("SIGTERM");
463
+ uiProcess.kill("SIGTERM");
464
+ };
465
+ process.once("SIGINT", stop);
466
+ process.once("SIGTERM", stop);
467
+ const firstExit = await Promise.race([serverExit, uiExit]);
468
+ if (!shuttingDown) {
469
+ shuttingDown = true;
470
+ serverProcess.kill("SIGTERM");
471
+ uiProcess.kill("SIGTERM");
472
+ await Promise.allSettled([serverExit, uiExit]);
473
+ throw new Error(`${firstExit.name} exited unexpectedly with ${formatExit(firstExit)}.`);
474
+ }
475
+ await Promise.allSettled([serverExit, uiExit]);
476
+ }
477
+ async function startUi(options) {
478
+ const builtUiDir = await resolveBuiltUiDir();
479
+ if (builtUiDir) {
480
+ await serveBuiltUi({ port: options.port, rootDir: builtUiDir });
481
+ return;
482
+ }
483
+ const uiDir = await resolveUiDir();
484
+ const projectRoot = (0, node_path_1.dirname)(uiDir);
485
+ const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
486
+ const child = (0, runCommand_1.spawnCommand)({
487
+ command: npmCommand,
488
+ args: ["run", "ui:dev", "--", "--host", "127.0.0.1", "--port", String(options.port)],
489
+ cwd: projectRoot,
490
+ stdio: "inherit",
491
+ env: {
492
+ ...process.env,
493
+ VITE_API_URL: options.apiUrl,
494
+ },
495
+ });
496
+ await waitForExit(child, "ui");
497
+ }
498
+ async function resolveBuiltUiDir() {
499
+ const candidates = [
500
+ (0, node_path_1.join)(__dirname, "..", "..", "ui", "dist"),
501
+ (0, node_path_1.join)(process.cwd(), "ui", "dist"),
502
+ (0, node_path_1.join)(process.cwd(), "dist", "ui"),
503
+ (0, node_path_1.join)(__dirname, ".."),
504
+ (0, node_path_1.join)(process.cwd(), "dist"),
505
+ ];
506
+ for (const candidate of candidates) {
507
+ if (await pathExists((0, node_path_1.join)(candidate, "index.html"))) {
508
+ return candidate;
509
+ }
510
+ }
511
+ return null;
512
+ }
513
+ async function serveBuiltUi(options) {
514
+ const rootDir = (0, node_path_1.resolve)(options.rootDir);
515
+ const server = (0, node_http_1.createServer)((req, res) => {
516
+ void handleBuiltUiRequest(req, res, rootDir);
517
+ });
518
+ await new Promise((resolvePromise, reject) => {
519
+ server.once("error", reject);
520
+ server.listen(options.port, "127.0.0.1", () => {
521
+ server.off("error", reject);
522
+ resolvePromise();
523
+ });
524
+ });
525
+ console.log(`Pragma UI listening on http://127.0.0.1:${options.port}`);
526
+ await new Promise((resolvePromise, reject) => {
527
+ server.once("close", () => resolvePromise());
528
+ server.once("error", reject);
529
+ });
530
+ }
531
+ async function handleBuiltUiRequest(req, res, rootDir) {
532
+ try {
533
+ const url = new URL(req.url ?? "/", "http://127.0.0.1");
534
+ const pathname = safeDecodePathname(url.pathname);
535
+ const requestedPath = pathname === "/" ? "/index.html" : pathname;
536
+ const candidatePath = (0, node_path_1.resolve)(rootDir, `.${requestedPath}`);
537
+ if (!isPathInsideRoot(candidatePath, rootDir)) {
538
+ res.statusCode = 403;
539
+ res.end("Forbidden");
540
+ return;
541
+ }
542
+ let filePath = candidatePath;
543
+ const fileInfo = await (0, promises_1.stat)(filePath).catch(() => null);
544
+ const isFile = Boolean(fileInfo?.isFile());
545
+ if (!isFile) {
546
+ filePath = (0, node_path_1.join)(rootDir, "index.html");
547
+ }
548
+ const content = await (0, promises_1.readFile)(filePath);
549
+ const mime = (0, mime_types_1.lookup)(filePath) || "application/octet-stream";
550
+ res.statusCode = 200;
551
+ res.setHeader("content-type", mime);
552
+ res.setHeader("cache-control", filePath.endsWith("index.html") ? "no-store" : "public, max-age=31536000, immutable");
553
+ res.end(content);
554
+ }
555
+ catch (error) {
556
+ res.statusCode = 500;
557
+ res.end(errorMessage(error));
558
+ }
559
+ }
560
+ async function resolveUiDir() {
561
+ const candidates = [
562
+ (0, node_path_1.join)(__dirname, "..", "..", "ui"),
563
+ (0, node_path_1.join)(__dirname, "..", "ui"),
564
+ (0, node_path_1.join)(process.cwd(), "ui"),
565
+ ];
566
+ for (const candidate of candidates) {
567
+ if (await pathExists(candidate)) {
568
+ return candidate;
569
+ }
570
+ }
571
+ throw new Error("UI folder not found.");
572
+ }
573
+ async function waitForHealth(apiUrl) {
574
+ const timeoutMs = 15000;
575
+ const start = Date.now();
576
+ while (Date.now() - start < timeoutMs) {
577
+ try {
578
+ await apiRequest(apiUrl, "/health");
579
+ return;
580
+ }
581
+ catch {
582
+ await sleep(250);
583
+ }
584
+ }
585
+ throw new Error("Server did not become ready in time.");
586
+ }
587
+ function spawnSelfCommand(args) {
588
+ return (0, runCommand_1.spawnNodeCommand)({
589
+ modulePath: __filename,
590
+ args,
591
+ cwd: process.cwd(),
592
+ stdio: "inherit",
593
+ env: process.env,
594
+ });
595
+ }
596
+ function waitForExit(child, name) {
597
+ return child.then((result) => {
598
+ return {
599
+ name,
600
+ exitCode: result.exitCode,
601
+ signal: result.signal,
602
+ };
603
+ });
604
+ }
605
+ function formatExit(result) {
606
+ if (result.signal) {
607
+ return `signal ${result.signal}`;
608
+ }
609
+ if (result.exitCode === null) {
610
+ return "unknown exit";
611
+ }
612
+ return `exit code ${result.exitCode}`;
613
+ }
614
+ async function apiRequest(apiUrl, path, init) {
615
+ const base = apiUrl.replace(/\/$/, "");
616
+ const response = await fetch(`${base}${path}`, init);
617
+ if (!response.ok) {
618
+ let message = `HTTP ${response.status}`;
619
+ try {
620
+ const body = (await response.json());
621
+ if (body.error) {
622
+ message = body.error;
623
+ }
624
+ }
625
+ catch {
626
+ // Keep default message.
627
+ }
628
+ throw new Error(message);
629
+ }
630
+ if (response.status === 204) {
631
+ return {};
632
+ }
633
+ return (await response.json());
634
+ }
635
+ function parsePort(portValue) {
636
+ const port = Number.parseInt(portValue, 10);
637
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
638
+ throw new Error(`Invalid --port value: ${portValue}. Use an integer 1-65535.`);
639
+ }
640
+ return port;
641
+ }
642
+ function resolveTaskCommandContext(input) {
643
+ const apiUrl = resolveRequiredOptionOrEnv(input.apiUrl, "PRAGMA_API_URL", "--api-url");
644
+ const taskId = resolveRequiredOptionOrEnv(input.taskId, "PRAGMA_TASK_ID", "--task-id");
645
+ const turnId = normalizeOptionalString(input.turnId) || normalizeOptionalString(process.env.PRAGMA_TURN_ID);
646
+ return { apiUrl, taskId, turnId };
647
+ }
648
+ function resolveThreadTurnCommandContext(input) {
649
+ const apiUrl = resolveRequiredOptionOrEnv(input.apiUrl, "PRAGMA_API_URL", "--api-url");
650
+ const threadId = resolveRequiredOptionOrEnv(input.threadId, "PRAGMA_THREAD_ID", "--thread-id");
651
+ const turnId = resolveRequiredOptionOrEnv(input.turnId, "PRAGMA_TURN_ID", "--turn-id");
652
+ return { apiUrl, threadId, turnId };
653
+ }
654
+ function resolveRequiredOptionOrEnv(optionValue, envName, optionLabel) {
655
+ const fromOption = normalizeOptionalString(optionValue);
656
+ if (fromOption) {
657
+ return fromOption;
658
+ }
659
+ const fromEnv = normalizeOptionalString(process.env[envName]);
660
+ if (fromEnv) {
661
+ return fromEnv;
662
+ }
663
+ throw new Error(`Missing ${optionLabel}. Pass ${optionLabel} or set ${envName}.`);
664
+ }
665
+ function normalizeOptionalString(value) {
666
+ if (typeof value !== "string") {
667
+ return undefined;
668
+ }
669
+ const trimmed = value.trim();
670
+ return trimmed.length > 0 ? trimmed : undefined;
671
+ }
672
+ function safeDecodePathname(pathname) {
673
+ try {
674
+ const decoded = decodeURIComponent(pathname);
675
+ return (0, node_path_1.normalize)(decoded.startsWith("/") ? decoded : `/${decoded}`);
676
+ }
677
+ catch {
678
+ return "/";
679
+ }
680
+ }
681
+ function isPathInsideRoot(candidatePath, rootDir) {
682
+ return candidatePath === rootDir || candidatePath.startsWith(`${rootDir}${node_path_1.sep}`);
683
+ }
684
+ function quoteShellArg(value) {
685
+ return `"${value.replace(/["\\$`]/g, "\\$&")}"`;
686
+ }
687
+ async function pathExists(path) {
688
+ try {
689
+ await (0, promises_1.access)(path);
690
+ return true;
691
+ }
692
+ catch {
693
+ return false;
694
+ }
695
+ }
696
+ function sleep(ms) {
697
+ return new Promise((resolve) => setTimeout(resolve, ms));
698
+ }
699
+ function errorMessage(error) {
700
+ return error instanceof Error ? error.message : String(error);
701
+ }
702
+ program.parseAsync(process.argv).catch((error) => {
703
+ console.error(errorMessage(error));
704
+ process.exitCode = 1;
705
+ });