Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
One-time setup that gathers your project's design context and saves it to CLAUDE.md for future sessions.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/live-insert.mjs
1/**2* CLI helper: find an anchor element in source and splice an insert-variant3* wrapper before or after it (no original variant — net-new content).4*5* Usage:6* node live-insert.mjs --id SESSION_ID --count N --position after \7* --classes "hero" --tag section [--file path]8*/910import fs from 'node:fs';11import path from 'node:path';12import { isGeneratedFile } from './lib/is-generated.mjs';13import {14buildSearchQueries,15findElement,16findAllElements,17filterByText,18findFileWithQuery,19detectCommentSyntax,20detectStyleMode,21buildCssAuthoring,22buildCssSelectorPrefixExamples,23} from './live-wrap.mjs';24import {25buildSvelteComponentCssAuthoring,26scaffoldSvelteComponentInsertSession,27shouldUseSvelteComponentInjection,28} from './live/svelte-component.mjs';2930const INSERT_POSITIONS = new Set(['before', 'after']);3132export function isInsertPosition(value) {33return INSERT_POSITIONS.has(value);34}3536export function computeInsertLine(startLine, endLine, position) {37return position === 'before' ? startLine : endLine + 1;38}3940export function buildInsertWrapperLines({ id, count, indent, commentSyntax, isJsx }) {41const styleContents = isJsx ? 'style={{ display: "contents" }}' : 'style="display: contents"';42const attrs =43'data-impeccable-variants="' + id + '" ' +44'data-impeccable-mode="insert" ' +45'data-impeccable-variant-count="' + count + '" ' +46styleContents;4748if (isJsx) {49return [50indent + '<div ' + attrs + '>',51indent + ' ' + commentSyntax.open + ' impeccable-variants-start ' + id + ' ' + commentSyntax.close,52indent + ' ' + commentSyntax.open + ' Variants: insert below this line ' + commentSyntax.close,53indent + ' ' + commentSyntax.open + ' impeccable-variants-end ' + id + ' ' + commentSyntax.close,54indent + '</div>',55];56}5758return [59indent + commentSyntax.open + ' impeccable-variants-start ' + id + ' ' + commentSyntax.close,60indent + '<div ' + attrs + '>',61indent + ' ' + commentSyntax.open + ' Variants: insert below this line ' + commentSyntax.close,62indent + '</div>',63indent + commentSyntax.open + ' impeccable-variants-end ' + id + ' ' + commentSyntax.close,64];65}6667function argVal(args, flag) {68const idx = args.indexOf(flag);69return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : null;70}7172function resolveElementMatch({ lines, queries, tag, text }) {73if (text) {74const candidates = [];75for (const q of queries) {76const all = findAllElements(lines, q, tag);77for (const c of all) {78if (!candidates.some((x) => x.startLine === c.startLine)) candidates.push(c);79}80if (candidates.length === 1) break;81}82if (candidates.length === 0) return { error: 'element_not_found' };83if (candidates.length === 1) return { match: candidates[0] };84const filtered = filterByText(candidates, lines, text);85if (filtered.length === 1) return { match: filtered[0] };86if (filtered.length === 0) return { match: candidates[0] };87return { error: 'element_ambiguous', candidates: filtered };88}8990for (const q of queries) {91const match = findElement(lines, q, tag);92if (match) return { match };93}94return { error: 'element_not_found' };95}9697export async function insertCli() {98const args = process.argv.slice(2);99100if (args.includes('--help') || args.includes('-h')) {101console.log(`Usage: node live-insert.mjs [options]102103Find an anchor element in source and splice an insert-variant wrapper.104105Required:106--id ID Session ID for the variant wrapper107--count N Number of expected variants (1-8)108--position POS before | after (relative to the anchor element)109110Element identification (at least one required):111--element-id ID HTML id attribute of the anchor element112--classes A,B,C Comma-separated CSS class names113--tag TAG Tag name (div, section, etc.)114--query TEXT Fallback: raw text to search for115116Optional:117--file PATH Source file to search in (skips auto-detection)118--text TEXT Anchor textContent for disambiguation (~80 chars)119120Output (JSON):121{ mode: "insert", file, position, insertLine, commentSyntax, styleMode, styleTag, cssAuthoring }`);122process.exit(0);123}124125const id = argVal(args, '--id');126const count = parseInt(argVal(args, '--count') || '3', 10);127const position = argVal(args, '--position');128const elementId = argVal(args, '--element-id');129const classes = argVal(args, '--classes');130const tag = argVal(args, '--tag');131const query = argVal(args, '--query');132const filePath = argVal(args, '--file');133const text = argVal(args, '--text');134135if (!id) { console.error('Missing --id'); process.exit(1); }136if (!position) { console.error('Missing --position (before | after)'); process.exit(1); }137if (!isInsertPosition(position)) { console.error('Invalid --position: ' + position); process.exit(1); }138if (!elementId && !classes && !query) {139console.error('Need at least one of: --element-id, --classes, --query');140process.exit(1);141}142143const queries = buildSearchQueries(elementId, classes, tag, query);144const genOpts = { cwd: process.cwd() };145146let targetFile = filePath;147if (!targetFile) {148for (const q of queries) {149targetFile = findFileWithQuery(q, process.cwd(), genOpts);150if (targetFile) break;151}152if (!targetFile) {153let generatedHit = null;154for (const q of queries) {155generatedHit = findFileWithQuery(q, process.cwd(), { ...genOpts, includeGenerated: true });156if (generatedHit) break;157}158console.error(JSON.stringify({159error: generatedHit ? 'element_not_in_source' : 'element_not_found',160fallback: 'agent-driven',161hint: 'See "Handle fallback" in live.md.',162}));163process.exit(1);164}165} else if (isGeneratedFile(targetFile, genOpts)) {166console.error(JSON.stringify({167error: 'file_is_generated',168fallback: 'agent-driven',169file: path.relative(process.cwd(), path.resolve(process.cwd(), targetFile)),170}));171process.exit(1);172}173174const content = fs.readFileSync(targetFile, 'utf-8');175const lines = content.split('\n');176const resolved = resolveElementMatch({ lines, queries, tag, text });177178if (resolved.error === 'element_ambiguous') {179console.error(JSON.stringify({180error: 'element_ambiguous',181fallback: 'agent-driven',182file: path.relative(process.cwd(), targetFile),183candidates: resolved.candidates.map((c) => ({184startLine: c.startLine + 1,185endLine: c.endLine + 1,186})),187}));188process.exit(1);189}190if (!resolved.match) {191console.error(JSON.stringify({ error: 'element_not_found', fallback: 'agent-driven' }));192process.exit(1);193}194195const { startLine, endLine } = resolved.match;196const commentSyntax = detectCommentSyntax(targetFile);197const styleMode = detectStyleMode(targetFile);198const isJsx = commentSyntax.open === '{/*';199const spliceIndex = computeInsertLine(startLine, endLine, position);200const relTargetFile = path.relative(process.cwd(), targetFile).split(path.sep).join('/');201202if (shouldUseSvelteComponentInjection(targetFile)) {203const session = scaffoldSvelteComponentInsertSession({204id,205count,206sourceFile: relTargetFile,207insertLine: spliceIndex + 1,208position,209anchorStartLine: startLine + 1,210anchorEndLine: endLine + 1,211anchorLines: lines.slice(startLine, endLine + 1),212cwd: process.cwd(),213});214console.log(JSON.stringify({215mode: 'insert',216position,217file: session.manifestFile,218sourceFile: relTargetFile,219previewMode: 'svelte-component',220componentDir: session.componentDir,221propContract: session.propContract,222insertLine: 1,223sourceInsertLine: spliceIndex + 1,224anchorStartLine: startLine + 1,225anchorEndLine: endLine + 1,226commentSyntax,227styleMode: 'svelte-component',228styleTag: null,229cssSelectorPrefixExamples: [],230cssAuthoring: buildSvelteComponentCssAuthoring(count),231}));232return;233}234235const indent = lines[spliceIndex]?.match(/^(\s*)/)?.[1]236?? lines[startLine]?.match(/^(\s*)/)?.[1]237?? '';238239const wrapperLines = buildInsertWrapperLines({240id,241count,242indent,243commentSyntax,244isJsx,245});246247const newLines = [248...lines.slice(0, spliceIndex),249...wrapperLines,250...lines.slice(spliceIndex),251];252fs.writeFileSync(targetFile, newLines.join('\n'), 'utf-8');253254const insertLine = spliceIndex + 3;255256console.log(JSON.stringify({257mode: 'insert',258position,259file: relTargetFile,260insertLine: insertLine + 1,261commentSyntax,262styleMode: styleMode.mode,263styleTag: styleMode.styleTag,264cssSelectorPrefixExamples: buildCssSelectorPrefixExamples(styleMode.mode, count),265cssAuthoring: buildCssAuthoring(styleMode, count),266}));267}268269const _running = process.argv[1];270if (_running?.endsWith('live-insert.mjs') || _running?.endsWith('live-insert.mjs/')) {271insertCli();272}273