@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.
- package/.turbo/turbo-build.log +6 -6
- package/CHANGELOG.md +20 -0
- package/dist/chunk-42U2R3FH.js +5752 -0
- package/dist/chunk-4UDNQZ3G.js +5752 -0
- package/dist/chunk-5NHWU4QU.js +5752 -0
- package/dist/chunk-6CDE6R7D.js +5752 -0
- package/dist/chunk-74HD63WM.js +5819 -0
- package/dist/chunk-7TRWWFGI.js +5752 -0
- package/dist/chunk-G67AWHXV.js +5752 -0
- package/dist/chunk-GFGEMANG.js +5820 -0
- package/dist/chunk-J2MTY7EY.js +5780 -0
- package/dist/chunk-L65TFTEI.js +5752 -0
- package/dist/chunk-O5NLOW2I.js +5752 -0
- package/dist/chunk-OGTT4YJG.js +5752 -0
- package/dist/chunk-OTOMFL3L.js +5773 -0
- package/dist/chunk-PHVOJ2R5.js +5781 -0
- package/dist/chunk-Q3WHF2FP.js +5752 -0
- package/dist/chunk-RN7FDRZH.js +5752 -0
- package/dist/chunk-SWPCETEB.js +5772 -0
- package/dist/chunk-VP4ABFQK.js +5795 -0
- package/dist/chunk-ZCLLCLRR.js +5752 -0
- package/dist/chunk-ZHHKZDHY.js +5795 -0
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -1
- package/dist/run-interactive-ink-2CVZHZLL.js +535 -0
- package/dist/run-interactive-ink-3TNAVPQ7.js +534 -0
- package/dist/run-interactive-ink-54UJ6WGA.js +535 -0
- package/dist/run-interactive-ink-64XY2KJD.js +535 -0
- package/dist/run-interactive-ink-7EB3ZX6P.js +535 -0
- package/dist/run-interactive-ink-7OSESHKH.js +535 -0
- package/dist/run-interactive-ink-BU4ZKI3Z.js +535 -0
- package/dist/run-interactive-ink-DORF57NC.js +535 -0
- package/dist/run-interactive-ink-EOW4MLEH.js +535 -0
- package/dist/run-interactive-ink-EU3DN4MJ.js +535 -0
- package/dist/run-interactive-ink-HMVUIZRO.js +533 -0
- package/dist/run-interactive-ink-MQTTMSSO.js +535 -0
- package/dist/run-interactive-ink-NT66KRS5.js +535 -0
- package/dist/run-interactive-ink-O5AV46ZE.js +535 -0
- package/dist/run-interactive-ink-OC57VVOY.js +535 -0
- package/dist/run-interactive-ink-PTUDJKT5.js +535 -0
- package/dist/run-interactive-ink-PYQFHCNW.js +537 -0
- package/dist/run-interactive-ink-QXIIUBIC.js +534 -0
- package/dist/run-interactive-ink-XEK5ZPSU.js +535 -0
- package/dist/run-interactive-ink-YWJ5OBNI.js +535 -0
- package/package.json +3 -4
- package/src/index.ts +295 -243
- package/src/init-onboarding.ts +12 -0
- package/src/run-interactive-ink.ts +18 -4
- package/test/cli.test.ts +129 -84
package/src/index.ts
CHANGED
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
import { createInterface } from "node:readline/promises";
|
|
43
43
|
import {
|
|
44
44
|
runInitOnboarding,
|
|
45
|
+
type DeployTarget,
|
|
45
46
|
type InitOnboardingOptions,
|
|
46
47
|
} from "./init-onboarding.js";
|
|
47
48
|
import {
|
|
@@ -478,10 +479,14 @@ ${name}/
|
|
|
478
479
|
├── tests/
|
|
479
480
|
│ └── basic.yaml # Test suite
|
|
480
481
|
└── skills/
|
|
481
|
-
|
|
482
|
+
├── starter/
|
|
483
|
+
│ ├── SKILL.md
|
|
484
|
+
│ └── scripts/
|
|
485
|
+
│ └── starter-echo.ts
|
|
486
|
+
└── fetch-page/
|
|
482
487
|
├── SKILL.md
|
|
483
488
|
└── scripts/
|
|
484
|
-
└──
|
|
489
|
+
└── fetch-page.ts
|
|
485
490
|
\`\`\`
|
|
486
491
|
|
|
487
492
|
## Deployment
|
|
@@ -489,7 +494,7 @@ ${name}/
|
|
|
489
494
|
\`\`\`bash
|
|
490
495
|
# Build for Vercel
|
|
491
496
|
poncho build vercel
|
|
492
|
-
|
|
497
|
+
vercel deploy --prod
|
|
493
498
|
|
|
494
499
|
# Build for Docker
|
|
495
500
|
poncho build docker
|
|
@@ -502,19 +507,7 @@ https://github.com/cesr/poncho-ai
|
|
|
502
507
|
|
|
503
508
|
const ENV_TEMPLATE = "ANTHROPIC_API_KEY=sk-ant-...\n";
|
|
504
509
|
const GITIGNORE_TEMPLATE =
|
|
505
|
-
".env\nnode_modules\ndist\n.poncho
|
|
506
|
-
const VERCEL_RUNTIME_DEPENDENCIES: Record<string, string> = {
|
|
507
|
-
"@anthropic-ai/sdk": "^0.74.0",
|
|
508
|
-
"@aws-sdk/client-dynamodb": "^3.988.0",
|
|
509
|
-
"@latitude-data/telemetry": "^2.0.2",
|
|
510
|
-
commander: "^12.0.0",
|
|
511
|
-
dotenv: "^16.4.0",
|
|
512
|
-
jiti: "^2.6.1",
|
|
513
|
-
mustache: "^4.2.0",
|
|
514
|
-
openai: "^6.3.0",
|
|
515
|
-
redis: "^5.10.0",
|
|
516
|
-
yaml: "^2.8.1",
|
|
517
|
-
};
|
|
510
|
+
".env\nnode_modules\ndist\n.poncho/\ninteractive-session.json\n.vercel\n";
|
|
518
511
|
const TEST_TEMPLATE = `tests:
|
|
519
512
|
- name: "Basic sanity"
|
|
520
513
|
task: "What is 2 + 2?"
|
|
@@ -545,40 +538,174 @@ const SKILL_TOOL_TEMPLATE = `export default async function run(input) {
|
|
|
545
538
|
}
|
|
546
539
|
`;
|
|
547
540
|
|
|
541
|
+
const FETCH_PAGE_SKILL_TEMPLATE = `---
|
|
542
|
+
name: fetch-page
|
|
543
|
+
description: Fetch a web page and return its text content
|
|
544
|
+
allowed-tools:
|
|
545
|
+
- ./scripts/fetch-page.ts
|
|
546
|
+
---
|
|
547
|
+
|
|
548
|
+
# Fetch Page
|
|
549
|
+
|
|
550
|
+
Fetches a URL and returns the page body as plain text (HTML tags stripped).
|
|
551
|
+
|
|
552
|
+
## Usage
|
|
553
|
+
|
|
554
|
+
Call \`run_skill_script\` with:
|
|
555
|
+
- **skill**: \`fetch-page\`
|
|
556
|
+
- **script**: \`./scripts/fetch-page.ts\`
|
|
557
|
+
- **input**: \`{ "url": "https://example.com" }\`
|
|
558
|
+
|
|
559
|
+
The script returns \`{ url, status, content }\` where \`content\` is the
|
|
560
|
+
text-only body (capped at ~32 000 chars to stay context-friendly).
|
|
561
|
+
`;
|
|
562
|
+
|
|
563
|
+
const FETCH_PAGE_SCRIPT_TEMPLATE = `export default async function run(input) {
|
|
564
|
+
const url = typeof input?.url === "string" ? input.url.trim() : "";
|
|
565
|
+
if (!url) {
|
|
566
|
+
return { error: "A \\"url\\" string is required." };
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const MAX_LENGTH = 32_000;
|
|
570
|
+
|
|
571
|
+
const response = await fetch(url, {
|
|
572
|
+
headers: { "User-Agent": "poncho-fetch-page/1.0" },
|
|
573
|
+
redirect: "follow",
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
if (!response.ok) {
|
|
577
|
+
return { url, status: response.status, error: response.statusText };
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const html = await response.text();
|
|
581
|
+
|
|
582
|
+
// Lightweight HTML-to-text: strip tags, collapse whitespace.
|
|
583
|
+
const text = html
|
|
584
|
+
.replace(/<script[\\s\\S]*?<\\/script>/gi, "")
|
|
585
|
+
.replace(/<style[\\s\\S]*?<\\/style>/gi, "")
|
|
586
|
+
.replace(/<[^>]+>/g, " ")
|
|
587
|
+
.replace(/ /gi, " ")
|
|
588
|
+
.replace(/&/gi, "&")
|
|
589
|
+
.replace(/</gi, "<")
|
|
590
|
+
.replace(/>/gi, ">")
|
|
591
|
+
.replace(/"/gi, '"')
|
|
592
|
+
.replace(/'/gi, "'")
|
|
593
|
+
.replace(/\\s+/g, " ")
|
|
594
|
+
.trim();
|
|
595
|
+
|
|
596
|
+
const content = text.length > MAX_LENGTH
|
|
597
|
+
? text.slice(0, MAX_LENGTH) + "… (truncated)"
|
|
598
|
+
: text;
|
|
599
|
+
|
|
600
|
+
return { url, status: response.status, content };
|
|
601
|
+
}
|
|
602
|
+
`;
|
|
603
|
+
|
|
548
604
|
const ensureFile = async (path: string, content: string): Promise<void> => {
|
|
549
605
|
await mkdir(dirname(path), { recursive: true });
|
|
550
606
|
await writeFile(path, content, { encoding: "utf8", flag: "wx" });
|
|
551
607
|
};
|
|
552
608
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
609
|
+
type DeployScaffoldTarget = Exclude<DeployTarget, "none">;
|
|
610
|
+
|
|
611
|
+
const normalizeDeployTarget = (target: string): DeployScaffoldTarget => {
|
|
612
|
+
const normalized = target.toLowerCase();
|
|
613
|
+
if (
|
|
614
|
+
normalized === "vercel" ||
|
|
615
|
+
normalized === "docker" ||
|
|
616
|
+
normalized === "lambda" ||
|
|
617
|
+
normalized === "fly"
|
|
618
|
+
) {
|
|
619
|
+
return normalized;
|
|
558
620
|
}
|
|
559
|
-
|
|
560
|
-
// Build outputs should contain materialized files, not symlinks to paths
|
|
561
|
-
// that may not exist inside deployment artifacts (e.g. .agents/skills/*).
|
|
562
|
-
await cp(sourcePath, destinationPath, { recursive: true, dereference: true });
|
|
621
|
+
throw new Error(`Unsupported build target: ${target}`);
|
|
563
622
|
};
|
|
564
623
|
|
|
565
|
-
const
|
|
566
|
-
const
|
|
624
|
+
const readCliVersion = async (): Promise<string> => {
|
|
625
|
+
const fallback = "0.1.0";
|
|
567
626
|
try {
|
|
568
|
-
|
|
569
|
-
|
|
627
|
+
const packageJsonPath = resolve(packageRoot, "package.json");
|
|
628
|
+
const content = await readFile(packageJsonPath, "utf8");
|
|
629
|
+
const parsed = JSON.parse(content) as { version?: unknown };
|
|
630
|
+
if (typeof parsed.version === "string" && parsed.version.trim().length > 0) {
|
|
631
|
+
return parsed.version;
|
|
632
|
+
}
|
|
570
633
|
} catch {
|
|
571
|
-
|
|
634
|
+
// Use fallback when package metadata cannot be read.
|
|
635
|
+
}
|
|
636
|
+
return fallback;
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
const writeScaffoldFile = async (
|
|
640
|
+
filePath: string,
|
|
641
|
+
content: string,
|
|
642
|
+
options: { force?: boolean; writtenPaths: string[]; baseDir: string },
|
|
643
|
+
): Promise<void> => {
|
|
644
|
+
if (!options.force) {
|
|
645
|
+
try {
|
|
646
|
+
await access(filePath);
|
|
647
|
+
throw new Error(
|
|
648
|
+
`Refusing to overwrite existing file: ${relative(options.baseDir, filePath)}. Re-run with --force to overwrite.`,
|
|
649
|
+
);
|
|
650
|
+
} catch (error) {
|
|
651
|
+
if (!(error instanceof Error) || !error.message.includes("Refusing to overwrite")) {
|
|
652
|
+
// File does not exist, safe to continue.
|
|
653
|
+
} else {
|
|
654
|
+
throw error;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
572
657
|
}
|
|
658
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
659
|
+
await writeFile(filePath, content, "utf8");
|
|
660
|
+
options.writtenPaths.push(relative(options.baseDir, filePath));
|
|
573
661
|
};
|
|
574
662
|
|
|
575
|
-
const
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
663
|
+
const ensureRuntimeCliDependency = async (
|
|
664
|
+
projectDir: string,
|
|
665
|
+
cliVersion: string,
|
|
666
|
+
): Promise<string[]> => {
|
|
667
|
+
const packageJsonPath = resolve(projectDir, "package.json");
|
|
668
|
+
const content = await readFile(packageJsonPath, "utf8");
|
|
669
|
+
const parsed = JSON.parse(content) as {
|
|
670
|
+
dependencies?: Record<string, string>;
|
|
671
|
+
devDependencies?: Record<string, string>;
|
|
672
|
+
};
|
|
673
|
+
const dependencies = { ...(parsed.dependencies ?? {}) };
|
|
674
|
+
const isLocalOnlySpecifier = (value: string | undefined): boolean =>
|
|
675
|
+
typeof value === "string" &&
|
|
676
|
+
(value.startsWith("link:") || value.startsWith("workspace:") || value.startsWith("file:"));
|
|
677
|
+
|
|
678
|
+
// Deployment projects should not depend on local monorepo paths.
|
|
679
|
+
if (isLocalOnlySpecifier(dependencies["@poncho-ai/harness"])) {
|
|
680
|
+
delete dependencies["@poncho-ai/harness"];
|
|
681
|
+
}
|
|
682
|
+
if (isLocalOnlySpecifier(dependencies["@poncho-ai/sdk"])) {
|
|
683
|
+
delete dependencies["@poncho-ai/sdk"];
|
|
684
|
+
}
|
|
685
|
+
dependencies["@poncho-ai/cli"] = `^${cliVersion}`;
|
|
686
|
+
parsed.dependencies = dependencies;
|
|
687
|
+
await writeFile(packageJsonPath, `${JSON.stringify(parsed, null, 2)}\n`, "utf8");
|
|
688
|
+
return [relative(projectDir, packageJsonPath)];
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
const scaffoldDeployTarget = async (
|
|
692
|
+
projectDir: string,
|
|
693
|
+
target: DeployScaffoldTarget,
|
|
694
|
+
options?: { force?: boolean },
|
|
695
|
+
): Promise<string[]> => {
|
|
696
|
+
const writtenPaths: string[] = [];
|
|
697
|
+
const cliVersion = await readCliVersion();
|
|
698
|
+
const sharedServerEntrypoint = `import { startDevServer } from "@poncho-ai/cli";
|
|
699
|
+
|
|
700
|
+
const port = Number.parseInt(process.env.PORT ?? "3000", 10);
|
|
701
|
+
await startDevServer(Number.isNaN(port) ? 3000 : port, { workingDir: process.cwd() });
|
|
702
|
+
`;
|
|
703
|
+
|
|
704
|
+
if (target === "vercel") {
|
|
705
|
+
const entryPath = resolve(projectDir, "api", "index.mjs");
|
|
706
|
+
await writeScaffoldFile(
|
|
707
|
+
entryPath,
|
|
708
|
+
`import { createRequestHandler } from "@poncho-ai/cli";
|
|
582
709
|
let handlerPromise;
|
|
583
710
|
export default async function handler(req, res) {
|
|
584
711
|
try {
|
|
@@ -596,68 +723,114 @@ export default async function handler(req, res) {
|
|
|
596
723
|
}
|
|
597
724
|
}
|
|
598
725
|
`,
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
"
|
|
646
|
-
"
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
"node:assert",
|
|
656
|
-
"node:buffer",
|
|
657
|
-
"node:timers",
|
|
658
|
-
"node:timers/promises",
|
|
659
|
-
],
|
|
726
|
+
{ force: options?.force, writtenPaths, baseDir: projectDir },
|
|
727
|
+
);
|
|
728
|
+
const vercelConfigPath = resolve(projectDir, "vercel.json");
|
|
729
|
+
await writeScaffoldFile(
|
|
730
|
+
vercelConfigPath,
|
|
731
|
+
`${JSON.stringify(
|
|
732
|
+
{
|
|
733
|
+
version: 2,
|
|
734
|
+
functions: {
|
|
735
|
+
"api/index.mjs": {
|
|
736
|
+
includeFiles: "{AGENT.md,poncho.config.js,skills/**,tests/**}",
|
|
737
|
+
},
|
|
738
|
+
},
|
|
739
|
+
routes: [{ src: "/(.*)", dest: "/api/index.mjs" }],
|
|
740
|
+
},
|
|
741
|
+
null,
|
|
742
|
+
2,
|
|
743
|
+
)}\n`,
|
|
744
|
+
{ force: options?.force, writtenPaths, baseDir: projectDir },
|
|
745
|
+
);
|
|
746
|
+
} else if (target === "docker") {
|
|
747
|
+
const dockerfilePath = resolve(projectDir, "Dockerfile");
|
|
748
|
+
await writeScaffoldFile(
|
|
749
|
+
dockerfilePath,
|
|
750
|
+
`FROM node:20-slim
|
|
751
|
+
WORKDIR /app
|
|
752
|
+
COPY package.json package.json
|
|
753
|
+
COPY AGENT.md AGENT.md
|
|
754
|
+
COPY poncho.config.js poncho.config.js
|
|
755
|
+
COPY skills skills
|
|
756
|
+
COPY tests tests
|
|
757
|
+
COPY .env.example .env.example
|
|
758
|
+
RUN corepack enable && npm install -g @poncho-ai/cli@^${cliVersion}
|
|
759
|
+
COPY server.js server.js
|
|
760
|
+
EXPOSE 3000
|
|
761
|
+
CMD ["node","server.js"]
|
|
762
|
+
`,
|
|
763
|
+
{ force: options?.force, writtenPaths, baseDir: projectDir },
|
|
764
|
+
);
|
|
765
|
+
await writeScaffoldFile(resolve(projectDir, "server.js"), sharedServerEntrypoint, {
|
|
766
|
+
force: options?.force,
|
|
767
|
+
writtenPaths,
|
|
768
|
+
baseDir: projectDir,
|
|
769
|
+
});
|
|
770
|
+
} else if (target === "lambda") {
|
|
771
|
+
await writeScaffoldFile(
|
|
772
|
+
resolve(projectDir, "lambda-handler.js"),
|
|
773
|
+
`import { startDevServer } from "@poncho-ai/cli";
|
|
774
|
+
let serverPromise;
|
|
775
|
+
export const handler = async (event = {}) => {
|
|
776
|
+
if (!serverPromise) {
|
|
777
|
+
serverPromise = startDevServer(0, { workingDir: process.cwd() });
|
|
778
|
+
}
|
|
779
|
+
const body = JSON.stringify({
|
|
780
|
+
status: "ready",
|
|
781
|
+
route: event.rawPath ?? event.path ?? "/",
|
|
660
782
|
});
|
|
783
|
+
return { statusCode: 200, headers: { "content-type": "application/json" }, body };
|
|
784
|
+
};
|
|
785
|
+
`,
|
|
786
|
+
{ force: options?.force, writtenPaths, baseDir: projectDir },
|
|
787
|
+
);
|
|
788
|
+
} else if (target === "fly") {
|
|
789
|
+
await writeScaffoldFile(
|
|
790
|
+
resolve(projectDir, "fly.toml"),
|
|
791
|
+
`app = "poncho-app"
|
|
792
|
+
[env]
|
|
793
|
+
PORT = "3000"
|
|
794
|
+
[http_service]
|
|
795
|
+
internal_port = 3000
|
|
796
|
+
force_https = true
|
|
797
|
+
auto_start_machines = true
|
|
798
|
+
auto_stop_machines = "stop"
|
|
799
|
+
min_machines_running = 0
|
|
800
|
+
`,
|
|
801
|
+
{ force: options?.force, writtenPaths, baseDir: projectDir },
|
|
802
|
+
);
|
|
803
|
+
await writeScaffoldFile(
|
|
804
|
+
resolve(projectDir, "Dockerfile"),
|
|
805
|
+
`FROM node:20-slim
|
|
806
|
+
WORKDIR /app
|
|
807
|
+
COPY package.json package.json
|
|
808
|
+
COPY AGENT.md AGENT.md
|
|
809
|
+
COPY poncho.config.js poncho.config.js
|
|
810
|
+
COPY skills skills
|
|
811
|
+
COPY tests tests
|
|
812
|
+
RUN npm install -g @poncho-ai/cli@^${cliVersion}
|
|
813
|
+
COPY server.js server.js
|
|
814
|
+
EXPOSE 3000
|
|
815
|
+
CMD ["node","server.js"]
|
|
816
|
+
`,
|
|
817
|
+
{ force: options?.force, writtenPaths, baseDir: projectDir },
|
|
818
|
+
);
|
|
819
|
+
await writeScaffoldFile(resolve(projectDir, "server.js"), sharedServerEntrypoint, {
|
|
820
|
+
force: options?.force,
|
|
821
|
+
writtenPaths,
|
|
822
|
+
baseDir: projectDir,
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
const packagePaths = await ensureRuntimeCliDependency(projectDir, cliVersion);
|
|
827
|
+
for (const path of packagePaths) {
|
|
828
|
+
if (!writtenPaths.includes(path)) {
|
|
829
|
+
writtenPaths.push(path);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
return writtenPaths;
|
|
661
834
|
};
|
|
662
835
|
|
|
663
836
|
const renderConfigFile = (config: PonchoConfig): string =>
|
|
@@ -765,6 +938,8 @@ export const initProject = async (
|
|
|
765
938
|
{ path: "tests/basic.yaml", content: TEST_TEMPLATE },
|
|
766
939
|
{ path: "skills/starter/SKILL.md", content: SKILL_TEMPLATE },
|
|
767
940
|
{ path: "skills/starter/scripts/starter-echo.ts", content: SKILL_TOOL_TEMPLATE },
|
|
941
|
+
{ path: "skills/fetch-page/SKILL.md", content: FETCH_PAGE_SKILL_TEMPLATE },
|
|
942
|
+
{ path: "skills/fetch-page/scripts/fetch-page.ts", content: FETCH_PAGE_SCRIPT_TEMPLATE },
|
|
768
943
|
];
|
|
769
944
|
if (onboarding.envFile) {
|
|
770
945
|
scaffoldFiles.push({ path: ".env", content: onboarding.envFile });
|
|
@@ -775,6 +950,13 @@ export const initProject = async (
|
|
|
775
950
|
process.stdout.write(` ${D}+${R} ${D}${file.path}${R}\n`);
|
|
776
951
|
}
|
|
777
952
|
|
|
953
|
+
if (onboarding.deployTarget !== "none") {
|
|
954
|
+
const deployFiles = await scaffoldDeployTarget(projectDir, onboarding.deployTarget);
|
|
955
|
+
for (const filePath of deployFiles) {
|
|
956
|
+
process.stdout.write(` ${D}+${R} ${D}${filePath}${R}\n`);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
778
960
|
await initializeOnboardingMarker(projectDir, {
|
|
779
961
|
allowIntro: !(onboardingOptions.yes ?? false),
|
|
780
962
|
});
|
|
@@ -2346,150 +2528,19 @@ export const runTests = async (
|
|
|
2346
2528
|
return { passed, failed };
|
|
2347
2529
|
};
|
|
2348
2530
|
|
|
2349
|
-
export const buildTarget = async (
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
const
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
const runtimePackageJson = JSON.stringify(
|
|
2358
|
-
{
|
|
2359
|
-
name: "poncho-runtime-bundle",
|
|
2360
|
-
private: true,
|
|
2361
|
-
type: "module",
|
|
2362
|
-
scripts: {
|
|
2363
|
-
start: "node server.js",
|
|
2364
|
-
},
|
|
2365
|
-
dependencies: {
|
|
2366
|
-
"@poncho-ai/cli": "^0.1.0",
|
|
2367
|
-
},
|
|
2368
|
-
},
|
|
2369
|
-
null,
|
|
2370
|
-
2,
|
|
2371
|
-
);
|
|
2372
|
-
|
|
2373
|
-
if (target === "vercel") {
|
|
2374
|
-
await mkdir(resolve(outDir, "api"), { recursive: true });
|
|
2375
|
-
await copyIfExists(resolve(workingDir, "AGENT.md"), resolve(outDir, "AGENT.md"));
|
|
2376
|
-
await copyIfExists(
|
|
2377
|
-
resolve(workingDir, "poncho.config.js"),
|
|
2378
|
-
resolve(outDir, "poncho.config.js"),
|
|
2379
|
-
);
|
|
2380
|
-
await copyIfExists(resolve(workingDir, "skills"), resolve(outDir, "skills"));
|
|
2381
|
-
await copyIfExists(resolve(workingDir, "tests"), resolve(outDir, "tests"));
|
|
2382
|
-
await writeFile(
|
|
2383
|
-
resolve(outDir, "vercel.json"),
|
|
2384
|
-
JSON.stringify(
|
|
2385
|
-
{
|
|
2386
|
-
version: 2,
|
|
2387
|
-
functions: {
|
|
2388
|
-
"api/index.js": {
|
|
2389
|
-
includeFiles: "{AGENT.md,poncho.config.js,skills/**,tests/**}",
|
|
2390
|
-
},
|
|
2391
|
-
},
|
|
2392
|
-
routes: [{ src: "/(.*)", dest: "/api/index.js" }],
|
|
2393
|
-
},
|
|
2394
|
-
null,
|
|
2395
|
-
2,
|
|
2396
|
-
),
|
|
2397
|
-
"utf8",
|
|
2398
|
-
);
|
|
2399
|
-
await buildVercelHandlerBundle(outDir);
|
|
2400
|
-
await writeFile(
|
|
2401
|
-
resolve(outDir, "package.json"),
|
|
2402
|
-
JSON.stringify(
|
|
2403
|
-
{
|
|
2404
|
-
private: true,
|
|
2405
|
-
type: "module",
|
|
2406
|
-
engines: {
|
|
2407
|
-
node: "20.x",
|
|
2408
|
-
},
|
|
2409
|
-
dependencies: VERCEL_RUNTIME_DEPENDENCIES,
|
|
2410
|
-
},
|
|
2411
|
-
null,
|
|
2412
|
-
2,
|
|
2413
|
-
),
|
|
2414
|
-
"utf8",
|
|
2415
|
-
);
|
|
2416
|
-
} else if (target === "docker") {
|
|
2417
|
-
await writeFile(
|
|
2418
|
-
resolve(outDir, "Dockerfile"),
|
|
2419
|
-
`FROM node:20-slim
|
|
2420
|
-
WORKDIR /app
|
|
2421
|
-
COPY package.json package.json
|
|
2422
|
-
COPY AGENT.md AGENT.md
|
|
2423
|
-
COPY poncho.config.js poncho.config.js
|
|
2424
|
-
COPY skills skills
|
|
2425
|
-
COPY tests tests
|
|
2426
|
-
COPY .env.example .env.example
|
|
2427
|
-
RUN corepack enable && npm install -g @poncho-ai/cli
|
|
2428
|
-
COPY server.js server.js
|
|
2429
|
-
EXPOSE 3000
|
|
2430
|
-
CMD ["node","server.js"]
|
|
2431
|
-
`,
|
|
2432
|
-
"utf8",
|
|
2433
|
-
);
|
|
2434
|
-
await writeFile(resolve(outDir, "server.js"), serverEntrypoint, "utf8");
|
|
2435
|
-
await writeFile(resolve(outDir, "package.json"), runtimePackageJson, "utf8");
|
|
2436
|
-
} else if (target === "lambda") {
|
|
2437
|
-
await writeFile(
|
|
2438
|
-
resolve(outDir, "lambda-handler.js"),
|
|
2439
|
-
`import { startDevServer } from "@poncho-ai/cli";
|
|
2440
|
-
let serverPromise;
|
|
2441
|
-
export const handler = async (event = {}) => {
|
|
2442
|
-
if (!serverPromise) {
|
|
2443
|
-
serverPromise = startDevServer(0, { workingDir: process.cwd() });
|
|
2444
|
-
}
|
|
2445
|
-
const body = JSON.stringify({
|
|
2446
|
-
status: "ready",
|
|
2447
|
-
route: event.rawPath ?? event.path ?? "/",
|
|
2531
|
+
export const buildTarget = async (
|
|
2532
|
+
workingDir: string,
|
|
2533
|
+
target: string,
|
|
2534
|
+
options?: { force?: boolean },
|
|
2535
|
+
): Promise<void> => {
|
|
2536
|
+
const normalizedTarget = normalizeDeployTarget(target);
|
|
2537
|
+
const writtenPaths = await scaffoldDeployTarget(workingDir, normalizedTarget, {
|
|
2538
|
+
force: options?.force,
|
|
2448
2539
|
});
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
"utf8",
|
|
2453
|
-
);
|
|
2454
|
-
await writeFile(resolve(outDir, "package.json"), runtimePackageJson, "utf8");
|
|
2455
|
-
} else if (target === "fly") {
|
|
2456
|
-
await writeFile(
|
|
2457
|
-
resolve(outDir, "fly.toml"),
|
|
2458
|
-
`app = "poncho-app"
|
|
2459
|
-
[env]
|
|
2460
|
-
PORT = "3000"
|
|
2461
|
-
[http_service]
|
|
2462
|
-
internal_port = 3000
|
|
2463
|
-
force_https = true
|
|
2464
|
-
auto_start_machines = true
|
|
2465
|
-
auto_stop_machines = "stop"
|
|
2466
|
-
min_machines_running = 0
|
|
2467
|
-
`,
|
|
2468
|
-
"utf8",
|
|
2469
|
-
);
|
|
2470
|
-
await writeFile(
|
|
2471
|
-
resolve(outDir, "Dockerfile"),
|
|
2472
|
-
`FROM node:20-slim
|
|
2473
|
-
WORKDIR /app
|
|
2474
|
-
COPY package.json package.json
|
|
2475
|
-
COPY AGENT.md AGENT.md
|
|
2476
|
-
COPY poncho.config.js poncho.config.js
|
|
2477
|
-
COPY skills skills
|
|
2478
|
-
COPY tests tests
|
|
2479
|
-
RUN npm install -g @poncho-ai/cli
|
|
2480
|
-
COPY server.js server.js
|
|
2481
|
-
EXPOSE 3000
|
|
2482
|
-
CMD ["node","server.js"]
|
|
2483
|
-
`,
|
|
2484
|
-
"utf8",
|
|
2485
|
-
);
|
|
2486
|
-
await writeFile(resolve(outDir, "server.js"), serverEntrypoint, "utf8");
|
|
2487
|
-
await writeFile(resolve(outDir, "package.json"), runtimePackageJson, "utf8");
|
|
2488
|
-
} else {
|
|
2489
|
-
throw new Error(`Unsupported build target: ${target}`);
|
|
2540
|
+
process.stdout.write(`Scaffolded deploy files for ${normalizedTarget}:\n`);
|
|
2541
|
+
for (const filePath of writtenPaths) {
|
|
2542
|
+
process.stdout.write(` - ${filePath}\n`);
|
|
2490
2543
|
}
|
|
2491
|
-
|
|
2492
|
-
process.stdout.write(`Build artifacts generated at ${outDir}\n`);
|
|
2493
2544
|
};
|
|
2494
2545
|
|
|
2495
2546
|
const normalizeMcpName = (entry: { url?: string; name?: string }): string =>
|
|
@@ -2881,9 +2932,10 @@ export const buildCli = (): Command => {
|
|
|
2881
2932
|
program
|
|
2882
2933
|
.command("build")
|
|
2883
2934
|
.argument("<target>", "vercel|docker|lambda|fly")
|
|
2884
|
-
.
|
|
2885
|
-
.
|
|
2886
|
-
|
|
2935
|
+
.option("--force", "overwrite existing deployment files")
|
|
2936
|
+
.description("Scaffold deployment files for a target")
|
|
2937
|
+
.action(async (target: string, options: { force?: boolean }) => {
|
|
2938
|
+
await buildTarget(process.cwd(), target, { force: options.force });
|
|
2887
2939
|
});
|
|
2888
2940
|
|
|
2889
2941
|
const mcpCommand = program.command("mcp").description("Manage MCP servers");
|
package/src/init-onboarding.ts
CHANGED
|
@@ -21,6 +21,7 @@ const bold = (s: string): string => `${C.bold}${s}${C.reset}`;
|
|
|
21
21
|
const INPUT_CARET = "»";
|
|
22
22
|
|
|
23
23
|
type OnboardingAnswers = Record<string, string | number | boolean>;
|
|
24
|
+
export type DeployTarget = "none" | "vercel" | "docker" | "fly" | "lambda";
|
|
24
25
|
|
|
25
26
|
export type InitOnboardingOptions = {
|
|
26
27
|
yes?: boolean;
|
|
@@ -33,6 +34,7 @@ export type InitOnboardingResult = {
|
|
|
33
34
|
envExample: string;
|
|
34
35
|
envFile: string;
|
|
35
36
|
envNeedsUserInput: boolean;
|
|
37
|
+
deployTarget: DeployTarget;
|
|
36
38
|
agentModel: {
|
|
37
39
|
provider: "anthropic" | "openai";
|
|
38
40
|
name: string;
|
|
@@ -276,6 +278,14 @@ const askOnboardingQuestions = async (options: InitOnboardingOptions): Promise<O
|
|
|
276
278
|
const getProviderModelName = (provider: string): string =>
|
|
277
279
|
provider === "openai" ? "gpt-4.1" : "claude-opus-4-5";
|
|
278
280
|
|
|
281
|
+
const normalizeDeployTarget = (value: unknown): DeployTarget => {
|
|
282
|
+
const target = typeof value === "string" ? value.toLowerCase() : "";
|
|
283
|
+
if (target === "vercel" || target === "docker" || target === "fly" || target === "lambda") {
|
|
284
|
+
return target;
|
|
285
|
+
}
|
|
286
|
+
return "none";
|
|
287
|
+
};
|
|
288
|
+
|
|
279
289
|
const maybeSet = (
|
|
280
290
|
target: object,
|
|
281
291
|
key: string,
|
|
@@ -506,6 +516,7 @@ export const runInitOnboarding = async (
|
|
|
506
516
|
): Promise<InitOnboardingResult> => {
|
|
507
517
|
const answers = await askOnboardingQuestions(options);
|
|
508
518
|
const provider = String(answers["model.provider"] ?? "anthropic");
|
|
519
|
+
const deployTarget = normalizeDeployTarget(answers["deploy.target"]);
|
|
509
520
|
const config = buildConfigFromOnboardingAnswers(answers);
|
|
510
521
|
const envExampleLines = collectEnvVars(answers);
|
|
511
522
|
const envFileLines = collectEnvFileLines(answers);
|
|
@@ -522,6 +533,7 @@ export const runInitOnboarding = async (
|
|
|
522
533
|
envExample: `${envExampleLines.join("\n")}\n`,
|
|
523
534
|
envFile: envFileLines.length > 0 ? `${envFileLines.join("\n")}\n` : "",
|
|
524
535
|
envNeedsUserInput,
|
|
536
|
+
deployTarget,
|
|
525
537
|
agentModel: {
|
|
526
538
|
provider: provider === "openai" ? "openai" : "anthropic",
|
|
527
539
|
name: getProviderModelName(provider),
|