@ekanos/cli 0.3.6 → 0.4.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.
Files changed (50) hide show
  1. package/README.md +4 -0
  2. package/dist/commands/dev.js +5 -2
  3. package/dist/commands/dev.js.map +1 -1
  4. package/dist/commands/init.js +12 -1
  5. package/dist/commands/init.js.map +1 -1
  6. package/dist/commands/upgrade.js +58 -7
  7. package/dist/commands/upgrade.js.map +1 -1
  8. package/dist/compatibility.d.ts +4 -1
  9. package/dist/compatibility.js +4 -1
  10. package/dist/compatibility.js.map +1 -1
  11. package/dist/context.d.ts +2 -0
  12. package/dist/context.js +5 -5
  13. package/dist/context.js.map +1 -1
  14. package/dist/design-findings.d.ts +22 -0
  15. package/dist/design-findings.js +47 -0
  16. package/dist/design-findings.js.map +1 -0
  17. package/dist/docs-links.d.ts +20 -0
  18. package/dist/docs-links.js +33 -0
  19. package/dist/docs-links.js.map +1 -0
  20. package/dist/errors.d.ts +4 -1
  21. package/dist/errors.js +5 -1
  22. package/dist/errors.js.map +1 -1
  23. package/dist/findings-renderer.js +5 -0
  24. package/dist/findings-renderer.js.map +1 -1
  25. package/dist/harness-scaffold.d.ts +4 -0
  26. package/dist/harness-scaffold.js +18 -1
  27. package/dist/harness-scaffold.js.map +1 -1
  28. package/dist/index.js +3 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/pnpm-builds.d.ts +41 -6
  31. package/dist/pnpm-builds.js +169 -0
  32. package/dist/pnpm-builds.js.map +1 -1
  33. package/dist/schema-skew.js +2 -1
  34. package/dist/schema-skew.js.map +1 -1
  35. package/dist/validate-findings.js +2 -0
  36. package/dist/validate-findings.js.map +1 -1
  37. package/package.json +3 -2
  38. package/templates/AGENTS.md.tmpl +77 -9
  39. package/templates/CLAUDE.md.tmpl +5 -0
  40. package/templates/claude-skill.md.tmpl +7 -0
  41. package/templates/eslint.config.mjs.tmpl +62 -0
  42. package/templates/harness/app/[slug]/page.tsx.tmpl +1 -0
  43. package/templates/harness.config.entry.ts.tmpl +8 -0
  44. package/templates/mcp.json.tmpl +4 -0
  45. package/templates/package.json.tmpl +7 -4
  46. package/templates/skills/build-integration/SKILL.md +11 -0
  47. package/templates/skills/build-integration/references/widget-design.md +128 -32
  48. package/templates/skills/polish-widgets/SKILL.md +35 -13
  49. package/templates/skills/polish-widgets/references/patterns.md +90 -58
  50. package/templates/src/integration.ts.tmpl +50 -6
package/README.md CHANGED
@@ -4,6 +4,9 @@ The Ekanos partner toolchain. Scaffold, validate, and test an Ekanos integration
4
4
  against the published `@ekanos` packages — from any Node project, with no
5
5
  bundler and no dependency on `@ekanos/sdk` at runtime.
6
6
 
7
+ **Docs:** [sdk.ekanos.dev/cli/overview](https://sdk.ekanos.dev/cli/overview),
8
+ with every verb at [sdk.ekanos.dev/cli/commands](https://sdk.ekanos.dev/cli/commands).
9
+
7
10
  ```bash
8
11
  npx @ekanos/cli init --slug my-integration --yes # scaffolds into ./my-integration (or --dir . to scaffold in place)
9
12
  npx @ekanos/cli validate
@@ -135,6 +138,7 @@ imply an isolation that does not exist.
135
138
  app/harness-shell.tsx 'use client' — injects your registry
136
139
  app/page.tsx server component — the index
137
140
  app/[slug]/layout.tsx ─┐
141
+ app/[slug]/page.tsx │ (redirects /<slug> to /<slug>/widgets)
138
142
  app/[slug]/widgets/page.tsx │
139
143
  app/[slug]/widgets/[widgetId]/page.tsx
140
144
  app/[slug]/tile/page.tsx ├─ one-line re-exports of @ekanos/harness/routes
@@ -2,7 +2,7 @@ import { spawn } from 'node:child_process';
2
2
  import * as path from 'node:path';
3
3
  import { assertHarnessCompatible } from '../compatibility.js';
4
4
  import { preconditionError, usageError } from '../errors.js';
5
- import { HARNESS_DIR, HARNESS_PACKAGE, assertShellDirIsOurs, bootstrapHarnessConfig, ensureGitignoreEntry, missingDependencies, readInstalledHarness, scaffoldShell, } from '../harness-scaffold.js';
5
+ import { BUILD_CACHE_DIR, HARNESS_DIR, HARNESS_PACKAGE, assertShellDirIsOurs, bootstrapHarnessConfig, ensureGitignoreEntry, missingDependencies, readInstalledHarness, scaffoldShell, } from '../harness-scaffold.js';
6
6
  import { detectPackageManager, execArgs, formatCommand, installCommand, } from '../package-manager.js';
7
7
  import { createOutputTail, findIgnoredBuilds, ignoredBuildsHint, } from '../pnpm-builds.js';
8
8
  import { loadProject } from '../project.js';
@@ -99,6 +99,9 @@ export async function runDev(ctx, args) {
99
99
  ctx.log(`Regenerating ${HARNESS_DIR} — ${shell.staleReasons.join('; ')}. ` +
100
100
  `Nothing you own changed; ${config.configPath} is untouched.`);
101
101
  }
102
+ if (shell.buildCacheCleared) {
103
+ ctx.log(` removed ${HARNESS_DIR}/${BUILD_CACHE_DIR} (Next's cache from the previous shell)`);
104
+ }
102
105
  if (config.bootstrapped) {
103
106
  ctx.log(` created harness.config.ts (yours — edit it, we never will)`);
104
107
  }
@@ -119,7 +122,7 @@ export async function runDev(ctx, args) {
119
122
  const command = (exposed ? `${EXPOSED_ENV}=1 ` : '') +
120
123
  formatCommand(pm.bin, execArgs(pm, 'next', serverArgs));
121
124
  const url = `http://${displayHost(host)}:${port}`;
122
- const scaffold = Object.assign({ slug: loaded.primary.slug, integrations: loaded.integrations.map((i) => i.slug), projectDir: loaded.projectDir, harnessDir: shell.harnessDir, harnessVersion, previousVersion: shell.previousVersion, previousMarker: shell.previousMarker, stale: shell.stale, staleReasons: shell.staleReasons, sourceGlobs: (_b = loaded.project.sourceGlobs) !== null && _b !== void 0 ? _b : [], created: shell.created, updated: shell.updated, unchanged: shell.unchanged, configBootstrapped: config.bootstrapped, configPath: config.configPath, gitignoreUpdated }, (notice ? { toolchain: notice } : {}));
125
+ const scaffold = Object.assign({ slug: loaded.primary.slug, integrations: loaded.integrations.map((i) => i.slug), projectDir: loaded.projectDir, harnessDir: shell.harnessDir, harnessVersion, previousVersion: shell.previousVersion, previousMarker: shell.previousMarker, stale: shell.stale, staleReasons: shell.staleReasons, buildCacheCleared: shell.buildCacheCleared, sourceGlobs: (_b = loaded.project.sourceGlobs) !== null && _b !== void 0 ? _b : [], created: shell.created, updated: shell.updated, unchanged: shell.unchanged, configBootstrapped: config.bootstrapped, configPath: config.configPath, gitignoreUpdated }, (notice ? { toolchain: notice } : {}));
123
126
  if (exposed) {
124
127
  ctx.warn(`WARNING: --host ${host} exposes the harness beyond this machine. It ` +
125
128
  'has no authentication, and it renders your fixtures and — in live ' +
@@ -1 +1 @@
1
- {"version":3,"file":"dev.js","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from 'node:child_process';\nimport * as path from 'node:path';\n\nimport { assertHarnessCompatible } from '../compatibility';\nimport type { CliContext } from '../context';\nimport { preconditionError, usageError } from '../errors';\nimport {\n HARNESS_DIR,\n HARNESS_PACKAGE,\n assertShellDirIsOurs,\n bootstrapHarnessConfig,\n ensureGitignoreEntry,\n missingDependencies,\n readInstalledHarness,\n scaffoldShell,\n} from '../harness-scaffold';\nimport {\n detectPackageManager,\n execArgs,\n formatCommand,\n installCommand,\n} from '../package-manager';\nimport {\n createOutputTail,\n findIgnoredBuilds,\n ignoredBuildsHint,\n} from '../pnpm-builds';\nimport { loadProject } from '../project';\nimport { displayPath } from '../ui';\nimport { announceUpdate, checkForUpdate } from '../update-notice';\n\nexport interface DevArgs {\n /** Project directory (contains ekanos.json). Defaults to cwd. */\n dir: string;\n /** Port for the dev server. */\n port?: string;\n /**\n * Interface to bind. Defaults to loopback — see `resolveHost`. Anything\n * else is an explicit, warned-about decision to expose the harness.\n *\n * NOTE: unrelated to the Ekanos HOST the update notice checks against —\n * this is the local bind address for `next dev`, never an Ekanos origin.\n */\n host?: string;\n /** Rewrite every generated file even when its bytes already match. */\n force: boolean;\n /**\n * Explicit `--start` / `--no-start`. Undefined means \"decide from the\n * output mode\": a JSON caller is an agent or a pipe and must not be handed\n * a process that never exits.\n */\n start?: boolean;\n env?: Record<string, string | undefined>;\n}\n\nconst DEFAULT_PORT = 3100;\n\n/**\n * Loopback, NOT Next's default.\n *\n * `next dev` binds 0.0.0.0 unless told otherwise, so the harness would be\n * reachable by anyone on the same network — a coffee-shop Wi-Fi, a shared\n * office VLAN — while we printed \"http://localhost:3100\" and implied\n * otherwise. It renders a partner's integration with their fixtures and, in\n * live mode, whatever their vendor API returns; there is no auth in front of\n * any of it. Binding loopback by default costs a partner nothing and makes\n * exposure a thing they ask for.\n */\nconst DEFAULT_HOST = '127.0.0.1';\n\n/** Addresses that are only reachable from this machine. */\nconst LOOPBACK_HOSTS = new Set(['127.0.0.1', '::1', 'localhost']);\n\n/**\n * Set for the dev server when `--host` binds beyond loopback. The shell's\n * API routes (`@ekanos/harness/api`) refuse non-loopback `Host` headers\n * unless this is `1`: they hold the partner's local OAuth credentials and\n * run handlers on request, so exposing them has to be a decision — this one,\n * made after the warning below — rather than a side effect of the bind.\n */\nconst EXPOSED_ENV = 'EKANOS_HARNESS_EXPOSED';\n\n/** Where the harness generates, and reads, local dev credentials. */\nconst LOCAL_SECRETS_PATH = path.join('.ekanos', 'local-secrets.json');\n\n/**\n * `dev` — scaffold the local harness shell into the partner's project, then\n * run it with their own Next.\n *\n * The shape of this verb follows from one rule: we must be able to ship a\n * harness bugfix as an `@ekanos/harness` version bump that a partner picks up\n * with `pnpm up`, with zero edits to any file they own. So everything under\n * `.ekanos/` is generated, gitignored and rewritten freely, `harness.config.ts`\n * at the project root is written once and never touched again, and all actual\n * harness behaviour lives in the installed package.\n *\n * Two idempotency properties matter and are both tested: a second run produces\n * byte-identical files and rewrites nothing (so Next's watcher stays quiet),\n * and `.gitignore` never grows a duplicate line.\n */\nexport async function runDev(ctx: CliContext, args: DevArgs): Promise<number> {\n const loaded = loadProject(args.dir);\n const port = parsePort(args.port);\n const host = parseHost(args.host);\n const exposed = !LOOPBACK_HOSTS.has(host);\n const pm = detectPackageManager(loaded.projectDir);\n\n const notice = await checkForUpdate({ env: args.env ?? process.env });\n announceUpdate(ctx, notice);\n\n // Fail before writing anything: the shell declares no dependencies of its\n // own, so a missing one would otherwise surface as an opaque Next\n // resolution error minutes later.\n const missing = missingDependencies(loaded.projectDir);\n if (missing.length > 0) {\n throw preconditionError(\n `${missing.length} package${missing.length === 1 ? '' : 's'} the ` +\n `harness needs ${missing.length === 1 ? 'is' : 'are'} not installed ` +\n `in ${loaded.projectDir}: ${missing.join(', ')}.`,\n `Run \"${installCommand(pm, missing)}\" in ${loaded.projectDir}, then ` +\n 're-run \"ekanos dev\".',\n );\n }\n\n const harness = readInstalledHarness(loaded.projectDir);\n const harnessVersion = harness?.version;\n if (!harnessVersion) {\n throw preconditionError(\n `${HARNESS_PACKAGE} is installed but declares no version.`,\n `Reinstall it with \"${installCommand(pm, [HARNESS_PACKAGE])}\" in ` +\n `${loaded.projectDir}, then re-run \"ekanos dev\".`,\n );\n }\n\n // The templates ship with the CLI and the code they import ships with the\n // harness, so a partner can upgrade one without the other. Name that\n // mismatch here rather than letting it surface later as a module-resolution\n // error inside a shell we cheerfully certified as current.\n assertHarnessCompatible({\n harnessVersion,\n harnessManifest: harness?.manifest,\n installHint: (spec) => installCommand(pm, [spec]),\n });\n\n // Validate the shell target BEFORE writing anything at all. Bootstrapping\n // harness.config.ts first and only then discovering that .ekanos/harness is\n // a file or someone else's tree would leave the project half-scaffolded.\n assertShellDirIsOurs(path.join(loaded.projectDir, HARNESS_DIR), args.force);\n\n const config = bootstrapHarnessConfig(loaded.projectDir, loaded.integrations);\n const shell = scaffoldShell({\n projectDir: loaded.projectDir,\n slug: loaded.primary.slug,\n harnessVersion,\n force: args.force,\n sourceGlobs: loaded.project.sourceGlobs,\n });\n const gitignoreUpdated = ensureGitignoreEntry(loaded.projectDir);\n\n if (shell.stale) {\n ctx.log(\n `Regenerating ${HARNESS_DIR} — ${shell.staleReasons.join('; ')}. ` +\n `Nothing you own changed; ${config.configPath} is untouched.`,\n );\n }\n\n if (config.bootstrapped) {\n ctx.log(` created harness.config.ts (yours — edit it, we never will)`);\n }\n for (const file of shell.created) ctx.log(` created ${HARNESS_DIR}/${file}`);\n for (const file of shell.updated) ctx.log(` updated ${HARNESS_DIR}/${file}`);\n if (gitignoreUpdated) ctx.log(' updated .gitignore (.ekanos/)');\n ctx.log(\n ` live-mode OAuth: the harness generates ${LOCAL_SECRETS_PATH} on first ` +\n 'use — fill it in and follow \"Local OAuth\" on the Activation surface.',\n );\n\n // `--hostname` is passed ALWAYS, never left to Next's default, so the URL\n // we print is the URL that is actually served.\n const serverArgs = ['dev', '--port', String(port), '--hostname', host];\n const serverEnv: Record<string, string> = exposed\n ? { [EXPOSED_ENV]: '1' }\n : {};\n const command =\n (exposed ? `${EXPOSED_ENV}=1 ` : '') +\n formatCommand(pm.bin, execArgs(pm, 'next', serverArgs));\n const url = `http://${displayHost(host)}:${port}`;\n\n const scaffold = {\n slug: loaded.primary.slug,\n integrations: loaded.integrations.map((i) => i.slug),\n projectDir: loaded.projectDir,\n harnessDir: shell.harnessDir,\n harnessVersion,\n previousVersion: shell.previousVersion,\n previousMarker: shell.previousMarker,\n stale: shell.stale,\n staleReasons: shell.staleReasons,\n sourceGlobs: loaded.project.sourceGlobs ?? [],\n created: shell.created,\n updated: shell.updated,\n unchanged: shell.unchanged,\n configBootstrapped: config.bootstrapped,\n configPath: config.configPath,\n gitignoreUpdated,\n ...(notice ? { toolchain: notice } : {}),\n };\n\n if (exposed) {\n ctx.warn(\n `WARNING: --host ${host} exposes the harness beyond this machine. It ` +\n 'has no authentication, and it renders your fixtures and — in live ' +\n 'mode — real responses from your vendor API. Its API routes hold the ' +\n `client secret from ${LOCAL_SECRETS_PATH}, the tokens onTokens stored, ` +\n 'and run your handlers on request; they refuse non-loopback callers ' +\n `unless ${EXPOSED_ENV}=1, which this sets. Anyone who can reach ` +\n `${url} can read and run all of it. Omit --host to bind ${DEFAULT_HOST}.`,\n );\n }\n\n // A JSON caller is an agent or a pipe. Handing it a dev server that never\n // exits would hang its tool call forever and it would never see the\n // envelope, so JSON mode scaffolds and reports the command instead of\n // running it. `--start` overrides; `--no-start` opts out in a terminal.\n const shouldStart = args.start ?? !ctx.jsonMode;\n if (!shouldStart) {\n return ctx.succeed(\n {\n ...scaffold,\n server: { started: false, command, url, port, host, exposed },\n },\n ctx.ui.result({\n title: 'Harness ready',\n rows: [\n ['Directory', displayPath(shell.harnessDir, process.cwd())],\n ['URL', url],\n ['Start', command],\n ],\n }),\n );\n }\n\n ctx.log(ctx.ui.dim(`Starting the harness on ${url} (${command})…`));\n const tail = createOutputTail();\n const exitCode = await spawnServer({\n ctx,\n tail,\n bin: pm.bin,\n argv: execArgs(pm, 'next', serverArgs),\n cwd: shell.harnessDir,\n env: serverEnv,\n });\n\n const data = {\n ...scaffold,\n server: { started: true, command, url, port, host, exposed, exitCode },\n };\n\n if (exitCode === 0) {\n return ctx.succeed(\n data,\n ctx.ui.result({ title: 'Harness stopped', rows: [['URL', url]] }),\n );\n }\n\n const ignoredBuilds = findIgnoredBuilds(tail.text());\n\n return ctx.failWith({\n code: 'dev_server_failed',\n message: `\"${command}\" exited with code ${exitCode}.`,\n hint:\n ignoredBuilds.length > 0\n ? ignoredBuildsHint(ignoredBuilds)\n : 'Read the Next output on stderr. If it cannot resolve @ekanos/harness ' +\n `or next, reinstall dependencies in ${loaded.projectDir}; if a route ` +\n `fails to compile, delete ${shell.harnessDir} and re-run \"ekanos dev\".`,\n // Transparent passthrough of Next's own exit code.\n exitCode,\n data,\n });\n}\n\nfunction spawnServer(params: {\n ctx: CliContext;\n tail: ReturnType<typeof createOutputTail>;\n bin: string;\n argv: string[];\n cwd: string;\n env: Record<string, string>;\n}): Promise<number> {\n return new Promise<number>((resolve, reject) => {\n const child = spawn(params.bin, params.argv, {\n cwd: params.cwd,\n env: { ...process.env, ...params.env },\n // stdin inherited so Next's own key handling works; stdout/stderr piped\n // so we can route them and keep OUR stdout reserved for the envelope.\n stdio: ['inherit', 'pipe', 'pipe'],\n });\n\n child.stdout?.on('data', (chunk: Buffer) => {\n params.tail.push(chunk);\n (params.ctx.jsonMode ? process.stderr : process.stdout).write(chunk);\n });\n child.stderr?.on('data', (chunk: Buffer) => {\n params.tail.push(chunk);\n process.stderr.write(chunk);\n });\n\n child.on('error', (error) => reject(error));\n child.on('close', (code) => resolve(code ?? 1));\n });\n}\n\n/**\n * The hostname to show a human. `127.0.0.1` is spelled `localhost` because\n * that is what people type; a wildcard bind is spelled `localhost` too, but\n * only after `exposed` has already been warned about — printing `0.0.0.0` as\n * a URL is not useful.\n */\nfunction displayHost(host: string): string {\n if (host === '127.0.0.1' || host === '0.0.0.0' || host === '::') {\n return 'localhost';\n }\n if (host === '::1') return '[::1]';\n return host;\n}\n\nfunction parseHost(raw: string | undefined): string {\n if (raw === undefined) return DEFAULT_HOST;\n\n const host = raw.trim();\n if (host.length === 0) {\n throw usageError(\n 'Invalid --host \"\".',\n `Pass an interface to bind, e.g. \"--host 0.0.0.0\" to expose the ` +\n `harness on your network, or omit it to bind ${DEFAULT_HOST}.`,\n );\n }\n return host;\n}\n\nfunction parsePort(raw: string | undefined): number {\n if (raw === undefined) return DEFAULT_PORT;\n\n const port = Number(raw);\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n throw usageError(\n `Invalid --port \"${raw}\".`,\n 'Pass an integer between 1 and 65535, e.g. \"--port 3100\".',\n );\n }\n return port;\n}\n"]}
1
+ {"version":3,"file":"dev.js","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from 'node:child_process';\nimport * as path from 'node:path';\n\nimport { assertHarnessCompatible } from '../compatibility';\nimport type { CliContext } from '../context';\nimport { preconditionError, usageError } from '../errors';\nimport {\n BUILD_CACHE_DIR,\n HARNESS_DIR,\n HARNESS_PACKAGE,\n assertShellDirIsOurs,\n bootstrapHarnessConfig,\n ensureGitignoreEntry,\n missingDependencies,\n readInstalledHarness,\n scaffoldShell,\n} from '../harness-scaffold';\nimport {\n detectPackageManager,\n execArgs,\n formatCommand,\n installCommand,\n} from '../package-manager';\nimport {\n createOutputTail,\n findIgnoredBuilds,\n ignoredBuildsHint,\n} from '../pnpm-builds';\nimport { loadProject } from '../project';\nimport { displayPath } from '../ui';\nimport { announceUpdate, checkForUpdate } from '../update-notice';\n\nexport interface DevArgs {\n /** Project directory (contains ekanos.json). Defaults to cwd. */\n dir: string;\n /** Port for the dev server. */\n port?: string;\n /**\n * Interface to bind. Defaults to loopback — see `resolveHost`. Anything\n * else is an explicit, warned-about decision to expose the harness.\n *\n * NOTE: unrelated to the Ekanos HOST the update notice checks against —\n * this is the local bind address for `next dev`, never an Ekanos origin.\n */\n host?: string;\n /** Rewrite every generated file even when its bytes already match. */\n force: boolean;\n /**\n * Explicit `--start` / `--no-start`. Undefined means \"decide from the\n * output mode\": a JSON caller is an agent or a pipe and must not be handed\n * a process that never exits.\n */\n start?: boolean;\n env?: Record<string, string | undefined>;\n}\n\nconst DEFAULT_PORT = 3100;\n\n/**\n * Loopback, NOT Next's default.\n *\n * `next dev` binds 0.0.0.0 unless told otherwise, so the harness would be\n * reachable by anyone on the same network — a coffee-shop Wi-Fi, a shared\n * office VLAN — while we printed \"http://localhost:3100\" and implied\n * otherwise. It renders a partner's integration with their fixtures and, in\n * live mode, whatever their vendor API returns; there is no auth in front of\n * any of it. Binding loopback by default costs a partner nothing and makes\n * exposure a thing they ask for.\n */\nconst DEFAULT_HOST = '127.0.0.1';\n\n/** Addresses that are only reachable from this machine. */\nconst LOOPBACK_HOSTS = new Set(['127.0.0.1', '::1', 'localhost']);\n\n/**\n * Set for the dev server when `--host` binds beyond loopback. The shell's\n * API routes (`@ekanos/harness/api`) refuse non-loopback `Host` headers\n * unless this is `1`: they hold the partner's local OAuth credentials and\n * run handlers on request, so exposing them has to be a decision — this one,\n * made after the warning below — rather than a side effect of the bind.\n */\nconst EXPOSED_ENV = 'EKANOS_HARNESS_EXPOSED';\n\n/** Where the harness generates, and reads, local dev credentials. */\nconst LOCAL_SECRETS_PATH = path.join('.ekanos', 'local-secrets.json');\n\n/**\n * `dev` — scaffold the local harness shell into the partner's project, then\n * run it with their own Next.\n *\n * The shape of this verb follows from one rule: we must be able to ship a\n * harness bugfix as an `@ekanos/harness` version bump that a partner picks up\n * with `pnpm up`, with zero edits to any file they own. So everything under\n * `.ekanos/` is generated, gitignored and rewritten freely, `harness.config.ts`\n * at the project root is written once and never touched again, and all actual\n * harness behaviour lives in the installed package.\n *\n * Two idempotency properties matter and are both tested: a second run produces\n * byte-identical files and rewrites nothing (so Next's watcher stays quiet),\n * and `.gitignore` never grows a duplicate line.\n */\nexport async function runDev(ctx: CliContext, args: DevArgs): Promise<number> {\n const loaded = loadProject(args.dir);\n const port = parsePort(args.port);\n const host = parseHost(args.host);\n const exposed = !LOOPBACK_HOSTS.has(host);\n const pm = detectPackageManager(loaded.projectDir);\n\n const notice = await checkForUpdate({ env: args.env ?? process.env });\n announceUpdate(ctx, notice);\n\n // Fail before writing anything: the shell declares no dependencies of its\n // own, so a missing one would otherwise surface as an opaque Next\n // resolution error minutes later.\n const missing = missingDependencies(loaded.projectDir);\n if (missing.length > 0) {\n throw preconditionError(\n `${missing.length} package${missing.length === 1 ? '' : 's'} the ` +\n `harness needs ${missing.length === 1 ? 'is' : 'are'} not installed ` +\n `in ${loaded.projectDir}: ${missing.join(', ')}.`,\n `Run \"${installCommand(pm, missing)}\" in ${loaded.projectDir}, then ` +\n 're-run \"ekanos dev\".',\n );\n }\n\n const harness = readInstalledHarness(loaded.projectDir);\n const harnessVersion = harness?.version;\n if (!harnessVersion) {\n throw preconditionError(\n `${HARNESS_PACKAGE} is installed but declares no version.`,\n `Reinstall it with \"${installCommand(pm, [HARNESS_PACKAGE])}\" in ` +\n `${loaded.projectDir}, then re-run \"ekanos dev\".`,\n );\n }\n\n // The templates ship with the CLI and the code they import ships with the\n // harness, so a partner can upgrade one without the other. Name that\n // mismatch here rather than letting it surface later as a module-resolution\n // error inside a shell we cheerfully certified as current.\n assertHarnessCompatible({\n harnessVersion,\n harnessManifest: harness?.manifest,\n installHint: (spec) => installCommand(pm, [spec]),\n });\n\n // Validate the shell target BEFORE writing anything at all. Bootstrapping\n // harness.config.ts first and only then discovering that .ekanos/harness is\n // a file or someone else's tree would leave the project half-scaffolded.\n assertShellDirIsOurs(path.join(loaded.projectDir, HARNESS_DIR), args.force);\n\n const config = bootstrapHarnessConfig(loaded.projectDir, loaded.integrations);\n const shell = scaffoldShell({\n projectDir: loaded.projectDir,\n slug: loaded.primary.slug,\n harnessVersion,\n force: args.force,\n sourceGlobs: loaded.project.sourceGlobs,\n });\n const gitignoreUpdated = ensureGitignoreEntry(loaded.projectDir);\n\n if (shell.stale) {\n ctx.log(\n `Regenerating ${HARNESS_DIR} — ${shell.staleReasons.join('; ')}. ` +\n `Nothing you own changed; ${config.configPath} is untouched.`,\n );\n }\n if (shell.buildCacheCleared) {\n ctx.log(\n ` removed ${HARNESS_DIR}/${BUILD_CACHE_DIR} (Next's cache from the previous shell)`,\n );\n }\n\n if (config.bootstrapped) {\n ctx.log(` created harness.config.ts (yours — edit it, we never will)`);\n }\n for (const file of shell.created) ctx.log(` created ${HARNESS_DIR}/${file}`);\n for (const file of shell.updated) ctx.log(` updated ${HARNESS_DIR}/${file}`);\n if (gitignoreUpdated) ctx.log(' updated .gitignore (.ekanos/)');\n ctx.log(\n ` live-mode OAuth: the harness generates ${LOCAL_SECRETS_PATH} on first ` +\n 'use — fill it in and follow \"Local OAuth\" on the Activation surface.',\n );\n\n // `--hostname` is passed ALWAYS, never left to Next's default, so the URL\n // we print is the URL that is actually served.\n const serverArgs = ['dev', '--port', String(port), '--hostname', host];\n const serverEnv: Record<string, string> = exposed\n ? { [EXPOSED_ENV]: '1' }\n : {};\n const command =\n (exposed ? `${EXPOSED_ENV}=1 ` : '') +\n formatCommand(pm.bin, execArgs(pm, 'next', serverArgs));\n const url = `http://${displayHost(host)}:${port}`;\n\n const scaffold = {\n slug: loaded.primary.slug,\n integrations: loaded.integrations.map((i) => i.slug),\n projectDir: loaded.projectDir,\n harnessDir: shell.harnessDir,\n harnessVersion,\n previousVersion: shell.previousVersion,\n previousMarker: shell.previousMarker,\n stale: shell.stale,\n staleReasons: shell.staleReasons,\n buildCacheCleared: shell.buildCacheCleared,\n sourceGlobs: loaded.project.sourceGlobs ?? [],\n created: shell.created,\n updated: shell.updated,\n unchanged: shell.unchanged,\n configBootstrapped: config.bootstrapped,\n configPath: config.configPath,\n gitignoreUpdated,\n ...(notice ? { toolchain: notice } : {}),\n };\n\n if (exposed) {\n ctx.warn(\n `WARNING: --host ${host} exposes the harness beyond this machine. It ` +\n 'has no authentication, and it renders your fixtures and — in live ' +\n 'mode — real responses from your vendor API. Its API routes hold the ' +\n `client secret from ${LOCAL_SECRETS_PATH}, the tokens onTokens stored, ` +\n 'and run your handlers on request; they refuse non-loopback callers ' +\n `unless ${EXPOSED_ENV}=1, which this sets. Anyone who can reach ` +\n `${url} can read and run all of it. Omit --host to bind ${DEFAULT_HOST}.`,\n );\n }\n\n // A JSON caller is an agent or a pipe. Handing it a dev server that never\n // exits would hang its tool call forever and it would never see the\n // envelope, so JSON mode scaffolds and reports the command instead of\n // running it. `--start` overrides; `--no-start` opts out in a terminal.\n const shouldStart = args.start ?? !ctx.jsonMode;\n if (!shouldStart) {\n return ctx.succeed(\n {\n ...scaffold,\n server: { started: false, command, url, port, host, exposed },\n },\n ctx.ui.result({\n title: 'Harness ready',\n rows: [\n ['Directory', displayPath(shell.harnessDir, process.cwd())],\n ['URL', url],\n ['Start', command],\n ],\n }),\n );\n }\n\n ctx.log(ctx.ui.dim(`Starting the harness on ${url} (${command})…`));\n const tail = createOutputTail();\n const exitCode = await spawnServer({\n ctx,\n tail,\n bin: pm.bin,\n argv: execArgs(pm, 'next', serverArgs),\n cwd: shell.harnessDir,\n env: serverEnv,\n });\n\n const data = {\n ...scaffold,\n server: { started: true, command, url, port, host, exposed, exitCode },\n };\n\n if (exitCode === 0) {\n return ctx.succeed(\n data,\n ctx.ui.result({ title: 'Harness stopped', rows: [['URL', url]] }),\n );\n }\n\n const ignoredBuilds = findIgnoredBuilds(tail.text());\n\n return ctx.failWith({\n code: 'dev_server_failed',\n message: `\"${command}\" exited with code ${exitCode}.`,\n hint:\n ignoredBuilds.length > 0\n ? ignoredBuildsHint(ignoredBuilds)\n : 'Read the Next output on stderr. If it cannot resolve @ekanos/harness ' +\n `or next, reinstall dependencies in ${loaded.projectDir}; if a route ` +\n `fails to compile, delete ${shell.harnessDir} and re-run \"ekanos dev\".`,\n // Transparent passthrough of Next's own exit code.\n exitCode,\n data,\n });\n}\n\nfunction spawnServer(params: {\n ctx: CliContext;\n tail: ReturnType<typeof createOutputTail>;\n bin: string;\n argv: string[];\n cwd: string;\n env: Record<string, string>;\n}): Promise<number> {\n return new Promise<number>((resolve, reject) => {\n const child = spawn(params.bin, params.argv, {\n cwd: params.cwd,\n env: { ...process.env, ...params.env },\n // stdin inherited so Next's own key handling works; stdout/stderr piped\n // so we can route them and keep OUR stdout reserved for the envelope.\n stdio: ['inherit', 'pipe', 'pipe'],\n });\n\n child.stdout?.on('data', (chunk: Buffer) => {\n params.tail.push(chunk);\n (params.ctx.jsonMode ? process.stderr : process.stdout).write(chunk);\n });\n child.stderr?.on('data', (chunk: Buffer) => {\n params.tail.push(chunk);\n process.stderr.write(chunk);\n });\n\n child.on('error', (error) => reject(error));\n child.on('close', (code) => resolve(code ?? 1));\n });\n}\n\n/**\n * The hostname to show a human. `127.0.0.1` is spelled `localhost` because\n * that is what people type; a wildcard bind is spelled `localhost` too, but\n * only after `exposed` has already been warned about — printing `0.0.0.0` as\n * a URL is not useful.\n */\nfunction displayHost(host: string): string {\n if (host === '127.0.0.1' || host === '0.0.0.0' || host === '::') {\n return 'localhost';\n }\n if (host === '::1') return '[::1]';\n return host;\n}\n\nfunction parseHost(raw: string | undefined): string {\n if (raw === undefined) return DEFAULT_HOST;\n\n const host = raw.trim();\n if (host.length === 0) {\n throw usageError(\n 'Invalid --host \"\".',\n `Pass an interface to bind, e.g. \"--host 0.0.0.0\" to expose the ` +\n `harness on your network, or omit it to bind ${DEFAULT_HOST}.`,\n );\n }\n return host;\n}\n\nfunction parsePort(raw: string | undefined): number {\n if (raw === undefined) return DEFAULT_PORT;\n\n const port = Number(raw);\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n throw usageError(\n `Invalid --port \"${raw}\".`,\n 'Pass an integer between 1 and 65535, e.g. \"--port 3100\".',\n );\n }\n return port;\n}\n"]}
@@ -2,6 +2,7 @@ import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
3
  import { printBanner } from '../banner.js';
4
4
  import { copyTreeVerbatim } from '../copy-tree.js';
5
+ import { SDK_DOCS_URL, docsPage } from '../docs-links.js';
5
6
  import { invalidStateError, usageError } from '../errors.js';
6
7
  import { mergeManifest, serializeManifest, } from '../merge-manifest.js';
7
8
  import { docEntryProblem, serializeProject, } from '../project.js';
@@ -65,6 +66,14 @@ const FILES = [
65
66
  // and, worse, no type error in the scaffold ever surfaces.
66
67
  { template: 'tsconfig.json.tmpl', dest: 'tsconfig.json' },
67
68
  { template: 'vitest.config.ts.tmpl', dest: 'vitest.config.ts' },
69
+ // The lint config, wiring up BOTH SDK presets: the capability control at
70
+ // `error` and the design rules at `warn`. INIT-ONLY, deliberately —
71
+ // `upgrade` cannot deliver it, because the `eslint` and
72
+ // `@typescript-eslint/parser` devDependencies and the `lint` script it
73
+ // needs come from the package.json merge, which only `init` runs. An
74
+ // existing project gets the preset by hand: add the two devDependencies,
75
+ // a `"lint": "eslint ."` script, and copy this template's contents.
76
+ { template: 'eslint.config.mjs.tmpl', dest: 'eslint.config.mjs' },
68
77
  // Wired via package.json's "prepare" script — see setup-hooks.mjs.tmpl's
69
78
  // own header for what it does and why it tolerates a non-git directory.
70
79
  { template: 'scripts/setup-hooks.mjs.tmpl', dest: 'scripts/setup-hooks.mjs' },
@@ -209,11 +218,12 @@ export async function runInit(ctx, args) {
209
218
  kept: manifest.kept,
210
219
  notes: manifest.notes,
211
220
  },
221
+ docs: SDK_DOCS_URL,
212
222
  };
213
223
  printBanner(ctx);
214
224
  if (useWizard) {
215
225
  const relDir = path.relative(args.cwd, targetDir) || '.';
216
- wizard.outro(`Next steps:\n\n cd ${relDir}\n npm install\n ekanos dev`);
226
+ wizard.outro(`Next steps:\n\n cd ${relDir}\n npm install\n ekanos dev\n\nDocs: ${docsPage('quickstart')}`);
217
227
  }
218
228
  // The wizard's note + outro already said everything; a trailing line would
219
229
  // only repeat them.
@@ -414,6 +424,7 @@ function humanResult(ui, params) {
414
424
  'ekanos dev',
415
425
  ],
416
426
  ],
427
+ ['Docs', docsPage('quickstart')],
417
428
  ],
418
429
  });
419
430
  }
@@ -1 +1 @@
1
- {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { printBanner } from '../banner';\nimport type { CliContext } from '../context';\nimport { copyTreeVerbatim } from '../copy-tree';\nimport { invalidStateError, usageError } from '../errors';\nimport type { ExitCode } from '../exit-codes';\nimport {\n type Manifest,\n type ManifestNote,\n mergeManifest,\n serializeManifest,\n} from '../merge-manifest';\nimport {\n type EkanosProject,\n docEntryProblem,\n serializeProject,\n} from '../project';\nimport { classifyPath, readFileNoFollow, writeFileNoFollow } from '../safe-fs';\nimport { readTemplate, templatesDir, toDisplayName } from '../templates';\nimport { type Ui, displayPath, pluralize } from '../ui';\nimport { canWizard, wizard } from '../wizard';\n\n/**\n * A file init did not write, and what the partner should do about it.\n *\n * Every ERROR path in this CLI carries an imperative hint; a skip did not, and\n * that asymmetry was the bug — `skipped package.json (already exists)` hid the\n * loss of eleven dependencies and the project's module type.\n */\nexport interface SkippedFile {\n file: string;\n reason: string;\n hint: string;\n}\n\nexport interface InitArgs {\n slug?: string;\n /**\n * Explicit target directory. When omitted, init scaffolds into `./<slug>`\n * under `cwd` — never into `cwd` itself. A real partner ran `init` in a\n * populated directory and got six files sprayed into it; `--dir .` is the\n * explicit opt-in for that.\n */\n dir?: string;\n cwd: string;\n force: boolean;\n /** Accepted for non-interactive ergonomics; init never prompts regardless. */\n yes: boolean;\n /**\n * Repeatable `--docs` entries — http(s) URLs or project-relative paths to\n * the target API's documentation, written into the scaffolded ekanos.json\n * as `docs`. Absent flag means the field is simply omitted; init never\n * prompts for it.\n */\n docs?: string[];\n /**\n * Test-only override for the clack wizard gate (`canWizard` in\n * `wizard.ts`) — never set from real CLI wiring. See `RunOptions` in\n * `index.ts`.\n */\n stdinIsTTY?: boolean;\n}\n\nconst SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\n/**\n * The files init writes: template basename → destination path.\n *\n * `package.json` is handled separately (see `writeManifest`) because it is the\n * one file a partner plausibly already has — `npm init -y` before `ekanos\n * init` is the ordinary way to start — and skipping it silently costs them\n * every dependency and script.\n */\n/**\n * The agent-context files. The SDK's day-1 users are coding agents, so the\n * scaffold ships the authoring contract alongside the code: AGENTS.md is the\n * contract, CLAUDE.md points at it, and the Claude Code skill teaches the\n * iterate-validate-publish loop. Version-matched to the CLI because they live\n * in its templates.\n *\n * Exported so `ekanos upgrade` can regenerate exactly this set from the\n * NEWLY installed CLI's templates after a version bump — a stale AGENTS.md\n * teaches an agent yesterday's contract, which is as harmful as a stale\n * binary. One list, so `init` and `upgrade` can never disagree on what \"the\n * agent-context files\" even are.\n */\nexport const AGENT_CONTEXT_FILES: { template: string; dest: string }[] = [\n { template: 'AGENTS.md.tmpl', dest: 'AGENTS.md' },\n { template: 'CLAUDE.md.tmpl', dest: 'CLAUDE.md' },\n { template: 'claude-skill.md.tmpl', dest: '.claude/skills/ekanos/SKILL.md' },\n];\n\n/**\n * The docs-grounding artifacts, exported for the same reason as\n * `AGENT_CONTEXT_FILES`: `upgrade` must create these when they are missing\n * (an older project upgrading into a toolchain whose AGENTS.md and skills\n * now reference them), and one list keeps `init` and `upgrade` from ever\n * disagreeing on what they are. Unlike the agent-context files these are\n * NEVER regenerated — `docs/api-notes.md` is agent/partner-authored after\n * the scaffold and `.mcp.json` may carry local additions — only created\n * when absent.\n */\nexport const DOCS_GROUNDING_FILES: { template: string; dest: string }[] = [\n // The agent-authored snapshot of the target API — see the template itself\n // and AGENTS.md's \"API documentation\" section for the convention.\n { template: 'docs/api-notes.md.tmpl', dest: 'docs/api-notes.md' },\n // Per-project MCP config so Claude Code picks up Context7 (the docs\n // fallback in the resolution ladder) without any global setup.\n { template: 'mcp.json.tmpl', dest: '.mcp.json' },\n];\n\nconst FILES: { template: string; dest: string }[] = [\n { template: 'ekanos.json.tmpl', dest: 'ekanos.json' },\n { template: 'gitignore.tmpl', dest: '.gitignore' },\n // pnpm's build-script approval for esbuild (vitest's dependency). Without\n // it a fresh scaffold's `ekanos test` fails on the first run under pnpm —\n // see the template's own header.\n { template: 'pnpm-workspace.yaml.tmpl', dest: 'pnpm-workspace.yaml' },\n // Without this, the `typecheck` script below prints tsc's help and exits 1 —\n // and, worse, no type error in the scaffold ever surfaces.\n { template: 'tsconfig.json.tmpl', dest: 'tsconfig.json' },\n { template: 'vitest.config.ts.tmpl', dest: 'vitest.config.ts' },\n // Wired via package.json's \"prepare\" script — see setup-hooks.mjs.tmpl's\n // own header for what it does and why it tolerates a non-git directory.\n { template: 'scripts/setup-hooks.mjs.tmpl', dest: 'scripts/setup-hooks.mjs' },\n { template: 'src/integration.ts.tmpl', dest: 'src/integration.ts' },\n {\n template: 'src/integration.test.ts.tmpl',\n dest: 'src/integration.test.ts',\n },\n ...DOCS_GROUNDING_FILES,\n ...AGENT_CONTEXT_FILES,\n];\n\n/**\n * `init` — instantiate a project from the bundled template: ekanos.json, a\n * working minimal defineIntegration, a test, a vitest config (with the\n * required SDK inline block), a .gitignore, and a package.json with real\n * published deps. Idempotent: an existing file is left untouched (reported as\n * skipped) unless --force is passed. Never prompts — a missing --slug fails\n * with a usage error naming the flag.\n *\n * In human mode at a real TTY (`canWizard`), the scaffold runs behind a\n * clack `intro`/spinner/`outro` flow instead of the flat `ctx.log` lines\n * below — see `wizard.ts`. This is a restyle, not a new interaction: the\n * data written to disk and the JSON/human-non-TTY envelope are identical\n * either way, and nothing here ever waits on a prompt.\n */\nexport async function runInit(\n ctx: CliContext,\n args: InitArgs,\n): Promise<ExitCode> {\n const slug = args.slug;\n if (!slug) {\n throw usageError(\n 'init requires a slug.',\n 'Pass it explicitly: \"ekanos init --slug <your-integration> --yes\". ' +\n 'The CLI never prompts, so the slug must be on the command line.',\n );\n }\n if (!SLUG_PATTERN.test(slug)) {\n throw usageError(\n `Invalid slug \"${slug}\".`,\n 'Use kebab-case: lowercase [a-z0-9] segments joined by single hyphens, ' +\n 'e.g. \"acme-crm\".',\n );\n }\n\n // Same rule the ekanos.json schema enforces, applied at the flag so a bad\n // entry fails HERE (usage, exit 2) instead of scaffolding a project whose\n // very first `ekanos validate` cannot even load its config.\n const docs = args.docs ?? [];\n for (const entry of docs) {\n const problem = docEntryProblem(entry);\n if (problem) {\n throw usageError(\n `Invalid --docs value: ${problem}`,\n 'Pass --docs an http(s) URL or a project-relative path, e.g. ' +\n '\"--docs https://developer.example.com/openapi.json\" or ' +\n '\"--docs docs/api.md\". Repeat the flag for multiple entries.',\n );\n }\n }\n\n const useWizard = canWizard(ctx, { stdinIsTTY: args.stdinIsTTY });\n if (useWizard) wizard.intro('ekanos init');\n\n // Default to `./<slug>`, not the cwd: scaffolding six files into whatever\n // directory the partner happened to be sitting in is how a real project got\n // polluted. `--dir .` remains the explicit way to scaffold in place.\n const targetDir =\n args.dir !== undefined\n ? path.resolve(args.cwd, args.dir)\n : path.resolve(args.cwd, slug);\n const vars = { SLUG: slug, DISPLAY_NAME: toDisplayName(slug) };\n\n const created: string[] = [];\n const skipped: SkippedFile[] = [];\n\n // The project root is the guard for every write below: creating it raw is\n // fine (it is the boundary, not inside it), but everything under it goes\n // through safe-fs so a planted symlink refuses loudly instead of teleporting\n // a write outside the project. See safe-fs.ts for the threat model. The one\n // case the guard cannot see is the boundary ITSELF being a symlink — the\n // containment math is lexical against `targetDir`, so a planted link there\n // would relocate every \"contained\" write wholesale. Refuse it. (A symlinked\n // ANCESTOR — macOS's /tmp, a linked home — stays fine: only the leaf is\n // checked.)\n let targetStats: fs.Stats | null = null;\n try {\n targetStats = fs.lstatSync(targetDir);\n } catch {\n targetStats = null; // does not exist yet — ours to create\n }\n if (targetStats?.isSymbolicLink()) {\n throw invalidStateError(\n `The target directory ${targetDir} is a symlink, so the scaffold's ` +\n 'write-containment guarantees cannot hold there.',\n 'Scaffold into the real directory (pass its resolved path to --dir), ' +\n 'or remove the symlink first.',\n );\n }\n fs.mkdirSync(targetDir, { recursive: true });\n\n const spinner = useWizard ? wizard.spinner() : null;\n spinner?.start(`Scaffolding \"${slug}\"…`);\n\n for (const { template, dest } of FILES) {\n const destPath = path.join(targetDir, dest);\n const kind = classifyPath(destPath);\n // A symlink is never \"already exists\": skipping would keep the plant, and\n // writing through it is the attack — writeFileNoFollow refuses it.\n if (kind !== 'missing' && kind !== 'symlink' && !args.force) {\n skipped.push({\n file: dest,\n reason: 'it already exists',\n hint:\n `Compare it against the scaffold and merge anything you are ` +\n `missing, or re-run with --force to overwrite ${dest}.`,\n });\n continue;\n }\n\n writeFileNoFollow(\n targetDir,\n destPath,\n dest === 'ekanos.json'\n ? renderEkanosConfig(vars, docs)\n : readTemplate(template, vars),\n );\n created.push(dest);\n }\n\n const skills = copyBundledSkillTrees(targetDir, args.force);\n created.push(...skills.created);\n skipped.push(...skills.skipped);\n\n const manifest = writeManifest(targetDir, vars, args.force);\n if (manifest.kind === 'created') created.push('package.json');\n\n spinner?.stop(`Scaffolded \"${slug}\"`);\n\n if (useWizard) {\n // Per-file \"created\" detail stays collapsed into the count above — the\n // note surfaces only what needs a partner's attention: skips (they hid\n // real data loss before, see `SkippedFile`'s doc comment) and manifest\n // notes.\n wizard.note(wizardSummaryDetail({ created, skipped, manifest }), 'Summary');\n } else {\n for (const file of created) ctx.log(` created ${file}`);\n if (manifest.kind === 'merged') {\n ctx.log(\n ` merged package.json (added ${manifest.added.length} field${\n manifest.added.length === 1 ? '' : 's'\n })`,\n );\n }\n if (manifest.kind === 'unchanged') {\n ctx.log(' package.json already had everything the scaffold adds');\n }\n for (const skip of skipped) {\n ctx.log(` skipped ${skip.file} — ${skip.reason}. ${skip.hint}`);\n }\n for (const note of manifest.notes) {\n ctx.warn(` ${note.message} ${note.hint}`);\n }\n }\n\n const data = {\n slug,\n dir: targetDir,\n created,\n skipped,\n manifest: {\n action: manifest.kind,\n added: manifest.added,\n kept: manifest.kept,\n notes: manifest.notes,\n },\n };\n\n printBanner(ctx);\n\n if (useWizard) {\n const relDir = path.relative(args.cwd, targetDir) || '.';\n wizard.outro(`Next steps:\\n\\n cd ${relDir}\\n npm install\\n ekanos dev`);\n }\n\n // The wizard's note + outro already said everything; a trailing line would\n // only repeat them.\n return ctx.succeed(\n data,\n useWizard\n ? ''\n : humanResult(ctx.ui, {\n slug,\n dir: displayPath(targetDir, args.cwd),\n created: created.length,\n skipped: skipped.length,\n manifestAction: manifest.kind,\n manifestAdded: manifest.added.length,\n noteCount: manifest.notes.length,\n }),\n );\n}\n\n/**\n * The clack `note()` body for a successful wizard-mode scaffold — collapses\n * \"created\" down to a count (the flat per-file lines this replaces are the\n * `else` branch above) and surfaces only what needs a partner's attention:\n * skips and manifest notes, each with its full hint.\n */\nfunction wizardSummaryDetail(params: {\n created: string[];\n skipped: SkippedFile[];\n manifest: ManifestOutcome;\n}): string {\n const { created, skipped, manifest } = params;\n const lines: string[] = [\n `${created.length} file${created.length === 1 ? '' : 's'} written`,\n ];\n\n if (manifest.kind === 'merged') {\n lines.push(\n `package.json merged (+${manifest.added.length} field${\n manifest.added.length === 1 ? '' : 's'\n })`,\n );\n } else if (manifest.kind === 'unchanged') {\n lines.push('package.json already had everything the scaffold adds');\n }\n\n if (skipped.length > 0) {\n lines.push('', 'Skipped (already existed):');\n\n // Identical advice that only differs by file name is said once, not\n // once per file — twenty near-identical sentences hide the files.\n const groups = new Map<string, string[]>();\n for (const skip of skipped) {\n const key = skip.hint.split(skip.file).join('{file}');\n groups.set(key, [...(groups.get(key) ?? []), skip.file]);\n }\n\n for (const [hint, files] of groups) {\n if (files.length === 1) {\n lines.push(` ${files[0]} — ${hint.replace('{file}', files[0]!)}`);\n continue;\n }\n lines.push(...files.map((file) => ` ${file}`));\n lines.push(` ${hint.split('{file}').join('each file')}`);\n }\n }\n\n for (const note of manifest.notes) {\n lines.push('', `⚠ ${note.message} ${note.hint}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * The scaffolded ekanos.json: the template, plus the collected `--docs`\n * entries when there are any. Parse-then-serialize (rather than string\n * surgery) so the written file is exactly what `loadProject` will read back.\n */\nfunction renderEkanosConfig(\n vars: Readonly<Record<string, string>>,\n docs: string[],\n): string {\n const rendered = readTemplate('ekanos.json.tmpl', vars);\n if (docs.length === 0) return rendered;\n const project = JSON.parse(rendered) as EkanosProject;\n return serializeProject({ ...project, docs });\n}\n\n/**\n * Copy every bundled skill tree (`templates/skills/<name>/`) into the\n * scaffolded project at `.claude/skills/<name>/`, verbatim — see\n * `copyTreeVerbatim` for why these are bytes, never rendered templates. The\n * walk is generic: a skill directory added to the published templates ships\n * with no code change here. A CLI build with no `templates/skills/` at all\n * (today's) scaffolds nothing and reports nothing.\n *\n * `skillsRoot` is injectable for tests; real callers take the default.\n */\nexport function copyBundledSkillTrees(\n targetDir: string,\n force: boolean,\n skillsRoot = path.join(templatesDir(), 'skills'),\n onRefusal: 'throw' | 'collect' = 'throw',\n): { created: string[]; skipped: SkippedFile[]; refused: string[] } {\n if (!fs.existsSync(skillsRoot))\n return { created: [], skipped: [], refused: [] };\n\n const created: string[] = [];\n const skipped: SkippedFile[] = [];\n const refused: string[] = [];\n\n const skillDirs = fs\n .readdirSync(skillsRoot, { withFileTypes: true })\n .filter((entry) => entry.isDirectory())\n .map((entry) => entry.name)\n .sort();\n\n for (const name of skillDirs) {\n const destBase = path.join('.claude', 'skills', name);\n const result = copyTreeVerbatim(\n path.join(skillsRoot, name),\n path.join(targetDir, destBase),\n { force, root: targetDir, onRefusal },\n );\n created.push(...result.created.map((rel) => path.join(destBase, rel)));\n refused.push(...result.refused.map((rel) => path.join(destBase, rel)));\n skipped.push(\n ...result.skipped.map((rel) => {\n const file = path.join(destBase, rel);\n return {\n file,\n reason: 'it already exists',\n hint:\n `Compare it against the bundled skill and merge anything you ` +\n `are missing, or re-run with --force to overwrite ${file}.`,\n };\n }),\n );\n }\n\n return { created, skipped, refused };\n}\n\ntype ManifestOutcome = {\n kind: 'created' | 'merged' | 'unchanged';\n added: string[];\n kept: string[];\n notes: ManifestNote[];\n};\n\n/**\n * Write or MERGE package.json.\n *\n * A partner who ran `npm init -y` first had a manifest with none of the\n * scaffold's dependencies and `\"type\": \"commonjs\"` under ESM sources, and the\n * old behaviour reported that as one word: \"skipped\". Merging is additive — a\n * version or script they already set is theirs and is never touched — so the\n * ordinary `npm init -y` path now lands somewhere that installs and runs.\n */\nfunction writeManifest(\n targetDir: string,\n vars: Readonly<Record<string, string>>,\n force: boolean,\n): ManifestOutcome {\n const destPath = path.join(targetDir, 'package.json');\n const rendered = readTemplate('package.json.tmpl', vars);\n\n // readFileNoFollow refuses a symlinked package.json outright, so the write\n // below can never follow one either; null means genuinely absent.\n const existingRaw = readFileNoFollow(targetDir, destPath);\n\n if (existingRaw === null || force) {\n writeFileNoFollow(targetDir, destPath, rendered);\n return { kind: 'created', added: [], kept: [], notes: [] };\n }\n\n const template = JSON.parse(rendered) as Manifest;\n\n let existing: Manifest;\n try {\n existing = JSON.parse(existingRaw) as Manifest;\n } catch (error) {\n // Unparseable: do not guess, and do not clobber. Name the fix.\n return {\n kind: 'unchanged',\n added: [],\n kept: [],\n notes: [\n {\n field: '(root)',\n message: `package.json is not valid JSON: ${\n error instanceof Error ? error.message : String(error)\n }`,\n hint:\n 'Fix the JSON syntax, then re-run \"ekanos init\" to have the ' +\n \"scaffold's dependencies and scripts merged in.\",\n },\n ],\n };\n }\n\n const result = mergeManifest(existing, template);\n if (!result.changed) {\n return {\n kind: 'unchanged',\n added: [],\n kept: result.kept,\n notes: result.notes,\n };\n }\n\n writeFileNoFollow(targetDir, destPath, serializeManifest(result.manifest));\n return {\n kind: 'merged',\n added: result.added,\n kept: result.kept,\n notes: result.notes,\n };\n}\n\nfunction humanResult(\n ui: Ui,\n params: {\n slug: string;\n dir: string;\n created: number;\n skipped: number;\n manifestAction: ManifestOutcome['kind'];\n manifestAdded: number;\n noteCount: number;\n },\n): string {\n const files = [\n ...(params.created > 0 ? [`${params.created} written`] : []),\n ...(params.manifestAction === 'merged'\n ? [`${pluralize(params.manifestAdded, 'package.json field')} merged`]\n : []),\n ...(params.skipped > 0 ? [`${params.skipped} skipped`] : []),\n ];\n const attention = params.skipped > 0 || params.noteCount > 0;\n\n if (files.length === 0 && params.noteCount === 0) {\n return ui.result({\n title: `${params.slug} is already scaffolded`,\n rows: [['Directory', params.dir]],\n });\n }\n\n return ui.result({\n title: `Scaffolded ${params.slug}`,\n tone: attention ? 'warn' : 'ok',\n rows: [\n ['Directory', params.dir],\n ...(files.length > 0 ? [['Files', files.join(' · ')] as const] : []),\n ...(params.noteCount > 0\n ? [\n [\n 'Attention',\n `${pluralize(params.noteCount, 'note')} above`,\n ] as const,\n ]\n : []),\n [\n 'Next',\n [\n ...(params.dir === '.' ? [] : [`cd ${params.dir}`]),\n 'npm install',\n 'ekanos dev',\n ],\n ],\n ],\n });\n}\n"]}
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE1D,OAAO,EAGL,aAAa,EACb,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAEL,eAAe,EACf,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/E,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,EAAW,WAAW,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AA2C9C,MAAM,YAAY,GAAG,4BAA4B,CAAC;AAElD;;;;;;;GAOG;AACH;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAyC;IACvE,EAAE,QAAQ,EAAE,gBAAgB,EAAE,IAAI,EAAE,WAAW,EAAE;IACjD,EAAE,QAAQ,EAAE,gBAAgB,EAAE,IAAI,EAAE,WAAW,EAAE;IACjD,EAAE,QAAQ,EAAE,sBAAsB,EAAE,IAAI,EAAE,gCAAgC,EAAE;CAC7E,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAyC;IACxE,0EAA0E;IAC1E,kEAAkE;IAClE,EAAE,QAAQ,EAAE,wBAAwB,EAAE,IAAI,EAAE,mBAAmB,EAAE;IACjE,oEAAoE;IACpE,+DAA+D;IAC/D,EAAE,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,WAAW,EAAE;CACjD,CAAC;AAEF,MAAM,KAAK,GAAyC;IAClD,EAAE,QAAQ,EAAE,kBAAkB,EAAE,IAAI,EAAE,aAAa,EAAE;IACrD,EAAE,QAAQ,EAAE,gBAAgB,EAAE,IAAI,EAAE,YAAY,EAAE;IAClD,0EAA0E;IAC1E,0EAA0E;IAC1E,iCAAiC;IACjC,EAAE,QAAQ,EAAE,0BAA0B,EAAE,IAAI,EAAE,qBAAqB,EAAE;IACrE,6EAA6E;IAC7E,2DAA2D;IAC3D,EAAE,QAAQ,EAAE,oBAAoB,EAAE,IAAI,EAAE,eAAe,EAAE;IACzD,EAAE,QAAQ,EAAE,uBAAuB,EAAE,IAAI,EAAE,kBAAkB,EAAE;IAC/D,yEAAyE;IACzE,oEAAoE;IACpE,wDAAwD;IACxD,uEAAuE;IACvE,qEAAqE;IACrE,yEAAyE;IACzE,oEAAoE;IACpE,EAAE,QAAQ,EAAE,wBAAwB,EAAE,IAAI,EAAE,mBAAmB,EAAE;IACjE,yEAAyE;IACzE,wEAAwE;IACxE,EAAE,QAAQ,EAAE,8BAA8B,EAAE,IAAI,EAAE,yBAAyB,EAAE;IAC7E,EAAE,QAAQ,EAAE,yBAAyB,EAAE,IAAI,EAAE,oBAAoB,EAAE;IACnE;QACE,QAAQ,EAAE,8BAA8B;QACxC,IAAI,EAAE,yBAAyB;KAChC;IACD,GAAG,oBAAoB;IACvB,GAAG,mBAAmB;CACvB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,GAAe,EACf,IAAc;;IAEd,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACvB,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,UAAU,CACd,uBAAuB,EACvB,qEAAqE;YACnE,iEAAiE,CACpE,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,MAAM,UAAU,CACd,iBAAiB,IAAI,IAAI,EACzB,wEAAwE;YACtE,kBAAkB,CACrB,CAAC;IACJ,CAAC;IAED,0EAA0E;IAC1E,0EAA0E;IAC1E,4DAA4D;IAC5D,MAAM,IAAI,SAAG,IAAI,CAAC,IAAI,mCAAI,EAAE,CAAC;IAC7B,KAAK,MAAM,KAAK,IAAI,IAAI,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;QACvC,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,UAAU,CACd,yBAAyB,OAAO,EAAE,EAClC,8DAA8D;gBAC5D,yDAAyD;gBACzD,6DAA6D,CAChE,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;IAClE,IAAI,SAAS;QAAE,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IAE3C,0EAA0E;IAC1E,4EAA4E;IAC5E,qEAAqE;IACrE,MAAM,SAAS,GACb,IAAI,CAAC,GAAG,KAAK,SAAS;QACpB,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC;QAClC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACnC,MAAM,IAAI,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC;IAE/D,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAkB,EAAE,CAAC;IAElC,0EAA0E;IAC1E,yEAAyE;IACzE,6EAA6E;IAC7E,4EAA4E;IAC5E,yEAAyE;IACzE,2EAA2E;IAC3E,4EAA4E;IAC5E,wEAAwE;IACxE,YAAY;IACZ,IAAI,WAAW,GAAoB,IAAI,CAAC;IACxC,IAAI,CAAC;QACH,WAAW,GAAG,EAAE,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IACxC,CAAC;eAAO,CAAC;QACP,WAAW,GAAG,IAAI,CAAC,CAAC,sCAAsC;IAC5D,CAAC;IACD,IAAI,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,cAAc,EAAE,EAAE,CAAC;QAClC,MAAM,iBAAiB,CACrB,wBAAwB,SAAS,mCAAmC;YAClE,iDAAiD,EACnD,sEAAsE;YACpE,8BAA8B,CACjC,CAAC;IACJ,CAAC;IACD,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE7C,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACpD,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,KAAK,CAAC,gBAAgB,IAAI,IAAI,CAAC,CAAC;IAEzC,KAAK,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,KAAK,EAAE,CAAC;QACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QAC5C,MAAM,IAAI,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;QACpC,0EAA0E;QAC1E,mEAAmE;QACnE,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YAC5D,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,IAAI;gBACV,MAAM,EAAE,mBAAmB;gBAC3B,IAAI,EACF,6DAA6D;oBAC7D,gDAAgD,IAAI,GAAG;aAC1D,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QAED,iBAAiB,CACf,SAAS,EACT,QAAQ,EACR,IAAI,KAAK,aAAa;YACpB,CAAC,CAAC,kBAAkB,CAAC,IAAI,EAAE,IAAI,CAAC;YAChC,CAAC,CAAC,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,CACjC,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrB,CAAC;IAED,MAAM,MAAM,GAAG,qBAAqB,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5D,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAChC,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAEhC,MAAM,QAAQ,GAAG,aAAa,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5D,IAAI,QAAQ,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAE9D,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,IAAI,CAAC,eAAe,IAAI,GAAG,CAAC,CAAC;IAEtC,IAAI,SAAS,EAAE,CAAC;QACd,uEAAuE;QACvE,uEAAuE;QACvE,uEAAuE;QACvE,SAAS;QACT,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS,CAAC,CAAC;IAC9E,CAAC;SAAM,CAAC;QACN,KAAK,MAAM,IAAI,IAAI,OAAO;YAAE,GAAG,CAAC,GAAG,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC;QACzD,IAAI,QAAQ,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC/B,GAAG,CAAC,GAAG,CACL,gCAAgC,QAAQ,CAAC,KAAK,CAAC,MAAM,SACnD,QAAQ,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GACrC,GAAG,CACJ,CAAC;QACJ,CAAC;QACD,IAAI,QAAQ,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YAClC,GAAG,CAAC,GAAG,CAAC,yDAAyD,CAAC,CAAC;QACrE,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;YAC3B,GAAG,CAAC,GAAG,CAAC,aAAa,IAAI,CAAC,IAAI,MAAM,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACnE,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;YAClC,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;IAED,MAAM,IAAI,GAAG;QACX,IAAI;QACJ,GAAG,EAAE,SAAS;QACd,OAAO;QACP,OAAO;QACP,QAAQ,EAAE;YACR,MAAM,EAAE,QAAQ,CAAC,IAAI;YACrB,KAAK,EAAE,QAAQ,CAAC,KAAK;YACrB,IAAI,EAAE,QAAQ,CAAC,IAAI;YACnB,KAAK,EAAE,QAAQ,CAAC,KAAK;SACtB;QACD,IAAI,EAAE,YAAY;KACnB,CAAC;IAEF,WAAW,CAAC,GAAG,CAAC,CAAC;IAEjB,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,IAAI,GAAG,CAAC;QACzD,MAAM,CAAC,KAAK,CACV,uBAAuB,MAAM,0CAA0C,QAAQ,CAAC,YAAY,CAAC,EAAE,CAChG,CAAC;IACJ,CAAC;IAED,2EAA2E;IAC3E,oBAAoB;IACpB,OAAO,GAAG,CAAC,OAAO,CAChB,IAAI,EACJ,SAAS;QACP,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,EAAE;YAClB,IAAI;YACJ,GAAG,EAAE,WAAW,CAAC,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC;YACrC,OAAO,EAAE,OAAO,CAAC,MAAM;YACvB,OAAO,EAAE,OAAO,CAAC,MAAM;YACvB,cAAc,EAAE,QAAQ,CAAC,IAAI;YAC7B,aAAa,EAAE,QAAQ,CAAC,KAAK,CAAC,MAAM;YACpC,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,MAAM;SACjC,CAAC,CACP,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAS,mBAAmB,CAAC,MAI5B;;IACC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC;IAC9C,MAAM,KAAK,GAAa;QACtB,GAAG,OAAO,CAAC,MAAM,QAAQ,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,UAAU;KACnE,CAAC;IAEF,IAAI,QAAQ,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC/B,KAAK,CAAC,IAAI,CACR,yBAAyB,QAAQ,CAAC,KAAK,CAAC,MAAM,SAC5C,QAAQ,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GACrC,GAAG,CACJ,CAAC;IACJ,CAAC;SAAM,IAAI,QAAQ,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;QACzC,KAAK,CAAC,IAAI,CAAC,uDAAuD,CAAC,CAAC;IACtE,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,4BAA4B,CAAC,CAAC;QAE7C,oEAAoE;QACpE,kEAAkE;QAClE,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAC;QAC3C,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;YAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACtD,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,OAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,mCAAI,EAAE,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAC3D,CAAC;QAED,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;YACnC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACvB,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAE,CAAC,EAAE,CAAC,CAAC;gBACnE,SAAS;YACX,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC;YAChD,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;QAClC,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IACnD,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;;GAIG;AACH,SAAS,kBAAkB,CACzB,IAAsC,EACtC,IAAc;IAEd,MAAM,QAAQ,GAAG,YAAY,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAC;IACxD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,QAAQ,CAAC;IACvC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAkB,CAAC;IACtD,OAAO,gBAAgB,iCAAM,OAAO,KAAE,IAAI,IAAG,CAAC;AAChD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB,CACnC,SAAiB,EACjB,KAAc,EACd,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,QAAQ,CAAC,EAChD,SAAS,GAAwB,OAAO;IAExC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC;QAC5B,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAEnD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAkB,EAAE,CAAC;IAClC,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,MAAM,SAAS,GAAG,EAAE;SACjB,WAAW,CAAC,UAAU,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;SAChD,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;SACtC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC;SAC1B,IAAI,EAAE,CAAC;IAEV,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;QACtD,MAAM,MAAM,GAAG,gBAAgB,CAC7B,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,EAC3B,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,EAC9B,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,CACtC,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;QACvE,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;QACvE,OAAO,CAAC,IAAI,CACV,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;YAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;YACtC,OAAO;gBACL,IAAI;gBACJ,MAAM,EAAE,mBAAmB;gBAC3B,IAAI,EACF,8DAA8D;oBAC9D,oDAAoD,IAAI,GAAG;aAC9D,CAAC;QACJ,CAAC,CAAC,CACH,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AACvC,CAAC;AASD;;;;;;;;GAQG;AACH,SAAS,aAAa,CACpB,SAAiB,EACjB,IAAsC,EACtC,KAAc;IAEd,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;IACtD,MAAM,QAAQ,GAAG,YAAY,CAAC,mBAAmB,EAAE,IAAI,CAAC,CAAC;IAEzD,2EAA2E;IAC3E,kEAAkE;IAClE,MAAM,WAAW,GAAG,gBAAgB,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IAE1D,IAAI,WAAW,KAAK,IAAI,IAAI,KAAK,EAAE,CAAC;QAClC,iBAAiB,CAAC,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACjD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IAC7D,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAa,CAAC;IAElD,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAa,CAAC;IACjD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,+DAA+D;QAC/D,OAAO;YACL,IAAI,EAAE,WAAW;YACjB,KAAK,EAAE,EAAE;YACT,IAAI,EAAE,EAAE;YACR,KAAK,EAAE;gBACL;oBACE,KAAK,EAAE,QAAQ;oBACf,OAAO,EAAE,mCACP,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CACvD,EAAE;oBACF,IAAI,EACF,6DAA6D;wBAC7D,gDAAgD;iBACnD;aACF;SACF,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACjD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO;YACL,IAAI,EAAE,WAAW;YACjB,KAAK,EAAE,EAAE;YACT,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,KAAK,EAAE,MAAM,CAAC,KAAK;SACpB,CAAC;IACJ,CAAC;IAED,iBAAiB,CAAC,SAAS,EAAE,QAAQ,EAAE,iBAAiB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC3E,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,KAAK,EAAE,MAAM,CAAC,KAAK;KACpB,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAClB,EAAM,EACN,MAQC;IAED,MAAM,KAAK,GAAG;QACZ,GAAG,CAAC,MAAM,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5D,GAAG,CAAC,MAAM,CAAC,cAAc,KAAK,QAAQ;YACpC,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,MAAM,CAAC,aAAa,EAAE,oBAAoB,CAAC,SAAS,CAAC;YACrE,CAAC,CAAC,EAAE,CAAC;QACP,GAAG,CAAC,MAAM,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;KAC7D,CAAC;IACF,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,GAAG,CAAC,IAAI,MAAM,CAAC,SAAS,GAAG,CAAC,CAAC;IAE7D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,SAAS,KAAK,CAAC,EAAE,CAAC;QACjD,OAAO,EAAE,CAAC,MAAM,CAAC;YACf,KAAK,EAAE,GAAG,MAAM,CAAC,IAAI,wBAAwB;YAC7C,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;SAClC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,EAAE,CAAC,MAAM,CAAC;QACf,KAAK,EAAE,cAAc,MAAM,CAAC,IAAI,EAAE;QAClC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI;QAC/B,IAAI,EAAE;YACJ,CAAC,WAAW,EAAE,MAAM,CAAC,GAAG,CAAC;YACzB,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACpE,GAAG,CAAC,MAAM,CAAC,SAAS,GAAG,CAAC;gBACtB,CAAC,CAAC;oBACE;wBACE,WAAW;wBACX,GAAG,SAAS,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,QAAQ;qBACtC;iBACX;gBACH,CAAC,CAAC,EAAE,CAAC;YACP;gBACE,MAAM;gBACN;oBACE,GAAG,CAAC,MAAM,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;oBACnD,aAAa;oBACb,YAAY;iBACb;aACF;YACD,CAAC,MAAM,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC;SACjC;KACF,CAAC,CAAC;AACL,CAAC","sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { printBanner } from '../banner';\nimport type { CliContext } from '../context';\nimport { copyTreeVerbatim } from '../copy-tree';\nimport { SDK_DOCS_URL, docsPage } from '../docs-links';\nimport { invalidStateError, usageError } from '../errors';\nimport type { ExitCode } from '../exit-codes';\nimport {\n type Manifest,\n type ManifestNote,\n mergeManifest,\n serializeManifest,\n} from '../merge-manifest';\nimport {\n type EkanosProject,\n docEntryProblem,\n serializeProject,\n} from '../project';\nimport { classifyPath, readFileNoFollow, writeFileNoFollow } from '../safe-fs';\nimport { readTemplate, templatesDir, toDisplayName } from '../templates';\nimport { type Ui, displayPath, pluralize } from '../ui';\nimport { canWizard, wizard } from '../wizard';\n\n/**\n * A file init did not write, and what the partner should do about it.\n *\n * Every ERROR path in this CLI carries an imperative hint; a skip did not, and\n * that asymmetry was the bug — `skipped package.json (already exists)` hid the\n * loss of eleven dependencies and the project's module type.\n */\nexport interface SkippedFile {\n file: string;\n reason: string;\n hint: string;\n}\n\nexport interface InitArgs {\n slug?: string;\n /**\n * Explicit target directory. When omitted, init scaffolds into `./<slug>`\n * under `cwd` — never into `cwd` itself. A real partner ran `init` in a\n * populated directory and got six files sprayed into it; `--dir .` is the\n * explicit opt-in for that.\n */\n dir?: string;\n cwd: string;\n force: boolean;\n /** Accepted for non-interactive ergonomics; init never prompts regardless. */\n yes: boolean;\n /**\n * Repeatable `--docs` entries — http(s) URLs or project-relative paths to\n * the target API's documentation, written into the scaffolded ekanos.json\n * as `docs`. Absent flag means the field is simply omitted; init never\n * prompts for it.\n */\n docs?: string[];\n /**\n * Test-only override for the clack wizard gate (`canWizard` in\n * `wizard.ts`) — never set from real CLI wiring. See `RunOptions` in\n * `index.ts`.\n */\n stdinIsTTY?: boolean;\n}\n\nconst SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\n/**\n * The files init writes: template basename → destination path.\n *\n * `package.json` is handled separately (see `writeManifest`) because it is the\n * one file a partner plausibly already has — `npm init -y` before `ekanos\n * init` is the ordinary way to start — and skipping it silently costs them\n * every dependency and script.\n */\n/**\n * The agent-context files. The SDK's day-1 users are coding agents, so the\n * scaffold ships the authoring contract alongside the code: AGENTS.md is the\n * contract, CLAUDE.md points at it, and the Claude Code skill teaches the\n * iterate-validate-publish loop. Version-matched to the CLI because they live\n * in its templates.\n *\n * Exported so `ekanos upgrade` can regenerate exactly this set from the\n * NEWLY installed CLI's templates after a version bump — a stale AGENTS.md\n * teaches an agent yesterday's contract, which is as harmful as a stale\n * binary. One list, so `init` and `upgrade` can never disagree on what \"the\n * agent-context files\" even are.\n */\nexport const AGENT_CONTEXT_FILES: { template: string; dest: string }[] = [\n { template: 'AGENTS.md.tmpl', dest: 'AGENTS.md' },\n { template: 'CLAUDE.md.tmpl', dest: 'CLAUDE.md' },\n { template: 'claude-skill.md.tmpl', dest: '.claude/skills/ekanos/SKILL.md' },\n];\n\n/**\n * The docs-grounding artifacts, exported for the same reason as\n * `AGENT_CONTEXT_FILES`: `upgrade` must create these when they are missing\n * (an older project upgrading into a toolchain whose AGENTS.md and skills\n * now reference them), and one list keeps `init` and `upgrade` from ever\n * disagreeing on what they are. Unlike the agent-context files these are\n * NEVER regenerated — `docs/api-notes.md` is agent/partner-authored after\n * the scaffold and `.mcp.json` may carry local additions — only created\n * when absent.\n */\nexport const DOCS_GROUNDING_FILES: { template: string; dest: string }[] = [\n // The agent-authored snapshot of the target API — see the template itself\n // and AGENTS.md's \"API documentation\" section for the convention.\n { template: 'docs/api-notes.md.tmpl', dest: 'docs/api-notes.md' },\n // Per-project MCP config so Claude Code picks up Context7 (the docs\n // fallback in the resolution ladder) without any global setup.\n { template: 'mcp.json.tmpl', dest: '.mcp.json' },\n];\n\nconst FILES: { template: string; dest: string }[] = [\n { template: 'ekanos.json.tmpl', dest: 'ekanos.json' },\n { template: 'gitignore.tmpl', dest: '.gitignore' },\n // pnpm's build-script approval for esbuild (vitest's dependency). Without\n // it a fresh scaffold's `ekanos test` fails on the first run under pnpm —\n // see the template's own header.\n { template: 'pnpm-workspace.yaml.tmpl', dest: 'pnpm-workspace.yaml' },\n // Without this, the `typecheck` script below prints tsc's help and exits 1 —\n // and, worse, no type error in the scaffold ever surfaces.\n { template: 'tsconfig.json.tmpl', dest: 'tsconfig.json' },\n { template: 'vitest.config.ts.tmpl', dest: 'vitest.config.ts' },\n // The lint config, wiring up BOTH SDK presets: the capability control at\n // `error` and the design rules at `warn`. INIT-ONLY, deliberately —\n // `upgrade` cannot deliver it, because the `eslint` and\n // `@typescript-eslint/parser` devDependencies and the `lint` script it\n // needs come from the package.json merge, which only `init` runs. An\n // existing project gets the preset by hand: add the two devDependencies,\n // a `\"lint\": \"eslint .\"` script, and copy this template's contents.\n { template: 'eslint.config.mjs.tmpl', dest: 'eslint.config.mjs' },\n // Wired via package.json's \"prepare\" script — see setup-hooks.mjs.tmpl's\n // own header for what it does and why it tolerates a non-git directory.\n { template: 'scripts/setup-hooks.mjs.tmpl', dest: 'scripts/setup-hooks.mjs' },\n { template: 'src/integration.ts.tmpl', dest: 'src/integration.ts' },\n {\n template: 'src/integration.test.ts.tmpl',\n dest: 'src/integration.test.ts',\n },\n ...DOCS_GROUNDING_FILES,\n ...AGENT_CONTEXT_FILES,\n];\n\n/**\n * `init` — instantiate a project from the bundled template: ekanos.json, a\n * working minimal defineIntegration, a test, a vitest config (with the\n * required SDK inline block), a .gitignore, and a package.json with real\n * published deps. Idempotent: an existing file is left untouched (reported as\n * skipped) unless --force is passed. Never prompts — a missing --slug fails\n * with a usage error naming the flag.\n *\n * In human mode at a real TTY (`canWizard`), the scaffold runs behind a\n * clack `intro`/spinner/`outro` flow instead of the flat `ctx.log` lines\n * below — see `wizard.ts`. This is a restyle, not a new interaction: the\n * data written to disk and the JSON/human-non-TTY envelope are identical\n * either way, and nothing here ever waits on a prompt.\n */\nexport async function runInit(\n ctx: CliContext,\n args: InitArgs,\n): Promise<ExitCode> {\n const slug = args.slug;\n if (!slug) {\n throw usageError(\n 'init requires a slug.',\n 'Pass it explicitly: \"ekanos init --slug <your-integration> --yes\". ' +\n 'The CLI never prompts, so the slug must be on the command line.',\n );\n }\n if (!SLUG_PATTERN.test(slug)) {\n throw usageError(\n `Invalid slug \"${slug}\".`,\n 'Use kebab-case: lowercase [a-z0-9] segments joined by single hyphens, ' +\n 'e.g. \"acme-crm\".',\n );\n }\n\n // Same rule the ekanos.json schema enforces, applied at the flag so a bad\n // entry fails HERE (usage, exit 2) instead of scaffolding a project whose\n // very first `ekanos validate` cannot even load its config.\n const docs = args.docs ?? [];\n for (const entry of docs) {\n const problem = docEntryProblem(entry);\n if (problem) {\n throw usageError(\n `Invalid --docs value: ${problem}`,\n 'Pass --docs an http(s) URL or a project-relative path, e.g. ' +\n '\"--docs https://developer.example.com/openapi.json\" or ' +\n '\"--docs docs/api.md\". Repeat the flag for multiple entries.',\n );\n }\n }\n\n const useWizard = canWizard(ctx, { stdinIsTTY: args.stdinIsTTY });\n if (useWizard) wizard.intro('ekanos init');\n\n // Default to `./<slug>`, not the cwd: scaffolding six files into whatever\n // directory the partner happened to be sitting in is how a real project got\n // polluted. `--dir .` remains the explicit way to scaffold in place.\n const targetDir =\n args.dir !== undefined\n ? path.resolve(args.cwd, args.dir)\n : path.resolve(args.cwd, slug);\n const vars = { SLUG: slug, DISPLAY_NAME: toDisplayName(slug) };\n\n const created: string[] = [];\n const skipped: SkippedFile[] = [];\n\n // The project root is the guard for every write below: creating it raw is\n // fine (it is the boundary, not inside it), but everything under it goes\n // through safe-fs so a planted symlink refuses loudly instead of teleporting\n // a write outside the project. See safe-fs.ts for the threat model. The one\n // case the guard cannot see is the boundary ITSELF being a symlink — the\n // containment math is lexical against `targetDir`, so a planted link there\n // would relocate every \"contained\" write wholesale. Refuse it. (A symlinked\n // ANCESTOR — macOS's /tmp, a linked home — stays fine: only the leaf is\n // checked.)\n let targetStats: fs.Stats | null = null;\n try {\n targetStats = fs.lstatSync(targetDir);\n } catch {\n targetStats = null; // does not exist yet — ours to create\n }\n if (targetStats?.isSymbolicLink()) {\n throw invalidStateError(\n `The target directory ${targetDir} is a symlink, so the scaffold's ` +\n 'write-containment guarantees cannot hold there.',\n 'Scaffold into the real directory (pass its resolved path to --dir), ' +\n 'or remove the symlink first.',\n );\n }\n fs.mkdirSync(targetDir, { recursive: true });\n\n const spinner = useWizard ? wizard.spinner() : null;\n spinner?.start(`Scaffolding \"${slug}\"…`);\n\n for (const { template, dest } of FILES) {\n const destPath = path.join(targetDir, dest);\n const kind = classifyPath(destPath);\n // A symlink is never \"already exists\": skipping would keep the plant, and\n // writing through it is the attack — writeFileNoFollow refuses it.\n if (kind !== 'missing' && kind !== 'symlink' && !args.force) {\n skipped.push({\n file: dest,\n reason: 'it already exists',\n hint:\n `Compare it against the scaffold and merge anything you are ` +\n `missing, or re-run with --force to overwrite ${dest}.`,\n });\n continue;\n }\n\n writeFileNoFollow(\n targetDir,\n destPath,\n dest === 'ekanos.json'\n ? renderEkanosConfig(vars, docs)\n : readTemplate(template, vars),\n );\n created.push(dest);\n }\n\n const skills = copyBundledSkillTrees(targetDir, args.force);\n created.push(...skills.created);\n skipped.push(...skills.skipped);\n\n const manifest = writeManifest(targetDir, vars, args.force);\n if (manifest.kind === 'created') created.push('package.json');\n\n spinner?.stop(`Scaffolded \"${slug}\"`);\n\n if (useWizard) {\n // Per-file \"created\" detail stays collapsed into the count above — the\n // note surfaces only what needs a partner's attention: skips (they hid\n // real data loss before, see `SkippedFile`'s doc comment) and manifest\n // notes.\n wizard.note(wizardSummaryDetail({ created, skipped, manifest }), 'Summary');\n } else {\n for (const file of created) ctx.log(` created ${file}`);\n if (manifest.kind === 'merged') {\n ctx.log(\n ` merged package.json (added ${manifest.added.length} field${\n manifest.added.length === 1 ? '' : 's'\n })`,\n );\n }\n if (manifest.kind === 'unchanged') {\n ctx.log(' package.json already had everything the scaffold adds');\n }\n for (const skip of skipped) {\n ctx.log(` skipped ${skip.file} — ${skip.reason}. ${skip.hint}`);\n }\n for (const note of manifest.notes) {\n ctx.warn(` ${note.message} ${note.hint}`);\n }\n }\n\n const data = {\n slug,\n dir: targetDir,\n created,\n skipped,\n manifest: {\n action: manifest.kind,\n added: manifest.added,\n kept: manifest.kept,\n notes: manifest.notes,\n },\n docs: SDK_DOCS_URL,\n };\n\n printBanner(ctx);\n\n if (useWizard) {\n const relDir = path.relative(args.cwd, targetDir) || '.';\n wizard.outro(\n `Next steps:\\n\\n cd ${relDir}\\n npm install\\n ekanos dev\\n\\nDocs: ${docsPage('quickstart')}`,\n );\n }\n\n // The wizard's note + outro already said everything; a trailing line would\n // only repeat them.\n return ctx.succeed(\n data,\n useWizard\n ? ''\n : humanResult(ctx.ui, {\n slug,\n dir: displayPath(targetDir, args.cwd),\n created: created.length,\n skipped: skipped.length,\n manifestAction: manifest.kind,\n manifestAdded: manifest.added.length,\n noteCount: manifest.notes.length,\n }),\n );\n}\n\n/**\n * The clack `note()` body for a successful wizard-mode scaffold — collapses\n * \"created\" down to a count (the flat per-file lines this replaces are the\n * `else` branch above) and surfaces only what needs a partner's attention:\n * skips and manifest notes, each with its full hint.\n */\nfunction wizardSummaryDetail(params: {\n created: string[];\n skipped: SkippedFile[];\n manifest: ManifestOutcome;\n}): string {\n const { created, skipped, manifest } = params;\n const lines: string[] = [\n `${created.length} file${created.length === 1 ? '' : 's'} written`,\n ];\n\n if (manifest.kind === 'merged') {\n lines.push(\n `package.json merged (+${manifest.added.length} field${\n manifest.added.length === 1 ? '' : 's'\n })`,\n );\n } else if (manifest.kind === 'unchanged') {\n lines.push('package.json already had everything the scaffold adds');\n }\n\n if (skipped.length > 0) {\n lines.push('', 'Skipped (already existed):');\n\n // Identical advice that only differs by file name is said once, not\n // once per file — twenty near-identical sentences hide the files.\n const groups = new Map<string, string[]>();\n for (const skip of skipped) {\n const key = skip.hint.split(skip.file).join('{file}');\n groups.set(key, [...(groups.get(key) ?? []), skip.file]);\n }\n\n for (const [hint, files] of groups) {\n if (files.length === 1) {\n lines.push(` ${files[0]} — ${hint.replace('{file}', files[0]!)}`);\n continue;\n }\n lines.push(...files.map((file) => ` ${file}`));\n lines.push(` ${hint.split('{file}').join('each file')}`);\n }\n }\n\n for (const note of manifest.notes) {\n lines.push('', `⚠ ${note.message} ${note.hint}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * The scaffolded ekanos.json: the template, plus the collected `--docs`\n * entries when there are any. Parse-then-serialize (rather than string\n * surgery) so the written file is exactly what `loadProject` will read back.\n */\nfunction renderEkanosConfig(\n vars: Readonly<Record<string, string>>,\n docs: string[],\n): string {\n const rendered = readTemplate('ekanos.json.tmpl', vars);\n if (docs.length === 0) return rendered;\n const project = JSON.parse(rendered) as EkanosProject;\n return serializeProject({ ...project, docs });\n}\n\n/**\n * Copy every bundled skill tree (`templates/skills/<name>/`) into the\n * scaffolded project at `.claude/skills/<name>/`, verbatim — see\n * `copyTreeVerbatim` for why these are bytes, never rendered templates. The\n * walk is generic: a skill directory added to the published templates ships\n * with no code change here. A CLI build with no `templates/skills/` at all\n * (today's) scaffolds nothing and reports nothing.\n *\n * `skillsRoot` is injectable for tests; real callers take the default.\n */\nexport function copyBundledSkillTrees(\n targetDir: string,\n force: boolean,\n skillsRoot = path.join(templatesDir(), 'skills'),\n onRefusal: 'throw' | 'collect' = 'throw',\n): { created: string[]; skipped: SkippedFile[]; refused: string[] } {\n if (!fs.existsSync(skillsRoot))\n return { created: [], skipped: [], refused: [] };\n\n const created: string[] = [];\n const skipped: SkippedFile[] = [];\n const refused: string[] = [];\n\n const skillDirs = fs\n .readdirSync(skillsRoot, { withFileTypes: true })\n .filter((entry) => entry.isDirectory())\n .map((entry) => entry.name)\n .sort();\n\n for (const name of skillDirs) {\n const destBase = path.join('.claude', 'skills', name);\n const result = copyTreeVerbatim(\n path.join(skillsRoot, name),\n path.join(targetDir, destBase),\n { force, root: targetDir, onRefusal },\n );\n created.push(...result.created.map((rel) => path.join(destBase, rel)));\n refused.push(...result.refused.map((rel) => path.join(destBase, rel)));\n skipped.push(\n ...result.skipped.map((rel) => {\n const file = path.join(destBase, rel);\n return {\n file,\n reason: 'it already exists',\n hint:\n `Compare it against the bundled skill and merge anything you ` +\n `are missing, or re-run with --force to overwrite ${file}.`,\n };\n }),\n );\n }\n\n return { created, skipped, refused };\n}\n\ntype ManifestOutcome = {\n kind: 'created' | 'merged' | 'unchanged';\n added: string[];\n kept: string[];\n notes: ManifestNote[];\n};\n\n/**\n * Write or MERGE package.json.\n *\n * A partner who ran `npm init -y` first had a manifest with none of the\n * scaffold's dependencies and `\"type\": \"commonjs\"` under ESM sources, and the\n * old behaviour reported that as one word: \"skipped\". Merging is additive — a\n * version or script they already set is theirs and is never touched — so the\n * ordinary `npm init -y` path now lands somewhere that installs and runs.\n */\nfunction writeManifest(\n targetDir: string,\n vars: Readonly<Record<string, string>>,\n force: boolean,\n): ManifestOutcome {\n const destPath = path.join(targetDir, 'package.json');\n const rendered = readTemplate('package.json.tmpl', vars);\n\n // readFileNoFollow refuses a symlinked package.json outright, so the write\n // below can never follow one either; null means genuinely absent.\n const existingRaw = readFileNoFollow(targetDir, destPath);\n\n if (existingRaw === null || force) {\n writeFileNoFollow(targetDir, destPath, rendered);\n return { kind: 'created', added: [], kept: [], notes: [] };\n }\n\n const template = JSON.parse(rendered) as Manifest;\n\n let existing: Manifest;\n try {\n existing = JSON.parse(existingRaw) as Manifest;\n } catch (error) {\n // Unparseable: do not guess, and do not clobber. Name the fix.\n return {\n kind: 'unchanged',\n added: [],\n kept: [],\n notes: [\n {\n field: '(root)',\n message: `package.json is not valid JSON: ${\n error instanceof Error ? error.message : String(error)\n }`,\n hint:\n 'Fix the JSON syntax, then re-run \"ekanos init\" to have the ' +\n \"scaffold's dependencies and scripts merged in.\",\n },\n ],\n };\n }\n\n const result = mergeManifest(existing, template);\n if (!result.changed) {\n return {\n kind: 'unchanged',\n added: [],\n kept: result.kept,\n notes: result.notes,\n };\n }\n\n writeFileNoFollow(targetDir, destPath, serializeManifest(result.manifest));\n return {\n kind: 'merged',\n added: result.added,\n kept: result.kept,\n notes: result.notes,\n };\n}\n\nfunction humanResult(\n ui: Ui,\n params: {\n slug: string;\n dir: string;\n created: number;\n skipped: number;\n manifestAction: ManifestOutcome['kind'];\n manifestAdded: number;\n noteCount: number;\n },\n): string {\n const files = [\n ...(params.created > 0 ? [`${params.created} written`] : []),\n ...(params.manifestAction === 'merged'\n ? [`${pluralize(params.manifestAdded, 'package.json field')} merged`]\n : []),\n ...(params.skipped > 0 ? [`${params.skipped} skipped`] : []),\n ];\n const attention = params.skipped > 0 || params.noteCount > 0;\n\n if (files.length === 0 && params.noteCount === 0) {\n return ui.result({\n title: `${params.slug} is already scaffolded`,\n rows: [['Directory', params.dir]],\n });\n }\n\n return ui.result({\n title: `Scaffolded ${params.slug}`,\n tone: attention ? 'warn' : 'ok',\n rows: [\n ['Directory', params.dir],\n ...(files.length > 0 ? [['Files', files.join(' · ')] as const] : []),\n ...(params.noteCount > 0\n ? [\n [\n 'Attention',\n `${pluralize(params.noteCount, 'note')} above`,\n ] as const,\n ]\n : []),\n [\n 'Next',\n [\n ...(params.dir === '.' ? [] : [`cd ${params.dir}`]),\n 'npm install',\n 'ekanos dev',\n ],\n ],\n ['Docs', docsPage('quickstart')],\n ],\n });\n}\n"]}
@@ -8,6 +8,7 @@ import { networkError, preconditionError, validationError } from '../errors.js';
8
8
  import { EXIT_CODES } from '../exit-codes.js';
9
9
  import { readInstalledPackageManifest } from '../harness-scaffold.js';
10
10
  import { detectPackageManager, installExactArgs, lockfileName, } from '../package-manager.js';
11
+ import { ensureBuildScriptsAnswered, skippedBuildScriptsHint, findIgnoredBuilds, ignoredBuildsHint, isIgnoredBuildsFailure, WORKSPACE_FILE, } from '../pnpm-builds.js';
11
12
  import { loadProject } from '../project.js';
12
13
  import { classifyPath, writeFileNoFollow } from '../safe-fs.js';
13
14
  import { renderTemplate, toDisplayName } from '../templates.js';
@@ -94,8 +95,26 @@ export async function runUpgrade(ctx, args, deps = {}) {
94
95
  }));
95
96
  }
96
97
  const snapshot = snapshotProject(loaded.projectDir, pm);
98
+ let answeredBuildScripts = [];
99
+ let buildScriptsSkipped;
97
100
  try {
98
101
  const runInstall = (_d = deps.runInstall) !== null && _d !== void 0 ? _d : defaultRunInstall;
102
+ // pnpm 10+ refuses dependency build scripts nobody has answered, and a
103
+ // new toolchain release can grow that list (0.4.0 added @swc/core and
104
+ // @parcel/watcher via next-intl). A project scaffolded under an older
105
+ // CLI has no line for them, so answer the closure BEFORE `pnpm add`, or
106
+ // the install writes placeholders and exits ERR_PNPM_IGNORED_BUILDS.
107
+ const buildScripts = pm.name === 'pnpm'
108
+ ? ensureBuildScriptsAnswered(loaded.projectDir)
109
+ : { changed: [] };
110
+ answeredBuildScripts = buildScripts.changed;
111
+ if (buildScripts.skipped) {
112
+ buildScriptsSkipped = buildScripts.skipped;
113
+ ctx.log(ctx.ui.dim(skippedBuildScriptsHint(buildScripts.skipped)));
114
+ }
115
+ if (answeredBuildScripts.length > 0) {
116
+ ctx.log(ctx.ui.dim(`Answered dependency build scripts in pnpm-workspace.yaml: ${answeredBuildScripts.join(', ')}.`));
117
+ }
99
118
  for (const group of [
100
119
  specs.filter((s) => !s.dev),
101
120
  specs.filter((s) => s.dev),
@@ -109,7 +128,9 @@ export async function runUpgrade(ctx, args, deps = {}) {
109
128
  return await failAndRollback(ctx, snapshot, pm, loaded.projectDir, runInstall, {
110
129
  step: 'install',
111
130
  exitCode: EXIT_CODES.PRECONDITION_FAILED,
112
- message: `"${pm.bin} ${argv.join(' ')}" exited with code ${result.code}.`,
131
+ message: isIgnoredBuildsFailure(result.output)
132
+ ? `"${pm.bin} ${argv.join(' ')}" exited with code ${result.code}: ${ignoredBuildsHint(findIgnoredBuilds(result.output))}`
133
+ : `"${pm.bin} ${argv.join(' ')}" exited with code ${result.code}.`,
113
134
  output: result.output,
114
135
  });
115
136
  }
@@ -176,6 +197,7 @@ export async function runUpgrade(ctx, args, deps = {}) {
176
197
  deltas,
177
198
  installed: installedKeys,
178
199
  skippedTransitive: skippedKeys,
200
+ buildScripts: Object.assign({ answered: answeredBuildScripts }, (buildScriptsSkipped ? { skipped: buildScriptsSkipped } : {})),
179
201
  regenerated,
180
202
  scaffolded,
181
203
  }, successSummary(ctx.ui, installedKeys, skippedKeys, deltas, regenerated, scaffolded));
@@ -257,7 +279,7 @@ function readManifest(packageJsonPath) {
257
279
  return parsed;
258
280
  }
259
281
  /**
260
- * Copy `package.json` and the lockfile to a temp directory before anything
282
+ * Copy `package.json`, `pnpm-workspace.yaml` and the lockfile to a temp directory before anything
261
283
  * installs. `restore()` copies them back (and removes a lockfile that did not
262
284
  * exist before, if the failed install created one); `dispose()` cleans up the
263
285
  * temp copies once the run is over, success or failure.
@@ -273,13 +295,24 @@ function snapshotProject(projectDir, pm) {
273
295
  ? path.join(projectDir, lockName)
274
296
  : path.join(projectDir, lockfileName(pm));
275
297
  const lockfileExisted = lockName !== undefined;
298
+ const workspacePath = path.join(projectDir, WORKSPACE_FILE);
299
+ const workspaceExisted = fs.existsSync(workspacePath);
276
300
  fs.copyFileSync(packageJsonPath, path.join(tmpDir, 'package.json'));
301
+ if (workspaceExisted) {
302
+ fs.copyFileSync(workspacePath, path.join(tmpDir, 'workspace.snapshot'));
303
+ }
277
304
  if (lockfileExisted) {
278
305
  fs.copyFileSync(lockfilePath, path.join(tmpDir, 'lockfile.snapshot'));
279
306
  }
280
307
  return {
281
308
  restore: () => {
282
309
  fs.copyFileSync(path.join(tmpDir, 'package.json'), packageJsonPath);
310
+ if (workspaceExisted) {
311
+ fs.copyFileSync(path.join(tmpDir, 'workspace.snapshot'), workspacePath);
312
+ }
313
+ else if (fs.existsSync(workspacePath)) {
314
+ fs.rmSync(workspacePath);
315
+ }
283
316
  if (lockfileExisted) {
284
317
  fs.copyFileSync(path.join(tmpDir, 'lockfile.snapshot'), lockfilePath);
285
318
  }
@@ -455,6 +488,11 @@ function regenerateAgentContextFiles(params) {
455
488
  const updated = [];
456
489
  const unchanged = [];
457
490
  const needsAttention = [];
491
+ const skipped = [];
492
+ const skip = (file, reason) => {
493
+ needsAttention.push(file);
494
+ skipped.push({ file, reason });
495
+ };
458
496
  for (const { template, dest } of AGENT_CONTEXT_FILES) {
459
497
  const destPath = path.join(params.projectDir, dest);
460
498
  const kind = classifyPath(destPath);
@@ -464,14 +502,16 @@ function regenerateAgentContextFiles(params) {
464
502
  // A symlink (or anything else odd) is not ours to write through — same
465
503
  // stance as init's scaffold pass, but reported instead of thrown: the
466
504
  // upgrade itself already succeeded, so surface it for a human.
467
- needsAttention.push(dest);
505
+ skip(dest, 'not-a-file');
468
506
  continue;
469
507
  }
470
508
  const current = fs.readFileSync(destPath, 'utf8');
471
509
  const oldSource = params.oldTemplates.get(template);
472
510
  const oldRendered = oldSource === undefined ? null : renderTemplate(oldSource, vars);
473
511
  if (oldRendered === null || current !== oldRendered) {
474
- needsAttention.push(dest);
512
+ // Differs from what the pre-upgrade CLI rendered: edited by the partner
513
+ // (or the project was renamed after scaffold — same thing to us).
514
+ skip(dest, 'edited-locally');
475
515
  continue;
476
516
  }
477
517
  const newTemplatePath = path.join(params.newTemplatesDir, template);
@@ -492,10 +532,10 @@ function regenerateAgentContextFiles(params) {
492
532
  // and this runs post-rollback, so a throw here would misreport a
493
533
  // successful toolchain upgrade as failed. Report it instead, like every
494
534
  // other post-rollback write path in this file.
495
- needsAttention.push(dest);
535
+ skip(dest, 'write-refused');
496
536
  }
497
537
  }
498
- return { updated, unchanged, needsAttention };
538
+ return { updated, unchanged, needsAttention, skipped };
499
539
  }
500
540
  function describeDelta(delta) {
501
541
  var _a;
@@ -542,7 +582,18 @@ function successSummary(ui, installedKeys, skippedKeys, deltas, regenerated, sca
542
582
  'init is per-file idempotent and creates only what is missing.');
543
583
  }
544
584
  if (regenerated.needsAttention.length > 0) {
545
- notes.push('Locally modified files were left untouched — merge the new template by hand.');
585
+ const edited = regenerated.skipped
586
+ .filter((s) => s.reason === 'edited-locally')
587
+ .map((s) => s.file);
588
+ const other = regenerated.skipped.filter((s) => s.reason !== 'edited-locally');
589
+ if (edited.length > 0) {
590
+ notes.push(`${edited.join(', ')} differ${edited.length === 1 ? 's' : ''} from what the previous CLI scaffolded (edited, or the project was renamed), so the new template was not applied — merge it by hand, or delete the file and run "ekanos init --slug <slug> --dir ." to re-create it.`);
591
+ }
592
+ for (const { file, reason } of other) {
593
+ notes.push(reason === 'not-a-file'
594
+ ? `${file} is a symlink or not a regular file, so it was left alone.`
595
+ : `${file} could not be written (a symlinked ancestor?), so it was left alone.`);
596
+ }
546
597
  }
547
598
  return ui.result({
548
599
  title: installedKeys.length > 0 ? 'Toolchain upgraded' : 'Nothing to install',