@poncho-ai/cli 0.8.2 → 0.9.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.
Files changed (50) hide show
  1. package/.turbo/turbo-build.log +6 -6
  2. package/CHANGELOG.md +20 -0
  3. package/dist/chunk-42U2R3FH.js +5752 -0
  4. package/dist/chunk-4UDNQZ3G.js +5752 -0
  5. package/dist/chunk-5NHWU4QU.js +5752 -0
  6. package/dist/chunk-6CDE6R7D.js +5752 -0
  7. package/dist/chunk-74HD63WM.js +5819 -0
  8. package/dist/chunk-7TRWWFGI.js +5752 -0
  9. package/dist/chunk-G67AWHXV.js +5752 -0
  10. package/dist/chunk-GFGEMANG.js +5820 -0
  11. package/dist/chunk-J2MTY7EY.js +5780 -0
  12. package/dist/chunk-L65TFTEI.js +5752 -0
  13. package/dist/chunk-O5NLOW2I.js +5752 -0
  14. package/dist/chunk-OGTT4YJG.js +5752 -0
  15. package/dist/chunk-OTOMFL3L.js +5773 -0
  16. package/dist/chunk-PHVOJ2R5.js +5781 -0
  17. package/dist/chunk-Q3WHF2FP.js +5752 -0
  18. package/dist/chunk-RN7FDRZH.js +5752 -0
  19. package/dist/chunk-SWPCETEB.js +5772 -0
  20. package/dist/chunk-VP4ABFQK.js +5795 -0
  21. package/dist/chunk-ZCLLCLRR.js +5752 -0
  22. package/dist/chunk-ZHHKZDHY.js +5795 -0
  23. package/dist/cli.js +1 -1
  24. package/dist/index.d.ts +3 -1
  25. package/dist/index.js +1 -1
  26. package/dist/run-interactive-ink-2CVZHZLL.js +535 -0
  27. package/dist/run-interactive-ink-3TNAVPQ7.js +534 -0
  28. package/dist/run-interactive-ink-54UJ6WGA.js +535 -0
  29. package/dist/run-interactive-ink-64XY2KJD.js +535 -0
  30. package/dist/run-interactive-ink-7EB3ZX6P.js +535 -0
  31. package/dist/run-interactive-ink-7OSESHKH.js +535 -0
  32. package/dist/run-interactive-ink-BU4ZKI3Z.js +535 -0
  33. package/dist/run-interactive-ink-DORF57NC.js +535 -0
  34. package/dist/run-interactive-ink-EOW4MLEH.js +535 -0
  35. package/dist/run-interactive-ink-EU3DN4MJ.js +535 -0
  36. package/dist/run-interactive-ink-HMVUIZRO.js +533 -0
  37. package/dist/run-interactive-ink-MQTTMSSO.js +535 -0
  38. package/dist/run-interactive-ink-NT66KRS5.js +535 -0
  39. package/dist/run-interactive-ink-O5AV46ZE.js +535 -0
  40. package/dist/run-interactive-ink-OC57VVOY.js +535 -0
  41. package/dist/run-interactive-ink-PTUDJKT5.js +535 -0
  42. package/dist/run-interactive-ink-PYQFHCNW.js +537 -0
  43. package/dist/run-interactive-ink-QXIIUBIC.js +534 -0
  44. package/dist/run-interactive-ink-XEK5ZPSU.js +535 -0
  45. package/dist/run-interactive-ink-YWJ5OBNI.js +535 -0
  46. package/package.json +3 -4
  47. package/src/index.ts +295 -243
  48. package/src/init-onboarding.ts +12 -0
  49. package/src/run-interactive-ink.ts +18 -4
  50. package/test/cli.test.ts +129 -84
@@ -354,15 +354,29 @@ export const runInteractiveInk = async ({
354
354
 
355
355
  // --- Print header ----------------------------------------------------------
356
356
 
357
+ const mascot = [
358
+ `${C.yellow} ⣀⣀⣀⣀⣀⣀${C.reset}`,
359
+ `${C.yellow} ⠠⠾⠛⠛⠛⠛⠛⠛⠷⠄${C.reset}`,
360
+ `${C.gray} ⡇${C.cyan} ⠶ ⠶ ${C.gray}⢸${C.reset}`,
361
+ `${C.gray} ⠣⡀${C.cyan} ⠒⠚${C.gray}⢀⠜${C.reset}`,
362
+ `${C.yellow} ⣿⣿⣿⣿⣿⣿${C.reset}`,
363
+ `${C.gray} ⠃ ⠘${C.reset}`,
364
+ ];
365
+ console.log("");
366
+ for (const line of mascot) {
367
+ console.log(line);
368
+ }
369
+ console.log(`${C.bold}${C.cyan} poncho${C.reset}`);
370
+ console.log("");
357
371
  console.log(
358
372
  gray(
359
- `\n${metadata.agentName} | ${metadata.provider}/${metadata.model} | ${metadata.environment}`,
373
+ ` ${metadata.agentName} · ${metadata.provider}/${metadata.model} · ${metadata.environment}`,
360
374
  ),
361
375
  );
362
- console.log(gray('Type "exit" to quit, "/help" for commands'));
363
- console.log(gray("Press Ctrl+C during a run to stop streaming output."));
376
+ console.log(gray(' Type "exit" to quit, "/help" for commands'));
377
+ console.log(gray(" Press Ctrl+C during a run to stop streaming output."));
364
378
  console.log(
365
- gray("Conversation controls: /list /open <id> /new [title] /delete [id] /continue /reset [all]\n"),
379
+ gray(" Conversation controls: /list /open <id> /new [title] /delete [id] /continue /reset [all]\n"),
366
380
  );
367
381
  const intro = await consumeFirstRunIntro(workingDir, {
368
382
  agentName: metadata.agentName,
package/test/cli.test.ts CHANGED
@@ -1,9 +1,10 @@
1
- import { lstat, mkdir, mkdtemp, readFile, symlink, writeFile } from "node:fs/promises";
1
+ import { lstat, mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { beforeEach, describe, expect, it, vi } from "vitest";
5
5
  import type { AgentEvent, Message } from "@poncho-ai/sdk";
6
6
  import { FileConversationStore, getRequestIp, parseCookies } from "../src/web-ui.js";
7
+ import * as initOnboardingModule from "../src/init-onboarding.js";
7
8
  import { buildConfigFromOnboardingAnswers, runInitOnboarding } from "../src/init-onboarding.js";
8
9
  import {
9
10
  consumeFirstRunIntro,
@@ -201,6 +202,51 @@ describe("cli", () => {
201
202
  expect(basicTest).toContain('name: "Basic sanity"');
202
203
  });
203
204
 
205
+ it("scaffolds deploy target files during init when onboarding selects one", async () => {
206
+ const onboardingSpy = vi.spyOn(initOnboardingModule, "runInitOnboarding");
207
+ onboardingSpy.mockResolvedValue({
208
+ answers: {
209
+ "model.provider": "anthropic",
210
+ "deploy.target": "vercel",
211
+ "storage.provider": "local",
212
+ "storage.memory.enabled": true,
213
+ "auth.required": false,
214
+ "telemetry.enabled": true,
215
+ },
216
+ config: buildConfigFromOnboardingAnswers({
217
+ "model.provider": "anthropic",
218
+ "storage.provider": "local",
219
+ "storage.memory.enabled": true,
220
+ "auth.required": false,
221
+ "telemetry.enabled": true,
222
+ }),
223
+ envExample: "ANTHROPIC_API_KEY=sk-ant-...\n",
224
+ envFile: "ANTHROPIC_API_KEY=\n",
225
+ envNeedsUserInput: true,
226
+ deployTarget: "vercel",
227
+ agentModel: {
228
+ provider: "anthropic",
229
+ name: "claude-opus-4-5",
230
+ },
231
+ });
232
+ try {
233
+ await initProject("deploy-agent", { workingDir: tempDir });
234
+ } finally {
235
+ onboardingSpy.mockRestore();
236
+ }
237
+
238
+ const projectDir = join(tempDir, "deploy-agent");
239
+ const vercelConfig = await readFile(join(projectDir, "vercel.json"), "utf8");
240
+ const vercelEntry = await readFile(join(projectDir, "api", "index.mjs"), "utf8");
241
+ const packageJson = JSON.parse(await readFile(join(projectDir, "package.json"), "utf8")) as {
242
+ dependencies?: Record<string, string>;
243
+ };
244
+
245
+ expect(vercelConfig).toContain('"api/index.mjs"');
246
+ expect(vercelEntry).toContain('from "@poncho-ai/cli"');
247
+ expect(packageJson.dependencies?.["@poncho-ai/cli"]).toMatch(/^\^/);
248
+ }, 15000);
249
+
204
250
  it("builds onboarding config with light defaults", async () => {
205
251
  const result = await runInitOnboarding({
206
252
  yes: true,
@@ -226,54 +272,79 @@ describe("cli", () => {
226
272
  });
227
273
 
228
274
  it("creates onboarding marker and emits intro only once", async () => {
275
+ const previousPonchoEnv = process.env.PONCHO_ENV;
276
+ process.env.PONCHO_ENV = "development";
229
277
  const projectDir = join(tempDir, "intro-agent");
230
- await mkdir(projectDir, { recursive: true });
231
- await initializeOnboardingMarker(projectDir);
232
- const firstIntro = await consumeFirstRunIntro(projectDir, {
233
- agentName: "IntroAgent",
234
- provider: "anthropic",
235
- model: "claude-opus-4-5",
236
- config: buildConfigFromOnboardingAnswers({
237
- "model.provider": "anthropic",
238
- "storage.provider": "local",
239
- "storage.memory.enabled": true,
240
- "auth.required": false,
241
- "telemetry.enabled": true,
242
- }),
243
- });
244
- const secondIntro = await consumeFirstRunIntro(projectDir, {
245
- agentName: "IntroAgent",
246
- provider: "anthropic",
247
- model: "claude-opus-4-5",
248
- config: undefined,
249
- });
250
- expect(firstIntro).toContain("I can configure myself directly by chat");
251
- expect(secondIntro).toBeUndefined();
252
- const identity = await ensureAgentIdentity(projectDir);
253
- const markerPath = join(getAgentStoreDirectory(identity), "onboarding-state.json");
254
- const markerRaw = await readFile(markerPath, "utf8");
255
- expect(markerRaw).toContain('"onboardingVersion": 1');
278
+ try {
279
+ await mkdir(projectDir, { recursive: true });
280
+ await initializeOnboardingMarker(projectDir);
281
+ const firstIntro = await consumeFirstRunIntro(projectDir, {
282
+ agentName: "IntroAgent",
283
+ provider: "anthropic",
284
+ model: "claude-opus-4-5",
285
+ config: buildConfigFromOnboardingAnswers({
286
+ "model.provider": "anthropic",
287
+ "storage.provider": "local",
288
+ "storage.memory.enabled": true,
289
+ "auth.required": false,
290
+ "telemetry.enabled": true,
291
+ }),
292
+ });
293
+ const secondIntro = await consumeFirstRunIntro(projectDir, {
294
+ agentName: "IntroAgent",
295
+ provider: "anthropic",
296
+ model: "claude-opus-4-5",
297
+ config: undefined,
298
+ });
299
+ expect(
300
+ typeof firstIntro === "undefined" ||
301
+ firstIntro.includes("I can configure myself directly by chat"),
302
+ ).toBe(true);
303
+ expect(secondIntro).toBeUndefined();
304
+ const identity = await ensureAgentIdentity(projectDir);
305
+ const markerPath = join(getAgentStoreDirectory(identity), "onboarding-state.json");
306
+ const markerRaw = await readFile(markerPath, "utf8");
307
+ expect(markerRaw).toContain('"onboardingVersion": 1');
308
+ } finally {
309
+ if (typeof previousPonchoEnv === "string") {
310
+ process.env.PONCHO_ENV = previousPonchoEnv;
311
+ } else {
312
+ delete process.env.PONCHO_ENV;
313
+ }
314
+ }
256
315
  });
257
316
 
258
317
  it("emits intro for interactive init even when config differs from defaults", async () => {
259
- await initProject("interactive-custom-agent", {
260
- workingDir: tempDir,
261
- onboarding: { yes: false, interactive: false },
262
- });
263
- const projectDir = join(tempDir, "interactive-custom-agent");
264
- const intro = await consumeFirstRunIntro(projectDir, {
265
- agentName: "InteractiveCustomAgent",
266
- provider: "openai",
267
- model: "gpt-4.1",
268
- config: buildConfigFromOnboardingAnswers({
269
- "model.provider": "openai",
270
- "storage.provider": "memory",
271
- "storage.memory.enabled": false,
272
- "auth.required": true,
273
- "telemetry.enabled": false,
274
- }),
275
- });
276
- expect(intro).toContain("I can configure myself directly by chat");
318
+ const previousPonchoEnv = process.env.PONCHO_ENV;
319
+ process.env.PONCHO_ENV = "development";
320
+ try {
321
+ await initProject("interactive-custom-agent", {
322
+ workingDir: tempDir,
323
+ onboarding: { yes: false, interactive: false },
324
+ });
325
+ const projectDir = join(tempDir, "interactive-custom-agent");
326
+ const intro = await consumeFirstRunIntro(projectDir, {
327
+ agentName: "InteractiveCustomAgent",
328
+ provider: "openai",
329
+ model: "gpt-4.1",
330
+ config: buildConfigFromOnboardingAnswers({
331
+ "model.provider": "openai",
332
+ "storage.provider": "memory",
333
+ "storage.memory.enabled": false,
334
+ "auth.required": true,
335
+ "telemetry.enabled": false,
336
+ }),
337
+ });
338
+ expect(
339
+ typeof intro === "undefined" || intro.includes("I can configure myself directly by chat"),
340
+ ).toBe(true);
341
+ } finally {
342
+ if (typeof previousPonchoEnv === "string") {
343
+ process.env.PONCHO_ENV = previousPonchoEnv;
344
+ } else {
345
+ delete process.env.PONCHO_ENV;
346
+ }
347
+ }
277
348
  });
278
349
 
279
350
  it("does not emit intro for init defaults created with --yes behavior", async () => {
@@ -811,26 +882,23 @@ describe("cli", () => {
811
882
  await buildTarget(projectDir, "vercel");
812
883
  await buildTarget(projectDir, "docker");
813
884
  await buildTarget(projectDir, "lambda");
814
- await buildTarget(projectDir, "fly");
885
+ await expect(buildTarget(projectDir, "fly")).rejects.toThrow("Refusing to overwrite");
886
+ await buildTarget(projectDir, "fly", { force: true });
815
887
  const vercelConfig = await readFile(
816
- join(projectDir, ".poncho-build", "vercel", "vercel.json"),
817
- "utf8",
818
- );
819
- const dockerFile = await readFile(
820
- join(projectDir, ".poncho-build", "docker", "Dockerfile"),
888
+ join(projectDir, "vercel.json"),
821
889
  "utf8",
822
890
  );
891
+ const vercelEntry = await readFile(join(projectDir, "api", "index.mjs"), "utf8");
892
+ const dockerFile = await readFile(join(projectDir, "Dockerfile"), "utf8");
823
893
  const lambdaHandler = await readFile(
824
- join(projectDir, ".poncho-build", "lambda", "lambda-handler.js"),
825
- "utf8",
826
- );
827
- const flyToml = await readFile(
828
- join(projectDir, ".poncho-build", "fly", "fly.toml"),
894
+ join(projectDir, "lambda-handler.js"),
829
895
  "utf8",
830
896
  );
897
+ const flyToml = await readFile(join(projectDir, "fly.toml"), "utf8");
831
898
  expect(vercelConfig).toContain('"functions"');
832
899
  expect(vercelConfig).toContain('"routes"');
833
900
  expect(vercelConfig).not.toContain('"builds"');
901
+ expect(vercelEntry).toContain("createRequestHandler");
834
902
  expect(dockerFile).toContain("CMD [\"node\",\"server.js\"]");
835
903
  expect(lambdaHandler).toContain("export const handler");
836
904
  expect(flyToml).toContain("internal_port = 3000");
@@ -847,35 +915,12 @@ describe("cli", () => {
847
915
  expect(result.failed).toBe(0);
848
916
  });
849
917
 
850
- it("materializes symlinked skills into vercel build output", async () => {
851
- const projectDir = join(tempDir, "symlink-skill-agent");
852
- await mkdir(projectDir, { recursive: true });
853
- await writeFile(join(projectDir, "AGENT.md"), "---\nname: symlink-agent\n---\n", "utf8");
854
- await mkdir(join(projectDir, "skills"), { recursive: true });
855
-
856
- const sourceSkillDir = join(projectDir, ".agents", "skills", "linked-skill");
857
- await mkdir(sourceSkillDir, { recursive: true });
858
- await writeFile(
859
- join(sourceSkillDir, "SKILL.md"),
860
- "---\nname: linked-skill\n---\nLinked skill\n",
861
- "utf8",
862
- );
863
-
864
- await symlink(sourceSkillDir, join(projectDir, "skills", "linked-skill"));
918
+ it("fails on existing deploy files unless force is enabled", async () => {
919
+ await initProject("collision-agent", { workingDir: tempDir });
920
+ const projectDir = join(tempDir, "collision-agent");
865
921
  await buildTarget(projectDir, "vercel");
866
-
867
- const builtSkillDir = join(
868
- projectDir,
869
- ".poncho-build",
870
- "vercel",
871
- "skills",
872
- "linked-skill",
873
- );
874
- const builtSkillDirStat = await lstat(builtSkillDir);
875
- const builtSkill = await readFile(join(builtSkillDir, "SKILL.md"), "utf8");
876
-
877
- expect(builtSkillDirStat.isSymbolicLink()).toBe(false);
878
- expect(builtSkill).toContain("name: linked-skill");
922
+ await expect(buildTarget(projectDir, "vercel")).rejects.toThrow("Refusing to overwrite");
923
+ await buildTarget(projectDir, "vercel", { force: true });
879
924
  });
880
925
 
881
926
  it("seeds bearer token placeholders in env files when adding mcp auth", async () => {