@soloworks/smking-wizard 0.1.0 → 0.2.1
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/CHANGELOG.md +24 -0
- package/dist/bin.mjs +27 -11
- package/dist/bin.mjs.map +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# @soloworks/smking-wizard
|
|
2
2
|
|
|
3
|
+
## 0.2.1 — 2026-05-14
|
|
4
|
+
|
|
5
|
+
**Fixes broken 0.2.0 install (404 on `@smking/shared`).**
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- `@smking/shared` (monorepo internal workspace package, not on npm) leaked into the published `dependencies` block on 0.2.0, so `npx @soloworks/smking-wizard` failed with `404 @smking/shared@0.0.0 is not in this registry`. Moved to `devDependencies` — the package is already inlined into `dist/bin.mjs` via tsdown's `noExternal` setting, so it's never needed at runtime by customers.
|
|
10
|
+
|
|
11
|
+
0.2.0 has been deprecated on npm. All users should jump straight from 0.1.0 to 0.2.1.
|
|
12
|
+
|
|
13
|
+
## 0.2.0 — 2026-05-14
|
|
14
|
+
|
|
15
|
+
**CMS webhook secret integration.**
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
- `set_env` tool now accepts `SMKING_WEBHOOK_SECRET` alongside the existing `SMKING_API_KEY` / `SMKING_BASE_URL`. When the install prompt's `.env` block contains a webhook secret (CMS-enabled sites), the agent writes it to the customer's `.env` / `.env.local` automatically — wiring CMS publish push-invalidation end-to-end without manual steps.
|
|
20
|
+
- Install prompts (Laravel + Next.js paths) inside the wizard now render an `SMKING_WEBHOOK_SECRET` line when the dashboard has provisioned one for the target site.
|
|
21
|
+
- Welcome screen + agent commandments updated to mention the webhook secret in the list of env values the wizard writes.
|
|
22
|
+
|
|
23
|
+
### Internal
|
|
24
|
+
|
|
25
|
+
- Wizard build (tsdown) + typecheck + tests gate the publish via `prepublishOnly` so binary drift is impossible.
|
|
26
|
+
|
|
3
27
|
## 0.1.0
|
|
4
28
|
|
|
5
29
|
Initial release — single-command install wizard for smking SDKs.
|
package/dist/bin.mjs
CHANGED
|
@@ -243,7 +243,7 @@ function WelcomeScreen() {
|
|
|
243
243
|
}),
|
|
244
244
|
/* @__PURE__ */ jsx(Text, {
|
|
245
245
|
color: "green",
|
|
246
|
-
children: " ✓ Write SMKING_API_KEY + SMKING_BASE_URL to your .env"
|
|
246
|
+
children: " ✓ Write SMKING_API_KEY + SMKING_BASE_URL (+ SMKING_WEBHOOK_SECRET if CMS enabled) to your .env"
|
|
247
247
|
}),
|
|
248
248
|
/* @__PURE__ */ jsx(Text, {
|
|
249
249
|
color: "green",
|
|
@@ -616,7 +616,7 @@ You are the install agent inside @soloworks/smking-wizard. The user's project is
|
|
|
616
616
|
|
|
617
617
|
3. After detecting framework, call \`install_package\` once with the detected framework.
|
|
618
618
|
|
|
619
|
-
4. After \`install_package\` succeeds, call \`set_env\` with SMKING_API_KEY and SMKING_BASE_URL extracted from the install prompt the user gave you. Both must match the values in the prompt verbatim.
|
|
619
|
+
4. After \`install_package\` succeeds, call \`set_env\` with SMKING_API_KEY and SMKING_BASE_URL extracted from the install prompt the user gave you. Both must match the values in the prompt verbatim. If the install prompt's \`.env\` block ALSO contains \`SMKING_WEBHOOK_SECRET\`, include that in the same \`set_env\` call (also verbatim — it's a 64-char hex string). The webhook secret powers CMS publish push-invalidate; omit it only if the prompt itself omits it (AEO-only sites).
|
|
620
620
|
|
|
621
621
|
5. After \`set_env\`, call \`run_doctor\`. Parse the JSON result.
|
|
622
622
|
|
|
@@ -950,12 +950,16 @@ async function installLaravel(ctx) {
|
|
|
950
950
|
status: "done"
|
|
951
951
|
});
|
|
952
952
|
if (ctx.apiKey && ctx.baseUrl && !ctx.apiKey.startsWith("<") && !ctx.baseUrl.startsWith("<")) {
|
|
953
|
-
const
|
|
953
|
+
const updates = {
|
|
954
954
|
SMKING_API_KEY: ctx.apiKey,
|
|
955
955
|
SMKING_BASE_URL: ctx.baseUrl
|
|
956
|
-
}
|
|
956
|
+
};
|
|
957
|
+
const willWriteWebhook = ctx.webhookSecret && ctx.webhookSecret.length > 0 && !ctx.webhookSecret.startsWith("<");
|
|
958
|
+
if (willWriteWebhook) updates.SMKING_WEBHOOK_SECRET = ctx.webhookSecret;
|
|
959
|
+
const envChange = setEnvKeys(join(ctx.cwd, ".env"), updates);
|
|
960
|
+
const labelKeys = willWriteWebhook ? "SMKING_API_KEY + SMKING_BASE_URL + SMKING_WEBHOOK_SECRET" : "SMKING_API_KEY + SMKING_BASE_URL";
|
|
957
961
|
steps.push({
|
|
958
|
-
name:
|
|
962
|
+
name: `Write ${labelKeys} to .env`,
|
|
959
963
|
status: envChange.changed.length > 0 ? "done" : "skipped",
|
|
960
964
|
detail: envChange.changed.length > 0 ? `wrote: ${envChange.changed.join(", ")}` : `already set: ${envChange.unchanged.join(", ")}`
|
|
961
965
|
});
|
|
@@ -1027,12 +1031,16 @@ async function installNextjs(ctx) {
|
|
|
1027
1031
|
detail: "env handled by separate set_env step (wizard agent path)"
|
|
1028
1032
|
});
|
|
1029
1033
|
else {
|
|
1030
|
-
const
|
|
1034
|
+
const updates = {
|
|
1031
1035
|
SMKING_API_KEY: ctx.apiKey,
|
|
1032
1036
|
SMKING_BASE_URL: ctx.baseUrl
|
|
1033
|
-
}
|
|
1037
|
+
};
|
|
1038
|
+
const willWriteWebhook = ctx.webhookSecret && ctx.webhookSecret.length > 0 && !ctx.webhookSecret.startsWith("<");
|
|
1039
|
+
if (willWriteWebhook) updates.SMKING_WEBHOOK_SECRET = ctx.webhookSecret;
|
|
1040
|
+
const envChange = setEnvKeys(join(ctx.cwd, ".env.local"), updates);
|
|
1041
|
+
const labelKeys = willWriteWebhook ? "SMKING_API_KEY + SMKING_BASE_URL + SMKING_WEBHOOK_SECRET" : "SMKING_API_KEY + SMKING_BASE_URL";
|
|
1034
1042
|
steps.push({
|
|
1035
|
-
name:
|
|
1043
|
+
name: `Write ${labelKeys} to .env.local`,
|
|
1036
1044
|
status: envChange.changed.length > 0 ? "done" : "skipped",
|
|
1037
1045
|
detail: envChange.changed.length > 0 ? `wrote: ${envChange.changed.join(", ")}` : `already set: ${envChange.unchanged.join(", ")}`
|
|
1038
1046
|
});
|
|
@@ -1192,7 +1200,7 @@ function buildWizardTools(ctx) {
|
|
|
1192
1200
|
},
|
|
1193
1201
|
{
|
|
1194
1202
|
name: "set_env",
|
|
1195
|
-
description: "Write SMKING_API_KEY
|
|
1203
|
+
description: "Write SMKING_API_KEY / SMKING_BASE_URL / SMKING_WEBHOOK_SECRET to the project's env file (.env for Laravel, .env.local for Next.js). Other keys are rejected. Idempotent.",
|
|
1196
1204
|
input_schema: {
|
|
1197
1205
|
type: "object",
|
|
1198
1206
|
properties: {
|
|
@@ -1203,19 +1211,27 @@ function buildWizardTools(ctx) {
|
|
|
1203
1211
|
SMKING_BASE_URL: {
|
|
1204
1212
|
type: "string",
|
|
1205
1213
|
description: "smking SaaS base URL (e.g. https://smking.com)"
|
|
1214
|
+
},
|
|
1215
|
+
SMKING_WEBHOOK_SECRET: {
|
|
1216
|
+
type: "string",
|
|
1217
|
+
description: "HMAC secret for verifying inbound CMS publish webhooks from SaaS. 64-char hex string. Optional — only present in the install prompt when the site has CMS enabled. Skip when absent from prompt."
|
|
1206
1218
|
}
|
|
1207
1219
|
}
|
|
1208
1220
|
},
|
|
1209
1221
|
run: async (input) => {
|
|
1210
1222
|
pushProgress("Setting env values…");
|
|
1211
1223
|
const typed = input;
|
|
1212
|
-
const allowedKeys = new Set([
|
|
1224
|
+
const allowedKeys = new Set([
|
|
1225
|
+
"SMKING_API_KEY",
|
|
1226
|
+
"SMKING_BASE_URL",
|
|
1227
|
+
"SMKING_WEBHOOK_SECRET"
|
|
1228
|
+
]);
|
|
1213
1229
|
const updates = {};
|
|
1214
1230
|
for (const [k, v] of Object.entries(typed)) {
|
|
1215
1231
|
if (!allowedKeys.has(k) || typeof v !== "string") continue;
|
|
1216
1232
|
updates[k] = v;
|
|
1217
1233
|
}
|
|
1218
|
-
if (Object.keys(updates).length === 0) return asResult({ error: "set_env called with no valid keys. Allowed: SMKING_API_KEY, SMKING_BASE_URL." });
|
|
1234
|
+
if (Object.keys(updates).length === 0) return asResult({ error: "set_env called with no valid keys. Allowed: SMKING_API_KEY, SMKING_BASE_URL, SMKING_WEBHOOK_SECRET." });
|
|
1219
1235
|
const envFile = detectFramework(ctx.cwd).framework === "nextjs" ? ".env.local" : ".env";
|
|
1220
1236
|
const result = setEnvKeys(join(ctx.cwd, envFile), updates);
|
|
1221
1237
|
pushProgress(`Env: changed=[${result.changed.join(", ")}] unchanged=[${result.unchanged.join(", ")}]`);
|
package/dist/bin.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bin.mjs","names":["DEFAULT_FLAGS: CliFlags","signals: string[]","resolver: (value: { code: string }) => void","rejecter: (err: Error) => void","pkg: { dependencies?: Record<string, string>; devDependencies?: Record<string, string> }","LOCKFILES: Array<{ name: string; pm: NodePackageManager }>","timer: NodeJS.Timeout | undefined","changed: string[]","unchanged: string[]","steps: InstallStep[]","bumpedTo: string | null","PM_ADD_ARGS: Record<NodePackageManager, string[]>","steps: InstallStep[]","bumpedTo: string | null","result","updates: Record<string, string>","cmd: string","args: string[]","fs","messages: Anthropic.MessageParam[]","apiCallPromise: Promise<Anthropic.Message>","toolResults: Anthropic.ToolResultBlockParam[]"],"sources":["../src/ui/store.ts","../../shared/src/constants/oauth.ts","../src/constants.ts","../src/guards/production-check.ts","../src/guards/git-status-check.ts","../src/ui/screens/welcome-screen.tsx","../src/oauth.ts","../src/ui/screens/oauth-screen.tsx","../src/agent/commandments.ts","../src/agent/prompt.ts","../src/detection/framework.ts","../src/detection/package-manager.ts","../src/lib/exec.ts","../src/lib/env-file.ts","../src/lib/registry.ts","../src/installers/laravel.ts","../src/installers/nextjs.ts","../src/agent/tools.ts","../src/agent/runtime.ts","../src/ui/screens/run-screen.tsx","../src/ui/screens/done-screen.tsx","../src/ui/screens/error-screen.tsx","../src/ui/app.tsx","../src/bin.ts"],"sourcesContent":["import { create } from \"zustand\";\nimport type { OAuthResult } from \"../oauth\";\n\n/**\n * Wizard's single source of truth. Each screen subscribes to the\n * slices it needs and the agent loop writes into here as work\n * progresses. Mirrors PostHog wizard's `src/ui/tui/store.ts` shape\n * minus the pieces we don't have yet (TodoWrite sync, status\n * channel — those land in Phase 5 when the agent integrates).\n */\n\nexport type Screen = \"welcome\" | \"oauth\" | \"run\" | \"done\" | \"error\";\n\nexport type CliFlags = {\n allowProd: boolean;\n allowDirty: boolean;\n dryRun: boolean;\n debug: boolean;\n};\n\nexport type AgentStatus = \"idle\" | \"running\" | \"succeeded\" | \"failed\";\n\nexport type WizardStore = {\n // ── CLI flags (set once at bin.ts, read by guards) ──────────\n cliFlags: CliFlags;\n setCliFlags: (flags: CliFlags) => void;\n\n // ── Screen routing ──────────────────────────────────────────\n screen: Screen;\n setScreen: (screen: Screen) => void;\n\n // ── OAuth ───────────────────────────────────────────────────\n oauthUrl: string | null;\n setOauthUrl: (url: string | null) => void;\n oauth: OAuthResult | null;\n setOauth: (result: OAuthResult) => void;\n\n // ── Agent progress (driven by tool handlers) ────────────────\n agentStatus: AgentStatus;\n setAgentStatus: (status: AgentStatus) => void;\n agentProgress: string[];\n pushProgress: (text: string) => void;\n agentSummary: string | null;\n setAgentSummary: (msg: string) => void;\n\n // ── Fatal error (drops user into Error screen) ──────────────\n fatal: string | null;\n setFatal: (msg: string) => void;\n};\n\nconst DEFAULT_FLAGS: CliFlags = {\n allowProd: false,\n allowDirty: false,\n dryRun: false,\n debug: false,\n};\n\n/**\n * Cap progress history at 50 lines. Past that, oldest entries drop\n * — keeps the in-memory log bounded for long agent loops and the\n * TUI render fast (we don't want to re-render 500 progress lines).\n */\nconst MAX_PROGRESS_LINES = 50;\n\nexport const useWizardStore = create<WizardStore>((set) => ({\n cliFlags: DEFAULT_FLAGS,\n setCliFlags: (cliFlags) => set({ cliFlags }),\n\n screen: \"welcome\",\n setScreen: (screen) => set({ screen }),\n\n oauthUrl: null,\n setOauthUrl: (oauthUrl) => set({ oauthUrl }),\n oauth: null,\n setOauth: (oauth) => set({ oauth }),\n\n agentStatus: \"idle\",\n setAgentStatus: (agentStatus) => set({ agentStatus }),\n agentProgress: [],\n pushProgress: (text) =>\n set((s) => {\n const next = [...s.agentProgress, text];\n if (next.length > MAX_PROGRESS_LINES) {\n next.splice(0, next.length - MAX_PROGRESS_LINES);\n }\n return { agentProgress: next };\n }),\n agentSummary: null,\n setAgentSummary: (agentSummary) => set({ agentSummary }),\n\n fatal: null,\n setFatal: (fatal) => set({ fatal, screen: \"error\" }),\n}));\n","export const TOKEN_EXPIRY = {\n ACCESS_TOKEN: 24 * 60 * 60, // 24 hours in seconds\n REFRESH_TOKEN: 90 * 24 * 60 * 60, // 90 days in seconds\n AUTH_CODE: 10 * 60, // 10 minutes in seconds\n /**\n * Wizard tokens have a shorter TTL because the wizard is a short-lived\n * install flow — 30 min covers OAuth + agent install + doctor loop\n * with margin for SSH-typing-the-URL scenarios.\n */\n WIZARD_ACCESS_TOKEN: 30 * 60,\n} as const;\n\nexport const OAUTH_SCOPES = [\n \"site:manage\",\n \"wizard:install\",\n \"wizard:llm\",\n] as const;\n\nexport const WIZARD_SCOPES = [\"wizard:install\", \"wizard:llm\"] as const;\n\nexport const GRANT_TYPES = [\"authorization_code\", \"refresh_token\"] as const;\n\n/**\n * Localhost callback ports the @soloworks/smking-wizard CLI tries in order. Six\n * ports give us headroom when the user already has a dev server on one\n * — wizard falls through on `EADDRINUSE`. Mirrors PostHog wizard's\n * `OAUTH_PORTS` for the same reason.\n */\nexport const WIZARD_OAUTH_PORTS = [8239, 8238, 8240, 8237, 8236, 8235] as const;\n\n/**\n * Quota knobs for the wizard LLM gateway proxy. Per-session caps the\n * cost of a single wizard run; per-user-day caps total wizard cost\n * across runs. Both enforced in `/api/v1/wizard/gateway/[...path]`.\n */\nexport const WIZARD_QUOTA = {\n PER_SESSION_TOKENS: 200_000, // ≈ $0.60 Sonnet 4.6\n PER_USER_DAILY_SESSIONS: 5, // ≈ $3 upper bound per user per day\n} as const;\n","import {\n WIZARD_OAUTH_PORTS,\n WIZARD_SCOPES,\n WIZARD_QUOTA,\n} from \"@smking/shared\";\n\n/**\n * Re-export the constants the SaaS already pins so the wizard CLI\n * and the backend stay in sync — changing a port list in one place\n * silently breaking the other would be a nasty bug, so we centralise.\n */\nexport const OAUTH_PORTS = WIZARD_OAUTH_PORTS;\nexport const SCOPES = WIZARD_SCOPES;\nexport const QUOTA = WIZARD_QUOTA;\n\n/**\n * smking SaaS origin the wizard talks to. Defaults to the current\n * Vercel production deployment; override via `SMKING_SAAS_URL` for\n * local dev (e.g. `http://localhost:3001`) or staging. Trailing slash\n * is stripped to make URL concatenation safe.\n *\n * Once a custom domain (e.g. `smking.com`) is wired up to the Vercel\n * project, swap the default here in the same commit that updates DNS.\n */\nexport const SAAS_URL = (\n process.env.SMKING_SAAS_URL ?? \"https://smking-alone.vercel.app\"\n).replace(/\\/$/, \"\");\n\n/**\n * Public OAuth client_id registered on the SaaS for the wizard. Must\n * match `WIZARD_OAUTH_CLIENT_ID` env var on the SaaS side, which\n * `validateClientCredentials` checks. Public client — no secret.\n */\nexport const WIZARD_CLIENT_ID =\n process.env.SMKING_WIZARD_CLIENT_ID ?? \"smking_wizard_v1\";\n\n/**\n * How long the wizard waits for the OAuth browser flow to complete\n * before giving up. 6 minutes covers slow logins, MFA prompts, and\n * SSH users who have to manually copy the URL to a different browser.\n */\nexport const OAUTH_TIMEOUT_MS = 6 * 60 * 1000;\n\n/**\n * PKCE code verifier length (RFC 7636 §4.1 says 43-128 chars). 96\n * is a comfortable middle that still fits a URL nicely if anyone\n * needs to copy/paste it for debugging.\n */\nexport const PKCE_VERIFIER_LENGTH = 96;\n\n/**\n * Read from package.json at build time. tsdown inlines it.\n */\nexport const WIZARD_VERSION = \"0.1.0\";\n\n/**\n * Minimum Node version. ink 6 needs Node 18.0.0, but we set 20.10\n * to align with the SaaS's `engines` and to get fetch / native test\n * runner stability.\n */\nexport const MIN_NODE_MAJOR = 20;\nexport const MIN_NODE_MINOR = 10;\n","import { existsSync } from \"node:fs\";\n\n/**\n * Refuse to run on production-like environments. Wizard mutates\n * customer files (composer.json, .env, layout.tsx) — running it\n * on a production server is a sharp tool. We block by default;\n * `--allow-prod` overrides for users who know why.\n *\n * Detection is heuristic — we look at:\n * - `NODE_ENV` / `VERCEL_ENV` / `RAILWAY_ENVIRONMENT` env vars\n * - `/.dockerenv` (running inside a Docker container)\n * - `/var/www` (typical LAMP production layout)\n *\n * None of these alone is definitive but together they catch the\n * common cases. Customers with custom prod environments get the\n * `--allow-prod` flag.\n */\n\nexport type GuardResult = {\n ok: boolean;\n reason?: string;\n override?: string; // CLI flag that can bypass this guard\n};\n\nexport function checkProduction(\n flags: { allowProd?: boolean } = {},\n): GuardResult {\n if (flags.allowProd) return { ok: true };\n\n const signals: string[] = [];\n\n if (process.env.NODE_ENV === \"production\") {\n signals.push(\"NODE_ENV=production\");\n }\n if (process.env.VERCEL_ENV === \"production\") {\n signals.push(\"VERCEL_ENV=production\");\n }\n if (process.env.RAILWAY_ENVIRONMENT === \"production\") {\n signals.push(\"RAILWAY_ENVIRONMENT=production\");\n }\n if (process.env.FLY_APP_NAME) {\n signals.push(\"FLY_APP_NAME set (running on fly.io)\");\n }\n if (existsSync(\"/.dockerenv\")) {\n signals.push(\"/.dockerenv present (inside Docker container)\");\n }\n // /var/www is a generic LAMP convention — only treat as a signal\n // when combined with another. Standalone it false-positives on\n // some Linux dev setups.\n if (existsSync(\"/var/www\") && signals.length > 0) {\n signals.push(\"/var/www present\");\n }\n\n if (signals.length === 0) return { ok: true };\n\n return {\n ok: false,\n reason:\n `Refusing to run on a production-like environment. Signals detected:\\n - ${signals.join(\n \"\\n - \",\n )}\\n\\nRun the wizard on your local dev machine, then deploy normally. If you genuinely need to run here, pass --allow-prod.`,\n override: \"--allow-prod\",\n };\n}\n","import { spawnSync } from \"node:child_process\";\nimport type { GuardResult } from \"./production-check\";\n\n/**\n * Require a clean git working tree before wizard runs. Two reasons:\n *\n * 1. **Predictable diff.** After wizard finishes, `git diff` shows\n * exactly what wizard did and nothing else — the customer can\n * review one cohesive change. Mixed in with prior uncommitted\n * work, the wizard's edits are hard to isolate.\n *\n * 2. **Easy rollback.** If wizard does something wrong, `git\n * restore .` cleanly reverts to the pre-wizard state. With\n * uncommitted work mixed in, that command also wipes the\n * customer's own work.\n *\n * Override via `--allow-dirty` for users who know what they're\n * doing. Repos without git (rare for a real project) pass the\n * guard since there's nothing to dirty.\n */\n\nexport function checkGitStatus(\n flags: { allowDirty?: boolean } = {},\n cwd: string = process.cwd(),\n): GuardResult {\n if (flags.allowDirty) return { ok: true };\n\n // `git status --porcelain` exits 0 for clean tree, 0 with output\n // for dirty tree, and non-zero if not a git repo. We tolerate\n // not-a-git-repo (nothing to dirty) and only flag actual dirty.\n const result = spawnSync(\"git\", [\"status\", \"--porcelain\"], {\n cwd,\n encoding: \"utf-8\",\n });\n\n if (result.error || result.status !== 0) {\n // Not a git repo or git missing. Tolerate — `git restore`\n // wouldn't be the customer's rollback strategy anyway.\n return { ok: true };\n }\n\n const dirty = result.stdout.trim();\n if (dirty === \"\") return { ok: true };\n\n // Truncate the dirty-files list so a huge uncommitted refactor\n // doesn't flood the TUI.\n const lines = dirty.split(\"\\n\");\n const preview = lines.slice(0, 10).join(\"\\n\");\n const more = lines.length > 10 ? `\\n ... and ${lines.length - 10} more files` : \"\";\n\n return {\n ok: false,\n reason: `Working tree has uncommitted changes. Wizard wants a clean slate so its diff is unambiguous.\\n\\nUncommitted files:\\n${preview}${more}\\n\\nCommit, stash, or discard them first. If you really need to run on a dirty tree, pass --allow-dirty.`,\n override: \"--allow-dirty\",\n };\n}\n","import { Box, Text, useInput } from \"ink\";\nimport { useWizardStore } from \"../store\";\nimport { WIZARD_VERSION } from \"../../constants\";\nimport { checkProduction } from \"../../guards/production-check\";\nimport { checkGitStatus } from \"../../guards/git-status-check\";\n\n/**\n * First thing the user sees. Trust signals up front — what the\n * wizard will do and what it will NEVER do. Press enter to continue.\n *\n * On Enter the screen also runs sync guards (production-env refuse\n * + git-status-must-be-clean) before advancing. Either guard failing\n * routes to the error screen with an actionable override hint.\n */\nexport function WelcomeScreen() {\n const setScreen = useWizardStore((s) => s.setScreen);\n const setFatal = useWizardStore((s) => s.setFatal);\n const cliFlags = useWizardStore((s) => s.cliFlags);\n\n useInput((_, key) => {\n if (!key.return) return;\n\n const prodCheck = checkProduction({ allowProd: cliFlags.allowProd });\n if (!prodCheck.ok) {\n setFatal(prodCheck.reason!);\n return;\n }\n\n const gitCheck = checkGitStatus({ allowDirty: cliFlags.allowDirty });\n if (!gitCheck.ok) {\n setFatal(gitCheck.reason!);\n return;\n }\n\n setScreen(\"oauth\");\n });\n\n return (\n <Box flexDirection=\"column\" gap={1}>\n <Box>\n <Text bold color=\"cyan\">\n 🪄 smking install wizard\n </Text>\n <Text color=\"gray\"> v{WIZARD_VERSION}</Text>\n </Box>\n\n <Text>\n This will install the smking SDK for your project (Laravel or\n Next.js), configure your environment, and verify the install\n with a doctor check.\n </Text>\n\n <Box flexDirection=\"column\">\n <Text bold>This wizard will:</Text>\n <Text color=\"green\"> ✓ Open your browser to log in to smking</Text>\n <Text color=\"green\"> ✓ Detect your framework (Laravel / Next.js)</Text>\n <Text color=\"green\"> ✓ Install the smking SDK package</Text>\n <Text color=\"green\"> ✓ Write SMKING_API_KEY + SMKING_BASE_URL to your .env</Text>\n <Text color=\"green\"> ✓ Run doctor to verify the install</Text>\n </Box>\n\n <Box flexDirection=\"column\">\n <Text bold>This wizard will NEVER:</Text>\n <Text color=\"red\"> ✗ Commit or push to git (you review the diff yourself)</Text>\n <Text color=\"red\"> ✗ Read or upload your .env values to any server</Text>\n <Text color=\"red\"> ✗ Run destructive commands (rm, drop, migrate:fresh, etc.)</Text>\n <Text color=\"red\"> ✗ Modify env variables other than SMKING_*</Text>\n <Text color=\"red\"> ✗ Install packages other than the smking SDK</Text>\n </Box>\n\n <Box marginTop={1}>\n <Text color=\"cyan\">Press Enter to continue · Ctrl-C to abort</Text>\n </Box>\n </Box>\n );\n}\n","import { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { createHash, randomBytes } from \"node:crypto\";\nimport open from \"open\";\nimport {\n OAUTH_PORTS,\n SCOPES,\n SAAS_URL,\n WIZARD_CLIENT_ID,\n OAUTH_TIMEOUT_MS,\n PKCE_VERIFIER_LENGTH,\n} from \"./constants\";\n\n/**\n * Wizard-side OAuth (RFC 6749 §4.1 authorization_code with PKCE per\n * RFC 7636). Mirrors PostHog wizard's `src/utils/oauth.ts` — port\n * fallback list, browser open + URL fallback for SSH, 6-min timeout.\n *\n * Flow:\n * 1. Generate code_verifier + code_challenge (S256)\n * 2. Start localhost callback server, try each port in OAUTH_PORTS\n * 3. Open browser at SaaS authorize URL with code_challenge\n * 4. User logs in + consents → SaaS redirects to localhost callback\n * 5. Callback server captures code + state, verifies state, returns\n * 6. Exchange code for access_token via POST /api/oauth/token with\n * code_verifier (no client_secret — we're a public client)\n */\n\nexport type OAuthResult = {\n accessToken: string;\n refreshToken: string;\n expiresIn: number;\n siteId: string | null;\n};\n\n/**\n * Generate a PKCE code verifier per RFC 7636 §4.1. Cryptographically\n * random base64url string in [43, 128] chars.\n */\nfunction generateCodeVerifier(): string {\n // 96 chars of base64url ≈ 72 random bytes (96 * 6 / 8 = 72).\n const bytes = Math.ceil((PKCE_VERIFIER_LENGTH * 6) / 8);\n return base64UrlEncode(randomBytes(bytes)).slice(0, PKCE_VERIFIER_LENGTH);\n}\n\n/**\n * S256 challenge: BASE64URL(SHA256(verifier)).\n */\nfunction generateCodeChallenge(verifier: string): string {\n return base64UrlEncode(createHash(\"sha256\").update(verifier).digest());\n}\n\nfunction base64UrlEncode(buf: Buffer): string {\n return buf\n .toString(\"base64\")\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nfunction generateState(): string {\n return randomBytes(16).toString(\"hex\");\n}\n\n/**\n * Try each port in OAUTH_PORTS until one binds, then run `handle` on\n * the first matching `/callback` request. Rejects on timeout or if\n * all ports are occupied.\n *\n * Returns the port number it bound to (so the redirect_uri can be\n * built to match) and a promise that resolves with the OAuth code +\n * state when the callback fires.\n */\nasync function startCallbackServer(\n expectedState: string,\n signal: AbortSignal,\n): Promise<{ port: number; result: Promise<{ code: string }> }> {\n let resolver: (value: { code: string }) => void;\n let rejecter: (err: Error) => void;\n const result = new Promise<{ code: string }>((res, rej) => {\n resolver = res;\n rejecter = rej;\n });\n\n const handler = (req: IncomingMessage, res: ServerResponse) => {\n if (!req.url) {\n res.writeHead(404);\n res.end();\n return;\n }\n const url = new URL(req.url, \"http://localhost\");\n if (url.pathname !== \"/callback\") {\n res.writeHead(404);\n res.end();\n return;\n }\n const code = url.searchParams.get(\"code\");\n const state = url.searchParams.get(\"state\");\n const error = url.searchParams.get(\"error\");\n\n if (error) {\n res.writeHead(400, { \"content-type\": \"text/html; charset=utf-8\" });\n res.end(renderErrorPage(error));\n rejecter(new Error(`OAuth error: ${error}`));\n return;\n }\n if (!code || !state) {\n res.writeHead(400, { \"content-type\": \"text/html; charset=utf-8\" });\n res.end(renderErrorPage(\"missing code or state\"));\n rejecter(new Error(\"OAuth callback missing code or state\"));\n return;\n }\n if (state !== expectedState) {\n res.writeHead(400, { \"content-type\": \"text/html; charset=utf-8\" });\n res.end(renderErrorPage(\"state mismatch\"));\n rejecter(new Error(\"OAuth state mismatch — possible CSRF\"));\n return;\n }\n res.writeHead(200, { \"content-type\": \"text/html; charset=utf-8\" });\n res.end(renderSuccessPage());\n resolver({ code });\n };\n\n for (const port of OAUTH_PORTS) {\n const server = createServer(handler);\n try {\n await new Promise<void>((res, rej) => {\n server.once(\"error\", rej);\n server.listen(port, \"127.0.0.1\", () => res());\n });\n // Bound successfully. Wire up cleanup on abort / completion.\n signal.addEventListener(\"abort\", () => {\n server.close();\n rejecter(new Error(\"OAuth aborted\"));\n });\n result.finally(() => server.close()).catch(() => {});\n return { port, result };\n } catch (err) {\n // EADDRINUSE — try the next port.\n server.close();\n if (\n err instanceof Error &&\n \"code\" in err &&\n (err as NodeJS.ErrnoException).code === \"EADDRINUSE\"\n ) {\n continue;\n }\n throw err;\n }\n }\n\n throw new Error(\n `All OAuth ports occupied: ${OAUTH_PORTS.join(\", \")}. Close other dev servers and retry.`,\n );\n}\n\n/**\n * Build the SaaS authorize URL. The SaaS authorize page expects:\n * client_id, redirect_uri, state, site_url, scope, response_type,\n * code_challenge, code_challenge_method\n *\n * `site_url` is the customer's project URL — used by the SaaS to\n * find-or-create the site row and tag the OAuth token to it. For\n * the wizard, we send the current working directory's git remote\n * (or a placeholder when none exists; SaaS uses it for display only).\n */\nfunction buildAuthorizeUrl(opts: {\n port: number;\n state: string;\n codeChallenge: string;\n siteUrl: string;\n}): string {\n const params = new URLSearchParams({\n client_id: WIZARD_CLIENT_ID,\n redirect_uri: `http://localhost:${opts.port}/callback`,\n state: opts.state,\n site_url: opts.siteUrl,\n scope: SCOPES.join(\" \"),\n response_type: \"code\",\n code_challenge: opts.codeChallenge,\n code_challenge_method: \"S256\",\n });\n return `${SAAS_URL}/oauth/authorize?${params.toString()}`;\n}\n\n/**\n * Exchange the authorization code for an access token. Public client\n * — no client_secret, code_verifier proves possession instead.\n */\nasync function exchangeCodeForToken(opts: {\n code: string;\n codeVerifier: string;\n port: number;\n siteUrl: string;\n}): Promise<OAuthResult> {\n const response = await fetch(`${SAAS_URL}/api/oauth/token`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({\n grant_type: \"authorization_code\",\n code: opts.code,\n redirect_uri: `http://localhost:${opts.port}/callback`,\n client_id: WIZARD_CLIENT_ID,\n code_verifier: opts.codeVerifier,\n site_url: opts.siteUrl,\n }),\n });\n\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n throw new Error(\n `Token exchange failed: HTTP ${response.status} — ${text.slice(0, 200)}`,\n );\n }\n\n const data = (await response.json()) as {\n access_token?: string;\n refresh_token?: string;\n expires_in?: number;\n site_id?: string | null;\n };\n\n if (!data.access_token || !data.refresh_token) {\n throw new Error(\"Token response missing access_token or refresh_token\");\n }\n\n return {\n accessToken: data.access_token,\n refreshToken: data.refresh_token,\n expiresIn: data.expires_in ?? 1800,\n siteId: data.site_id ?? null,\n };\n}\n\n/**\n * Top-level OAuth orchestrator. Caller passes a `siteUrl` (typically\n * `git remote get-url origin` output or a placeholder) and an\n * `onUrlReady` callback so the TUI can display the URL for SSH\n * fallback at the same time the browser is opened.\n *\n * Returns the token bundle on success; throws on timeout, state\n * mismatch, or token exchange failure.\n */\nexport async function performOAuthFlow(opts: {\n siteUrl: string;\n onUrlReady: (url: string) => void;\n}): Promise<OAuthResult> {\n const verifier = generateCodeVerifier();\n const challenge = generateCodeChallenge(verifier);\n const state = generateState();\n\n const controller = new AbortController();\n const timeoutHandle = setTimeout(\n () => controller.abort(),\n OAUTH_TIMEOUT_MS,\n );\n\n try {\n const { port, result } = await startCallbackServer(state, controller.signal);\n const authorizeUrl = buildAuthorizeUrl({\n port,\n state,\n codeChallenge: challenge,\n siteUrl: opts.siteUrl,\n });\n\n // Notify TUI so URL shows + browser opens concurrently.\n opts.onUrlReady(authorizeUrl);\n // `open` swallows failures (returns a ChildProcess); SSH users\n // see the URL in the TUI and copy/paste it manually.\n open(authorizeUrl).catch(() => {});\n\n const { code } = await result;\n const tokens = await exchangeCodeForToken({\n code,\n codeVerifier: verifier,\n port,\n siteUrl: opts.siteUrl,\n });\n return tokens;\n } finally {\n clearTimeout(timeoutHandle);\n }\n}\n\n// ── Callback HTML pages ────────────────────────────────────────\n\nfunction renderSuccessPage(): string {\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<title>smking — connected</title>\n<style>\n body { font: 14px/1.5 -apple-system, \"Helvetica Neue\", sans-serif; max-width: 480px; margin: 80px auto; padding: 0 24px; color: #222; }\n h1 { font-size: 24px; }\n .hint { color: #666; }\n</style>\n</head>\n<body>\n<h1>✅ Connected to smking</h1>\n<p>You can close this tab and return to your terminal.</p>\n<p class=\"hint\">smking install wizard is now finishing setup.</p>\n<script>setTimeout(() => window.close(), 1500);</script>\n</body>\n</html>`;\n}\n\nfunction renderErrorPage(error: string): string {\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<title>smking — error</title>\n<style>\n body { font: 14px/1.5 -apple-system, \"Helvetica Neue\", sans-serif; max-width: 480px; margin: 80px auto; padding: 0 24px; color: #222; }\n h1 { font-size: 24px; color: #c00; }\n code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; }\n</style>\n</head>\n<body>\n<h1>❌ smking OAuth failed</h1>\n<p>Error: <code>${escapeHtml(error)}</code></p>\n<p>Return to your terminal and re-run <code>npx @soloworks/smking-wizard</code>.</p>\n</body>\n</html>`;\n}\n\nfunction escapeHtml(input: string): string {\n return input\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n","import { useEffect } from \"react\";\nimport { Box, Text } from \"ink\";\nimport { Spinner } from \"@inkjs/ui\";\nimport { useWizardStore } from \"../store\";\nimport { performOAuthFlow } from \"../../oauth\";\n\n/**\n * OAuth screen — kicks off the browser flow on mount and waits for\n * the localhost callback. Displays the authorize URL in the TUI as\n * a fallback for SSH users who don't have a graphical browser\n * (mirroring PostHog's pattern).\n *\n * Lifecycle:\n * 1. mount → performOAuthFlow() starts\n * 2. callback server emits URL via onUrlReady → store.setOauthUrl\n * 3. user logs in + redirects to localhost → callback fires\n * 4. OAuth tokens stored → setScreen(\"done\")\n * 5. any error → setFatal() → screen routes to \"error\"\n */\nexport function OAuthScreen() {\n const oauthUrl = useWizardStore((s) => s.oauthUrl);\n const setOauthUrl = useWizardStore((s) => s.setOauthUrl);\n const setOauth = useWizardStore((s) => s.setOauth);\n const setScreen = useWizardStore((s) => s.setScreen);\n const setFatal = useWizardStore((s) => s.setFatal);\n\n useEffect(() => {\n let cancelled = false;\n performOAuthFlow({\n // For Phase 3 the wizard CLI doesn't know the project's git\n // remote yet (framework detection lands in Phase 4). Send a\n // placeholder; SaaS uses site_url for display only on the\n // consent screen, not for routing.\n siteUrl: process.cwd(),\n onUrlReady: (url) => {\n if (!cancelled) setOauthUrl(url);\n },\n })\n .then((tokens) => {\n if (cancelled) return;\n setOauth(tokens);\n // Route into the run screen so the agent can take over.\n // RunScreen mounts → kicks off the agent loop on its useEffect.\n setScreen(\"run\");\n })\n .catch((err: unknown) => {\n if (cancelled) return;\n const msg = err instanceof Error ? err.message : String(err);\n setFatal(`OAuth failed: ${msg}`);\n });\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return (\n <Box flexDirection=\"column\" gap={1}>\n <Box>\n <Spinner label=\"Waiting for browser login…\" />\n </Box>\n\n {oauthUrl ? (\n <Box flexDirection=\"column\" marginTop={1}>\n <Text color=\"gray\">\n If your browser didn't open, paste this URL into a browser:\n </Text>\n <Box marginTop={1}>\n <Text color=\"cyan\">{oauthUrl}</Text>\n </Box>\n </Box>\n ) : (\n <Text color=\"gray\">Generating PKCE challenge…</Text>\n )}\n\n <Box marginTop={1}>\n <Text color=\"gray\">Timeout: 6 minutes · Ctrl-C to abort</Text>\n </Box>\n </Box>\n );\n}\n","/**\n * System-prompt rules appended to whatever Claude's default coding\n * agent persona provides. Kept deliberately short — 11 numbered\n * rules, no preamble, no explanation. The install prompt itself\n * carries the framework-specific instructions; commandments cover\n * what's true regardless of framework.\n *\n * Each rule maps to one of the wizard's safety guards or design\n * decisions documented in the implementation plan. Don't add prose\n * paragraphs here — every word costs every wizard run a token.\n */\nexport const COMMANDMENTS = `# Wizard agent commandments\n\nYou are the install agent inside @soloworks/smking-wizard. The user's project is open in the cwd. Follow these rules without exception:\n\n1. Use only the dedicated tools listed below. You have NO Bash, NO general Read/Write/Edit. The available tools are: \\`detect_framework\\`, \\`detect_package_manager\\`, \\`install_package\\`, \\`set_env\\`, \\`run_doctor\\`, \\`read_project_file\\`, \\`run_artisan\\` (Laravel only), and \\`report_failure\\`. If a step seems to need a different tool, that step is out of scope.\n\n2. Start by calling \\`detect_framework\\`. If it returns \\`unknown\\`, call \\`report_failure\\` with the evidence string and stop — do not guess.\n\n3. After detecting framework, call \\`install_package\\` once with the detected framework.\n\n4. After \\`install_package\\` succeeds, call \\`set_env\\` with SMKING_API_KEY and SMKING_BASE_URL extracted from the install prompt the user gave you. Both must match the values in the prompt verbatim.\n\n5. After \\`set_env\\`, call \\`run_doctor\\`. Parse the JSON result.\n\n6. If \\`run_doctor\\` returns \\`ok: true\\`, you are done. Output a one-line confirmation and stop.\n\n7. If \\`run_doctor\\` returns \\`ok: false\\`, identify which check failed and try to fix it BEFORE giving up. Diagnose first, retry second:\n - For Laravel cache/config-stale symptoms (API reachable: fail but env is set correctly; doctor flips red right after \\`set_env\\`): try \\`run_artisan(command=\"config:clear\")\\` then \\`run_artisan(command=\"cache:clear\")\\`, then re-run \\`run_doctor\\`. This solves a common stale-config scenario.\n - For \"X-Smking-Status: server_error\" / circuit-breaker symptoms: try \\`run_artisan(command=\"smking:cache:purge\")\\`, then re-run \\`run_doctor\\`.\n - For unknown failures, use \\`read_project_file\\` to inspect \\`composer.json\\` (version pin), \\`app/Http/Kernel.php\\` (middleware register state), or \\`.env\\` (actual values). The content often reveals the root cause and tells you whether a retry has any chance.\n - Only retry \\`install_package\\` or \\`set_env\\` when the inspected state confirms those are the layer to fix.\n - Maximum THREE retries for the same failing check across ALL fix attempts combined (not three retries per fix type).\n\n8. After three failed retries on the same check — or when \\`read_project_file\\` reveals an unfixable condition (Laravel version too old, custom Kernel, missing PHP extension) — call \\`report_failure\\` with the failed check list, environment details, the raw doctor output, AND any relevant content from \\`read_project_file\\` that explains the root cause. Then stop.\n\n9. NEVER attempt to use git, edit files outside the wizard tools, run shell commands, or install packages other than smking SDKs. The wizard tools are the complete surface.\n\n10. NEVER include API keys, secrets, or token values in your text response. If you need to mention them, refer by name (e.g. \"SMKING_API_KEY\") not by value.\n\n11. Be terse. The user sees a TUI with a spinner — every paragraph you emit is a paragraph they have to wait through. One sentence per major action is enough.`;\n","import { SAAS_URL } from \"../constants\";\nimport type { Framework } from \"../detection/framework\";\n\n/**\n * Fetch the install prompt for the detected framework from the\n * smking SaaS. The prompt content lives in\n * `apps/web/src/features/aeo/lib/install-prompts/{laravel,nextjs}.ts`\n * and has the customer's `publicApiKey` and `baseUrl` already\n * substituted server-side — wizard never receives a raw key\n * separately from the prompt itself.\n *\n * The agent reads this markdown as its initial user message, then\n * walks through the steps using the wizard's dedicated tools.\n */\nexport async function fetchInstallPrompt(opts: {\n framework: Framework;\n oauthToken: string;\n}): Promise<string> {\n if (opts.framework !== \"laravel\" && opts.framework !== \"nextjs\") {\n throw new Error(\n `fetchInstallPrompt called with unsupported framework: ${opts.framework}`,\n );\n }\n\n const url = new URL(`${SAAS_URL}/api/v1/wizard/install-prompt`);\n url.searchParams.set(\"framework\", opts.framework);\n\n const response = await fetch(url, {\n headers: {\n authorization: `Bearer ${opts.oauthToken}`,\n accept: \"text/markdown\",\n },\n });\n\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n throw new Error(\n `Failed to fetch install prompt: HTTP ${response.status} — ${text.slice(0, 200)}`,\n );\n }\n\n return response.text();\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\n/**\n * Framework detection for the wizard. Walks the cwd looking for\n * deterministic signals — never asks the LLM what framework it is,\n * because that's the kind of question we can answer 100% correctly\n * from file presence checks alone.\n *\n * Order matters: Laravel comes before Next.js. If a project somehow\n * has both `artisan` AND `package.json` with `next`, the wizard\n * picks Laravel because that's the more specific signal (PHP\n * project structure is harder to false-positive on).\n */\n\nexport type Framework = \"laravel\" | \"nextjs\" | \"unknown\";\n\nexport type FrameworkDetection = {\n framework: Framework;\n evidence: string;\n appDir?: string; // For Next.js: `app/` or `src/app/`\n};\n\nexport function detectFramework(cwd: string = process.cwd()): FrameworkDetection {\n // ── Laravel ────────────────────────────────────────────────\n // `artisan` is unique to Laravel — no other framework ships it.\n // composer.json with `laravel/framework` is a secondary signal we\n // could use but `artisan` alone is sufficient.\n const artisanPath = join(cwd, \"artisan\");\n if (existsSync(artisanPath)) {\n return {\n framework: \"laravel\",\n evidence: `artisan present at ${artisanPath}`,\n };\n }\n\n // ── Next.js ────────────────────────────────────────────────\n // `package.json` with `next` in dependencies/devDependencies +\n // an `app/` or `src/app/` directory (App Router only — Pages\n // Router isn't supported by `<SmkingAEO />`).\n const pkgPath = join(cwd, \"package.json\");\n if (existsSync(pkgPath)) {\n let pkg: { dependencies?: Record<string, string>; devDependencies?: Record<string, string> };\n try {\n pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\"));\n } catch {\n return {\n framework: \"unknown\",\n evidence: \"package.json present but unparseable\",\n };\n }\n\n const hasNext =\n !!pkg.dependencies?.[\"next\"] || !!pkg.devDependencies?.[\"next\"];\n if (hasNext) {\n const appDir = [\"app\", \"src/app\"].find((d) =>\n existsSync(join(cwd, d)),\n );\n if (!appDir) {\n return {\n framework: \"unknown\",\n evidence:\n \"Next.js detected but no app/ or src/app/ — wizard only supports App Router\",\n };\n }\n return {\n framework: \"nextjs\",\n evidence: `next in package.json + ${appDir}/ directory`,\n appDir,\n };\n }\n }\n\n return {\n framework: \"unknown\",\n evidence:\n \"no `artisan` (Laravel) and no `package.json` with `next` (Next.js) found in cwd\",\n };\n}\n","import { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\n/**\n * Package manager detection — lockfile presence is the canonical\n * signal. Order matters in case multiple lockfiles coexist (which\n * is itself a customer-side bug, but we still pick one):\n * pnpm > bun > yarn > npm\n *\n * pnpm wins ties because smking's monorepo uses pnpm and the\n * wizard's default install path is pnpm-friendly.\n */\n\nexport type NodePackageManager = \"pnpm\" | \"bun\" | \"yarn\" | \"npm\";\n\nconst LOCKFILES: Array<{ name: string; pm: NodePackageManager }> = [\n { name: \"pnpm-lock.yaml\", pm: \"pnpm\" },\n { name: \"bun.lockb\", pm: \"bun\" },\n { name: \"bun.lock\", pm: \"bun\" },\n { name: \"yarn.lock\", pm: \"yarn\" },\n { name: \"package-lock.json\", pm: \"npm\" },\n];\n\nexport type PackageManagerDetection = {\n packageManager: NodePackageManager;\n lockfile: string | null;\n};\n\nexport function detectNodePackageManager(\n cwd: string = process.cwd(),\n): PackageManagerDetection {\n for (const { name, pm } of LOCKFILES) {\n if (existsSync(join(cwd, name))) {\n return { packageManager: pm, lockfile: name };\n }\n }\n // No lockfile — assume npm as the lowest common denominator.\n return { packageManager: \"npm\", lockfile: null };\n}\n","import { spawn } from \"node:child_process\";\n\n/**\n * Thin promise wrapper around `child_process.spawn`. Captures\n * stdout + stderr + exit code so installers can inspect output to\n * detect \"package already installed\" idempotency.\n *\n * Why not `execa`? — we'd pull in a runtime dependency just for\n * convenience around the Node stdlib. The Node API is fine for our\n * narrow needs (install commands, no shell interpolation).\n */\n\nexport type ExecResult = {\n stdout: string;\n stderr: string;\n code: number;\n};\n\nexport function run(\n cmd: string,\n args: string[],\n opts: {\n cwd?: string;\n env?: NodeJS.ProcessEnv;\n timeoutMs?: number;\n } = {},\n): Promise<ExecResult> {\n return new Promise((resolve, reject) => {\n const proc = spawn(cmd, args, {\n cwd: opts.cwd ?? process.cwd(),\n env: { ...process.env, ...opts.env },\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let stdout = \"\";\n let stderr = \"\";\n proc.stdout?.on(\"data\", (chunk) => {\n stdout += chunk.toString();\n });\n proc.stderr?.on(\"data\", (chunk) => {\n stderr += chunk.toString();\n });\n\n let timer: NodeJS.Timeout | undefined;\n if (opts.timeoutMs) {\n timer = setTimeout(() => {\n proc.kill(\"SIGTERM\");\n }, opts.timeoutMs);\n }\n\n proc.on(\"error\", (err) => {\n if (timer) clearTimeout(timer);\n reject(err);\n });\n proc.on(\"close\", (code) => {\n if (timer) clearTimeout(timer);\n resolve({ stdout, stderr, code: code ?? 1 });\n });\n });\n}\n","import { existsSync, readFileSync, writeFileSync } from \"node:fs\";\n\n/**\n * Append or update env keys in a `.env` / `.env.local` file.\n * - If a key already exists with the same value: no-op (idempotent).\n * - If a key exists with a different value: update in place.\n * - If a key is new: append at the end with a one-line comment\n * crediting the wizard (so the customer can grep back to find\n * what added it).\n *\n * Preserves all unrelated content (comments, blank lines, other\n * keys). Won't touch quoting style — if the file uses `KEY=\"value\"`,\n * we write `KEY=value` for new keys; that's the standard format\n * and customer-side dotenv libraries handle both.\n */\n\nexport type EnvFileChange = {\n changed: string[];\n unchanged: string[];\n};\n\nexport function setEnvKeys(\n envPath: string,\n updates: Record<string, string>,\n): EnvFileChange {\n const existing = existsSync(envPath)\n ? readFileSync(envPath, \"utf-8\")\n : \"\";\n const lines = existing.split(\"\\n\");\n\n const changed: string[] = [];\n const unchanged: string[] = [];\n\n for (const [key, value] of Object.entries(updates)) {\n const idx = lines.findIndex((line) => {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) return false;\n return trimmed.startsWith(`${key}=`);\n });\n\n if (idx >= 0) {\n const currentLine = lines[idx];\n const currentValue = currentLine\n .slice(currentLine.indexOf(\"=\") + 1)\n // Strip optional matching quotes for comparison only.\n .replace(/^[\"']|[\"']$/g, \"\");\n if (currentValue === value) {\n unchanged.push(key);\n continue;\n }\n lines[idx] = `${key}=${value}`;\n changed.push(key);\n } else {\n // Append. Add a separating blank line + comment block if the\n // file doesn't already end with our marker.\n const hasOurMarker = lines.some((l) =>\n l.includes(\"Added by @soloworks/smking-wizard\"),\n );\n if (!hasOurMarker) {\n if (lines.at(-1)?.trim() !== \"\") {\n lines.push(\"\");\n }\n lines.push(\"# Added by @soloworks/smking-wizard\");\n }\n lines.push(`${key}=${value}`);\n changed.push(key);\n }\n }\n\n if (changed.length > 0) {\n writeFileSync(envPath, lines.join(\"\\n\"), \"utf-8\");\n }\n\n return { changed, unchanged };\n}\n","/**\n * Live registry queries for the latest published smking SDK versions.\n *\n * Why this exists: hardcoding a version (`\"smking/laravel:^0.10\"`) in\n * the installer goes stale the moment we ship a new minor — every\n * wizard `npx` run would then install the *previous* major.minor line\n * even though packagist already has a newer one. Querying the registry\n * at install time means the wizard always proposes the freshest stable\n * release without us re-cutting a wizard build.\n *\n * Failure mode: network down / registry returning 500 / unparseable\n * response → callers fall back to bare `composer require <pkg>` (no\n * constraint). That still installs the latest version compatible with\n * the customer's existing `composer.json` — not ideal if they're\n * pinned to an old `^0.X`, but never breaks the install.\n */\n\nconst REGISTRY_TIMEOUT_MS = 5_000;\n\nexport type LatestVersion = {\n /** Bare semver string, e.g. `\"0.10.1\"` */\n version: string;\n /** Caret range for use as a constraint, e.g. `\"^0.10\"` */\n caretRange: string;\n /** Source registry, for logging/debug */\n source: \"packagist\" | \"npm\";\n};\n\n/**\n * Convert `\"0.10.1\"` → `\"^0.10\"`. Pins major.minor so consumers get\n * the latest patch in this line automatically. For composer 0.x:\n * `^0.10` resolves to `>=0.10.0 <0.11.0`. For npm same semantics.\n */\nexport function toCaretRange(version: string): string {\n const match = version.match(/^(\\d+)\\.(\\d+)/);\n if (!match) throw new Error(`unparseable version string: ${version}`);\n return `^${match[1]}.${match[2]}`;\n}\n\n/**\n * `https://repo.packagist.org/p2/<name>.json` returns the metadata\n * with `packages.<name>` as a newest-first array of `{version, ...}`.\n * We pick the first entry matching a clean semver tag (no `-rc`,\n * `-dev`, `-alpha`).\n */\nexport async function getLatestPackagistVersion(\n packageName: string,\n): Promise<LatestVersion> {\n const url = `https://repo.packagist.org/p2/${packageName}.json`;\n const res = await fetch(url, {\n signal: AbortSignal.timeout(REGISTRY_TIMEOUT_MS),\n headers: { accept: \"application/json\" },\n });\n if (!res.ok) {\n throw new Error(`packagist ${packageName} HTTP ${res.status}`);\n }\n const data = (await res.json()) as {\n packages?: Record<string, Array<{ version?: string }>>;\n };\n const versions = data.packages?.[packageName] ?? [];\n // Packagist tags look like `v0.10.1`. Reject anything with a `-`\n // qualifier (prereleases) or `dev-` prefix (branch tags).\n const stable = versions.find((v) => {\n const ver = v.version ?? \"\";\n return /^v?\\d+\\.\\d+\\.\\d+$/.test(ver);\n });\n if (!stable?.version) {\n throw new Error(`packagist ${packageName}: no stable version found`);\n }\n const version = stable.version.replace(/^v/, \"\");\n return { version, caretRange: toCaretRange(version), source: \"packagist\" };\n}\n\n/**\n * npm root metadata returns `dist-tags.latest` — the version the\n * publisher tagged as the default for `npm install <name>` (with no\n * version specifier). This is what we want for \"install the freshest\n * stable line\"; explicit prerelease tags like `next` / `beta` are\n * intentionally ignored.\n */\nexport async function getLatestNpmVersion(\n packageName: string,\n): Promise<LatestVersion> {\n // URL-encode the scope slash for `@org/pkg` names — npm registry\n // accepts both `@scope/name` and `@scope%2Fname`, but encoding is\n // safer for fetch URL parsers.\n const encoded = packageName.replace(\"/\", \"%2F\");\n const url = `https://registry.npmjs.org/${encoded}`;\n const res = await fetch(url, {\n signal: AbortSignal.timeout(REGISTRY_TIMEOUT_MS),\n headers: { accept: \"application/json\" },\n });\n if (!res.ok) {\n throw new Error(`npm ${packageName} HTTP ${res.status}`);\n }\n const data = (await res.json()) as {\n \"dist-tags\"?: { latest?: string };\n };\n const latest = data[\"dist-tags\"]?.latest;\n if (!latest) {\n throw new Error(`npm ${packageName}: no latest tag`);\n }\n return { version: latest, caretRange: toCaretRange(latest), source: \"npm\" };\n}\n","import { join } from \"node:path\";\nimport { run } from \"../lib/exec\";\nimport { setEnvKeys } from \"../lib/env-file\";\nimport { getLatestPackagistVersion } from \"../lib/registry\";\n\n/**\n * Laravel installer — runs the deterministic command sequence from\n * `apps/web/src/features/aeo/lib/install-prompts/laravel.ts`. No\n * LLM involved; this is a fixed sequence that's been validated\n * end-to-end on the LocalWP test environment.\n *\n * Steps:\n * 1. `composer require smking/laravel` — pulls package + auto-runs\n * service provider auto-discovery via Laravel's package event.\n * 2. `php artisan vendor:publish --tag=smking-config` — copies\n * `config/smking.php` to the customer's repo so they can edit\n * `only` / `except` patterns later.\n * 3. Write SMKING_API_KEY + SMKING_BASE_URL to `.env`.\n * 4. `php artisan config:clear` — invalidates Laravel's config\n * cache so the new env vars take effect on next request.\n */\n\nexport type LaravelInstallContext = {\n apiKey: string;\n baseUrl: string;\n cwd: string;\n};\n\nexport type InstallStep = {\n name: string;\n status: \"done\" | \"skipped\" | \"failed\";\n detail?: string;\n};\n\nexport type InstallResult = {\n ok: boolean;\n steps: InstallStep[];\n};\n\nexport async function installLaravel(\n ctx: LaravelInstallContext,\n): Promise<InstallResult> {\n const steps: InstallStep[] = [];\n\n // ── Step 1: composer require ──────────────────────────────\n // Query packagist for the latest stable version so we always bump\n // the customer's composer.json constraint to the freshest line —\n // without that, a customer pinned to \"^0.7\" would silently get\n // v0.7.4 even though packagist has v0.10.x. Falls back to the\n // bare `composer require` (no constraint) if the registry is\n // unreachable so install never hard-fails on a network blip.\n let constraint = \"smking/laravel\";\n let bumpedTo: string | null = null;\n try {\n const latest = await getLatestPackagistVersion(\"smking/laravel\");\n constraint = `smking/laravel:${latest.caretRange}`;\n bumpedTo = latest.version;\n } catch {\n // Registry query failed — proceed without constraint. Agent\n // can still inspect composer.json afterwards via\n // `read_project_file` to detect stale-version symptoms.\n }\n const composerResult = await run(\n \"composer\",\n [\"require\", constraint],\n { cwd: ctx.cwd, timeoutMs: 180_000 },\n );\n if (composerResult.code !== 0) {\n steps.push({\n name: `composer require ${constraint}`,\n status: \"failed\",\n detail: composerResult.stderr.slice(0, 500),\n });\n return { ok: false, steps };\n }\n // Composer's \"is already installed\" message vs first-time install\n // — both are success, but expose to the user which path we took.\n const alreadyInstalled = composerResult.stdout.includes(\n \"is already in the composer.json\",\n );\n steps.push({\n name: `composer require ${constraint}`,\n status: alreadyInstalled ? \"skipped\" : \"done\",\n detail: bumpedTo\n ? `latest packagist: v${bumpedTo} (caret bumped)`\n : \"registry query failed — installed at existing constraint\",\n });\n\n // ── Step 2: vendor:publish ────────────────────────────────\n const publishResult = await run(\n \"php\",\n [\"artisan\", \"vendor:publish\", \"--tag=smking-config\"],\n { cwd: ctx.cwd, timeoutMs: 60_000 },\n );\n if (publishResult.code !== 0) {\n steps.push({\n name: \"php artisan vendor:publish --tag=smking-config\",\n status: \"failed\",\n detail: publishResult.stderr.slice(0, 500),\n });\n return { ok: false, steps };\n }\n steps.push({\n name: \"php artisan vendor:publish --tag=smking-config\",\n status: \"done\",\n });\n\n // ── Step 3: .env keys ─────────────────────────────────────\n // Only write env keys when caller supplied real values. The wizard\n // agent path passes placeholder strings (because the real values\n // live in the install prompt the agent parses, then sets via the\n // dedicated `set_env` tool) — writing those placeholders into the\n // customer's .env would leave it polluted if the agent later\n // aborted before `set_env` ran (e.g. doctor retry exhaustion).\n // Empty / placeholder values short-circuit so this installer stays\n // a pure \"package install\" step and env handling is owned by\n // `set_env`. Callers wanting one-shot install+env still work —\n // they pass real `pk_...` / `https://...` strings.\n const envValueLooksReal =\n ctx.apiKey &&\n ctx.baseUrl &&\n !ctx.apiKey.startsWith(\"<\") &&\n !ctx.baseUrl.startsWith(\"<\");\n if (envValueLooksReal) {\n const envChange = setEnvKeys(join(ctx.cwd, \".env\"), {\n SMKING_API_KEY: ctx.apiKey,\n SMKING_BASE_URL: ctx.baseUrl,\n });\n steps.push({\n name: \"Write SMKING_API_KEY + SMKING_BASE_URL to .env\",\n status: envChange.changed.length > 0 ? \"done\" : \"skipped\",\n detail:\n envChange.changed.length > 0\n ? `wrote: ${envChange.changed.join(\", \")}`\n : `already set: ${envChange.unchanged.join(\", \")}`,\n });\n } else {\n steps.push({\n name: \"Write SMKING_API_KEY + SMKING_BASE_URL to .env\",\n status: \"skipped\",\n detail: \"env handled by separate set_env step (wizard agent path)\",\n });\n }\n\n // ── Step 4: config:clear ──────────────────────────────────\n const clearResult = await run(\n \"php\",\n [\"artisan\", \"config:clear\"],\n { cwd: ctx.cwd, timeoutMs: 30_000 },\n );\n if (clearResult.code !== 0) {\n // Non-fatal — the env change will take effect on next config\n // rebuild anyway. Surface as info, not failure.\n steps.push({\n name: \"php artisan config:clear\",\n status: \"failed\",\n detail: clearResult.stderr.slice(0, 500),\n });\n // Continue — install is essentially done, just with stale cache.\n } else {\n steps.push({ name: \"php artisan config:clear\", status: \"done\" });\n }\n\n return { ok: true, steps };\n}\n","import { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { run } from \"../lib/exec\";\nimport { setEnvKeys } from \"../lib/env-file\";\nimport { getLatestNpmVersion } from \"../lib/registry\";\nimport type { NodePackageManager } from \"../detection/package-manager\";\nimport type { InstallStep, InstallResult } from \"./laravel\";\n\n/**\n * Next.js installer — runs the deterministic command sequence from\n * `apps/web/src/features/aeo/lib/install-prompts/nextjs.ts`:\n *\n * 1. `<pm> add @soloworks/smking-next` (pm = pnpm | npm | yarn | bun)\n * 2. Write SMKING_API_KEY + SMKING_BASE_URL to `.env.local`.\n * 3. Add `<SmkingAEO />` import + render to `app/layout.tsx`\n * (idempotent — skips if already present).\n *\n * App Router only. Pages Router isn't supported by the SDK.\n */\n\nexport type NextjsInstallContext = {\n apiKey: string;\n baseUrl: string;\n cwd: string;\n packageManager: NodePackageManager;\n appDir: string; // \"app\" or \"src/app\"\n};\n\nconst PM_ADD_ARGS: Record<NodePackageManager, string[]> = {\n pnpm: [\"add\"],\n bun: [\"add\"],\n yarn: [\"add\"],\n npm: [\"install\"],\n};\n\nexport async function installNextjs(\n ctx: NextjsInstallContext,\n): Promise<InstallResult> {\n const steps: InstallStep[] = [];\n\n // ── Step 1: package install ───────────────────────────────\n // Query npm for the latest stable version so we bump customer's\n // package.json constraint to the freshest major.minor line. See\n // installers/laravel.ts Step 1 for the same rationale on the\n // packagist side. Falls back to no-version-specifier install if\n // the npm registry query fails.\n let packageSpec = \"@soloworks/smking-next\";\n let bumpedTo: string | null = null;\n try {\n const latest = await getLatestNpmVersion(\"@soloworks/smking-next\");\n packageSpec = `@soloworks/smking-next@${latest.caretRange}`;\n bumpedTo = latest.version;\n } catch {\n // Network / npm down — install at existing constraint. Agent\n // can `read_project_file` package.json afterwards to detect\n // stale-version symptoms.\n }\n const addArgs = [...PM_ADD_ARGS[ctx.packageManager], packageSpec];\n const installResult = await run(ctx.packageManager, addArgs, {\n cwd: ctx.cwd,\n timeoutMs: 180_000,\n });\n if (installResult.code !== 0) {\n steps.push({\n name: `${ctx.packageManager} ${addArgs.join(\" \")}`,\n status: \"failed\",\n detail: installResult.stderr.slice(0, 500),\n });\n return { ok: false, steps };\n }\n steps.push({\n name: `${ctx.packageManager} ${addArgs.join(\" \")}`,\n status: \"done\",\n detail: bumpedTo\n ? `latest npm: v${bumpedTo} (caret bumped)`\n : \"registry query failed — installed at existing constraint\",\n });\n\n // ── Step 2: .env.local keys ───────────────────────────────\n // See installers/laravel.ts Step 3 for the rationale: when the wizard\n // agent calls this with placeholder values, skip — env is handled by\n // the agent's separate `set_env` tool. Only write when caller supplied\n // real values (one-shot install path outside the agent).\n const envValueLooksReal =\n ctx.apiKey &&\n ctx.baseUrl &&\n !ctx.apiKey.startsWith(\"<\") &&\n !ctx.baseUrl.startsWith(\"<\");\n if (!envValueLooksReal) {\n steps.push({\n name: \"Write SMKING_API_KEY + SMKING_BASE_URL to .env.local\",\n status: \"skipped\",\n detail: \"env handled by separate set_env step (wizard agent path)\",\n });\n } else {\n const envChange = setEnvKeys(join(ctx.cwd, \".env.local\"), {\n SMKING_API_KEY: ctx.apiKey,\n SMKING_BASE_URL: ctx.baseUrl,\n });\n steps.push({\n name: \"Write SMKING_API_KEY + SMKING_BASE_URL to .env.local\",\n status: envChange.changed.length > 0 ? \"done\" : \"skipped\",\n detail:\n envChange.changed.length > 0\n ? `wrote: ${envChange.changed.join(\", \")}`\n : `already set: ${envChange.unchanged.join(\", \")}`,\n });\n }\n\n // ── Step 3: layout.tsx edit ───────────────────────────────\n const layoutResult = addSmkingAEOToLayout(ctx.cwd, ctx.appDir);\n steps.push(layoutResult);\n if (layoutResult.status === \"failed\") {\n return { ok: false, steps };\n }\n\n return { ok: true, steps };\n}\n\n/**\n * Find `<appDir>/layout.tsx` (or .jsx/.ts/.js), inject SmkingAEO\n * import + render. Idempotent: if `SmkingAEO` substring is already\n * present, returns skipped without re-editing.\n *\n * Uses targeted string manipulation rather than full AST parsing\n * because:\n * - The edit shape is fixed (import + JSX inside <body>)\n * - Layout files are conventional Next.js boilerplate; the\n * vanilla case covers 95% of customers\n * - Edge cases (custom layouts, no <body>) gracefully degrade\n * to \"manual instructions in error message\"\n *\n * AST parsing (magicast / recast) is an option we may revisit if\n * field reports show this is too fragile.\n */\nfunction addSmkingAEOToLayout(cwd: string, appDir: string): InstallStep {\n const candidates = [\n join(cwd, appDir, \"layout.tsx\"),\n join(cwd, appDir, \"layout.jsx\"),\n join(cwd, appDir, \"layout.ts\"),\n join(cwd, appDir, \"layout.js\"),\n ];\n const layoutPath = candidates.find((p) => existsSync(p));\n if (!layoutPath) {\n return {\n name: \"Inject <SmkingAEO /> into root layout\",\n status: \"failed\",\n detail: `no layout file found under ${appDir}/ — add <SmkingAEO apiKey={process.env.SMKING_API_KEY!} /> to your root layout manually`,\n };\n }\n\n const content = readFileSync(layoutPath, \"utf-8\");\n\n // Idempotency: already imported.\n if (content.includes(\"SmkingAEO\")) {\n return {\n name: \"Inject <SmkingAEO /> into root layout\",\n status: \"skipped\",\n detail: `${layoutPath} already references SmkingAEO`,\n };\n }\n\n // Find last import statement to inject our import after.\n const importRegex = /^import\\s+[^;]+;?\\s*$/gm;\n const importMatches = [...content.matchAll(importRegex)];\n if (importMatches.length === 0) {\n return {\n name: \"Inject <SmkingAEO /> into root layout\",\n status: \"failed\",\n detail: `${layoutPath} has no import statements — add SmkingAEO manually`,\n };\n }\n\n const lastImport = importMatches[importMatches.length - 1];\n const lastImportEnd = lastImport.index + lastImport[0].length;\n const importLine = '\\nimport { SmkingAEO } from \"@soloworks/smking-next\";';\n let updated =\n content.slice(0, lastImportEnd) +\n importLine +\n content.slice(lastImportEnd);\n\n // Find <body> opening tag to inject SmkingAEO after.\n const bodyMatch = updated.match(/<body([^>]*)>/);\n if (!bodyMatch || bodyMatch.index === undefined) {\n return {\n name: \"Inject <SmkingAEO /> into root layout\",\n status: \"failed\",\n detail: `${layoutPath} has no <body> tag — add SmkingAEO manually inside <body>`,\n };\n }\n const bodyEnd = bodyMatch.index + bodyMatch[0].length;\n const injection =\n \"\\n <SmkingAEO apiKey={process.env.SMKING_API_KEY!} />\";\n updated =\n updated.slice(0, bodyEnd) + injection + updated.slice(bodyEnd);\n\n writeFileSync(layoutPath, updated, \"utf-8\");\n return {\n name: \"Inject <SmkingAEO /> into root layout\",\n status: \"done\",\n detail: `wrote import + render to ${layoutPath}`,\n };\n}\n","import Anthropic from \"@anthropic-ai/sdk\";\nimport { detectFramework, type Framework } from \"../detection/framework\";\nimport { detectNodePackageManager } from \"../detection/package-manager\";\nimport { installLaravel } from \"../installers/laravel\";\nimport { installNextjs } from \"../installers/nextjs\";\nimport { setEnvKeys } from \"../lib/env-file\";\nimport { run } from \"../lib/exec\";\nimport { SAAS_URL } from \"../constants\";\nimport { useWizardStore } from \"../ui/store\";\nimport { promises as fs } from \"node:fs\";\nimport { join, relative, resolve } from \"node:path\";\n\n/**\n * The 6 dedicated tools the wizard agent can call. Defined as plain\n * tool descriptors (no SDK helpers) so we can use them with the\n * manual agentic loop in runtime.ts — `client.beta.messages.create`\n * + custom dispatch — instead of `toolRunner` which had reliability\n * issues with our gateway + adaptive thinking combo.\n *\n * Each tool has a JSON Schema input shape (Anthropic-compatible)\n * and a `run` handler that:\n * - pushes a progress message into the store (drives the TUI)\n * - performs the deterministic work\n * - returns a JSON-stringified result (tool_result content shape)\n */\n\ntype ToolContext = {\n cwd: string;\n oauthToken: string;\n siteId: string | null;\n};\n\nexport type WizardToolDef = {\n name: string;\n description: string;\n input_schema: Anthropic.Tool.InputSchema;\n run: (input: unknown) => Promise<string>;\n};\n\nfunction asResult(value: unknown): string {\n return JSON.stringify(value);\n}\n\nfunction stepIcon(status: \"done\" | \"skipped\" | \"failed\"): string {\n return status === \"done\" ? \"✓\" : status === \"skipped\" ? \"⊝\" : \"✗\";\n}\n\nexport function buildWizardTools(ctx: ToolContext): WizardToolDef[] {\n const pushProgress = (text: string) =>\n useWizardStore.getState().pushProgress(text);\n\n return [\n // ── 1. detect_framework ───────────────────────────────────\n {\n name: \"detect_framework\",\n description:\n \"Detect whether the current project is Laravel or Next.js. Returns { framework, evidence, appDir? }. Always call this first before any install steps.\",\n input_schema: {\n type: \"object\",\n properties: {},\n },\n run: async () => {\n pushProgress(\"Detecting framework…\");\n const result = detectFramework(ctx.cwd);\n pushProgress(\n result.framework === \"unknown\"\n ? `Framework: unknown (${result.evidence})`\n : `Framework: ${result.framework}`,\n );\n return asResult(result);\n },\n },\n\n // ── 2. detect_package_manager ─────────────────────────────\n {\n name: \"detect_package_manager\",\n description:\n \"Detect the Node package manager from the project's lockfile (pnpm / bun / yarn / npm). Returns { packageManager, lockfile }.\",\n input_schema: {\n type: \"object\",\n properties: {},\n },\n run: async () => {\n const result = detectNodePackageManager(ctx.cwd);\n pushProgress(\n `Package manager: ${result.packageManager}${\n result.lockfile\n ? ` (${result.lockfile})`\n : \" (no lockfile, assuming npm)\"\n }`,\n );\n return asResult(result);\n },\n },\n\n // ── 3. install_package ────────────────────────────────────\n {\n name: \"install_package\",\n description:\n \"Install the smking SDK for the detected framework. Runs composer require + vendor:publish + config:clear (Laravel), or pnpm/npm/yarn/bun add + edit layout.tsx (Next.js). Idempotent — already-installed steps return 'skipped'. Returns { ok, steps[] }.\",\n input_schema: {\n type: \"object\",\n properties: {\n framework: {\n type: \"string\",\n enum: [\"laravel\", \"nextjs\"],\n description: \"Framework to install for (must match detect_framework result)\",\n },\n },\n required: [\"framework\"],\n },\n run: async (input) => {\n const { framework } = input as { framework: \"laravel\" | \"nextjs\" };\n pushProgress(`Installing smking SDK for ${framework}…`);\n\n if (framework === \"laravel\") {\n const result = await installLaravel({\n apiKey: \"<set-by-set_env>\",\n baseUrl: \"<set-by-set_env>\",\n cwd: ctx.cwd,\n });\n for (const step of result.steps) {\n pushProgress(` ${stepIcon(step.status)} ${step.name}`);\n }\n return asResult(result);\n }\n\n // Next.js — need appDir + packageManager\n const detection = detectFramework(ctx.cwd);\n if (detection.framework !== \"nextjs\" || !detection.appDir) {\n return asResult({\n ok: false,\n steps: [\n {\n name: \"install_nextjs\",\n status: \"failed\",\n detail:\n \"framework re-detection returned non-nextjs — call detect_framework first and confirm\",\n },\n ],\n });\n }\n const pmDetection = detectNodePackageManager(ctx.cwd);\n const result = await installNextjs({\n apiKey: \"<set-by-set_env>\",\n baseUrl: \"<set-by-set_env>\",\n cwd: ctx.cwd,\n packageManager: pmDetection.packageManager,\n appDir: detection.appDir,\n });\n for (const step of result.steps) {\n pushProgress(` ${stepIcon(step.status)} ${step.name}`);\n }\n return asResult(result);\n },\n },\n\n // ── 4. set_env ────────────────────────────────────────────\n {\n name: \"set_env\",\n description:\n \"Write SMKING_API_KEY and/or SMKING_BASE_URL to the project's env file (.env for Laravel, .env.local for Next.js). Other keys are rejected. Idempotent.\",\n input_schema: {\n type: \"object\",\n properties: {\n SMKING_API_KEY: {\n type: \"string\",\n description: \"Publishable site API key (starts with pk_)\",\n },\n SMKING_BASE_URL: {\n type: \"string\",\n description: \"smking SaaS base URL (e.g. https://smking.com)\",\n },\n },\n },\n run: async (input) => {\n pushProgress(\"Setting env values…\");\n const typed = input as {\n SMKING_API_KEY?: string;\n SMKING_BASE_URL?: string;\n };\n // Defensive: only accept SMKING_* keys even if the agent tries\n // to sneak in others. JSON Schema doesn't enforce strictness\n // in Anthropic's tool definitions, so the gate lives here.\n const allowedKeys = new Set([\n \"SMKING_API_KEY\",\n \"SMKING_BASE_URL\",\n ]);\n const updates: Record<string, string> = {};\n for (const [k, v] of Object.entries(typed)) {\n if (!allowedKeys.has(k) || typeof v !== \"string\") continue;\n updates[k] = v;\n }\n if (Object.keys(updates).length === 0) {\n return asResult({\n error:\n \"set_env called with no valid keys. Allowed: SMKING_API_KEY, SMKING_BASE_URL.\",\n });\n }\n const framework = detectFramework(ctx.cwd).framework;\n const envFile = framework === \"nextjs\" ? \".env.local\" : \".env\";\n const result = setEnvKeys(join(ctx.cwd, envFile), updates);\n pushProgress(\n `Env: changed=[${result.changed.join(\", \")}] unchanged=[${result.unchanged.join(\", \")}]`,\n );\n return asResult({ envFile, ...result });\n },\n },\n\n // ── 5. run_doctor ─────────────────────────────────────────\n {\n name: \"run_doctor\",\n description:\n \"Run the smking doctor self-check (php artisan smking:doctor --json for Laravel, smking-next doctor --json for Next.js). Returns structured JSON: { checks: [{name, status, detail}], summary: {passed, failed, info, ok} }.\",\n input_schema: {\n type: \"object\",\n properties: {},\n },\n run: async () => {\n pushProgress(\"Running smking:doctor…\");\n const framework = detectFramework(ctx.cwd).framework;\n\n let cmd: string;\n let args: string[];\n if (framework === \"laravel\") {\n cmd = \"php\";\n args = [\"artisan\", \"smking:doctor\", \"--json\"];\n } else if (framework === \"nextjs\") {\n const pm = detectNodePackageManager(ctx.cwd).packageManager;\n if (pm === \"pnpm\") {\n cmd = \"pnpm\";\n args = [\"exec\", \"smking-next\", \"doctor\", \"--json\"];\n } else if (pm === \"bun\") {\n cmd = \"bunx\";\n args = [\"smking-next\", \"doctor\", \"--json\"];\n } else if (pm === \"yarn\") {\n cmd = \"yarn\";\n args = [\"smking-next\", \"doctor\", \"--json\"];\n } else {\n cmd = \"npx\";\n args = [\"smking-next\", \"doctor\", \"--json\"];\n }\n } else {\n return asResult({\n ok: false,\n error: `Cannot run doctor — framework is ${framework}`,\n });\n }\n\n const result = await run(cmd, args, {\n cwd: ctx.cwd,\n timeoutMs: 60_000,\n });\n\n try {\n const parsed = JSON.parse(result.stdout);\n const failed = parsed.summary?.failed ?? 0;\n pushProgress(\n `Doctor: ${parsed.summary?.passed ?? 0} pass · ${failed} fail · ${parsed.summary?.info ?? 0} info`,\n );\n return asResult(parsed);\n } catch {\n return asResult({\n ok: false,\n error: `Doctor output was not JSON (exit code ${result.code})`,\n stdout: result.stdout.slice(0, 1000),\n stderr: result.stderr.slice(0, 1000),\n });\n }\n },\n },\n\n // ── 6. read_project_file ──────────────────────────────────\n // Inspection tool. Lets the agent look at composer.json /\n // package.json / Kernel.php / layout.tsx / .env etc. so it can\n // diagnose *why* a check failed before deciding whether to\n // retry or report. Heavy safety lives in the handler:\n // - path is resolved relative to cwd; absolute or `..`\n // traversal is rejected\n // - extension allowlist (config / source / env / lock files)\n // prevents fishing for binaries or random docs\n // - content truncated at 50KB to bound agent context spend\n {\n name: \"read_project_file\",\n description:\n \"Read a project file (relative path inside cwd) so you can inspect its content when debugging an install or doctor failure. Use this BEFORE concluding a problem is unfixable — composer.json reveals constraint pins, Kernel.php reveals middleware register state, .env reveals what's actually set. Allowed file types: json, php, ts, tsx, js, jsx, env, conf, yaml, yml, md, lock, htaccess. Returns { path, content, bytes } or { error }.\",\n input_schema: {\n type: \"object\",\n properties: {\n path: {\n type: \"string\",\n description:\n \"Project-relative path, e.g. `composer.json`, `app/Http/Kernel.php`, `.env`, `app/layout.tsx`. No absolute paths, no `..`.\",\n },\n },\n required: [\"path\"],\n },\n run: async (input) => {\n const { path } = input as { path: string };\n pushProgress(`Reading ${path}…`);\n // Safety 1: path traversal\n if (path.startsWith(\"/\") || path.includes(\"..\")) {\n return asResult({\n error: \"absolute paths and `..` traversal are not allowed\",\n });\n }\n const absPath = resolve(ctx.cwd, path);\n // Safety 2: resolved path must still be inside cwd. Defends\n // against symlink-style escapes that path-string checks miss.\n const rel = relative(ctx.cwd, absPath);\n if (rel.startsWith(\"..\") || resolve(rel) === resolve(\"\")) {\n return asResult({ error: \"resolved path escapes cwd\" });\n }\n // Safety 3: extension allowlist. The dotfile shortcuts\n // (.env, .env.local) and well-known dotfiles (.htaccess)\n // are explicitly permitted by basename match below.\n const allowedExt =\n /\\.(json|php|ts|tsx|js|jsx|env|conf|yaml|yml|md|lock|htaccess)$/i;\n const basename = path.split(\"/\").pop() ?? \"\";\n const allowedBasename = new Set([\n \".env\",\n \".env.local\",\n \".env.production\",\n \".env.example\",\n \".htaccess\",\n ]);\n if (!allowedExt.test(basename) && !allowedBasename.has(basename)) {\n return asResult({\n error: `file extension not allowed: ${basename}`,\n });\n }\n try {\n const content = await fs.readFile(absPath, \"utf-8\");\n const truncated =\n content.length > 50_000\n ? content.slice(0, 50_000) + \"\\n... [truncated at 50KB]\"\n : content;\n pushProgress(` read ${path} (${content.length} bytes)`);\n return asResult({\n path,\n content: truncated,\n bytes: content.length,\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return asResult({ error: msg });\n }\n },\n },\n\n // ── 7. run_artisan ────────────────────────────────────────\n // Laravel-only debug action. Allowlisted commands cover the\n // common \"cache/config stale\" recoveries plus inspection. NOT\n // a general `bash` escape hatch — every command is explicitly\n // listed and every flag matches one of a small regex set.\n //\n // Why these commands:\n // - smking:doctor — already used by run_doctor, but exposed\n // here so the agent can re-run after a cache:clear without\n // paying run_doctor's JSON-parse overhead.\n // - smking:cache:purge — clears SDK-side AEO response cache\n // (the sleepytofu 24hr-stale scenario).\n // - smking:status — circuit breaker state inspection.\n // - smking:publish-robots — v0.8.0+ artisan command.\n // - config:clear / config:cache — Laravel config cache cycle\n // after .env changes.\n // - cache:clear — generic app cache.\n // - route:list — confirm SDK routes are mounted (path-takeover).\n {\n name: \"run_artisan\",\n description:\n \"Run a Laravel artisan command from a fixed allowlist. Use for cache/config recovery (cache:clear, config:clear), SDK-specific inspection (smking:status, smking:cache:purge), or route confirmation (route:list). Not for arbitrary fixes — if you need a command not in the allowlist, call report_failure instead. Returns { exitCode, stdout, stderr }.\",\n input_schema: {\n type: \"object\",\n properties: {\n command: {\n type: \"string\",\n enum: [\n \"smking:doctor\",\n \"smking:cache:purge\",\n \"smking:status\",\n \"smking:publish-robots\",\n \"config:clear\",\n \"config:cache\",\n \"cache:clear\",\n \"route:list\",\n ],\n description:\n \"Artisan command name (no `php artisan` prefix). Must be one of the allowlist values.\",\n },\n args: {\n type: \"array\",\n items: { type: \"string\" },\n description:\n \"Optional CLI flags. Allowed forms: `--json`, `--force`, `--tag=<value>`, `--path=<value>`, `--key=<value>`. Other flags are rejected.\",\n },\n },\n required: [\"command\"],\n },\n run: async (input) => {\n const { command, args = [] } = input as {\n command: string;\n args?: string[];\n };\n const framework = detectFramework(ctx.cwd).framework;\n if (framework !== \"laravel\") {\n return asResult({\n error: `run_artisan only works in Laravel projects (detected: ${framework})`,\n });\n }\n // Allowlisted commands (must mirror enum above — defense in\n // depth; JSON schema enum doesn't always block at runtime).\n const ALLOWED_COMMANDS = new Set([\n \"smking:doctor\",\n \"smking:cache:purge\",\n \"smking:status\",\n \"smking:publish-robots\",\n \"config:clear\",\n \"config:cache\",\n \"cache:clear\",\n \"route:list\",\n ]);\n if (!ALLOWED_COMMANDS.has(command)) {\n return asResult({\n error: `command not in allowlist: ${command}`,\n });\n }\n // Flag allowlist — exact-match plain flags + parametric\n // forms with safe value charsets. Reject anything that\n // could shell out (no `;`, `|`, backticks, `$()`).\n const PLAIN_FLAGS = new Set([\"--json\", \"--force\"]);\n const PARAMETRIC = /^--(tag|path|key)=[\\w/.:_\\-+]+$/;\n for (const arg of args) {\n if (!PLAIN_FLAGS.has(arg) && !PARAMETRIC.test(arg)) {\n return asResult({ error: `disallowed flag: ${arg}` });\n }\n }\n pushProgress(`php artisan ${command} ${args.join(\" \")}`.trim());\n const result = await run(\"php\", [\"artisan\", command, ...args], {\n cwd: ctx.cwd,\n timeoutMs: 60_000,\n });\n // Cap output sizes so a chatty command (e.g. route:list on\n // a large app) can't blow agent context.\n return asResult({\n command,\n args,\n exitCode: result.code,\n stdout: result.stdout.slice(0, 5_000),\n stderr: result.stderr.slice(0, 2_000),\n });\n },\n },\n\n // ── 8. report_failure ─────────────────────────────────────\n {\n name: \"report_failure\",\n description:\n \"Report an unfixable install failure to smking support. Call this only after retrying the same failing check 3 times. Posts to smking dashboard. Returns { ticketId }.\",\n input_schema: {\n type: \"object\",\n properties: {\n failed_checks: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n name: { type: \"string\" },\n status: {\n type: \"string\",\n enum: [\"pass\", \"fail\", \"info\"],\n },\n detail: { type: \"string\" },\n },\n required: [\"name\", \"status\", \"detail\"],\n },\n minItems: 1,\n },\n environment: {\n type: \"object\",\n additionalProperties: { type: \"string\" },\n },\n raw_output: { type: \"string\" },\n },\n required: [\"failed_checks\", \"environment\", \"raw_output\"],\n },\n run: async (input) => {\n pushProgress(\"Reporting failure to smking support…\");\n const typed = input as {\n failed_checks: Array<{\n name: string;\n status: \"pass\" | \"fail\" | \"info\";\n detail: string;\n }>;\n environment: Record<string, string>;\n raw_output: string;\n };\n const framework = detectFramework(ctx.cwd).framework;\n\n const response = await fetch(`${SAAS_URL}/api/v1/doctor-reports`, {\n method: \"POST\",\n headers: {\n authorization: `Bearer ${ctx.oauthToken}`,\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify({\n framework,\n failed_checks: typed.failed_checks,\n environment: typed.environment,\n raw_output: typed.raw_output,\n }),\n });\n\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n return asResult({\n ok: false,\n error: `report_failure HTTP ${response.status}: ${text.slice(0, 200)}`,\n });\n }\n\n const data = (await response.json()) as {\n ticketId?: string;\n message?: string;\n };\n pushProgress(`Reported · ticket ${data.ticketId ?? \"(no id)\"}`);\n return asResult(data);\n },\n },\n ];\n}\n\nexport type { Framework };\n","import { appendFileSync } from \"node:fs\";\nimport Anthropic from \"@anthropic-ai/sdk\";\nimport { COMMANDMENTS } from \"./commandments\";\nimport { fetchInstallPrompt } from \"./prompt\";\nimport { buildWizardTools, type WizardToolDef } from \"./tools\";\nimport { detectFramework } from \"../detection/framework\";\nimport { SAAS_URL } from \"../constants\";\n\n/**\n * File-based debug logger. ink TUI takes over stdout/stderr so\n * `console.log` is invisible during a wizard run. Writing to\n * `/tmp/smking-wizard.log` gives us a tail-able stream of what's\n * happening inside the agent loop.\n */\nconst DEBUG_LOG = \"/tmp/smking-wizard.log\";\nfunction debugLog(msg: string, data?: unknown): void {\n try {\n const stamp = new Date().toISOString();\n const line = data\n ? `${stamp} ${msg} ${JSON.stringify(data, null, 2)}\\n`\n : `${stamp} ${msg}\\n`;\n appendFileSync(DEBUG_LOG, line);\n } catch {\n // Ignore log errors — never crash the wizard over telemetry.\n }\n}\n\n/**\n * Top-level agent loop. Uses **manual agentic loop** with\n * `messages.create` rather than `toolRunner` — the helper had\n * reliability issues with our gateway proxy + adaptive thinking\n * combination (hung indefinitely on the await even after Anthropic\n * returned a valid response).\n *\n * Manual loop pattern (from claude-api skill):\n * 1. messages.create → get response\n * 2. if stop_reason === \"end_turn\": exit\n * 3. if stop_reason === \"tool_use\": run each tool_use block's\n * handler, build tool_result content, push to messages\n * 4. repeat\n *\n * We bound the loop at MAX_ITERATIONS to prevent runaway agents.\n */\n\nexport type AgentContext = {\n cwd: string;\n oauthToken: string;\n siteId: string | null;\n};\n\nexport type AgentResult = {\n ok: boolean;\n message: string;\n};\n\nconst MAX_ITERATIONS = 30;\n\nexport async function runAgent(ctx: AgentContext): Promise<AgentResult> {\n debugLog(\"=== runAgent starting ===\", {\n cwd: ctx.cwd,\n siteId: ctx.siteId,\n saasUrl: SAAS_URL,\n });\n\n const detection = detectFramework(ctx.cwd);\n debugLog(\"framework detection result\", detection);\n if (detection.framework === \"unknown\") {\n return {\n ok: false,\n message: `Unable to detect framework in ${ctx.cwd}. ${detection.evidence}`,\n };\n }\n\n const client = new Anthropic({\n baseURL: `${SAAS_URL}/api/v1/wizard/gateway`,\n authToken: ctx.oauthToken,\n maxRetries: 0,\n });\n\n debugLog(\"fetching install prompt\", { framework: detection.framework });\n const installPrompt = await fetchInstallPrompt({\n framework: detection.framework,\n oauthToken: ctx.oauthToken,\n });\n debugLog(\"install prompt fetched\", { length: installPrompt.length });\n\n const toolDefs = buildWizardTools(ctx);\n debugLog(\"tools built\", {\n count: toolDefs.length,\n names: toolDefs.map((t) => t.name),\n });\n\n // Build the static tools array + dynamic handler map.\n const tools = toolDefs.map((t) => ({\n name: t.name,\n description: t.description,\n input_schema: t.input_schema,\n }));\n const handlers = new Map<string, WizardToolDef[\"run\"]>(\n toolDefs.map((t) => [t.name, t.run]),\n );\n\n const messages: Anthropic.MessageParam[] = [\n { role: \"user\", content: installPrompt },\n ];\n\n try {\n let lastTextSummary = \"\";\n // Track terminal outcomes so we can decide ok=true/false on end_turn:\n // - doctorPassed: run_doctor returned summary.ok===true\n // - reportedFailure: report_failure was called (means agent gave up\n // and filed a support ticket — the install is not green)\n // Agent can `end_turn` after either path; we treat ok as\n // (doctorPassed AND !reportedFailure) so a successful doctor run\n // is the only path that lights up the green DoneScreen.\n let doctorPassed = false;\n let reportedFailure = false;\n\n for (let iteration = 0; iteration < MAX_ITERATIONS; iteration++) {\n debugLog(`iteration ${iteration} — calling messages.create`);\n // Hard timeout — if SDK hangs (we've seen it deadlock on beta\n // namespace responses), break out so we get diagnostic logs.\n //\n // Per claude-api skill audit:\n // - model: claude-opus-4-7 (skill default; matches plan)\n // - thinking: adaptive — agent loop with tools is the \"remotely\n // complicated\" case the skill flags. Opus 4.7 omits\n // thinking text by default; we don't surface it.\n // - effort: Opus 4.7 default is `high`, which is the skill's\n // \"intelligence-sensitive\" sweet spot — leave it\n // implicit. (Bumping to `xhigh` would help agentic\n // correctness but ~doubles thinking-token cost.)\n // - cache_control on system block: COMMANDMENTS is identical\n // across all iterations within a session. Cache\n // reads cost ~0.1× input price. A typical install\n // does 4-6 round-trips at ~3.7K input each — cache\n // turns ~22K full-price input into ~3.7K write +\n // ~18K reads. SDK 0.68 doesn't type top-level\n // cache_control yet, so we attach it to the system\n // block (also caches `tools` since render order is\n // tools → system → messages).\n // Explicit annotation — without `stream`, the SDK return type is a\n // union of Message | Stream<...>. TypeScript 5 doesn't narrow that\n // union when system is an array (vs string), so we lock in Message\n // here to keep response.content / .stop_reason / .usage typed.\n const apiCallPromise: Promise<Anthropic.Message> =\n client.messages.create({\n model: \"claude-opus-4-7\",\n max_tokens: 16_000,\n thinking: { type: \"adaptive\" },\n system: [\n {\n type: \"text\",\n text: COMMANDMENTS,\n cache_control: { type: \"ephemeral\" },\n },\n ],\n tools,\n messages,\n });\n const timeoutPromise = new Promise<never>((_, reject) =>\n setTimeout(() => reject(new Error(\"messages.create timed out after 60s\")), 60_000),\n );\n const response = await Promise.race([apiCallPromise, timeoutPromise]);\n\n debugLog(`iteration ${iteration} — response received`, {\n stop_reason: response.stop_reason,\n content_block_types: response.content.map((c) => c.type),\n usage: response.usage,\n });\n\n // Always append the assistant turn (including thinking blocks)\n // so the next iteration's context is correct. Anthropic requires\n // thinking blocks with signatures to be preserved across turns\n // when using adaptive thinking + tool use.\n messages.push({ role: \"assistant\", content: response.content });\n\n // Capture latest text block for the final summary message.\n const texts = response.content\n .filter((c): c is Anthropic.TextBlock => c.type === \"text\")\n .map((c) => c.text)\n .join(\"\\n\")\n .trim();\n if (texts) lastTextSummary = texts;\n\n if (response.stop_reason === \"end_turn\") {\n debugLog(\"end_turn reached — agent done\");\n break;\n }\n\n if (response.stop_reason !== \"tool_use\") {\n // refusal / max_tokens / pause_turn / other — bail out with\n // whatever text the agent emitted as the summary.\n debugLog(\"non-tool_use stop_reason — exiting loop\", {\n stop_reason: response.stop_reason,\n });\n return {\n ok: false,\n message: `Agent stopped unexpectedly (${response.stop_reason}): ${lastTextSummary || \"no text emitted\"}`,\n };\n }\n\n // Process all tool_use blocks in the response.\n const toolResults: Anthropic.ToolResultBlockParam[] = [];\n for (const block of response.content) {\n if (block.type !== \"tool_use\") continue;\n const handler = handlers.get(block.name);\n if (!handler) {\n debugLog(\"unknown tool called\", { name: block.name });\n toolResults.push({\n type: \"tool_result\",\n tool_use_id: block.id,\n content: JSON.stringify({\n error: `Unknown tool: ${block.name}. Available tools: ${[...handlers.keys()].join(\", \")}`,\n }),\n is_error: true,\n });\n continue;\n }\n debugLog(\"calling tool handler\", {\n name: block.name,\n input: block.input,\n });\n try {\n const result = await handler(block.input);\n debugLog(\"tool handler returned\", {\n name: block.name,\n resultLength: result.length,\n });\n // Inspect tool outcomes that bear on the wizard's overall\n // success/failure. Parse defensively — handlers return a\n // JSON-stringified shape we control, so a parse failure\n // here is a bug we want to surface, not silent-ignore.\n if (block.name === \"run_doctor\") {\n try {\n const parsed = JSON.parse(result) as {\n summary?: { ok?: boolean };\n };\n if (parsed.summary?.ok === true) {\n doctorPassed = true;\n }\n } catch {\n // Doctor result wasn't JSON — leave doctorPassed false.\n }\n } else if (block.name === \"report_failure\") {\n reportedFailure = true;\n }\n toolResults.push({\n type: \"tool_result\",\n tool_use_id: block.id,\n content: result,\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n debugLog(\"tool handler threw\", { name: block.name, error: msg });\n toolResults.push({\n type: \"tool_result\",\n tool_use_id: block.id,\n content: JSON.stringify({ error: msg }),\n is_error: true,\n });\n }\n }\n\n messages.push({ role: \"user\", content: toolResults });\n }\n\n // ok=true ONLY when doctor passed and we didn't open a ticket.\n // Otherwise the install isn't actually green — route to error\n // screen so the customer sees ❌ not ✅.\n const ok = doctorPassed && !reportedFailure;\n debugLog(\"loop exited — returning summary\", {\n ok,\n doctorPassed,\n reportedFailure,\n summary: lastTextSummary,\n });\n return {\n ok,\n message:\n lastTextSummary ||\n (ok ? \"Wizard completed.\" : \"Wizard ended without a successful doctor run.\"),\n };\n } catch (err) {\n debugLog(\"agent loop threw\", {\n error: err instanceof Error ? err.message : String(err),\n stack: err instanceof Error ? err.stack : undefined,\n });\n return {\n ok: false,\n message:\n err instanceof Error\n ? err.message\n : `Agent loop failed: ${String(err)}`,\n };\n }\n}\n","import { useEffect } from \"react\";\nimport { Box, Text } from \"ink\";\nimport { Spinner } from \"@inkjs/ui\";\nimport { useWizardStore } from \"../store\";\nimport { runAgent } from \"../../agent/runtime\";\n\n/**\n * Live agent screen. Mounts → kicks off the install agent loop →\n * routes to done/error based on result. Renders the rolling\n * progress log + a spinner showing the latest action.\n *\n * Progress entries come from `pushProgress` calls inside each\n * wizard tool's `run` handler. The log is capped at 50 lines in\n * the store; we render the latest 12 to fit in a typical terminal\n * without scrolling.\n */\nexport function RunScreen() {\n const oauth = useWizardStore((s) => s.oauth);\n const agentStatus = useWizardStore((s) => s.agentStatus);\n const agentProgress = useWizardStore((s) => s.agentProgress);\n const setAgentStatus = useWizardStore((s) => s.setAgentStatus);\n const setAgentSummary = useWizardStore((s) => s.setAgentSummary);\n const setScreen = useWizardStore((s) => s.setScreen);\n const setFatal = useWizardStore((s) => s.setFatal);\n\n useEffect(() => {\n if (agentStatus !== \"idle\") return; // guard against double-mount\n if (!oauth) {\n setFatal(\"Reached run screen without OAuth tokens — restart wizard.\");\n return;\n }\n\n let cancelled = false;\n setAgentStatus(\"running\");\n runAgent({\n cwd: process.cwd(),\n oauthToken: oauth.accessToken,\n siteId: oauth.siteId,\n })\n .then((result) => {\n if (cancelled) return;\n setAgentSummary(result.message);\n if (result.ok) {\n setAgentStatus(\"succeeded\");\n setScreen(\"done\");\n } else {\n setAgentStatus(\"failed\");\n setFatal(result.message);\n }\n })\n .catch((err: unknown) => {\n if (cancelled) return;\n const msg = err instanceof Error ? err.message : String(err);\n setAgentStatus(\"failed\");\n setFatal(`Agent crashed: ${msg}`);\n });\n\n return () => {\n cancelled = true;\n };\n // Only run once on mount.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Show the latest N progress lines so a long install loop fits.\n const visibleLines = agentProgress.slice(-12);\n const latest = agentProgress[agentProgress.length - 1] ?? \"Starting agent…\";\n\n return (\n <Box flexDirection=\"column\" gap={1}>\n <Box>\n <Spinner label={latest} />\n </Box>\n\n {visibleLines.length > 1 ? (\n <Box flexDirection=\"column\" marginTop={1}>\n {visibleLines.slice(0, -1).map((line, idx) => (\n <Text key={idx} color=\"gray\">\n {line}\n </Text>\n ))}\n </Box>\n ) : null}\n\n <Box marginTop={1}>\n <Text color=\"gray\">Ctrl-C to abort</Text>\n </Box>\n </Box>\n );\n}\n","import { useEffect } from \"react\";\nimport { Box, Text, useApp, useInput } from \"ink\";\nimport { useWizardStore } from \"../store\";\n\n/**\n * Terminal state after a successful wizard run. Reached when\n * `runAgent` returns `ok: true` — meaning `run_doctor` returned\n * `summary.ok === true` AND `report_failure` was never called\n * (see runtime.ts for the gate).\n *\n * Shows the agent's own summary message so the customer sees what\n * actually got installed / verified, not a generic \"connected\".\n *\n * Press any key to exit. Auto-exits after 15s — longer than the\n * error-screen's 8s because customers need time to read the\n * pass/fail breakdown and notice any info-level recommendations.\n */\nexport function DoneScreen() {\n const { exit } = useApp();\n const oauth = useWizardStore((s) => s.oauth);\n const agentSummary = useWizardStore((s) => s.agentSummary);\n\n useInput(() => {\n exit();\n });\n\n useEffect(() => {\n const timer = setTimeout(() => exit(), 15_000);\n return () => clearTimeout(timer);\n }, [exit]);\n\n return (\n <Box flexDirection=\"column\" gap={1}>\n <Text bold color=\"green\">\n ✅ smking installed\n </Text>\n\n {agentSummary ? <Text>{agentSummary}</Text> : null}\n\n {oauth?.siteId ? (\n <Text color=\"gray\">\n Site: {oauth.siteId} · token expires in{\" \"}\n {Math.round(oauth.expiresIn / 60)} minutes\n </Text>\n ) : null}\n\n <Box marginTop={1}>\n <Text color=\"gray\">Press any key to exit (auto-exit in 15s).</Text>\n </Box>\n </Box>\n );\n}\n","import { useEffect } from \"react\";\nimport { Box, Text, useApp, useInput } from \"ink\";\nimport { useWizardStore } from \"../store\";\n\n/**\n * Fatal-error terminal screen. Any `setFatal()` call from anywhere\n * in the wizard routes here. Shows the error message and exits 1\n * on key press / auto-exit so callers can detect failure.\n */\nexport function ErrorScreen() {\n const { exit } = useApp();\n const fatal = useWizardStore((s) => s.fatal);\n\n useInput(() => {\n exit(new Error(fatal ?? \"wizard failed\"));\n });\n\n useEffect(() => {\n const timer = setTimeout(\n () => exit(new Error(fatal ?? \"wizard failed\")),\n 8000,\n );\n return () => clearTimeout(timer);\n }, [exit, fatal]);\n\n return (\n <Box flexDirection=\"column\" gap={1}>\n <Text bold color=\"red\">\n ❌ Wizard failed\n </Text>\n <Text>{fatal ?? \"Unknown error\"}</Text>\n <Box marginTop={1}>\n <Text color=\"gray\">\n Press any key to exit (auto-exit in 8s). Re-run{\" \"}\n <Text color=\"cyan\">npx @soloworks/smking-wizard</Text> after fixing the issue.\n </Text>\n </Box>\n </Box>\n );\n}\n","import { Box } from \"ink\";\nimport { useWizardStore } from \"./store\";\nimport { WelcomeScreen } from \"./screens/welcome-screen\";\nimport { OAuthScreen } from \"./screens/oauth-screen\";\nimport { RunScreen } from \"./screens/run-screen\";\nimport { DoneScreen } from \"./screens/done-screen\";\nimport { ErrorScreen } from \"./screens/error-screen\";\n\n/**\n * Root component. Routes by `store.screen` and renders the matching\n * full-screen view. Each screen owns its own lifecycle (useEffect)\n * — App.tsx itself is a switch, nothing more.\n */\nexport function App() {\n const screen = useWizardStore((s) => s.screen);\n\n return (\n <Box flexDirection=\"column\" paddingX={1} paddingY={1}>\n {screen === \"welcome\" && <WelcomeScreen />}\n {screen === \"oauth\" && <OAuthScreen />}\n {screen === \"run\" && <RunScreen />}\n {screen === \"done\" && <DoneScreen />}\n {screen === \"error\" && <ErrorScreen />}\n </Box>\n );\n}\n","import { createElement } from \"react\";\nimport { render } from \"ink\";\nimport yargs from \"yargs\";\nimport { hideBin } from \"yargs/helpers\";\nimport { App } from \"./ui/app\";\nimport { useWizardStore } from \"./ui/store\";\nimport {\n MIN_NODE_MAJOR,\n MIN_NODE_MINOR,\n WIZARD_VERSION,\n} from \"./constants\";\n\n/**\n * `npx @soloworks/smking-wizard` entry point.\n *\n * Phase 3 scope: parse args, check Node version, render the ink TUI.\n * Phase 4+ adds framework detection / installer dispatch / agent\n * loop — those plug into the TUI via the zustand store, not via new\n * command-line subcommands. The wizard is single-command on purpose\n * (PostHog model): one `npx @soloworks/smking-wizard` does everything.\n */\n\nfunction checkNodeVersion(): void {\n const [maj, min] = process.versions.node.split(\".\").map(Number);\n if (\n maj < MIN_NODE_MAJOR ||\n (maj === MIN_NODE_MAJOR && min < MIN_NODE_MINOR)\n ) {\n process.stderr.write(\n `smking wizard requires Node ${MIN_NODE_MAJOR}.${MIN_NODE_MINOR}+. You have ${process.versions.node}.\\n`,\n );\n process.exit(1);\n }\n}\n\nasync function main(): Promise<number> {\n checkNodeVersion();\n\n const argv = await yargs(hideBin(process.argv))\n .scriptName(\"smking-wizard\")\n .usage(\"$0 [options]\")\n .option(\"debug\", {\n type: \"boolean\",\n default: false,\n describe: \"Enable verbose debug output\",\n })\n .option(\"dry-run\", {\n type: \"boolean\",\n default: false,\n describe: \"Print intended changes without writing files (Phase 4+)\",\n })\n .option(\"allow-prod\", {\n type: \"boolean\",\n default: false,\n describe:\n \"Override the production-environment refusal (use only if you know why)\",\n })\n .option(\"allow-dirty\", {\n type: \"boolean\",\n default: false,\n describe:\n \"Override the git-status-must-be-clean check (use only if you know why)\",\n })\n .version(WIZARD_VERSION)\n .help()\n .strict()\n .parse();\n\n if (argv.debug) {\n process.env.SMKING_WIZARD_DEBUG = \"1\";\n }\n\n // Seed CLI flags into the store before the TUI mounts so guards\n // running on the welcome screen see the right overrides.\n useWizardStore.getState().setCliFlags({\n allowProd: !!argv[\"allow-prod\"],\n allowDirty: !!argv[\"allow-dirty\"],\n dryRun: !!argv[\"dry-run\"],\n debug: !!argv.debug,\n });\n\n // Render ink TUI. `waitUntilExit()` resolves when any screen\n // calls `useApp().exit()`. Throws if exit was called with an\n // Error — we convert to non-zero exit code below.\n //\n // Use `createElement` rather than JSX so this entry stays in `.ts`\n // (rolldown's `.ts` parser doesn't accept JSX without renaming or\n // adding an esbuild plugin — and bin.ts has no other JSX to justify\n // either workaround).\n const { waitUntilExit } = render(createElement(App));\n\n try {\n await waitUntilExit();\n return 0;\n } catch (err) {\n process.stderr.write(\n `${err instanceof Error ? err.message : String(err)}\\n`,\n );\n return 1;\n }\n}\n\nmain().then(\n (code) => process.exit(code),\n (err) => {\n process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exit(1);\n },\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bin.mjs","names":["DEFAULT_FLAGS: CliFlags","signals: string[]","resolver: (value: { code: string }) => void","rejecter: (err: Error) => void","pkg: { dependencies?: Record<string, string>; devDependencies?: Record<string, string> }","LOCKFILES: Array<{ name: string; pm: NodePackageManager }>","timer: NodeJS.Timeout | undefined","changed: string[]","unchanged: string[]","steps: InstallStep[]","bumpedTo: string | null","updates: Record<string, string>","PM_ADD_ARGS: Record<NodePackageManager, string[]>","steps: InstallStep[]","bumpedTo: string | null","updates: Record<string, string>","result","updates: Record<string, string>","cmd: string","args: string[]","fs","messages: Anthropic.MessageParam[]","apiCallPromise: Promise<Anthropic.Message>","toolResults: Anthropic.ToolResultBlockParam[]"],"sources":["../src/ui/store.ts","../../shared/src/constants/oauth.ts","../src/constants.ts","../src/guards/production-check.ts","../src/guards/git-status-check.ts","../src/ui/screens/welcome-screen.tsx","../src/oauth.ts","../src/ui/screens/oauth-screen.tsx","../src/agent/commandments.ts","../src/agent/prompt.ts","../src/detection/framework.ts","../src/detection/package-manager.ts","../src/lib/exec.ts","../src/lib/env-file.ts","../src/lib/registry.ts","../src/installers/laravel.ts","../src/installers/nextjs.ts","../src/agent/tools.ts","../src/agent/runtime.ts","../src/ui/screens/run-screen.tsx","../src/ui/screens/done-screen.tsx","../src/ui/screens/error-screen.tsx","../src/ui/app.tsx","../src/bin.ts"],"sourcesContent":["import { create } from \"zustand\";\nimport type { OAuthResult } from \"../oauth\";\n\n/**\n * Wizard's single source of truth. Each screen subscribes to the\n * slices it needs and the agent loop writes into here as work\n * progresses. Mirrors PostHog wizard's `src/ui/tui/store.ts` shape\n * minus the pieces we don't have yet (TodoWrite sync, status\n * channel — those land in Phase 5 when the agent integrates).\n */\n\nexport type Screen = \"welcome\" | \"oauth\" | \"run\" | \"done\" | \"error\";\n\nexport type CliFlags = {\n allowProd: boolean;\n allowDirty: boolean;\n dryRun: boolean;\n debug: boolean;\n};\n\nexport type AgentStatus = \"idle\" | \"running\" | \"succeeded\" | \"failed\";\n\nexport type WizardStore = {\n // ── CLI flags (set once at bin.ts, read by guards) ──────────\n cliFlags: CliFlags;\n setCliFlags: (flags: CliFlags) => void;\n\n // ── Screen routing ──────────────────────────────────────────\n screen: Screen;\n setScreen: (screen: Screen) => void;\n\n // ── OAuth ───────────────────────────────────────────────────\n oauthUrl: string | null;\n setOauthUrl: (url: string | null) => void;\n oauth: OAuthResult | null;\n setOauth: (result: OAuthResult) => void;\n\n // ── Agent progress (driven by tool handlers) ────────────────\n agentStatus: AgentStatus;\n setAgentStatus: (status: AgentStatus) => void;\n agentProgress: string[];\n pushProgress: (text: string) => void;\n agentSummary: string | null;\n setAgentSummary: (msg: string) => void;\n\n // ── Fatal error (drops user into Error screen) ──────────────\n fatal: string | null;\n setFatal: (msg: string) => void;\n};\n\nconst DEFAULT_FLAGS: CliFlags = {\n allowProd: false,\n allowDirty: false,\n dryRun: false,\n debug: false,\n};\n\n/**\n * Cap progress history at 50 lines. Past that, oldest entries drop\n * — keeps the in-memory log bounded for long agent loops and the\n * TUI render fast (we don't want to re-render 500 progress lines).\n */\nconst MAX_PROGRESS_LINES = 50;\n\nexport const useWizardStore = create<WizardStore>((set) => ({\n cliFlags: DEFAULT_FLAGS,\n setCliFlags: (cliFlags) => set({ cliFlags }),\n\n screen: \"welcome\",\n setScreen: (screen) => set({ screen }),\n\n oauthUrl: null,\n setOauthUrl: (oauthUrl) => set({ oauthUrl }),\n oauth: null,\n setOauth: (oauth) => set({ oauth }),\n\n agentStatus: \"idle\",\n setAgentStatus: (agentStatus) => set({ agentStatus }),\n agentProgress: [],\n pushProgress: (text) =>\n set((s) => {\n const next = [...s.agentProgress, text];\n if (next.length > MAX_PROGRESS_LINES) {\n next.splice(0, next.length - MAX_PROGRESS_LINES);\n }\n return { agentProgress: next };\n }),\n agentSummary: null,\n setAgentSummary: (agentSummary) => set({ agentSummary }),\n\n fatal: null,\n setFatal: (fatal) => set({ fatal, screen: \"error\" }),\n}));\n","export const TOKEN_EXPIRY = {\n ACCESS_TOKEN: 24 * 60 * 60, // 24 hours in seconds\n REFRESH_TOKEN: 90 * 24 * 60 * 60, // 90 days in seconds\n AUTH_CODE: 10 * 60, // 10 minutes in seconds\n /**\n * Wizard tokens have a shorter TTL because the wizard is a short-lived\n * install flow — 30 min covers OAuth + agent install + doctor loop\n * with margin for SSH-typing-the-URL scenarios.\n */\n WIZARD_ACCESS_TOKEN: 30 * 60,\n} as const;\n\nexport const OAUTH_SCOPES = [\n \"site:manage\",\n \"wizard:install\",\n \"wizard:llm\",\n] as const;\n\nexport const WIZARD_SCOPES = [\"wizard:install\", \"wizard:llm\"] as const;\n\nexport const GRANT_TYPES = [\"authorization_code\", \"refresh_token\"] as const;\n\n/**\n * Localhost callback ports the @soloworks/smking-wizard CLI tries in order. Six\n * ports give us headroom when the user already has a dev server on one\n * — wizard falls through on `EADDRINUSE`. Mirrors PostHog wizard's\n * `OAUTH_PORTS` for the same reason.\n */\nexport const WIZARD_OAUTH_PORTS = [8239, 8238, 8240, 8237, 8236, 8235] as const;\n\n/**\n * Quota knobs for the wizard LLM gateway proxy. Per-session caps the\n * cost of a single wizard run; per-user-day caps total wizard cost\n * across runs. Both enforced in `/api/v1/wizard/gateway/[...path]`.\n */\nexport const WIZARD_QUOTA = {\n PER_SESSION_TOKENS: 200_000, // ≈ $0.60 Sonnet 4.6\n PER_USER_DAILY_SESSIONS: 5, // ≈ $3 upper bound per user per day\n} as const;\n","import {\n WIZARD_OAUTH_PORTS,\n WIZARD_SCOPES,\n WIZARD_QUOTA,\n} from \"@smking/shared\";\n\n/**\n * Re-export the constants the SaaS already pins so the wizard CLI\n * and the backend stay in sync — changing a port list in one place\n * silently breaking the other would be a nasty bug, so we centralise.\n */\nexport const OAUTH_PORTS = WIZARD_OAUTH_PORTS;\nexport const SCOPES = WIZARD_SCOPES;\nexport const QUOTA = WIZARD_QUOTA;\n\n/**\n * smking SaaS origin the wizard talks to. Defaults to the current\n * Vercel production deployment; override via `SMKING_SAAS_URL` for\n * local dev (e.g. `http://localhost:3001`) or staging. Trailing slash\n * is stripped to make URL concatenation safe.\n *\n * Once a custom domain (e.g. `smking.com`) is wired up to the Vercel\n * project, swap the default here in the same commit that updates DNS.\n */\nexport const SAAS_URL = (\n process.env.SMKING_SAAS_URL ?? \"https://smking-alone.vercel.app\"\n).replace(/\\/$/, \"\");\n\n/**\n * Public OAuth client_id registered on the SaaS for the wizard. Must\n * match `WIZARD_OAUTH_CLIENT_ID` env var on the SaaS side, which\n * `validateClientCredentials` checks. Public client — no secret.\n */\nexport const WIZARD_CLIENT_ID =\n process.env.SMKING_WIZARD_CLIENT_ID ?? \"smking_wizard_v1\";\n\n/**\n * How long the wizard waits for the OAuth browser flow to complete\n * before giving up. 6 minutes covers slow logins, MFA prompts, and\n * SSH users who have to manually copy the URL to a different browser.\n */\nexport const OAUTH_TIMEOUT_MS = 6 * 60 * 1000;\n\n/**\n * PKCE code verifier length (RFC 7636 §4.1 says 43-128 chars). 96\n * is a comfortable middle that still fits a URL nicely if anyone\n * needs to copy/paste it for debugging.\n */\nexport const PKCE_VERIFIER_LENGTH = 96;\n\n/**\n * Read from package.json at build time. tsdown inlines it.\n */\nexport const WIZARD_VERSION = \"0.1.0\";\n\n/**\n * Minimum Node version. ink 6 needs Node 18.0.0, but we set 20.10\n * to align with the SaaS's `engines` and to get fetch / native test\n * runner stability.\n */\nexport const MIN_NODE_MAJOR = 20;\nexport const MIN_NODE_MINOR = 10;\n","import { existsSync } from \"node:fs\";\n\n/**\n * Refuse to run on production-like environments. Wizard mutates\n * customer files (composer.json, .env, layout.tsx) — running it\n * on a production server is a sharp tool. We block by default;\n * `--allow-prod` overrides for users who know why.\n *\n * Detection is heuristic — we look at:\n * - `NODE_ENV` / `VERCEL_ENV` / `RAILWAY_ENVIRONMENT` env vars\n * - `/.dockerenv` (running inside a Docker container)\n * - `/var/www` (typical LAMP production layout)\n *\n * None of these alone is definitive but together they catch the\n * common cases. Customers with custom prod environments get the\n * `--allow-prod` flag.\n */\n\nexport type GuardResult = {\n ok: boolean;\n reason?: string;\n override?: string; // CLI flag that can bypass this guard\n};\n\nexport function checkProduction(\n flags: { allowProd?: boolean } = {},\n): GuardResult {\n if (flags.allowProd) return { ok: true };\n\n const signals: string[] = [];\n\n if (process.env.NODE_ENV === \"production\") {\n signals.push(\"NODE_ENV=production\");\n }\n if (process.env.VERCEL_ENV === \"production\") {\n signals.push(\"VERCEL_ENV=production\");\n }\n if (process.env.RAILWAY_ENVIRONMENT === \"production\") {\n signals.push(\"RAILWAY_ENVIRONMENT=production\");\n }\n if (process.env.FLY_APP_NAME) {\n signals.push(\"FLY_APP_NAME set (running on fly.io)\");\n }\n if (existsSync(\"/.dockerenv\")) {\n signals.push(\"/.dockerenv present (inside Docker container)\");\n }\n // /var/www is a generic LAMP convention — only treat as a signal\n // when combined with another. Standalone it false-positives on\n // some Linux dev setups.\n if (existsSync(\"/var/www\") && signals.length > 0) {\n signals.push(\"/var/www present\");\n }\n\n if (signals.length === 0) return { ok: true };\n\n return {\n ok: false,\n reason:\n `Refusing to run on a production-like environment. Signals detected:\\n - ${signals.join(\n \"\\n - \",\n )}\\n\\nRun the wizard on your local dev machine, then deploy normally. If you genuinely need to run here, pass --allow-prod.`,\n override: \"--allow-prod\",\n };\n}\n","import { spawnSync } from \"node:child_process\";\nimport type { GuardResult } from \"./production-check\";\n\n/**\n * Require a clean git working tree before wizard runs. Two reasons:\n *\n * 1. **Predictable diff.** After wizard finishes, `git diff` shows\n * exactly what wizard did and nothing else — the customer can\n * review one cohesive change. Mixed in with prior uncommitted\n * work, the wizard's edits are hard to isolate.\n *\n * 2. **Easy rollback.** If wizard does something wrong, `git\n * restore .` cleanly reverts to the pre-wizard state. With\n * uncommitted work mixed in, that command also wipes the\n * customer's own work.\n *\n * Override via `--allow-dirty` for users who know what they're\n * doing. Repos without git (rare for a real project) pass the\n * guard since there's nothing to dirty.\n */\n\nexport function checkGitStatus(\n flags: { allowDirty?: boolean } = {},\n cwd: string = process.cwd(),\n): GuardResult {\n if (flags.allowDirty) return { ok: true };\n\n // `git status --porcelain` exits 0 for clean tree, 0 with output\n // for dirty tree, and non-zero if not a git repo. We tolerate\n // not-a-git-repo (nothing to dirty) and only flag actual dirty.\n const result = spawnSync(\"git\", [\"status\", \"--porcelain\"], {\n cwd,\n encoding: \"utf-8\",\n });\n\n if (result.error || result.status !== 0) {\n // Not a git repo or git missing. Tolerate — `git restore`\n // wouldn't be the customer's rollback strategy anyway.\n return { ok: true };\n }\n\n const dirty = result.stdout.trim();\n if (dirty === \"\") return { ok: true };\n\n // Truncate the dirty-files list so a huge uncommitted refactor\n // doesn't flood the TUI.\n const lines = dirty.split(\"\\n\");\n const preview = lines.slice(0, 10).join(\"\\n\");\n const more = lines.length > 10 ? `\\n ... and ${lines.length - 10} more files` : \"\";\n\n return {\n ok: false,\n reason: `Working tree has uncommitted changes. Wizard wants a clean slate so its diff is unambiguous.\\n\\nUncommitted files:\\n${preview}${more}\\n\\nCommit, stash, or discard them first. If you really need to run on a dirty tree, pass --allow-dirty.`,\n override: \"--allow-dirty\",\n };\n}\n","import { Box, Text, useInput } from \"ink\";\nimport { useWizardStore } from \"../store\";\nimport { WIZARD_VERSION } from \"../../constants\";\nimport { checkProduction } from \"../../guards/production-check\";\nimport { checkGitStatus } from \"../../guards/git-status-check\";\n\n/**\n * First thing the user sees. Trust signals up front — what the\n * wizard will do and what it will NEVER do. Press enter to continue.\n *\n * On Enter the screen also runs sync guards (production-env refuse\n * + git-status-must-be-clean) before advancing. Either guard failing\n * routes to the error screen with an actionable override hint.\n */\nexport function WelcomeScreen() {\n const setScreen = useWizardStore((s) => s.setScreen);\n const setFatal = useWizardStore((s) => s.setFatal);\n const cliFlags = useWizardStore((s) => s.cliFlags);\n\n useInput((_, key) => {\n if (!key.return) return;\n\n const prodCheck = checkProduction({ allowProd: cliFlags.allowProd });\n if (!prodCheck.ok) {\n setFatal(prodCheck.reason!);\n return;\n }\n\n const gitCheck = checkGitStatus({ allowDirty: cliFlags.allowDirty });\n if (!gitCheck.ok) {\n setFatal(gitCheck.reason!);\n return;\n }\n\n setScreen(\"oauth\");\n });\n\n return (\n <Box flexDirection=\"column\" gap={1}>\n <Box>\n <Text bold color=\"cyan\">\n 🪄 smking install wizard\n </Text>\n <Text color=\"gray\"> v{WIZARD_VERSION}</Text>\n </Box>\n\n <Text>\n This will install the smking SDK for your project (Laravel or\n Next.js), configure your environment, and verify the install\n with a doctor check.\n </Text>\n\n <Box flexDirection=\"column\">\n <Text bold>This wizard will:</Text>\n <Text color=\"green\"> ✓ Open your browser to log in to smking</Text>\n <Text color=\"green\"> ✓ Detect your framework (Laravel / Next.js)</Text>\n <Text color=\"green\"> ✓ Install the smking SDK package</Text>\n <Text color=\"green\"> ✓ Write SMKING_API_KEY + SMKING_BASE_URL (+ SMKING_WEBHOOK_SECRET if CMS enabled) to your .env</Text>\n <Text color=\"green\"> ✓ Run doctor to verify the install</Text>\n </Box>\n\n <Box flexDirection=\"column\">\n <Text bold>This wizard will NEVER:</Text>\n <Text color=\"red\"> ✗ Commit or push to git (you review the diff yourself)</Text>\n <Text color=\"red\"> ✗ Read or upload your .env values to any server</Text>\n <Text color=\"red\"> ✗ Run destructive commands (rm, drop, migrate:fresh, etc.)</Text>\n <Text color=\"red\"> ✗ Modify env variables other than SMKING_*</Text>\n <Text color=\"red\"> ✗ Install packages other than the smking SDK</Text>\n </Box>\n\n <Box marginTop={1}>\n <Text color=\"cyan\">Press Enter to continue · Ctrl-C to abort</Text>\n </Box>\n </Box>\n );\n}\n","import { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { createHash, randomBytes } from \"node:crypto\";\nimport open from \"open\";\nimport {\n OAUTH_PORTS,\n SCOPES,\n SAAS_URL,\n WIZARD_CLIENT_ID,\n OAUTH_TIMEOUT_MS,\n PKCE_VERIFIER_LENGTH,\n} from \"./constants\";\n\n/**\n * Wizard-side OAuth (RFC 6749 §4.1 authorization_code with PKCE per\n * RFC 7636). Mirrors PostHog wizard's `src/utils/oauth.ts` — port\n * fallback list, browser open + URL fallback for SSH, 6-min timeout.\n *\n * Flow:\n * 1. Generate code_verifier + code_challenge (S256)\n * 2. Start localhost callback server, try each port in OAUTH_PORTS\n * 3. Open browser at SaaS authorize URL with code_challenge\n * 4. User logs in + consents → SaaS redirects to localhost callback\n * 5. Callback server captures code + state, verifies state, returns\n * 6. Exchange code for access_token via POST /api/oauth/token with\n * code_verifier (no client_secret — we're a public client)\n */\n\nexport type OAuthResult = {\n accessToken: string;\n refreshToken: string;\n expiresIn: number;\n siteId: string | null;\n};\n\n/**\n * Generate a PKCE code verifier per RFC 7636 §4.1. Cryptographically\n * random base64url string in [43, 128] chars.\n */\nfunction generateCodeVerifier(): string {\n // 96 chars of base64url ≈ 72 random bytes (96 * 6 / 8 = 72).\n const bytes = Math.ceil((PKCE_VERIFIER_LENGTH * 6) / 8);\n return base64UrlEncode(randomBytes(bytes)).slice(0, PKCE_VERIFIER_LENGTH);\n}\n\n/**\n * S256 challenge: BASE64URL(SHA256(verifier)).\n */\nfunction generateCodeChallenge(verifier: string): string {\n return base64UrlEncode(createHash(\"sha256\").update(verifier).digest());\n}\n\nfunction base64UrlEncode(buf: Buffer): string {\n return buf\n .toString(\"base64\")\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nfunction generateState(): string {\n return randomBytes(16).toString(\"hex\");\n}\n\n/**\n * Try each port in OAUTH_PORTS until one binds, then run `handle` on\n * the first matching `/callback` request. Rejects on timeout or if\n * all ports are occupied.\n *\n * Returns the port number it bound to (so the redirect_uri can be\n * built to match) and a promise that resolves with the OAuth code +\n * state when the callback fires.\n */\nasync function startCallbackServer(\n expectedState: string,\n signal: AbortSignal,\n): Promise<{ port: number; result: Promise<{ code: string }> }> {\n let resolver: (value: { code: string }) => void;\n let rejecter: (err: Error) => void;\n const result = new Promise<{ code: string }>((res, rej) => {\n resolver = res;\n rejecter = rej;\n });\n\n const handler = (req: IncomingMessage, res: ServerResponse) => {\n if (!req.url) {\n res.writeHead(404);\n res.end();\n return;\n }\n const url = new URL(req.url, \"http://localhost\");\n if (url.pathname !== \"/callback\") {\n res.writeHead(404);\n res.end();\n return;\n }\n const code = url.searchParams.get(\"code\");\n const state = url.searchParams.get(\"state\");\n const error = url.searchParams.get(\"error\");\n\n if (error) {\n res.writeHead(400, { \"content-type\": \"text/html; charset=utf-8\" });\n res.end(renderErrorPage(error));\n rejecter(new Error(`OAuth error: ${error}`));\n return;\n }\n if (!code || !state) {\n res.writeHead(400, { \"content-type\": \"text/html; charset=utf-8\" });\n res.end(renderErrorPage(\"missing code or state\"));\n rejecter(new Error(\"OAuth callback missing code or state\"));\n return;\n }\n if (state !== expectedState) {\n res.writeHead(400, { \"content-type\": \"text/html; charset=utf-8\" });\n res.end(renderErrorPage(\"state mismatch\"));\n rejecter(new Error(\"OAuth state mismatch — possible CSRF\"));\n return;\n }\n res.writeHead(200, { \"content-type\": \"text/html; charset=utf-8\" });\n res.end(renderSuccessPage());\n resolver({ code });\n };\n\n for (const port of OAUTH_PORTS) {\n const server = createServer(handler);\n try {\n await new Promise<void>((res, rej) => {\n server.once(\"error\", rej);\n server.listen(port, \"127.0.0.1\", () => res());\n });\n // Bound successfully. Wire up cleanup on abort / completion.\n signal.addEventListener(\"abort\", () => {\n server.close();\n rejecter(new Error(\"OAuth aborted\"));\n });\n result.finally(() => server.close()).catch(() => {});\n return { port, result };\n } catch (err) {\n // EADDRINUSE — try the next port.\n server.close();\n if (\n err instanceof Error &&\n \"code\" in err &&\n (err as NodeJS.ErrnoException).code === \"EADDRINUSE\"\n ) {\n continue;\n }\n throw err;\n }\n }\n\n throw new Error(\n `All OAuth ports occupied: ${OAUTH_PORTS.join(\", \")}. Close other dev servers and retry.`,\n );\n}\n\n/**\n * Build the SaaS authorize URL. The SaaS authorize page expects:\n * client_id, redirect_uri, state, site_url, scope, response_type,\n * code_challenge, code_challenge_method\n *\n * `site_url` is the customer's project URL — used by the SaaS to\n * find-or-create the site row and tag the OAuth token to it. For\n * the wizard, we send the current working directory's git remote\n * (or a placeholder when none exists; SaaS uses it for display only).\n */\nfunction buildAuthorizeUrl(opts: {\n port: number;\n state: string;\n codeChallenge: string;\n siteUrl: string;\n}): string {\n const params = new URLSearchParams({\n client_id: WIZARD_CLIENT_ID,\n redirect_uri: `http://localhost:${opts.port}/callback`,\n state: opts.state,\n site_url: opts.siteUrl,\n scope: SCOPES.join(\" \"),\n response_type: \"code\",\n code_challenge: opts.codeChallenge,\n code_challenge_method: \"S256\",\n });\n return `${SAAS_URL}/oauth/authorize?${params.toString()}`;\n}\n\n/**\n * Exchange the authorization code for an access token. Public client\n * — no client_secret, code_verifier proves possession instead.\n */\nasync function exchangeCodeForToken(opts: {\n code: string;\n codeVerifier: string;\n port: number;\n siteUrl: string;\n}): Promise<OAuthResult> {\n const response = await fetch(`${SAAS_URL}/api/oauth/token`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({\n grant_type: \"authorization_code\",\n code: opts.code,\n redirect_uri: `http://localhost:${opts.port}/callback`,\n client_id: WIZARD_CLIENT_ID,\n code_verifier: opts.codeVerifier,\n site_url: opts.siteUrl,\n }),\n });\n\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n throw new Error(\n `Token exchange failed: HTTP ${response.status} — ${text.slice(0, 200)}`,\n );\n }\n\n const data = (await response.json()) as {\n access_token?: string;\n refresh_token?: string;\n expires_in?: number;\n site_id?: string | null;\n };\n\n if (!data.access_token || !data.refresh_token) {\n throw new Error(\"Token response missing access_token or refresh_token\");\n }\n\n return {\n accessToken: data.access_token,\n refreshToken: data.refresh_token,\n expiresIn: data.expires_in ?? 1800,\n siteId: data.site_id ?? null,\n };\n}\n\n/**\n * Top-level OAuth orchestrator. Caller passes a `siteUrl` (typically\n * `git remote get-url origin` output or a placeholder) and an\n * `onUrlReady` callback so the TUI can display the URL for SSH\n * fallback at the same time the browser is opened.\n *\n * Returns the token bundle on success; throws on timeout, state\n * mismatch, or token exchange failure.\n */\nexport async function performOAuthFlow(opts: {\n siteUrl: string;\n onUrlReady: (url: string) => void;\n}): Promise<OAuthResult> {\n const verifier = generateCodeVerifier();\n const challenge = generateCodeChallenge(verifier);\n const state = generateState();\n\n const controller = new AbortController();\n const timeoutHandle = setTimeout(\n () => controller.abort(),\n OAUTH_TIMEOUT_MS,\n );\n\n try {\n const { port, result } = await startCallbackServer(state, controller.signal);\n const authorizeUrl = buildAuthorizeUrl({\n port,\n state,\n codeChallenge: challenge,\n siteUrl: opts.siteUrl,\n });\n\n // Notify TUI so URL shows + browser opens concurrently.\n opts.onUrlReady(authorizeUrl);\n // `open` swallows failures (returns a ChildProcess); SSH users\n // see the URL in the TUI and copy/paste it manually.\n open(authorizeUrl).catch(() => {});\n\n const { code } = await result;\n const tokens = await exchangeCodeForToken({\n code,\n codeVerifier: verifier,\n port,\n siteUrl: opts.siteUrl,\n });\n return tokens;\n } finally {\n clearTimeout(timeoutHandle);\n }\n}\n\n// ── Callback HTML pages ────────────────────────────────────────\n\nfunction renderSuccessPage(): string {\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<title>smking — connected</title>\n<style>\n body { font: 14px/1.5 -apple-system, \"Helvetica Neue\", sans-serif; max-width: 480px; margin: 80px auto; padding: 0 24px; color: #222; }\n h1 { font-size: 24px; }\n .hint { color: #666; }\n</style>\n</head>\n<body>\n<h1>✅ Connected to smking</h1>\n<p>You can close this tab and return to your terminal.</p>\n<p class=\"hint\">smking install wizard is now finishing setup.</p>\n<script>setTimeout(() => window.close(), 1500);</script>\n</body>\n</html>`;\n}\n\nfunction renderErrorPage(error: string): string {\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<title>smking — error</title>\n<style>\n body { font: 14px/1.5 -apple-system, \"Helvetica Neue\", sans-serif; max-width: 480px; margin: 80px auto; padding: 0 24px; color: #222; }\n h1 { font-size: 24px; color: #c00; }\n code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; }\n</style>\n</head>\n<body>\n<h1>❌ smking OAuth failed</h1>\n<p>Error: <code>${escapeHtml(error)}</code></p>\n<p>Return to your terminal and re-run <code>npx @soloworks/smking-wizard</code>.</p>\n</body>\n</html>`;\n}\n\nfunction escapeHtml(input: string): string {\n return input\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n","import { useEffect } from \"react\";\nimport { Box, Text } from \"ink\";\nimport { Spinner } from \"@inkjs/ui\";\nimport { useWizardStore } from \"../store\";\nimport { performOAuthFlow } from \"../../oauth\";\n\n/**\n * OAuth screen — kicks off the browser flow on mount and waits for\n * the localhost callback. Displays the authorize URL in the TUI as\n * a fallback for SSH users who don't have a graphical browser\n * (mirroring PostHog's pattern).\n *\n * Lifecycle:\n * 1. mount → performOAuthFlow() starts\n * 2. callback server emits URL via onUrlReady → store.setOauthUrl\n * 3. user logs in + redirects to localhost → callback fires\n * 4. OAuth tokens stored → setScreen(\"done\")\n * 5. any error → setFatal() → screen routes to \"error\"\n */\nexport function OAuthScreen() {\n const oauthUrl = useWizardStore((s) => s.oauthUrl);\n const setOauthUrl = useWizardStore((s) => s.setOauthUrl);\n const setOauth = useWizardStore((s) => s.setOauth);\n const setScreen = useWizardStore((s) => s.setScreen);\n const setFatal = useWizardStore((s) => s.setFatal);\n\n useEffect(() => {\n let cancelled = false;\n performOAuthFlow({\n // For Phase 3 the wizard CLI doesn't know the project's git\n // remote yet (framework detection lands in Phase 4). Send a\n // placeholder; SaaS uses site_url for display only on the\n // consent screen, not for routing.\n siteUrl: process.cwd(),\n onUrlReady: (url) => {\n if (!cancelled) setOauthUrl(url);\n },\n })\n .then((tokens) => {\n if (cancelled) return;\n setOauth(tokens);\n // Route into the run screen so the agent can take over.\n // RunScreen mounts → kicks off the agent loop on its useEffect.\n setScreen(\"run\");\n })\n .catch((err: unknown) => {\n if (cancelled) return;\n const msg = err instanceof Error ? err.message : String(err);\n setFatal(`OAuth failed: ${msg}`);\n });\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return (\n <Box flexDirection=\"column\" gap={1}>\n <Box>\n <Spinner label=\"Waiting for browser login…\" />\n </Box>\n\n {oauthUrl ? (\n <Box flexDirection=\"column\" marginTop={1}>\n <Text color=\"gray\">\n If your browser didn't open, paste this URL into a browser:\n </Text>\n <Box marginTop={1}>\n <Text color=\"cyan\">{oauthUrl}</Text>\n </Box>\n </Box>\n ) : (\n <Text color=\"gray\">Generating PKCE challenge…</Text>\n )}\n\n <Box marginTop={1}>\n <Text color=\"gray\">Timeout: 6 minutes · Ctrl-C to abort</Text>\n </Box>\n </Box>\n );\n}\n","/**\n * System-prompt rules appended to whatever Claude's default coding\n * agent persona provides. Kept deliberately short — 11 numbered\n * rules, no preamble, no explanation. The install prompt itself\n * carries the framework-specific instructions; commandments cover\n * what's true regardless of framework.\n *\n * Each rule maps to one of the wizard's safety guards or design\n * decisions documented in the implementation plan. Don't add prose\n * paragraphs here — every word costs every wizard run a token.\n */\nexport const COMMANDMENTS = `# Wizard agent commandments\n\nYou are the install agent inside @soloworks/smking-wizard. The user's project is open in the cwd. Follow these rules without exception:\n\n1. Use only the dedicated tools listed below. You have NO Bash, NO general Read/Write/Edit. The available tools are: \\`detect_framework\\`, \\`detect_package_manager\\`, \\`install_package\\`, \\`set_env\\`, \\`run_doctor\\`, \\`read_project_file\\`, \\`run_artisan\\` (Laravel only), and \\`report_failure\\`. If a step seems to need a different tool, that step is out of scope.\n\n2. Start by calling \\`detect_framework\\`. If it returns \\`unknown\\`, call \\`report_failure\\` with the evidence string and stop — do not guess.\n\n3. After detecting framework, call \\`install_package\\` once with the detected framework.\n\n4. After \\`install_package\\` succeeds, call \\`set_env\\` with SMKING_API_KEY and SMKING_BASE_URL extracted from the install prompt the user gave you. Both must match the values in the prompt verbatim. If the install prompt's \\`.env\\` block ALSO contains \\`SMKING_WEBHOOK_SECRET\\`, include that in the same \\`set_env\\` call (also verbatim — it's a 64-char hex string). The webhook secret powers CMS publish push-invalidate; omit it only if the prompt itself omits it (AEO-only sites).\n\n5. After \\`set_env\\`, call \\`run_doctor\\`. Parse the JSON result.\n\n6. If \\`run_doctor\\` returns \\`ok: true\\`, you are done. Output a one-line confirmation and stop.\n\n7. If \\`run_doctor\\` returns \\`ok: false\\`, identify which check failed and try to fix it BEFORE giving up. Diagnose first, retry second:\n - For Laravel cache/config-stale symptoms (API reachable: fail but env is set correctly; doctor flips red right after \\`set_env\\`): try \\`run_artisan(command=\"config:clear\")\\` then \\`run_artisan(command=\"cache:clear\")\\`, then re-run \\`run_doctor\\`. This solves a common stale-config scenario.\n - For \"X-Smking-Status: server_error\" / circuit-breaker symptoms: try \\`run_artisan(command=\"smking:cache:purge\")\\`, then re-run \\`run_doctor\\`.\n - For unknown failures, use \\`read_project_file\\` to inspect \\`composer.json\\` (version pin), \\`app/Http/Kernel.php\\` (middleware register state), or \\`.env\\` (actual values). The content often reveals the root cause and tells you whether a retry has any chance.\n - Only retry \\`install_package\\` or \\`set_env\\` when the inspected state confirms those are the layer to fix.\n - Maximum THREE retries for the same failing check across ALL fix attempts combined (not three retries per fix type).\n\n8. After three failed retries on the same check — or when \\`read_project_file\\` reveals an unfixable condition (Laravel version too old, custom Kernel, missing PHP extension) — call \\`report_failure\\` with the failed check list, environment details, the raw doctor output, AND any relevant content from \\`read_project_file\\` that explains the root cause. Then stop.\n\n9. NEVER attempt to use git, edit files outside the wizard tools, run shell commands, or install packages other than smking SDKs. The wizard tools are the complete surface.\n\n10. NEVER include API keys, secrets, or token values in your text response. If you need to mention them, refer by name (e.g. \"SMKING_API_KEY\") not by value.\n\n11. Be terse. The user sees a TUI with a spinner — every paragraph you emit is a paragraph they have to wait through. One sentence per major action is enough.`;\n","import { SAAS_URL } from \"../constants\";\nimport type { Framework } from \"../detection/framework\";\n\n/**\n * Fetch the install prompt for the detected framework from the\n * smking SaaS. The prompt content lives in\n * `apps/web/src/features/aeo/lib/install-prompts/{laravel,nextjs}.ts`\n * and has the customer's `publicApiKey` and `baseUrl` already\n * substituted server-side — wizard never receives a raw key\n * separately from the prompt itself.\n *\n * The agent reads this markdown as its initial user message, then\n * walks through the steps using the wizard's dedicated tools.\n */\nexport async function fetchInstallPrompt(opts: {\n framework: Framework;\n oauthToken: string;\n}): Promise<string> {\n if (opts.framework !== \"laravel\" && opts.framework !== \"nextjs\") {\n throw new Error(\n `fetchInstallPrompt called with unsupported framework: ${opts.framework}`,\n );\n }\n\n const url = new URL(`${SAAS_URL}/api/v1/wizard/install-prompt`);\n url.searchParams.set(\"framework\", opts.framework);\n\n const response = await fetch(url, {\n headers: {\n authorization: `Bearer ${opts.oauthToken}`,\n accept: \"text/markdown\",\n },\n });\n\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n throw new Error(\n `Failed to fetch install prompt: HTTP ${response.status} — ${text.slice(0, 200)}`,\n );\n }\n\n return response.text();\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\n/**\n * Framework detection for the wizard. Walks the cwd looking for\n * deterministic signals — never asks the LLM what framework it is,\n * because that's the kind of question we can answer 100% correctly\n * from file presence checks alone.\n *\n * Order matters: Laravel comes before Next.js. If a project somehow\n * has both `artisan` AND `package.json` with `next`, the wizard\n * picks Laravel because that's the more specific signal (PHP\n * project structure is harder to false-positive on).\n */\n\nexport type Framework = \"laravel\" | \"nextjs\" | \"unknown\";\n\nexport type FrameworkDetection = {\n framework: Framework;\n evidence: string;\n appDir?: string; // For Next.js: `app/` or `src/app/`\n};\n\nexport function detectFramework(cwd: string = process.cwd()): FrameworkDetection {\n // ── Laravel ────────────────────────────────────────────────\n // `artisan` is unique to Laravel — no other framework ships it.\n // composer.json with `laravel/framework` is a secondary signal we\n // could use but `artisan` alone is sufficient.\n const artisanPath = join(cwd, \"artisan\");\n if (existsSync(artisanPath)) {\n return {\n framework: \"laravel\",\n evidence: `artisan present at ${artisanPath}`,\n };\n }\n\n // ── Next.js ────────────────────────────────────────────────\n // `package.json` with `next` in dependencies/devDependencies +\n // an `app/` or `src/app/` directory (App Router only — Pages\n // Router isn't supported by `<SmkingAEO />`).\n const pkgPath = join(cwd, \"package.json\");\n if (existsSync(pkgPath)) {\n let pkg: { dependencies?: Record<string, string>; devDependencies?: Record<string, string> };\n try {\n pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\"));\n } catch {\n return {\n framework: \"unknown\",\n evidence: \"package.json present but unparseable\",\n };\n }\n\n const hasNext =\n !!pkg.dependencies?.[\"next\"] || !!pkg.devDependencies?.[\"next\"];\n if (hasNext) {\n const appDir = [\"app\", \"src/app\"].find((d) =>\n existsSync(join(cwd, d)),\n );\n if (!appDir) {\n return {\n framework: \"unknown\",\n evidence:\n \"Next.js detected but no app/ or src/app/ — wizard only supports App Router\",\n };\n }\n return {\n framework: \"nextjs\",\n evidence: `next in package.json + ${appDir}/ directory`,\n appDir,\n };\n }\n }\n\n return {\n framework: \"unknown\",\n evidence:\n \"no `artisan` (Laravel) and no `package.json` with `next` (Next.js) found in cwd\",\n };\n}\n","import { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\n/**\n * Package manager detection — lockfile presence is the canonical\n * signal. Order matters in case multiple lockfiles coexist (which\n * is itself a customer-side bug, but we still pick one):\n * pnpm > bun > yarn > npm\n *\n * pnpm wins ties because smking's monorepo uses pnpm and the\n * wizard's default install path is pnpm-friendly.\n */\n\nexport type NodePackageManager = \"pnpm\" | \"bun\" | \"yarn\" | \"npm\";\n\nconst LOCKFILES: Array<{ name: string; pm: NodePackageManager }> = [\n { name: \"pnpm-lock.yaml\", pm: \"pnpm\" },\n { name: \"bun.lockb\", pm: \"bun\" },\n { name: \"bun.lock\", pm: \"bun\" },\n { name: \"yarn.lock\", pm: \"yarn\" },\n { name: \"package-lock.json\", pm: \"npm\" },\n];\n\nexport type PackageManagerDetection = {\n packageManager: NodePackageManager;\n lockfile: string | null;\n};\n\nexport function detectNodePackageManager(\n cwd: string = process.cwd(),\n): PackageManagerDetection {\n for (const { name, pm } of LOCKFILES) {\n if (existsSync(join(cwd, name))) {\n return { packageManager: pm, lockfile: name };\n }\n }\n // No lockfile — assume npm as the lowest common denominator.\n return { packageManager: \"npm\", lockfile: null };\n}\n","import { spawn } from \"node:child_process\";\n\n/**\n * Thin promise wrapper around `child_process.spawn`. Captures\n * stdout + stderr + exit code so installers can inspect output to\n * detect \"package already installed\" idempotency.\n *\n * Why not `execa`? — we'd pull in a runtime dependency just for\n * convenience around the Node stdlib. The Node API is fine for our\n * narrow needs (install commands, no shell interpolation).\n */\n\nexport type ExecResult = {\n stdout: string;\n stderr: string;\n code: number;\n};\n\nexport function run(\n cmd: string,\n args: string[],\n opts: {\n cwd?: string;\n env?: NodeJS.ProcessEnv;\n timeoutMs?: number;\n } = {},\n): Promise<ExecResult> {\n return new Promise((resolve, reject) => {\n const proc = spawn(cmd, args, {\n cwd: opts.cwd ?? process.cwd(),\n env: { ...process.env, ...opts.env },\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let stdout = \"\";\n let stderr = \"\";\n proc.stdout?.on(\"data\", (chunk) => {\n stdout += chunk.toString();\n });\n proc.stderr?.on(\"data\", (chunk) => {\n stderr += chunk.toString();\n });\n\n let timer: NodeJS.Timeout | undefined;\n if (opts.timeoutMs) {\n timer = setTimeout(() => {\n proc.kill(\"SIGTERM\");\n }, opts.timeoutMs);\n }\n\n proc.on(\"error\", (err) => {\n if (timer) clearTimeout(timer);\n reject(err);\n });\n proc.on(\"close\", (code) => {\n if (timer) clearTimeout(timer);\n resolve({ stdout, stderr, code: code ?? 1 });\n });\n });\n}\n","import { existsSync, readFileSync, writeFileSync } from \"node:fs\";\n\n/**\n * Append or update env keys in a `.env` / `.env.local` file.\n * - If a key already exists with the same value: no-op (idempotent).\n * - If a key exists with a different value: update in place.\n * - If a key is new: append at the end with a one-line comment\n * crediting the wizard (so the customer can grep back to find\n * what added it).\n *\n * Preserves all unrelated content (comments, blank lines, other\n * keys). Won't touch quoting style — if the file uses `KEY=\"value\"`,\n * we write `KEY=value` for new keys; that's the standard format\n * and customer-side dotenv libraries handle both.\n */\n\nexport type EnvFileChange = {\n changed: string[];\n unchanged: string[];\n};\n\nexport function setEnvKeys(\n envPath: string,\n updates: Record<string, string>,\n): EnvFileChange {\n const existing = existsSync(envPath)\n ? readFileSync(envPath, \"utf-8\")\n : \"\";\n const lines = existing.split(\"\\n\");\n\n const changed: string[] = [];\n const unchanged: string[] = [];\n\n for (const [key, value] of Object.entries(updates)) {\n const idx = lines.findIndex((line) => {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) return false;\n return trimmed.startsWith(`${key}=`);\n });\n\n if (idx >= 0) {\n const currentLine = lines[idx];\n const currentValue = currentLine\n .slice(currentLine.indexOf(\"=\") + 1)\n // Strip optional matching quotes for comparison only.\n .replace(/^[\"']|[\"']$/g, \"\");\n if (currentValue === value) {\n unchanged.push(key);\n continue;\n }\n lines[idx] = `${key}=${value}`;\n changed.push(key);\n } else {\n // Append. Add a separating blank line + comment block if the\n // file doesn't already end with our marker.\n const hasOurMarker = lines.some((l) =>\n l.includes(\"Added by @soloworks/smking-wizard\"),\n );\n if (!hasOurMarker) {\n if (lines.at(-1)?.trim() !== \"\") {\n lines.push(\"\");\n }\n lines.push(\"# Added by @soloworks/smking-wizard\");\n }\n lines.push(`${key}=${value}`);\n changed.push(key);\n }\n }\n\n if (changed.length > 0) {\n writeFileSync(envPath, lines.join(\"\\n\"), \"utf-8\");\n }\n\n return { changed, unchanged };\n}\n","/**\n * Live registry queries for the latest published smking SDK versions.\n *\n * Why this exists: hardcoding a version (`\"smking/laravel:^0.10\"`) in\n * the installer goes stale the moment we ship a new minor — every\n * wizard `npx` run would then install the *previous* major.minor line\n * even though packagist already has a newer one. Querying the registry\n * at install time means the wizard always proposes the freshest stable\n * release without us re-cutting a wizard build.\n *\n * Failure mode: network down / registry returning 500 / unparseable\n * response → callers fall back to bare `composer require <pkg>` (no\n * constraint). That still installs the latest version compatible with\n * the customer's existing `composer.json` — not ideal if they're\n * pinned to an old `^0.X`, but never breaks the install.\n */\n\nconst REGISTRY_TIMEOUT_MS = 5_000;\n\nexport type LatestVersion = {\n /** Bare semver string, e.g. `\"0.10.1\"` */\n version: string;\n /** Caret range for use as a constraint, e.g. `\"^0.10\"` */\n caretRange: string;\n /** Source registry, for logging/debug */\n source: \"packagist\" | \"npm\";\n};\n\n/**\n * Convert `\"0.10.1\"` → `\"^0.10\"`. Pins major.minor so consumers get\n * the latest patch in this line automatically. For composer 0.x:\n * `^0.10` resolves to `>=0.10.0 <0.11.0`. For npm same semantics.\n */\nexport function toCaretRange(version: string): string {\n const match = version.match(/^(\\d+)\\.(\\d+)/);\n if (!match) throw new Error(`unparseable version string: ${version}`);\n return `^${match[1]}.${match[2]}`;\n}\n\n/**\n * `https://repo.packagist.org/p2/<name>.json` returns the metadata\n * with `packages.<name>` as a newest-first array of `{version, ...}`.\n * We pick the first entry matching a clean semver tag (no `-rc`,\n * `-dev`, `-alpha`).\n */\nexport async function getLatestPackagistVersion(\n packageName: string,\n): Promise<LatestVersion> {\n const url = `https://repo.packagist.org/p2/${packageName}.json`;\n const res = await fetch(url, {\n signal: AbortSignal.timeout(REGISTRY_TIMEOUT_MS),\n headers: { accept: \"application/json\" },\n });\n if (!res.ok) {\n throw new Error(`packagist ${packageName} HTTP ${res.status}`);\n }\n const data = (await res.json()) as {\n packages?: Record<string, Array<{ version?: string }>>;\n };\n const versions = data.packages?.[packageName] ?? [];\n // Packagist tags look like `v0.10.1`. Reject anything with a `-`\n // qualifier (prereleases) or `dev-` prefix (branch tags).\n const stable = versions.find((v) => {\n const ver = v.version ?? \"\";\n return /^v?\\d+\\.\\d+\\.\\d+$/.test(ver);\n });\n if (!stable?.version) {\n throw new Error(`packagist ${packageName}: no stable version found`);\n }\n const version = stable.version.replace(/^v/, \"\");\n return { version, caretRange: toCaretRange(version), source: \"packagist\" };\n}\n\n/**\n * npm root metadata returns `dist-tags.latest` — the version the\n * publisher tagged as the default for `npm install <name>` (with no\n * version specifier). This is what we want for \"install the freshest\n * stable line\"; explicit prerelease tags like `next` / `beta` are\n * intentionally ignored.\n */\nexport async function getLatestNpmVersion(\n packageName: string,\n): Promise<LatestVersion> {\n // URL-encode the scope slash for `@org/pkg` names — npm registry\n // accepts both `@scope/name` and `@scope%2Fname`, but encoding is\n // safer for fetch URL parsers.\n const encoded = packageName.replace(\"/\", \"%2F\");\n const url = `https://registry.npmjs.org/${encoded}`;\n const res = await fetch(url, {\n signal: AbortSignal.timeout(REGISTRY_TIMEOUT_MS),\n headers: { accept: \"application/json\" },\n });\n if (!res.ok) {\n throw new Error(`npm ${packageName} HTTP ${res.status}`);\n }\n const data = (await res.json()) as {\n \"dist-tags\"?: { latest?: string };\n };\n const latest = data[\"dist-tags\"]?.latest;\n if (!latest) {\n throw new Error(`npm ${packageName}: no latest tag`);\n }\n return { version: latest, caretRange: toCaretRange(latest), source: \"npm\" };\n}\n","import { join } from \"node:path\";\nimport { run } from \"../lib/exec\";\nimport { setEnvKeys } from \"../lib/env-file\";\nimport { getLatestPackagistVersion } from \"../lib/registry\";\n\n/**\n * Laravel installer — runs the deterministic command sequence from\n * `apps/web/src/features/aeo/lib/install-prompts/laravel.ts`. No\n * LLM involved; this is a fixed sequence that's been validated\n * end-to-end on the LocalWP test environment.\n *\n * Steps:\n * 1. `composer require smking/laravel` — pulls package + auto-runs\n * service provider auto-discovery via Laravel's package event.\n * 2. `php artisan vendor:publish --tag=smking-config` — copies\n * `config/smking.php` to the customer's repo so they can edit\n * `only` / `except` patterns later.\n * 3. Write SMKING_API_KEY + SMKING_BASE_URL to `.env`.\n * 4. `php artisan config:clear` — invalidates Laravel's config\n * cache so the new env vars take effect on next request.\n */\n\nexport type LaravelInstallContext = {\n apiKey: string;\n baseUrl: string;\n /**\n * v0.11+ — per-site HMAC secret for CMS publish webhook. Optional:\n * customer may not be using CMS body rendering, and AEO-only install\n * works without it. When provided, written to `.env` alongside the\n * API key so the auto-mounted `/api/smking/webhook` route can verify\n * inbound publish notifications from SaaS.\n */\n webhookSecret?: string;\n cwd: string;\n};\n\nexport type InstallStep = {\n name: string;\n status: \"done\" | \"skipped\" | \"failed\";\n detail?: string;\n};\n\nexport type InstallResult = {\n ok: boolean;\n steps: InstallStep[];\n};\n\nexport async function installLaravel(\n ctx: LaravelInstallContext,\n): Promise<InstallResult> {\n const steps: InstallStep[] = [];\n\n // ── Step 1: composer require ──────────────────────────────\n // Query packagist for the latest stable version so we always bump\n // the customer's composer.json constraint to the freshest line —\n // without that, a customer pinned to \"^0.7\" would silently get\n // v0.7.4 even though packagist has v0.10.x. Falls back to the\n // bare `composer require` (no constraint) if the registry is\n // unreachable so install never hard-fails on a network blip.\n let constraint = \"smking/laravel\";\n let bumpedTo: string | null = null;\n try {\n const latest = await getLatestPackagistVersion(\"smking/laravel\");\n constraint = `smking/laravel:${latest.caretRange}`;\n bumpedTo = latest.version;\n } catch {\n // Registry query failed — proceed without constraint. Agent\n // can still inspect composer.json afterwards via\n // `read_project_file` to detect stale-version symptoms.\n }\n const composerResult = await run(\n \"composer\",\n [\"require\", constraint],\n { cwd: ctx.cwd, timeoutMs: 180_000 },\n );\n if (composerResult.code !== 0) {\n steps.push({\n name: `composer require ${constraint}`,\n status: \"failed\",\n detail: composerResult.stderr.slice(0, 500),\n });\n return { ok: false, steps };\n }\n // Composer's \"is already installed\" message vs first-time install\n // — both are success, but expose to the user which path we took.\n const alreadyInstalled = composerResult.stdout.includes(\n \"is already in the composer.json\",\n );\n steps.push({\n name: `composer require ${constraint}`,\n status: alreadyInstalled ? \"skipped\" : \"done\",\n detail: bumpedTo\n ? `latest packagist: v${bumpedTo} (caret bumped)`\n : \"registry query failed — installed at existing constraint\",\n });\n\n // ── Step 2: vendor:publish ────────────────────────────────\n const publishResult = await run(\n \"php\",\n [\"artisan\", \"vendor:publish\", \"--tag=smking-config\"],\n { cwd: ctx.cwd, timeoutMs: 60_000 },\n );\n if (publishResult.code !== 0) {\n steps.push({\n name: \"php artisan vendor:publish --tag=smking-config\",\n status: \"failed\",\n detail: publishResult.stderr.slice(0, 500),\n });\n return { ok: false, steps };\n }\n steps.push({\n name: \"php artisan vendor:publish --tag=smking-config\",\n status: \"done\",\n });\n\n // ── Step 3: .env keys ─────────────────────────────────────\n // Only write env keys when caller supplied real values. The wizard\n // agent path passes placeholder strings (because the real values\n // live in the install prompt the agent parses, then sets via the\n // dedicated `set_env` tool) — writing those placeholders into the\n // customer's .env would leave it polluted if the agent later\n // aborted before `set_env` ran (e.g. doctor retry exhaustion).\n // Empty / placeholder values short-circuit so this installer stays\n // a pure \"package install\" step and env handling is owned by\n // `set_env`. Callers wanting one-shot install+env still work —\n // they pass real `pk_...` / `https://...` strings.\n const envValueLooksReal =\n ctx.apiKey &&\n ctx.baseUrl &&\n !ctx.apiKey.startsWith(\"<\") &&\n !ctx.baseUrl.startsWith(\"<\");\n if (envValueLooksReal) {\n const updates: Record<string, string> = {\n SMKING_API_KEY: ctx.apiKey,\n SMKING_BASE_URL: ctx.baseUrl,\n };\n // v0.11+ — webhook secret is optional. Skip when caller didn't\n // pass one (AEO-only install path) so we don't blank-out an\n // existing customer-set secret with empty string.\n const willWriteWebhook =\n ctx.webhookSecret &&\n ctx.webhookSecret.length > 0 &&\n !ctx.webhookSecret.startsWith(\"<\");\n if (willWriteWebhook) {\n updates.SMKING_WEBHOOK_SECRET = ctx.webhookSecret!;\n }\n const envChange = setEnvKeys(join(ctx.cwd, \".env\"), updates);\n const labelKeys = willWriteWebhook\n ? \"SMKING_API_KEY + SMKING_BASE_URL + SMKING_WEBHOOK_SECRET\"\n : \"SMKING_API_KEY + SMKING_BASE_URL\";\n steps.push({\n name: `Write ${labelKeys} to .env`,\n status: envChange.changed.length > 0 ? \"done\" : \"skipped\",\n detail:\n envChange.changed.length > 0\n ? `wrote: ${envChange.changed.join(\", \")}`\n : `already set: ${envChange.unchanged.join(\", \")}`,\n });\n } else {\n steps.push({\n name: \"Write SMKING_API_KEY + SMKING_BASE_URL to .env\",\n status: \"skipped\",\n detail: \"env handled by separate set_env step (wizard agent path)\",\n });\n }\n\n // ── Step 4: config:clear ──────────────────────────────────\n const clearResult = await run(\n \"php\",\n [\"artisan\", \"config:clear\"],\n { cwd: ctx.cwd, timeoutMs: 30_000 },\n );\n if (clearResult.code !== 0) {\n // Non-fatal — the env change will take effect on next config\n // rebuild anyway. Surface as info, not failure.\n steps.push({\n name: \"php artisan config:clear\",\n status: \"failed\",\n detail: clearResult.stderr.slice(0, 500),\n });\n // Continue — install is essentially done, just with stale cache.\n } else {\n steps.push({ name: \"php artisan config:clear\", status: \"done\" });\n }\n\n return { ok: true, steps };\n}\n","import { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { run } from \"../lib/exec\";\nimport { setEnvKeys } from \"../lib/env-file\";\nimport { getLatestNpmVersion } from \"../lib/registry\";\nimport type { NodePackageManager } from \"../detection/package-manager\";\nimport type { InstallStep, InstallResult } from \"./laravel\";\n\n/**\n * Next.js installer — runs the deterministic command sequence from\n * `apps/web/src/features/aeo/lib/install-prompts/nextjs.ts`:\n *\n * 1. `<pm> add @soloworks/smking-next` (pm = pnpm | npm | yarn | bun)\n * 2. Write SMKING_API_KEY + SMKING_BASE_URL to `.env.local`.\n * 3. Add `<SmkingAEO />` import + render to `app/layout.tsx`\n * (idempotent — skips if already present).\n *\n * App Router only. Pages Router isn't supported by the SDK.\n */\n\nexport type NextjsInstallContext = {\n apiKey: string;\n baseUrl: string;\n /**\n * v0.11+ — per-site HMAC secret for CMS publish webhook. Optional:\n * customer may not be using CMS body rendering, and AEO-only install\n * works without it. When provided, written to `.env.local` alongside\n * the API key so the `/api/smking/webhook` route (mounted via a\n * one-line re-export per the install prompt) can verify inbound\n * publish notifications from SaaS.\n */\n webhookSecret?: string;\n cwd: string;\n packageManager: NodePackageManager;\n appDir: string; // \"app\" or \"src/app\"\n};\n\nconst PM_ADD_ARGS: Record<NodePackageManager, string[]> = {\n pnpm: [\"add\"],\n bun: [\"add\"],\n yarn: [\"add\"],\n npm: [\"install\"],\n};\n\nexport async function installNextjs(\n ctx: NextjsInstallContext,\n): Promise<InstallResult> {\n const steps: InstallStep[] = [];\n\n // ── Step 1: package install ───────────────────────────────\n // Query npm for the latest stable version so we bump customer's\n // package.json constraint to the freshest major.minor line. See\n // installers/laravel.ts Step 1 for the same rationale on the\n // packagist side. Falls back to no-version-specifier install if\n // the npm registry query fails.\n let packageSpec = \"@soloworks/smking-next\";\n let bumpedTo: string | null = null;\n try {\n const latest = await getLatestNpmVersion(\"@soloworks/smking-next\");\n packageSpec = `@soloworks/smking-next@${latest.caretRange}`;\n bumpedTo = latest.version;\n } catch {\n // Network / npm down — install at existing constraint. Agent\n // can `read_project_file` package.json afterwards to detect\n // stale-version symptoms.\n }\n const addArgs = [...PM_ADD_ARGS[ctx.packageManager], packageSpec];\n const installResult = await run(ctx.packageManager, addArgs, {\n cwd: ctx.cwd,\n timeoutMs: 180_000,\n });\n if (installResult.code !== 0) {\n steps.push({\n name: `${ctx.packageManager} ${addArgs.join(\" \")}`,\n status: \"failed\",\n detail: installResult.stderr.slice(0, 500),\n });\n return { ok: false, steps };\n }\n steps.push({\n name: `${ctx.packageManager} ${addArgs.join(\" \")}`,\n status: \"done\",\n detail: bumpedTo\n ? `latest npm: v${bumpedTo} (caret bumped)`\n : \"registry query failed — installed at existing constraint\",\n });\n\n // ── Step 2: .env.local keys ───────────────────────────────\n // See installers/laravel.ts Step 3 for the rationale: when the wizard\n // agent calls this with placeholder values, skip — env is handled by\n // the agent's separate `set_env` tool. Only write when caller supplied\n // real values (one-shot install path outside the agent).\n const envValueLooksReal =\n ctx.apiKey &&\n ctx.baseUrl &&\n !ctx.apiKey.startsWith(\"<\") &&\n !ctx.baseUrl.startsWith(\"<\");\n if (!envValueLooksReal) {\n steps.push({\n name: \"Write SMKING_API_KEY + SMKING_BASE_URL to .env.local\",\n status: \"skipped\",\n detail: \"env handled by separate set_env step (wizard agent path)\",\n });\n } else {\n const updates: Record<string, string> = {\n SMKING_API_KEY: ctx.apiKey,\n SMKING_BASE_URL: ctx.baseUrl,\n };\n const willWriteWebhook =\n ctx.webhookSecret &&\n ctx.webhookSecret.length > 0 &&\n !ctx.webhookSecret.startsWith(\"<\");\n if (willWriteWebhook) {\n updates.SMKING_WEBHOOK_SECRET = ctx.webhookSecret!;\n }\n const envChange = setEnvKeys(join(ctx.cwd, \".env.local\"), updates);\n const labelKeys = willWriteWebhook\n ? \"SMKING_API_KEY + SMKING_BASE_URL + SMKING_WEBHOOK_SECRET\"\n : \"SMKING_API_KEY + SMKING_BASE_URL\";\n steps.push({\n name: `Write ${labelKeys} to .env.local`,\n status: envChange.changed.length > 0 ? \"done\" : \"skipped\",\n detail:\n envChange.changed.length > 0\n ? `wrote: ${envChange.changed.join(\", \")}`\n : `already set: ${envChange.unchanged.join(\", \")}`,\n });\n }\n\n // ── Step 3: layout.tsx edit ───────────────────────────────\n const layoutResult = addSmkingAEOToLayout(ctx.cwd, ctx.appDir);\n steps.push(layoutResult);\n if (layoutResult.status === \"failed\") {\n return { ok: false, steps };\n }\n\n return { ok: true, steps };\n}\n\n/**\n * Find `<appDir>/layout.tsx` (or .jsx/.ts/.js), inject SmkingAEO\n * import + render. Idempotent: if `SmkingAEO` substring is already\n * present, returns skipped without re-editing.\n *\n * Uses targeted string manipulation rather than full AST parsing\n * because:\n * - The edit shape is fixed (import + JSX inside <body>)\n * - Layout files are conventional Next.js boilerplate; the\n * vanilla case covers 95% of customers\n * - Edge cases (custom layouts, no <body>) gracefully degrade\n * to \"manual instructions in error message\"\n *\n * AST parsing (magicast / recast) is an option we may revisit if\n * field reports show this is too fragile.\n */\nfunction addSmkingAEOToLayout(cwd: string, appDir: string): InstallStep {\n const candidates = [\n join(cwd, appDir, \"layout.tsx\"),\n join(cwd, appDir, \"layout.jsx\"),\n join(cwd, appDir, \"layout.ts\"),\n join(cwd, appDir, \"layout.js\"),\n ];\n const layoutPath = candidates.find((p) => existsSync(p));\n if (!layoutPath) {\n return {\n name: \"Inject <SmkingAEO /> into root layout\",\n status: \"failed\",\n detail: `no layout file found under ${appDir}/ — add <SmkingAEO apiKey={process.env.SMKING_API_KEY!} /> to your root layout manually`,\n };\n }\n\n const content = readFileSync(layoutPath, \"utf-8\");\n\n // Idempotency: already imported.\n if (content.includes(\"SmkingAEO\")) {\n return {\n name: \"Inject <SmkingAEO /> into root layout\",\n status: \"skipped\",\n detail: `${layoutPath} already references SmkingAEO`,\n };\n }\n\n // Find last import statement to inject our import after.\n const importRegex = /^import\\s+[^;]+;?\\s*$/gm;\n const importMatches = [...content.matchAll(importRegex)];\n if (importMatches.length === 0) {\n return {\n name: \"Inject <SmkingAEO /> into root layout\",\n status: \"failed\",\n detail: `${layoutPath} has no import statements — add SmkingAEO manually`,\n };\n }\n\n const lastImport = importMatches[importMatches.length - 1];\n const lastImportEnd = lastImport.index + lastImport[0].length;\n const importLine = '\\nimport { SmkingAEO } from \"@soloworks/smking-next\";';\n let updated =\n content.slice(0, lastImportEnd) +\n importLine +\n content.slice(lastImportEnd);\n\n // Find <body> opening tag to inject SmkingAEO after.\n const bodyMatch = updated.match(/<body([^>]*)>/);\n if (!bodyMatch || bodyMatch.index === undefined) {\n return {\n name: \"Inject <SmkingAEO /> into root layout\",\n status: \"failed\",\n detail: `${layoutPath} has no <body> tag — add SmkingAEO manually inside <body>`,\n };\n }\n const bodyEnd = bodyMatch.index + bodyMatch[0].length;\n const injection =\n \"\\n <SmkingAEO apiKey={process.env.SMKING_API_KEY!} />\";\n updated =\n updated.slice(0, bodyEnd) + injection + updated.slice(bodyEnd);\n\n writeFileSync(layoutPath, updated, \"utf-8\");\n return {\n name: \"Inject <SmkingAEO /> into root layout\",\n status: \"done\",\n detail: `wrote import + render to ${layoutPath}`,\n };\n}\n","import Anthropic from \"@anthropic-ai/sdk\";\nimport { detectFramework, type Framework } from \"../detection/framework\";\nimport { detectNodePackageManager } from \"../detection/package-manager\";\nimport { installLaravel } from \"../installers/laravel\";\nimport { installNextjs } from \"../installers/nextjs\";\nimport { setEnvKeys } from \"../lib/env-file\";\nimport { run } from \"../lib/exec\";\nimport { SAAS_URL } from \"../constants\";\nimport { useWizardStore } from \"../ui/store\";\nimport { promises as fs } from \"node:fs\";\nimport { join, relative, resolve } from \"node:path\";\n\n/**\n * The 6 dedicated tools the wizard agent can call. Defined as plain\n * tool descriptors (no SDK helpers) so we can use them with the\n * manual agentic loop in runtime.ts — `client.beta.messages.create`\n * + custom dispatch — instead of `toolRunner` which had reliability\n * issues with our gateway + adaptive thinking combo.\n *\n * Each tool has a JSON Schema input shape (Anthropic-compatible)\n * and a `run` handler that:\n * - pushes a progress message into the store (drives the TUI)\n * - performs the deterministic work\n * - returns a JSON-stringified result (tool_result content shape)\n */\n\ntype ToolContext = {\n cwd: string;\n oauthToken: string;\n siteId: string | null;\n};\n\nexport type WizardToolDef = {\n name: string;\n description: string;\n input_schema: Anthropic.Tool.InputSchema;\n run: (input: unknown) => Promise<string>;\n};\n\nfunction asResult(value: unknown): string {\n return JSON.stringify(value);\n}\n\nfunction stepIcon(status: \"done\" | \"skipped\" | \"failed\"): string {\n return status === \"done\" ? \"✓\" : status === \"skipped\" ? \"⊝\" : \"✗\";\n}\n\nexport function buildWizardTools(ctx: ToolContext): WizardToolDef[] {\n const pushProgress = (text: string) =>\n useWizardStore.getState().pushProgress(text);\n\n return [\n // ── 1. detect_framework ───────────────────────────────────\n {\n name: \"detect_framework\",\n description:\n \"Detect whether the current project is Laravel or Next.js. Returns { framework, evidence, appDir? }. Always call this first before any install steps.\",\n input_schema: {\n type: \"object\",\n properties: {},\n },\n run: async () => {\n pushProgress(\"Detecting framework…\");\n const result = detectFramework(ctx.cwd);\n pushProgress(\n result.framework === \"unknown\"\n ? `Framework: unknown (${result.evidence})`\n : `Framework: ${result.framework}`,\n );\n return asResult(result);\n },\n },\n\n // ── 2. detect_package_manager ─────────────────────────────\n {\n name: \"detect_package_manager\",\n description:\n \"Detect the Node package manager from the project's lockfile (pnpm / bun / yarn / npm). Returns { packageManager, lockfile }.\",\n input_schema: {\n type: \"object\",\n properties: {},\n },\n run: async () => {\n const result = detectNodePackageManager(ctx.cwd);\n pushProgress(\n `Package manager: ${result.packageManager}${\n result.lockfile\n ? ` (${result.lockfile})`\n : \" (no lockfile, assuming npm)\"\n }`,\n );\n return asResult(result);\n },\n },\n\n // ── 3. install_package ────────────────────────────────────\n {\n name: \"install_package\",\n description:\n \"Install the smking SDK for the detected framework. Runs composer require + vendor:publish + config:clear (Laravel), or pnpm/npm/yarn/bun add + edit layout.tsx (Next.js). Idempotent — already-installed steps return 'skipped'. Returns { ok, steps[] }.\",\n input_schema: {\n type: \"object\",\n properties: {\n framework: {\n type: \"string\",\n enum: [\"laravel\", \"nextjs\"],\n description: \"Framework to install for (must match detect_framework result)\",\n },\n },\n required: [\"framework\"],\n },\n run: async (input) => {\n const { framework } = input as { framework: \"laravel\" | \"nextjs\" };\n pushProgress(`Installing smking SDK for ${framework}…`);\n\n if (framework === \"laravel\") {\n const result = await installLaravel({\n apiKey: \"<set-by-set_env>\",\n baseUrl: \"<set-by-set_env>\",\n // webhookSecret deliberately omitted; agent path writes\n // all env values through set_env so the installer skips\n // its own write step entirely.\n cwd: ctx.cwd,\n });\n for (const step of result.steps) {\n pushProgress(` ${stepIcon(step.status)} ${step.name}`);\n }\n return asResult(result);\n }\n\n // Next.js — need appDir + packageManager\n const detection = detectFramework(ctx.cwd);\n if (detection.framework !== \"nextjs\" || !detection.appDir) {\n return asResult({\n ok: false,\n steps: [\n {\n name: \"install_nextjs\",\n status: \"failed\",\n detail:\n \"framework re-detection returned non-nextjs — call detect_framework first and confirm\",\n },\n ],\n });\n }\n const pmDetection = detectNodePackageManager(ctx.cwd);\n const result = await installNextjs({\n apiKey: \"<set-by-set_env>\",\n baseUrl: \"<set-by-set_env>\",\n cwd: ctx.cwd,\n packageManager: pmDetection.packageManager,\n appDir: detection.appDir,\n });\n for (const step of result.steps) {\n pushProgress(` ${stepIcon(step.status)} ${step.name}`);\n }\n return asResult(result);\n },\n },\n\n // ── 4. set_env ────────────────────────────────────────────\n {\n name: \"set_env\",\n description:\n \"Write SMKING_API_KEY / SMKING_BASE_URL / SMKING_WEBHOOK_SECRET to the project's env file (.env for Laravel, .env.local for Next.js). Other keys are rejected. Idempotent.\",\n input_schema: {\n type: \"object\",\n properties: {\n SMKING_API_KEY: {\n type: \"string\",\n description: \"Publishable site API key (starts with pk_)\",\n },\n SMKING_BASE_URL: {\n type: \"string\",\n description: \"smking SaaS base URL (e.g. https://smking.com)\",\n },\n SMKING_WEBHOOK_SECRET: {\n type: \"string\",\n description:\n \"HMAC secret for verifying inbound CMS publish webhooks from SaaS. 64-char hex string. Optional — only present in the install prompt when the site has CMS enabled. Skip when absent from prompt.\",\n },\n },\n },\n run: async (input) => {\n pushProgress(\"Setting env values…\");\n const typed = input as {\n SMKING_API_KEY?: string;\n SMKING_BASE_URL?: string;\n SMKING_WEBHOOK_SECRET?: string;\n };\n // Defensive: only accept SMKING_* keys even if the agent tries\n // to sneak in others. JSON Schema doesn't enforce strictness\n // in Anthropic's tool definitions, so the gate lives here.\n const allowedKeys = new Set([\n \"SMKING_API_KEY\",\n \"SMKING_BASE_URL\",\n \"SMKING_WEBHOOK_SECRET\",\n ]);\n const updates: Record<string, string> = {};\n for (const [k, v] of Object.entries(typed)) {\n if (!allowedKeys.has(k) || typeof v !== \"string\") continue;\n updates[k] = v;\n }\n if (Object.keys(updates).length === 0) {\n return asResult({\n error:\n \"set_env called with no valid keys. Allowed: SMKING_API_KEY, SMKING_BASE_URL, SMKING_WEBHOOK_SECRET.\",\n });\n }\n const framework = detectFramework(ctx.cwd).framework;\n const envFile = framework === \"nextjs\" ? \".env.local\" : \".env\";\n const result = setEnvKeys(join(ctx.cwd, envFile), updates);\n pushProgress(\n `Env: changed=[${result.changed.join(\", \")}] unchanged=[${result.unchanged.join(\", \")}]`,\n );\n return asResult({ envFile, ...result });\n },\n },\n\n // ── 5. run_doctor ─────────────────────────────────────────\n {\n name: \"run_doctor\",\n description:\n \"Run the smking doctor self-check (php artisan smking:doctor --json for Laravel, smking-next doctor --json for Next.js). Returns structured JSON: { checks: [{name, status, detail}], summary: {passed, failed, info, ok} }.\",\n input_schema: {\n type: \"object\",\n properties: {},\n },\n run: async () => {\n pushProgress(\"Running smking:doctor…\");\n const framework = detectFramework(ctx.cwd).framework;\n\n let cmd: string;\n let args: string[];\n if (framework === \"laravel\") {\n cmd = \"php\";\n args = [\"artisan\", \"smking:doctor\", \"--json\"];\n } else if (framework === \"nextjs\") {\n const pm = detectNodePackageManager(ctx.cwd).packageManager;\n if (pm === \"pnpm\") {\n cmd = \"pnpm\";\n args = [\"exec\", \"smking-next\", \"doctor\", \"--json\"];\n } else if (pm === \"bun\") {\n cmd = \"bunx\";\n args = [\"smking-next\", \"doctor\", \"--json\"];\n } else if (pm === \"yarn\") {\n cmd = \"yarn\";\n args = [\"smking-next\", \"doctor\", \"--json\"];\n } else {\n cmd = \"npx\";\n args = [\"smking-next\", \"doctor\", \"--json\"];\n }\n } else {\n return asResult({\n ok: false,\n error: `Cannot run doctor — framework is ${framework}`,\n });\n }\n\n const result = await run(cmd, args, {\n cwd: ctx.cwd,\n timeoutMs: 60_000,\n });\n\n try {\n const parsed = JSON.parse(result.stdout);\n const failed = parsed.summary?.failed ?? 0;\n pushProgress(\n `Doctor: ${parsed.summary?.passed ?? 0} pass · ${failed} fail · ${parsed.summary?.info ?? 0} info`,\n );\n return asResult(parsed);\n } catch {\n return asResult({\n ok: false,\n error: `Doctor output was not JSON (exit code ${result.code})`,\n stdout: result.stdout.slice(0, 1000),\n stderr: result.stderr.slice(0, 1000),\n });\n }\n },\n },\n\n // ── 6. read_project_file ──────────────────────────────────\n // Inspection tool. Lets the agent look at composer.json /\n // package.json / Kernel.php / layout.tsx / .env etc. so it can\n // diagnose *why* a check failed before deciding whether to\n // retry or report. Heavy safety lives in the handler:\n // - path is resolved relative to cwd; absolute or `..`\n // traversal is rejected\n // - extension allowlist (config / source / env / lock files)\n // prevents fishing for binaries or random docs\n // - content truncated at 50KB to bound agent context spend\n {\n name: \"read_project_file\",\n description:\n \"Read a project file (relative path inside cwd) so you can inspect its content when debugging an install or doctor failure. Use this BEFORE concluding a problem is unfixable — composer.json reveals constraint pins, Kernel.php reveals middleware register state, .env reveals what's actually set. Allowed file types: json, php, ts, tsx, js, jsx, env, conf, yaml, yml, md, lock, htaccess. Returns { path, content, bytes } or { error }.\",\n input_schema: {\n type: \"object\",\n properties: {\n path: {\n type: \"string\",\n description:\n \"Project-relative path, e.g. `composer.json`, `app/Http/Kernel.php`, `.env`, `app/layout.tsx`. No absolute paths, no `..`.\",\n },\n },\n required: [\"path\"],\n },\n run: async (input) => {\n const { path } = input as { path: string };\n pushProgress(`Reading ${path}…`);\n // Safety 1: path traversal\n if (path.startsWith(\"/\") || path.includes(\"..\")) {\n return asResult({\n error: \"absolute paths and `..` traversal are not allowed\",\n });\n }\n const absPath = resolve(ctx.cwd, path);\n // Safety 2: resolved path must still be inside cwd. Defends\n // against symlink-style escapes that path-string checks miss.\n const rel = relative(ctx.cwd, absPath);\n if (rel.startsWith(\"..\") || resolve(rel) === resolve(\"\")) {\n return asResult({ error: \"resolved path escapes cwd\" });\n }\n // Safety 3: extension allowlist. The dotfile shortcuts\n // (.env, .env.local) and well-known dotfiles (.htaccess)\n // are explicitly permitted by basename match below.\n const allowedExt =\n /\\.(json|php|ts|tsx|js|jsx|env|conf|yaml|yml|md|lock|htaccess)$/i;\n const basename = path.split(\"/\").pop() ?? \"\";\n const allowedBasename = new Set([\n \".env\",\n \".env.local\",\n \".env.production\",\n \".env.example\",\n \".htaccess\",\n ]);\n if (!allowedExt.test(basename) && !allowedBasename.has(basename)) {\n return asResult({\n error: `file extension not allowed: ${basename}`,\n });\n }\n try {\n const content = await fs.readFile(absPath, \"utf-8\");\n const truncated =\n content.length > 50_000\n ? content.slice(0, 50_000) + \"\\n... [truncated at 50KB]\"\n : content;\n pushProgress(` read ${path} (${content.length} bytes)`);\n return asResult({\n path,\n content: truncated,\n bytes: content.length,\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return asResult({ error: msg });\n }\n },\n },\n\n // ── 7. run_artisan ────────────────────────────────────────\n // Laravel-only debug action. Allowlisted commands cover the\n // common \"cache/config stale\" recoveries plus inspection. NOT\n // a general `bash` escape hatch — every command is explicitly\n // listed and every flag matches one of a small regex set.\n //\n // Why these commands:\n // - smking:doctor — already used by run_doctor, but exposed\n // here so the agent can re-run after a cache:clear without\n // paying run_doctor's JSON-parse overhead.\n // - smking:cache:purge — clears SDK-side AEO response cache\n // (the sleepytofu 24hr-stale scenario).\n // - smking:status — circuit breaker state inspection.\n // - smking:publish-robots — v0.8.0+ artisan command.\n // - config:clear / config:cache — Laravel config cache cycle\n // after .env changes.\n // - cache:clear — generic app cache.\n // - route:list — confirm SDK routes are mounted (path-takeover).\n {\n name: \"run_artisan\",\n description:\n \"Run a Laravel artisan command from a fixed allowlist. Use for cache/config recovery (cache:clear, config:clear), SDK-specific inspection (smking:status, smking:cache:purge), or route confirmation (route:list). Not for arbitrary fixes — if you need a command not in the allowlist, call report_failure instead. Returns { exitCode, stdout, stderr }.\",\n input_schema: {\n type: \"object\",\n properties: {\n command: {\n type: \"string\",\n enum: [\n \"smking:doctor\",\n \"smking:cache:purge\",\n \"smking:status\",\n \"smking:publish-robots\",\n \"config:clear\",\n \"config:cache\",\n \"cache:clear\",\n \"route:list\",\n ],\n description:\n \"Artisan command name (no `php artisan` prefix). Must be one of the allowlist values.\",\n },\n args: {\n type: \"array\",\n items: { type: \"string\" },\n description:\n \"Optional CLI flags. Allowed forms: `--json`, `--force`, `--tag=<value>`, `--path=<value>`, `--key=<value>`. Other flags are rejected.\",\n },\n },\n required: [\"command\"],\n },\n run: async (input) => {\n const { command, args = [] } = input as {\n command: string;\n args?: string[];\n };\n const framework = detectFramework(ctx.cwd).framework;\n if (framework !== \"laravel\") {\n return asResult({\n error: `run_artisan only works in Laravel projects (detected: ${framework})`,\n });\n }\n // Allowlisted commands (must mirror enum above — defense in\n // depth; JSON schema enum doesn't always block at runtime).\n const ALLOWED_COMMANDS = new Set([\n \"smking:doctor\",\n \"smking:cache:purge\",\n \"smking:status\",\n \"smking:publish-robots\",\n \"config:clear\",\n \"config:cache\",\n \"cache:clear\",\n \"route:list\",\n ]);\n if (!ALLOWED_COMMANDS.has(command)) {\n return asResult({\n error: `command not in allowlist: ${command}`,\n });\n }\n // Flag allowlist — exact-match plain flags + parametric\n // forms with safe value charsets. Reject anything that\n // could shell out (no `;`, `|`, backticks, `$()`).\n const PLAIN_FLAGS = new Set([\"--json\", \"--force\"]);\n const PARAMETRIC = /^--(tag|path|key)=[\\w/.:_\\-+]+$/;\n for (const arg of args) {\n if (!PLAIN_FLAGS.has(arg) && !PARAMETRIC.test(arg)) {\n return asResult({ error: `disallowed flag: ${arg}` });\n }\n }\n pushProgress(`php artisan ${command} ${args.join(\" \")}`.trim());\n const result = await run(\"php\", [\"artisan\", command, ...args], {\n cwd: ctx.cwd,\n timeoutMs: 60_000,\n });\n // Cap output sizes so a chatty command (e.g. route:list on\n // a large app) can't blow agent context.\n return asResult({\n command,\n args,\n exitCode: result.code,\n stdout: result.stdout.slice(0, 5_000),\n stderr: result.stderr.slice(0, 2_000),\n });\n },\n },\n\n // ── 8. report_failure ─────────────────────────────────────\n {\n name: \"report_failure\",\n description:\n \"Report an unfixable install failure to smking support. Call this only after retrying the same failing check 3 times. Posts to smking dashboard. Returns { ticketId }.\",\n input_schema: {\n type: \"object\",\n properties: {\n failed_checks: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n name: { type: \"string\" },\n status: {\n type: \"string\",\n enum: [\"pass\", \"fail\", \"info\"],\n },\n detail: { type: \"string\" },\n },\n required: [\"name\", \"status\", \"detail\"],\n },\n minItems: 1,\n },\n environment: {\n type: \"object\",\n additionalProperties: { type: \"string\" },\n },\n raw_output: { type: \"string\" },\n },\n required: [\"failed_checks\", \"environment\", \"raw_output\"],\n },\n run: async (input) => {\n pushProgress(\"Reporting failure to smking support…\");\n const typed = input as {\n failed_checks: Array<{\n name: string;\n status: \"pass\" | \"fail\" | \"info\";\n detail: string;\n }>;\n environment: Record<string, string>;\n raw_output: string;\n };\n const framework = detectFramework(ctx.cwd).framework;\n\n const response = await fetch(`${SAAS_URL}/api/v1/doctor-reports`, {\n method: \"POST\",\n headers: {\n authorization: `Bearer ${ctx.oauthToken}`,\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify({\n framework,\n failed_checks: typed.failed_checks,\n environment: typed.environment,\n raw_output: typed.raw_output,\n }),\n });\n\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n return asResult({\n ok: false,\n error: `report_failure HTTP ${response.status}: ${text.slice(0, 200)}`,\n });\n }\n\n const data = (await response.json()) as {\n ticketId?: string;\n message?: string;\n };\n pushProgress(`Reported · ticket ${data.ticketId ?? \"(no id)\"}`);\n return asResult(data);\n },\n },\n ];\n}\n\nexport type { Framework };\n","import { appendFileSync } from \"node:fs\";\nimport Anthropic from \"@anthropic-ai/sdk\";\nimport { COMMANDMENTS } from \"./commandments\";\nimport { fetchInstallPrompt } from \"./prompt\";\nimport { buildWizardTools, type WizardToolDef } from \"./tools\";\nimport { detectFramework } from \"../detection/framework\";\nimport { SAAS_URL } from \"../constants\";\n\n/**\n * File-based debug logger. ink TUI takes over stdout/stderr so\n * `console.log` is invisible during a wizard run. Writing to\n * `/tmp/smking-wizard.log` gives us a tail-able stream of what's\n * happening inside the agent loop.\n */\nconst DEBUG_LOG = \"/tmp/smking-wizard.log\";\nfunction debugLog(msg: string, data?: unknown): void {\n try {\n const stamp = new Date().toISOString();\n const line = data\n ? `${stamp} ${msg} ${JSON.stringify(data, null, 2)}\\n`\n : `${stamp} ${msg}\\n`;\n appendFileSync(DEBUG_LOG, line);\n } catch {\n // Ignore log errors — never crash the wizard over telemetry.\n }\n}\n\n/**\n * Top-level agent loop. Uses **manual agentic loop** with\n * `messages.create` rather than `toolRunner` — the helper had\n * reliability issues with our gateway proxy + adaptive thinking\n * combination (hung indefinitely on the await even after Anthropic\n * returned a valid response).\n *\n * Manual loop pattern (from claude-api skill):\n * 1. messages.create → get response\n * 2. if stop_reason === \"end_turn\": exit\n * 3. if stop_reason === \"tool_use\": run each tool_use block's\n * handler, build tool_result content, push to messages\n * 4. repeat\n *\n * We bound the loop at MAX_ITERATIONS to prevent runaway agents.\n */\n\nexport type AgentContext = {\n cwd: string;\n oauthToken: string;\n siteId: string | null;\n};\n\nexport type AgentResult = {\n ok: boolean;\n message: string;\n};\n\nconst MAX_ITERATIONS = 30;\n\nexport async function runAgent(ctx: AgentContext): Promise<AgentResult> {\n debugLog(\"=== runAgent starting ===\", {\n cwd: ctx.cwd,\n siteId: ctx.siteId,\n saasUrl: SAAS_URL,\n });\n\n const detection = detectFramework(ctx.cwd);\n debugLog(\"framework detection result\", detection);\n if (detection.framework === \"unknown\") {\n return {\n ok: false,\n message: `Unable to detect framework in ${ctx.cwd}. ${detection.evidence}`,\n };\n }\n\n const client = new Anthropic({\n baseURL: `${SAAS_URL}/api/v1/wizard/gateway`,\n authToken: ctx.oauthToken,\n maxRetries: 0,\n });\n\n debugLog(\"fetching install prompt\", { framework: detection.framework });\n const installPrompt = await fetchInstallPrompt({\n framework: detection.framework,\n oauthToken: ctx.oauthToken,\n });\n debugLog(\"install prompt fetched\", { length: installPrompt.length });\n\n const toolDefs = buildWizardTools(ctx);\n debugLog(\"tools built\", {\n count: toolDefs.length,\n names: toolDefs.map((t) => t.name),\n });\n\n // Build the static tools array + dynamic handler map.\n const tools = toolDefs.map((t) => ({\n name: t.name,\n description: t.description,\n input_schema: t.input_schema,\n }));\n const handlers = new Map<string, WizardToolDef[\"run\"]>(\n toolDefs.map((t) => [t.name, t.run]),\n );\n\n const messages: Anthropic.MessageParam[] = [\n { role: \"user\", content: installPrompt },\n ];\n\n try {\n let lastTextSummary = \"\";\n // Track terminal outcomes so we can decide ok=true/false on end_turn:\n // - doctorPassed: run_doctor returned summary.ok===true\n // - reportedFailure: report_failure was called (means agent gave up\n // and filed a support ticket — the install is not green)\n // Agent can `end_turn` after either path; we treat ok as\n // (doctorPassed AND !reportedFailure) so a successful doctor run\n // is the only path that lights up the green DoneScreen.\n let doctorPassed = false;\n let reportedFailure = false;\n\n for (let iteration = 0; iteration < MAX_ITERATIONS; iteration++) {\n debugLog(`iteration ${iteration} — calling messages.create`);\n // Hard timeout — if SDK hangs (we've seen it deadlock on beta\n // namespace responses), break out so we get diagnostic logs.\n //\n // Per claude-api skill audit:\n // - model: claude-opus-4-7 (skill default; matches plan)\n // - thinking: adaptive — agent loop with tools is the \"remotely\n // complicated\" case the skill flags. Opus 4.7 omits\n // thinking text by default; we don't surface it.\n // - effort: Opus 4.7 default is `high`, which is the skill's\n // \"intelligence-sensitive\" sweet spot — leave it\n // implicit. (Bumping to `xhigh` would help agentic\n // correctness but ~doubles thinking-token cost.)\n // - cache_control on system block: COMMANDMENTS is identical\n // across all iterations within a session. Cache\n // reads cost ~0.1× input price. A typical install\n // does 4-6 round-trips at ~3.7K input each — cache\n // turns ~22K full-price input into ~3.7K write +\n // ~18K reads. SDK 0.68 doesn't type top-level\n // cache_control yet, so we attach it to the system\n // block (also caches `tools` since render order is\n // tools → system → messages).\n // Explicit annotation — without `stream`, the SDK return type is a\n // union of Message | Stream<...>. TypeScript 5 doesn't narrow that\n // union when system is an array (vs string), so we lock in Message\n // here to keep response.content / .stop_reason / .usage typed.\n const apiCallPromise: Promise<Anthropic.Message> =\n client.messages.create({\n model: \"claude-opus-4-7\",\n max_tokens: 16_000,\n thinking: { type: \"adaptive\" },\n system: [\n {\n type: \"text\",\n text: COMMANDMENTS,\n cache_control: { type: \"ephemeral\" },\n },\n ],\n tools,\n messages,\n });\n const timeoutPromise = new Promise<never>((_, reject) =>\n setTimeout(() => reject(new Error(\"messages.create timed out after 60s\")), 60_000),\n );\n const response = await Promise.race([apiCallPromise, timeoutPromise]);\n\n debugLog(`iteration ${iteration} — response received`, {\n stop_reason: response.stop_reason,\n content_block_types: response.content.map((c) => c.type),\n usage: response.usage,\n });\n\n // Always append the assistant turn (including thinking blocks)\n // so the next iteration's context is correct. Anthropic requires\n // thinking blocks with signatures to be preserved across turns\n // when using adaptive thinking + tool use.\n messages.push({ role: \"assistant\", content: response.content });\n\n // Capture latest text block for the final summary message.\n const texts = response.content\n .filter((c): c is Anthropic.TextBlock => c.type === \"text\")\n .map((c) => c.text)\n .join(\"\\n\")\n .trim();\n if (texts) lastTextSummary = texts;\n\n if (response.stop_reason === \"end_turn\") {\n debugLog(\"end_turn reached — agent done\");\n break;\n }\n\n if (response.stop_reason !== \"tool_use\") {\n // refusal / max_tokens / pause_turn / other — bail out with\n // whatever text the agent emitted as the summary.\n debugLog(\"non-tool_use stop_reason — exiting loop\", {\n stop_reason: response.stop_reason,\n });\n return {\n ok: false,\n message: `Agent stopped unexpectedly (${response.stop_reason}): ${lastTextSummary || \"no text emitted\"}`,\n };\n }\n\n // Process all tool_use blocks in the response.\n const toolResults: Anthropic.ToolResultBlockParam[] = [];\n for (const block of response.content) {\n if (block.type !== \"tool_use\") continue;\n const handler = handlers.get(block.name);\n if (!handler) {\n debugLog(\"unknown tool called\", { name: block.name });\n toolResults.push({\n type: \"tool_result\",\n tool_use_id: block.id,\n content: JSON.stringify({\n error: `Unknown tool: ${block.name}. Available tools: ${[...handlers.keys()].join(\", \")}`,\n }),\n is_error: true,\n });\n continue;\n }\n debugLog(\"calling tool handler\", {\n name: block.name,\n input: block.input,\n });\n try {\n const result = await handler(block.input);\n debugLog(\"tool handler returned\", {\n name: block.name,\n resultLength: result.length,\n });\n // Inspect tool outcomes that bear on the wizard's overall\n // success/failure. Parse defensively — handlers return a\n // JSON-stringified shape we control, so a parse failure\n // here is a bug we want to surface, not silent-ignore.\n if (block.name === \"run_doctor\") {\n try {\n const parsed = JSON.parse(result) as {\n summary?: { ok?: boolean };\n };\n if (parsed.summary?.ok === true) {\n doctorPassed = true;\n }\n } catch {\n // Doctor result wasn't JSON — leave doctorPassed false.\n }\n } else if (block.name === \"report_failure\") {\n reportedFailure = true;\n }\n toolResults.push({\n type: \"tool_result\",\n tool_use_id: block.id,\n content: result,\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n debugLog(\"tool handler threw\", { name: block.name, error: msg });\n toolResults.push({\n type: \"tool_result\",\n tool_use_id: block.id,\n content: JSON.stringify({ error: msg }),\n is_error: true,\n });\n }\n }\n\n messages.push({ role: \"user\", content: toolResults });\n }\n\n // ok=true ONLY when doctor passed and we didn't open a ticket.\n // Otherwise the install isn't actually green — route to error\n // screen so the customer sees ❌ not ✅.\n const ok = doctorPassed && !reportedFailure;\n debugLog(\"loop exited — returning summary\", {\n ok,\n doctorPassed,\n reportedFailure,\n summary: lastTextSummary,\n });\n return {\n ok,\n message:\n lastTextSummary ||\n (ok ? \"Wizard completed.\" : \"Wizard ended without a successful doctor run.\"),\n };\n } catch (err) {\n debugLog(\"agent loop threw\", {\n error: err instanceof Error ? err.message : String(err),\n stack: err instanceof Error ? err.stack : undefined,\n });\n return {\n ok: false,\n message:\n err instanceof Error\n ? err.message\n : `Agent loop failed: ${String(err)}`,\n };\n }\n}\n","import { useEffect } from \"react\";\nimport { Box, Text } from \"ink\";\nimport { Spinner } from \"@inkjs/ui\";\nimport { useWizardStore } from \"../store\";\nimport { runAgent } from \"../../agent/runtime\";\n\n/**\n * Live agent screen. Mounts → kicks off the install agent loop →\n * routes to done/error based on result. Renders the rolling\n * progress log + a spinner showing the latest action.\n *\n * Progress entries come from `pushProgress` calls inside each\n * wizard tool's `run` handler. The log is capped at 50 lines in\n * the store; we render the latest 12 to fit in a typical terminal\n * without scrolling.\n */\nexport function RunScreen() {\n const oauth = useWizardStore((s) => s.oauth);\n const agentStatus = useWizardStore((s) => s.agentStatus);\n const agentProgress = useWizardStore((s) => s.agentProgress);\n const setAgentStatus = useWizardStore((s) => s.setAgentStatus);\n const setAgentSummary = useWizardStore((s) => s.setAgentSummary);\n const setScreen = useWizardStore((s) => s.setScreen);\n const setFatal = useWizardStore((s) => s.setFatal);\n\n useEffect(() => {\n if (agentStatus !== \"idle\") return; // guard against double-mount\n if (!oauth) {\n setFatal(\"Reached run screen without OAuth tokens — restart wizard.\");\n return;\n }\n\n let cancelled = false;\n setAgentStatus(\"running\");\n runAgent({\n cwd: process.cwd(),\n oauthToken: oauth.accessToken,\n siteId: oauth.siteId,\n })\n .then((result) => {\n if (cancelled) return;\n setAgentSummary(result.message);\n if (result.ok) {\n setAgentStatus(\"succeeded\");\n setScreen(\"done\");\n } else {\n setAgentStatus(\"failed\");\n setFatal(result.message);\n }\n })\n .catch((err: unknown) => {\n if (cancelled) return;\n const msg = err instanceof Error ? err.message : String(err);\n setAgentStatus(\"failed\");\n setFatal(`Agent crashed: ${msg}`);\n });\n\n return () => {\n cancelled = true;\n };\n // Only run once on mount.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Show the latest N progress lines so a long install loop fits.\n const visibleLines = agentProgress.slice(-12);\n const latest = agentProgress[agentProgress.length - 1] ?? \"Starting agent…\";\n\n return (\n <Box flexDirection=\"column\" gap={1}>\n <Box>\n <Spinner label={latest} />\n </Box>\n\n {visibleLines.length > 1 ? (\n <Box flexDirection=\"column\" marginTop={1}>\n {visibleLines.slice(0, -1).map((line, idx) => (\n <Text key={idx} color=\"gray\">\n {line}\n </Text>\n ))}\n </Box>\n ) : null}\n\n <Box marginTop={1}>\n <Text color=\"gray\">Ctrl-C to abort</Text>\n </Box>\n </Box>\n );\n}\n","import { useEffect } from \"react\";\nimport { Box, Text, useApp, useInput } from \"ink\";\nimport { useWizardStore } from \"../store\";\n\n/**\n * Terminal state after a successful wizard run. Reached when\n * `runAgent` returns `ok: true` — meaning `run_doctor` returned\n * `summary.ok === true` AND `report_failure` was never called\n * (see runtime.ts for the gate).\n *\n * Shows the agent's own summary message so the customer sees what\n * actually got installed / verified, not a generic \"connected\".\n *\n * Press any key to exit. Auto-exits after 15s — longer than the\n * error-screen's 8s because customers need time to read the\n * pass/fail breakdown and notice any info-level recommendations.\n */\nexport function DoneScreen() {\n const { exit } = useApp();\n const oauth = useWizardStore((s) => s.oauth);\n const agentSummary = useWizardStore((s) => s.agentSummary);\n\n useInput(() => {\n exit();\n });\n\n useEffect(() => {\n const timer = setTimeout(() => exit(), 15_000);\n return () => clearTimeout(timer);\n }, [exit]);\n\n return (\n <Box flexDirection=\"column\" gap={1}>\n <Text bold color=\"green\">\n ✅ smking installed\n </Text>\n\n {agentSummary ? <Text>{agentSummary}</Text> : null}\n\n {oauth?.siteId ? (\n <Text color=\"gray\">\n Site: {oauth.siteId} · token expires in{\" \"}\n {Math.round(oauth.expiresIn / 60)} minutes\n </Text>\n ) : null}\n\n <Box marginTop={1}>\n <Text color=\"gray\">Press any key to exit (auto-exit in 15s).</Text>\n </Box>\n </Box>\n );\n}\n","import { useEffect } from \"react\";\nimport { Box, Text, useApp, useInput } from \"ink\";\nimport { useWizardStore } from \"../store\";\n\n/**\n * Fatal-error terminal screen. Any `setFatal()` call from anywhere\n * in the wizard routes here. Shows the error message and exits 1\n * on key press / auto-exit so callers can detect failure.\n */\nexport function ErrorScreen() {\n const { exit } = useApp();\n const fatal = useWizardStore((s) => s.fatal);\n\n useInput(() => {\n exit(new Error(fatal ?? \"wizard failed\"));\n });\n\n useEffect(() => {\n const timer = setTimeout(\n () => exit(new Error(fatal ?? \"wizard failed\")),\n 8000,\n );\n return () => clearTimeout(timer);\n }, [exit, fatal]);\n\n return (\n <Box flexDirection=\"column\" gap={1}>\n <Text bold color=\"red\">\n ❌ Wizard failed\n </Text>\n <Text>{fatal ?? \"Unknown error\"}</Text>\n <Box marginTop={1}>\n <Text color=\"gray\">\n Press any key to exit (auto-exit in 8s). Re-run{\" \"}\n <Text color=\"cyan\">npx @soloworks/smking-wizard</Text> after fixing the issue.\n </Text>\n </Box>\n </Box>\n );\n}\n","import { Box } from \"ink\";\nimport { useWizardStore } from \"./store\";\nimport { WelcomeScreen } from \"./screens/welcome-screen\";\nimport { OAuthScreen } from \"./screens/oauth-screen\";\nimport { RunScreen } from \"./screens/run-screen\";\nimport { DoneScreen } from \"./screens/done-screen\";\nimport { ErrorScreen } from \"./screens/error-screen\";\n\n/**\n * Root component. Routes by `store.screen` and renders the matching\n * full-screen view. Each screen owns its own lifecycle (useEffect)\n * — App.tsx itself is a switch, nothing more.\n */\nexport function App() {\n const screen = useWizardStore((s) => s.screen);\n\n return (\n <Box flexDirection=\"column\" paddingX={1} paddingY={1}>\n {screen === \"welcome\" && <WelcomeScreen />}\n {screen === \"oauth\" && <OAuthScreen />}\n {screen === \"run\" && <RunScreen />}\n {screen === \"done\" && <DoneScreen />}\n {screen === \"error\" && <ErrorScreen />}\n </Box>\n );\n}\n","import { createElement } from \"react\";\nimport { render } from \"ink\";\nimport yargs from \"yargs\";\nimport { hideBin } from \"yargs/helpers\";\nimport { App } from \"./ui/app\";\nimport { useWizardStore } from \"./ui/store\";\nimport {\n MIN_NODE_MAJOR,\n MIN_NODE_MINOR,\n WIZARD_VERSION,\n} from \"./constants\";\n\n/**\n * `npx @soloworks/smking-wizard` entry point.\n *\n * Phase 3 scope: parse args, check Node version, render the ink TUI.\n * Phase 4+ adds framework detection / installer dispatch / agent\n * loop — those plug into the TUI via the zustand store, not via new\n * command-line subcommands. The wizard is single-command on purpose\n * (PostHog model): one `npx @soloworks/smking-wizard` does everything.\n */\n\nfunction checkNodeVersion(): void {\n const [maj, min] = process.versions.node.split(\".\").map(Number);\n if (\n maj < MIN_NODE_MAJOR ||\n (maj === MIN_NODE_MAJOR && min < MIN_NODE_MINOR)\n ) {\n process.stderr.write(\n `smking wizard requires Node ${MIN_NODE_MAJOR}.${MIN_NODE_MINOR}+. You have ${process.versions.node}.\\n`,\n );\n process.exit(1);\n }\n}\n\nasync function main(): Promise<number> {\n checkNodeVersion();\n\n const argv = await yargs(hideBin(process.argv))\n .scriptName(\"smking-wizard\")\n .usage(\"$0 [options]\")\n .option(\"debug\", {\n type: \"boolean\",\n default: false,\n describe: \"Enable verbose debug output\",\n })\n .option(\"dry-run\", {\n type: \"boolean\",\n default: false,\n describe: \"Print intended changes without writing files (Phase 4+)\",\n })\n .option(\"allow-prod\", {\n type: \"boolean\",\n default: false,\n describe:\n \"Override the production-environment refusal (use only if you know why)\",\n })\n .option(\"allow-dirty\", {\n type: \"boolean\",\n default: false,\n describe:\n \"Override the git-status-must-be-clean check (use only if you know why)\",\n })\n .version(WIZARD_VERSION)\n .help()\n .strict()\n .parse();\n\n if (argv.debug) {\n process.env.SMKING_WIZARD_DEBUG = \"1\";\n }\n\n // Seed CLI flags into the store before the TUI mounts so guards\n // running on the welcome screen see the right overrides.\n useWizardStore.getState().setCliFlags({\n allowProd: !!argv[\"allow-prod\"],\n allowDirty: !!argv[\"allow-dirty\"],\n dryRun: !!argv[\"dry-run\"],\n debug: !!argv.debug,\n });\n\n // Render ink TUI. `waitUntilExit()` resolves when any screen\n // calls `useApp().exit()`. Throws if exit was called with an\n // Error — we convert to non-zero exit code below.\n //\n // Use `createElement` rather than JSX so this entry stays in `.ts`\n // (rolldown's `.ts` parser doesn't accept JSX without renaming or\n // adding an esbuild plugin — and bin.ts has no other JSX to justify\n // either workaround).\n const { waitUntilExit } = render(createElement(App));\n\n try {\n await waitUntilExit();\n return 0;\n } catch (err) {\n process.stderr.write(\n `${err instanceof Error ? err.message : String(err)}\\n`,\n );\n return 1;\n }\n}\n\nmain().then(\n (code) => process.exit(code),\n (err) => {\n process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exit(1);\n },\n);\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soloworks/smking-wizard",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "AI-powered install wizard for smking SDKs — npx @soloworks/smking-wizard auto-installs smking/laravel or @soloworks/smking-next with OAuth + a Claude agent that runs doctor and auto-fixes failures.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/sillyleo/smking/tree/main/packages/smking-wizard",
|
|
@@ -39,8 +39,7 @@
|
|
|
39
39
|
"react": "^19.0.0",
|
|
40
40
|
"yargs": "^17.7.2",
|
|
41
41
|
"zod": "^4.0.0",
|
|
42
|
-
"zustand": "^5.0.0"
|
|
43
|
-
"@smking/shared": "0.0.0"
|
|
42
|
+
"zustand": "^5.0.0"
|
|
44
43
|
},
|
|
45
44
|
"devDependencies": {
|
|
46
45
|
"@types/node": "^22.0.0",
|
|
@@ -48,7 +47,8 @@
|
|
|
48
47
|
"@types/yargs": "^17.0.0",
|
|
49
48
|
"tsdown": "^0.18.0",
|
|
50
49
|
"typescript": "^5",
|
|
51
|
-
"vitest": "^4.1.5"
|
|
50
|
+
"vitest": "^4.1.5",
|
|
51
|
+
"@smking/shared": "0.0.0"
|
|
52
52
|
},
|
|
53
53
|
"scripts": {
|
|
54
54
|
"build": "tsdown",
|