@poncho-ai/cli 0.14.1 → 0.16.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.
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  main
4
- } from "./chunk-AIEVSNGF.js";
4
+ } from "./chunk-XT5HPFIW.js";
5
5
 
6
6
  // src/cli.ts
7
7
  void main();
package/dist/index.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  runTests,
24
24
  startDevServer,
25
25
  updateAgentGuidance
26
- } from "./chunk-AIEVSNGF.js";
26
+ } from "./chunk-XT5HPFIW.js";
27
27
  export {
28
28
  addSkill,
29
29
  buildCli,
@@ -2,7 +2,7 @@ import {
2
2
  consumeFirstRunIntro,
3
3
  inferConversationTitle,
4
4
  resolveHarnessEnvironment
5
- } from "./chunk-AIEVSNGF.js";
5
+ } from "./chunk-XT5HPFIW.js";
6
6
 
7
7
  // src/run-interactive-ink.ts
8
8
  import * as readline from "readline";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poncho-ai/cli",
3
- "version": "0.14.1",
3
+ "version": "0.16.0",
4
4
  "description": "CLI for building and deploying AI agents",
5
5
  "repository": {
6
6
  "type": "git",
@@ -27,9 +27,9 @@
27
27
  "react": "^19.2.4",
28
28
  "react-devtools-core": "^6.1.5",
29
29
  "yaml": "^2.8.1",
30
- "@poncho-ai/harness": "0.14.1",
31
- "@poncho-ai/messaging": "0.2.1",
32
- "@poncho-ai/sdk": "1.0.2"
30
+ "@poncho-ai/harness": "0.15.0",
31
+ "@poncho-ai/messaging": "0.2.3",
32
+ "@poncho-ai/sdk": "1.1.0"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@types/busboy": "^1.5.4",
package/src/index.ts CHANGED
@@ -391,12 +391,14 @@ Stopping is best-effort and keeps partial assistant output/tool activity already
391
391
  \`\`\`bash
392
392
  # Local web UI + API server
393
393
  poncho dev
394
+ poncho dev --port 8080
394
395
 
395
396
  # Local interactive CLI
396
397
  poncho run --interactive
397
398
 
398
399
  # One-off run
399
400
  poncho run "Your task here"
401
+ poncho run "Explain this code" --file ./src/index.ts
400
402
 
401
403
  # Run tests
402
404
  poncho test
@@ -478,9 +480,9 @@ How it works:
478
480
  - Use \`approval-required\` to require human approval for specific MCP calls or script files.
479
481
  - Deactivating a skill (\`deactivate_skill\`) removes its MCP tools from runtime registration.
480
482
 
481
- Pattern format is strict slash-only:
483
+ Pattern format:
482
484
 
483
- - MCP: \`server/tool\`, \`server/*\`
485
+ - MCP: \`mcp:server/tool\`, \`mcp:server/*\` (protocol-like prefix)
484
486
  - Scripts: relative paths such as \`./scripts/file.ts\`, \`./scripts/*\`, \`./tools/deploy.ts\`
485
487
 
486
488
  Skill authoring guardrails:
@@ -538,6 +540,7 @@ export default {
538
540
  },
539
541
  },
540
542
  },
543
+ // browser: true, // Enable browser automation tools (requires @poncho-ai/browser)
541
544
  // webUi: false, // Disable built-in UI for API-only deployments
542
545
  };
543
546
  \`\`\`
@@ -575,8 +578,9 @@ cron:
575
578
  \`\`\`
576
579
 
577
580
  - \`poncho dev\`: jobs run via an in-process scheduler.
578
- - \`poncho build vercel\`: generates \`vercel.json\` cron entries.
581
+ - \`poncho build vercel\`: generates \`vercel.json\` cron entries. Set \`CRON_SECRET\` to the same value as \`PONCHO_AUTH_TOKEN\` so Vercel can authenticate.
579
582
  - Docker/Fly.io: scheduler runs automatically.
583
+ - Lambda: use AWS EventBridge to trigger \`GET /api/cron/<jobName>\` with \`Authorization: Bearer <token>\`.
580
584
  - Trigger manually: \`curl http://localhost:3000/api/cron/daily-report\`
581
585
 
582
586
  ## Messaging (Slack)
@@ -597,6 +601,8 @@ Connect your agent to Slack so it responds to @mentions:
597
601
  messaging: [{ platform: 'slack' }]
598
602
  \`\`\`
599
603
 
604
+ **Vercel deployments:** install \`@vercel/functions\` so Poncho can keep the serverless function alive while processing: \`npm install @vercel/functions\`
605
+
600
606
  ## Messaging (Email via Resend)
601
607
 
602
608
  Connect your agent to email so users can interact by sending emails:
@@ -617,6 +623,8 @@ Connect your agent to email so users can interact by sending emails:
617
623
 
618
624
  For full control over outbound emails, use **tool mode** (\`mode: 'tool'\`) — the agent gets a \`send_email\` tool instead of auto-replying. See the repo README for details.
619
625
 
626
+ **Vercel deployments:** install \`@vercel/functions\` so Poncho can keep the serverless function alive while processing: \`npm install @vercel/functions\`
627
+
620
628
  ## Deployment
621
629
 
622
630
  \`\`\`bash
@@ -627,8 +635,28 @@ vercel deploy --prod
627
635
  # Build for Docker
628
636
  poncho build docker
629
637
  docker build -t ${name} .
638
+ docker run -p 3000:3000 -e ANTHROPIC_API_KEY=sk-ant-... ${name}
639
+
640
+ # AWS Lambda
641
+ poncho build lambda
642
+
643
+ # Fly.io
644
+ poncho build fly
645
+ fly deploy
630
646
  \`\`\`
631
647
 
648
+ Set environment variables on your deployment platform:
649
+
650
+ \`\`\`bash
651
+ ANTHROPIC_API_KEY=sk-ant-... # Required
652
+ PONCHO_AUTH_TOKEN=your-secret # Optional: protect your endpoint
653
+ PONCHO_MAX_DURATION=55 # Optional: serverless timeout in seconds (enables auto-continuation)
654
+ \`\`\`
655
+
656
+ When \`PONCHO_MAX_DURATION\` is set, the agent automatically checkpoints and resumes across
657
+ request cycles when it approaches the platform timeout. The web UI and client SDK handle
658
+ this transparently.
659
+
632
660
  ## Troubleshooting
633
661
 
634
662
  ### Vercel deploy issues
@@ -1855,8 +1883,8 @@ export const createRequestHandler = async (options?: {
1855
1883
  const sessionStore = new SessionStore();
1856
1884
  const loginRateLimiter = new LoginRateLimiter();
1857
1885
 
1858
- // Unified authentication using PONCHO_AUTH_TOKEN for both Web UI and API
1859
- const authToken = process.env.PONCHO_AUTH_TOKEN ?? "";
1886
+ const authTokenEnv = config?.auth?.tokenEnv ?? "PONCHO_AUTH_TOKEN";
1887
+ const authToken = process.env[authTokenEnv] ?? "";
1860
1888
  const authRequired = config?.auth?.required ?? false;
1861
1889
  const requireAuth = authRequired && authToken.length > 0;
1862
1890
 
@@ -1885,6 +1913,7 @@ export const createRequestHandler = async (options?: {
1885
1913
  return;
1886
1914
  }
1887
1915
  const [pathname] = request.url.split("?");
1916
+ const requestUrl = new URL(request.url ?? "/", `http://${request.headers.host ?? "localhost"}`);
1888
1917
 
1889
1918
  if (webUiEnabled) {
1890
1919
  if (request.method === "GET" && (pathname === "/" || pathname.startsWith("/c/"))) {
@@ -2058,6 +2087,140 @@ export const createRequestHandler = async (options?: {
2058
2087
  }
2059
2088
  }
2060
2089
 
2090
+ // --- Browser endpoints (single session, routed by conversationId) ---
2091
+
2092
+ type BrowserSessionTyped = {
2093
+ isLaunched: boolean;
2094
+ isActiveFor: (cid: string) => boolean;
2095
+ getUrl: (cid: string) => string | undefined;
2096
+ onFrame: (cid: string, cb: (f: { data: string; width: number; height: number; timestamp: number }) => void) => () => void;
2097
+ onStatus: (cid: string, cb: (s: { active: boolean; url?: string; interactionAllowed: boolean }) => void) => () => void;
2098
+ startScreencast: (cid: string, opts?: Record<string, unknown>) => Promise<void>;
2099
+ screenshot: (cid: string) => Promise<string>;
2100
+ injectMouse: (cid: string, e: { type: string; x: number; y: number; button?: string; clickCount?: number; deltaX?: number; deltaY?: number }) => Promise<void>;
2101
+ injectKeyboard: (cid: string, e: { type: string; key: string; code?: string }) => Promise<void>;
2102
+ injectScroll: (cid: string, e: { deltaX: number; deltaY: number; x?: number; y?: number }) => Promise<void>;
2103
+ injectPaste: (cid: string, text: string) => Promise<void>;
2104
+ navigate: (cid: string, action: string) => Promise<void>;
2105
+ };
2106
+
2107
+ const browserSession = harness.browserSession as BrowserSessionTyped | undefined;
2108
+
2109
+ if (pathname === "/api/browser/status" && request.method === "GET") {
2110
+ const cid = requestUrl.searchParams.get("conversationId") ?? "";
2111
+ writeJson(response, 200, {
2112
+ active: cid && browserSession ? browserSession.isActiveFor(cid) : false,
2113
+ url: cid && browserSession ? browserSession.getUrl(cid) ?? null : null,
2114
+ conversationId: cid || null,
2115
+ });
2116
+ return;
2117
+ }
2118
+
2119
+ if (pathname === "/api/browser/stream" && request.method === "GET") {
2120
+ const cid = requestUrl.searchParams.get("conversationId");
2121
+ if (!cid || !browserSession) {
2122
+ writeJson(response, 404, { error: "No browser session available" });
2123
+ return;
2124
+ }
2125
+
2126
+ response.writeHead(200, {
2127
+ "Content-Type": "text/event-stream",
2128
+ "Cache-Control": "no-cache, no-transform",
2129
+ Connection: "keep-alive",
2130
+ "X-Accel-Buffering": "no",
2131
+ });
2132
+ response.flushHeaders();
2133
+
2134
+ let frameCount = 0;
2135
+ const sendSse = (event: string, data: unknown) => {
2136
+ if (response.destroyed) return;
2137
+ response.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
2138
+ };
2139
+
2140
+ sendSse("browser:status", {
2141
+ active: browserSession.isActiveFor(cid),
2142
+ url: browserSession.getUrl(cid),
2143
+ interactionAllowed: browserSession.isActiveFor(cid),
2144
+ });
2145
+
2146
+ const removeFrame = browserSession.onFrame(cid, (frame) => {
2147
+ frameCount++;
2148
+ if (frameCount <= 3 || frameCount % 50 === 0) {
2149
+ console.log(`[poncho][browser-sse] Frame ${frameCount}: ${frame.width}x${frame.height}, data bytes: ${frame.data?.length ?? 0}`);
2150
+ }
2151
+ sendSse("browser:frame", frame);
2152
+ });
2153
+ const removeStatus = browserSession.onStatus(cid, (status) => {
2154
+ sendSse("browser:status", status);
2155
+ });
2156
+
2157
+ if (browserSession.isActiveFor(cid)) {
2158
+ // Send a screenshot as the first frame for immediate visual feedback,
2159
+ // then start the live screencast for subsequent frames.
2160
+ browserSession.screenshot(cid).then((data) => {
2161
+ if (!response.destroyed) {
2162
+ sendSse("browser:frame", { data, width: 1280, height: 720, timestamp: Date.now() });
2163
+ }
2164
+ return browserSession.startScreencast(cid);
2165
+ }).catch((err: unknown) => {
2166
+ console.error("[poncho][browser-sse] initial frame/screencast failed:", (err as Error)?.message ?? err);
2167
+ });
2168
+ }
2169
+
2170
+ request.on("close", () => {
2171
+ removeFrame();
2172
+ removeStatus();
2173
+ });
2174
+ return;
2175
+ }
2176
+
2177
+ if (pathname === "/api/browser/input" && request.method === "POST") {
2178
+ const chunks: Buffer[] = [];
2179
+ for await (const chunk of request) chunks.push(chunk as Buffer);
2180
+ const body = JSON.parse(Buffer.concat(chunks).toString());
2181
+ const cid = body.conversationId as string;
2182
+ if (!cid || !browserSession || !browserSession.isActiveFor(cid)) {
2183
+ writeJson(response, 404, { error: "No active browser session" });
2184
+ return;
2185
+ }
2186
+ try {
2187
+ if (body.kind === "mouse") {
2188
+ await browserSession.injectMouse(cid, body.event);
2189
+ } else if (body.kind === "keyboard") {
2190
+ await browserSession.injectKeyboard(cid, body.event);
2191
+ } else if (body.kind === "scroll") {
2192
+ await browserSession.injectScroll(cid, body.event);
2193
+ } else if (body.kind === "paste") {
2194
+ await browserSession.injectPaste(cid, body.text ?? body.event?.text ?? "");
2195
+ } else {
2196
+ writeJson(response, 400, { error: "Unknown input kind" });
2197
+ return;
2198
+ }
2199
+ writeJson(response, 200, { ok: true });
2200
+ } catch (err) {
2201
+ writeJson(response, 500, { error: (err as Error)?.message ?? "Input injection failed" });
2202
+ }
2203
+ return;
2204
+ }
2205
+
2206
+ if (pathname === "/api/browser/navigate" && request.method === "POST") {
2207
+ const chunks: Buffer[] = [];
2208
+ for await (const chunk of request) chunks.push(chunk as Buffer);
2209
+ const body = JSON.parse(Buffer.concat(chunks).toString());
2210
+ const cid = body.conversationId as string;
2211
+ if (!cid || !browserSession || !browserSession.isActiveFor(cid)) {
2212
+ writeJson(response, 400, { error: "No active browser session" });
2213
+ return;
2214
+ }
2215
+ try {
2216
+ await browserSession.navigate(cid, body.action);
2217
+ writeJson(response, 200, { ok: true });
2218
+ } catch (err) {
2219
+ writeJson(response, 500, { error: (err as Error)?.message ?? "Navigation failed" });
2220
+ }
2221
+ return;
2222
+ }
2223
+
2061
2224
  if (pathname === "/api/conversations" && request.method === "GET") {
2062
2225
  const conversations = await conversationStore.list(ownerId);
2063
2226
  writeJson(response, 200, {
@@ -316,8 +316,6 @@ export const buildConfigFromOnboardingAnswers = (
316
316
  maxRecallConversations,
317
317
  },
318
318
  };
319
- maybeSet(storage, "url", answers["storage.url"]);
320
- maybeSet(storage, "token", answers["storage.token"]);
321
319
  maybeSet(storage, "table", answers["storage.table"]);
322
320
  maybeSet(storage, "region", answers["storage.region"]);
323
321
 
@@ -392,8 +390,8 @@ export const isDefaultOnboardingConfig = (
392
390
  return false;
393
391
  }
394
392
  if (
395
- typeof config.storage?.url !== "undefined" ||
396
- typeof config.storage?.token !== "undefined" ||
393
+ typeof config.storage?.urlEnv !== "undefined" ||
394
+ typeof config.storage?.tokenEnv !== "undefined" ||
397
395
  typeof config.storage?.table !== "undefined" ||
398
396
  typeof config.storage?.region !== "undefined"
399
397
  ) {