@koda-sl/baker-cli 0.152.0 → 0.153.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/README.md +5 -2
- package/dist/cli.js +64 -46
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4668,10 +4668,12 @@ Without `--slug` the command behaves as before (one creative record per publishe
|
|
|
4668
4668
|
Deterministic design-quality critic for landing pages — an offline, no-LLM detector for the well-known "AI slop" tells (gradient text, overused fonts like Inter, side-tab colored borders, cream/parchment grounds, purple-on-heading palettes, buzzword copy, broken images, and more). It scores a landing's `.astro` source and returns findings tiered **block / warn / advisory**.
|
|
4669
4669
|
|
|
4670
4670
|
```bash
|
|
4671
|
-
baker landing critique spring-offer
|
|
4672
|
-
baker landing critique spring-offer --full
|
|
4671
|
+
baker landing critique spring-offer # score src/pages/spring-offer/
|
|
4672
|
+
baker landing critique spring-offer --full # also print advisory-tier findings
|
|
4673
|
+
baker landing critique spring-offer summer-offer claude # score three landings in one call
|
|
4673
4674
|
```
|
|
4674
4675
|
|
|
4676
|
+
- **Takes several slugs at once.** `data.landings` is always an array with one entry per slug (`{ slug, overall, counts, dimensions, findings }`). For a single slug those fields are also spread at the top of `data`, so existing one-landing readers are unaffected. A wrong slug anywhere in the batch fails the whole call before any landing is scored.
|
|
4675
4677
|
- **Advisory by design — findings never fail it.** A finding is guidance, not a gate; the command errors (exit 2) only on a wrong slug (`NOT_FOUND` / `INVALID_SLUG`, with `error.fix.availableSlugs`).
|
|
4676
4678
|
- **BRAND.md is the allowlist.** A font, hue, or ground that the client's `src/brand/BRAND.md` (mirrored into `src/styles/global.css` `@theme`, hex or oklch) commits is a decision, not a tell — matched values downgrade to advisory `brandDrift` so the critic never fights a real brand.
|
|
4677
4679
|
- **Records a freshness snapshot** to `.cache/landing-critique/<slug>.json` (the source hash it scored). The publish quality gate reads it to enforce that every changed landing was critiqued and ships no block-tier tell. Re-run after edits so the snapshot stays fresh.
|
|
@@ -4739,6 +4741,7 @@ This CLI is designed for AI agent consumption. Key patterns:
|
|
|
4739
4741
|
- **0.119.0**: `draft amend`/`draft show` land on both `baker ads google` and `baker ads linkedin` — a generic JSON-merge-patch to update any staged op in place plus a full-payload receipt, replacing remove+recreate as the correction path. Google gains `assets update` and `asset-groups create|update` (Performance Max asset groups are now their own entity — `ads create --format performanceMaxAssetGroup` never worked and is gone); `ads create --format video` moves from a bare YouTube id to `--video-assets` refs (**breaking flag change** — stage the video as an asset first); `--format demandGen` gains `--image-assets`/`--square-image-assets`/`--logo-image-assets` and flag-building for headlines/descriptions. LinkedIn's `draft list` now renders a readable Campaign group ▸ Campaign ▸ Creative tree by default (`--json` for raw), `creatives update` gains `--campaign` (re-parent while staged), and `campaigns update` passes create-only fields (`--group`/`--type`/`--locale`/`--associated-entity`) through when amending a `li_temp_*` staged create instead of always stripping them.
|
|
4740
4742
|
- **0.150.0**: `baker landing critique` gains an `agent-washing` rule in the `copy` family — an autonomy claim ("fully autonomous", "while you sleep", "no human intervention") with no signal anywhere on the page about who oversees the agent (approval, review, override, undo/rollback, audit log, escalation) scores a warn. Page-scope, so a hero may defer the trust story to a later section; generic privacy boilerplate does not clear it. `CRITIC_VERSION` bumps to `2`.
|
|
4741
4743
|
- **0.152.0**: `baker ads linkedin campaigns url-params <ad-set-id>` stages an ad set's URL tracking parameters (LinkedIn's `adTrackingParameters`) — `--param key=value` for fixed values (repeatable or `&`-joined), `--dynamic key=PLACEHOLDER` for values LinkedIn fills in per ad (`CAMPAIGN_NAME`, `CREATIVE_ID`, …), `--clear` to remove them. LinkedIn appends these to the landing URL of every ad in the ad set, including ads already running, so this replaces stamping the same UTM onto each ad's `--landing-url` — which missed later ads and double-appended keys the ad set already set. Account-level parameters remain UI-only (LinkedIn exposes no API for them).
|
|
4744
|
+
- **0.153.0**: `baker landing critique` accepts several slugs in one call (`baker landing critique a b c`). `data.landings[]` always carries one entry per slug; a single slug additionally keeps the old top-level `data.slug`/`counts`/`dimensions`/`findings` shape, so existing readers are unaffected. An invalid or missing slug anywhere in the batch still fails the whole call up front. The scaffold's Stop-hook critique gate now emits one batched command instead of one line per stale landing.
|
|
4742
4745
|
|
|
4743
4746
|
## Publishing
|
|
4744
4747
|
|
package/dist/cli.js
CHANGED
|
@@ -28065,7 +28065,11 @@ registerSchema({
|
|
|
28065
28065
|
command: "landing.critique",
|
|
28066
28066
|
description: "Start here: `baker landing critique my-landing` after building or editing a landing. Deterministic design-quality critic (ADVISORY \u2014 findings never fail it). Flags the known AI 'slop' tells (gradient text, overused fonts, side-tab borders, cream palettes, buzzword copy, broken images\u2026) tiered block/warn/advisory, and records the critique the publish quality gate requires.",
|
|
28067
28067
|
args: {
|
|
28068
|
-
slug: {
|
|
28068
|
+
slug: {
|
|
28069
|
+
type: "string",
|
|
28070
|
+
description: "Landing slug (folder under src/pages/). Pass several, space-separated, to critique them in one call.",
|
|
28071
|
+
required: true
|
|
28072
|
+
},
|
|
28069
28073
|
full: {
|
|
28070
28074
|
type: "boolean",
|
|
28071
28075
|
description: "Also print advisory-tier findings (default: block + warn only)",
|
|
@@ -28074,6 +28078,11 @@ registerSchema({
|
|
|
28074
28078
|
}
|
|
28075
28079
|
});
|
|
28076
28080
|
var SLUG_RE = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
28081
|
+
function parseCritiqueSlugs(args) {
|
|
28082
|
+
const rest = Array.isArray(args._) ? args._ : [];
|
|
28083
|
+
const all = [args.slug, ...rest].filter((s) => typeof s === "string" && s.length > 0);
|
|
28084
|
+
return [...new Set(all.map(String))];
|
|
28085
|
+
}
|
|
28077
28086
|
function fail5(code, message, fix) {
|
|
28078
28087
|
process.stderr.write(
|
|
28079
28088
|
`${JSON.stringify({ ok: false, error: { code, message, ...fix ? { fix } : {} } }, null, 2)}
|
|
@@ -28087,63 +28096,53 @@ var critiqueCommand2 = defineCommand135({
|
|
|
28087
28096
|
description: "Start here: `baker landing critique <slug>` after building or editing a landing. Deterministic design-quality critic (ADVISORY \u2014 findings never fail it). Flags the known AI design tells (gradient text, overused fonts, side-tab borders, cream palettes, buzzword copy, broken images) tiered block/warn/advisory, respecting the client's BRAND.md as the allowlist. Also records the critique that publishing requires \u2014 run it before finishing a landing."
|
|
28088
28097
|
},
|
|
28089
28098
|
args: {
|
|
28090
|
-
slug: {
|
|
28099
|
+
slug: {
|
|
28100
|
+
type: "positional",
|
|
28101
|
+
required: true,
|
|
28102
|
+
description: "Landing slug (folder under src/pages/). Space-separate several to critique them in one call."
|
|
28103
|
+
},
|
|
28091
28104
|
full: { type: "boolean", description: "Also print advisory-tier findings", default: false }
|
|
28092
28105
|
},
|
|
28093
28106
|
async run({ args }) {
|
|
28094
|
-
const
|
|
28107
|
+
const slugs = parseCritiqueSlugs(args);
|
|
28095
28108
|
const projectRoot = process.cwd();
|
|
28096
|
-
|
|
28097
|
-
|
|
28098
|
-
|
|
28099
|
-
|
|
28100
|
-
|
|
28101
|
-
|
|
28102
|
-
|
|
28103
|
-
|
|
28104
|
-
|
|
28105
|
-
|
|
28106
|
-
|
|
28107
|
-
|
|
28108
|
-
|
|
28109
|
-
const [sources, brand, sourceSha] = await Promise.all([
|
|
28110
|
-
readLandingSources(landingDir),
|
|
28111
|
-
loadBrandTokens(projectRoot),
|
|
28112
|
-
computeLandingSourceSha(landingDir)
|
|
28113
|
-
]);
|
|
28114
|
-
const report = critiqueLanding({ slug, sources, brand });
|
|
28115
|
-
let snapshotFailed = false;
|
|
28116
|
-
try {
|
|
28117
|
-
await writeCritiqueSnapshot(projectRoot, {
|
|
28118
|
-
slug,
|
|
28119
|
-
sourceSha,
|
|
28120
|
-
criticVersion: CRITIC_VERSION,
|
|
28121
|
-
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
28122
|
-
blockCount: report.counts.block,
|
|
28123
|
-
warnCount: report.counts.warn
|
|
28124
|
-
});
|
|
28125
|
-
} catch {
|
|
28126
|
-
snapshotFailed = true;
|
|
28109
|
+
for (const slug of slugs) {
|
|
28110
|
+
if (!SLUG_RE.test(slug) || slug.includes("..")) {
|
|
28111
|
+
fail5(
|
|
28112
|
+
"INVALID_SLUG",
|
|
28113
|
+
`"${slug}" is not a landing slug \u2014 use the folder name directly under src/pages/ (letters, digits, dashes; not a path, not a _-private folder).`,
|
|
28114
|
+
{ availableSlugs: await listLandingSlugs(projectRoot) }
|
|
28115
|
+
);
|
|
28116
|
+
}
|
|
28117
|
+
if (!await isDir(path27.resolve(projectRoot, "src", "pages", slug))) {
|
|
28118
|
+
fail5("NOT_FOUND", `No landing at src/pages/${slug}/`, {
|
|
28119
|
+
availableSlugs: await listLandingSlugs(projectRoot)
|
|
28120
|
+
});
|
|
28121
|
+
}
|
|
28127
28122
|
}
|
|
28128
|
-
const
|
|
28123
|
+
const brand = await loadBrandTokens(projectRoot);
|
|
28124
|
+
const results = await Promise.all(slugs.map((slug) => critiqueOne(projectRoot, slug, brand)));
|
|
28125
|
+
const landings = results.map(({ slug, report }) => ({
|
|
28126
|
+
slug,
|
|
28127
|
+
overall: report.overall,
|
|
28128
|
+
counts: report.counts,
|
|
28129
|
+
dimensions: report.dimensions,
|
|
28130
|
+
findings: report.findings.filter((f) => args.full ? true : f.severity !== "advisory").map(present)
|
|
28131
|
+
}));
|
|
28132
|
+
const blocked = results.filter((r) => r.report.counts.block > 0).map((r) => r.slug);
|
|
28133
|
+
const snapshotFailures = results.filter((r) => r.snapshotFailed).map((r) => r.slug);
|
|
28129
28134
|
const hints = [
|
|
28130
|
-
|
|
28131
|
-
...
|
|
28132
|
-
|
|
28135
|
+
blocked.length > 0 ? `Block-tier tells are held at publish \u2014 fix them (${blocked.join(", ")}). Warn/advisory are guidance. Scores are 0\u20131 (higher is better).` : "No block-tier tells. Warn/advisory findings are guidance, not gates. Re-run after edits so the publish gate stays fresh.",
|
|
28136
|
+
...snapshotFailures.length > 0 ? [
|
|
28137
|
+
`Could not record the critique snapshot for ${snapshotFailures.join(", ")} (.cache/ not writable?) \u2014 the publish quality gate will still report ${snapshotFailures.length === 1 ? "this landing" : "these landings"} as un-critiqued.`
|
|
28133
28138
|
] : []
|
|
28134
28139
|
];
|
|
28140
|
+
const single = landings.length === 1 ? landings[0] : null;
|
|
28135
28141
|
process.stdout.write(
|
|
28136
28142
|
`${JSON.stringify(
|
|
28137
28143
|
{
|
|
28138
28144
|
ok: true,
|
|
28139
|
-
data: {
|
|
28140
|
-
advisory: true,
|
|
28141
|
-
slug,
|
|
28142
|
-
overall: report.overall,
|
|
28143
|
-
counts: report.counts,
|
|
28144
|
-
dimensions: report.dimensions,
|
|
28145
|
-
findings: shown.map(present)
|
|
28146
|
-
},
|
|
28145
|
+
data: { advisory: true, ...single ?? {}, landings },
|
|
28147
28146
|
hints
|
|
28148
28147
|
},
|
|
28149
28148
|
null,
|
|
@@ -28153,6 +28152,25 @@ var critiqueCommand2 = defineCommand135({
|
|
|
28153
28152
|
);
|
|
28154
28153
|
}
|
|
28155
28154
|
});
|
|
28155
|
+
async function critiqueOne(projectRoot, slug, brand) {
|
|
28156
|
+
const landingDir = path27.resolve(projectRoot, "src", "pages", slug);
|
|
28157
|
+
const [sources, sourceSha] = await Promise.all([readLandingSources(landingDir), computeLandingSourceSha(landingDir)]);
|
|
28158
|
+
const report = critiqueLanding({ slug, sources, brand });
|
|
28159
|
+
let snapshotFailed = false;
|
|
28160
|
+
try {
|
|
28161
|
+
await writeCritiqueSnapshot(projectRoot, {
|
|
28162
|
+
slug,
|
|
28163
|
+
sourceSha,
|
|
28164
|
+
criticVersion: CRITIC_VERSION,
|
|
28165
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
28166
|
+
blockCount: report.counts.block,
|
|
28167
|
+
warnCount: report.counts.warn
|
|
28168
|
+
});
|
|
28169
|
+
} catch {
|
|
28170
|
+
snapshotFailed = true;
|
|
28171
|
+
}
|
|
28172
|
+
return { slug, report, snapshotFailed };
|
|
28173
|
+
}
|
|
28156
28174
|
async function listLandingSlugs(projectRoot) {
|
|
28157
28175
|
try {
|
|
28158
28176
|
const entries = await readdir8(path27.join(projectRoot, "src", "pages"), { withFileTypes: true });
|