@ekanos/cli 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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
@@ -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';
@@ -217,11 +218,12 @@ export async function runInit(ctx, args) {
217
218
  kept: manifest.kept,
218
219
  notes: manifest.notes,
219
220
  },
221
+ docs: SDK_DOCS_URL,
220
222
  };
221
223
  printBanner(ctx);
222
224
  if (useWizard) {
223
225
  const relDir = path.relative(args.cwd, targetDir) || '.';
224
- 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')}`);
225
227
  }
226
228
  // The wizard's note + outro already said everything; a trailing line would
227
229
  // only repeat them.
@@ -422,6 +424,7 @@ function humanResult(ui, params) {
422
424
  'ekanos dev',
423
425
  ],
424
426
  ],
427
+ ['Docs', docsPage('quickstart')],
425
428
  ],
426
429
  });
427
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 // 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 };\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":"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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 { 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"]}
package/dist/context.d.ts CHANGED
@@ -125,6 +125,8 @@ export declare class CliContext {
125
125
  code: string;
126
126
  message: string;
127
127
  hint: string;
128
+ /** A docs URL for this error; JSON gets a `docs` field, human mode a line. */
129
+ docs?: string;
128
130
  exitCode: number;
129
131
  data?: unknown;
130
132
  findingsSummary?: FindingsSummary;
package/dist/context.js CHANGED
@@ -115,6 +115,7 @@ export class CliContext {
115
115
  code: error.code,
116
116
  message: error.message,
117
117
  hint: error.hint,
118
+ docs: error.docs,
118
119
  exitCode: error.exitCode,
119
120
  data,
120
121
  findingsSummary: options === null || options === void 0 ? void 0 : options.findingsSummary,
@@ -135,11 +136,7 @@ export class CliContext {
135
136
  'error must carry an imperative remediation.');
136
137
  }
137
138
  if (this.jsonMode) {
138
- const envelope = Object.assign(Object.assign({ ok: false }, (params.data !== undefined ? { data: params.data } : {})), { error: {
139
- code: params.code,
140
- message: params.message,
141
- hint: params.hint,
142
- } });
139
+ const envelope = Object.assign(Object.assign({ ok: false }, (params.data !== undefined ? { data: params.data } : {})), { error: Object.assign({ code: params.code, message: params.message, hint: params.hint }, (params.docs ? { docs: params.docs } : {})) });
143
140
  this.stdout.write(`${JSON.stringify(envelope)}\n`);
144
141
  }
145
142
  else if (params.findingsSummary) {
@@ -149,6 +146,9 @@ export class CliContext {
149
146
  else {
150
147
  this.stderr.write(`${this.ui.err('Error:')} ${params.message}\n`);
151
148
  this.stderr.write(`${this.ui.dim('Hint:')} ${params.hint}\n`);
149
+ if (params.docs) {
150
+ this.stderr.write(`${this.ui.dim('Docs:')} ${params.docs}\n`);
151
+ }
152
152
  }
153
153
  return params.exitCode;
154
154
  }
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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","sourcesContent":["import { CliError } from './errors';\nimport { EXIT_CODES, type ExitCode } from './exit-codes';\nimport { type Ui, createUi } from './ui';\n\n/**\n * A single writable sink. Real streams satisfy this; tests pass a capturing\n * buffer so the stdout-purity invariant can be asserted byte-for-byte.\n */\nexport interface WriteStream {\n write(chunk: string): void;\n isTTY?: boolean;\n}\n\nexport interface ContextInput {\n /**\n * The user's EXPLICIT preference, already parsed: `true` for `--json`,\n * `false` for `--no-json`, `undefined` when neither was passed. The\n * undefined case is what allows the mode to be inferred instead.\n */\n jsonFlag?: boolean;\n stdout?: WriteStream;\n stderr?: WriteStream;\n /** Overrides process.env for CLAUDECODE detection (tests). */\n env?: Record<string, string | undefined>;\n}\n\n/** The success envelope. */\ninterface OkEnvelope {\n ok: true;\n data: unknown;\n}\n\n/** The error envelope. `data` is optional — validate carries its findings here. */\ninterface ErrEnvelope {\n ok: false;\n data?: unknown;\n error: { code: string; message: string; hint: string };\n}\n\n/** See `fail`'s doc comment — the human-mode epilogue seam for a command that already rendered a findings block. */\ninterface FindingsSummary {\n /** The command name to name in the summary line, e.g. \"ekanos validate\". */\n command: string;\n errorCount: number;\n}\n\nexport interface FailOptions {\n findingsSummary?: FindingsSummary;\n}\n\n/**\n * `data` is deliberately `unknown` above — each verb owns its own shape — but\n * one convention spans several of them: `validate`, `dev`, `publish` and\n * `status` all add an OPTIONAL `toolchain` field to `data` (never to the\n * envelope's top level) when the proactive update check in `update-notice.ts`\n * finds this CLI is behind. See that file's `ToolchainNoticeData` for the\n * shape, and `README.md`'s \"Keeping the toolchain current\" section for the\n * full contract. It is absent, not `null`, when there is nothing to report.\n */\n\n/**\n * The agent-native output envelope, resolved ONCE at startup and threaded\n * through every command.\n *\n * The load-bearing invariant: in JSON mode, stdout receives EXACTLY ONE JSON\n * object for the whole process — nothing else, ever. Every progress line, warn,\n * and scrap of human prose goes to stderr. This is the single most common way\n * an agent's tool call breaks (a stray log line on stdout makes the JSON\n * unparseable), so the context enforces it structurally: `succeed`/`fail` are\n * the only writers of stdout, and a `settled` latch makes a second call throw\n * rather than emit a second object.\n */\nexport class CliContext {\n readonly jsonMode: boolean;\n /**\n * The environment the context resolved against — exposed so decoration\n * (the gradient banner) can honour `NO_COLOR` / `EKANOS_NO_BANNER` without\n * every call site threading `process.env` through by hand.\n */\n readonly env: Readonly<Record<string, string | undefined>>;\n /**\n * The style layer (`ui.ts`), resolved once against stderr — the stream\n * every human-mode render target uses — so every command shares one color\n * decision instead of each re-deriving it. See `ui.ts` for the tokens and\n * components this carries.\n */\n readonly ui: Ui;\n private readonly stdout: WriteStream;\n private readonly stderr: WriteStream;\n private settled = false;\n\n constructor(input: ContextInput = {}) {\n const stdout = input.stdout ?? defaultStream(process.stdout);\n const stderr = input.stderr ?? defaultStream(process.stderr);\n const env = input.env ?? process.env;\n\n this.stdout = stdout;\n this.stderr = stderr;\n this.env = env;\n this.ui = createUi({ isTTY: stderr.isTTY === true, env });\n\n // Resolve JSON mode once. An EXPLICIT flag always wins: `--json` forces the\n // envelope on even at a terminal, `--no-json` forces the human form even\n // when piped. Only when the user expressed no preference is the mode\n // inferred — from a non-TTY stdout (piped into another program or an agent)\n // or the CLAUDECODE marker.\n const nonTty = stdout.isTTY !== true;\n this.jsonMode = input.jsonFlag ?? (nonTty || env.CLAUDECODE === '1');\n }\n\n /** Whether stderr is a real terminal — decoration (the banner) gates on this. */\n get stderrIsTTY(): boolean {\n return this.stderr.isTTY === true;\n }\n\n /**\n * Whether stdout is a real terminal — `wizard.ts`'s `canWizard` gates on\n * this (clack renders to stdout, unlike the rest of this CLI's human\n * output). `ctx.jsonMode` being false does not by itself guarantee this:\n * `--no-json` forces human mode even against a piped stdout.\n */\n get stdoutIsTTY(): boolean {\n return this.stdout.isTTY === true;\n }\n\n /** Human/progress output — ALWAYS stderr, so stdout stays pure in JSON mode. */\n log(message: string): void {\n this.stderr.write(`${message}\\n`);\n }\n\n /** A warning — stderr, no ANSI. */\n warn(message: string): void {\n this.stderr.write(`${message}\\n`);\n }\n\n /**\n * Emit the `claude-code-hint` protocol line to STDERR. Used on --help and on\n * an unknown command so an agent reading stderr is told what to do next\n * without the line ever touching stdout.\n */\n emitClaudeCodeHint(payload: Record<string, unknown>): void {\n // The protocol line is machine-readable JSON for an agent reading\n // stderr. A person at a terminal (human mode on a real stderr TTY) gets\n // the readable error or help text instead of a wall of JSON above it.\n if (!this.jsonMode && this.stderrIsTTY) return;\n\n this.stderr.write(`claude-code-hint: ${JSON.stringify(payload)}\\n`);\n }\n\n /**\n * Terminal success. Writes the single stdout object (JSON mode) or a concise\n * human line (human mode). Returns the OK exit code so the caller can end the\n * process with it. Idempotent-by-latch: a second settle throws.\n */\n succeed(data: unknown, humanSummary?: string): ExitCode {\n this.settle();\n if (this.jsonMode) {\n const envelope: OkEnvelope = { ok: true, data };\n this.stdout.write(`${JSON.stringify(envelope)}\\n`);\n } else if (humanSummary !== '') {\n // An explicit empty summary means \"already said it\" — a wizard flow's\n // outro is the result, and a trailing line would just repeat it.\n this.stdout.write(`${humanSummary ?? 'OK'}\\n`);\n }\n return EXIT_CODES.OK;\n }\n\n /**\n * Terminal failure. Writes the single stdout error object (JSON mode) or the\n * message + hint to stderr (human mode). Returns the error's exit code.\n *\n * `error.hint` is guaranteed non-empty by CliError's constructor, so the\n * envelope's \"every error carries a hint\" invariant holds for every path\n * that reaches here.\n *\n * `options.findingsSummary` is the seam for a command that has ALREADY\n * rendered the shared findings block to stderr (human mode) before calling\n * this — `validate`'s and `publish`'s gate-refusal paths. Passing it swaps\n * the default two-line `Error: … / Hint: …` epilogue (whose hint always\n * points at the JSON-only `data.findings`) for one short summary line,\n * `✖ <command> failed: <n> error(s)`, so a partner never reads the same\n * finding jargon twice. JSON mode is completely unaffected either way —\n * `error.hint` always carries the original, JSON-only-appropriate text in\n * the envelope.\n */\n fail(error: CliError, data?: unknown, options?: FailOptions): ExitCode {\n this.failWith({\n code: error.code,\n message: error.message,\n hint: error.hint,\n exitCode: error.exitCode,\n data,\n findingsSummary: options?.findingsSummary,\n });\n return error.exitCode;\n }\n\n /**\n * The low-level failure emitter. Used directly by `test`, whose exit code is\n * a TRANSPARENT passthrough of the delegated runner's exit code (which is not\n * one of the CLI's own frozen numeric conditions) and whose error `code` is\n * an out-of-taxonomy string. Enforces the same non-empty-hint invariant as\n * CliError so no failure path can emit an empty hint.\n */\n failWith(params: {\n code: string;\n message: string;\n hint: string;\n exitCode: number;\n data?: unknown;\n findingsSummary?: FindingsSummary;\n }): number {\n this.settle();\n if (!params.hint || params.hint.trim().length === 0) {\n throw new Error(\n `failWith for \"${params.message}\" was given an empty hint. Every CLI ` +\n 'error must carry an imperative remediation.',\n );\n }\n if (this.jsonMode) {\n const envelope: ErrEnvelope = {\n ok: false,\n ...(params.data !== undefined ? { data: params.data } : {}),\n error: {\n code: params.code,\n message: params.message,\n hint: params.hint,\n },\n };\n this.stdout.write(`${JSON.stringify(envelope)}\\n`);\n } else if (params.findingsSummary) {\n const { command, errorCount } = params.findingsSummary;\n this.stderr.write(\n `${this.ui.err(\n `${this.ui.glyphs.err} ${command} failed: ${errorCount} error${\n errorCount === 1 ? '' : 's'\n }`,\n )}\\n`,\n );\n } else {\n this.stderr.write(`${this.ui.err('Error:')} ${params.message}\\n`);\n this.stderr.write(`${this.ui.dim('Hint:')} ${params.hint}\\n`);\n }\n return params.exitCode;\n }\n\n private settle(): void {\n if (this.settled) {\n throw new Error(\n 'CliContext settled twice — a command tried to emit a second ' +\n 'terminal result. In JSON mode this would corrupt stdout with two ' +\n 'objects. This is a bug in the command.',\n );\n }\n this.settled = true;\n }\n}\n\nfunction defaultStream(stream: NodeJS.WriteStream): WriteStream {\n return {\n write: (chunk: string) => {\n stream.write(chunk);\n },\n isTTY: stream.isTTY,\n };\n}\n"]}
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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","sourcesContent":["import { CliError } from './errors';\nimport { EXIT_CODES, type ExitCode } from './exit-codes';\nimport { type Ui, createUi } from './ui';\n\n/**\n * A single writable sink. Real streams satisfy this; tests pass a capturing\n * buffer so the stdout-purity invariant can be asserted byte-for-byte.\n */\nexport interface WriteStream {\n write(chunk: string): void;\n isTTY?: boolean;\n}\n\nexport interface ContextInput {\n /**\n * The user's EXPLICIT preference, already parsed: `true` for `--json`,\n * `false` for `--no-json`, `undefined` when neither was passed. The\n * undefined case is what allows the mode to be inferred instead.\n */\n jsonFlag?: boolean;\n stdout?: WriteStream;\n stderr?: WriteStream;\n /** Overrides process.env for CLAUDECODE detection (tests). */\n env?: Record<string, string | undefined>;\n}\n\n/** The success envelope. */\ninterface OkEnvelope {\n ok: true;\n data: unknown;\n}\n\n/** The error envelope. `data` is optional — validate carries its findings here. */\ninterface ErrEnvelope {\n ok: false;\n data?: unknown;\n error: { code: string; message: string; hint: string; docs?: string };\n}\n\n/** See `fail`'s doc comment — the human-mode epilogue seam for a command that already rendered a findings block. */\ninterface FindingsSummary {\n /** The command name to name in the summary line, e.g. \"ekanos validate\". */\n command: string;\n errorCount: number;\n}\n\nexport interface FailOptions {\n findingsSummary?: FindingsSummary;\n}\n\n/**\n * `data` is deliberately `unknown` above — each verb owns its own shape — but\n * one convention spans several of them: `validate`, `dev`, `publish` and\n * `status` all add an OPTIONAL `toolchain` field to `data` (never to the\n * envelope's top level) when the proactive update check in `update-notice.ts`\n * finds this CLI is behind. See that file's `ToolchainNoticeData` for the\n * shape, and `README.md`'s \"Keeping the toolchain current\" section for the\n * full contract. It is absent, not `null`, when there is nothing to report.\n */\n\n/**\n * The agent-native output envelope, resolved ONCE at startup and threaded\n * through every command.\n *\n * The load-bearing invariant: in JSON mode, stdout receives EXACTLY ONE JSON\n * object for the whole process — nothing else, ever. Every progress line, warn,\n * and scrap of human prose goes to stderr. This is the single most common way\n * an agent's tool call breaks (a stray log line on stdout makes the JSON\n * unparseable), so the context enforces it structurally: `succeed`/`fail` are\n * the only writers of stdout, and a `settled` latch makes a second call throw\n * rather than emit a second object.\n */\nexport class CliContext {\n readonly jsonMode: boolean;\n /**\n * The environment the context resolved against — exposed so decoration\n * (the gradient banner) can honour `NO_COLOR` / `EKANOS_NO_BANNER` without\n * every call site threading `process.env` through by hand.\n */\n readonly env: Readonly<Record<string, string | undefined>>;\n /**\n * The style layer (`ui.ts`), resolved once against stderr — the stream\n * every human-mode render target uses — so every command shares one color\n * decision instead of each re-deriving it. See `ui.ts` for the tokens and\n * components this carries.\n */\n readonly ui: Ui;\n private readonly stdout: WriteStream;\n private readonly stderr: WriteStream;\n private settled = false;\n\n constructor(input: ContextInput = {}) {\n const stdout = input.stdout ?? defaultStream(process.stdout);\n const stderr = input.stderr ?? defaultStream(process.stderr);\n const env = input.env ?? process.env;\n\n this.stdout = stdout;\n this.stderr = stderr;\n this.env = env;\n this.ui = createUi({ isTTY: stderr.isTTY === true, env });\n\n // Resolve JSON mode once. An EXPLICIT flag always wins: `--json` forces the\n // envelope on even at a terminal, `--no-json` forces the human form even\n // when piped. Only when the user expressed no preference is the mode\n // inferred — from a non-TTY stdout (piped into another program or an agent)\n // or the CLAUDECODE marker.\n const nonTty = stdout.isTTY !== true;\n this.jsonMode = input.jsonFlag ?? (nonTty || env.CLAUDECODE === '1');\n }\n\n /** Whether stderr is a real terminal — decoration (the banner) gates on this. */\n get stderrIsTTY(): boolean {\n return this.stderr.isTTY === true;\n }\n\n /**\n * Whether stdout is a real terminal — `wizard.ts`'s `canWizard` gates on\n * this (clack renders to stdout, unlike the rest of this CLI's human\n * output). `ctx.jsonMode` being false does not by itself guarantee this:\n * `--no-json` forces human mode even against a piped stdout.\n */\n get stdoutIsTTY(): boolean {\n return this.stdout.isTTY === true;\n }\n\n /** Human/progress output — ALWAYS stderr, so stdout stays pure in JSON mode. */\n log(message: string): void {\n this.stderr.write(`${message}\\n`);\n }\n\n /** A warning — stderr, no ANSI. */\n warn(message: string): void {\n this.stderr.write(`${message}\\n`);\n }\n\n /**\n * Emit the `claude-code-hint` protocol line to STDERR. Used on --help and on\n * an unknown command so an agent reading stderr is told what to do next\n * without the line ever touching stdout.\n */\n emitClaudeCodeHint(payload: Record<string, unknown>): void {\n // The protocol line is machine-readable JSON for an agent reading\n // stderr. A person at a terminal (human mode on a real stderr TTY) gets\n // the readable error or help text instead of a wall of JSON above it.\n if (!this.jsonMode && this.stderrIsTTY) return;\n\n this.stderr.write(`claude-code-hint: ${JSON.stringify(payload)}\\n`);\n }\n\n /**\n * Terminal success. Writes the single stdout object (JSON mode) or a concise\n * human line (human mode). Returns the OK exit code so the caller can end the\n * process with it. Idempotent-by-latch: a second settle throws.\n */\n succeed(data: unknown, humanSummary?: string): ExitCode {\n this.settle();\n if (this.jsonMode) {\n const envelope: OkEnvelope = { ok: true, data };\n this.stdout.write(`${JSON.stringify(envelope)}\\n`);\n } else if (humanSummary !== '') {\n // An explicit empty summary means \"already said it\" — a wizard flow's\n // outro is the result, and a trailing line would just repeat it.\n this.stdout.write(`${humanSummary ?? 'OK'}\\n`);\n }\n return EXIT_CODES.OK;\n }\n\n /**\n * Terminal failure. Writes the single stdout error object (JSON mode) or the\n * message + hint to stderr (human mode). Returns the error's exit code.\n *\n * `error.hint` is guaranteed non-empty by CliError's constructor, so the\n * envelope's \"every error carries a hint\" invariant holds for every path\n * that reaches here.\n *\n * `options.findingsSummary` is the seam for a command that has ALREADY\n * rendered the shared findings block to stderr (human mode) before calling\n * this — `validate`'s and `publish`'s gate-refusal paths. Passing it swaps\n * the default two-line `Error: … / Hint: …` epilogue (whose hint always\n * points at the JSON-only `data.findings`) for one short summary line,\n * `✖ <command> failed: <n> error(s)`, so a partner never reads the same\n * finding jargon twice. JSON mode is completely unaffected either way —\n * `error.hint` always carries the original, JSON-only-appropriate text in\n * the envelope.\n */\n fail(error: CliError, data?: unknown, options?: FailOptions): ExitCode {\n this.failWith({\n code: error.code,\n message: error.message,\n hint: error.hint,\n docs: error.docs,\n exitCode: error.exitCode,\n data,\n findingsSummary: options?.findingsSummary,\n });\n return error.exitCode;\n }\n\n /**\n * The low-level failure emitter. Used directly by `test`, whose exit code is\n * a TRANSPARENT passthrough of the delegated runner's exit code (which is not\n * one of the CLI's own frozen numeric conditions) and whose error `code` is\n * an out-of-taxonomy string. Enforces the same non-empty-hint invariant as\n * CliError so no failure path can emit an empty hint.\n */\n failWith(params: {\n code: string;\n message: string;\n hint: string;\n /** A docs URL for this error; JSON gets a `docs` field, human mode a line. */\n docs?: string;\n exitCode: number;\n data?: unknown;\n findingsSummary?: FindingsSummary;\n }): number {\n this.settle();\n if (!params.hint || params.hint.trim().length === 0) {\n throw new Error(\n `failWith for \"${params.message}\" was given an empty hint. Every CLI ` +\n 'error must carry an imperative remediation.',\n );\n }\n if (this.jsonMode) {\n const envelope: ErrEnvelope = {\n ok: false,\n ...(params.data !== undefined ? { data: params.data } : {}),\n error: {\n code: params.code,\n message: params.message,\n hint: params.hint,\n ...(params.docs ? { docs: params.docs } : {}),\n },\n };\n this.stdout.write(`${JSON.stringify(envelope)}\\n`);\n } else if (params.findingsSummary) {\n const { command, errorCount } = params.findingsSummary;\n this.stderr.write(\n `${this.ui.err(\n `${this.ui.glyphs.err} ${command} failed: ${errorCount} error${\n errorCount === 1 ? '' : 's'\n }`,\n )}\\n`,\n );\n } else {\n this.stderr.write(`${this.ui.err('Error:')} ${params.message}\\n`);\n this.stderr.write(`${this.ui.dim('Hint:')} ${params.hint}\\n`);\n if (params.docs) {\n this.stderr.write(`${this.ui.dim('Docs:')} ${params.docs}\\n`);\n }\n }\n return params.exitCode;\n }\n\n private settle(): void {\n if (this.settled) {\n throw new Error(\n 'CliContext settled twice — a command tried to emit a second ' +\n 'terminal result. In JSON mode this would corrupt stdout with two ' +\n 'objects. This is a bug in the command.',\n );\n }\n this.settled = true;\n }\n}\n\nfunction defaultStream(stream: NodeJS.WriteStream): WriteStream {\n return {\n write: (chunk: string) => {\n stream.write(chunk);\n },\n isTTY: stream.isTTY,\n };\n}\n"]}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The public Ekanos SDK docs. Every docs URL the CLI prints or emits is built
3
+ * from this one constant, so a move of the site is a one-line change.
4
+ */
5
+ export declare const SDK_DOCS_URL = "https://sdk.ekanos.dev";
6
+ /** A page on the docs site, e.g. `docsPage('cli/commands')`. */
7
+ export declare function docsPage(page: string, anchor?: string): string;
8
+ /**
9
+ * The common-errors entries for the CLI error codes the docs document. Anchors
10
+ * are the headings under "CLI errors" in common-errors.mdx.
11
+ */
12
+ export declare const ERROR_DOCS: Readonly<Record<string, string>>;
13
+ /** The schema-skew entry: documented under `precondition_failed`, but only for skew. */
14
+ export declare const SCHEMA_SKEW_DOCS: string;
15
+ /**
16
+ * The validation findings the common-errors page documents, by `check` id.
17
+ * Anchors are the headings under "Validation findings". A check absent here
18
+ * has no docs entry, so the renderer prints no link rather than a dead one.
19
+ */
20
+ export declare const FINDING_DOCS: Readonly<Record<string, string>>;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The public Ekanos SDK docs. Every docs URL the CLI prints or emits is built
3
+ * from this one constant, so a move of the site is a one-line change.
4
+ */
5
+ export const SDK_DOCS_URL = 'https://sdk.ekanos.dev';
6
+ /** A page on the docs site, e.g. `docsPage('cli/commands')`. */
7
+ export function docsPage(page, anchor) {
8
+ return `${SDK_DOCS_URL}/${page}${anchor ? `#${anchor}` : ''}`;
9
+ }
10
+ const COMMON_ERRORS = 'troubleshooting/common-errors';
11
+ /**
12
+ * The common-errors entries for the CLI error codes the docs document. Anchors
13
+ * are the headings under "CLI errors" in common-errors.mdx.
14
+ */
15
+ export const ERROR_DOCS = {
16
+ auth_required: docsPage(COMMON_ERRORS, 'exit-4-auth_required'),
17
+ forbidden: docsPage(COMMON_ERRORS, 'exit-5-forbidden'),
18
+ gate_failed: docsPage(COMMON_ERRORS, 'exit-10-gate_failed'),
19
+ };
20
+ /** The schema-skew entry: documented under `precondition_failed`, but only for skew. */
21
+ export const SCHEMA_SKEW_DOCS = docsPage(COMMON_ERRORS, 'exit-9-precondition_failed-schema-skew');
22
+ /**
23
+ * The validation findings the common-errors page documents, by `check` id.
24
+ * Anchors are the headings under "Validation findings". A check absent here
25
+ * has no docs entry, so the renderer prints no link rather than a dead one.
26
+ */
27
+ export const FINDING_DOCS = {
28
+ 'project.host-compiler-rules': docsPage(COMMON_ERRORS, 'project-host-compiler-rules-warning'),
29
+ 'project.slug-agreement': docsPage(COMMON_ERRORS, 'project-slug-agreement-error'),
30
+ 'icons/pro-only': docsPage(COMMON_ERRORS, 'icons/pro-only-warning'),
31
+ 'collision.widget-id': docsPage(COMMON_ERRORS, 'collision-widget-id-error'),
32
+ };
33
+ //# sourceMappingURL=docs-links.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docs-links.js","sourceRoot":"","sources":["../src/docs-links.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,wBAAwB,CAAC;AAErD,gEAAgE;AAChE,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,MAAe;IACpD,OAAO,GAAG,YAAY,IAAI,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;AAChE,CAAC;AAED,MAAM,aAAa,GAAG,+BAA+B,CAAC;AAEtD;;;GAGG;AACH,MAAM,CAAC,MAAM,UAAU,GAAqC;IAC1D,aAAa,EAAE,QAAQ,CAAC,aAAa,EAAE,sBAAsB,CAAC;IAC9D,SAAS,EAAE,QAAQ,CAAC,aAAa,EAAE,kBAAkB,CAAC;IACtD,WAAW,EAAE,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC;CAC5D,CAAC;AAEF,wFAAwF;AACxF,MAAM,CAAC,MAAM,gBAAgB,GAAG,QAAQ,CACtC,aAAa,EACb,wCAAwC,CACzC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAqC;IAC5D,6BAA6B,EAAE,QAAQ,CACrC,aAAa,EACb,qCAAqC,CACtC;IACD,wBAAwB,EAAE,QAAQ,CAChC,aAAa,EACb,8BAA8B,CAC/B;IACD,gBAAgB,EAAE,QAAQ,CAAC,aAAa,EAAE,wBAAwB,CAAC;IACnE,qBAAqB,EAAE,QAAQ,CAAC,aAAa,EAAE,2BAA2B,CAAC;CAC5E,CAAC","sourcesContent":["/**\n * The public Ekanos SDK docs. Every docs URL the CLI prints or emits is built\n * from this one constant, so a move of the site is a one-line change.\n */\nexport const SDK_DOCS_URL = 'https://sdk.ekanos.dev';\n\n/** A page on the docs site, e.g. `docsPage('cli/commands')`. */\nexport function docsPage(page: string, anchor?: string): string {\n return `${SDK_DOCS_URL}/${page}${anchor ? `#${anchor}` : ''}`;\n}\n\nconst COMMON_ERRORS = 'troubleshooting/common-errors';\n\n/**\n * The common-errors entries for the CLI error codes the docs document. Anchors\n * are the headings under \"CLI errors\" in common-errors.mdx.\n */\nexport const ERROR_DOCS: Readonly<Record<string, string>> = {\n auth_required: docsPage(COMMON_ERRORS, 'exit-4-auth_required'),\n forbidden: docsPage(COMMON_ERRORS, 'exit-5-forbidden'),\n gate_failed: docsPage(COMMON_ERRORS, 'exit-10-gate_failed'),\n};\n\n/** The schema-skew entry: documented under `precondition_failed`, but only for skew. */\nexport const SCHEMA_SKEW_DOCS = docsPage(\n COMMON_ERRORS,\n 'exit-9-precondition_failed-schema-skew',\n);\n\n/**\n * The validation findings the common-errors page documents, by `check` id.\n * Anchors are the headings under \"Validation findings\". A check absent here\n * has no docs entry, so the renderer prints no link rather than a dead one.\n */\nexport const FINDING_DOCS: Readonly<Record<string, string>> = {\n 'project.host-compiler-rules': docsPage(\n COMMON_ERRORS,\n 'project-host-compiler-rules-warning',\n ),\n 'project.slug-agreement': docsPage(\n COMMON_ERRORS,\n 'project-slug-agreement-error',\n ),\n 'icons/pro-only': docsPage(COMMON_ERRORS, 'icons/pro-only-warning'),\n 'collision.widget-id': docsPage(COMMON_ERRORS, 'collision-widget-id-error'),\n};\n"]}
package/dist/errors.d.ts CHANGED
@@ -11,11 +11,14 @@ export declare class CliError extends Error {
11
11
  readonly code: ErrorCode;
12
12
  readonly exitCode: ExitCode;
13
13
  readonly hint: string;
14
+ /** A docs URL for this error, when the docs document it. Absent otherwise. */
15
+ readonly docs?: string;
14
16
  constructor(params: {
15
17
  code: ErrorCode;
16
18
  exitCode: ExitCode;
17
19
  message: string;
18
20
  hint: string;
21
+ docs?: string;
19
22
  });
20
23
  }
21
24
  /** A usage error (unknown flag/command, bad argument). Exit 2. */
@@ -23,7 +26,7 @@ export declare function usageError(message: string, hint: string): CliError;
23
26
  /** Validation produced error-severity findings. Exit 3. */
24
27
  export declare function validationError(message: string, hint: string): CliError;
25
28
  /** A precondition failure (missing tool, config, or file). Exit 9. */
26
- export declare function preconditionError(message: string, hint: string): CliError;
29
+ export declare function preconditionError(message: string, hint: string, docs?: string): CliError;
27
30
  /** An invalid-state failure (idempotency / ordering). Exit 7. */
28
31
  export declare function invalidStateError(message: string, hint: string): CliError;
29
32
  /** A not-found failure. Exit 6. */
package/dist/errors.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { ERROR_DOCS } from './docs-links.js';
1
2
  import { ERROR_CODES, EXIT_CODES, } from './exit-codes.js';
2
3
  /**
3
4
  * The one error type every command path throws. It carries the three things
@@ -9,6 +10,7 @@ import { ERROR_CODES, EXIT_CODES, } from './exit-codes.js';
9
10
  */
10
11
  export class CliError extends Error {
11
12
  constructor(params) {
13
+ var _a;
12
14
  super(params.message);
13
15
  this.name = 'CliError';
14
16
  if (!params.hint || params.hint.trim().length === 0) {
@@ -18,6 +20,7 @@ export class CliError extends Error {
18
20
  this.code = params.code;
19
21
  this.exitCode = params.exitCode;
20
22
  this.hint = params.hint;
23
+ this.docs = (_a = params.docs) !== null && _a !== void 0 ? _a : ERROR_DOCS[params.code];
21
24
  }
22
25
  }
23
26
  /** A usage error (unknown flag/command, bad argument). Exit 2. */
@@ -39,12 +42,13 @@ export function validationError(message, hint) {
39
42
  });
40
43
  }
41
44
  /** A precondition failure (missing tool, config, or file). Exit 9. */
42
- export function preconditionError(message, hint) {
45
+ export function preconditionError(message, hint, docs) {
43
46
  return new CliError({
44
47
  code: ERROR_CODES.PRECONDITION_FAILED,
45
48
  exitCode: EXIT_CODES.PRECONDITION_FAILED,
46
49
  message,
47
50
  hint,
51
+ docs,
48
52
  });
49
53
  }
50
54
  /** An invalid-state failure (idempotency / ordering). Exit 7. */
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ERROR_CODES,\n EXIT_CODES,\n type ErrorCode,\n type ExitCode,\n} from './exit-codes';\n\n/**\n * The one error type every command path throws. It carries the three things\n * the envelope needs — a stable string `code`, a human `message`, and a\n * non-empty imperative `hint` — plus the numeric `exitCode` the process ends\n * with. Constructing one with an empty hint is a programming error and throws\n * immediately, which is what lets `context.fail()` guarantee the envelope's\n * \"every error has a hint\" invariant without a runtime check at the edge.\n */\nexport class CliError extends Error {\n readonly code: ErrorCode;\n readonly exitCode: ExitCode;\n readonly hint: string;\n\n constructor(params: {\n code: ErrorCode;\n exitCode: ExitCode;\n message: string;\n hint: string;\n }) {\n super(params.message);\n this.name = 'CliError';\n\n if (!params.hint || params.hint.trim().length === 0) {\n throw new Error(\n `CliError for \"${params.message}\" was constructed with an empty hint. ` +\n 'Every CLI error must carry an imperative remediation.',\n );\n }\n\n this.code = params.code;\n this.exitCode = params.exitCode;\n this.hint = params.hint;\n }\n}\n\n/** A usage error (unknown flag/command, bad argument). Exit 2. */\nexport function usageError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.USAGE,\n exitCode: EXIT_CODES.USAGE,\n message,\n hint,\n });\n}\n\n/** Validation produced error-severity findings. Exit 3. */\nexport function validationError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.VALIDATION,\n exitCode: EXIT_CODES.VALIDATION,\n message,\n hint,\n });\n}\n\n/** A precondition failure (missing tool, config, or file). Exit 9. */\nexport function preconditionError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.PRECONDITION_FAILED,\n exitCode: EXIT_CODES.PRECONDITION_FAILED,\n message,\n hint,\n });\n}\n\n/** An invalid-state failure (idempotency / ordering). Exit 7. */\nexport function invalidStateError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.INVALID_STATE,\n exitCode: EXIT_CODES.INVALID_STATE,\n message,\n hint,\n });\n}\n\n/** A not-found failure. Exit 6. */\nexport function notFoundError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.NOT_FOUND,\n exitCode: EXIT_CODES.NOT_FOUND,\n message,\n hint,\n });\n}\n\n/** Authentication is required but absent or expired. Exit 4. */\nexport function authRequiredError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.AUTH_REQUIRED,\n exitCode: EXIT_CODES.AUTH_REQUIRED,\n message,\n hint,\n });\n}\n\n/** Authenticated, but the server refused. Exit 5. */\nexport function forbiddenError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.FORBIDDEN,\n exitCode: EXIT_CODES.FORBIDDEN,\n message,\n hint,\n });\n}\n\n/** A publish gate rejected the submission. Exit 10. */\nexport function gateFailedError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.GATE_FAILED,\n exitCode: EXIT_CODES.GATE_FAILED,\n message,\n hint,\n });\n}\n\n/** A network operation failed (DNS, TLS, timeout, 5xx). Exit 8. */\nexport function networkError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.NETWORK,\n exitCode: EXIT_CODES.NETWORK,\n message,\n hint,\n });\n}\n\n/**\n * Coerce anything thrown into a CliError. A thrown CliError passes through; a\n * plain Error becomes an INTERNAL (exit 1) with a generic-but-non-empty hint.\n */\nexport function toCliError(error: unknown): CliError {\n if (error instanceof CliError) return error;\n\n const message = error instanceof Error ? error.message : String(error);\n return new CliError({\n code: ERROR_CODES.INTERNAL,\n exitCode: EXIT_CODES.INTERNAL,\n message,\n hint:\n 'This is an internal CLI error. Re-run with --json to capture the full ' +\n 'envelope and report it at npm@govastly.com if it persists.',\n });\n}\n"]}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EACL,WAAW,EACX,UAAU,GAGX,MAAM,cAAc,CAAC;AAEtB;;;;;;;GAOG;AACH,MAAM,OAAO,QAAS,SAAQ,KAAK;IAOjC,YAAY,MAMX;;QACC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACtB,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QAEvB,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACpD,MAAM,IAAI,KAAK,CACb,iBAAiB,MAAM,CAAC,OAAO,wCAAwC;gBACrE,uDAAuD,CAC1D,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;QACxB,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;QAChC,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;QACxB,IAAI,CAAC,IAAI,SAAG,MAAM,CAAC,IAAI,mCAAI,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACrD,CAAC;CACF;AAED,kEAAkE;AAClE,MAAM,UAAU,UAAU,CAAC,OAAe,EAAE,IAAY;IACtD,OAAO,IAAI,QAAQ,CAAC;QAClB,IAAI,EAAE,WAAW,CAAC,KAAK;QACvB,QAAQ,EAAE,UAAU,CAAC,KAAK;QAC1B,OAAO;QACP,IAAI;KACL,CAAC,CAAC;AACL,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,eAAe,CAAC,OAAe,EAAE,IAAY;IAC3D,OAAO,IAAI,QAAQ,CAAC;QAClB,IAAI,EAAE,WAAW,CAAC,UAAU;QAC5B,QAAQ,EAAE,UAAU,CAAC,UAAU;QAC/B,OAAO;QACP,IAAI;KACL,CAAC,CAAC;AACL,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,iBAAiB,CAC/B,OAAe,EACf,IAAY,EACZ,IAAa;IAEb,OAAO,IAAI,QAAQ,CAAC;QAClB,IAAI,EAAE,WAAW,CAAC,mBAAmB;QACrC,QAAQ,EAAE,UAAU,CAAC,mBAAmB;QACxC,OAAO;QACP,IAAI;QACJ,IAAI;KACL,CAAC,CAAC;AACL,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,iBAAiB,CAAC,OAAe,EAAE,IAAY;IAC7D,OAAO,IAAI,QAAQ,CAAC;QAClB,IAAI,EAAE,WAAW,CAAC,aAAa;QAC/B,QAAQ,EAAE,UAAU,CAAC,aAAa;QAClC,OAAO;QACP,IAAI;KACL,CAAC,CAAC;AACL,CAAC;AAED,mCAAmC;AACnC,MAAM,UAAU,aAAa,CAAC,OAAe,EAAE,IAAY;IACzD,OAAO,IAAI,QAAQ,CAAC;QAClB,IAAI,EAAE,WAAW,CAAC,SAAS;QAC3B,QAAQ,EAAE,UAAU,CAAC,SAAS;QAC9B,OAAO;QACP,IAAI;KACL,CAAC,CAAC;AACL,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,iBAAiB,CAAC,OAAe,EAAE,IAAY;IAC7D,OAAO,IAAI,QAAQ,CAAC;QAClB,IAAI,EAAE,WAAW,CAAC,aAAa;QAC/B,QAAQ,EAAE,UAAU,CAAC,aAAa;QAClC,OAAO;QACP,IAAI;KACL,CAAC,CAAC;AACL,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,cAAc,CAAC,OAAe,EAAE,IAAY;IAC1D,OAAO,IAAI,QAAQ,CAAC;QAClB,IAAI,EAAE,WAAW,CAAC,SAAS;QAC3B,QAAQ,EAAE,UAAU,CAAC,SAAS;QAC9B,OAAO;QACP,IAAI;KACL,CAAC,CAAC;AACL,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,eAAe,CAAC,OAAe,EAAE,IAAY;IAC3D,OAAO,IAAI,QAAQ,CAAC;QAClB,IAAI,EAAE,WAAW,CAAC,WAAW;QAC7B,QAAQ,EAAE,UAAU,CAAC,WAAW;QAChC,OAAO;QACP,IAAI;KACL,CAAC,CAAC;AACL,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,IAAY;IACxD,OAAO,IAAI,QAAQ,CAAC;QAClB,IAAI,EAAE,WAAW,CAAC,OAAO;QACzB,QAAQ,EAAE,UAAU,CAAC,OAAO;QAC5B,OAAO;QACP,IAAI;KACL,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,IAAI,KAAK,YAAY,QAAQ;QAAE,OAAO,KAAK,CAAC;IAE5C,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,OAAO,IAAI,QAAQ,CAAC;QAClB,IAAI,EAAE,WAAW,CAAC,QAAQ;QAC1B,QAAQ,EAAE,UAAU,CAAC,QAAQ;QAC7B,OAAO;QACP,IAAI,EACF,wEAAwE;YACxE,4DAA4D;KAC/D,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { ERROR_DOCS } from './docs-links';\nimport {\n ERROR_CODES,\n EXIT_CODES,\n type ErrorCode,\n type ExitCode,\n} from './exit-codes';\n\n/**\n * The one error type every command path throws. It carries the three things\n * the envelope needs — a stable string `code`, a human `message`, and a\n * non-empty imperative `hint` — plus the numeric `exitCode` the process ends\n * with. Constructing one with an empty hint is a programming error and throws\n * immediately, which is what lets `context.fail()` guarantee the envelope's\n * \"every error has a hint\" invariant without a runtime check at the edge.\n */\nexport class CliError extends Error {\n readonly code: ErrorCode;\n readonly exitCode: ExitCode;\n readonly hint: string;\n /** A docs URL for this error, when the docs document it. Absent otherwise. */\n readonly docs?: string;\n\n constructor(params: {\n code: ErrorCode;\n exitCode: ExitCode;\n message: string;\n hint: string;\n docs?: string;\n }) {\n super(params.message);\n this.name = 'CliError';\n\n if (!params.hint || params.hint.trim().length === 0) {\n throw new Error(\n `CliError for \"${params.message}\" was constructed with an empty hint. ` +\n 'Every CLI error must carry an imperative remediation.',\n );\n }\n\n this.code = params.code;\n this.exitCode = params.exitCode;\n this.hint = params.hint;\n this.docs = params.docs ?? ERROR_DOCS[params.code];\n }\n}\n\n/** A usage error (unknown flag/command, bad argument). Exit 2. */\nexport function usageError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.USAGE,\n exitCode: EXIT_CODES.USAGE,\n message,\n hint,\n });\n}\n\n/** Validation produced error-severity findings. Exit 3. */\nexport function validationError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.VALIDATION,\n exitCode: EXIT_CODES.VALIDATION,\n message,\n hint,\n });\n}\n\n/** A precondition failure (missing tool, config, or file). Exit 9. */\nexport function preconditionError(\n message: string,\n hint: string,\n docs?: string,\n): CliError {\n return new CliError({\n code: ERROR_CODES.PRECONDITION_FAILED,\n exitCode: EXIT_CODES.PRECONDITION_FAILED,\n message,\n hint,\n docs,\n });\n}\n\n/** An invalid-state failure (idempotency / ordering). Exit 7. */\nexport function invalidStateError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.INVALID_STATE,\n exitCode: EXIT_CODES.INVALID_STATE,\n message,\n hint,\n });\n}\n\n/** A not-found failure. Exit 6. */\nexport function notFoundError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.NOT_FOUND,\n exitCode: EXIT_CODES.NOT_FOUND,\n message,\n hint,\n });\n}\n\n/** Authentication is required but absent or expired. Exit 4. */\nexport function authRequiredError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.AUTH_REQUIRED,\n exitCode: EXIT_CODES.AUTH_REQUIRED,\n message,\n hint,\n });\n}\n\n/** Authenticated, but the server refused. Exit 5. */\nexport function forbiddenError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.FORBIDDEN,\n exitCode: EXIT_CODES.FORBIDDEN,\n message,\n hint,\n });\n}\n\n/** A publish gate rejected the submission. Exit 10. */\nexport function gateFailedError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.GATE_FAILED,\n exitCode: EXIT_CODES.GATE_FAILED,\n message,\n hint,\n });\n}\n\n/** A network operation failed (DNS, TLS, timeout, 5xx). Exit 8. */\nexport function networkError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.NETWORK,\n exitCode: EXIT_CODES.NETWORK,\n message,\n hint,\n });\n}\n\n/**\n * Coerce anything thrown into a CliError. A thrown CliError passes through; a\n * plain Error becomes an INTERNAL (exit 1) with a generic-but-non-empty hint.\n */\nexport function toCliError(error: unknown): CliError {\n if (error instanceof CliError) return error;\n\n const message = error instanceof Error ? error.message : String(error);\n return new CliError({\n code: ERROR_CODES.INTERNAL,\n exitCode: EXIT_CODES.INTERNAL,\n message,\n hint:\n 'This is an internal CLI error. Re-run with --json to capture the full ' +\n 'envelope and report it at npm@govastly.com if it persists.',\n });\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
+ import { FINDING_DOCS } from './docs-links.js';
3
4
  import { ENCLOSING_NOTE } from './source-locator.js';
4
5
  // Compile-time proof the two real shapes this renders are both assignable.
5
6
  const _typeCheck = (f) => f;
@@ -199,6 +200,10 @@ function buildGroupBody(ui, group, opts) {
199
200
  if (dedupedHint !== null) {
200
201
  lines.push(` ${ui.hint(dedupedHint)}`);
201
202
  }
203
+ const docs = FINDING_DOCS[group.check];
204
+ if (docs !== undefined) {
205
+ lines.push(` ${ui.dim(`Docs: ${docs}`)}`);
206
+ }
202
207
  return lines;
203
208
  }
204
209
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"findings-renderer.js","sourceRoot":"","sources":["../src/findings-renderer.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Finding } from '@ekanos/integration-schema';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport type { CliContext } from './context';\nimport type { GateFinding } from './publish-api';\nimport { ENCLOSING_NOTE } from './source-locator';\nimport type { BadgeKind, Ui } from './ui';\n\n/**\n * The shared findings block: one rendering used by `validate` (a failed run),\n * `publish` (a gate refusal), and `status --findings`, so a partner sees the\n * exact same shape wherever a finding shows up. This is the ONLY findings\n * renderer in the package — nothing else may hand-format a `Finding`/\n * `GateFinding` array for a human.\n *\n * Deliberately structural rather than importing `Finding` or `GateFinding`\n * directly as the parameter type: both already satisfy this shape (their\n * extra/missing fields are all optional), and a structural type keeps this\n * module from caring which of the two ships that array.\n */\nexport interface RenderableFinding {\n check: string;\n severity: 'error' | 'warn' | 'info';\n file?: string;\n line?: number;\n message: string;\n hint: string;\n /** `'gate' | 'ci' | 'host' | 'revocation'` on the wire — see `GateFinding`. */\n origin?: string;\n createdAt?: string;\n resolvedAt?: string | null;\n /**\n * Optional multi-line supplement rendered directly under the finding's\n * message — currently only `ekanos test`'s curated failures appendix sets\n * this (an assertion diff). Additive: neither `Finding` nor `GateFinding`\n * carries it, so every pre-existing caller renders byte-identically to\n * before. Lines are shown as-is except a leading `-`/`+` (unified-diff\n * style), which is colored red/green respectively.\n */\n detail?: string;\n}\n\n// Compile-time proof the two real shapes this renders are both assignable.\nconst _typeCheck: (f: Finding | GateFinding) => RenderableFinding = (f) => f;\nvoid _typeCheck;\n\nexport interface RenderFindingsOptions {\n /** Findings shown per group before an overflow line takes over. Default 10. */\n maxPerGroup?: number;\n /** Base for resolving a finding's `file` before reading it for a codeframe. Default `process.cwd()`. */\n cwd?: string;\n /** Injectable for `relativeTime` snapshot tests. Default `new Date()`. */\n now?: Date;\n}\n\nconst DEFAULT_MAX_PER_GROUP = 10;\n\nconst SEVERITY_RANK: Record<RenderableFinding['severity'], number> = {\n error: 0,\n warn: 1,\n info: 2,\n};\n\ninterface Group {\n check: string;\n findings: RenderableFinding[];\n /** Insertion order, for a stable tie-break when sorting groups. */\n order: number;\n}\n\n/**\n * `relativeTime('2026-09-01T10:00:00Z')` → `\"2h ago\"`. Deliberately coarse\n * (rounds to the nearest unit, one unit at a time) — this is a findings-list\n * badge, not an audit-log timestamp, and a partner scanning for \"is this\n * recent\" doesn't need sub-minute precision.\n */\nexport function relativeTime(iso: string, now: Date = new Date()): string {\n const then = new Date(iso).getTime();\n if (Number.isNaN(then)) return iso;\n\n const diffSec = Math.max(0, Math.round((now.getTime() - then) / 1000));\n\n if (diffSec < 5) return 'just now';\n if (diffSec < 60) return `${diffSec}s ago`;\n\n const diffMin = Math.round(diffSec / 60);\n if (diffMin < 60) return `${diffMin}m ago`;\n\n const diffHour = Math.round(diffMin / 60);\n if (diffHour < 24) return `${diffHour}h ago`;\n\n const diffDay = Math.round(diffHour / 24);\n if (diffDay < 30) return `${diffDay}d ago`;\n\n const diffMonth = Math.round(diffDay / 30);\n if (diffMonth < 12) return `${diffMonth}mo ago`;\n\n const diffYear = Math.round(diffMonth / 12);\n return `${diffYear}y ago`;\n}\n\n/**\n * A message beginning with a zod-style dot-path (`components.widgets[2].id: …`,\n * the shape `collectDefinitionFindings` produces — see the `Finding` doc\n * comment in `@ekanos/integration-schema`). Captures the path and the rest\n * separately so the renderer can dim the path instead of shouting the whole\n * sentence in one uniform color.\n */\nconst DOT_PATH_MESSAGE =\n /^([A-Za-z0-9_]+(?:\\.[A-Za-z0-9_]+|\\[\\d+\\])*): ([^]+)$/;\n\n/**\n * Render one finding's message: a leading dot-path identical to the\n * finding's own `file` is pure duplication (the path would just repeat what\n * `file:line` already told the reader) and is dropped; any other dot-path\n * prefix is kept but dimmed, so the sentence itself reads at full contrast.\n * A message with no dot-path prefix passes through untouched.\n *\n * A trailing `source-locator.ts` `ENCLOSING_NOTE` (plain text in the data —\n * see that module's doc comment on why it never carries color itself) is\n * peeled off before the dot-path handling above and dimmed on its own, then\n * reattached — so it stays visually secondary to the message itself in\n * exactly the case where the frame it accompanies points at the enclosing\n * object rather than the missing key.\n */\nfunction renderMessage(ui: Ui, finding: RenderableFinding): string {\n const enclosingSuffix = ` ${ENCLOSING_NOTE}`;\n const hasEnclosingNote = finding.message.endsWith(enclosingSuffix);\n const message = hasEnclosingNote\n ? finding.message.slice(0, -enclosingSuffix.length)\n : finding.message;\n\n const match = DOT_PATH_MESSAGE.exec(message);\n const rendered = (() => {\n if (!match) return message;\n\n const [, dotPath, rest] = match;\n if (dotPath === finding.file) return rest ?? message;\n\n return `${ui.dim(dotPath ?? '')}: ${rest ?? ''}`;\n })();\n\n return hasEnclosingNote ? `${rendered} ${ui.dim(ENCLOSING_NOTE)}` : rendered;\n}\n\nfunction severityColor(\n ui: Ui,\n severity: RenderableFinding['severity'],\n): (text: string) => string {\n if (severity === 'error') return ui.err;\n if (severity === 'warn') return ui.warn;\n return ui.dim;\n}\n\nfunction severityGlyph(\n ui: Ui,\n severity: RenderableFinding['severity'],\n): string {\n if (severity === 'warn') return ui.glyphs.warn;\n if (severity === 'info') return ui.glyphs.info;\n return ui.glyphs.err;\n}\n\nfunction ellipsis(ui: Ui): string {\n return ui.glyphs.ok === '✓' ? '…' : '...';\n}\n\n/** Attempts a codeframe for `finding`; `null` when it isn't renderable (no line, or the file can't be read locally). */\nfunction tryCodeframe(\n ui: Ui,\n finding: RenderableFinding,\n cwd: string,\n): string | null {\n if (finding.file === undefined || finding.line === undefined) return null;\n\n const resolved = path.resolve(cwd, finding.file);\n if (!fs.existsSync(resolved)) return null;\n\n try {\n const source = fs.readFileSync(resolved, 'utf8');\n return ui.codeframe({ file: finding.file, line: finding.line, source });\n } catch {\n // Best-effort decoration — an unreadable file (permissions, a race with\n // a concurrent edit) just means no codeframe, never a renderer failure.\n return null;\n }\n}\n\nfunction originBadgeKind(_origin: string): BadgeKind {\n return 'dim';\n}\n\n/**\n * Renders a finding's `detail` block, one line per source line: a\n * unified-diff `-` prefix is red, a `+` prefix is green, everything else\n * (context lines, \"Expected\"/\"Received\" headers, an overflow note) is dim.\n * `---`/`+++` file-header lines never appear here — `test-report.ts` never\n * emits them — but are guarded against anyway since this is the one place\n * that would mis-color them.\n */\nfunction renderDetailLines(ui: Ui, detail: string): string[] {\n return detail.split('\\n').map((line) => {\n if (line.startsWith('-') && !line.startsWith('---')) return ui.err(line);\n if (line.startsWith('+') && !line.startsWith('+++')) return ui.ok(line);\n return ui.dim(line);\n });\n}\n\nfunction buildGroupHeader(\n ui: Ui,\n group: Group,\n opts: Required<Pick<RenderFindingsOptions, 'now'>>,\n): string {\n const errorCount = group.findings.filter(\n (f) => f.severity === 'error',\n ).length;\n const warnCount = group.findings.filter((f) => f.severity === 'warn').length;\n const infoCount = group.findings.filter((f) => f.severity === 'info').length;\n\n const worst = group.findings[0]?.severity ?? 'info';\n const color = severityColor(ui, worst);\n const glyph = severityGlyph(ui, worst);\n\n const parts: string[] = [];\n if (errorCount > 0)\n parts.push(`${errorCount} error${errorCount === 1 ? '' : 's'}`);\n if (warnCount > 0)\n parts.push(`${warnCount} warning${warnCount === 1 ? '' : 's'}`);\n if (infoCount > 0) parts.push(`${infoCount} info`);\n\n // The representative for the group's origin badge is the first (i.e.\n // worst-severity, then earliest) finding that carries one — a check\n // almost always shares one origin, and this avoids a badge per line.\n const withOrigin = group.findings.find((f) => f.origin !== undefined);\n const badge =\n withOrigin?.origin === undefined\n ? ''\n : ` ${ui.badge(\n withOrigin.createdAt\n ? `${withOrigin.origin} · ${relativeTime(withOrigin.createdAt, opts.now)}`\n : withOrigin.origin,\n originBadgeKind(withOrigin.origin),\n )}`;\n\n return `${color(`${glyph} ${group.check}`)} · ${parts.join(', ')}${badge}`;\n}\n\nfunction buildGroupBody(\n ui: Ui,\n group: Group,\n opts: Required<Pick<RenderFindingsOptions, 'maxPerGroup' | 'cwd'>>,\n): string[] {\n const uniqueHints = [...new Set(group.findings.map((f) => f.hint))];\n const [firstHint] = uniqueHints;\n const dedupedHint =\n uniqueHints.length === 1 && firstHint !== undefined ? firstHint : null;\n\n const visible = group.findings.slice(0, opts.maxPerGroup);\n const overflow = group.findings.length - visible.length;\n\n const lines: string[] = [];\n let codeframeRendered = false;\n\n for (const finding of visible) {\n const location =\n finding.file !== undefined\n ? ui.accent(\n finding.line !== undefined\n ? `${finding.file}:${finding.line}`\n : finding.file,\n )\n : null;\n const message = renderMessage(ui, finding);\n\n lines.push(` ${ui.dim('│')}${location ? ` ${location}` : ''} ${message}`);\n\n if (finding.detail !== undefined) {\n lines.push(\n ...renderDetailLines(ui, finding.detail).map((line) => ` ${line}`),\n );\n }\n\n if (dedupedHint === null) {\n lines.push(` ${ui.hint(finding.hint)}`);\n }\n\n if (!codeframeRendered) {\n const frame = tryCodeframe(ui, finding, opts.cwd);\n if (frame !== null) {\n codeframeRendered = true;\n lines.push(\n frame\n .split('\\n')\n .map((line) => ` ${line}`)\n .join('\\n'),\n );\n }\n }\n }\n\n if (overflow > 0) {\n lines.push(\n ` ${ui.dim(`${ellipsis(ui)} and ${overflow} more in this group`)}`,\n );\n }\n\n if (dedupedHint !== null) {\n lines.push(` ${ui.hint(dedupedHint)}`);\n }\n\n return lines;\n}\n\n/**\n * Pure formatter — no I/O beyond an optional codeframe file read, no\n * printing. Kept separate from `renderFindings` so tests can assert exact\n * strings against a `Ui` built with a known color/glyph decision, without\n * constructing a full `CliContext`.\n */\nexport function formatFindings(\n ui: Ui,\n findings: readonly RenderableFinding[],\n options: RenderFindingsOptions = {},\n): string {\n if (findings.length === 0) return '';\n\n const maxPerGroup = options.maxPerGroup ?? DEFAULT_MAX_PER_GROUP;\n const cwd = options.cwd ?? process.cwd();\n const now = options.now ?? new Date();\n\n const groupsByCheck = new Map<string, Group>();\n findings.forEach((finding, index) => {\n const existing = groupsByCheck.get(finding.check);\n if (existing) {\n existing.findings.push(finding);\n return;\n }\n groupsByCheck.set(finding.check, {\n check: finding.check,\n findings: [finding],\n order: index,\n });\n });\n\n const groups = [...groupsByCheck.values()];\n for (const group of groups) {\n group.findings.sort(\n (a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity],\n );\n }\n groups.sort((a, b) => {\n const worstA = SEVERITY_RANK[a.findings[0]?.severity ?? 'info'];\n const worstB = SEVERITY_RANK[b.findings[0]?.severity ?? 'info'];\n return worstA - worstB || a.order - b.order;\n });\n\n const blocks = groups.map((group) => {\n const header = buildGroupHeader(ui, group, { now });\n const body = buildGroupBody(ui, group, { maxPerGroup, cwd });\n return [header, ...body].join('\\n');\n });\n\n const totalErrors = findings.filter((f) => f.severity === 'error').length;\n const totalWarnings = findings.filter((f) => f.severity === 'warn').length;\n const totalInfo = findings.filter((f) => f.severity === 'info').length;\n\n const summaryParts: string[] = [];\n if (totalErrors > 0)\n summaryParts.push(`${totalErrors} error${totalErrors === 1 ? '' : 's'}`);\n if (totalWarnings > 0)\n summaryParts.push(\n `${totalWarnings} warning${totalWarnings === 1 ? '' : 's'}`,\n );\n if (totalInfo > 0 && totalErrors === 0 && totalWarnings === 0) {\n summaryParts.push(`${totalInfo} info`);\n }\n\n const summaryColor =\n totalErrors > 0 ? ui.err : totalWarnings > 0 ? ui.warn : ui.dim;\n const summaryGlyph =\n totalErrors > 0\n ? ui.glyphs.err\n : totalWarnings > 0\n ? ui.glyphs.warn\n : ui.glyphs.info;\n const checksCount = groups.length;\n\n const summary = summaryColor(\n `${summaryGlyph} ${summaryParts.join(', ')} across ${checksCount} check${\n checksCount === 1 ? '' : 's'\n }`,\n );\n\n return [...blocks, summary].join('\\n\\n');\n}\n\n/** Render the shared findings block to `ctx`'s human-mode output (stderr). A no-op for an empty findings array. */\nexport function renderFindings(\n ctx: CliContext,\n findings: readonly RenderableFinding[],\n options: RenderFindingsOptions = {},\n): void {\n const block = formatFindings(ctx.ui, findings, options);\n if (block !== '') ctx.log(block);\n}\n"]}
1
+ {"version":3,"file":"findings-renderer.js","sourceRoot":"","sources":["../src/findings-renderer.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Finding } from '@ekanos/integration-schema';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport type { CliContext } from './context';\nimport { FINDING_DOCS } from './docs-links';\nimport type { GateFinding } from './publish-api';\nimport { ENCLOSING_NOTE } from './source-locator';\nimport type { BadgeKind, Ui } from './ui';\n\n/**\n * The shared findings block: one rendering used by `validate` (a failed run),\n * `publish` (a gate refusal), and `status --findings`, so a partner sees the\n * exact same shape wherever a finding shows up. This is the ONLY findings\n * renderer in the package — nothing else may hand-format a `Finding`/\n * `GateFinding` array for a human.\n *\n * Deliberately structural rather than importing `Finding` or `GateFinding`\n * directly as the parameter type: both already satisfy this shape (their\n * extra/missing fields are all optional), and a structural type keeps this\n * module from caring which of the two ships that array.\n */\nexport interface RenderableFinding {\n check: string;\n severity: 'error' | 'warn' | 'info';\n file?: string;\n line?: number;\n message: string;\n hint: string;\n /** `'gate' | 'ci' | 'host' | 'revocation'` on the wire — see `GateFinding`. */\n origin?: string;\n createdAt?: string;\n resolvedAt?: string | null;\n /**\n * Optional multi-line supplement rendered directly under the finding's\n * message — currently only `ekanos test`'s curated failures appendix sets\n * this (an assertion diff). Additive: neither `Finding` nor `GateFinding`\n * carries it, so every pre-existing caller renders byte-identically to\n * before. Lines are shown as-is except a leading `-`/`+` (unified-diff\n * style), which is colored red/green respectively.\n */\n detail?: string;\n}\n\n// Compile-time proof the two real shapes this renders are both assignable.\nconst _typeCheck: (f: Finding | GateFinding) => RenderableFinding = (f) => f;\nvoid _typeCheck;\n\nexport interface RenderFindingsOptions {\n /** Findings shown per group before an overflow line takes over. Default 10. */\n maxPerGroup?: number;\n /** Base for resolving a finding's `file` before reading it for a codeframe. Default `process.cwd()`. */\n cwd?: string;\n /** Injectable for `relativeTime` snapshot tests. Default `new Date()`. */\n now?: Date;\n}\n\nconst DEFAULT_MAX_PER_GROUP = 10;\n\nconst SEVERITY_RANK: Record<RenderableFinding['severity'], number> = {\n error: 0,\n warn: 1,\n info: 2,\n};\n\ninterface Group {\n check: string;\n findings: RenderableFinding[];\n /** Insertion order, for a stable tie-break when sorting groups. */\n order: number;\n}\n\n/**\n * `relativeTime('2026-09-01T10:00:00Z')` → `\"2h ago\"`. Deliberately coarse\n * (rounds to the nearest unit, one unit at a time) — this is a findings-list\n * badge, not an audit-log timestamp, and a partner scanning for \"is this\n * recent\" doesn't need sub-minute precision.\n */\nexport function relativeTime(iso: string, now: Date = new Date()): string {\n const then = new Date(iso).getTime();\n if (Number.isNaN(then)) return iso;\n\n const diffSec = Math.max(0, Math.round((now.getTime() - then) / 1000));\n\n if (diffSec < 5) return 'just now';\n if (diffSec < 60) return `${diffSec}s ago`;\n\n const diffMin = Math.round(diffSec / 60);\n if (diffMin < 60) return `${diffMin}m ago`;\n\n const diffHour = Math.round(diffMin / 60);\n if (diffHour < 24) return `${diffHour}h ago`;\n\n const diffDay = Math.round(diffHour / 24);\n if (diffDay < 30) return `${diffDay}d ago`;\n\n const diffMonth = Math.round(diffDay / 30);\n if (diffMonth < 12) return `${diffMonth}mo ago`;\n\n const diffYear = Math.round(diffMonth / 12);\n return `${diffYear}y ago`;\n}\n\n/**\n * A message beginning with a zod-style dot-path (`components.widgets[2].id: …`,\n * the shape `collectDefinitionFindings` produces — see the `Finding` doc\n * comment in `@ekanos/integration-schema`). Captures the path and the rest\n * separately so the renderer can dim the path instead of shouting the whole\n * sentence in one uniform color.\n */\nconst DOT_PATH_MESSAGE =\n /^([A-Za-z0-9_]+(?:\\.[A-Za-z0-9_]+|\\[\\d+\\])*): ([^]+)$/;\n\n/**\n * Render one finding's message: a leading dot-path identical to the\n * finding's own `file` is pure duplication (the path would just repeat what\n * `file:line` already told the reader) and is dropped; any other dot-path\n * prefix is kept but dimmed, so the sentence itself reads at full contrast.\n * A message with no dot-path prefix passes through untouched.\n *\n * A trailing `source-locator.ts` `ENCLOSING_NOTE` (plain text in the data —\n * see that module's doc comment on why it never carries color itself) is\n * peeled off before the dot-path handling above and dimmed on its own, then\n * reattached — so it stays visually secondary to the message itself in\n * exactly the case where the frame it accompanies points at the enclosing\n * object rather than the missing key.\n */\nfunction renderMessage(ui: Ui, finding: RenderableFinding): string {\n const enclosingSuffix = ` ${ENCLOSING_NOTE}`;\n const hasEnclosingNote = finding.message.endsWith(enclosingSuffix);\n const message = hasEnclosingNote\n ? finding.message.slice(0, -enclosingSuffix.length)\n : finding.message;\n\n const match = DOT_PATH_MESSAGE.exec(message);\n const rendered = (() => {\n if (!match) return message;\n\n const [, dotPath, rest] = match;\n if (dotPath === finding.file) return rest ?? message;\n\n return `${ui.dim(dotPath ?? '')}: ${rest ?? ''}`;\n })();\n\n return hasEnclosingNote ? `${rendered} ${ui.dim(ENCLOSING_NOTE)}` : rendered;\n}\n\nfunction severityColor(\n ui: Ui,\n severity: RenderableFinding['severity'],\n): (text: string) => string {\n if (severity === 'error') return ui.err;\n if (severity === 'warn') return ui.warn;\n return ui.dim;\n}\n\nfunction severityGlyph(\n ui: Ui,\n severity: RenderableFinding['severity'],\n): string {\n if (severity === 'warn') return ui.glyphs.warn;\n if (severity === 'info') return ui.glyphs.info;\n return ui.glyphs.err;\n}\n\nfunction ellipsis(ui: Ui): string {\n return ui.glyphs.ok === '✓' ? '…' : '...';\n}\n\n/** Attempts a codeframe for `finding`; `null` when it isn't renderable (no line, or the file can't be read locally). */\nfunction tryCodeframe(\n ui: Ui,\n finding: RenderableFinding,\n cwd: string,\n): string | null {\n if (finding.file === undefined || finding.line === undefined) return null;\n\n const resolved = path.resolve(cwd, finding.file);\n if (!fs.existsSync(resolved)) return null;\n\n try {\n const source = fs.readFileSync(resolved, 'utf8');\n return ui.codeframe({ file: finding.file, line: finding.line, source });\n } catch {\n // Best-effort decoration — an unreadable file (permissions, a race with\n // a concurrent edit) just means no codeframe, never a renderer failure.\n return null;\n }\n}\n\nfunction originBadgeKind(_origin: string): BadgeKind {\n return 'dim';\n}\n\n/**\n * Renders a finding's `detail` block, one line per source line: a\n * unified-diff `-` prefix is red, a `+` prefix is green, everything else\n * (context lines, \"Expected\"/\"Received\" headers, an overflow note) is dim.\n * `---`/`+++` file-header lines never appear here — `test-report.ts` never\n * emits them — but are guarded against anyway since this is the one place\n * that would mis-color them.\n */\nfunction renderDetailLines(ui: Ui, detail: string): string[] {\n return detail.split('\\n').map((line) => {\n if (line.startsWith('-') && !line.startsWith('---')) return ui.err(line);\n if (line.startsWith('+') && !line.startsWith('+++')) return ui.ok(line);\n return ui.dim(line);\n });\n}\n\nfunction buildGroupHeader(\n ui: Ui,\n group: Group,\n opts: Required<Pick<RenderFindingsOptions, 'now'>>,\n): string {\n const errorCount = group.findings.filter(\n (f) => f.severity === 'error',\n ).length;\n const warnCount = group.findings.filter((f) => f.severity === 'warn').length;\n const infoCount = group.findings.filter((f) => f.severity === 'info').length;\n\n const worst = group.findings[0]?.severity ?? 'info';\n const color = severityColor(ui, worst);\n const glyph = severityGlyph(ui, worst);\n\n const parts: string[] = [];\n if (errorCount > 0)\n parts.push(`${errorCount} error${errorCount === 1 ? '' : 's'}`);\n if (warnCount > 0)\n parts.push(`${warnCount} warning${warnCount === 1 ? '' : 's'}`);\n if (infoCount > 0) parts.push(`${infoCount} info`);\n\n // The representative for the group's origin badge is the first (i.e.\n // worst-severity, then earliest) finding that carries one — a check\n // almost always shares one origin, and this avoids a badge per line.\n const withOrigin = group.findings.find((f) => f.origin !== undefined);\n const badge =\n withOrigin?.origin === undefined\n ? ''\n : ` ${ui.badge(\n withOrigin.createdAt\n ? `${withOrigin.origin} · ${relativeTime(withOrigin.createdAt, opts.now)}`\n : withOrigin.origin,\n originBadgeKind(withOrigin.origin),\n )}`;\n\n return `${color(`${glyph} ${group.check}`)} · ${parts.join(', ')}${badge}`;\n}\n\nfunction buildGroupBody(\n ui: Ui,\n group: Group,\n opts: Required<Pick<RenderFindingsOptions, 'maxPerGroup' | 'cwd'>>,\n): string[] {\n const uniqueHints = [...new Set(group.findings.map((f) => f.hint))];\n const [firstHint] = uniqueHints;\n const dedupedHint =\n uniqueHints.length === 1 && firstHint !== undefined ? firstHint : null;\n\n const visible = group.findings.slice(0, opts.maxPerGroup);\n const overflow = group.findings.length - visible.length;\n\n const lines: string[] = [];\n let codeframeRendered = false;\n\n for (const finding of visible) {\n const location =\n finding.file !== undefined\n ? ui.accent(\n finding.line !== undefined\n ? `${finding.file}:${finding.line}`\n : finding.file,\n )\n : null;\n const message = renderMessage(ui, finding);\n\n lines.push(` ${ui.dim('│')}${location ? ` ${location}` : ''} ${message}`);\n\n if (finding.detail !== undefined) {\n lines.push(\n ...renderDetailLines(ui, finding.detail).map((line) => ` ${line}`),\n );\n }\n\n if (dedupedHint === null) {\n lines.push(` ${ui.hint(finding.hint)}`);\n }\n\n if (!codeframeRendered) {\n const frame = tryCodeframe(ui, finding, opts.cwd);\n if (frame !== null) {\n codeframeRendered = true;\n lines.push(\n frame\n .split('\\n')\n .map((line) => ` ${line}`)\n .join('\\n'),\n );\n }\n }\n }\n\n if (overflow > 0) {\n lines.push(\n ` ${ui.dim(`${ellipsis(ui)} and ${overflow} more in this group`)}`,\n );\n }\n\n if (dedupedHint !== null) {\n lines.push(` ${ui.hint(dedupedHint)}`);\n }\n\n const docs = FINDING_DOCS[group.check];\n if (docs !== undefined) {\n lines.push(` ${ui.dim(`Docs: ${docs}`)}`);\n }\n\n return lines;\n}\n\n/**\n * Pure formatter — no I/O beyond an optional codeframe file read, no\n * printing. Kept separate from `renderFindings` so tests can assert exact\n * strings against a `Ui` built with a known color/glyph decision, without\n * constructing a full `CliContext`.\n */\nexport function formatFindings(\n ui: Ui,\n findings: readonly RenderableFinding[],\n options: RenderFindingsOptions = {},\n): string {\n if (findings.length === 0) return '';\n\n const maxPerGroup = options.maxPerGroup ?? DEFAULT_MAX_PER_GROUP;\n const cwd = options.cwd ?? process.cwd();\n const now = options.now ?? new Date();\n\n const groupsByCheck = new Map<string, Group>();\n findings.forEach((finding, index) => {\n const existing = groupsByCheck.get(finding.check);\n if (existing) {\n existing.findings.push(finding);\n return;\n }\n groupsByCheck.set(finding.check, {\n check: finding.check,\n findings: [finding],\n order: index,\n });\n });\n\n const groups = [...groupsByCheck.values()];\n for (const group of groups) {\n group.findings.sort(\n (a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity],\n );\n }\n groups.sort((a, b) => {\n const worstA = SEVERITY_RANK[a.findings[0]?.severity ?? 'info'];\n const worstB = SEVERITY_RANK[b.findings[0]?.severity ?? 'info'];\n return worstA - worstB || a.order - b.order;\n });\n\n const blocks = groups.map((group) => {\n const header = buildGroupHeader(ui, group, { now });\n const body = buildGroupBody(ui, group, { maxPerGroup, cwd });\n return [header, ...body].join('\\n');\n });\n\n const totalErrors = findings.filter((f) => f.severity === 'error').length;\n const totalWarnings = findings.filter((f) => f.severity === 'warn').length;\n const totalInfo = findings.filter((f) => f.severity === 'info').length;\n\n const summaryParts: string[] = [];\n if (totalErrors > 0)\n summaryParts.push(`${totalErrors} error${totalErrors === 1 ? '' : 's'}`);\n if (totalWarnings > 0)\n summaryParts.push(\n `${totalWarnings} warning${totalWarnings === 1 ? '' : 's'}`,\n );\n if (totalInfo > 0 && totalErrors === 0 && totalWarnings === 0) {\n summaryParts.push(`${totalInfo} info`);\n }\n\n const summaryColor =\n totalErrors > 0 ? ui.err : totalWarnings > 0 ? ui.warn : ui.dim;\n const summaryGlyph =\n totalErrors > 0\n ? ui.glyphs.err\n : totalWarnings > 0\n ? ui.glyphs.warn\n : ui.glyphs.info;\n const checksCount = groups.length;\n\n const summary = summaryColor(\n `${summaryGlyph} ${summaryParts.join(', ')} across ${checksCount} check${\n checksCount === 1 ? '' : 's'\n }`,\n );\n\n return [...blocks, summary].join('\\n\\n');\n}\n\n/** Render the shared findings block to `ctx`'s human-mode output (stderr). A no-op for an empty findings array. */\nexport function renderFindings(\n ctx: CliContext,\n findings: readonly RenderableFinding[],\n options: RenderFindingsOptions = {},\n): void {\n const block = formatFindings(ctx.ui, findings, options);\n if (block !== '') ctx.log(block);\n}\n"]}
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ import { runUse } from './commands/use.js';
12
12
  import { runValidate } from './commands/validate.js';
13
13
  import { runWhoami } from './commands/whoami.js';
14
14
  import { CliContext } from './context.js';
15
+ import { SDK_DOCS_URL, docsPage } from './docs-links.js';
15
16
  import { toCliError, usageError } from './errors.js';
16
17
  import { EXIT_CODES } from './exit-codes.js';
17
18
  import { parseArgv } from './parse-argv.js';
@@ -387,6 +388,7 @@ function emitVersion(ctx) {
387
388
  const version = readVersion();
388
389
  return ctx.succeed({ name: 'ekanos', version }, version);
389
390
  }
391
+ const DOCS_FOOTER = `\nDocs: ${SDK_DOCS_URL} (commands: ${docsPage('cli/commands')})`;
390
392
  function emitHelp(ctx) {
391
393
  ctx.emitClaudeCodeHint({
392
394
  tool: 'ekanos',
@@ -394,7 +396,7 @@ function emitHelp(ctx) {
394
396
  usage: USAGE,
395
397
  exitCodes: EXIT_CODES,
396
398
  });
397
- return ctx.succeed({ usage: USAGE, commands: COMMANDS }, USAGE);
399
+ return ctx.succeed({ usage: USAGE, commands: COMMANDS, docs: SDK_DOCS_URL }, `${USAGE}${DOCS_FOOTER}`);
398
400
  }
399
401
  function emitCommandHelp(ctx, command) {
400
402
  var _a;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\n\nimport { runDev } from './commands/dev';\nimport { runInit } from './commands/init';\nimport { runLogin } from './commands/login';\nimport { runLogout } from './commands/logout';\nimport { runPublish } from './commands/publish';\nimport { runSources } from './commands/sources';\nimport { runStatus } from './commands/status';\nimport { runTest } from './commands/test';\nimport { runUpgrade } from './commands/upgrade';\nimport { runUse } from './commands/use';\nimport { runValidate } from './commands/validate';\nimport { runWhoami } from './commands/whoami';\nimport { CliContext, type WriteStream } from './context';\nimport { toCliError, usageError } from './errors';\nimport { EXIT_CODES } from './exit-codes';\nimport { type FlagSpecs, parseArgv } from './parse-argv';\n\nexport { CliContext } from './context';\nexport { EXIT_CODES, ERROR_CODES } from './exit-codes';\nexport type { ExitCode, ErrorCode } from './exit-codes';\n\nconst GLOBAL_SPECS: FlagSpecs = {\n json: { type: 'boolean' },\n help: { type: 'boolean' },\n version: { type: 'boolean' },\n};\n\nconst COMMAND_SPECS: Record<string, FlagSpecs> = {\n init: {\n slug: { type: 'string' },\n dir: { type: 'string' },\n force: { type: 'boolean' },\n yes: { type: 'boolean' },\n // Repeatable: each occurrence appends one docs entry (URL or relative\n // path) to the scaffolded ekanos.json.\n docs: { type: 'string', repeatable: true },\n },\n validate: {\n dir: { type: 'string' },\n },\n dev: {\n dir: { type: 'string' },\n port: { type: 'string' },\n host: { type: 'string' },\n force: { type: 'boolean' },\n start: { type: 'boolean' },\n },\n test: {\n dir: { type: 'string' },\n // Skips the vitest JSON-reporter injection and the curated failures\n // appendix, reproducing the pre-appendix raw-passthrough behavior —\n // also the escape hatch if the vitest-detection heuristic ever guesses\n // wrong for a project. See `test-report.ts`.\n raw: { type: 'boolean' },\n },\n login: {\n host: { type: 'string' },\n force: { type: 'boolean' },\n },\n logout: {\n host: { type: 'string' },\n all: { type: 'boolean' },\n },\n whoami: {\n host: { type: 'string' },\n },\n status: {\n host: { type: 'string' },\n dir: { type: 'string' },\n findings: { type: 'boolean' },\n all: { type: 'boolean' },\n watch: { type: 'boolean' },\n },\n sources: {\n host: { type: 'string' },\n dir: { type: 'string' },\n },\n use: {\n host: { type: 'string' },\n dir: { type: 'string' },\n },\n publish: {\n host: { type: 'string' },\n source: { type: 'string' },\n dir: { type: 'string' },\n yes: { type: 'boolean' },\n },\n upgrade: {\n dir: { type: 'string' },\n host: { type: 'string' },\n check: { type: 'boolean' },\n yes: { type: 'boolean' },\n // `--no-verify` is the negation of this — parse-argv's `--no-<bool>`\n // convention (same as `--no-json`/`--no-start`), not a flag of its own.\n verify: { type: 'boolean' },\n },\n};\n\n/** Commands that take a positional argument, and what it means. Every other command is positional-free. */\nconst POSITIONAL_COMMANDS: Record<string, string> = {\n use: 'source-slug',\n};\n\nconst COMMANDS = Object.keys(COMMAND_SPECS);\n\nconst USAGE = [\n 'Usage: ekanos <command> [options]',\n '',\n 'Commands:',\n ' init Scaffold a new integration project into ./<slug> (--slug <slug> --yes)',\n ' validate Validate ekanos.json + the integration definition (--json)',\n ' dev Scaffold .ekanos/harness and run it with your own Next',\n ' test Run the project test script via its package manager (--json).',\n ' For a vitest test script, also renders a curated failures',\n ' appendix from a JSON reporter report; --raw skips that and',\n ' the reporter injection entirely.',\n ' login Authenticate against an Ekanos host in a browser (--host <url>)',\n ' logout Revoke and delete the stored session (--host <url> | --all)',\n ' whoami Print the identity the stored session authenticates as, plus',\n ' a compact list of the sources you hold a seat on',\n \" status Show login state and this project's submissions on the host\",\n ' (--findings for full gate findings, --all to expand other',\n ' integrations in the source, --watch to block until this',\n \" project's current version reaches a gate verdict)\",\n ' sources List every source you hold a developer/source-admin seat on',\n ' use Set this project\\'s publish target (\"ekanos use <source-slug>\")',\n ' publish Validate, pack and submit the integration to an Ekanos host',\n ' upgrade Move this toolchain to a coherent target version, with',\n ' verify-and-rollback (--check --yes --no-verify --host <url>)',\n '',\n 'Keeping the toolchain current:',\n ' validate, dev and publish all check for a newer @ekanos/cli in the',\n ' background (never blocking, silenced by EKANOS_NO_UPDATE_CHECK) and warn',\n ' on stderr when one exists. Run \"ekanos upgrade --check\" any time a',\n ' validate finding smells like version skew (an unrecognized-key error you',\n ' are confident is correct) — it is read-only and safe to run freely. A',\n ' bare \"ekanos upgrade\" MUTATES the project (installs the target versions,',\n ' verifies with validate + the test script, rolls back on any failure).',\n '',\n 'Host resolution (logout, whoami, status, sources, use, publish, upgrade):',\n ' --host, else EKANOS_HOST, else the \"host\" field in ekanos.json, else the',\n ' sole stored login. \"ekanos publish\" saves host and source to ekanos.json.',\n ' \"ekanos login\" never reads ekanos.json for a host (--host, else',\n ' EKANOS_HOST, else the sole stored login) — it is the verb that creates',\n ' credentials, so a committed file must never choose where they go.',\n '',\n 'Publish targeting (see \"ekanos sources\" / \"ekanos use\"):',\n ' publish refuses (FORBIDDEN) a source you hold no seat on. With multiple',\n ' seats and an implicit (ekanos.json) source, a human run asks to confirm',\n ' the target; a scripted/JSON run needs --source or --yes to proceed —',\n ' it never prompts and never guesses.',\n '',\n 'Global options:',\n ' --json Emit a single JSON envelope on stdout (auto-on when piped);',\n ' --no-json forces the human-readable form',\n ' --version Print the CLI version',\n ' --help Show this help',\n].join('\\n');\n\nexport interface RunOptions {\n stdout?: WriteStream;\n stderr?: WriteStream;\n env?: Record<string, string | undefined>;\n cwd?: string;\n /**\n * Test-only override for `publish`'s interactive-confirm gate and `init`\n * / `login`'s clack wizard gate (`canWizard` in `wizard.ts`) — see\n * `PublishArgs` in `commands/publish.ts`. Never set this from real CLI\n * wiring; it exists so a test can exercise the human y/N confirm branch or\n * the wizard flow without a real terminal.\n */\n stdinIsTTY?: boolean;\n confirm?: (question: string) => Promise<boolean>;\n /**\n * Test-only override for `status --watch`'s poll delay — see `StatusArgs`\n * in `commands/status.ts`. Never set this from real CLI wiring; it exists\n * so a test can exercise the poll loop without waiting 30s per iteration.\n */\n sleep?: (ms: number) => Promise<void>;\n /**\n * Test-only override for `status`'s AGE column (\"now\") — see `StatusArgs`\n * in `commands/status.ts`. Never set this from real CLI wiring; it exists\n * so a test can assert an exact `relativeTime` string deterministically.\n */\n now?: Date;\n}\n\n/**\n * The single entry point. Resolves the agent-native envelope once, dispatches\n * to a verb, and guarantees exactly one terminal emission (success or error).\n * Returns the process exit code — `bin.ts` is the only place that calls\n * `process.exit`, so this stays fully testable.\n */\nexport async function run(\n argv: readonly string[],\n options: RunOptions = {},\n): Promise<number> {\n const jsonFlag = scanJsonFlag(argv);\n const ctx = new CliContext({\n jsonFlag,\n stdout: options.stdout,\n stderr: options.stderr,\n env: options.env,\n });\n const cwd = options.cwd ?? process.cwd();\n const env = options.env ?? process.env;\n\n try {\n // `--version` is global and verb-independent, so it is answered before the\n // command is even resolved: `ekanos --version` and `ekanos validate\n // --version` both report the tool's version and exit 0.\n if (scanFlagPresence(argv, 'version')) {\n return emitVersion(ctx);\n }\n\n const { command, rest } = splitCommand(argv);\n\n // Top-level help / no command.\n if (!command) {\n if (scanFlagPresence(rest, 'help') || argv.length === 0) {\n return emitHelp(ctx);\n }\n // A leading token that is not a command (e.g. `--nonsense`).\n ctx.emitClaudeCodeHint({\n tool: 'ekanos',\n error: 'unknown-command',\n commands: COMMANDS,\n usage: USAGE,\n });\n throw usageError(\n `Unknown command or flag \"${argv[0]}\".`,\n `Run one of: ${COMMANDS.join(', ')}. See \"ekanos --help\".`,\n );\n }\n\n if (!COMMANDS.includes(command)) {\n ctx.emitClaudeCodeHint({\n tool: 'ekanos',\n error: 'unknown-command',\n given: command,\n commands: COMMANDS,\n usage: USAGE,\n });\n throw usageError(\n `Unknown command \"${command}\".`,\n `Run one of: ${COMMANDS.join(', ')}. See \"ekanos --help\".`,\n );\n }\n\n const specs: FlagSpecs = { ...GLOBAL_SPECS, ...COMMAND_SPECS[command] };\n const parsed = parseArgv(rest, specs);\n\n if (parsed.flags.help === true) {\n return emitCommandHelp(ctx, command);\n }\n\n const positionalName = POSITIONAL_COMMANDS[command];\n\n if (!positionalName && parsed.positionals.length > 0) {\n throw usageError(\n `\"${command}\" takes no positional arguments (got \"${parsed.positionals[0]}\").`,\n `Pass options as flags, e.g. \"--dir <path>\". See \"ekanos ${command} --help\".`,\n );\n }\n\n if (positionalName && parsed.positionals.length > 1) {\n throw usageError(\n `\"${command}\" takes at most one positional argument (a ` +\n `${positionalName}), got ${parsed.positionals.length}.`,\n `Pass exactly one, e.g. \"ekanos ${command} <${positionalName}>\". ` +\n `See \"ekanos ${command} --help\".`,\n );\n }\n\n const dir = (parsed.flags.dir as string | undefined) ?? cwd;\n\n switch (command) {\n case 'init':\n return await runInit(ctx, {\n slug: parsed.flags.slug as string | undefined,\n dir: parsed.flags.dir as string | undefined,\n cwd,\n force: parsed.flags.force === true,\n yes: parsed.flags.yes === true,\n docs: parsed.flags.docs as string[] | undefined,\n stdinIsTTY: options.stdinIsTTY,\n });\n case 'validate':\n return await runValidate(ctx, { dir, env });\n case 'dev':\n return await runDev(ctx, {\n dir,\n port: parsed.flags.port as string | undefined,\n host: parsed.flags.host as string | undefined,\n force: parsed.flags.force === true,\n start: parsed.flags.start as boolean | undefined,\n env,\n });\n case 'test':\n return await runTest(ctx, {\n dir,\n passthrough: parsed.passthrough,\n raw: parsed.flags.raw === true,\n });\n case 'login':\n return await runLogin(ctx, {\n host: parsed.flags.host as string | undefined,\n force: parsed.flags.force === true,\n cwd,\n env,\n stdinIsTTY: options.stdinIsTTY,\n });\n case 'logout':\n return await runLogout(ctx, {\n host: parsed.flags.host as string | undefined,\n all: parsed.flags.all === true,\n cwd,\n env,\n });\n case 'whoami':\n return await runWhoami(ctx, {\n host: parsed.flags.host as string | undefined,\n cwd,\n env,\n });\n case 'status':\n return await runStatus(ctx, {\n host: parsed.flags.host as string | undefined,\n dir,\n env,\n findings: parsed.flags.findings === true,\n all: parsed.flags.all === true,\n watch: parsed.flags.watch === true,\n sleep: options.sleep,\n now: options.now,\n });\n case 'sources':\n return await runSources(ctx, {\n host: parsed.flags.host as string | undefined,\n dir,\n env,\n });\n case 'use':\n return await runUse(ctx, {\n host: parsed.flags.host as string | undefined,\n dir,\n slug: parsed.positionals[0],\n env,\n });\n case 'publish':\n return await runPublish(ctx, {\n host: parsed.flags.host as string | undefined,\n source: parsed.flags.source as string | undefined,\n dir,\n yes: parsed.flags.yes === true,\n env,\n stdinIsTTY: options.stdinIsTTY,\n confirm: options.confirm,\n });\n case 'upgrade':\n return await runUpgrade(ctx, {\n dir,\n host: parsed.flags.host as string | undefined,\n env,\n check: parsed.flags.check === true,\n yes: parsed.flags.yes === true,\n noVerify: parsed.flags.verify === false,\n stdinIsTTY: options.stdinIsTTY,\n confirm: options.confirm,\n });\n default:\n // Unreachable — COMMANDS gate above — but keeps the switch total.\n throw usageError(\n `Unknown command \"${command}\".`,\n `Run one of: ${COMMANDS.join(', ')}.`,\n );\n }\n } catch (error) {\n return ctx.fail(toCliError(error));\n }\n}\n\n/**\n * Presence of `--json` (or `--no-json`) BEFORE the first `--` passthrough.\n *\n * Tri-state on purpose. `undefined` means \"the user expressed no preference\",\n * which is what lets the context fall back to inferring the mode from a\n * non-TTY stdout or CLAUDECODE. Collapsing `--no-json` to `false` here would\n * make it indistinguishable from absence, and the flag could then never turn\n * JSON mode OFF in exactly the cases it exists for.\n */\nfunction scanJsonFlag(argv: readonly string[]): boolean | undefined {\n for (const token of argv) {\n if (token === '--') break;\n if (token === '--json') return true;\n if (token === '--no-json') return false;\n }\n return undefined;\n}\n\nfunction scanFlagPresence(argv: readonly string[], name: string): boolean {\n for (const token of argv) {\n if (token === '--') break;\n if (token === `--${name}`) return true;\n }\n return false;\n}\n\n/**\n * Split argv into the command (first bare token before `--`) and the rest\n * (everything else, order preserved, including global flags and the `--`\n * passthrough). Global flags are all booleans, so any bare token is the\n * command — no value-consumption ambiguity.\n */\nfunction splitCommand(argv: readonly string[]): {\n command: string | null;\n rest: string[];\n} {\n for (let i = 0; i < argv.length; i++) {\n const token = argv[i]!;\n if (token === '--') break;\n if (!token.startsWith('-')) {\n return {\n command: token,\n rest: [...argv.slice(0, i), ...argv.slice(i + 1)],\n };\n }\n }\n return { command: null, rest: [...argv] };\n}\n\n/**\n * The CLI's own version, read from the package manifest at runtime rather than\n * baked in by the build — one source of truth, so a release bump cannot leave\n * `--version` reporting a stale number. The path resolves identically from\n * `src/index.ts` and from the emitted `dist/index.js`, both of which sit one\n * directory below the package root.\n */\nfunction readVersion(): string {\n const manifest = new URL('../package.json', import.meta.url);\n const parsed: unknown = JSON.parse(fs.readFileSync(manifest, 'utf8'));\n\n return typeof parsed === 'object' &&\n parsed !== null &&\n 'version' in parsed &&\n typeof parsed.version === 'string'\n ? parsed.version\n : '0.0.0';\n}\n\nfunction emitVersion(ctx: CliContext): number {\n const version = readVersion();\n\n return ctx.succeed({ name: 'ekanos', version }, version);\n}\n\nfunction emitHelp(ctx: CliContext): number {\n ctx.emitClaudeCodeHint({\n tool: 'ekanos',\n commands: COMMANDS,\n usage: USAGE,\n exitCodes: EXIT_CODES,\n });\n return ctx.succeed({ usage: USAGE, commands: COMMANDS }, USAGE);\n}\n\nfunction emitCommandHelp(ctx: CliContext, command: string): number {\n const specs = COMMAND_SPECS[command] ?? {};\n const flags = Object.keys(specs).map((name) => `--${name}`);\n const help = `Usage: ekanos ${command} [${flags.join('] [')}]`;\n ctx.emitClaudeCodeHint({ tool: 'ekanos', command, flags });\n return ctx.succeed({ command, flags }, help);\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\n\nimport { runDev } from './commands/dev';\nimport { runInit } from './commands/init';\nimport { runLogin } from './commands/login';\nimport { runLogout } from './commands/logout';\nimport { runPublish } from './commands/publish';\nimport { runSources } from './commands/sources';\nimport { runStatus } from './commands/status';\nimport { runTest } from './commands/test';\nimport { runUpgrade } from './commands/upgrade';\nimport { runUse } from './commands/use';\nimport { runValidate } from './commands/validate';\nimport { runWhoami } from './commands/whoami';\nimport { CliContext, type WriteStream } from './context';\nimport { SDK_DOCS_URL, docsPage } from './docs-links';\nimport { toCliError, usageError } from './errors';\nimport { EXIT_CODES } from './exit-codes';\nimport { type FlagSpecs, parseArgv } from './parse-argv';\n\nexport { CliContext } from './context';\nexport { EXIT_CODES, ERROR_CODES } from './exit-codes';\nexport type { ExitCode, ErrorCode } from './exit-codes';\n\nconst GLOBAL_SPECS: FlagSpecs = {\n json: { type: 'boolean' },\n help: { type: 'boolean' },\n version: { type: 'boolean' },\n};\n\nconst COMMAND_SPECS: Record<string, FlagSpecs> = {\n init: {\n slug: { type: 'string' },\n dir: { type: 'string' },\n force: { type: 'boolean' },\n yes: { type: 'boolean' },\n // Repeatable: each occurrence appends one docs entry (URL or relative\n // path) to the scaffolded ekanos.json.\n docs: { type: 'string', repeatable: true },\n },\n validate: {\n dir: { type: 'string' },\n },\n dev: {\n dir: { type: 'string' },\n port: { type: 'string' },\n host: { type: 'string' },\n force: { type: 'boolean' },\n start: { type: 'boolean' },\n },\n test: {\n dir: { type: 'string' },\n // Skips the vitest JSON-reporter injection and the curated failures\n // appendix, reproducing the pre-appendix raw-passthrough behavior —\n // also the escape hatch if the vitest-detection heuristic ever guesses\n // wrong for a project. See `test-report.ts`.\n raw: { type: 'boolean' },\n },\n login: {\n host: { type: 'string' },\n force: { type: 'boolean' },\n },\n logout: {\n host: { type: 'string' },\n all: { type: 'boolean' },\n },\n whoami: {\n host: { type: 'string' },\n },\n status: {\n host: { type: 'string' },\n dir: { type: 'string' },\n findings: { type: 'boolean' },\n all: { type: 'boolean' },\n watch: { type: 'boolean' },\n },\n sources: {\n host: { type: 'string' },\n dir: { type: 'string' },\n },\n use: {\n host: { type: 'string' },\n dir: { type: 'string' },\n },\n publish: {\n host: { type: 'string' },\n source: { type: 'string' },\n dir: { type: 'string' },\n yes: { type: 'boolean' },\n },\n upgrade: {\n dir: { type: 'string' },\n host: { type: 'string' },\n check: { type: 'boolean' },\n yes: { type: 'boolean' },\n // `--no-verify` is the negation of this — parse-argv's `--no-<bool>`\n // convention (same as `--no-json`/`--no-start`), not a flag of its own.\n verify: { type: 'boolean' },\n },\n};\n\n/** Commands that take a positional argument, and what it means. Every other command is positional-free. */\nconst POSITIONAL_COMMANDS: Record<string, string> = {\n use: 'source-slug',\n};\n\nconst COMMANDS = Object.keys(COMMAND_SPECS);\n\nconst USAGE = [\n 'Usage: ekanos <command> [options]',\n '',\n 'Commands:',\n ' init Scaffold a new integration project into ./<slug> (--slug <slug> --yes)',\n ' validate Validate ekanos.json + the integration definition (--json)',\n ' dev Scaffold .ekanos/harness and run it with your own Next',\n ' test Run the project test script via its package manager (--json).',\n ' For a vitest test script, also renders a curated failures',\n ' appendix from a JSON reporter report; --raw skips that and',\n ' the reporter injection entirely.',\n ' login Authenticate against an Ekanos host in a browser (--host <url>)',\n ' logout Revoke and delete the stored session (--host <url> | --all)',\n ' whoami Print the identity the stored session authenticates as, plus',\n ' a compact list of the sources you hold a seat on',\n \" status Show login state and this project's submissions on the host\",\n ' (--findings for full gate findings, --all to expand other',\n ' integrations in the source, --watch to block until this',\n \" project's current version reaches a gate verdict)\",\n ' sources List every source you hold a developer/source-admin seat on',\n ' use Set this project\\'s publish target (\"ekanos use <source-slug>\")',\n ' publish Validate, pack and submit the integration to an Ekanos host',\n ' upgrade Move this toolchain to a coherent target version, with',\n ' verify-and-rollback (--check --yes --no-verify --host <url>)',\n '',\n 'Keeping the toolchain current:',\n ' validate, dev and publish all check for a newer @ekanos/cli in the',\n ' background (never blocking, silenced by EKANOS_NO_UPDATE_CHECK) and warn',\n ' on stderr when one exists. Run \"ekanos upgrade --check\" any time a',\n ' validate finding smells like version skew (an unrecognized-key error you',\n ' are confident is correct) — it is read-only and safe to run freely. A',\n ' bare \"ekanos upgrade\" MUTATES the project (installs the target versions,',\n ' verifies with validate + the test script, rolls back on any failure).',\n '',\n 'Host resolution (logout, whoami, status, sources, use, publish, upgrade):',\n ' --host, else EKANOS_HOST, else the \"host\" field in ekanos.json, else the',\n ' sole stored login. \"ekanos publish\" saves host and source to ekanos.json.',\n ' \"ekanos login\" never reads ekanos.json for a host (--host, else',\n ' EKANOS_HOST, else the sole stored login) — it is the verb that creates',\n ' credentials, so a committed file must never choose where they go.',\n '',\n 'Publish targeting (see \"ekanos sources\" / \"ekanos use\"):',\n ' publish refuses (FORBIDDEN) a source you hold no seat on. With multiple',\n ' seats and an implicit (ekanos.json) source, a human run asks to confirm',\n ' the target; a scripted/JSON run needs --source or --yes to proceed —',\n ' it never prompts and never guesses.',\n '',\n 'Global options:',\n ' --json Emit a single JSON envelope on stdout (auto-on when piped);',\n ' --no-json forces the human-readable form',\n ' --version Print the CLI version',\n ' --help Show this help',\n].join('\\n');\n\nexport interface RunOptions {\n stdout?: WriteStream;\n stderr?: WriteStream;\n env?: Record<string, string | undefined>;\n cwd?: string;\n /**\n * Test-only override for `publish`'s interactive-confirm gate and `init`\n * / `login`'s clack wizard gate (`canWizard` in `wizard.ts`) — see\n * `PublishArgs` in `commands/publish.ts`. Never set this from real CLI\n * wiring; it exists so a test can exercise the human y/N confirm branch or\n * the wizard flow without a real terminal.\n */\n stdinIsTTY?: boolean;\n confirm?: (question: string) => Promise<boolean>;\n /**\n * Test-only override for `status --watch`'s poll delay — see `StatusArgs`\n * in `commands/status.ts`. Never set this from real CLI wiring; it exists\n * so a test can exercise the poll loop without waiting 30s per iteration.\n */\n sleep?: (ms: number) => Promise<void>;\n /**\n * Test-only override for `status`'s AGE column (\"now\") — see `StatusArgs`\n * in `commands/status.ts`. Never set this from real CLI wiring; it exists\n * so a test can assert an exact `relativeTime` string deterministically.\n */\n now?: Date;\n}\n\n/**\n * The single entry point. Resolves the agent-native envelope once, dispatches\n * to a verb, and guarantees exactly one terminal emission (success or error).\n * Returns the process exit code — `bin.ts` is the only place that calls\n * `process.exit`, so this stays fully testable.\n */\nexport async function run(\n argv: readonly string[],\n options: RunOptions = {},\n): Promise<number> {\n const jsonFlag = scanJsonFlag(argv);\n const ctx = new CliContext({\n jsonFlag,\n stdout: options.stdout,\n stderr: options.stderr,\n env: options.env,\n });\n const cwd = options.cwd ?? process.cwd();\n const env = options.env ?? process.env;\n\n try {\n // `--version` is global and verb-independent, so it is answered before the\n // command is even resolved: `ekanos --version` and `ekanos validate\n // --version` both report the tool's version and exit 0.\n if (scanFlagPresence(argv, 'version')) {\n return emitVersion(ctx);\n }\n\n const { command, rest } = splitCommand(argv);\n\n // Top-level help / no command.\n if (!command) {\n if (scanFlagPresence(rest, 'help') || argv.length === 0) {\n return emitHelp(ctx);\n }\n // A leading token that is not a command (e.g. `--nonsense`).\n ctx.emitClaudeCodeHint({\n tool: 'ekanos',\n error: 'unknown-command',\n commands: COMMANDS,\n usage: USAGE,\n });\n throw usageError(\n `Unknown command or flag \"${argv[0]}\".`,\n `Run one of: ${COMMANDS.join(', ')}. See \"ekanos --help\".`,\n );\n }\n\n if (!COMMANDS.includes(command)) {\n ctx.emitClaudeCodeHint({\n tool: 'ekanos',\n error: 'unknown-command',\n given: command,\n commands: COMMANDS,\n usage: USAGE,\n });\n throw usageError(\n `Unknown command \"${command}\".`,\n `Run one of: ${COMMANDS.join(', ')}. See \"ekanos --help\".`,\n );\n }\n\n const specs: FlagSpecs = { ...GLOBAL_SPECS, ...COMMAND_SPECS[command] };\n const parsed = parseArgv(rest, specs);\n\n if (parsed.flags.help === true) {\n return emitCommandHelp(ctx, command);\n }\n\n const positionalName = POSITIONAL_COMMANDS[command];\n\n if (!positionalName && parsed.positionals.length > 0) {\n throw usageError(\n `\"${command}\" takes no positional arguments (got \"${parsed.positionals[0]}\").`,\n `Pass options as flags, e.g. \"--dir <path>\". See \"ekanos ${command} --help\".`,\n );\n }\n\n if (positionalName && parsed.positionals.length > 1) {\n throw usageError(\n `\"${command}\" takes at most one positional argument (a ` +\n `${positionalName}), got ${parsed.positionals.length}.`,\n `Pass exactly one, e.g. \"ekanos ${command} <${positionalName}>\". ` +\n `See \"ekanos ${command} --help\".`,\n );\n }\n\n const dir = (parsed.flags.dir as string | undefined) ?? cwd;\n\n switch (command) {\n case 'init':\n return await runInit(ctx, {\n slug: parsed.flags.slug as string | undefined,\n dir: parsed.flags.dir as string | undefined,\n cwd,\n force: parsed.flags.force === true,\n yes: parsed.flags.yes === true,\n docs: parsed.flags.docs as string[] | undefined,\n stdinIsTTY: options.stdinIsTTY,\n });\n case 'validate':\n return await runValidate(ctx, { dir, env });\n case 'dev':\n return await runDev(ctx, {\n dir,\n port: parsed.flags.port as string | undefined,\n host: parsed.flags.host as string | undefined,\n force: parsed.flags.force === true,\n start: parsed.flags.start as boolean | undefined,\n env,\n });\n case 'test':\n return await runTest(ctx, {\n dir,\n passthrough: parsed.passthrough,\n raw: parsed.flags.raw === true,\n });\n case 'login':\n return await runLogin(ctx, {\n host: parsed.flags.host as string | undefined,\n force: parsed.flags.force === true,\n cwd,\n env,\n stdinIsTTY: options.stdinIsTTY,\n });\n case 'logout':\n return await runLogout(ctx, {\n host: parsed.flags.host as string | undefined,\n all: parsed.flags.all === true,\n cwd,\n env,\n });\n case 'whoami':\n return await runWhoami(ctx, {\n host: parsed.flags.host as string | undefined,\n cwd,\n env,\n });\n case 'status':\n return await runStatus(ctx, {\n host: parsed.flags.host as string | undefined,\n dir,\n env,\n findings: parsed.flags.findings === true,\n all: parsed.flags.all === true,\n watch: parsed.flags.watch === true,\n sleep: options.sleep,\n now: options.now,\n });\n case 'sources':\n return await runSources(ctx, {\n host: parsed.flags.host as string | undefined,\n dir,\n env,\n });\n case 'use':\n return await runUse(ctx, {\n host: parsed.flags.host as string | undefined,\n dir,\n slug: parsed.positionals[0],\n env,\n });\n case 'publish':\n return await runPublish(ctx, {\n host: parsed.flags.host as string | undefined,\n source: parsed.flags.source as string | undefined,\n dir,\n yes: parsed.flags.yes === true,\n env,\n stdinIsTTY: options.stdinIsTTY,\n confirm: options.confirm,\n });\n case 'upgrade':\n return await runUpgrade(ctx, {\n dir,\n host: parsed.flags.host as string | undefined,\n env,\n check: parsed.flags.check === true,\n yes: parsed.flags.yes === true,\n noVerify: parsed.flags.verify === false,\n stdinIsTTY: options.stdinIsTTY,\n confirm: options.confirm,\n });\n default:\n // Unreachable — COMMANDS gate above — but keeps the switch total.\n throw usageError(\n `Unknown command \"${command}\".`,\n `Run one of: ${COMMANDS.join(', ')}.`,\n );\n }\n } catch (error) {\n return ctx.fail(toCliError(error));\n }\n}\n\n/**\n * Presence of `--json` (or `--no-json`) BEFORE the first `--` passthrough.\n *\n * Tri-state on purpose. `undefined` means \"the user expressed no preference\",\n * which is what lets the context fall back to inferring the mode from a\n * non-TTY stdout or CLAUDECODE. Collapsing `--no-json` to `false` here would\n * make it indistinguishable from absence, and the flag could then never turn\n * JSON mode OFF in exactly the cases it exists for.\n */\nfunction scanJsonFlag(argv: readonly string[]): boolean | undefined {\n for (const token of argv) {\n if (token === '--') break;\n if (token === '--json') return true;\n if (token === '--no-json') return false;\n }\n return undefined;\n}\n\nfunction scanFlagPresence(argv: readonly string[], name: string): boolean {\n for (const token of argv) {\n if (token === '--') break;\n if (token === `--${name}`) return true;\n }\n return false;\n}\n\n/**\n * Split argv into the command (first bare token before `--`) and the rest\n * (everything else, order preserved, including global flags and the `--`\n * passthrough). Global flags are all booleans, so any bare token is the\n * command — no value-consumption ambiguity.\n */\nfunction splitCommand(argv: readonly string[]): {\n command: string | null;\n rest: string[];\n} {\n for (let i = 0; i < argv.length; i++) {\n const token = argv[i]!;\n if (token === '--') break;\n if (!token.startsWith('-')) {\n return {\n command: token,\n rest: [...argv.slice(0, i), ...argv.slice(i + 1)],\n };\n }\n }\n return { command: null, rest: [...argv] };\n}\n\n/**\n * The CLI's own version, read from the package manifest at runtime rather than\n * baked in by the build — one source of truth, so a release bump cannot leave\n * `--version` reporting a stale number. The path resolves identically from\n * `src/index.ts` and from the emitted `dist/index.js`, both of which sit one\n * directory below the package root.\n */\nfunction readVersion(): string {\n const manifest = new URL('../package.json', import.meta.url);\n const parsed: unknown = JSON.parse(fs.readFileSync(manifest, 'utf8'));\n\n return typeof parsed === 'object' &&\n parsed !== null &&\n 'version' in parsed &&\n typeof parsed.version === 'string'\n ? parsed.version\n : '0.0.0';\n}\n\nfunction emitVersion(ctx: CliContext): number {\n const version = readVersion();\n\n return ctx.succeed({ name: 'ekanos', version }, version);\n}\n\nconst DOCS_FOOTER = `\\nDocs: ${SDK_DOCS_URL} (commands: ${docsPage('cli/commands')})`;\n\nfunction emitHelp(ctx: CliContext): number {\n ctx.emitClaudeCodeHint({\n tool: 'ekanos',\n commands: COMMANDS,\n usage: USAGE,\n exitCodes: EXIT_CODES,\n });\n return ctx.succeed(\n { usage: USAGE, commands: COMMANDS, docs: SDK_DOCS_URL },\n `${USAGE}${DOCS_FOOTER}`,\n );\n}\n\nfunction emitCommandHelp(ctx: CliContext, command: string): number {\n const specs = COMMAND_SPECS[command] ?? {};\n const flags = Object.keys(specs).map((name) => `--${name}`);\n const help = `Usage: ekanos ${command} [${flags.join('] [')}]`;\n ctx.emitClaudeCodeHint({ tool: 'ekanos', command, flags });\n return ctx.succeed({ command, flags }, help);\n}\n"]}
@@ -1,6 +1,7 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
3
  import { compareVersions } from './compatibility.js';
4
+ import { SCHEMA_SKEW_DOCS } from './docs-links.js';
4
5
  import { preconditionError } from './errors.js';
5
6
  import { readInstalledPackageManifest } from './harness-scaffold.js';
6
7
  import { templatesDir } from './templates.js';
@@ -150,7 +151,7 @@ export function assertSchemaNotSkewed(ctx, projectDir) {
150
151
  'Upgrade the CLI: run "ekanos upgrade" (it resolves a coherent target ' +
151
152
  'version set), or pin it explicitly by hand with ' +
152
153
  '"npm i -D @ekanos/cli@latest". Then re-run "ekanos validate" or ' +
153
- '"ekanos publish".');
154
+ '"ekanos publish".', SCHEMA_SKEW_DOCS);
154
155
  }
155
156
  if (result.status === 'project-older') {
156
157
  ctx.warn(`ekanos: the installed ${INTEGRATION_SCHEMA_PACKAGE}@${result.projectSchemaVersion} ` +
@@ -1 +1 @@
1
- {"version":3,"file":"schema-skew.js","sourceRoot":"","sources":["../src/schema-skew.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { compareVersions } from './compatibility';\nimport type { CliContext } from './context';\nimport { preconditionError } from './errors';\nimport { readInstalledPackageManifest } from './harness-scaffold';\nimport { templatesDir } from './templates';\n\n/**\n * The schema-skew gate: `validate` and `publish` parse a partner's\n * declaration with the REAL zod schemas from `@ekanos/integration-schema`,\n * pinned to the EXACT version this build of `@ekanos/cli` was built against.\n * A partner project can end up with a NEWER schema installed than the one\n * this CLI validates with — `@ekanos/sdk` (or the project directly) got\n * upgraded, the CLI did not — and that state is not merely \"a little behind\".\n * It is actively WRONG: a field the newer schema added parses here as an\n * `unrecognized_keys` zod issue, and the generic \"host-assigned fields are\n * never partner-authorable\" hint tells the partner to delete code that is\n * completely correct. That happened to a real partner. Failing loudly here\n * beats answering falsely, so this gate runs BEFORE validate or publish do\n * any of their own work — an incompatible pair produces no findings at all,\n * rather than confidently wrong ones.\n *\n * The opposite skew — the project's schema is OLDER than the CLI's — is not\n * dangerous in the same way (the CLI's validation is still correct; the\n * partner just cannot use fields the CLI would recognize yet), so it warns\n * instead of blocking.\n */\n\nexport const INTEGRATION_SCHEMA_PACKAGE = '@ekanos/integration-schema';\nconst SDK_PACKAGE = '@ekanos/sdk';\n\nconst SEMVER_LIKE = /^\\d+\\.\\d+\\.\\d+/;\n\nfunction looksLikeSemver(value: unknown): value is string {\n return typeof value === 'string' && SEMVER_LIKE.test(value);\n}\n\nfunction readJsonFile(filePath: string): unknown {\n try {\n return JSON.parse(fs.readFileSync(filePath, 'utf8'));\n } catch {\n return null;\n }\n}\n\n/**\n * The version of `@ekanos/integration-schema` THIS CLI validates with.\n *\n * Two tiers, tried in order:\n *\n * 1. The exact version declared in this CLI's own `package.json`\n * dependencies. In a published build this is a real pinned semver — `pnpm\n * publish` rewrites the monorepo's `workspace:*` protocol to the exact\n * version of the linked package at publish time, which is exactly the\n * \"declare it explicitly\" contract `compatibility.ts` already commits to\n * for the shell contract.\n * 2. Failing that (this repo's own dev checkout, where the dependency is\n * still the literal `workspace:*` string), fall back to whatever\n * `@ekanos/integration-schema` this CLI itself resolves on disk — the\n * package a workspace install actually links next to the CLI.\n *\n * Returns null — never throws — when neither resolves, which degrades the\n * gate to a no-op exactly like \"the project has no node_modules\" does. A\n * partner's validate/publish must never fail because THIS lookup couldn't\n * find an answer.\n */\nexport function bundledSchemaVersion(): string | null {\n const cliRoot = path.join(templatesDir(), '..');\n\n const ownManifest = readJsonFile(path.join(cliRoot, 'package.json'));\n const declared =\n typeof ownManifest === 'object' && ownManifest !== null\n ? ((ownManifest as { dependencies?: Record<string, unknown> })\n .dependencies ?? {})[INTEGRATION_SCHEMA_PACKAGE]\n : undefined;\n if (looksLikeSemver(declared)) return declared;\n\n const nested = readJsonFile(\n path.join(\n cliRoot,\n 'node_modules',\n '@ekanos',\n 'integration-schema',\n 'package.json',\n ),\n );\n const nestedVersion =\n typeof nested === 'object' && nested !== null\n ? (nested as { version?: unknown }).version\n : undefined;\n\n return looksLikeSemver(nestedVersion) ? nestedVersion : null;\n}\n\nexport interface SchemaSkewResult {\n status: 'ok' | 'project-newer' | 'project-older' | 'unresolvable';\n cliSchemaVersion: string | null;\n projectSchemaVersion: string | null;\n}\n\n/**\n * What the PROJECT resolves for `@ekanos/integration-schema`, compared\n * against what this CLI bundles.\n *\n * The project rarely depends on the schema package directly — it is a\n * transitive dependency of `@ekanos/sdk` — so two candidates are considered,\n * per the task: a direct top-level resolution (works when a package manager\n * hoists it, or when a partner added it explicitly) AND the version\n * `@ekanos/sdk` itself declares a dependency on (its package.json is always a\n * direct project dependency, so it always resolves at the top level even when\n * the schema package's own copy does not). The greater of whichever resolves\n * is what \"the project's installed schema\" means here — either could be the\n * copy Node actually loads, and closing the gate on the newer of the two is\n * the safe direction: it only ever asks a partner to upgrade a CLI that is\n * genuinely behind.\n */\nexport function detectSchemaSkew(projectDir: string): SchemaSkewResult {\n const cliSchemaVersion = bundledSchemaVersion();\n if (!cliSchemaVersion) {\n return {\n status: 'unresolvable',\n cliSchemaVersion: null,\n projectSchemaVersion: null,\n };\n }\n\n const direct = readInstalledPackageManifest(\n projectDir,\n INTEGRATION_SCHEMA_PACKAGE,\n );\n const sdk = readInstalledPackageManifest(projectDir, SDK_PACKAGE);\n const sdkDeclaredSchema =\n sdk && typeof sdk.manifest === 'object' && sdk.manifest !== null\n ? ((sdk.manifest as { dependencies?: Record<string, unknown> })\n .dependencies ?? {})[INTEGRATION_SCHEMA_PACKAGE]\n : undefined;\n\n const candidates = [direct?.version, sdkDeclaredSchema].filter(\n looksLikeSemver,\n );\n\n if (candidates.length === 0) {\n return {\n status: 'unresolvable',\n cliSchemaVersion,\n projectSchemaVersion: null,\n };\n }\n\n const projectSchemaVersion = candidates.reduce((max, candidate) =>\n compareVersions(candidate, max) > 0 ? candidate : max,\n );\n\n const cmp = compareVersions(projectSchemaVersion, cliSchemaVersion);\n\n return {\n status: cmp > 0 ? 'project-newer' : cmp < 0 ? 'project-older' : 'ok',\n cliSchemaVersion,\n projectSchemaVersion,\n };\n}\n\n/**\n * The gate itself. Call before `validate`/`publish` do anything else.\n *\n * Throws PRECONDITION_FAILED (never a validation finding — the definition\n * cannot even be trusted to parse correctly in this state) when the project's\n * schema is newer. Warns on stderr and returns normally for every other\n * status, including the two that mean \"nothing to say\" (`ok`, `unresolvable`)\n * so callers can uniformly branch on the returned result to decide whether to\n * annotate their own JSON envelope.\n */\nexport function assertSchemaNotSkewed(\n ctx: CliContext,\n projectDir: string,\n): SchemaSkewResult {\n const result = detectSchemaSkew(projectDir);\n\n if (result.status === 'project-newer') {\n throw preconditionError(\n `The project's ${INTEGRATION_SCHEMA_PACKAGE}@${result.projectSchemaVersion} ` +\n `is newer than ${result.cliSchemaVersion}, the version this ` +\n `@ekanos/cli build validates with. Findings from \"ekanos validate\" ` +\n 'are UNRELIABLE in this state: a field the newer schema added ' +\n 'parses here as an unrecognized key, and the usual hint for that ' +\n 'tells you to delete code that is actually correct.',\n // NEVER interpolate the schema version into a \"@ekanos/cli@<version>\"\n // install spec — the two version completely independently (this build\n // is cli 0.1.6 validating against schema 0.1.4). \"ekanos upgrade\"\n // resolves the actual coherent set; the manual fallback can only ever\n // be \"latest\", never a computed number.\n 'Upgrade the CLI: run \"ekanos upgrade\" (it resolves a coherent target ' +\n 'version set), or pin it explicitly by hand with ' +\n '\"npm i -D @ekanos/cli@latest\". Then re-run \"ekanos validate\" or ' +\n '\"ekanos publish\".',\n );\n }\n\n if (result.status === 'project-older') {\n ctx.warn(\n `ekanos: the installed ${INTEGRATION_SCHEMA_PACKAGE}@${result.projectSchemaVersion} ` +\n `is older than ${result.cliSchemaVersion}, the version this CLI ` +\n 'validates with — some fields this CLI recognizes may not be usable ' +\n 'yet. Run \"ekanos upgrade\" to bring the toolchain in sync.',\n );\n }\n\n return result;\n}\n"]}
1
+ {"version":3,"file":"schema-skew.js","sourceRoot":"","sources":["../src/schema-skew.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { compareVersions } from './compatibility';\nimport type { CliContext } from './context';\nimport { SCHEMA_SKEW_DOCS } from './docs-links';\nimport { preconditionError } from './errors';\nimport { readInstalledPackageManifest } from './harness-scaffold';\nimport { templatesDir } from './templates';\n\n/**\n * The schema-skew gate: `validate` and `publish` parse a partner's\n * declaration with the REAL zod schemas from `@ekanos/integration-schema`,\n * pinned to the EXACT version this build of `@ekanos/cli` was built against.\n * A partner project can end up with a NEWER schema installed than the one\n * this CLI validates with — `@ekanos/sdk` (or the project directly) got\n * upgraded, the CLI did not — and that state is not merely \"a little behind\".\n * It is actively WRONG: a field the newer schema added parses here as an\n * `unrecognized_keys` zod issue, and the generic \"host-assigned fields are\n * never partner-authorable\" hint tells the partner to delete code that is\n * completely correct. That happened to a real partner. Failing loudly here\n * beats answering falsely, so this gate runs BEFORE validate or publish do\n * any of their own work — an incompatible pair produces no findings at all,\n * rather than confidently wrong ones.\n *\n * The opposite skew — the project's schema is OLDER than the CLI's — is not\n * dangerous in the same way (the CLI's validation is still correct; the\n * partner just cannot use fields the CLI would recognize yet), so it warns\n * instead of blocking.\n */\n\nexport const INTEGRATION_SCHEMA_PACKAGE = '@ekanos/integration-schema';\nconst SDK_PACKAGE = '@ekanos/sdk';\n\nconst SEMVER_LIKE = /^\\d+\\.\\d+\\.\\d+/;\n\nfunction looksLikeSemver(value: unknown): value is string {\n return typeof value === 'string' && SEMVER_LIKE.test(value);\n}\n\nfunction readJsonFile(filePath: string): unknown {\n try {\n return JSON.parse(fs.readFileSync(filePath, 'utf8'));\n } catch {\n return null;\n }\n}\n\n/**\n * The version of `@ekanos/integration-schema` THIS CLI validates with.\n *\n * Two tiers, tried in order:\n *\n * 1. The exact version declared in this CLI's own `package.json`\n * dependencies. In a published build this is a real pinned semver — `pnpm\n * publish` rewrites the monorepo's `workspace:*` protocol to the exact\n * version of the linked package at publish time, which is exactly the\n * \"declare it explicitly\" contract `compatibility.ts` already commits to\n * for the shell contract.\n * 2. Failing that (this repo's own dev checkout, where the dependency is\n * still the literal `workspace:*` string), fall back to whatever\n * `@ekanos/integration-schema` this CLI itself resolves on disk — the\n * package a workspace install actually links next to the CLI.\n *\n * Returns null — never throws — when neither resolves, which degrades the\n * gate to a no-op exactly like \"the project has no node_modules\" does. A\n * partner's validate/publish must never fail because THIS lookup couldn't\n * find an answer.\n */\nexport function bundledSchemaVersion(): string | null {\n const cliRoot = path.join(templatesDir(), '..');\n\n const ownManifest = readJsonFile(path.join(cliRoot, 'package.json'));\n const declared =\n typeof ownManifest === 'object' && ownManifest !== null\n ? ((ownManifest as { dependencies?: Record<string, unknown> })\n .dependencies ?? {})[INTEGRATION_SCHEMA_PACKAGE]\n : undefined;\n if (looksLikeSemver(declared)) return declared;\n\n const nested = readJsonFile(\n path.join(\n cliRoot,\n 'node_modules',\n '@ekanos',\n 'integration-schema',\n 'package.json',\n ),\n );\n const nestedVersion =\n typeof nested === 'object' && nested !== null\n ? (nested as { version?: unknown }).version\n : undefined;\n\n return looksLikeSemver(nestedVersion) ? nestedVersion : null;\n}\n\nexport interface SchemaSkewResult {\n status: 'ok' | 'project-newer' | 'project-older' | 'unresolvable';\n cliSchemaVersion: string | null;\n projectSchemaVersion: string | null;\n}\n\n/**\n * What the PROJECT resolves for `@ekanos/integration-schema`, compared\n * against what this CLI bundles.\n *\n * The project rarely depends on the schema package directly — it is a\n * transitive dependency of `@ekanos/sdk` — so two candidates are considered,\n * per the task: a direct top-level resolution (works when a package manager\n * hoists it, or when a partner added it explicitly) AND the version\n * `@ekanos/sdk` itself declares a dependency on (its package.json is always a\n * direct project dependency, so it always resolves at the top level even when\n * the schema package's own copy does not). The greater of whichever resolves\n * is what \"the project's installed schema\" means here — either could be the\n * copy Node actually loads, and closing the gate on the newer of the two is\n * the safe direction: it only ever asks a partner to upgrade a CLI that is\n * genuinely behind.\n */\nexport function detectSchemaSkew(projectDir: string): SchemaSkewResult {\n const cliSchemaVersion = bundledSchemaVersion();\n if (!cliSchemaVersion) {\n return {\n status: 'unresolvable',\n cliSchemaVersion: null,\n projectSchemaVersion: null,\n };\n }\n\n const direct = readInstalledPackageManifest(\n projectDir,\n INTEGRATION_SCHEMA_PACKAGE,\n );\n const sdk = readInstalledPackageManifest(projectDir, SDK_PACKAGE);\n const sdkDeclaredSchema =\n sdk && typeof sdk.manifest === 'object' && sdk.manifest !== null\n ? ((sdk.manifest as { dependencies?: Record<string, unknown> })\n .dependencies ?? {})[INTEGRATION_SCHEMA_PACKAGE]\n : undefined;\n\n const candidates = [direct?.version, sdkDeclaredSchema].filter(\n looksLikeSemver,\n );\n\n if (candidates.length === 0) {\n return {\n status: 'unresolvable',\n cliSchemaVersion,\n projectSchemaVersion: null,\n };\n }\n\n const projectSchemaVersion = candidates.reduce((max, candidate) =>\n compareVersions(candidate, max) > 0 ? candidate : max,\n );\n\n const cmp = compareVersions(projectSchemaVersion, cliSchemaVersion);\n\n return {\n status: cmp > 0 ? 'project-newer' : cmp < 0 ? 'project-older' : 'ok',\n cliSchemaVersion,\n projectSchemaVersion,\n };\n}\n\n/**\n * The gate itself. Call before `validate`/`publish` do anything else.\n *\n * Throws PRECONDITION_FAILED (never a validation finding — the definition\n * cannot even be trusted to parse correctly in this state) when the project's\n * schema is newer. Warns on stderr and returns normally for every other\n * status, including the two that mean \"nothing to say\" (`ok`, `unresolvable`)\n * so callers can uniformly branch on the returned result to decide whether to\n * annotate their own JSON envelope.\n */\nexport function assertSchemaNotSkewed(\n ctx: CliContext,\n projectDir: string,\n): SchemaSkewResult {\n const result = detectSchemaSkew(projectDir);\n\n if (result.status === 'project-newer') {\n throw preconditionError(\n `The project's ${INTEGRATION_SCHEMA_PACKAGE}@${result.projectSchemaVersion} ` +\n `is newer than ${result.cliSchemaVersion}, the version this ` +\n `@ekanos/cli build validates with. Findings from \"ekanos validate\" ` +\n 'are UNRELIABLE in this state: a field the newer schema added ' +\n 'parses here as an unrecognized key, and the usual hint for that ' +\n 'tells you to delete code that is actually correct.',\n // NEVER interpolate the schema version into a \"@ekanos/cli@<version>\"\n // install spec — the two version completely independently (this build\n // is cli 0.1.6 validating against schema 0.1.4). \"ekanos upgrade\"\n // resolves the actual coherent set; the manual fallback can only ever\n // be \"latest\", never a computed number.\n 'Upgrade the CLI: run \"ekanos upgrade\" (it resolves a coherent target ' +\n 'version set), or pin it explicitly by hand with ' +\n '\"npm i -D @ekanos/cli@latest\". Then re-run \"ekanos validate\" or ' +\n '\"ekanos publish\".',\n SCHEMA_SKEW_DOCS,\n );\n }\n\n if (result.status === 'project-older') {\n ctx.warn(\n `ekanos: the installed ${INTEGRATION_SCHEMA_PACKAGE}@${result.projectSchemaVersion} ` +\n `is older than ${result.cliSchemaVersion}, the version this CLI ` +\n 'validates with — some fields this CLI recognizes may not be usable ' +\n 'yet. Run \"ekanos upgrade\" to bring the toolchain in sync.',\n );\n }\n\n return result;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@ekanos/cli",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
+ "private": false,
4
5
  "description": "The Ekanos partner toolchain CLI: scaffold, validate, and test an Ekanos integration against the published @ekanos packages. Agent-native — every verb speaks JSON with a stable exit-code taxonomy.",
5
6
  "keywords": [
6
7
  "cli",
@@ -8,6 +9,7 @@
8
9
  "fusion",
9
10
  "integration"
10
11
  ],
12
+ "homepage": "https://sdk.ekanos.dev/cli/overview",
11
13
  "bugs": {
12
14
  "email": "npm@govastly.com"
13
15
  },
@@ -33,7 +35,7 @@
33
35
  },
34
36
  "dependencies": {
35
37
  "@clack/prompts": "1.7.0",
36
- "@ekanos/integration-schema": "0.7.0",
38
+ "@ekanos/integration-schema": "0.8.0",
37
39
  "esbuild": "0.28.1",
38
40
  "picocolors": "1.1.1"
39
41
  },
@@ -47,6 +47,39 @@ workflows will not run.
47
47
  `ekanos.json` at the project root maps the slug to the entry module. The slug
48
48
  lives in both files; `ekanos validate` reports a finding if they disagree.
49
49
 
50
+ ## SDK docs — the current reference
51
+
52
+ The public Ekanos SDK docs at **https://sdk.ekanos.dev** are the current
53
+ reference for everything here. This file is a snapshot taken when the project
54
+ was scaffolded and can lag the SDK, so consult the docs when you need an API
55
+ detail you are not sure of, when you hit an error code or a validate finding
56
+ you do not recognize, and after upgrading `@ekanos/*` packages.
57
+
58
+ | Topic | Page |
59
+ | --- | --- |
60
+ | Integration shape, entrypoints | https://sdk.ekanos.dev/sdk/integration-shape |
61
+ | Capability context (`ctx`) | https://sdk.ekanos.dev/sdk/capability-context |
62
+ | Widgets | https://sdk.ekanos.dev/sdk/surfaces/widgets |
63
+ | Tools | https://sdk.ekanos.dev/sdk/surfaces/tools |
64
+ | Actions | https://sdk.ekanos.dev/sdk/surfaces/actions |
65
+ | Webhooks | https://sdk.ekanos.dev/sdk/surfaces/webhooks |
66
+ | Schedules | https://sdk.ekanos.dev/sdk/surfaces/schedules |
67
+ | OAuth | https://sdk.ekanos.dev/sdk/surfaces/oauth |
68
+ | Storage | https://sdk.ekanos.dev/sdk/surfaces/storage |
69
+ | Egress and secrets | https://sdk.ekanos.dev/sdk/surfaces/egress, https://sdk.ekanos.dev/sdk/surfaces/secrets |
70
+ | `proposes` | https://sdk.ekanos.dev/sdk/surfaces/proposes |
71
+ | Testing | https://sdk.ekanos.dev/sdk/testing |
72
+ | CLI commands, JSON output and exit codes | https://sdk.ekanos.dev/cli/commands, https://sdk.ekanos.dev/cli/json-output-and-exit-codes |
73
+ | Publishing | https://sdk.ekanos.dev/cli/publishing |
74
+ | Harness | https://sdk.ekanos.dev/harness/overview |
75
+ | Errors and validation findings | https://sdk.ekanos.dev/troubleshooting/common-errors |
76
+
77
+ The `ekanos-sdk-docs` MCP server in `.mcp.json` (https://sdk.ekanos.dev/mcp)
78
+ searches these docs directly; prefer it over a general web search for any
79
+ question about the SDK, CLI or harness. Agents without `.mcp.json` support can
80
+ add that URL to their own MCP configuration, or fetch a page and append `.mdx`
81
+ to its path for the raw source.
82
+
50
83
  ## API documentation — ground every claim about the target API
51
84
 
52
85
  `ekanos.json` may declare a `docs` array: http(s) URLs and project-relative
@@ -371,7 +404,8 @@ your handler with the values the user actually typed.
371
404
  Build widget UI from `@ekanos/ui` — the published slice of the host design
372
405
  system. Available modules (import individually, e.g. `@ekanos/ui/button`):
373
406
  `ai-prompt-input`, `alert`, `badge` (counts, categories and tags), `button`,
374
- `card`, `confirm-dialog`, `dialog`, `dropdown-menu`, `empty-note`, `form`,
407
+ `card`, `confirm-dialog`, `data-table` (the house table: sorting, paging,
408
+ row clicks), `dialog`, `dropdown-menu`, `empty-note`, `empty-state`, `form`,
375
409
  `icon`, `if`, `input`, `label`, `page` (page shape: `PageRail`,
376
410
  `PageTitleSection`, `PageToolbar`, `PageSidenavLayout`, `ListMasthead`,
377
411
  `ScopePills`, `ScopeLinks`), `search-field`, `select`, `separator`, `sheet`,
@@ -385,8 +419,9 @@ A headline figure is a `Stat`, never a hand-sized span; a record's state is a
385
419
  `neutral`), never a `Badge` tone variant — those were deleted from the host
386
420
  kit on 2026-10-06 and `@ekanos/ui` only still accepts one so integrations
387
421
  published before then keep rendering. Never hand-roll a `<button>`,
388
- `<input>`, `<select>`, `<textarea>`, `<label>` or `<table>` — `table` is
389
- exported, so compose it rather than writing raw markup — and never write
422
+ `<input>`, `<select>`, `<textarea>`, `<label>` or `<table>` — records
423
+ compared across columns are a `DataTable`, static tabular facts are `table`,
424
+ and neither is raw markup — and never write
390
425
  `style={{ … }}`: an inline style escapes the theme, dark mode and the
391
426
  Source's branding. `ekanos validate` reports each of these as a `design/*`
392
427
  warning. Widget layout and type rules are in
@@ -5,3 +5,8 @@ This project is an Ekanos integration for the Ekanos platform — read
5
5
  The toolchain is `npx ekanos <verb>` (init, validate, dev, test, login,
6
6
  status, publish, upgrade); every verb supports `--json` and a stable
7
7
  exit-code taxonomy.
8
+
9
+ Docs: https://sdk.ekanos.dev is the current SDK, CLI and harness reference
10
+ (search it with the `ekanos-sdk-docs` MCP server in `.mcp.json`). The topic →
11
+ URL map is in AGENTS.md; check it for API details, error codes, and after
12
+ upgrading `@ekanos/*` packages.
@@ -17,6 +17,13 @@ use the `polish-widgets` skill. The target API's contract lives
17
17
  in `docs/api-notes.md`: write API client code from that file, and update it
18
18
  in the same commit when the live API disagrees.
19
19
 
20
+ ## Docs
21
+
22
+ https://sdk.ekanos.dev is the current reference and AGENTS.md may lag it. Look
23
+ there (or use the `ekanos-sdk-docs` MCP server) for an API detail, an error
24
+ code or validate finding (https://sdk.ekanos.dev/troubleshooting/common-errors),
25
+ and after upgrading `@ekanos/*` packages. The topic → URL map is in AGENTS.md.
26
+
20
27
  ## The loop
21
28
 
22
29
  1. `npx ekanos dev` — local harness (only when you need to see UI). For an
@@ -3,6 +3,10 @@
3
3
  "context7": {
4
4
  "command": "npx",
5
5
  "args": ["-y", "@upstash/context7-mcp"]
6
+ },
7
+ "ekanos-sdk-docs": {
8
+ "type": "http",
9
+ "url": "https://sdk.ekanos.dev/mcp"
6
10
  }
7
11
  }
8
12
  }
@@ -12,12 +12,12 @@
12
12
  "prepare": "node scripts/setup-hooks.mjs"
13
13
  },
14
14
  "dependencies": {
15
- "@ekanos/sdk": "^0.7.0",
16
- "@ekanos/ui": "^0.7.0"
15
+ "@ekanos/sdk": "^0.8.0",
16
+ "@ekanos/ui": "^0.8.0"
17
17
  },
18
18
  "devDependencies": {
19
- "@ekanos/cli": "^0.4.0",
20
- "@ekanos/harness": "^0.5.0",
19
+ "@ekanos/cli": "^0.4.2",
20
+ "@ekanos/harness": "^0.6.0",
21
21
  "@hookform/resolvers": "^5.2.2",
22
22
  "@tailwindcss/postcss": "^4.3.3",
23
23
  "@tanstack/react-query": "^5.101.4",
@@ -13,3 +13,13 @@ allowBuilds:
13
13
  # next-intl -> @swc/core: Next compiles with its own bundled SWC, and the
14
14
  # platform binary arrives as an optional dependency, not via this script.
15
15
  '@swc/core': false
16
+
17
+ # pnpm 11+ holds a freshly published version back for a day
18
+ # (minimumReleaseAge) before a range such as ^0.4.0 may resolve to it. The
19
+ # @ekanos packages are the toolchain this project was scaffolded from, pinned
20
+ # to the versions the scaffolding CLI knows; on a release day a new project
21
+ # would otherwise install the previous release and the CLI would report
22
+ # itself out of step with its own project. The hold still applies to every
23
+ # other dependency.
24
+ minimumReleaseAgeExclude:
25
+ - '@ekanos/*'
@@ -16,6 +16,17 @@ identity is the `slug` in `ekanos.json`; the definition is one
16
16
  Every `ekanos` verb supports `--json` and exits nonzero on failure. Validate
17
17
  findings carry a `hint` that is a remediation instruction: follow it.
18
18
 
19
+ ## Docs
20
+
21
+ https://sdk.ekanos.dev is the current SDK/CLI/harness reference; the topic → URL
22
+ map is in AGENTS.md ("SDK docs"). Check it before guessing at a surface's
23
+ options (widgets, tools, actions, webhooks, schedules, OAuth, storage,
24
+ `proposes`), when a finding or error code is unfamiliar
25
+ (https://sdk.ekanos.dev/troubleshooting/common-errors), and after an upgrade
26
+ when the scaffolded files may lag. The `ekanos-sdk-docs` MCP server in
27
+ `.mcp.json` searches it. This is the platform's docs, not the target API's:
28
+ the target API still comes from `docs/api-notes.md`.
29
+
19
30
  ## Workflow
20
31
 
21
32
  1. **Interview** — establish the plan and the docs, get approval
@@ -13,6 +13,12 @@ creation-time design rules live in
13
13
  `../build-integration/references/widget-design.md`; this skill assumes they
14
14
  exist and enforces them on code already written.
15
15
 
16
+ ## Docs
17
+
18
+ Widget API and styling details: https://sdk.ekanos.dev/sdk/surfaces/widgets,
19
+ https://sdk.ekanos.dev/sdk/styling and https://sdk.ekanos.dev/ui/components.
20
+ This skill may lag them; the docs are current.
21
+
16
22
  ## The key question
17
23
 
18
24
  Every widget exists to answer 1–2 questions. Name the question FIRST. Its