@poncho-ai/cli 0.13.0 → 0.14.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.
@@ -0,0 +1,674 @@
1
+ /**
2
+ * OpenAPI 3.1 spec and interactive docs page for the Poncho agent HTTP API.
3
+ */
4
+
5
+ export const buildOpenApiSpec = (options: { agentName: string }): Record<string, unknown> => ({
6
+ openapi: "3.1.0",
7
+ info: {
8
+ title: `${options.agentName} API`,
9
+ description:
10
+ "HTTP API for interacting with a Poncho agent. Supports conversation management, " +
11
+ "streaming message responses via Server-Sent Events (SSE), tool approval workflows, " +
12
+ "file uploads, and cron job triggers.",
13
+ version: "1.0.0",
14
+ },
15
+ servers: [{ url: "/", description: "Current host" }],
16
+ components: {
17
+ securitySchemes: {
18
+ bearerAuth: {
19
+ type: "http",
20
+ scheme: "bearer",
21
+ description:
22
+ "Pass the PONCHO_AUTH_TOKEN value as a Bearer token. " +
23
+ "Required only when `auth.required: true` in poncho.config.js.",
24
+ },
25
+ },
26
+ schemas: {
27
+ ConversationSummary: {
28
+ type: "object",
29
+ properties: {
30
+ conversationId: { type: "string" },
31
+ title: { type: "string" },
32
+ runtimeRunId: { type: "string" },
33
+ ownerId: { type: "string" },
34
+ tenantId: { type: ["string", "null"] },
35
+ createdAt: { type: "number", description: "Unix epoch ms" },
36
+ updatedAt: { type: "number", description: "Unix epoch ms" },
37
+ messageCount: { type: "integer" },
38
+ },
39
+ },
40
+ Message: {
41
+ type: "object",
42
+ properties: {
43
+ role: { type: "string", enum: ["user", "assistant"] },
44
+ content: {
45
+ oneOf: [
46
+ { type: "string" },
47
+ {
48
+ type: "array",
49
+ items: {
50
+ oneOf: [
51
+ {
52
+ type: "object",
53
+ properties: {
54
+ type: { type: "string", const: "text" },
55
+ text: { type: "string" },
56
+ },
57
+ },
58
+ {
59
+ type: "object",
60
+ properties: {
61
+ type: { type: "string", const: "file" },
62
+ data: { type: "string" },
63
+ mediaType: { type: "string" },
64
+ filename: { type: "string" },
65
+ },
66
+ },
67
+ ],
68
+ },
69
+ },
70
+ ],
71
+ },
72
+ metadata: {
73
+ type: "object",
74
+ properties: {
75
+ id: { type: "string" },
76
+ timestamp: { type: "number" },
77
+ tokenCount: { type: "number" },
78
+ step: { type: "number" },
79
+ toolActivity: { type: "array", items: { type: "string" } },
80
+ },
81
+ },
82
+ },
83
+ },
84
+ Conversation: {
85
+ type: "object",
86
+ properties: {
87
+ conversationId: { type: "string" },
88
+ title: { type: "string" },
89
+ ownerId: { type: "string" },
90
+ tenantId: { type: ["string", "null"] },
91
+ createdAt: { type: "number" },
92
+ updatedAt: { type: "number" },
93
+ messages: { type: "array", items: { $ref: "#/components/schemas/Message" } },
94
+ pendingApprovals: {
95
+ type: "array",
96
+ items: { $ref: "#/components/schemas/PendingApproval" },
97
+ },
98
+ },
99
+ },
100
+ PendingApproval: {
101
+ type: "object",
102
+ properties: {
103
+ approvalId: { type: "string" },
104
+ runId: { type: "string" },
105
+ tool: { type: "string" },
106
+ input: {},
107
+ },
108
+ },
109
+ TokenUsage: {
110
+ type: "object",
111
+ properties: {
112
+ input: { type: "integer" },
113
+ output: { type: "integer" },
114
+ cached: { type: "integer" },
115
+ },
116
+ },
117
+ RunResult: {
118
+ type: "object",
119
+ properties: {
120
+ status: { type: "string", enum: ["completed", "error", "cancelled"] },
121
+ response: { type: "string" },
122
+ steps: { type: "integer" },
123
+ tokens: { $ref: "#/components/schemas/TokenUsage" },
124
+ duration: { type: "number", description: "Duration in ms" },
125
+ continuation: { type: "boolean" },
126
+ maxSteps: { type: "integer" },
127
+ },
128
+ },
129
+ FileAttachment: {
130
+ type: "object",
131
+ properties: {
132
+ data: { type: "string", description: "base64-encoded file data" },
133
+ mediaType: { type: "string" },
134
+ filename: { type: "string" },
135
+ },
136
+ required: ["data", "mediaType"],
137
+ },
138
+ Error: {
139
+ type: "object",
140
+ properties: {
141
+ code: { type: "string" },
142
+ message: { type: "string" },
143
+ },
144
+ },
145
+ },
146
+ },
147
+ security: [{ bearerAuth: [] }],
148
+ paths: {
149
+ "/health": {
150
+ get: {
151
+ tags: ["Health"],
152
+ summary: "Health check",
153
+ security: [],
154
+ responses: {
155
+ "200": {
156
+ description: "Server is healthy",
157
+ content: {
158
+ "application/json": {
159
+ schema: {
160
+ type: "object",
161
+ properties: { status: { type: "string", const: "ok" } },
162
+ },
163
+ },
164
+ },
165
+ },
166
+ },
167
+ },
168
+ },
169
+
170
+ "/api/auth/session": {
171
+ get: {
172
+ tags: ["Auth"],
173
+ summary: "Check session status",
174
+ description: "Returns whether the caller is authenticated and provides a CSRF token for subsequent mutating requests.",
175
+ security: [],
176
+ responses: {
177
+ "200": {
178
+ description: "Session status",
179
+ content: {
180
+ "application/json": {
181
+ schema: {
182
+ type: "object",
183
+ properties: {
184
+ authenticated: { type: "boolean" },
185
+ sessionId: { type: "string" },
186
+ ownerId: { type: "string" },
187
+ csrfToken: { type: "string" },
188
+ },
189
+ required: ["authenticated"],
190
+ },
191
+ },
192
+ },
193
+ },
194
+ },
195
+ },
196
+ },
197
+ "/api/auth/login": {
198
+ post: {
199
+ tags: ["Auth"],
200
+ summary: "Authenticate with passphrase",
201
+ description: "Creates a session cookie. Only needed for browser-based auth; API clients should use Bearer tokens instead.",
202
+ security: [],
203
+ requestBody: {
204
+ required: true,
205
+ content: {
206
+ "application/json": {
207
+ schema: {
208
+ type: "object",
209
+ properties: { passphrase: { type: "string" } },
210
+ required: ["passphrase"],
211
+ },
212
+ },
213
+ },
214
+ },
215
+ responses: {
216
+ "200": {
217
+ description: "Login successful",
218
+ content: {
219
+ "application/json": {
220
+ schema: {
221
+ type: "object",
222
+ properties: {
223
+ ok: { type: "boolean" },
224
+ sessionId: { type: "string" },
225
+ csrfToken: { type: "string" },
226
+ },
227
+ },
228
+ },
229
+ },
230
+ },
231
+ "401": {
232
+ description: "Invalid passphrase",
233
+ content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } },
234
+ },
235
+ "429": {
236
+ description: "Too many login attempts",
237
+ content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } },
238
+ },
239
+ },
240
+ },
241
+ },
242
+ "/api/auth/logout": {
243
+ post: {
244
+ tags: ["Auth"],
245
+ summary: "End session",
246
+ responses: {
247
+ "200": {
248
+ description: "Logged out",
249
+ content: {
250
+ "application/json": {
251
+ schema: { type: "object", properties: { ok: { type: "boolean" } } },
252
+ },
253
+ },
254
+ },
255
+ },
256
+ },
257
+ },
258
+
259
+ "/api/conversations": {
260
+ get: {
261
+ tags: ["Conversations"],
262
+ summary: "List conversations",
263
+ responses: {
264
+ "200": {
265
+ description: "Conversation list",
266
+ content: {
267
+ "application/json": {
268
+ schema: {
269
+ type: "object",
270
+ properties: {
271
+ conversations: {
272
+ type: "array",
273
+ items: { $ref: "#/components/schemas/ConversationSummary" },
274
+ },
275
+ },
276
+ },
277
+ },
278
+ },
279
+ },
280
+ },
281
+ },
282
+ post: {
283
+ tags: ["Conversations"],
284
+ summary: "Create a conversation",
285
+ requestBody: {
286
+ content: {
287
+ "application/json": {
288
+ schema: {
289
+ type: "object",
290
+ properties: { title: { type: "string" } },
291
+ },
292
+ },
293
+ },
294
+ },
295
+ responses: {
296
+ "201": {
297
+ description: "Conversation created",
298
+ content: {
299
+ "application/json": {
300
+ schema: {
301
+ type: "object",
302
+ properties: { conversation: { $ref: "#/components/schemas/Conversation" } },
303
+ },
304
+ },
305
+ },
306
+ },
307
+ },
308
+ },
309
+ },
310
+
311
+ "/api/conversations/{conversationId}": {
312
+ get: {
313
+ tags: ["Conversations"],
314
+ summary: "Get conversation",
315
+ description: "Returns the full conversation including messages and any pending tool approval requests.",
316
+ parameters: [
317
+ { name: "conversationId", in: "path", required: true, schema: { type: "string" } },
318
+ ],
319
+ responses: {
320
+ "200": {
321
+ description: "Conversation with messages",
322
+ content: {
323
+ "application/json": {
324
+ schema: {
325
+ type: "object",
326
+ properties: { conversation: { $ref: "#/components/schemas/Conversation" } },
327
+ },
328
+ },
329
+ },
330
+ },
331
+ "404": {
332
+ description: "Conversation not found",
333
+ content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } },
334
+ },
335
+ },
336
+ },
337
+ patch: {
338
+ tags: ["Conversations"],
339
+ summary: "Rename conversation",
340
+ parameters: [
341
+ { name: "conversationId", in: "path", required: true, schema: { type: "string" } },
342
+ ],
343
+ requestBody: {
344
+ required: true,
345
+ content: {
346
+ "application/json": {
347
+ schema: {
348
+ type: "object",
349
+ properties: { title: { type: "string" } },
350
+ required: ["title"],
351
+ },
352
+ },
353
+ },
354
+ },
355
+ responses: {
356
+ "200": {
357
+ description: "Conversation renamed",
358
+ content: {
359
+ "application/json": {
360
+ schema: {
361
+ type: "object",
362
+ properties: { conversation: { $ref: "#/components/schemas/Conversation" } },
363
+ },
364
+ },
365
+ },
366
+ },
367
+ },
368
+ },
369
+ delete: {
370
+ tags: ["Conversations"],
371
+ summary: "Delete conversation",
372
+ parameters: [
373
+ { name: "conversationId", in: "path", required: true, schema: { type: "string" } },
374
+ ],
375
+ responses: {
376
+ "200": {
377
+ description: "Conversation deleted",
378
+ content: {
379
+ "application/json": {
380
+ schema: { type: "object", properties: { ok: { type: "boolean" } } },
381
+ },
382
+ },
383
+ },
384
+ },
385
+ },
386
+ },
387
+
388
+ "/api/conversations/{conversationId}/messages": {
389
+ post: {
390
+ tags: ["Messages"],
391
+ summary: "Send a message (streaming)",
392
+ description:
393
+ "Sends a user message and streams the agent's response via Server-Sent Events.\n\n" +
394
+ "### SSE protocol\n\n" +
395
+ "The response is a stream of SSE frames. Each frame has the format:\n\n" +
396
+ "```\nevent: <type>\ndata: <json>\n\n```\n\n" +
397
+ "**Event types:**\n\n" +
398
+ "| Event | Payload | Description |\n" +
399
+ "| --- | --- | --- |\n" +
400
+ "| `run:started` | `{ runId, agentId }` | Agent run has begun |\n" +
401
+ "| `model:chunk` | `{ content }` | Incremental text token from the model |\n" +
402
+ "| `model:response` | `{ usage: { input, output, cached } }` | Model call finished |\n" +
403
+ "| `step:started` | `{ step }` | Agent step started |\n" +
404
+ "| `step:completed` | `{ step, duration }` | Agent step finished |\n" +
405
+ "| `tool:started` | `{ tool, input }` | Tool invocation started |\n" +
406
+ "| `tool:completed` | `{ tool, output, duration }` | Tool finished successfully |\n" +
407
+ "| `tool:error` | `{ tool, error, recoverable }` | Tool returned an error |\n" +
408
+ "| `tool:approval:required` | `{ tool, input, approvalId }` | Tool needs human approval |\n" +
409
+ "| `tool:approval:granted` | `{ approvalId }` | Approval was granted |\n" +
410
+ "| `tool:approval:denied` | `{ approvalId, reason? }` | Approval was denied |\n" +
411
+ "| `run:completed` | `{ runId, result: RunResult }` | Agent finished |\n" +
412
+ "| `run:error` | `{ runId, error: { code, message } }` | Agent failed |\n" +
413
+ "| `run:cancelled` | `{ runId }` | Run was cancelled via stop endpoint |\n\n" +
414
+ "To build the assistant's response, concatenate all `model:chunk` content values.\n\n" +
415
+ "### Reconnection\n\n" +
416
+ "If the SSE connection drops mid-stream, reconnect via " +
417
+ "`GET /api/conversations/{conversationId}/events` to replay buffered events.",
418
+ parameters: [
419
+ { name: "conversationId", in: "path", required: true, schema: { type: "string" } },
420
+ ],
421
+ requestBody: {
422
+ required: true,
423
+ content: {
424
+ "application/json": {
425
+ schema: {
426
+ type: "object",
427
+ properties: {
428
+ message: { type: "string", description: "User message text" },
429
+ parameters: {
430
+ type: "object",
431
+ additionalProperties: true,
432
+ description: "Key-value parameters passed to the agent run",
433
+ },
434
+ files: {
435
+ type: "array",
436
+ items: { $ref: "#/components/schemas/FileAttachment" },
437
+ description: "Attached files (base64-encoded)",
438
+ },
439
+ },
440
+ required: ["message"],
441
+ },
442
+ },
443
+ "multipart/form-data": {
444
+ schema: {
445
+ type: "object",
446
+ properties: {
447
+ message: { type: "string" },
448
+ parameters: { type: "string", description: "JSON-encoded parameters object" },
449
+ files: { type: "array", items: { type: "string", format: "binary" } },
450
+ },
451
+ },
452
+ },
453
+ },
454
+ },
455
+ responses: {
456
+ "200": {
457
+ description: "SSE stream of agent events",
458
+ content: { "text/event-stream": { schema: { type: "string" } } },
459
+ },
460
+ "404": {
461
+ description: "Conversation not found",
462
+ content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } },
463
+ },
464
+ },
465
+ },
466
+ },
467
+
468
+ "/api/conversations/{conversationId}/events": {
469
+ get: {
470
+ tags: ["Messages"],
471
+ summary: "Attach to live event stream",
472
+ description:
473
+ "Connects to the SSE event stream for an in-progress run. " +
474
+ "Replays all buffered events from the current run, then streams live events. " +
475
+ "If no run is active, sends a `stream:end` event and closes.",
476
+ parameters: [
477
+ { name: "conversationId", in: "path", required: true, schema: { type: "string" } },
478
+ ],
479
+ responses: {
480
+ "200": {
481
+ description: "SSE stream (same event format as POST /messages)",
482
+ content: { "text/event-stream": { schema: { type: "string" } } },
483
+ },
484
+ "404": {
485
+ description: "Conversation not found",
486
+ content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } },
487
+ },
488
+ },
489
+ },
490
+ },
491
+
492
+ "/api/conversations/{conversationId}/stop": {
493
+ post: {
494
+ tags: ["Messages"],
495
+ summary: "Stop an in-flight run",
496
+ parameters: [
497
+ { name: "conversationId", in: "path", required: true, schema: { type: "string" } },
498
+ ],
499
+ requestBody: {
500
+ required: true,
501
+ content: {
502
+ "application/json": {
503
+ schema: {
504
+ type: "object",
505
+ properties: {
506
+ runId: { type: "string", description: "The run ID to cancel (from run:started event)" },
507
+ },
508
+ required: ["runId"],
509
+ },
510
+ },
511
+ },
512
+ },
513
+ responses: {
514
+ "200": {
515
+ description: "Stop result",
516
+ content: {
517
+ "application/json": {
518
+ schema: {
519
+ type: "object",
520
+ properties: {
521
+ ok: { type: "boolean" },
522
+ stopped: { type: "boolean" },
523
+ runId: { type: "string" },
524
+ },
525
+ },
526
+ },
527
+ },
528
+ },
529
+ },
530
+ },
531
+ },
532
+
533
+ "/api/approvals/{approvalId}": {
534
+ post: {
535
+ tags: ["Approvals"],
536
+ summary: "Resolve a tool approval request",
537
+ description:
538
+ "When an agent run encounters a gated tool, it emits a `tool:approval:required` SSE event " +
539
+ "and pauses. Use this endpoint to approve or deny the tool invocation.",
540
+ parameters: [
541
+ { name: "approvalId", in: "path", required: true, schema: { type: "string" } },
542
+ ],
543
+ requestBody: {
544
+ required: true,
545
+ content: {
546
+ "application/json": {
547
+ schema: {
548
+ type: "object",
549
+ properties: { approved: { type: "boolean" } },
550
+ required: ["approved"],
551
+ },
552
+ },
553
+ },
554
+ },
555
+ responses: {
556
+ "200": {
557
+ description: "Approval resolved",
558
+ content: {
559
+ "application/json": {
560
+ schema: {
561
+ type: "object",
562
+ properties: {
563
+ ok: { type: "boolean" },
564
+ approvalId: { type: "string" },
565
+ approved: { type: "boolean" },
566
+ },
567
+ },
568
+ },
569
+ },
570
+ },
571
+ "404": {
572
+ description: "Approval not found or expired",
573
+ content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } },
574
+ },
575
+ },
576
+ },
577
+ },
578
+
579
+ "/api/uploads/{key}": {
580
+ get: {
581
+ tags: ["Assets"],
582
+ summary: "Retrieve an uploaded file",
583
+ description: "Serves a file previously uploaded during a conversation. The key is returned in file content part references.",
584
+ parameters: [
585
+ {
586
+ name: "key",
587
+ in: "path",
588
+ required: true,
589
+ schema: { type: "string" },
590
+ description: "Upload key (e.g. filename or storage path)",
591
+ },
592
+ ],
593
+ responses: {
594
+ "200": {
595
+ description: "File content with appropriate Content-Type",
596
+ content: { "application/octet-stream": { schema: { type: "string", format: "binary" } } },
597
+ },
598
+ "404": {
599
+ description: "Upload not found",
600
+ content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } },
601
+ },
602
+ },
603
+ },
604
+ },
605
+
606
+ "/api/cron/{jobName}": {
607
+ get: {
608
+ tags: ["Cron"],
609
+ summary: "Trigger a cron job",
610
+ description:
611
+ "Triggers a named cron job defined in AGENT.md frontmatter. " +
612
+ "Supports continuation via the `continue` query parameter.",
613
+ parameters: [
614
+ { name: "jobName", in: "path", required: true, schema: { type: "string" } },
615
+ {
616
+ name: "continue",
617
+ in: "query",
618
+ schema: { type: "string" },
619
+ description: "Conversation ID to continue a previous cron run",
620
+ },
621
+ ],
622
+ responses: {
623
+ "200": {
624
+ description: "Cron job result",
625
+ content: {
626
+ "application/json": {
627
+ schema: {
628
+ type: "object",
629
+ properties: {
630
+ conversationId: { type: "string" },
631
+ response: { type: "string" },
632
+ steps: { type: "integer" },
633
+ status: { type: "string" },
634
+ continuation: { type: "string", description: "URL to continue this run" },
635
+ },
636
+ },
637
+ },
638
+ },
639
+ },
640
+ "404": {
641
+ description: "Cron job not found",
642
+ content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } },
643
+ },
644
+ },
645
+ },
646
+ },
647
+ },
648
+ tags: [
649
+ { name: "Health", description: "Server health check" },
650
+ { name: "Auth", description: "Session and authentication management" },
651
+ { name: "Conversations", description: "Create, list, read, rename, and delete conversations" },
652
+ {
653
+ name: "Messages",
654
+ description: "Send messages and stream agent responses via SSE",
655
+ },
656
+ { name: "Approvals", description: "Resolve gated tool approval requests" },
657
+ { name: "Assets", description: "Retrieve uploaded files" },
658
+ { name: "Cron", description: "Trigger cron jobs defined in AGENT.md" },
659
+ ],
660
+ });
661
+
662
+ export const renderApiDocsHtml = (specUrl: string): string => `<!DOCTYPE html>
663
+ <html lang="en">
664
+ <head>
665
+ <meta charset="utf-8" />
666
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
667
+ <title>API Documentation</title>
668
+ <style>body { margin: 0; }</style>
669
+ </head>
670
+ <body>
671
+ <script id="api-reference" data-url="${specUrl}"></script>
672
+ <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
673
+ </body>
674
+ </html>`;