@smeltjs/core 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/bin.d.ts.map +1 -1
- package/dist/cli/bin.js +9 -4
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/doctor.d.ts +66 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +137 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/hooks.d.ts +36 -2
- package/dist/cli/hooks.d.ts.map +1 -1
- package/dist/cli/hooks.js +108 -64
- package/dist/cli/hooks.js.map +1 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +8 -14
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/installed.d.ts +56 -0
- package/dist/cli/installed.d.ts.map +1 -0
- package/dist/cli/installed.js +105 -0
- package/dist/cli/installed.js.map +1 -0
- package/dist/cli/lava.d.ts +31 -0
- package/dist/cli/lava.d.ts.map +1 -0
- package/dist/cli/lava.js +82 -0
- package/dist/cli/lava.js.map +1 -0
- package/dist/cli/setup.d.ts +87 -0
- package/dist/cli/setup.d.ts.map +1 -0
- package/dist/cli/setup.js +513 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/cli/shell.d.ts +11 -4
- package/dist/cli/shell.d.ts.map +1 -1
- package/dist/cli/shell.js.map +1 -1
- package/dist/cli/subcommands/agents.d.ts.map +1 -1
- package/dist/cli/subcommands/agents.js +2 -1
- package/dist/cli/subcommands/agents.js.map +1 -1
- package/dist/cli/subcommands/doctor.d.ts +15 -0
- package/dist/cli/subcommands/doctor.d.ts.map +1 -0
- package/dist/cli/subcommands/doctor.js +43 -0
- package/dist/cli/subcommands/doctor.js.map +1 -0
- package/dist/cli/subcommands/flags.d.ts +13 -0
- package/dist/cli/subcommands/flags.d.ts.map +1 -1
- package/dist/cli/subcommands/flags.js +27 -1
- package/dist/cli/subcommands/flags.js.map +1 -1
- package/dist/cli/subcommands/hooks.d.ts.map +1 -1
- package/dist/cli/subcommands/hooks.js +10 -2
- package/dist/cli/subcommands/hooks.js.map +1 -1
- package/dist/cli/subcommands/init.d.ts.map +1 -1
- package/dist/cli/subcommands/init.js +2 -1
- package/dist/cli/subcommands/init.js.map +1 -1
- package/dist/cli/subcommands/registry.d.ts +3 -1
- package/dist/cli/subcommands/registry.d.ts.map +1 -1
- package/dist/cli/subcommands/registry.js +6 -0
- package/dist/cli/subcommands/registry.js.map +1 -1
- package/dist/cli/subcommands/setup.d.ts +23 -0
- package/dist/cli/subcommands/setup.d.ts.map +1 -0
- package/dist/cli/subcommands/setup.js +87 -0
- package/dist/cli/subcommands/setup.js.map +1 -0
- package/dist/cli/subcommands/subcommand.d.ts +1 -1
- package/dist/cli/subcommands/subcommand.d.ts.map +1 -1
- package/dist/cli/subcommands/subcommand.js.map +1 -1
- package/dist/cli/wizard.d.ts +72 -0
- package/dist/cli/wizard.d.ts.map +1 -0
- package/dist/cli/wizard.js +86 -0
- package/dist/cli/wizard.js.map +1 -0
- package/dist/harness/claude-code.d.ts.map +1 -1
- package/dist/harness/claude-code.js +10 -0
- package/dist/harness/claude-code.js.map +1 -1
- package/dist/harness/codex.d.ts.map +1 -1
- package/dist/harness/codex.js +1 -0
- package/dist/harness/codex.js.map +1 -1
- package/dist/harness/grok.d.ts.map +1 -1
- package/dist/harness/grok.js +4 -1
- package/dist/harness/grok.js.map +1 -1
- package/dist/harness/kilocode.d.ts.map +1 -1
- package/dist/harness/kilocode.js +1 -1
- package/dist/harness/kilocode.js.map +1 -1
- package/dist/harness/opencode.d.ts.map +1 -1
- package/dist/harness/opencode.js +10 -0
- package/dist/harness/opencode.js.map +1 -1
- package/dist/harness/profile.d.ts +23 -2
- package/dist/harness/profile.d.ts.map +1 -1
- package/dist/harness/profile.js.map +1 -1
- package/dist/harness/snippet.d.ts +14 -1
- package/dist/harness/snippet.d.ts.map +1 -1
- package/dist/harness/snippet.js +18 -2
- package/dist/harness/snippet.js.map +1 -1
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/setup/recipe.d.ts +73 -0
- package/dist/setup/recipe.d.ts.map +1 -0
- package/dist/setup/recipe.js +76 -0
- package/dist/setup/recipe.js.map +1 -0
- package/dist/text/json-edit.d.ts +15 -1
- package/dist/text/json-edit.d.ts.map +1 -1
- package/dist/text/json-edit.js +76 -5
- package/dist/text/json-edit.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/init.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\n\nimport { CliUsageError } from '../errors.ts';\nimport { DEFAULT_STRATEGY, STRATEGIES } from '../plan/planners.ts';\nimport type { Strategy } from '../plan/planners.ts';\nimport { STRUCTURAL_LANGUAGES } from '../plan/structural.ts';\n\nimport { answerReader, CLI_NAME } from './shell.ts';\nimport type { AnswerStream } from './shell.ts';\nimport {\n CONFIG_FILE_NAME,\n CONFIG_VERSION,\n findConfigFile,\n parseConfig,\n renderConfig,\n} from './config.ts';\nimport type { SmeltConfig, SmeltConfigHooks, SmeltConfigStore } from './config.ts';\n\n/**\n * `smelt init` — the setup wizard.\n *\n * The same testability pattern as `run.ts`: the wizard is a pure function over an\n * input/output pair, so every flow — fresh run, back-navigation, re-run editing,\n * declined overwrites — runs in-process in tests, and `bin.ts` wires the real stdio.\n *\n * Three rules shape everything here:\n *\n * 1. **Nothing is written until the final confirm**, which lists exactly what will be\n * written or changed. A wizard that writes as it goes cannot be backed out of.\n * 2. **An existing file is never overwritten without an explicit per-file yes.** Not\n * a global \"overwrite all\", not a default — one question per existing file, and\n * anything but a literal `yes` skips it. Guarded by\n * `test/guards/init-wizard.test.ts`, with a mutation proving the guard goes red.\n * 3. **Every step accepts `back`.** A wizard you cannot reverse inside is a form. That\n * includes the directory question a fresh run asks inside a workspace: `back` from\n * the first step returns to it, keeping the answers given since.\n * 4. **Everything lands in one named directory.** A fresh run inside a workspace asks\n * which end of config discovery to write to (see `chooseDirectory`) and an edit run\n * writes beside the config it found; either way the directory is printed in the\n * \"About to write\" listing before the confirm, and nothing is ever written outside\n * it. A wizard that writes where the user cannot see is a wizard writing somewhere\n * they did not agree to.\n *\n * Law 4 note: the wizard's copy states what each choice *does*, never what it saves —\n * no percentages, no rates, no numbers smelt has not measured.\n */\n\n/** The generated measure-hook stub's file name. */\nexport const MEASURE_STUB_FILE = 'smelt.measure.ts';\n\n/** The generated reranker stub's file name. */\nexport const RERANK_STUB_FILE = 'smelt.rerank.ts';\n\n/** Where the wizard's bytes come from and go. Injected so `runInit` is testable in-process. */\nexport interface InitIo {\n /**\n * Interactive input — the real stdin in `bin.ts`, a scripted stream in tests.\n * Structural on purpose; see {@link AnswerStream}.\n */\n readonly input: AnswerStream;\n readonly output: (text: string) => void;\n /**\n * Where config discovery starts, and where a fresh run's files land — unless `cwd`\n * is inside a workspace and the user picks its root instead (`chooseDirectory`). An\n * edit run writes next to the discovered config, which may be an ancestor of `cwd`.\n * In every flow the \"About to write\" listing names the directory before anything is\n * written, and nothing is written outside it.\n */\n readonly cwd: string;\n}\n\n/** Everything the wizard decides. Pure data until the final confirm writes it. */\ninterface WizardChoices {\n budgetBytes: number | undefined;\n store: SmeltConfigStore;\n strategy: Strategy;\n /** Generate {@link MEASURE_STUB_FILE}? Never deletes an existing one. */\n measureStub: boolean;\n /** Generate {@link RERANK_STUB_FILE}? Never deletes an existing one. */\n rerankStub: boolean;\n /**\n * An existing config's `hooks` block, carried through verbatim. This wizard never\n * edits it — `smelt hooks install` owns those choices — but a re-run that silently\n * dropped it would be an edit the user never made.\n */\n hooks: SmeltConfigHooks | undefined;\n}\n\ntype StepOutcome = 'ok' | 'back';\n\ninterface PlannedWrite {\n readonly name: string;\n readonly path: string;\n readonly content: string;\n readonly exists: boolean;\n /** The file already holds exactly these bytes — nothing to write. */\n readonly unchanged: boolean;\n}\n\n/**\n * The wizard, start to finish. Returns an exit code (0 in every completed flow,\n * including \"declined to write anything\").\n *\n * @throws {CliUsageError} on a malformed existing config, or when input ends before\n * the wizard finishes — both are usage-shaped, and nothing further is written.\n */\nexport async function runInit(io: InitIo): Promise<number> {\n // `answerReader` — not `Readable.from` + readline — because the wizard must *let go*\n // of stdin when it is done. See the note on `answerReader` for the hang that shape\n // caused: files written, `Done.` printed, and the process never exiting.\n const lines = answerReader(io.input);\n const ask = async (prompt: string): Promise<string> => {\n io.output(prompt);\n const next = await lines.next();\n if (next === undefined) {\n throw new CliUsageError(\n `${CLI_NAME} init: input ended before the wizard finished. ` +\n `Files already confirmed and written stay; nothing further was written.`,\n );\n }\n return next.trim();\n };\n\n try {\n const existing = loadExisting(io.cwd);\n return existing === undefined\n ? await freshRun(io, ask)\n : await editRun(io, ask, existing.path, existing.config);\n } finally {\n await lines.release();\n }\n}\n\nfunction loadExisting(cwd: string): { path: string; config: SmeltConfig } | undefined {\n const path = findConfigFile(cwd);\n if (path === undefined) return undefined;\n // Malformed is a loud usage error, not a silent fresh start: overwriting a config\n // the user wrote, because it had a typo, would be the wizard deciding for them.\n return { path, config: parseConfig(readFileSync(path, 'utf8'), path) };\n}\n\n// ---------------------------------------------------------------------------\n// The two flows\n// ---------------------------------------------------------------------------\n\ntype Asker = (prompt: string) => Promise<string>;\n\ninterface Step {\n readonly id: 'budget' | 'store' | 'strategy' | 'measure' | 'rerank';\n run(io: InitIo, ask: Asker, choices: WizardChoices, dir: string): Promise<StepOutcome>;\n}\n\nconst STEPS: readonly Step[] = [\n { id: 'budget', run: stepBudget },\n { id: 'store', run: stepStore },\n { id: 'strategy', run: stepStrategy },\n { id: 'measure', run: stepMeasure },\n { id: 'rerank', run: stepRerank },\n];\n\nasync function freshRun(io: InitIo, ask: Asker): Promise<number> {\n const choices: WizardChoices = {\n budgetBytes: undefined,\n store: { kind: 'memory' },\n strategy: DEFAULT_STRATEGY,\n measureStub: false,\n rerankStub: false,\n hooks: undefined,\n };\n\n // Outside a workspace there is no directory question, so the first step really is the\n // first thing asked. Inside one there is, and `back` from the first step returns to it\n // — with every answer so far carried along, because reversing is not restarting.\n const root = findWorkspaceRoot(io.cwd);\n for (;;) {\n const dir = root === undefined ? io.cwd : await chooseDirectory(io, ask, root);\n io.output(\n `${CLI_NAME} init — sets up ${CONFIG_FILE_NAME} in ${dir}.\\n` +\n `A run reads the nearest ${CONFIG_FILE_NAME}, walking UP from the directory it ` +\n `is run in, so this one is found from ${dir} and everything below it.\\n` +\n `Answer \\`back\\` at any step to return to the previous one. ` +\n `Nothing is written until you confirm at the end.\\n\\n`,\n );\n if ((await runSteps(io, ask, choices, dir, root !== undefined)) === 'done') return 0;\n }\n}\n\n/**\n * The step loop for one chosen directory: every step, then the confirm, with `back`\n * reversing all the way through both. Returns `'directory'` when `back` walks off the\n * front and there is a directory question to walk back into — `freshRun` re-asks it and\n * calls this again with the same {@link WizardChoices}.\n */\nasync function runSteps(\n io: InitIo,\n ask: Asker,\n choices: WizardChoices,\n dir: string,\n canReopenDirectory: boolean,\n): Promise<'done' | 'directory'> {\n let index = 0;\n for (;;) {\n while (index < STEPS.length) {\n const outcome = await STEPS[index]!.run(io, ask, choices, dir);\n if (outcome !== 'back') {\n index += 1;\n } else if (index > 0) {\n index -= 1;\n } else if (canReopenDirectory) {\n return 'directory';\n } else {\n io.output(`This is the first step — there is nothing before it.\\n`);\n }\n }\n const verdict = await confirmAndWrite(io, ask, choices, dir);\n if (verdict !== 'back') return 'done';\n index = STEPS.length - 1;\n }\n}\n\n/**\n * Which directory this fresh run writes into — and the wizard's answer to the\n * monorepo trap.\n *\n * `smelt init` in `packages/web` used to write a config in `packages/web`, silently,\n * while discovery walks **up**: every run from the repo root then found no config and\n * used none, and the user had watched themselves set a budget. So when the working\n * directory sits inside a workspace (`pnpm-workspace.yaml`, or a `package.json` with a\n * `workspaces` field, in any ancestor), the wizard states the discovery rule and asks\n * which end of it to write to, defaulting to the root that covers the whole tree.\n *\n * The answer becomes the directory every later step, the \"About to write\" listing and\n * every `writeFileSync` uses — so the wizard still writes only inside the one directory\n * the user was shown and confirmed, here as much as anywhere else. Outside a workspace\n * there is nothing to choose and nothing is asked; the discovery rule is stated either\n * way, because \"where will runs look for this?\" is not a question a user should have to\n * infer from a path.\n *\n * Rule 3 holds here too: this is genuinely the first question, so `back` at it has\n * nowhere to go — but `back` from the first *step* comes back to it (see `freshRun`),\n * carrying the answers already given. The most consequential question in the wizard is\n * not the one you cannot reverse into.\n */\nasync function chooseDirectory(io: InitIo, ask: Asker, root: string): Promise<string> {\n io.output(\n `${CLI_NAME} init — ${io.cwd} is inside a workspace rooted at ${root}.\\n` +\n `A run reads the nearest ${CONFIG_FILE_NAME}, walking UP from the directory it is ` +\n `run in. A config written here is found from here and below; a run from ${root} ` +\n `would not find it. One at the workspace root is found from every package in it.\\n` +\n ` 1. ${root} (the workspace root)\\n` +\n ` 2. ${io.cwd} (here — this package only)\\n`,\n );\n for (;;) {\n const answer = await ask(`where should ${CONFIG_FILE_NAME} go? (1/2) [1]> `);\n if (answer === 'back') {\n io.output(`This is the first question — there is nothing before it.\\n`);\n continue;\n }\n const pick = answer === '' ? '1' : answer;\n if (pick === '1') return root;\n if (pick === '2') return io.cwd;\n io.output(`1 for the workspace root, 2 for here.\\n`);\n }\n}\n\n/**\n * The nearest **ancestor** of `cwd` that declares a workspace, or `undefined`.\n *\n * Ancestors only: a `cwd` that is itself the workspace root has nothing to choose\n * between, so it is asked nothing. Two markers, because those are the two the\n * ecosystem writes — pnpm's `pnpm-workspace.yaml`, and the `workspaces` field npm,\n * yarn and bun read out of `package.json`. Detection never reads a lockfile and never\n * shells out; a `package.json` it cannot parse claims nothing, so it is not a root.\n */\nexport function findWorkspaceRoot(cwd: string): string | undefined {\n let dir = resolve(cwd);\n for (;;) {\n const parent = dirname(dir);\n if (parent === dir) return undefined;\n dir = parent;\n if (declaresWorkspace(dir)) return dir;\n }\n}\n\nfunction declaresWorkspace(dir: string): boolean {\n if (existsSync(join(dir, 'pnpm-workspace.yaml'))) return true;\n const manifest = join(dir, 'package.json');\n if (!existsSync(manifest)) return false;\n let workspaces: unknown;\n try {\n ({ workspaces } = JSON.parse(readFileSync(manifest, 'utf8')) as { workspaces?: unknown });\n } catch {\n return false; // a package.json smelt cannot read is not a claim about anything\n }\n return Array.isArray(workspaces) || (typeof workspaces === 'object' && workspaces !== null);\n}\n\nasync function editRun(\n io: InitIo,\n ask: Asker,\n configPath: string,\n config: SmeltConfig,\n): Promise<number> {\n const dir = dirname(configPath);\n const choices: WizardChoices = {\n budgetBytes: config.defaultBudgetBytes,\n store: config.store ?? { kind: 'memory' },\n strategy: config.strategy ?? DEFAULT_STRATEGY,\n measureStub: false,\n rerankStub: false,\n hooks: config.hooks,\n };\n\n io.output(\n `${CLI_NAME} init — ${CONFIG_FILE_NAME} already exists at ${configPath}.\\n` +\n `Change one setting at a time; nothing is written until you confirm.\\n`,\n );\n\n for (;;) {\n io.output(`\\nCurrent values:\\n${summary(choices, dir)}\\n`);\n const answer = await ask(\n `Change which setting? (budget / store / strategy / measure / rerank, ` +\n `\\`done\\` to review and confirm, \\`back\\` to leave without writing)\\n> `,\n );\n if (answer === 'back') {\n io.output(`Left as it was. Nothing has been written.\\n`);\n return 0;\n }\n if (answer === 'done') {\n const verdict = await confirmAndWrite(io, ask, choices, dir);\n if (verdict !== 'back') return 0;\n continue; // back from confirm returns to this menu\n }\n const step = STEPS.find((candidate) => candidate.id === answer);\n if (step === undefined) {\n io.output(`Not a setting: \"${answer}\". One of: budget, store, strategy, measure, rerank.\\n`);\n continue;\n }\n await step.run(io, ask, choices, dir); // its own `back` returns here\n }\n}\n\nfunction summary(choices: WizardChoices, dir: string): string {\n const stubLine = (file: string, generate: boolean): string => {\n if (generate) return `generate ${file}`;\n return existsSync(join(dir, file)) ? `${file} exists (kept as is)` : 'none';\n };\n return [\n ` budget: ${choices.budgetBytes === undefined ? '(not set)' : `${String(choices.budgetBytes)} bytes`}`,\n ` store: ${choices.store.kind === 'memory' ? 'memory' : `directory (${choices.store.path})`}`,\n ` strategy: ${choices.strategy}`,\n ` measure: ${stubLine(MEASURE_STUB_FILE, choices.measureStub)}`,\n ` rerank: ${stubLine(RERANK_STUB_FILE, choices.rerankStub)}`,\n ].join('\\n');\n}\n\n// ---------------------------------------------------------------------------\n// The steps. Each one accepts `back`.\n// ---------------------------------------------------------------------------\n\nasync function stepBudget(io: InitIo, ask: Asker, choices: WizardChoices): Promise<StepOutcome> {\n io.output(\n `\\nDefault byte budget — used when a \\`${CLI_NAME}\\` run omits --budget.\\n` +\n `Budgets are UTF-8 bytes, permanently; an explicit --budget always wins.\\n` +\n `There is no suggested number: the right budget depends on your traffic, and ` +\n `smelt does not invent numbers it has not measured.\\n`,\n );\n for (;;) {\n const current = choices.budgetBytes === undefined ? '' : ` [${String(choices.budgetBytes)}]`;\n const answer = await ask(`budget in bytes${current} (or back)> `);\n if (answer === 'back') return 'back';\n if (answer === '' && choices.budgetBytes !== undefined) return 'ok';\n if (/^\\d+$/.test(answer) && Number(answer) > 0) {\n choices.budgetBytes = Number(answer);\n return 'ok';\n }\n io.output(`A whole number of bytes greater than zero, e.g. 4000.\\n`);\n }\n}\n\nasync function stepStore(io: InitIo, ask: Asker, choices: WizardChoices): Promise<StepOutcome> {\n io.output(\n `\\nWhere elided bytes live. Every elision is reversible only while a store holds ` +\n `its bytes (Law 3):\\n` +\n ` 1. memory — per-process; retrievals do not survive the process\\n` +\n ` 2. directory — persistent, content-addressed, on disk; retrieval counters ` +\n `survive restarts\\n`,\n );\n for (;;) {\n const current = choices.store.kind === 'memory' ? '1' : '2';\n const answer = await ask(`store (1/2) [${current}] (or back)> `);\n if (answer === 'back') return 'back';\n const pick = answer === '' ? current : answer;\n if (pick === '1') {\n choices.store = { kind: 'memory' };\n return 'ok';\n }\n if (pick === '2') {\n const previous = choices.store.kind === 'directory' ? choices.store.path : '.smelt/store';\n const path = await ask(`store directory, relative to ${CONFIG_FILE_NAME} [${previous}]> `);\n if (path === 'back') continue; // back to the store choice, not out of the step\n choices.store = { kind: 'directory', path: path === '' ? previous : path };\n return 'ok';\n }\n io.output(`1 for memory, 2 for directory, or back.\\n`);\n }\n}\n\n/**\n * One sentence per strategy, for the wizard's menu — `Record<Strategy, string>`, so a\n * strategy added to the `PLANNERS` registry without a line here is a compile error\n * rather than an option the wizard silently never offers. The menu itself, its\n * numbering and the re-prompt are all rendered from {@link STRATEGIES}, which is why\n * the third choice cost one entry in this table.\n */\nconst STRATEGY_BLURB: Readonly<Record<Strategy, string>> = {\n lexical: 'line windows around your focus terms; works on any text',\n structural:\n `parses ${String(STRUCTURAL_LANGUAGES.length)} languages with bundled grammars ` +\n `(${STRUCTURAL_LANGUAGES.join(', ')}) and collapses siblings by name; refuses ` +\n `other languages rather than approximating`,\n auto:\n 'structural for those languages and lexical for everything else; the result ' +\n 'names whichever one actually ran',\n};\n\nasync function stepStrategy(io: InitIo, ask: Asker, choices: WizardChoices): Promise<StepOutcome> {\n const picks = STRATEGIES.map((_, index) => String(index + 1));\n io.output(\n `\\nDefault planner strategy — used when a run omits --strategy:\\n` +\n STRATEGIES.map(\n (name, index) => ` ${picks[index]!}. ${name.padEnd(12)}— ${STRATEGY_BLURB[name]}\\n`,\n ).join(''),\n );\n for (;;) {\n const current = picks[STRATEGIES.indexOf(choices.strategy)]!;\n const answer = await ask(`strategy (${picks.join('/')}) [${current}] (or back)> `);\n if (answer === 'back') return 'back';\n const pick = answer === '' ? current : answer;\n const index = picks.indexOf(pick);\n if (index >= 0) {\n choices.strategy = STRATEGIES[index]!;\n return 'ok';\n }\n io.output(`${STRATEGIES.map((name, i) => `${picks[i]!} for ${name}`).join(', ')}, or back.\\n`);\n }\n}\n\nasync function stepMeasure(\n io: InitIo,\n ask: Asker,\n choices: WizardChoices,\n dir: string,\n): Promise<StepOutcome> {\n const exists = existsSync(join(dir, MEASURE_STUB_FILE));\n io.output(\n `\\nMeasure hook — your own counter (a tokenizer, usually), so results carry a ` +\n `second, labelled number next to the byte counts. smelt ships none: a token ` +\n `count without its tokenizer named is not a measurement.\\n` +\n ` 1. none\\n` +\n ` 2. generate ${MEASURE_STUB_FILE}, a typed stub you fill in\\n` +\n (exists ? `(${MEASURE_STUB_FILE} already exists; it is never deleted from here.)\\n` : ``),\n );\n for (;;) {\n const answer = await ask(`measure (1/2) [${choices.measureStub ? '2' : '1'}] (or back)> `);\n if (answer === 'back') return 'back';\n const pick = answer === '' ? (choices.measureStub ? '2' : '1') : answer;\n if (pick === '1' || pick === '2') {\n choices.measureStub = pick === '2';\n return 'ok';\n }\n io.output(`1 for none, 2 to generate the stub, or back.\\n`);\n }\n}\n\nasync function stepRerank(\n io: InitIo,\n ask: Asker,\n choices: WizardChoices,\n dir: string,\n): Promise<StepOutcome> {\n const exists = existsSync(join(dir, RERANK_STUB_FILE));\n io.output(\n `\\nReranker — a RerankStage of your own. smelt never bundles one and never will: ` +\n `an outbound call must live in your code, under your key, visible in your own ` +\n `review (Law 1).\\n` +\n ` 1. none\\n` +\n ` 2. generate ${RERANK_STUB_FILE}, a typed stub with the HTTP call sketched as a TODO\\n` +\n (exists ? `(${RERANK_STUB_FILE} already exists; it is never deleted from here.)\\n` : ``),\n );\n for (;;) {\n const answer = await ask(`rerank (1/2) [${choices.rerankStub ? '2' : '1'}] (or back)> `);\n if (answer === 'back') return 'back';\n const pick = answer === '' ? (choices.rerankStub ? '2' : '1') : answer;\n if (pick === '1' || pick === '2') {\n choices.rerankStub = pick === '2';\n return 'ok';\n }\n io.output(`1 for none, 2 to generate the stub, or back.\\n`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// The confirm step: the only place anything is written\n// ---------------------------------------------------------------------------\n\nconst writeLabel = (write: PlannedWrite): string => {\n if (write.unchanged) return 'unchanged — nothing to write';\n return write.exists ? 'exists — will ask before overwriting' : 'new';\n};\n\nasync function confirmAndWrite(\n io: InitIo,\n ask: Asker,\n choices: WizardChoices,\n dir: string,\n): Promise<'done' | 'back'> {\n if (choices.budgetBytes === undefined) {\n // Unreachable in the fresh flow (the budget step requires a number) but reachable\n // from an edit of a config that never had one — `defaultBudgetBytes` is optional,\n // so such a config parses fine. The wizard still insists: a confirm without a\n // budget would write a file this wizard just called incomplete. `back` here is a\n // real `back` — it returns to the caller, never falls forward into the confirm.\n io.output(`No budget is set yet — set one before confirming.\\n`);\n if ((await stepBudget(io, ask, choices)) === 'back') return 'back';\n }\n\n const writes = plannedWrites(choices, dir);\n io.output(\n `\\nAbout to write, into ${dir}:\\n` +\n writes.map((write) => ` ${write.name.padEnd(20)} (${writeLabel(write)})\\n`).join('') +\n `Nothing has been written yet.\\n`,\n );\n\n for (;;) {\n const answer = await ask(`confirm (yes / no / back)> `);\n if (answer === 'back') return 'back';\n if (answer === 'no') {\n io.output(`Nothing was written.\\n`);\n return 'done';\n }\n if (answer === 'yes') break;\n io.output(`yes to write, no to leave everything untouched, back to change a setting.\\n`);\n }\n\n for (const write of writes) {\n if (write.unchanged) {\n io.output(` ${write.name} — unchanged, not rewritten\\n`);\n continue;\n }\n if (write.exists) {\n // The per-file consent rule: one explicit question per existing file, and only\n // a literal `yes` overwrites. This is the line the mutation\n // `init-overwrite-without-consent` breaks to prove the guard can go red.\n const answer = await ask(` ${write.name} exists — overwrite it? (yes/no)> `);\n if (answer !== 'yes') {\n io.output(` skipped ${write.name} — the existing file was not touched\\n`);\n continue;\n }\n }\n writeFileSync(write.path, write.content);\n io.output(` wrote ${write.name}\\n`);\n }\n io.output(`Done.\\n`);\n io.output(\n `Also: \\`${CLI_NAME} hooks install\\` wires the smelt guard into agent-harness ` +\n `hooks (Claude Code, Codex, and more) — it detects installed harnesses and asks ` +\n `before writing anything.\\n`,\n );\n return 'done';\n}\n\nfunction plannedWrites(choices: WizardChoices, dir: string): readonly PlannedWrite[] {\n const plan = (name: string, content: string): PlannedWrite => {\n const path = join(dir, name);\n const exists = existsSync(path);\n const unchanged = exists && readFileSync(path, 'utf8') === content;\n return { name, path, content, exists, unchanged };\n };\n const writes = [plan(CONFIG_FILE_NAME, renderConfig(chosenConfig(choices)))];\n if (choices.measureStub) writes.push(plan(MEASURE_STUB_FILE, measureStubSource()));\n if (choices.rerankStub) writes.push(plan(RERANK_STUB_FILE, rerankStubSource()));\n return writes;\n}\n\n/**\n * The config the wizard's choices mean — the wizard's *policy*, handed to the one\n * writer in `config.ts`. `strategy` and `store` are always written: the wizard asked\n * about both, and a chosen value left out of the file would be a setting the user\n * watched themselves pick and never got.\n */\nfunction chosenConfig(choices: {\n readonly budgetBytes: number | undefined;\n readonly store: SmeltConfigStore;\n readonly strategy: Strategy;\n /** Carried through from an existing config; this wizard never edits it. */\n readonly hooks?: SmeltConfigHooks | undefined;\n}): SmeltConfig {\n return {\n smeltConfig: CONFIG_VERSION,\n ...(choices.budgetBytes === undefined ? {} : { defaultBudgetBytes: choices.budgetBytes }),\n strategy: choices.strategy,\n store: choices.store,\n ...(choices.hooks === undefined ? {} : { hooks: choices.hooks }),\n };\n}\n\n// ---------------------------------------------------------------------------\n// The generated stubs. String literals on purpose: smelt's own import graph must not\n// gain an HTTP client, and the zero-network guard's string-stripper ignores string\n// bodies — the sketched fetch below lives in the CONSUMER'S file, never in smelt's.\n//\n// The comment token between `from` and the package name in each stub's import line is\n// deliberate: the guard's import scanner reads raw source, so spelling that import\n// plainly inside this template would register as an edge in smelt's own graph — which\n// it is not. The comment keeps the generated file valid TypeScript while keeping this\n// data out of the guard's walk.\n// ---------------------------------------------------------------------------\n\n/** The `smelt.measure.ts` the wizard writes. Exported so tests can compile it. */\nexport function measureStubSource(): string {\n return `/**\n * Measure hook — generated by \\`smelt init\\`.\n *\n * smelt's budgets are UTF-8 bytes, permanently; this hook adds a second, labelled\n * number to every result in YOUR unit, counted by YOUR tokenizer. Both \\`id\\` and\n * \\`unit\\` are required: a count without the counter named is not a measurement.\n *\n * Wire it in, in your own code:\n *\n * const smelter = createSmelter({ defaultBudgetBytes: 8_000, measure });\n *\n * with createSmelter imported from your @smeltjs/core install and measure from here.\n */\nimport type { Measure } from /* your install */ '@smeltjs/core';\n\nexport const measure: Measure = {\n // TODO: name the counter that produces these numbers, e.g. 'tiktoken/o200k_base'.\n id: 'TODO/your-tokenizer',\n unit: 'tokens',\n count(text: string): number {\n // TODO: replace with your real tokenizer. It must be local and synchronous —\n // a count() that calls an API would make your process call an API on every smelt.\n // e.g. return encode(text).length;\n throw new Error(\n 'smelt.measure.ts: count() is not implemented yet — ' +\n 'fill it in with your tokenizer (length of input: ' + String(text.length) + ')',\n );\n },\n};\n`;\n}\n\n/** The `smelt.rerank.ts` the wizard writes. Exported so tests can compile it. */\nexport function rerankStubSource(): string {\n return `/**\n * Reranker stub — generated by \\`smelt init\\`.\n *\n * smelt itself makes zero network calls and never bundles a reranker (Law 1): the\n * moment one ships as a default, every consumer's source code leaves the machine and\n * they find out from a changelog, or never. So the outbound call lives HERE, in your\n * file, reading your env var, visible in your own review.\n *\n * Wire it into your own pipeline; smelt never calls this for you.\n */\nimport type { RerankCandidate, RerankedCandidate, RerankStage } from /* your install */ '@smeltjs/core';\n\n/** The env var YOUR code reads. Rename it to match your vendor. */\nconst API_KEY_ENV = 'RERANKER_API_KEY';\n\nexport const rerank: RerankStage = {\n id: 'my-reranker/v1',\n async rerank(\n candidates: readonly RerankCandidate[],\n query: string,\n ): Promise<readonly RerankedCandidate[]> {\n const apiKey = process.env[API_KEY_ENV];\n if (apiKey === undefined || apiKey === '') {\n throw new Error(\n 'smelt.rerank.ts: ' + API_KEY_ENV + ' is not set. This stage makes an outbound ' +\n 'HTTP call from YOUR code with YOUR key; without a key it refuses to pretend.',\n );\n }\n\n // TODO: the outbound call. Voyage AI is one example vendor; any reranker with an\n // HTTP API fits this shape:\n //\n // const response = await fetch('https://api.voyageai.com/v1/rerank', {\n // method: 'POST',\n // headers: {\n // 'content-type': 'application/json',\n // authorization: \\`Bearer \\${apiKey}\\`,\n // },\n // body: JSON.stringify({\n // query,\n // documents: candidates.map((candidate) => candidate.text),\n // }),\n // });\n // if (!response.ok) throw new Error('rerank failed: ' + String(response.status));\n // const body = (await response.json()) as {\n // data: { index: number; relevance_score: number }[];\n // };\n // return body.data.map(({ index, relevance_score }) => ({\n // ...candidates[index]!,\n // score: relevance_score,\n // }));\n //\n void candidates;\n void query;\n throw new Error(\n 'smelt.rerank.ts: implement the outbound call sketched above, then delete this throw.',\n );\n },\n};\n`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\n\nimport { DEFAULT_STRATEGY, STRATEGIES } from '../plan/planners.ts';\nimport type { Strategy } from '../plan/planners.ts';\nimport { STRUCTURAL_LANGUAGES } from '../plan/structural.ts';\nimport { SETUP_RECIPE } from '../setup/recipe.ts';\n\nimport { CLI_NAME } from './shell.ts';\nimport { wizardAsk } from './wizard.ts';\nimport type { AnswerStream } from './shell.ts';\nimport {\n CONFIG_FILE_NAME,\n CONFIG_VERSION,\n findConfigFile,\n parseConfig,\n renderConfig,\n} from './config.ts';\nimport type { SmeltConfig, SmeltConfigHooks, SmeltConfigStore } from './config.ts';\n\n/**\n * `smelt init` — the setup wizard.\n *\n * The same testability pattern as `run.ts`: the wizard is a pure function over an\n * input/output pair, so every flow — fresh run, back-navigation, re-run editing,\n * declined overwrites — runs in-process in tests, and `bin.ts` wires the real stdio.\n *\n * Three rules shape everything here:\n *\n * 1. **Nothing is written until the final confirm**, which lists exactly what will be\n * written or changed. A wizard that writes as it goes cannot be backed out of.\n * 2. **An existing file is never overwritten without an explicit per-file yes.** Not\n * a global \"overwrite all\", not a default — one question per existing file, and\n * anything but a literal `yes` skips it. Guarded by\n * `test/guards/init-wizard.test.ts`, with a mutation proving the guard goes red.\n * 3. **Every step accepts `back`.** A wizard you cannot reverse inside is a form. That\n * includes the directory question a fresh run asks inside a workspace: `back` from\n * the first step returns to it, keeping the answers given since.\n * 4. **Everything lands in one named directory.** A fresh run inside a workspace asks\n * which end of config discovery to write to (see `chooseDirectory`) and an edit run\n * writes beside the config it found; either way the directory is printed in the\n * \"About to write\" listing before the confirm, and nothing is ever written outside\n * it. A wizard that writes where the user cannot see is a wizard writing somewhere\n * they did not agree to.\n *\n * Law 4 note: the wizard's copy states what each choice *does*, never what it saves —\n * no percentages, no rates, no numbers smelt has not measured.\n */\n\n/** The generated measure-hook stub's file name. */\nexport const MEASURE_STUB_FILE = 'smelt.measure.ts';\n\n/** The generated reranker stub's file name. */\nexport const RERANK_STUB_FILE = 'smelt.rerank.ts';\n\n/** Where the wizard's bytes come from and go. Injected so `runInit` is testable in-process. */\nexport interface InitIo {\n /**\n * Interactive input — the real stdin in `bin.ts`, a scripted stream in tests.\n * Structural on purpose; see {@link AnswerStream}.\n */\n readonly input: AnswerStream;\n readonly output: (text: string) => void;\n /**\n * Where config discovery starts, and where a fresh run's files land — unless `cwd`\n * is inside a workspace and the user picks its root instead (`chooseDirectory`). An\n * edit run writes next to the discovered config, which may be an ancestor of `cwd`.\n * In every flow the \"About to write\" listing names the directory before anything is\n * written, and nothing is written outside it.\n */\n readonly cwd: string;\n}\n\n/** Everything the wizard decides. Pure data until the final confirm writes it. */\ninterface WizardChoices {\n budgetBytes: number | undefined;\n store: SmeltConfigStore;\n strategy: Strategy;\n /** Generate {@link MEASURE_STUB_FILE}? Never deletes an existing one. */\n measureStub: boolean;\n /** Generate {@link RERANK_STUB_FILE}? Never deletes an existing one. */\n rerankStub: boolean;\n /**\n * An existing config's `hooks` block, carried through verbatim. This wizard never\n * edits it — `smelt hooks install` owns those choices — but a re-run that silently\n * dropped it would be an edit the user never made.\n */\n hooks: SmeltConfigHooks | undefined;\n}\n\ntype StepOutcome = 'ok' | 'back';\n\ninterface PlannedWrite {\n readonly name: string;\n readonly path: string;\n readonly content: string;\n readonly exists: boolean;\n /** The file already holds exactly these bytes — nothing to write. */\n readonly unchanged: boolean;\n}\n\n/**\n * The wizard, start to finish. Returns an exit code (0 in every completed flow,\n * including \"declined to write anything\").\n *\n * @throws {CliUsageError} on a malformed existing config, or when input ends before\n * the wizard finishes — both are usage-shaped, and nothing further is written.\n */\nexport async function runInit(io: InitIo): Promise<number> {\n // `answerReader` — not `Readable.from` + readline — because the wizard must *let go*\n // of stdin when it is done. See the note on `answerReader` for the hang that shape\n // caused: files written, `Done.` printed, and the process never exiting.\n const wizard = wizardAsk(\n io.input,\n io.output,\n `${CLI_NAME} init: input ended before the wizard finished. ` +\n `Files already confirmed and written stay; nothing further was written.`,\n );\n const ask = wizard.ask;\n\n try {\n const existing = loadExisting(io.cwd);\n return existing === undefined\n ? await freshRun(io, ask)\n : await editRun(io, ask, existing.path, existing.config);\n } finally {\n await wizard.release();\n }\n}\n\nfunction loadExisting(cwd: string): { path: string; config: SmeltConfig } | undefined {\n const path = findConfigFile(cwd);\n if (path === undefined) return undefined;\n // Malformed is a loud usage error, not a silent fresh start: overwriting a config\n // the user wrote, because it had a typo, would be the wizard deciding for them.\n return { path, config: parseConfig(readFileSync(path, 'utf8'), path) };\n}\n\n// ---------------------------------------------------------------------------\n// The two flows\n// ---------------------------------------------------------------------------\n\ntype Asker = (prompt: string) => Promise<string>;\n\ninterface Step {\n readonly id: 'budget' | 'store' | 'strategy' | 'measure' | 'rerank';\n run(io: InitIo, ask: Asker, choices: WizardChoices, dir: string): Promise<StepOutcome>;\n}\n\nconst STEPS: readonly Step[] = [\n { id: 'budget', run: stepBudget },\n { id: 'store', run: stepStore },\n { id: 'strategy', run: stepStrategy },\n { id: 'measure', run: stepMeasure },\n { id: 'rerank', run: stepRerank },\n];\n\nasync function freshRun(io: InitIo, ask: Asker): Promise<number> {\n const choices: WizardChoices = {\n budgetBytes: undefined,\n store: { kind: 'memory' },\n strategy: DEFAULT_STRATEGY,\n measureStub: false,\n rerankStub: false,\n hooks: undefined,\n };\n\n // Outside a workspace there is no directory question, so the first step really is the\n // first thing asked. Inside one there is, and `back` from the first step returns to it\n // — with every answer so far carried along, because reversing is not restarting.\n const root = findWorkspaceRoot(io.cwd);\n for (;;) {\n const dir = root === undefined ? io.cwd : await chooseDirectory(io, ask, root);\n io.output(\n `${CLI_NAME} init — sets up ${CONFIG_FILE_NAME} in ${dir}.\\n` +\n `A run reads the nearest ${CONFIG_FILE_NAME}, walking UP from the directory it ` +\n `is run in, so this one is found from ${dir} and everything below it.\\n` +\n `Answer \\`back\\` at any step to return to the previous one. ` +\n `Nothing is written until you confirm at the end.\\n\\n`,\n );\n if ((await runSteps(io, ask, choices, dir, root !== undefined)) === 'done') return 0;\n }\n}\n\n/**\n * The step loop for one chosen directory: every step, then the confirm, with `back`\n * reversing all the way through both. Returns `'directory'` when `back` walks off the\n * front and there is a directory question to walk back into — `freshRun` re-asks it and\n * calls this again with the same {@link WizardChoices}.\n */\nasync function runSteps(\n io: InitIo,\n ask: Asker,\n choices: WizardChoices,\n dir: string,\n canReopenDirectory: boolean,\n): Promise<'done' | 'directory'> {\n let index = 0;\n for (;;) {\n while (index < STEPS.length) {\n const outcome = await STEPS[index]!.run(io, ask, choices, dir);\n if (outcome !== 'back') {\n index += 1;\n } else if (index > 0) {\n index -= 1;\n } else if (canReopenDirectory) {\n return 'directory';\n } else {\n io.output(`This is the first step — there is nothing before it.\\n`);\n }\n }\n const verdict = await confirmAndWrite(io, ask, choices, dir);\n if (verdict !== 'back') return 'done';\n index = STEPS.length - 1;\n }\n}\n\n/**\n * Which directory this fresh run writes into — and the wizard's answer to the\n * monorepo trap.\n *\n * `smelt init` in `packages/web` used to write a config in `packages/web`, silently,\n * while discovery walks **up**: every run from the repo root then found no config and\n * used none, and the user had watched themselves set a budget. So when the working\n * directory sits inside a workspace (`pnpm-workspace.yaml`, or a `package.json` with a\n * `workspaces` field, in any ancestor), the wizard states the discovery rule and asks\n * which end of it to write to, defaulting to the root that covers the whole tree.\n *\n * The answer becomes the directory every later step, the \"About to write\" listing and\n * every `writeFileSync` uses — so the wizard still writes only inside the one directory\n * the user was shown and confirmed, here as much as anywhere else. Outside a workspace\n * there is nothing to choose and nothing is asked; the discovery rule is stated either\n * way, because \"where will runs look for this?\" is not a question a user should have to\n * infer from a path.\n *\n * Rule 3 holds here too: this is genuinely the first question, so `back` at it has\n * nowhere to go — but `back` from the first *step* comes back to it (see `freshRun`),\n * carrying the answers already given. The most consequential question in the wizard is\n * not the one you cannot reverse into.\n */\nasync function chooseDirectory(io: InitIo, ask: Asker, root: string): Promise<string> {\n io.output(\n `${CLI_NAME} init — ${io.cwd} is inside a workspace rooted at ${root}.\\n` +\n `A run reads the nearest ${CONFIG_FILE_NAME}, walking UP from the directory it is ` +\n `run in. A config written here is found from here and below; a run from ${root} ` +\n `would not find it. One at the workspace root is found from every package in it.\\n` +\n ` 1. ${root} (the workspace root)\\n` +\n ` 2. ${io.cwd} (here — this package only)\\n`,\n );\n for (;;) {\n const answer = await ask(`where should ${CONFIG_FILE_NAME} go? (1/2) [1]> `);\n if (answer === 'back') {\n io.output(`This is the first question — there is nothing before it.\\n`);\n continue;\n }\n const pick = answer === '' ? '1' : answer;\n if (pick === '1') return root;\n if (pick === '2') return io.cwd;\n io.output(`1 for the workspace root, 2 for here.\\n`);\n }\n}\n\n/**\n * The nearest **ancestor** of `cwd` that declares a workspace, or `undefined`.\n *\n * Ancestors only: a `cwd` that is itself the workspace root has nothing to choose\n * between, so it is asked nothing. Two markers, because those are the two the\n * ecosystem writes — pnpm's `pnpm-workspace.yaml`, and the `workspaces` field npm,\n * yarn and bun read out of `package.json`. Detection never reads a lockfile and never\n * shells out; a `package.json` it cannot parse claims nothing, so it is not a root.\n */\nexport function findWorkspaceRoot(cwd: string): string | undefined {\n let dir = resolve(cwd);\n for (;;) {\n const parent = dirname(dir);\n if (parent === dir) return undefined;\n dir = parent;\n if (declaresWorkspace(dir)) return dir;\n }\n}\n\nfunction declaresWorkspace(dir: string): boolean {\n if (existsSync(join(dir, 'pnpm-workspace.yaml'))) return true;\n const manifest = join(dir, 'package.json');\n if (!existsSync(manifest)) return false;\n let workspaces: unknown;\n try {\n ({ workspaces } = JSON.parse(readFileSync(manifest, 'utf8')) as { workspaces?: unknown });\n } catch {\n return false; // a package.json smelt cannot read is not a claim about anything\n }\n return Array.isArray(workspaces) || (typeof workspaces === 'object' && workspaces !== null);\n}\n\nasync function editRun(\n io: InitIo,\n ask: Asker,\n configPath: string,\n config: SmeltConfig,\n): Promise<number> {\n const dir = dirname(configPath);\n const choices: WizardChoices = {\n budgetBytes: config.defaultBudgetBytes,\n store: config.store ?? { kind: 'memory' },\n strategy: config.strategy ?? DEFAULT_STRATEGY,\n measureStub: false,\n rerankStub: false,\n hooks: config.hooks,\n };\n\n io.output(\n `${CLI_NAME} init — ${CONFIG_FILE_NAME} already exists at ${configPath}.\\n` +\n `Change one setting at a time; nothing is written until you confirm.\\n`,\n );\n\n for (;;) {\n io.output(`\\nCurrent values:\\n${summary(choices, dir)}\\n`);\n const answer = await ask(\n `Change which setting? (budget / store / strategy / measure / rerank, ` +\n `\\`done\\` to review and confirm, \\`back\\` to leave without writing)\\n> `,\n );\n if (answer === 'back') {\n io.output(`Left as it was. Nothing has been written.\\n`);\n return 0;\n }\n if (answer === 'done') {\n const verdict = await confirmAndWrite(io, ask, choices, dir);\n if (verdict !== 'back') return 0;\n continue; // back from confirm returns to this menu\n }\n const step = STEPS.find((candidate) => candidate.id === answer);\n if (step === undefined) {\n io.output(`Not a setting: \"${answer}\". One of: budget, store, strategy, measure, rerank.\\n`);\n continue;\n }\n await step.run(io, ask, choices, dir); // its own `back` returns here\n }\n}\n\nfunction summary(choices: WizardChoices, dir: string): string {\n const stubLine = (file: string, generate: boolean): string => {\n if (generate) return `generate ${file}`;\n return existsSync(join(dir, file)) ? `${file} exists (kept as is)` : 'none';\n };\n return [\n ` budget: ${choices.budgetBytes === undefined ? '(not set)' : `${String(choices.budgetBytes)} bytes`}`,\n ` store: ${choices.store.kind === 'memory' ? 'memory' : `directory (${choices.store.path})`}`,\n ` strategy: ${choices.strategy}`,\n ` measure: ${stubLine(MEASURE_STUB_FILE, choices.measureStub)}`,\n ` rerank: ${stubLine(RERANK_STUB_FILE, choices.rerankStub)}`,\n ].join('\\n');\n}\n\n// ---------------------------------------------------------------------------\n// The steps. Each one accepts `back`.\n// ---------------------------------------------------------------------------\n\nasync function stepBudget(io: InitIo, ask: Asker, choices: WizardChoices): Promise<StepOutcome> {\n io.output(\n `\\nDefault byte budget — used when a \\`${CLI_NAME}\\` run omits --budget.\\n` +\n `Budgets are UTF-8 bytes, permanently; an explicit --budget always wins.\\n` +\n `There is no suggested number: the right budget depends on your traffic, and ` +\n `smelt does not invent numbers it has not measured.\\n`,\n );\n for (;;) {\n const current = choices.budgetBytes === undefined ? '' : ` [${String(choices.budgetBytes)}]`;\n const answer = await ask(`budget in bytes${current} (or back)> `);\n if (answer === 'back') return 'back';\n if (answer === '' && choices.budgetBytes !== undefined) return 'ok';\n if (/^\\d+$/.test(answer) && Number(answer) > 0) {\n choices.budgetBytes = Number(answer);\n return 'ok';\n }\n io.output(`A whole number of bytes greater than zero, e.g. 4000.\\n`);\n }\n}\n\nasync function stepStore(io: InitIo, ask: Asker, choices: WizardChoices): Promise<StepOutcome> {\n io.output(\n `\\nWhere elided bytes live. Every elision is reversible only while a store holds ` +\n `its bytes (Law 3):\\n` +\n ` 1. memory — per-process; retrievals do not survive the process\\n` +\n ` 2. directory — persistent, content-addressed, on disk; retrieval counters ` +\n `survive restarts\\n`,\n );\n for (;;) {\n const current = choices.store.kind === 'memory' ? '1' : '2';\n const answer = await ask(`store (1/2) [${current}] (or back)> `);\n if (answer === 'back') return 'back';\n const pick = answer === '' ? current : answer;\n if (pick === '1') {\n choices.store = { kind: 'memory' };\n return 'ok';\n }\n if (pick === '2') {\n const previous =\n choices.store.kind === 'directory' ? choices.store.path : SETUP_RECIPE.store.defaultDir;\n const path = await ask(`store directory, relative to ${CONFIG_FILE_NAME} [${previous}]> `);\n if (path === 'back') continue; // back to the store choice, not out of the step\n choices.store = { kind: 'directory', path: path === '' ? previous : path };\n return 'ok';\n }\n io.output(`1 for memory, 2 for directory, or back.\\n`);\n }\n}\n\n/**\n * One sentence per strategy, for the wizard's menu — `Record<Strategy, string>`, so a\n * strategy added to the `PLANNERS` registry without a line here is a compile error\n * rather than an option the wizard silently never offers. The menu itself, its\n * numbering and the re-prompt are all rendered from {@link STRATEGIES}, which is why\n * the third choice cost one entry in this table.\n */\nconst STRATEGY_BLURB: Readonly<Record<Strategy, string>> = {\n lexical: 'line windows around your focus terms; works on any text',\n structural:\n `parses ${String(STRUCTURAL_LANGUAGES.length)} languages with bundled grammars ` +\n `(${STRUCTURAL_LANGUAGES.join(', ')}) and collapses siblings by name; refuses ` +\n `other languages rather than approximating`,\n auto:\n 'structural for those languages and lexical for everything else; the result ' +\n 'names whichever one actually ran',\n};\n\nasync function stepStrategy(io: InitIo, ask: Asker, choices: WizardChoices): Promise<StepOutcome> {\n const picks = STRATEGIES.map((_, index) => String(index + 1));\n io.output(\n `\\nDefault planner strategy — used when a run omits --strategy:\\n` +\n STRATEGIES.map(\n (name, index) => ` ${picks[index]!}. ${name.padEnd(12)}— ${STRATEGY_BLURB[name]}\\n`,\n ).join(''),\n );\n for (;;) {\n const current = picks[STRATEGIES.indexOf(choices.strategy)]!;\n const answer = await ask(`strategy (${picks.join('/')}) [${current}] (or back)> `);\n if (answer === 'back') return 'back';\n const pick = answer === '' ? current : answer;\n const index = picks.indexOf(pick);\n if (index >= 0) {\n choices.strategy = STRATEGIES[index]!;\n return 'ok';\n }\n io.output(`${STRATEGIES.map((name, i) => `${picks[i]!} for ${name}`).join(', ')}, or back.\\n`);\n }\n}\n\nasync function stepMeasure(\n io: InitIo,\n ask: Asker,\n choices: WizardChoices,\n dir: string,\n): Promise<StepOutcome> {\n const exists = existsSync(join(dir, MEASURE_STUB_FILE));\n io.output(\n `\\nMeasure hook — your own counter (a tokenizer, usually), so results carry a ` +\n `second, labelled number next to the byte counts. smelt ships none: a token ` +\n `count without its tokenizer named is not a measurement.\\n` +\n ` 1. none\\n` +\n ` 2. generate ${MEASURE_STUB_FILE}, a typed stub you fill in\\n` +\n (exists ? `(${MEASURE_STUB_FILE} already exists; it is never deleted from here.)\\n` : ``),\n );\n for (;;) {\n const answer = await ask(`measure (1/2) [${choices.measureStub ? '2' : '1'}] (or back)> `);\n if (answer === 'back') return 'back';\n const pick = answer === '' ? (choices.measureStub ? '2' : '1') : answer;\n if (pick === '1' || pick === '2') {\n choices.measureStub = pick === '2';\n return 'ok';\n }\n io.output(`1 for none, 2 to generate the stub, or back.\\n`);\n }\n}\n\nasync function stepRerank(\n io: InitIo,\n ask: Asker,\n choices: WizardChoices,\n dir: string,\n): Promise<StepOutcome> {\n const exists = existsSync(join(dir, RERANK_STUB_FILE));\n io.output(\n `\\nReranker — a RerankStage of your own. smelt never bundles one and never will: ` +\n `an outbound call must live in your code, under your key, visible in your own ` +\n `review (Law 1).\\n` +\n ` 1. none\\n` +\n ` 2. generate ${RERANK_STUB_FILE}, a typed stub with the HTTP call sketched as a TODO\\n` +\n (exists ? `(${RERANK_STUB_FILE} already exists; it is never deleted from here.)\\n` : ``),\n );\n for (;;) {\n const answer = await ask(`rerank (1/2) [${choices.rerankStub ? '2' : '1'}] (or back)> `);\n if (answer === 'back') return 'back';\n const pick = answer === '' ? (choices.rerankStub ? '2' : '1') : answer;\n if (pick === '1' || pick === '2') {\n choices.rerankStub = pick === '2';\n return 'ok';\n }\n io.output(`1 for none, 2 to generate the stub, or back.\\n`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// The confirm step: the only place anything is written\n// ---------------------------------------------------------------------------\n\nconst writeLabel = (write: PlannedWrite): string => {\n if (write.unchanged) return 'unchanged — nothing to write';\n return write.exists ? 'exists — will ask before overwriting' : 'new';\n};\n\nasync function confirmAndWrite(\n io: InitIo,\n ask: Asker,\n choices: WizardChoices,\n dir: string,\n): Promise<'done' | 'back'> {\n if (choices.budgetBytes === undefined) {\n // Unreachable in the fresh flow (the budget step requires a number) but reachable\n // from an edit of a config that never had one — `defaultBudgetBytes` is optional,\n // so such a config parses fine. The wizard still insists: a confirm without a\n // budget would write a file this wizard just called incomplete. `back` here is a\n // real `back` — it returns to the caller, never falls forward into the confirm.\n io.output(`No budget is set yet — set one before confirming.\\n`);\n if ((await stepBudget(io, ask, choices)) === 'back') return 'back';\n }\n\n const writes = plannedWrites(choices, dir);\n io.output(\n `\\nAbout to write, into ${dir}:\\n` +\n writes.map((write) => ` ${write.name.padEnd(20)} (${writeLabel(write)})\\n`).join('') +\n `Nothing has been written yet.\\n`,\n );\n\n for (;;) {\n const answer = await ask(`confirm (yes / no / back)> `);\n if (answer === 'back') return 'back';\n if (answer === 'no') {\n io.output(`Nothing was written.\\n`);\n return 'done';\n }\n if (answer === 'yes') break;\n io.output(`yes to write, no to leave everything untouched, back to change a setting.\\n`);\n }\n\n for (const write of writes) {\n if (write.unchanged) {\n io.output(` ${write.name} — unchanged, not rewritten\\n`);\n continue;\n }\n if (write.exists) {\n // The per-file consent rule: one explicit question per existing file, and only\n // a literal `yes` overwrites. This is the line the mutation\n // `init-overwrite-without-consent` breaks to prove the guard can go red.\n const answer = await ask(` ${write.name} exists — overwrite it? (yes/no)> `);\n if (answer !== 'yes') {\n io.output(` skipped ${write.name} — the existing file was not touched\\n`);\n continue;\n }\n }\n writeFileSync(write.path, write.content);\n io.output(` wrote ${write.name}\\n`);\n }\n io.output(`Done.\\n`);\n io.output(\n `Also: \\`${CLI_NAME} hooks install\\` wires the smelt guard into agent-harness ` +\n `hooks (Claude Code, Codex, and more) — it detects installed harnesses and asks ` +\n `before writing anything.\\n`,\n );\n return 'done';\n}\n\nfunction plannedWrites(choices: WizardChoices, dir: string): readonly PlannedWrite[] {\n const plan = (name: string, content: string): PlannedWrite => {\n const path = join(dir, name);\n const exists = existsSync(path);\n const unchanged = exists && readFileSync(path, 'utf8') === content;\n return { name, path, content, exists, unchanged };\n };\n const writes = [plan(CONFIG_FILE_NAME, renderConfig(chosenConfig(choices)))];\n if (choices.measureStub) writes.push(plan(MEASURE_STUB_FILE, measureStubSource()));\n if (choices.rerankStub) writes.push(plan(RERANK_STUB_FILE, rerankStubSource()));\n return writes;\n}\n\n/**\n * The config the wizard's choices mean — the wizard's *policy*, handed to the one\n * writer in `config.ts`. `strategy` and `store` are always written: the wizard asked\n * about both, and a chosen value left out of the file would be a setting the user\n * watched themselves pick and never got.\n */\nfunction chosenConfig(choices: {\n readonly budgetBytes: number | undefined;\n readonly store: SmeltConfigStore;\n readonly strategy: Strategy;\n /** Carried through from an existing config; this wizard never edits it. */\n readonly hooks?: SmeltConfigHooks | undefined;\n}): SmeltConfig {\n return {\n smeltConfig: CONFIG_VERSION,\n ...(choices.budgetBytes === undefined ? {} : { defaultBudgetBytes: choices.budgetBytes }),\n strategy: choices.strategy,\n store: choices.store,\n ...(choices.hooks === undefined ? {} : { hooks: choices.hooks }),\n };\n}\n\n// ---------------------------------------------------------------------------\n// The generated stubs. String literals on purpose: smelt's own import graph must not\n// gain an HTTP client, and the zero-network guard's string-stripper ignores string\n// bodies — the sketched fetch below lives in the CONSUMER'S file, never in smelt's.\n//\n// The comment token between `from` and the package name in each stub's import line is\n// deliberate: the guard's import scanner reads raw source, so spelling that import\n// plainly inside this template would register as an edge in smelt's own graph — which\n// it is not. The comment keeps the generated file valid TypeScript while keeping this\n// data out of the guard's walk.\n// ---------------------------------------------------------------------------\n\n/** The `smelt.measure.ts` the wizard writes. Exported so tests can compile it. */\nexport function measureStubSource(): string {\n return `/**\n * Measure hook — generated by \\`smelt init\\`.\n *\n * smelt's budgets are UTF-8 bytes, permanently; this hook adds a second, labelled\n * number to every result in YOUR unit, counted by YOUR tokenizer. Both \\`id\\` and\n * \\`unit\\` are required: a count without the counter named is not a measurement.\n *\n * Wire it in, in your own code:\n *\n * const smelter = createSmelter({ defaultBudgetBytes: 8_000, measure });\n *\n * with createSmelter imported from your @smeltjs/core install and measure from here.\n */\nimport type { Measure } from /* your install */ '@smeltjs/core';\n\nexport const measure: Measure = {\n // TODO: name the counter that produces these numbers, e.g. 'tiktoken/o200k_base'.\n id: 'TODO/your-tokenizer',\n unit: 'tokens',\n count(text: string): number {\n // TODO: replace with your real tokenizer. It must be local and synchronous —\n // a count() that calls an API would make your process call an API on every smelt.\n // e.g. return encode(text).length;\n throw new Error(\n 'smelt.measure.ts: count() is not implemented yet — ' +\n 'fill it in with your tokenizer (length of input: ' + String(text.length) + ')',\n );\n },\n};\n`;\n}\n\n/** The `smelt.rerank.ts` the wizard writes. Exported so tests can compile it. */\nexport function rerankStubSource(): string {\n return `/**\n * Reranker stub — generated by \\`smelt init\\`.\n *\n * smelt itself makes zero network calls and never bundles a reranker (Law 1): the\n * moment one ships as a default, every consumer's source code leaves the machine and\n * they find out from a changelog, or never. So the outbound call lives HERE, in your\n * file, reading your env var, visible in your own review.\n *\n * Wire it into your own pipeline; smelt never calls this for you.\n */\nimport type { RerankCandidate, RerankedCandidate, RerankStage } from /* your install */ '@smeltjs/core';\n\n/** The env var YOUR code reads. Rename it to match your vendor. */\nconst API_KEY_ENV = 'RERANKER_API_KEY';\n\nexport const rerank: RerankStage = {\n id: 'my-reranker/v1',\n async rerank(\n candidates: readonly RerankCandidate[],\n query: string,\n ): Promise<readonly RerankedCandidate[]> {\n const apiKey = process.env[API_KEY_ENV];\n if (apiKey === undefined || apiKey === '') {\n throw new Error(\n 'smelt.rerank.ts: ' + API_KEY_ENV + ' is not set. This stage makes an outbound ' +\n 'HTTP call from YOUR code with YOUR key; without a key it refuses to pretend.',\n );\n }\n\n // TODO: the outbound call. Voyage AI is one example vendor; any reranker with an\n // HTTP API fits this shape:\n //\n // const response = await fetch('https://api.voyageai.com/v1/rerank', {\n // method: 'POST',\n // headers: {\n // 'content-type': 'application/json',\n // authorization: \\`Bearer \\${apiKey}\\`,\n // },\n // body: JSON.stringify({\n // query,\n // documents: candidates.map((candidate) => candidate.text),\n // }),\n // });\n // if (!response.ok) throw new Error('rerank failed: ' + String(response.status));\n // const body = (await response.json()) as {\n // data: { index: number; relevance_score: number }[];\n // };\n // return body.data.map(({ index, relevance_score }) => ({\n // ...candidates[index]!,\n // score: relevance_score,\n // }));\n //\n void candidates;\n void query;\n throw new Error(\n 'smelt.rerank.ts: implement the outbound call sketched above, then delete this throw.',\n );\n },\n};\n`;\n}\n"]}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { SmeltConfig } from './config.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The one reader of InstalledState (CONTEXT.md): everything smelt has written to a
|
|
4
|
+
* project, as data. The write side has been one seam since the profiles —
|
|
5
|
+
* `planInstall`/`planRemove` fold over `HarnessProfile.install` — but the read side
|
|
6
|
+
* grew three shallow readers (doctor, the hooks preset's toggle reading, setup's
|
|
7
|
+
* repair policy), and one had already drifted: doctor's text-level token search
|
|
8
|
+
* could not see a guard-only install, because the guard command carries no token.
|
|
9
|
+
* `jsonHooksContainOurs` fixed that instance; this module is the structural fix —
|
|
10
|
+
* the disk facts live once, and the consumers bring only their verdicts.
|
|
11
|
+
*
|
|
12
|
+
* Reads only. Nothing here writes, and nothing here decides: "behind" is a verdict
|
|
13
|
+
* against a binary version (doctor's), "repair" is a policy (setup's) — the reader
|
|
14
|
+
* states what is on disk and stops there.
|
|
15
|
+
*/
|
|
16
|
+
/** One instruction block found on disk, with the release that wrote it. */
|
|
17
|
+
export interface InstalledBlock {
|
|
18
|
+
readonly file: string;
|
|
19
|
+
/** The harness profiles whose instruction file this is (often exactly one). */
|
|
20
|
+
readonly harnesses: readonly string[];
|
|
21
|
+
/** The release that wrote it, or `undefined` when it predates stamping. */
|
|
22
|
+
readonly installedBy?: string;
|
|
23
|
+
/** False for whole-owned files that carry the token but no marker block. */
|
|
24
|
+
readonly stampable: boolean;
|
|
25
|
+
}
|
|
26
|
+
/** One MCP registration a profile declares, checked on disk. */
|
|
27
|
+
export interface InstalledMcp {
|
|
28
|
+
readonly file: string;
|
|
29
|
+
readonly server: string;
|
|
30
|
+
readonly registered: boolean;
|
|
31
|
+
}
|
|
32
|
+
/** The config as it sits: present, parseable, or malformed (a finding, not a crash). */
|
|
33
|
+
export interface InstalledConfig {
|
|
34
|
+
readonly present: boolean;
|
|
35
|
+
readonly malformed?: boolean;
|
|
36
|
+
/** The parse failure's own message, when the config is malformed. */
|
|
37
|
+
readonly malformedWhy?: string;
|
|
38
|
+
readonly parsed?: SmeltConfig;
|
|
39
|
+
readonly path?: string;
|
|
40
|
+
}
|
|
41
|
+
/** Everything the readers need, in one reading. */
|
|
42
|
+
export interface InstalledState {
|
|
43
|
+
readonly blocks: readonly InstalledBlock[];
|
|
44
|
+
readonly hookFiles: readonly string[];
|
|
45
|
+
readonly mcp: readonly InstalledMcp[];
|
|
46
|
+
readonly config: InstalledConfig;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Whether a file's current bytes already carry entries of smelt's — the one
|
|
50
|
+
* "ours" predicate for a file by name: the entry-level check for JSON hook files
|
|
51
|
+
* (the guard command carries only a shim path), the token for everything else.
|
|
52
|
+
*/
|
|
53
|
+
export declare function fileIsOurs(name: string, text: string): boolean;
|
|
54
|
+
/** Everything installed, read once. Pure reads; safe to call on any directory. */
|
|
55
|
+
export declare function readInstalledState(cwd: string): InstalledState;
|
|
56
|
+
//# sourceMappingURL=installed.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"installed.d.ts","sourceRoot":"","sources":["../../src/cli/installed.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAK/C;;;;;;;;;;;;;GAaG;AAEH,2EAA2E;AAC3E,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,2EAA2E;IAC3E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED,gEAAgE;AAChE,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAC9B;AAED,wFAAwF;AACxF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,qEAAqE;IACrE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,mDAAmD;AACnD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,SAAS,cAAc,EAAE,CAAC;IAC3C,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,GAAG,EAAE,SAAS,YAAY,EAAE,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;CAClC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED,kFAAkF;AAClF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,CAmE9D","sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { findConfigFile, parseConfig } from './config.ts';\nimport type { SmeltConfig } from './config.ts';\nimport { jsonHooksContainOurs } from './hooks.ts';\nimport { GUARD_ONLY_FILES, HARNESS_PROFILES, JSON_HOOK_FILES } from '../harness/registry.ts';\nimport { OURS_TOKEN, SNIPPET_START_MD, snippetStampVersion } from '../harness/snippet.ts';\n\n/**\n * The one reader of InstalledState (CONTEXT.md): everything smelt has written to a\n * project, as data. The write side has been one seam since the profiles —\n * `planInstall`/`planRemove` fold over `HarnessProfile.install` — but the read side\n * grew three shallow readers (doctor, the hooks preset's toggle reading, setup's\n * repair policy), and one had already drifted: doctor's text-level token search\n * could not see a guard-only install, because the guard command carries no token.\n * `jsonHooksContainOurs` fixed that instance; this module is the structural fix —\n * the disk facts live once, and the consumers bring only their verdicts.\n *\n * Reads only. Nothing here writes, and nothing here decides: \"behind\" is a verdict\n * against a binary version (doctor's), \"repair\" is a policy (setup's) — the reader\n * states what is on disk and stops there.\n */\n\n/** One instruction block found on disk, with the release that wrote it. */\nexport interface InstalledBlock {\n readonly file: string;\n /** The harness profiles whose instruction file this is (often exactly one). */\n readonly harnesses: readonly string[];\n /** The release that wrote it, or `undefined` when it predates stamping. */\n readonly installedBy?: string;\n /** False for whole-owned files that carry the token but no marker block. */\n readonly stampable: boolean;\n}\n\n/** One MCP registration a profile declares, checked on disk. */\nexport interface InstalledMcp {\n readonly file: string;\n readonly server: string;\n readonly registered: boolean;\n}\n\n/** The config as it sits: present, parseable, or malformed (a finding, not a crash). */\nexport interface InstalledConfig {\n readonly present: boolean;\n readonly malformed?: boolean;\n /** The parse failure's own message, when the config is malformed. */\n readonly malformedWhy?: string;\n readonly parsed?: SmeltConfig;\n readonly path?: string;\n}\n\n/** Everything the readers need, in one reading. */\nexport interface InstalledState {\n readonly blocks: readonly InstalledBlock[];\n readonly hookFiles: readonly string[];\n readonly mcp: readonly InstalledMcp[];\n readonly config: InstalledConfig;\n}\n\n/**\n * Whether a file's current bytes already carry entries of smelt's — the one\n * \"ours\" predicate for a file by name: the entry-level check for JSON hook files\n * (the guard command carries only a shim path), the token for everything else.\n */\nexport function fileIsOurs(name: string, text: string): boolean {\n return JSON_HOOK_FILES.includes(name) ? jsonHooksContainOurs(text) : text.includes(OURS_TOKEN);\n}\n\n/** Everything installed, read once. Pure reads; safe to call on any directory. */\nexport function readInstalledState(cwd: string): InstalledState {\n // ── instruction blocks: every profile's instruction file that exists and is ours ──\n const owners = new Map<string, string[]>();\n for (const profile of Object.values(HARNESS_PROFILES)) {\n const list = owners.get(profile.instructionFile) ?? [];\n list.push(profile.id);\n owners.set(profile.instructionFile, list);\n }\n const blocks: InstalledBlock[] = [];\n for (const [file, harnesses] of owners) {\n const path = join(cwd, file);\n if (!existsSync(path)) continue;\n const text = readFileSync(path, 'utf8');\n if (!text.includes(OURS_TOKEN)) continue;\n const installedBy = snippetStampVersion(text);\n blocks.push({\n file,\n harnesses,\n ...(installedBy === undefined ? {} : { installedBy }),\n stampable: text.includes(SNIPPET_START_MD),\n });\n }\n\n // ── hook wiring: JSON hook files and guard-only shims that carry our entries ──\n const hookFiles: string[] = [];\n for (const name of [...JSON_HOOK_FILES, ...GUARD_ONLY_FILES]) {\n const path = join(cwd, name);\n if (!existsSync(path)) continue;\n if (fileIsOurs(name, readFileSync(path, 'utf8'))) hookFiles.push(name);\n }\n\n // ── MCP registrations: every profile's declared step, checked on disk ──\n const mcp = new Map<string, InstalledMcp>();\n for (const profile of Object.values(HARNESS_PROFILES)) {\n for (const step of profile.install) {\n if (step.kind !== 'mcp-registration') continue;\n const key = `${step.file}·${step.path[1]}`;\n if (mcp.has(key)) continue;\n mcp.set(key, {\n file: step.file,\n server: step.path[1],\n registered: mcpEntryRegistered(cwd, step.file, step.path),\n });\n }\n }\n\n // ── the config ──\n const configPath = findConfigFile(cwd);\n let config: InstalledConfig = { present: false };\n if (configPath !== undefined) {\n try {\n config = {\n present: true,\n path: configPath,\n parsed: parseConfig(readFileSync(configPath, 'utf8'), configPath),\n };\n } catch (error) {\n config = {\n present: true,\n malformed: true,\n malformedWhy: error instanceof Error ? error.message : String(error),\n path: configPath,\n };\n }\n }\n\n return { blocks, hookFiles, mcp: [...mcp.values()], config };\n}\n\n/** The server entry a profile declares, present and parseable on disk or not. */\nfunction mcpEntryRegistered(cwd: string, file: string, path: readonly [string, string]): boolean {\n const full = join(cwd, file);\n if (!existsSync(full)) return false;\n try {\n const parsed: unknown = JSON.parse(readFileSync(full, 'utf8'));\n if (typeof parsed !== 'object' || parsed === null) return false;\n const container = (parsed as Record<string, unknown>)[path[0]];\n if (typeof container !== 'object' || container === null) return false;\n return (container as Record<string, unknown>)[path[1]] !== undefined;\n } catch {\n return false;\n }\n}\n"]}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { findConfigFile, parseConfig } from './config.js';
|
|
4
|
+
import { jsonHooksContainOurs } from './hooks.js';
|
|
5
|
+
import { GUARD_ONLY_FILES, HARNESS_PROFILES, JSON_HOOK_FILES } from '../harness/registry.js';
|
|
6
|
+
import { OURS_TOKEN, SNIPPET_START_MD, snippetStampVersion } from '../harness/snippet.js';
|
|
7
|
+
/**
|
|
8
|
+
* Whether a file's current bytes already carry entries of smelt's — the one
|
|
9
|
+
* "ours" predicate for a file by name: the entry-level check for JSON hook files
|
|
10
|
+
* (the guard command carries only a shim path), the token for everything else.
|
|
11
|
+
*/
|
|
12
|
+
export function fileIsOurs(name, text) {
|
|
13
|
+
return JSON_HOOK_FILES.includes(name) ? jsonHooksContainOurs(text) : text.includes(OURS_TOKEN);
|
|
14
|
+
}
|
|
15
|
+
/** Everything installed, read once. Pure reads; safe to call on any directory. */
|
|
16
|
+
export function readInstalledState(cwd) {
|
|
17
|
+
// ── instruction blocks: every profile's instruction file that exists and is ours ──
|
|
18
|
+
const owners = new Map();
|
|
19
|
+
for (const profile of Object.values(HARNESS_PROFILES)) {
|
|
20
|
+
const list = owners.get(profile.instructionFile) ?? [];
|
|
21
|
+
list.push(profile.id);
|
|
22
|
+
owners.set(profile.instructionFile, list);
|
|
23
|
+
}
|
|
24
|
+
const blocks = [];
|
|
25
|
+
for (const [file, harnesses] of owners) {
|
|
26
|
+
const path = join(cwd, file);
|
|
27
|
+
if (!existsSync(path))
|
|
28
|
+
continue;
|
|
29
|
+
const text = readFileSync(path, 'utf8');
|
|
30
|
+
if (!text.includes(OURS_TOKEN))
|
|
31
|
+
continue;
|
|
32
|
+
const installedBy = snippetStampVersion(text);
|
|
33
|
+
blocks.push({
|
|
34
|
+
file,
|
|
35
|
+
harnesses,
|
|
36
|
+
...(installedBy === undefined ? {} : { installedBy }),
|
|
37
|
+
stampable: text.includes(SNIPPET_START_MD),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
// ── hook wiring: JSON hook files and guard-only shims that carry our entries ──
|
|
41
|
+
const hookFiles = [];
|
|
42
|
+
for (const name of [...JSON_HOOK_FILES, ...GUARD_ONLY_FILES]) {
|
|
43
|
+
const path = join(cwd, name);
|
|
44
|
+
if (!existsSync(path))
|
|
45
|
+
continue;
|
|
46
|
+
if (fileIsOurs(name, readFileSync(path, 'utf8')))
|
|
47
|
+
hookFiles.push(name);
|
|
48
|
+
}
|
|
49
|
+
// ── MCP registrations: every profile's declared step, checked on disk ──
|
|
50
|
+
const mcp = new Map();
|
|
51
|
+
for (const profile of Object.values(HARNESS_PROFILES)) {
|
|
52
|
+
for (const step of profile.install) {
|
|
53
|
+
if (step.kind !== 'mcp-registration')
|
|
54
|
+
continue;
|
|
55
|
+
const key = `${step.file}·${step.path[1]}`;
|
|
56
|
+
if (mcp.has(key))
|
|
57
|
+
continue;
|
|
58
|
+
mcp.set(key, {
|
|
59
|
+
file: step.file,
|
|
60
|
+
server: step.path[1],
|
|
61
|
+
registered: mcpEntryRegistered(cwd, step.file, step.path),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// ── the config ──
|
|
66
|
+
const configPath = findConfigFile(cwd);
|
|
67
|
+
let config = { present: false };
|
|
68
|
+
if (configPath !== undefined) {
|
|
69
|
+
try {
|
|
70
|
+
config = {
|
|
71
|
+
present: true,
|
|
72
|
+
path: configPath,
|
|
73
|
+
parsed: parseConfig(readFileSync(configPath, 'utf8'), configPath),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
config = {
|
|
78
|
+
present: true,
|
|
79
|
+
malformed: true,
|
|
80
|
+
malformedWhy: error instanceof Error ? error.message : String(error),
|
|
81
|
+
path: configPath,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return { blocks, hookFiles, mcp: [...mcp.values()], config };
|
|
86
|
+
}
|
|
87
|
+
/** The server entry a profile declares, present and parseable on disk or not. */
|
|
88
|
+
function mcpEntryRegistered(cwd, file, path) {
|
|
89
|
+
const full = join(cwd, file);
|
|
90
|
+
if (!existsSync(full))
|
|
91
|
+
return false;
|
|
92
|
+
try {
|
|
93
|
+
const parsed = JSON.parse(readFileSync(full, 'utf8'));
|
|
94
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
95
|
+
return false;
|
|
96
|
+
const container = parsed[path[0]];
|
|
97
|
+
if (typeof container !== 'object' || container === null)
|
|
98
|
+
return false;
|
|
99
|
+
return container[path[1]] !== undefined;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=installed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"installed.js","sourceRoot":"","sources":["../../src/cli/installed.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { findConfigFile, parseConfig } from './config.ts';\nimport type { SmeltConfig } from './config.ts';\nimport { jsonHooksContainOurs } from './hooks.ts';\nimport { GUARD_ONLY_FILES, HARNESS_PROFILES, JSON_HOOK_FILES } from '../harness/registry.ts';\nimport { OURS_TOKEN, SNIPPET_START_MD, snippetStampVersion } from '../harness/snippet.ts';\n\n/**\n * The one reader of InstalledState (CONTEXT.md): everything smelt has written to a\n * project, as data. The write side has been one seam since the profiles —\n * `planInstall`/`planRemove` fold over `HarnessProfile.install` — but the read side\n * grew three shallow readers (doctor, the hooks preset's toggle reading, setup's\n * repair policy), and one had already drifted: doctor's text-level token search\n * could not see a guard-only install, because the guard command carries no token.\n * `jsonHooksContainOurs` fixed that instance; this module is the structural fix —\n * the disk facts live once, and the consumers bring only their verdicts.\n *\n * Reads only. Nothing here writes, and nothing here decides: \"behind\" is a verdict\n * against a binary version (doctor's), \"repair\" is a policy (setup's) — the reader\n * states what is on disk and stops there.\n */\n\n/** One instruction block found on disk, with the release that wrote it. */\nexport interface InstalledBlock {\n readonly file: string;\n /** The harness profiles whose instruction file this is (often exactly one). */\n readonly harnesses: readonly string[];\n /** The release that wrote it, or `undefined` when it predates stamping. */\n readonly installedBy?: string;\n /** False for whole-owned files that carry the token but no marker block. */\n readonly stampable: boolean;\n}\n\n/** One MCP registration a profile declares, checked on disk. */\nexport interface InstalledMcp {\n readonly file: string;\n readonly server: string;\n readonly registered: boolean;\n}\n\n/** The config as it sits: present, parseable, or malformed (a finding, not a crash). */\nexport interface InstalledConfig {\n readonly present: boolean;\n readonly malformed?: boolean;\n /** The parse failure's own message, when the config is malformed. */\n readonly malformedWhy?: string;\n readonly parsed?: SmeltConfig;\n readonly path?: string;\n}\n\n/** Everything the readers need, in one reading. */\nexport interface InstalledState {\n readonly blocks: readonly InstalledBlock[];\n readonly hookFiles: readonly string[];\n readonly mcp: readonly InstalledMcp[];\n readonly config: InstalledConfig;\n}\n\n/**\n * Whether a file's current bytes already carry entries of smelt's — the one\n * \"ours\" predicate for a file by name: the entry-level check for JSON hook files\n * (the guard command carries only a shim path), the token for everything else.\n */\nexport function fileIsOurs(name: string, text: string): boolean {\n return JSON_HOOK_FILES.includes(name) ? jsonHooksContainOurs(text) : text.includes(OURS_TOKEN);\n}\n\n/** Everything installed, read once. Pure reads; safe to call on any directory. */\nexport function readInstalledState(cwd: string): InstalledState {\n // ── instruction blocks: every profile's instruction file that exists and is ours ──\n const owners = new Map<string, string[]>();\n for (const profile of Object.values(HARNESS_PROFILES)) {\n const list = owners.get(profile.instructionFile) ?? [];\n list.push(profile.id);\n owners.set(profile.instructionFile, list);\n }\n const blocks: InstalledBlock[] = [];\n for (const [file, harnesses] of owners) {\n const path = join(cwd, file);\n if (!existsSync(path)) continue;\n const text = readFileSync(path, 'utf8');\n if (!text.includes(OURS_TOKEN)) continue;\n const installedBy = snippetStampVersion(text);\n blocks.push({\n file,\n harnesses,\n ...(installedBy === undefined ? {} : { installedBy }),\n stampable: text.includes(SNIPPET_START_MD),\n });\n }\n\n // ── hook wiring: JSON hook files and guard-only shims that carry our entries ──\n const hookFiles: string[] = [];\n for (const name of [...JSON_HOOK_FILES, ...GUARD_ONLY_FILES]) {\n const path = join(cwd, name);\n if (!existsSync(path)) continue;\n if (fileIsOurs(name, readFileSync(path, 'utf8'))) hookFiles.push(name);\n }\n\n // ── MCP registrations: every profile's declared step, checked on disk ──\n const mcp = new Map<string, InstalledMcp>();\n for (const profile of Object.values(HARNESS_PROFILES)) {\n for (const step of profile.install) {\n if (step.kind !== 'mcp-registration') continue;\n const key = `${step.file}·${step.path[1]}`;\n if (mcp.has(key)) continue;\n mcp.set(key, {\n file: step.file,\n server: step.path[1],\n registered: mcpEntryRegistered(cwd, step.file, step.path),\n });\n }\n }\n\n // ── the config ──\n const configPath = findConfigFile(cwd);\n let config: InstalledConfig = { present: false };\n if (configPath !== undefined) {\n try {\n config = {\n present: true,\n path: configPath,\n parsed: parseConfig(readFileSync(configPath, 'utf8'), configPath),\n };\n } catch (error) {\n config = {\n present: true,\n malformed: true,\n malformedWhy: error instanceof Error ? error.message : String(error),\n path: configPath,\n };\n }\n }\n\n return { blocks, hookFiles, mcp: [...mcp.values()], config };\n}\n\n/** The server entry a profile declares, present and parseable on disk or not. */\nfunction mcpEntryRegistered(cwd: string, file: string, path: readonly [string, string]): boolean {\n const full = join(cwd, file);\n if (!existsSync(full)) return false;\n try {\n const parsed: unknown = JSON.parse(readFileSync(full, 'utf8'));\n if (typeof parsed !== 'object' || parsed === null) return false;\n const container = (parsed as Record<string, unknown>)[path[0]];\n if (typeof container !== 'object' || container === null) return false;\n return (container as Record<string, unknown>)[path[1]] !== undefined;\n } catch {\n return false;\n }\n}\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The lava renderer — the wizards' presentation, as one adapter behind the output
|
|
3
|
+
* seam (ADR-0001: Node-native; the charm.land palette, not its Go).
|
|
4
|
+
*
|
|
5
|
+
* The spike verdict this module embodies: clack and ink could not sit *inside* the
|
|
6
|
+
* wizard loop. Their prompts own the terminal — raw mode, cursor surgery, direct
|
|
7
|
+
* stdin reads — which is exactly what the injected `AnswerStream` exists to prevent:
|
|
8
|
+
* the guards test every wizard in-process by scripting that stream, and a component
|
|
9
|
+
* that bypasses it cannot be guard-tested at all. So the renderer went one seam
|
|
10
|
+
* out: it decorates the bytes the wizards already emit, line-semantically, and
|
|
11
|
+
* switches itself off unless a real, interactive, colour-honouring terminal is on
|
|
12
|
+
* the other side. No dependency, no layout engine — and every wizard guard passes
|
|
13
|
+
* byte-identical, because `colorize(_, false)` is the identity.
|
|
14
|
+
*
|
|
15
|
+
* The rules are line-shaped, never word-shaped: ANSI codes wrap whole lines, so the
|
|
16
|
+
* text a guard asserts (`wrote CLAUDE.md`, `Nothing was written.`) stays contiguous
|
|
17
|
+
* inside the styled line. `--yes`, `--json`, piped stdin and `NO_COLOR` all mean
|
|
18
|
+
* plain bytes — a machine parsing wizard output must never parse around escape
|
|
19
|
+
* sequences.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Style one block of wizard output. `on === false` returns the text untouched —
|
|
23
|
+
* the property every guard's byte-identity leans on.
|
|
24
|
+
*/
|
|
25
|
+
export declare function colorize(text: string, on: boolean): string;
|
|
26
|
+
/**
|
|
27
|
+
* The banner an interactive wizard opens with: the title over a lava gradient bar.
|
|
28
|
+
* Returns plain text when `on` is false.
|
|
29
|
+
*/
|
|
30
|
+
export declare function lavaBanner(title: string, on: boolean): string;
|
|
31
|
+
//# sourceMappingURL=lava.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lava.d.ts","sourceRoot":"","sources":["../../src/cli/lava.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAmBH;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,GAAG,MAAM,CAsB1D;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,GAAG,MAAM,CAY7D","sourcesContent":["/**\n * The lava renderer — the wizards' presentation, as one adapter behind the output\n * seam (ADR-0001: Node-native; the charm.land palette, not its Go).\n *\n * The spike verdict this module embodies: clack and ink could not sit *inside* the\n * wizard loop. Their prompts own the terminal — raw mode, cursor surgery, direct\n * stdin reads — which is exactly what the injected `AnswerStream` exists to prevent:\n * the guards test every wizard in-process by scripting that stream, and a component\n * that bypasses it cannot be guard-tested at all. So the renderer went one seam\n * out: it decorates the bytes the wizards already emit, line-semantically, and\n * switches itself off unless a real, interactive, colour-honouring terminal is on\n * the other side. No dependency, no layout engine — and every wizard guard passes\n * byte-identical, because `colorize(_, false)` is the identity.\n *\n * The rules are line-shaped, never word-shaped: ANSI codes wrap whole lines, so the\n * text a guard asserts (`wrote CLAUDE.md`, `Nothing was written.`) stays contiguous\n * inside the styled line. `--yes`, `--json`, piped stdin and `NO_COLOR` all mean\n * plain bytes — a machine parsing wizard output must never parse around escape\n * sequences.\n */\n\n/** Where the lava gradient starts and ends, in truecolor. */\nconst LAVA_FROM = [124, 45, 18] as const; // deep ember\nconst LAVA_TO = [245, 158, 11] as const; // amber\n\nconst CODE = {\n reset: '\\x1b[0m',\n bold: '\\x1b[1m',\n dim: '\\x1b[2m',\n green: '\\x1b[32m',\n red: '\\x1b[31m',\n amber: '\\x1b[33m',\n} as const;\n\nfunction truecolor([r, g, b]: readonly number[]): string {\n return `\\x1b[38;2;${String(r)};${String(g)};${String(b)}m`;\n}\n\n/**\n * Style one block of wizard output. `on === false` returns the text untouched —\n * the property every guard's byte-identity leans on.\n */\nexport function colorize(text: string, on: boolean): string {\n if (!on) return text;\n return text\n .split('\\n')\n .map((line) => {\n if (line === '') return line;\n if (line.includes('✓') || line.trim().startsWith('Done.')) {\n return `${CODE.green}${line}${CODE.reset}`;\n }\n if (\n line.includes('✗') ||\n line.includes('ORPHAN') ||\n line.includes('MALFORMED') ||\n line.includes('MISSING')\n ) {\n return `${CODE.red}${line}${CODE.reset}`;\n }\n if (line.trim().startsWith('note:')) return `${CODE.amber}${line}${CODE.reset}`;\n if (line.endsWith('> ') || /»/u.test(line)) return `${CODE.bold}${line}${CODE.reset}`;\n return line;\n })\n .join('\\n');\n}\n\n/**\n * The banner an interactive wizard opens with: the title over a lava gradient bar.\n * Returns plain text when `on` is false.\n */\nexport function lavaBanner(title: string, on: boolean): string {\n const bar = '━'.repeat(24);\n if (!on) return `${bar}\\n ${title}\\n${bar}`;\n const steps = bar.length;\n const painted = Array.from({ length: steps }, (_, i) => {\n const t = i / (steps - 1);\n const r = Math.round(LAVA_FROM[0] + (LAVA_TO[0] - LAVA_FROM[0]) * t);\n const g = Math.round(LAVA_FROM[1] + (LAVA_TO[1] - LAVA_FROM[1]) * t);\n const b = Math.round(LAVA_FROM[2] + (LAVA_TO[2] - LAVA_FROM[2]) * t);\n return `${truecolor([r, g, b])}━`;\n }).join('');\n return `${painted}${CODE.reset}\\n ${CODE.bold}${title}${CODE.reset}\\n${painted}${CODE.reset}`;\n}\n"]}
|
package/dist/cli/lava.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The lava renderer — the wizards' presentation, as one adapter behind the output
|
|
3
|
+
* seam (ADR-0001: Node-native; the charm.land palette, not its Go).
|
|
4
|
+
*
|
|
5
|
+
* The spike verdict this module embodies: clack and ink could not sit *inside* the
|
|
6
|
+
* wizard loop. Their prompts own the terminal — raw mode, cursor surgery, direct
|
|
7
|
+
* stdin reads — which is exactly what the injected `AnswerStream` exists to prevent:
|
|
8
|
+
* the guards test every wizard in-process by scripting that stream, and a component
|
|
9
|
+
* that bypasses it cannot be guard-tested at all. So the renderer went one seam
|
|
10
|
+
* out: it decorates the bytes the wizards already emit, line-semantically, and
|
|
11
|
+
* switches itself off unless a real, interactive, colour-honouring terminal is on
|
|
12
|
+
* the other side. No dependency, no layout engine — and every wizard guard passes
|
|
13
|
+
* byte-identical, because `colorize(_, false)` is the identity.
|
|
14
|
+
*
|
|
15
|
+
* The rules are line-shaped, never word-shaped: ANSI codes wrap whole lines, so the
|
|
16
|
+
* text a guard asserts (`wrote CLAUDE.md`, `Nothing was written.`) stays contiguous
|
|
17
|
+
* inside the styled line. `--yes`, `--json`, piped stdin and `NO_COLOR` all mean
|
|
18
|
+
* plain bytes — a machine parsing wizard output must never parse around escape
|
|
19
|
+
* sequences.
|
|
20
|
+
*/
|
|
21
|
+
/** Where the lava gradient starts and ends, in truecolor. */
|
|
22
|
+
const LAVA_FROM = [124, 45, 18]; // deep ember
|
|
23
|
+
const LAVA_TO = [245, 158, 11]; // amber
|
|
24
|
+
const CODE = {
|
|
25
|
+
reset: '\x1b[0m',
|
|
26
|
+
bold: '\x1b[1m',
|
|
27
|
+
dim: '\x1b[2m',
|
|
28
|
+
green: '\x1b[32m',
|
|
29
|
+
red: '\x1b[31m',
|
|
30
|
+
amber: '\x1b[33m',
|
|
31
|
+
};
|
|
32
|
+
function truecolor([r, g, b]) {
|
|
33
|
+
return `\x1b[38;2;${String(r)};${String(g)};${String(b)}m`;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Style one block of wizard output. `on === false` returns the text untouched —
|
|
37
|
+
* the property every guard's byte-identity leans on.
|
|
38
|
+
*/
|
|
39
|
+
export function colorize(text, on) {
|
|
40
|
+
if (!on)
|
|
41
|
+
return text;
|
|
42
|
+
return text
|
|
43
|
+
.split('\n')
|
|
44
|
+
.map((line) => {
|
|
45
|
+
if (line === '')
|
|
46
|
+
return line;
|
|
47
|
+
if (line.includes('✓') || line.trim().startsWith('Done.')) {
|
|
48
|
+
return `${CODE.green}${line}${CODE.reset}`;
|
|
49
|
+
}
|
|
50
|
+
if (line.includes('✗') ||
|
|
51
|
+
line.includes('ORPHAN') ||
|
|
52
|
+
line.includes('MALFORMED') ||
|
|
53
|
+
line.includes('MISSING')) {
|
|
54
|
+
return `${CODE.red}${line}${CODE.reset}`;
|
|
55
|
+
}
|
|
56
|
+
if (line.trim().startsWith('note:'))
|
|
57
|
+
return `${CODE.amber}${line}${CODE.reset}`;
|
|
58
|
+
if (line.endsWith('> ') || /»/u.test(line))
|
|
59
|
+
return `${CODE.bold}${line}${CODE.reset}`;
|
|
60
|
+
return line;
|
|
61
|
+
})
|
|
62
|
+
.join('\n');
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The banner an interactive wizard opens with: the title over a lava gradient bar.
|
|
66
|
+
* Returns plain text when `on` is false.
|
|
67
|
+
*/
|
|
68
|
+
export function lavaBanner(title, on) {
|
|
69
|
+
const bar = '━'.repeat(24);
|
|
70
|
+
if (!on)
|
|
71
|
+
return `${bar}\n ${title}\n${bar}`;
|
|
72
|
+
const steps = bar.length;
|
|
73
|
+
const painted = Array.from({ length: steps }, (_, i) => {
|
|
74
|
+
const t = i / (steps - 1);
|
|
75
|
+
const r = Math.round(LAVA_FROM[0] + (LAVA_TO[0] - LAVA_FROM[0]) * t);
|
|
76
|
+
const g = Math.round(LAVA_FROM[1] + (LAVA_TO[1] - LAVA_FROM[1]) * t);
|
|
77
|
+
const b = Math.round(LAVA_FROM[2] + (LAVA_TO[2] - LAVA_FROM[2]) * t);
|
|
78
|
+
return `${truecolor([r, g, b])}━`;
|
|
79
|
+
}).join('');
|
|
80
|
+
return `${painted}${CODE.reset}\n ${CODE.bold}${title}${CODE.reset}\n${painted}${CODE.reset}`;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=lava.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lava.js","sourceRoot":"","sources":["../../src/cli/lava.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The lava renderer — the wizards' presentation, as one adapter behind the output\n * seam (ADR-0001: Node-native; the charm.land palette, not its Go).\n *\n * The spike verdict this module embodies: clack and ink could not sit *inside* the\n * wizard loop. Their prompts own the terminal — raw mode, cursor surgery, direct\n * stdin reads — which is exactly what the injected `AnswerStream` exists to prevent:\n * the guards test every wizard in-process by scripting that stream, and a component\n * that bypasses it cannot be guard-tested at all. So the renderer went one seam\n * out: it decorates the bytes the wizards already emit, line-semantically, and\n * switches itself off unless a real, interactive, colour-honouring terminal is on\n * the other side. No dependency, no layout engine — and every wizard guard passes\n * byte-identical, because `colorize(_, false)` is the identity.\n *\n * The rules are line-shaped, never word-shaped: ANSI codes wrap whole lines, so the\n * text a guard asserts (`wrote CLAUDE.md`, `Nothing was written.`) stays contiguous\n * inside the styled line. `--yes`, `--json`, piped stdin and `NO_COLOR` all mean\n * plain bytes — a machine parsing wizard output must never parse around escape\n * sequences.\n */\n\n/** Where the lava gradient starts and ends, in truecolor. */\nconst LAVA_FROM = [124, 45, 18] as const; // deep ember\nconst LAVA_TO = [245, 158, 11] as const; // amber\n\nconst CODE = {\n reset: '\\x1b[0m',\n bold: '\\x1b[1m',\n dim: '\\x1b[2m',\n green: '\\x1b[32m',\n red: '\\x1b[31m',\n amber: '\\x1b[33m',\n} as const;\n\nfunction truecolor([r, g, b]: readonly number[]): string {\n return `\\x1b[38;2;${String(r)};${String(g)};${String(b)}m`;\n}\n\n/**\n * Style one block of wizard output. `on === false` returns the text untouched —\n * the property every guard's byte-identity leans on.\n */\nexport function colorize(text: string, on: boolean): string {\n if (!on) return text;\n return text\n .split('\\n')\n .map((line) => {\n if (line === '') return line;\n if (line.includes('✓') || line.trim().startsWith('Done.')) {\n return `${CODE.green}${line}${CODE.reset}`;\n }\n if (\n line.includes('✗') ||\n line.includes('ORPHAN') ||\n line.includes('MALFORMED') ||\n line.includes('MISSING')\n ) {\n return `${CODE.red}${line}${CODE.reset}`;\n }\n if (line.trim().startsWith('note:')) return `${CODE.amber}${line}${CODE.reset}`;\n if (line.endsWith('> ') || /»/u.test(line)) return `${CODE.bold}${line}${CODE.reset}`;\n return line;\n })\n .join('\\n');\n}\n\n/**\n * The banner an interactive wizard opens with: the title over a lava gradient bar.\n * Returns plain text when `on` is false.\n */\nexport function lavaBanner(title: string, on: boolean): string {\n const bar = '━'.repeat(24);\n if (!on) return `${bar}\\n ${title}\\n${bar}`;\n const steps = bar.length;\n const painted = Array.from({ length: steps }, (_, i) => {\n const t = i / (steps - 1);\n const r = Math.round(LAVA_FROM[0] + (LAVA_TO[0] - LAVA_FROM[0]) * t);\n const g = Math.round(LAVA_FROM[1] + (LAVA_TO[1] - LAVA_FROM[1]) * t);\n const b = Math.round(LAVA_FROM[2] + (LAVA_TO[2] - LAVA_FROM[2]) * t);\n return `${truecolor([r, g, b])}━`;\n }).join('');\n return `${painted}${CODE.reset}\\n ${CODE.bold}${title}${CODE.reset}\\n${painted}${CODE.reset}`;\n}\n"]}
|