Skip to content

Commit 6c90166

Browse files
feat(www,arkenv): agent prompt docs URL + install skill on --agent (#1793)
## Summary - Homepage `getAgentPrompt` now embeds the **current deployment’s docs origin** (via new `getDocsUrl`) and tells agents to install the skill + prefer `@arkenv/core` / `@arkenv/standard`. - `arkenv init --agent` installs the agent skill when missing (same as `--yes`); skips when already present. - Patch changeset for the CLI behavior change. ## Docs URL resolution (`getDocsUrl`) Preference order (trimmed; trailing slashes stripped): 1. `NEXT_PUBLIC_SITE_URL` — used as-is (no trailing slash) 2. else `VERCEL_PROJECT_PRODUCTION_URL` → `https://${that}` (keeps scheme if already present) 3. else `VERCEL_URL` → `https://${that}` (preview deploys) 4. else fallback `https://arkenv.js.org` Setting `NEXT_PUBLIC_SITE_URL` or the Vercel **production** URL makes the homepage prompt auto-flip when the site moves off a preview host (e.g. `arkenv-v1.vercel.app` → `arkenv.js.org`) without hardcoding the preview forever. ## Prompt sample (fallback docs URL) ``` Set up ArkEnv with `npx arkenv@alpha init --agent`. Use docs only at https://arkenv.js.org. If the ArkEnv skill is missing, install it with `npx skills add yamcodes/arkenv`. Prefer `@arkenv/core` / `@arkenv/standard` over legacy `import from "arkenv"`. Install any missing dependencies, wire the env schema into the app entry, start the app, and tell me when validation works from editor to runtime. ``` ## Test plan - [x] `vitest run lib/config/release.test.ts` (www) - [x] `vitest run components/page/install-panel.test.tsx` (www) - [x] `vitest run src/cli/commands/init.test.ts` (arkenv) - [ ] Spot-check Copy prompt on a Vercel preview: expect `Use docs only at https://<VERCEL_URL>` (or production URL when set) --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 19c5ac3 commit 6c90166

3 files changed

Lines changed: 188 additions & 11 deletions

File tree

apps/www/lib/config/release.test.ts

Lines changed: 80 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
1-
import { describe, expect, it } from "vitest";
1+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
22
import {
33
getAgentPrompt,
4+
getDocsUrl,
45
getInitCommand,
56
getPackageSpecifier,
67
RELEASE_CONFIG,
78
RELEASE_TAG,
89
} from "./release";
910

1011
describe("release config", () => {
12+
beforeEach(() => {
13+
vi.stubEnv("NEXT_PUBLIC_SITE_URL", "");
14+
vi.stubEnv("VERCEL_PROJECT_PRODUCTION_URL", "");
15+
vi.stubEnv("VERCEL_URL", "");
16+
});
17+
18+
afterEach(() => {
19+
vi.unstubAllEnvs();
20+
});
21+
1122
it("defaults RELEASE_TAG to alpha", () => {
1223
expect(RELEASE_TAG).toBe("alpha");
1324
});
@@ -45,11 +56,11 @@ describe("release config", () => {
4556
});
4657

4758
it("formats agent prompt with active release tag", () => {
48-
expect(getAgentPrompt("alpha")).toBe(
49-
"Set up ArkEnv with `npx arkenv@alpha init --agent`. Install any missing dependencies, wire the env schema into the app entry, start the app, and tell me when validation works from editor to runtime.",
59+
expect(getAgentPrompt("alpha", "https://arkenv.js.org")).toBe(
60+
"Set up ArkEnv with `npx arkenv@alpha init --agent`. For docs/reference, start from https://arkenv.js.org/llms.txt and fetch any linked pages as markdown (append `.md`). Install the runtime engine as a dependency: `@arkenv/core` (with `arktype`) if ArkType is already in the project or there is no env validator yet; otherwise `@arkenv/standard` for use with the project's existing Standard Schema library (Zod, Valibot, etc.). Install the `arkenv` CLI as a devDependency. Use the project's package manager for installs. Wire the env schema into the app entry, start the app, and tell me when validation works from editor to runtime. When that works, suggest as a next step (do not install it yourself) that I install the ArkEnv skill with `npx skills add yamcodes/arkenv` — it teaches framework-specific env setup, keeping app code on `import { env } from \"./env\"`, and avoiding raw `process.env` / `import.meta.env`.",
5061
);
51-
expect(getAgentPrompt("")).toBe(
52-
"Set up ArkEnv with `npx arkenv init --agent`. Install any missing dependencies, wire the env schema into the app entry, start the app, and tell me when validation works from editor to runtime.",
62+
expect(getAgentPrompt("", "https://arkenv.js.org")).toBe(
63+
"Set up ArkEnv with `npx arkenv init --agent`. For docs/reference, start from https://arkenv.js.org/llms.txt and fetch any linked pages as markdown (append `.md`). Install the runtime engine as a dependency: `@arkenv/core` (with `arktype`) if ArkType is already in the project or there is no env validator yet; otherwise `@arkenv/standard` for use with the project's existing Standard Schema library (Zod, Valibot, etc.). Install the `arkenv` CLI as a devDependency. Use the project's package manager for installs. Wire the env schema into the app entry, start the app, and tell me when validation works from editor to runtime. When that works, suggest as a next step (do not install it yourself) that I install the ArkEnv skill with `npx skills add yamcodes/arkenv` — it teaches framework-specific env setup, keeping app code on `import { env } from \"./env\"`, and avoiding raw `process.env` / `import.meta.env`.",
5364
);
5465
});
5566

@@ -58,8 +69,70 @@ describe("release config", () => {
5869
expect(RELEASE_CONFIG.tag).toBe("alpha");
5970
expect(RELEASE_CONFIG.packageSpecifier).toBe("arkenv@alpha");
6071
expect(RELEASE_CONFIG.initCommand).toBe("npx arkenv@alpha init");
61-
expect(RELEASE_CONFIG.agentPrompt).toBe(
62-
"Set up ArkEnv with `npx arkenv@alpha init --agent`. Install any missing dependencies, wire the env schema into the app entry, start the app, and tell me when validation works from editor to runtime.",
72+
expect(RELEASE_CONFIG.agentPrompt).toContain(
73+
"npx arkenv@alpha init --agent",
74+
);
75+
expect(RELEASE_CONFIG.agentPrompt).toContain("/llms.txt");
76+
expect(RELEASE_CONFIG.agentPrompt).toContain(
77+
"suggest as a next step (do not install it yourself)",
78+
);
79+
expect(RELEASE_CONFIG.agentPrompt).toContain(
80+
"npx skills add yamcodes/arkenv",
81+
);
82+
expect(RELEASE_CONFIG.agentPrompt).toContain("@arkenv/core");
83+
});
84+
85+
it("resolves docs URL from NEXT_PUBLIC_SITE_URL first", () => {
86+
vi.stubEnv("NEXT_PUBLIC_SITE_URL", "https://custom.example/");
87+
vi.stubEnv("VERCEL_PROJECT_PRODUCTION_URL", "prod.example");
88+
vi.stubEnv("VERCEL_URL", "preview.example");
89+
expect(getDocsUrl()).toBe("https://custom.example");
90+
});
91+
92+
it("prepends https:// to bare-hostname NEXT_PUBLIC_SITE_URL", () => {
93+
vi.stubEnv("NEXT_PUBLIC_SITE_URL", "arkenv.js.org");
94+
vi.stubEnv("VERCEL_PROJECT_PRODUCTION_URL", "prod.example");
95+
vi.stubEnv("VERCEL_URL", "preview.example");
96+
expect(getDocsUrl()).toBe("https://arkenv.js.org");
97+
});
98+
99+
it("resolves docs URL from VERCEL_PROJECT_PRODUCTION_URL next", () => {
100+
vi.stubEnv("NEXT_PUBLIC_SITE_URL", "");
101+
vi.stubEnv("VERCEL_PROJECT_PRODUCTION_URL", "arkenv.js.org");
102+
vi.stubEnv("VERCEL_URL", "arkenv-v1.vercel.app");
103+
expect(getDocsUrl()).toBe("https://arkenv.js.org");
104+
});
105+
106+
it("resolves docs URL from VERCEL_URL for preview deploys", () => {
107+
vi.stubEnv("NEXT_PUBLIC_SITE_URL", "");
108+
vi.stubEnv("VERCEL_PROJECT_PRODUCTION_URL", "");
109+
vi.stubEnv("VERCEL_URL", "arkenv-v1.vercel.app");
110+
expect(getDocsUrl()).toBe("https://arkenv-v1.vercel.app");
111+
});
112+
113+
it("falls back to arkenv.js.org when env is unset", () => {
114+
vi.stubEnv("NEXT_PUBLIC_SITE_URL", "");
115+
vi.stubEnv("VERCEL_PROJECT_PRODUCTION_URL", "");
116+
vi.stubEnv("VERCEL_URL", "");
117+
expect(getDocsUrl()).toBe("https://arkenv.js.org");
118+
});
119+
120+
it("embeds the resolved docs URL in the agent prompt", () => {
121+
vi.stubEnv("NEXT_PUBLIC_SITE_URL", "");
122+
vi.stubEnv("VERCEL_PROJECT_PRODUCTION_URL", "");
123+
vi.stubEnv("VERCEL_URL", "arkenv-v1.vercel.app");
124+
const prompt = getAgentPrompt("alpha");
125+
expect(prompt).toContain(
126+
"For docs/reference, start from https://arkenv-v1.vercel.app/llms.txt",
63127
);
128+
expect(prompt).toContain("npx skills add yamcodes/arkenv");
129+
expect(prompt).toContain("do not install it yourself");
130+
expect(prompt).toContain('import { env } from "./env"');
131+
expect(prompt).toContain("devDependency");
132+
expect(prompt).toContain("project's package manager for installs");
133+
expect(prompt).toContain("@arkenv/core");
134+
expect(prompt).toContain("@arkenv/standard");
135+
expect(prompt).toContain("(with `arktype`)");
136+
expect(prompt).toContain("Standard Schema library");
64137
});
65138
});

apps/www/lib/config/release.ts

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,54 @@ export const RELEASE_TAG = rawTag.trim();
1010

1111
export type PackageManager = "npm" | "pnpm" | "bun" | "yarn";
1212

13+
const FALLBACK_DOCS_URL = "https://arkenv.js.org";
14+
15+
/**
16+
* Resolves the docs origin for the current deployment.
17+
*
18+
* Preference order:
19+
* 1. `NEXT_PUBLIC_SITE_URL` (trimmed, no trailing slash)
20+
* 2. `https://${VERCEL_PROJECT_PRODUCTION_URL}` (production domain; flips when DNS moves)
21+
* 3. `https://${VERCEL_URL}` (preview deployment host)
22+
* 4. Fallback `https://arkenv.js.org`
23+
*
24+
* Setting `NEXT_PUBLIC_SITE_URL` or the Vercel production URL makes the homepage
25+
* agent prompt auto-update when the site moves off a preview host (e.g.
26+
* arkenv-v1.vercel.app → arkenv.js.org) without hardcoding the preview forever.
27+
*
28+
* @param env - Env bag to read (defaults to `process.env`; injectable for tests).
29+
* @returns Absolute docs origin with no trailing slash.
30+
*/
31+
export function getDocsUrl(env: NodeJS.ProcessEnv = process.env): string {
32+
const siteUrl = env.NEXT_PUBLIC_SITE_URL?.trim().replace(/\/+$/, "");
33+
if (siteUrl) {
34+
return siteUrl.startsWith("http://") || siteUrl.startsWith("https://")
35+
? siteUrl
36+
: `https://${siteUrl}`;
37+
}
38+
39+
const productionHost = env.VERCEL_PROJECT_PRODUCTION_URL?.trim().replace(
40+
/\/+$/,
41+
"",
42+
);
43+
if (productionHost) {
44+
return productionHost.startsWith("http://") ||
45+
productionHost.startsWith("https://")
46+
? productionHost
47+
: `https://${productionHost}`;
48+
}
49+
50+
const previewHost = env.VERCEL_URL?.trim().replace(/\/+$/, "");
51+
if (previewHost) {
52+
return previewHost.startsWith("http://") ||
53+
previewHost.startsWith("https://")
54+
? previewHost
55+
: `https://${previewHost}`;
56+
}
57+
58+
return FALLBACK_DOCS_URL;
59+
}
60+
1361
/**
1462
* Returns the npm package specifier with an optional release tag.
1563
* When the tag is empty or falsy, returns the bare package name.
@@ -53,14 +101,27 @@ export function getInitCommand(
53101
}
54102

55103
/**
56-
* Returns the standard AI agent onboarding prompt formatted with the active release tag.
104+
* Returns the standard AI agent onboarding prompt formatted with the active
105+
* release tag and the current deployment's docs URL.
57106
*
58-
* @param tag - Release tag override (defaults to `RELEASE_TAG`).
107+
* @param tag - Release tag override (defaults to RELEASE_TAG).
108+
* @param docsUrl - Docs origin override (defaults to getDocsUrl).
59109
* @returns Formatted prompt string for AI coding agents.
60110
*/
61-
export function getAgentPrompt(tag = RELEASE_TAG): string {
111+
export function getAgentPrompt(
112+
tag = RELEASE_TAG,
113+
docsUrl = getDocsUrl(),
114+
): string {
62115
const command = getInitCommand("npm", tag, "init --agent");
63-
return `Set up ArkEnv with \`${command}\`. Install any missing dependencies, wire the env schema into the app entry, start the app, and tell me when validation works from editor to runtime.`;
116+
return [
117+
`Set up ArkEnv with \`${command}\`.`,
118+
`For docs/reference, start from ${docsUrl}/llms.txt and fetch any linked pages as markdown (append \`.md\`).`,
119+
"Install the runtime engine as a dependency: `@arkenv/core` (with `arktype`) if ArkType is already in the project or there is no env validator yet; otherwise `@arkenv/standard` for use with the project's existing Standard Schema library (Zod, Valibot, etc.).",
120+
"Install the `arkenv` CLI as a devDependency.",
121+
"Use the project's package manager for installs.",
122+
"Wire the env schema into the app entry, start the app, and tell me when validation works from editor to runtime.",
123+
'When that works, suggest as a next step (do not install it yourself) that I install the ArkEnv skill with `npx skills add yamcodes/arkenv` — it teaches framework-specific env setup, keeping app code on `import { env } from "./env"`, and avoiding raw `process.env` / `import.meta.env`.',
124+
].join(" ");
64125
}
65126

66127
/**

packages/arkenv/src/cli/commands/init.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,49 @@ describe("InitUseCase", () => {
648648
expect(result.options.skillDetected).toBe(true);
649649
});
650650

651+
it("should set installSkill to false for isAgent when skill is missing", async () => {
652+
vi.mocked(scanner.hasSkill).mockResolvedValue(false);
653+
vi.mocked(prompt.runWizard).mockResolvedValue({
654+
path: "./env.ts",
655+
validator: "arktype",
656+
framework: "vanilla",
657+
language: "ts",
658+
});
659+
660+
const result = await (useCase as any).collect({
661+
isYes: false,
662+
isForce: false,
663+
isQuiet: true,
664+
isAgent: true,
665+
});
666+
667+
expect(result).not.toBeNull();
668+
expect(result.options.installSkill).toBe(false);
669+
expect(prompt.confirm).not.toHaveBeenCalled();
670+
});
671+
672+
it("should set installSkill to false for isAgent when skill is already present", async () => {
673+
vi.mocked(scanner.hasSkill).mockResolvedValue(true);
674+
vi.mocked(prompt.runWizard).mockResolvedValue({
675+
path: "./env.ts",
676+
validator: "arktype",
677+
framework: "vanilla",
678+
language: "ts",
679+
});
680+
681+
const result = await (useCase as any).collect({
682+
isYes: false,
683+
isForce: false,
684+
isQuiet: true,
685+
isAgent: true,
686+
});
687+
688+
expect(result).not.toBeNull();
689+
expect(result.options.installSkill).toBe(false);
690+
expect(result.options.skillDetected).toBe(true);
691+
expect(prompt.confirm).not.toHaveBeenCalled();
692+
});
693+
651694
describe("version freshness pre-flight check", () => {
652695
let versionChecker: any;
653696
let spawner: any;

0 commit comments

Comments
 (0)