@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/.turbo/turbo-build.log +6 -6
- package/CHANGELOG.md +26 -0
- package/dist/{chunk-AIEVSNGF.js → chunk-XT5HPFIW.js} +1025 -353
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{run-interactive-ink-7ULE5JJI.js → run-interactive-ink-2JQJDP7W.js} +1 -1
- package/package.json +4 -4
- package/src/index.ts +168 -5
- package/src/init-onboarding.ts +2 -4
- package/src/web-ui-client.ts +2605 -0
- package/src/web-ui-store.ts +340 -0
- package/src/web-ui-styles.ts +1340 -0
- package/src/web-ui.ts +64 -3809
- package/test/init-onboarding.contract.test.ts +2 -3
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poncho-ai/cli",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
31
|
-
"@poncho-ai/messaging": "0.2.
|
|
32
|
-
"@poncho-ai/sdk": "1.0
|
|
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
|
|
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
|
-
|
|
1859
|
-
const authToken = process.env
|
|
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, {
|
package/src/init-onboarding.ts
CHANGED
|
@@ -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?.
|
|
396
|
-
typeof config.storage?.
|
|
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
|
) {
|