create-clicksmith 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-WEMLICDQ.js → chunk-TE3BLNLM.js} +31 -14
- package/dist/chunk-TE3BLNLM.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +8 -13
- package/LICENSE +0 -21
- package/dist/chunk-WEMLICDQ.js.map +0 -1
|
@@ -112,7 +112,6 @@ import { dirname, join as join2 } from "path";
|
|
|
112
112
|
import {
|
|
113
113
|
applyManagedBlock,
|
|
114
114
|
DEFAULT_AGENTS_CONFIG,
|
|
115
|
-
mergeAgentsConfig,
|
|
116
115
|
parseAgentsConfig,
|
|
117
116
|
renderInstructions
|
|
118
117
|
} from "@clicksmith/agent-config";
|
|
@@ -127,7 +126,10 @@ async function planInstall(info, options = {}) {
|
|
|
127
126
|
const messages = [];
|
|
128
127
|
const nextSteps = [];
|
|
129
128
|
for (const target of targets) {
|
|
130
|
-
const rendered = renderInstructions(target, {
|
|
129
|
+
const rendered = renderInstructions(target, {
|
|
130
|
+
stableAttrs: info.stableAttrs,
|
|
131
|
+
daemonPort: port
|
|
132
|
+
});
|
|
131
133
|
const existing = await readText(join2(root, rendered.path));
|
|
132
134
|
const contents = rendered.shared ? applyManagedBlock(existing, rendered.content) : rendered.content;
|
|
133
135
|
changes.push({
|
|
@@ -138,21 +140,33 @@ async function planInstall(info, options = {}) {
|
|
|
138
140
|
}
|
|
139
141
|
const configPath = join2(".clicksmith", "agents.config.json");
|
|
140
142
|
const existingConfigRaw = await readText(join2(root, configPath));
|
|
141
|
-
|
|
142
|
-
|
|
143
|
+
if (existingConfigRaw == null) {
|
|
144
|
+
changes.push({
|
|
145
|
+
path: configPath,
|
|
146
|
+
action: "create",
|
|
147
|
+
contents: `${JSON.stringify(
|
|
148
|
+
{ version: 1, defaultAgent: DEFAULT_AGENTS_CONFIG.defaultAgent, agents: [] },
|
|
149
|
+
null,
|
|
150
|
+
2
|
|
151
|
+
)}
|
|
152
|
+
`
|
|
153
|
+
});
|
|
154
|
+
} else {
|
|
143
155
|
try {
|
|
144
156
|
const parsed = parseAgentsConfig(JSON.parse(existingConfigRaw));
|
|
145
|
-
if (parsed.ok)
|
|
157
|
+
if (!parsed.ok) {
|
|
158
|
+
messages.push("Existing agents.config.json was invalid; left it untouched.");
|
|
159
|
+
}
|
|
146
160
|
} catch {
|
|
147
161
|
messages.push("Existing agents.config.json was invalid JSON; left it untouched.");
|
|
148
162
|
}
|
|
163
|
+
changes.push({
|
|
164
|
+
path: configPath,
|
|
165
|
+
action: "skip",
|
|
166
|
+
contents: existingConfigRaw,
|
|
167
|
+
reason: "existing user agent overrides preserved"
|
|
168
|
+
});
|
|
149
169
|
}
|
|
150
|
-
changes.push({
|
|
151
|
-
path: configPath,
|
|
152
|
-
action: existingConfigRaw == null ? "create" : "merge",
|
|
153
|
-
contents: `${JSON.stringify(merged, null, 2)}
|
|
154
|
-
`
|
|
155
|
-
});
|
|
156
170
|
const mcp = mcpCommand(info.packageManager);
|
|
157
171
|
changes.push(await mcpChange(root, ".mcp.json", mcp));
|
|
158
172
|
if (targets.includes("cursor")) {
|
|
@@ -166,7 +180,9 @@ async function planInstall(info, options = {}) {
|
|
|
166
180
|
`Could not automatically wire ${info.viteConfig}. Add: import clicksmith from '@clicksmith/unplugin/vite'; and put clicksmith() first in plugins.`
|
|
167
181
|
);
|
|
168
182
|
} else if (useUnplugin) {
|
|
169
|
-
messages.push(
|
|
183
|
+
messages.push(
|
|
184
|
+
"No Vite config found \u2014 add the @clicksmith/unplugin plugin to your bundler manually."
|
|
185
|
+
);
|
|
170
186
|
} else {
|
|
171
187
|
messages.push(
|
|
172
188
|
`Stable source locators via unplugin aren't available for this stack; ClickSmith will use ${info.stableAttrs.length ? `your attributes (${info.stableAttrs.join(", ")})` : "attribute/behavioral/DOM"} as the fallback locator (source \u2192 attr \u2192 behavioral \u2192 dom).`
|
|
@@ -177,7 +193,8 @@ async function planInstall(info, options = {}) {
|
|
|
177
193
|
nextSteps.push(
|
|
178
194
|
`${installCmd(info.packageManager)} # install the new dependencies`,
|
|
179
195
|
`${runCmd(info.packageManager, "clicksmith daemon")} # start the localhost daemon`,
|
|
180
|
-
"
|
|
196
|
+
`${runCmd(info.packageManager, "clicksmith doctor")} # verify agent CLIs are visible to the daemon`,
|
|
197
|
+
"Install the ClickSmith browser extension, toggle AI Mode, and Alt+Click an element."
|
|
181
198
|
);
|
|
182
199
|
return { changes, messages, nextSteps };
|
|
183
200
|
}
|
|
@@ -292,4 +309,4 @@ export {
|
|
|
292
309
|
planInstall,
|
|
293
310
|
applyPlan
|
|
294
311
|
};
|
|
295
|
-
//# sourceMappingURL=chunk-
|
|
312
|
+
//# sourceMappingURL=chunk-TE3BLNLM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/detect.ts","../src/install.ts"],"sourcesContent":["import { readFile, readdir } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nexport type PackageManager = 'pnpm' | 'yarn' | 'bun' | 'npm';\nexport type Bundler = 'vite' | 'webpack' | 'rspack' | 'rollup' | 'next' | 'unknown';\nexport type Framework = 'react' | 'vue' | 'svelte' | 'angular' | 'solid' | 'unknown';\n\nexport interface ProjectInfo {\n root: string;\n packageManager: PackageManager;\n bundler: Bundler;\n framework: Framework;\n /** Stable attributes already used in the codebase (e.g. data-testid). */\n stableAttrs: string[];\n /** Whether the dev-only data-loc unplugin can be wired up for this stack. */\n supportsUnplugin: boolean;\n /** Path to the detected Vite config, if any (relative to root). */\n viteConfig?: string;\n}\n\nconst KNOWN_STABLE_ATTRS = ['data-testid', 'data-test', 'data-cy', 'data-qa'];\n\n/** Inspect a project directory and infer its stack. Read-only. */\nexport async function detectProject(root: string): Promise<ProjectInfo> {\n const pkg = await readJson(join(root, 'package.json'));\n const deps = { ...(pkg?.dependencies ?? {}), ...(pkg?.devDependencies ?? {}) } as Record<string, string>;\n\n const packageManager = await detectPackageManager(root);\n const bundler = detectBundler(deps);\n const framework = detectFramework(deps);\n const viteConfig = await findFile(root, ['vite.config.ts', 'vite.config.js', 'vite.config.mjs']);\n const stableAttrs = await scanStableAttrs(root);\n\n const supportsUnplugin =\n framework === 'react' && ['vite', 'webpack', 'rspack', 'rollup'].includes(bundler);\n\n return {\n root,\n packageManager,\n bundler,\n framework,\n stableAttrs,\n supportsUnplugin,\n ...(viteConfig ? { viteConfig } : {}),\n };\n}\n\nasync function detectPackageManager(root: string): Promise<PackageManager> {\n if (await fileExists(join(root, 'pnpm-lock.yaml'))) return 'pnpm';\n if (await fileExists(join(root, 'yarn.lock'))) return 'yarn';\n if (await fileExists(join(root, 'bun.lockb'))) return 'bun';\n return 'npm';\n}\n\nfunction detectBundler(deps: Record<string, string>): Bundler {\n if (deps.next) return 'next';\n if (deps.vite) return 'vite';\n if (deps['@rspack/core']) return 'rspack';\n if (deps.webpack) return 'webpack';\n if (deps.rollup) return 'rollup';\n return 'unknown';\n}\n\nfunction detectFramework(deps: Record<string, string>): Framework {\n if (deps['@angular/core']) return 'angular';\n if (deps.svelte) return 'svelte';\n if (deps.vue) return 'vue';\n if (deps['solid-js']) return 'solid';\n if (deps.react) return 'react';\n return 'unknown';\n}\n\n/** Scan a handful of source files for stable test/id attributes. */\nasync function scanStableAttrs(root: string): Promise<string[]> {\n const found = new Set<string>();\n const dirs = ['src', 'app', 'components'];\n for (const dir of dirs) {\n const files = await listSourceFiles(join(root, dir), 40);\n for (const file of files) {\n const content = await safeRead(file);\n if (!content) continue;\n for (const attr of KNOWN_STABLE_ATTRS) {\n if (content.includes(attr)) found.add(attr);\n }\n if (found.size === KNOWN_STABLE_ATTRS.length) return [...found];\n }\n }\n return [...found];\n}\n\nasync function listSourceFiles(dir: string, limit: number): Promise<string[]> {\n const out: string[] = [];\n async function walk(d: string) {\n if (out.length >= limit) return;\n let entries;\n try {\n entries = await readdir(d, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n if (out.length >= limit) return;\n if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue;\n const full = join(d, entry.name);\n if (entry.isDirectory()) await walk(full);\n else if (/\\.(jsx?|tsx?|vue|svelte)$/.test(entry.name)) out.push(full);\n }\n }\n await walk(dir);\n return out;\n}\n\nasync function findFile(root: string, names: string[]): Promise<string | undefined> {\n for (const name of names) {\n if (await fileExists(join(root, name))) return name;\n }\n return undefined;\n}\n\nasync function readJson(file: string): Promise<Record<string, unknown> | undefined> {\n const raw = await safeRead(file);\n if (!raw) return undefined;\n try {\n return JSON.parse(raw);\n } catch {\n return undefined;\n }\n}\n\nasync function safeRead(file: string): Promise<string | undefined> {\n try {\n return await readFile(file, 'utf8');\n } catch {\n return undefined;\n }\n}\n\nasync function fileExists(file: string): Promise<boolean> {\n return (await safeRead(file)) !== undefined;\n}\n","import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport {\n applyManagedBlock,\n DEFAULT_AGENTS_CONFIG,\n parseAgentsConfig,\n renderInstructions,\n type InstructionTarget,\n} from '@clicksmith/agent-config';\nimport { DEFAULT_DAEMON_PORT } from '@clicksmith/core';\nimport type { PackageManager, ProjectInfo } from './detect.js';\n\nexport interface InstallOptions {\n /** Which agents to render instruction files for. */\n agents?: InstructionTarget[];\n /** Wire the dev-only data-loc unplugin. Defaults to detection. */\n useUnplugin?: boolean;\n daemonPort?: number;\n}\n\nexport interface FileChange {\n path: string;\n action: 'create' | 'merge' | 'skip';\n contents: string;\n reason?: string;\n}\n\nexport interface InstallPlan {\n changes: FileChange[];\n messages: string[];\n nextSteps: string[];\n}\n\nconst DEFAULT_AGENT_TARGETS: InstructionTarget[] = ['claude', 'cursor', 'codex', 'generic'];\n\n/**\n * Compute every file change needed to install ClickSmith into a project,\n * reading existing files so merges preserve user content. Nothing is written —\n * call {@link applyPlan} for that. This split keeps the installer fully testable.\n */\nexport async function planInstall(\n info: ProjectInfo,\n options: InstallOptions = {},\n): Promise<InstallPlan> {\n const root = info.root;\n const targets = options.agents ?? DEFAULT_AGENT_TARGETS;\n const useUnplugin = options.useUnplugin ?? info.supportsUnplugin;\n const port = options.daemonPort ?? DEFAULT_DAEMON_PORT;\n\n const changes: FileChange[] = [];\n const messages: string[] = [];\n const nextSteps: string[] = [];\n\n // 1. Agent instruction files (managed blocks preserve user content).\n for (const target of targets) {\n const rendered = renderInstructions(target, {\n stableAttrs: info.stableAttrs,\n daemonPort: port,\n });\n const existing = await readText(join(root, rendered.path));\n const contents = rendered.shared\n ? applyManagedBlock(existing, rendered.content)\n : rendered.content;\n changes.push({\n path: rendered.path,\n action: existing == null ? 'create' : 'merge',\n contents,\n });\n }\n\n // 2. agents.config.json — keep this as user overrides only. The daemon ships\n // built-in defaults, so copying them into projects would freeze old commands.\n const configPath = join('.clicksmith', 'agents.config.json');\n const existingConfigRaw = await readText(join(root, configPath));\n if (existingConfigRaw == null) {\n changes.push({\n path: configPath,\n action: 'create',\n contents: `${JSON.stringify(\n { version: 1, defaultAgent: DEFAULT_AGENTS_CONFIG.defaultAgent, agents: [] },\n null,\n 2,\n )}\\n`,\n });\n } else {\n try {\n const parsed = parseAgentsConfig(JSON.parse(existingConfigRaw));\n if (!parsed.ok) {\n messages.push('Existing agents.config.json was invalid; left it untouched.');\n }\n } catch {\n messages.push('Existing agents.config.json was invalid JSON; left it untouched.');\n }\n changes.push({\n path: configPath,\n action: 'skip',\n contents: existingConfigRaw,\n reason: 'existing user agent overrides preserved',\n });\n }\n\n // 3. MCP registration for the daemon's stdio server.\n const mcp = mcpCommand(info.packageManager);\n changes.push(await mcpChange(root, '.mcp.json', mcp));\n if (targets.includes('cursor')) {\n changes.push(await mcpChange(root, join('.cursor', 'mcp.json'), mcp));\n }\n\n // 4. Wire the data-loc unplugin (best effort) + record the dependency.\n if (useUnplugin && info.viteConfig) {\n const wired = await wireViteConfig(root, info.viteConfig);\n if (wired) changes.push(wired);\n else\n messages.push(\n `Could not automatically wire ${info.viteConfig}. Add: import clicksmith from '@clicksmith/unplugin/vite'; and put clicksmith() first in plugins.`,\n );\n } else if (useUnplugin) {\n messages.push(\n 'No Vite config found — add the @clicksmith/unplugin plugin to your bundler manually.',\n );\n } else {\n messages.push(\n `Stable source locators via unplugin aren't available for this stack; ClickSmith will use ${\n info.stableAttrs.length\n ? `your attributes (${info.stableAttrs.join(', ')})`\n : 'attribute/behavioral/DOM'\n } as the fallback locator (source → attr → behavioral → dom).`,\n );\n }\n\n // 5. package.json — add the dependencies needed for the bin + plugin.\n changes.push(await packageJsonChange(root, useUnplugin));\n\n // 6. Ensure .clicksmith/ is gitignored.\n changes.push(await gitignoreChange(root));\n\n nextSteps.push(\n `${installCmd(info.packageManager)} # install the new dependencies`,\n `${runCmd(info.packageManager, 'clicksmith daemon')} # start the localhost daemon`,\n `${runCmd(info.packageManager, 'clicksmith doctor')} # verify agent CLIs are visible to the daemon`,\n 'Install the ClickSmith browser extension, toggle AI Mode, and Alt+Click an element.',\n );\n\n return { changes, messages, nextSteps };\n}\n\n/** Apply a plan to disk, writing create/merge changes and skipping the rest. */\nexport async function applyPlan(root: string, plan: InstallPlan): Promise<FileChange[]> {\n const written: FileChange[] = [];\n for (const change of plan.changes) {\n if (change.action === 'skip') continue;\n const full = join(root, change.path);\n await mkdir(dirname(full), { recursive: true });\n await writeFile(full, change.contents, 'utf8');\n written.push(change);\n }\n return written;\n}\n\n/* ------------------------------- helpers ---------------------------------- */\n\ninterface McpServerSpec {\n command: string;\n args: string[];\n}\n\nfunction mcpCommand(pm: PackageManager): McpServerSpec {\n switch (pm) {\n case 'pnpm':\n return { command: 'pnpm', args: ['exec', 'clicksmith', 'mcp'] };\n case 'yarn':\n return { command: 'yarn', args: ['clicksmith', 'mcp'] };\n case 'bun':\n return { command: 'bunx', args: ['clicksmith', 'mcp'] };\n case 'npm':\n return { command: 'npx', args: ['clicksmith', 'mcp'] };\n }\n}\n\nasync function mcpChange(root: string, path: string, spec: McpServerSpec): Promise<FileChange> {\n const existingRaw = await readText(join(root, path));\n let doc: { mcpServers?: Record<string, unknown> } = {};\n if (existingRaw) {\n try {\n doc = JSON.parse(existingRaw);\n } catch {\n doc = {};\n }\n }\n doc.mcpServers = { ...(doc.mcpServers ?? {}), clicksmith: spec };\n return {\n path,\n action: existingRaw == null ? 'create' : 'merge',\n contents: `${JSON.stringify(doc, null, 2)}\\n`,\n };\n}\n\nasync function wireViteConfig(root: string, relPath: string): Promise<FileChange | null> {\n const file = join(root, relPath);\n const code = await readText(file);\n if (code == null) return null;\n if (code.includes('@clicksmith/unplugin')) {\n return { path: relPath, action: 'skip', contents: code, reason: 'already wired' };\n }\n const importLine = `import clicksmith from '@clicksmith/unplugin/vite';\\n`;\n // Insert plugin first in the array so it runs before the framework plugin.\n const pluginsMatch = code.match(/plugins\\s*:\\s*\\[/);\n if (!pluginsMatch) return null;\n const idx = pluginsMatch.index! + pluginsMatch[0].length;\n const next = importLine + code.slice(0, idx) + 'clicksmith(), ' + code.slice(idx);\n return { path: relPath, action: 'merge', contents: next };\n}\n\nasync function packageJsonChange(root: string, useUnplugin: boolean): Promise<FileChange> {\n const raw = await readText(join(root, 'package.json'));\n const pkg = raw ? JSON.parse(raw) : { name: 'project', version: '0.0.0' };\n pkg.devDependencies = { ...(pkg.devDependencies ?? {}) };\n pkg.devDependencies['@clicksmith/daemon'] = pkg.devDependencies['@clicksmith/daemon'] ?? 'latest';\n if (useUnplugin) {\n pkg.devDependencies['@clicksmith/unplugin'] =\n pkg.devDependencies['@clicksmith/unplugin'] ?? 'latest';\n }\n return {\n path: 'package.json',\n action: raw == null ? 'create' : 'merge',\n contents: `${JSON.stringify(pkg, null, 2)}\\n`,\n };\n}\n\nasync function gitignoreChange(root: string): Promise<FileChange> {\n const existing = await readText(join(root, '.gitignore'));\n if (\n existing?.split(/\\r?\\n/).some((l) => l.trim() === '.clicksmith/' || l.trim() === '.clicksmith')\n ) {\n return { path: '.gitignore', action: 'skip', contents: existing };\n }\n const contents = existing\n ? `${existing.trimEnd()}\\n\\n# ClickSmith runtime state\\n.clicksmith/\\n`\n : '# ClickSmith runtime state\\n.clicksmith/\\n';\n return { path: '.gitignore', action: existing == null ? 'create' : 'merge', contents };\n}\n\nfunction installCmd(pm: PackageManager): string {\n return pm === 'npm' ? 'npm install' : `${pm} install`;\n}\n\nfunction runCmd(pm: PackageManager, script: string): string {\n switch (pm) {\n case 'pnpm':\n return `pnpm exec ${script}`;\n case 'yarn':\n return `yarn ${script}`;\n case 'bun':\n return `bunx ${script}`;\n case 'npm':\n return `npx ${script}`;\n }\n}\n\nasync function readText(file: string): Promise<string | undefined> {\n try {\n return await readFile(file, 'utf8');\n } catch {\n return undefined;\n }\n}\n"],"mappings":";AAAA,SAAS,UAAU,eAAe;AAClC,SAAS,YAAY;AAmBrB,IAAM,qBAAqB,CAAC,eAAe,aAAa,WAAW,SAAS;AAG5E,eAAsB,cAAc,MAAoC;AACtE,QAAM,MAAM,MAAM,SAAS,KAAK,MAAM,cAAc,CAAC;AACrD,QAAM,OAAO,EAAE,GAAI,KAAK,gBAAgB,CAAC,GAAI,GAAI,KAAK,mBAAmB,CAAC,EAAG;AAE7E,QAAM,iBAAiB,MAAM,qBAAqB,IAAI;AACtD,QAAM,UAAU,cAAc,IAAI;AAClC,QAAM,YAAY,gBAAgB,IAAI;AACtC,QAAM,aAAa,MAAM,SAAS,MAAM,CAAC,kBAAkB,kBAAkB,iBAAiB,CAAC;AAC/F,QAAM,cAAc,MAAM,gBAAgB,IAAI;AAE9C,QAAM,mBACJ,cAAc,WAAW,CAAC,QAAQ,WAAW,UAAU,QAAQ,EAAE,SAAS,OAAO;AAEnF,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;AAAA,EACrC;AACF;AAEA,eAAe,qBAAqB,MAAuC;AACzE,MAAI,MAAM,WAAW,KAAK,MAAM,gBAAgB,CAAC,EAAG,QAAO;AAC3D,MAAI,MAAM,WAAW,KAAK,MAAM,WAAW,CAAC,EAAG,QAAO;AACtD,MAAI,MAAM,WAAW,KAAK,MAAM,WAAW,CAAC,EAAG,QAAO;AACtD,SAAO;AACT;AAEA,SAAS,cAAc,MAAuC;AAC5D,MAAI,KAAK,KAAM,QAAO;AACtB,MAAI,KAAK,KAAM,QAAO;AACtB,MAAI,KAAK,cAAc,EAAG,QAAO;AACjC,MAAI,KAAK,QAAS,QAAO;AACzB,MAAI,KAAK,OAAQ,QAAO;AACxB,SAAO;AACT;AAEA,SAAS,gBAAgB,MAAyC;AAChE,MAAI,KAAK,eAAe,EAAG,QAAO;AAClC,MAAI,KAAK,OAAQ,QAAO;AACxB,MAAI,KAAK,IAAK,QAAO;AACrB,MAAI,KAAK,UAAU,EAAG,QAAO;AAC7B,MAAI,KAAK,MAAO,QAAO;AACvB,SAAO;AACT;AAGA,eAAe,gBAAgB,MAAiC;AAC9D,QAAM,QAAQ,oBAAI,IAAY;AAC9B,QAAM,OAAO,CAAC,OAAO,OAAO,YAAY;AACxC,aAAW,OAAO,MAAM;AACtB,UAAM,QAAQ,MAAM,gBAAgB,KAAK,MAAM,GAAG,GAAG,EAAE;AACvD,eAAW,QAAQ,OAAO;AACxB,YAAM,UAAU,MAAM,SAAS,IAAI;AACnC,UAAI,CAAC,QAAS;AACd,iBAAW,QAAQ,oBAAoB;AACrC,YAAI,QAAQ,SAAS,IAAI,EAAG,OAAM,IAAI,IAAI;AAAA,MAC5C;AACA,UAAI,MAAM,SAAS,mBAAmB,OAAQ,QAAO,CAAC,GAAG,KAAK;AAAA,IAChE;AAAA,EACF;AACA,SAAO,CAAC,GAAG,KAAK;AAClB;AAEA,eAAe,gBAAgB,KAAa,OAAkC;AAC5E,QAAM,MAAgB,CAAC;AACvB,iBAAe,KAAK,GAAW;AAC7B,QAAI,IAAI,UAAU,MAAO;AACzB,QAAI;AACJ,QAAI;AACF,gBAAU,MAAM,QAAQ,GAAG,EAAE,eAAe,KAAK,CAAC;AAAA,IACpD,QAAQ;AACN;AAAA,IACF;AACA,eAAW,SAAS,SAAS;AAC3B,UAAI,IAAI,UAAU,MAAO;AACzB,UAAI,MAAM,SAAS,kBAAkB,MAAM,KAAK,WAAW,GAAG,EAAG;AACjE,YAAM,OAAO,KAAK,GAAG,MAAM,IAAI;AAC/B,UAAI,MAAM,YAAY,EAAG,OAAM,KAAK,IAAI;AAAA,eAC/B,4BAA4B,KAAK,MAAM,IAAI,EAAG,KAAI,KAAK,IAAI;AAAA,IACtE;AAAA,EACF;AACA,QAAM,KAAK,GAAG;AACd,SAAO;AACT;AAEA,eAAe,SAAS,MAAc,OAA8C;AAClF,aAAW,QAAQ,OAAO;AACxB,QAAI,MAAM,WAAW,KAAK,MAAM,IAAI,CAAC,EAAG,QAAO;AAAA,EACjD;AACA,SAAO;AACT;AAEA,eAAe,SAAS,MAA4D;AAClF,QAAM,MAAM,MAAM,SAAS,IAAI;AAC/B,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI;AACF,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,SAAS,MAA2C;AACjE,MAAI;AACF,WAAO,MAAM,SAAS,MAAM,MAAM;AAAA,EACpC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,WAAW,MAAgC;AACxD,SAAQ,MAAM,SAAS,IAAI,MAAO;AACpC;;;AC3IA,SAAS,OAAO,YAAAA,WAAU,iBAAiB;AAC3C,SAAS,SAAS,QAAAC,aAAY;AAC9B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,2BAA2B;AAwBpC,IAAM,wBAA6C,CAAC,UAAU,UAAU,SAAS,SAAS;AAO1F,eAAsB,YACpB,MACA,UAA0B,CAAC,GACL;AACtB,QAAM,OAAO,KAAK;AAClB,QAAM,UAAU,QAAQ,UAAU;AAClC,QAAM,cAAc,QAAQ,eAAe,KAAK;AAChD,QAAM,OAAO,QAAQ,cAAc;AAEnC,QAAM,UAAwB,CAAC;AAC/B,QAAM,WAAqB,CAAC;AAC5B,QAAM,YAAsB,CAAC;AAG7B,aAAW,UAAU,SAAS;AAC5B,UAAM,WAAW,mBAAmB,QAAQ;AAAA,MAC1C,aAAa,KAAK;AAAA,MAClB,YAAY;AAAA,IACd,CAAC;AACD,UAAM,WAAW,MAAM,SAASA,MAAK,MAAM,SAAS,IAAI,CAAC;AACzD,UAAM,WAAW,SAAS,SACtB,kBAAkB,UAAU,SAAS,OAAO,IAC5C,SAAS;AACb,YAAQ,KAAK;AAAA,MACX,MAAM,SAAS;AAAA,MACf,QAAQ,YAAY,OAAO,WAAW;AAAA,MACtC;AAAA,IACF,CAAC;AAAA,EACH;AAIA,QAAM,aAAaA,MAAK,eAAe,oBAAoB;AAC3D,QAAM,oBAAoB,MAAM,SAASA,MAAK,MAAM,UAAU,CAAC;AAC/D,MAAI,qBAAqB,MAAM;AAC7B,YAAQ,KAAK;AAAA,MACX,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,UAAU,GAAG,KAAK;AAAA,QAChB,EAAE,SAAS,GAAG,cAAc,sBAAsB,cAAc,QAAQ,CAAC,EAAE;AAAA,QAC3E;AAAA,QACA;AAAA,MACF,CAAC;AAAA;AAAA,IACH,CAAC;AAAA,EACH,OAAO;AACL,QAAI;AACF,YAAM,SAAS,kBAAkB,KAAK,MAAM,iBAAiB,CAAC;AAC9D,UAAI,CAAC,OAAO,IAAI;AACd,iBAAS,KAAK,6DAA6D;AAAA,MAC7E;AAAA,IACF,QAAQ;AACN,eAAS,KAAK,kEAAkE;AAAA,IAClF;AACA,YAAQ,KAAK;AAAA,MACX,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,QAAQ;AAAA,IACV,CAAC;AAAA,EACH;AAGA,QAAM,MAAM,WAAW,KAAK,cAAc;AAC1C,UAAQ,KAAK,MAAM,UAAU,MAAM,aAAa,GAAG,CAAC;AACpD,MAAI,QAAQ,SAAS,QAAQ,GAAG;AAC9B,YAAQ,KAAK,MAAM,UAAU,MAAMA,MAAK,WAAW,UAAU,GAAG,GAAG,CAAC;AAAA,EACtE;AAGA,MAAI,eAAe,KAAK,YAAY;AAClC,UAAM,QAAQ,MAAM,eAAe,MAAM,KAAK,UAAU;AACxD,QAAI,MAAO,SAAQ,KAAK,KAAK;AAAA;AAE3B,eAAS;AAAA,QACP,gCAAgC,KAAK,UAAU;AAAA,MACjD;AAAA,EACJ,WAAW,aAAa;AACtB,aAAS;AAAA,MACP;AAAA,IACF;AAAA,EACF,OAAO;AACL,aAAS;AAAA,MACP,4FACE,KAAK,YAAY,SACb,oBAAoB,KAAK,YAAY,KAAK,IAAI,CAAC,MAC/C,0BACN;AAAA,IACF;AAAA,EACF;AAGA,UAAQ,KAAK,MAAM,kBAAkB,MAAM,WAAW,CAAC;AAGvD,UAAQ,KAAK,MAAM,gBAAgB,IAAI,CAAC;AAExC,YAAU;AAAA,IACR,GAAG,WAAW,KAAK,cAAc,CAAC;AAAA,IAClC,GAAG,OAAO,KAAK,gBAAgB,mBAAmB,CAAC;AAAA,IACnD,GAAG,OAAO,KAAK,gBAAgB,mBAAmB,CAAC;AAAA,IACnD;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,UAAU,UAAU;AACxC;AAGA,eAAsB,UAAU,MAAc,MAA0C;AACtF,QAAM,UAAwB,CAAC;AAC/B,aAAW,UAAU,KAAK,SAAS;AACjC,QAAI,OAAO,WAAW,OAAQ;AAC9B,UAAM,OAAOA,MAAK,MAAM,OAAO,IAAI;AACnC,UAAM,MAAM,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC9C,UAAM,UAAU,MAAM,OAAO,UAAU,MAAM;AAC7C,YAAQ,KAAK,MAAM;AAAA,EACrB;AACA,SAAO;AACT;AASA,SAAS,WAAW,IAAmC;AACrD,UAAQ,IAAI;AAAA,IACV,KAAK;AACH,aAAO,EAAE,SAAS,QAAQ,MAAM,CAAC,QAAQ,cAAc,KAAK,EAAE;AAAA,IAChE,KAAK;AACH,aAAO,EAAE,SAAS,QAAQ,MAAM,CAAC,cAAc,KAAK,EAAE;AAAA,IACxD,KAAK;AACH,aAAO,EAAE,SAAS,QAAQ,MAAM,CAAC,cAAc,KAAK,EAAE;AAAA,IACxD,KAAK;AACH,aAAO,EAAE,SAAS,OAAO,MAAM,CAAC,cAAc,KAAK,EAAE;AAAA,EACzD;AACF;AAEA,eAAe,UAAU,MAAc,MAAc,MAA0C;AAC7F,QAAM,cAAc,MAAM,SAASA,MAAK,MAAM,IAAI,CAAC;AACnD,MAAI,MAAgD,CAAC;AACrD,MAAI,aAAa;AACf,QAAI;AACF,YAAM,KAAK,MAAM,WAAW;AAAA,IAC9B,QAAQ;AACN,YAAM,CAAC;AAAA,IACT;AAAA,EACF;AACA,MAAI,aAAa,EAAE,GAAI,IAAI,cAAc,CAAC,GAAI,YAAY,KAAK;AAC/D,SAAO;AAAA,IACL;AAAA,IACA,QAAQ,eAAe,OAAO,WAAW;AAAA,IACzC,UAAU,GAAG,KAAK,UAAU,KAAK,MAAM,CAAC,CAAC;AAAA;AAAA,EAC3C;AACF;AAEA,eAAe,eAAe,MAAc,SAA6C;AACvF,QAAM,OAAOA,MAAK,MAAM,OAAO;AAC/B,QAAM,OAAO,MAAM,SAAS,IAAI;AAChC,MAAI,QAAQ,KAAM,QAAO;AACzB,MAAI,KAAK,SAAS,sBAAsB,GAAG;AACzC,WAAO,EAAE,MAAM,SAAS,QAAQ,QAAQ,UAAU,MAAM,QAAQ,gBAAgB;AAAA,EAClF;AACA,QAAM,aAAa;AAAA;AAEnB,QAAM,eAAe,KAAK,MAAM,kBAAkB;AAClD,MAAI,CAAC,aAAc,QAAO;AAC1B,QAAM,MAAM,aAAa,QAAS,aAAa,CAAC,EAAE;AAClD,QAAM,OAAO,aAAa,KAAK,MAAM,GAAG,GAAG,IAAI,mBAAmB,KAAK,MAAM,GAAG;AAChF,SAAO,EAAE,MAAM,SAAS,QAAQ,SAAS,UAAU,KAAK;AAC1D;AAEA,eAAe,kBAAkB,MAAc,aAA2C;AACxF,QAAM,MAAM,MAAM,SAASA,MAAK,MAAM,cAAc,CAAC;AACrD,QAAM,MAAM,MAAM,KAAK,MAAM,GAAG,IAAI,EAAE,MAAM,WAAW,SAAS,QAAQ;AACxE,MAAI,kBAAkB,EAAE,GAAI,IAAI,mBAAmB,CAAC,EAAG;AACvD,MAAI,gBAAgB,oBAAoB,IAAI,IAAI,gBAAgB,oBAAoB,KAAK;AACzF,MAAI,aAAa;AACf,QAAI,gBAAgB,sBAAsB,IACxC,IAAI,gBAAgB,sBAAsB,KAAK;AAAA,EACnD;AACA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ,OAAO,OAAO,WAAW;AAAA,IACjC,UAAU,GAAG,KAAK,UAAU,KAAK,MAAM,CAAC,CAAC;AAAA;AAAA,EAC3C;AACF;AAEA,eAAe,gBAAgB,MAAmC;AAChE,QAAM,WAAW,MAAM,SAASA,MAAK,MAAM,YAAY,CAAC;AACxD,MACE,UAAU,MAAM,OAAO,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,MAAM,kBAAkB,EAAE,KAAK,MAAM,aAAa,GAC9F;AACA,WAAO,EAAE,MAAM,cAAc,QAAQ,QAAQ,UAAU,SAAS;AAAA,EAClE;AACA,QAAM,WAAW,WACb,GAAG,SAAS,QAAQ,CAAC;AAAA;AAAA;AAAA;AAAA,IACrB;AACJ,SAAO,EAAE,MAAM,cAAc,QAAQ,YAAY,OAAO,WAAW,SAAS,SAAS;AACvF;AAEA,SAAS,WAAW,IAA4B;AAC9C,SAAO,OAAO,QAAQ,gBAAgB,GAAG,EAAE;AAC7C;AAEA,SAAS,OAAO,IAAoB,QAAwB;AAC1D,UAAQ,IAAI;AAAA,IACV,KAAK;AACH,aAAO,aAAa,MAAM;AAAA,IAC5B,KAAK;AACH,aAAO,QAAQ,MAAM;AAAA,IACvB,KAAK;AACH,aAAO,QAAQ,MAAM;AAAA,IACvB,KAAK;AACH,aAAO,OAAO,MAAM;AAAA,EACxB;AACF;AAEA,eAAe,SAAS,MAA2C;AACjE,MAAI;AACF,WAAO,MAAMD,UAAS,MAAM,MAAM;AAAA,EACpC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":["readFile","join"]}
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,27 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-clicksmith",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Project installer for ClickSmith: detect your stack, wire stable locators, write agent instructions, merge agents.config.json, and register the MCP server.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
8
|
-
".": {
|
|
9
|
-
"types": "./dist/index.d.ts",
|
|
10
|
-
"import": "./dist/index.js"
|
|
11
|
-
}
|
|
8
|
+
".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" }
|
|
12
9
|
},
|
|
13
10
|
"main": "./dist/index.js",
|
|
14
11
|
"types": "./dist/index.d.ts",
|
|
15
12
|
"bin": {
|
|
16
13
|
"create-clicksmith": "./dist/cli.js"
|
|
17
14
|
},
|
|
18
|
-
"files": [
|
|
19
|
-
"dist"
|
|
20
|
-
],
|
|
21
|
-
"dependencies": {
|
|
22
|
-
"@clicksmith/agent-config": "0.1.0",
|
|
23
|
-
"@clicksmith/core": "0.1.0"
|
|
24
|
-
},
|
|
15
|
+
"files": ["dist"],
|
|
25
16
|
"scripts": {
|
|
26
17
|
"build": "tsup",
|
|
27
18
|
"dev": "tsup --watch",
|
|
@@ -30,5 +21,9 @@
|
|
|
30
21
|
"typecheck": "tsc --noEmit",
|
|
31
22
|
"lint": "eslint src",
|
|
32
23
|
"clean": "rimraf dist .turbo"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@clicksmith/agent-config": "workspace:*",
|
|
27
|
+
"@clicksmith/core": "workspace:*"
|
|
33
28
|
}
|
|
34
|
-
}
|
|
29
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 ClickSmith contributors
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/detect.ts","../src/install.ts"],"sourcesContent":["import { readFile, readdir } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nexport type PackageManager = 'pnpm' | 'yarn' | 'bun' | 'npm';\nexport type Bundler = 'vite' | 'webpack' | 'rspack' | 'rollup' | 'next' | 'unknown';\nexport type Framework = 'react' | 'vue' | 'svelte' | 'angular' | 'solid' | 'unknown';\n\nexport interface ProjectInfo {\n root: string;\n packageManager: PackageManager;\n bundler: Bundler;\n framework: Framework;\n /** Stable attributes already used in the codebase (e.g. data-testid). */\n stableAttrs: string[];\n /** Whether the dev-only data-loc unplugin can be wired up for this stack. */\n supportsUnplugin: boolean;\n /** Path to the detected Vite config, if any (relative to root). */\n viteConfig?: string;\n}\n\nconst KNOWN_STABLE_ATTRS = ['data-testid', 'data-test', 'data-cy', 'data-qa'];\n\n/** Inspect a project directory and infer its stack. Read-only. */\nexport async function detectProject(root: string): Promise<ProjectInfo> {\n const pkg = await readJson(join(root, 'package.json'));\n const deps = { ...(pkg?.dependencies ?? {}), ...(pkg?.devDependencies ?? {}) } as Record<string, string>;\n\n const packageManager = await detectPackageManager(root);\n const bundler = detectBundler(deps);\n const framework = detectFramework(deps);\n const viteConfig = await findFile(root, ['vite.config.ts', 'vite.config.js', 'vite.config.mjs']);\n const stableAttrs = await scanStableAttrs(root);\n\n const supportsUnplugin =\n framework === 'react' && ['vite', 'webpack', 'rspack', 'rollup'].includes(bundler);\n\n return {\n root,\n packageManager,\n bundler,\n framework,\n stableAttrs,\n supportsUnplugin,\n ...(viteConfig ? { viteConfig } : {}),\n };\n}\n\nasync function detectPackageManager(root: string): Promise<PackageManager> {\n if (await fileExists(join(root, 'pnpm-lock.yaml'))) return 'pnpm';\n if (await fileExists(join(root, 'yarn.lock'))) return 'yarn';\n if (await fileExists(join(root, 'bun.lockb'))) return 'bun';\n return 'npm';\n}\n\nfunction detectBundler(deps: Record<string, string>): Bundler {\n if (deps.next) return 'next';\n if (deps.vite) return 'vite';\n if (deps['@rspack/core']) return 'rspack';\n if (deps.webpack) return 'webpack';\n if (deps.rollup) return 'rollup';\n return 'unknown';\n}\n\nfunction detectFramework(deps: Record<string, string>): Framework {\n if (deps['@angular/core']) return 'angular';\n if (deps.svelte) return 'svelte';\n if (deps.vue) return 'vue';\n if (deps['solid-js']) return 'solid';\n if (deps.react) return 'react';\n return 'unknown';\n}\n\n/** Scan a handful of source files for stable test/id attributes. */\nasync function scanStableAttrs(root: string): Promise<string[]> {\n const found = new Set<string>();\n const dirs = ['src', 'app', 'components'];\n for (const dir of dirs) {\n const files = await listSourceFiles(join(root, dir), 40);\n for (const file of files) {\n const content = await safeRead(file);\n if (!content) continue;\n for (const attr of KNOWN_STABLE_ATTRS) {\n if (content.includes(attr)) found.add(attr);\n }\n if (found.size === KNOWN_STABLE_ATTRS.length) return [...found];\n }\n }\n return [...found];\n}\n\nasync function listSourceFiles(dir: string, limit: number): Promise<string[]> {\n const out: string[] = [];\n async function walk(d: string) {\n if (out.length >= limit) return;\n let entries;\n try {\n entries = await readdir(d, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n if (out.length >= limit) return;\n if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue;\n const full = join(d, entry.name);\n if (entry.isDirectory()) await walk(full);\n else if (/\\.(jsx?|tsx?|vue|svelte)$/.test(entry.name)) out.push(full);\n }\n }\n await walk(dir);\n return out;\n}\n\nasync function findFile(root: string, names: string[]): Promise<string | undefined> {\n for (const name of names) {\n if (await fileExists(join(root, name))) return name;\n }\n return undefined;\n}\n\nasync function readJson(file: string): Promise<Record<string, unknown> | undefined> {\n const raw = await safeRead(file);\n if (!raw) return undefined;\n try {\n return JSON.parse(raw);\n } catch {\n return undefined;\n }\n}\n\nasync function safeRead(file: string): Promise<string | undefined> {\n try {\n return await readFile(file, 'utf8');\n } catch {\n return undefined;\n }\n}\n\nasync function fileExists(file: string): Promise<boolean> {\n return (await safeRead(file)) !== undefined;\n}\n","import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport {\n applyManagedBlock,\n DEFAULT_AGENTS_CONFIG,\n mergeAgentsConfig,\n parseAgentsConfig,\n renderInstructions,\n type InstructionTarget,\n} from '@clicksmith/agent-config';\nimport { DEFAULT_DAEMON_PORT } from '@clicksmith/core';\nimport type { PackageManager, ProjectInfo } from './detect.js';\n\nexport interface InstallOptions {\n /** Which agents to render instruction files for. */\n agents?: InstructionTarget[];\n /** Wire the dev-only data-loc unplugin. Defaults to detection. */\n useUnplugin?: boolean;\n daemonPort?: number;\n}\n\nexport interface FileChange {\n path: string;\n action: 'create' | 'merge' | 'skip';\n contents: string;\n reason?: string;\n}\n\nexport interface InstallPlan {\n changes: FileChange[];\n messages: string[];\n nextSteps: string[];\n}\n\nconst DEFAULT_AGENT_TARGETS: InstructionTarget[] = ['claude', 'cursor', 'codex', 'generic'];\n\n/**\n * Compute every file change needed to install ClickSmith into a project,\n * reading existing files so merges preserve user content. Nothing is written —\n * call {@link applyPlan} for that. This split keeps the installer fully testable.\n */\nexport async function planInstall(info: ProjectInfo, options: InstallOptions = {}): Promise<InstallPlan> {\n const root = info.root;\n const targets = options.agents ?? DEFAULT_AGENT_TARGETS;\n const useUnplugin = options.useUnplugin ?? info.supportsUnplugin;\n const port = options.daemonPort ?? DEFAULT_DAEMON_PORT;\n\n const changes: FileChange[] = [];\n const messages: string[] = [];\n const nextSteps: string[] = [];\n\n // 1. Agent instruction files (managed blocks preserve user content).\n for (const target of targets) {\n const rendered = renderInstructions(target, { stableAttrs: info.stableAttrs, daemonPort: port });\n const existing = await readText(join(root, rendered.path));\n const contents = rendered.shared\n ? applyManagedBlock(existing, rendered.content)\n : rendered.content;\n changes.push({\n path: rendered.path,\n action: existing == null ? 'create' : 'merge',\n contents,\n });\n }\n\n // 2. agents.config.json — merge defaults with any existing project config.\n const configPath = join('.clicksmith', 'agents.config.json');\n const existingConfigRaw = await readText(join(root, configPath));\n let merged = DEFAULT_AGENTS_CONFIG;\n if (existingConfigRaw) {\n try {\n const parsed = parseAgentsConfig(JSON.parse(existingConfigRaw));\n if (parsed.ok) merged = mergeAgentsConfig(DEFAULT_AGENTS_CONFIG, parsed.config);\n } catch {\n messages.push('Existing agents.config.json was invalid JSON; left it untouched.');\n }\n }\n changes.push({\n path: configPath,\n action: existingConfigRaw == null ? 'create' : 'merge',\n contents: `${JSON.stringify(merged, null, 2)}\\n`,\n });\n\n // 3. MCP registration for the daemon's stdio server.\n const mcp = mcpCommand(info.packageManager);\n changes.push(await mcpChange(root, '.mcp.json', mcp));\n if (targets.includes('cursor')) {\n changes.push(await mcpChange(root, join('.cursor', 'mcp.json'), mcp));\n }\n\n // 4. Wire the data-loc unplugin (best effort) + record the dependency.\n if (useUnplugin && info.viteConfig) {\n const wired = await wireViteConfig(root, info.viteConfig);\n if (wired) changes.push(wired);\n else\n messages.push(\n `Could not automatically wire ${info.viteConfig}. Add: import clicksmith from '@clicksmith/unplugin/vite'; and put clicksmith() first in plugins.`,\n );\n } else if (useUnplugin) {\n messages.push('No Vite config found — add the @clicksmith/unplugin plugin to your bundler manually.');\n } else {\n messages.push(\n `Stable source locators via unplugin aren't available for this stack; ClickSmith will use ${\n info.stableAttrs.length ? `your attributes (${info.stableAttrs.join(', ')})` : 'attribute/behavioral/DOM'\n } as the fallback locator (source → attr → behavioral → dom).`,\n );\n }\n\n // 5. package.json — add the dependencies needed for the bin + plugin.\n changes.push(await packageJsonChange(root, useUnplugin));\n\n // 6. Ensure .clicksmith/ is gitignored.\n changes.push(await gitignoreChange(root));\n\n nextSteps.push(\n `${installCmd(info.packageManager)} # install the new dependencies`,\n `${runCmd(info.packageManager, 'clicksmith daemon')} # start the localhost daemon`,\n 'Load the ClickSmith extension, toggle AI Mode, and Alt+Click an element.',\n );\n\n return { changes, messages, nextSteps };\n}\n\n/** Apply a plan to disk, writing create/merge changes and skipping the rest. */\nexport async function applyPlan(root: string, plan: InstallPlan): Promise<FileChange[]> {\n const written: FileChange[] = [];\n for (const change of plan.changes) {\n if (change.action === 'skip') continue;\n const full = join(root, change.path);\n await mkdir(dirname(full), { recursive: true });\n await writeFile(full, change.contents, 'utf8');\n written.push(change);\n }\n return written;\n}\n\n/* ------------------------------- helpers ---------------------------------- */\n\ninterface McpServerSpec {\n command: string;\n args: string[];\n}\n\nfunction mcpCommand(pm: PackageManager): McpServerSpec {\n switch (pm) {\n case 'pnpm':\n return { command: 'pnpm', args: ['exec', 'clicksmith', 'mcp'] };\n case 'yarn':\n return { command: 'yarn', args: ['clicksmith', 'mcp'] };\n case 'bun':\n return { command: 'bunx', args: ['clicksmith', 'mcp'] };\n case 'npm':\n return { command: 'npx', args: ['clicksmith', 'mcp'] };\n }\n}\n\nasync function mcpChange(root: string, path: string, spec: McpServerSpec): Promise<FileChange> {\n const existingRaw = await readText(join(root, path));\n let doc: { mcpServers?: Record<string, unknown> } = {};\n if (existingRaw) {\n try {\n doc = JSON.parse(existingRaw);\n } catch {\n doc = {};\n }\n }\n doc.mcpServers = { ...(doc.mcpServers ?? {}), clicksmith: spec };\n return {\n path,\n action: existingRaw == null ? 'create' : 'merge',\n contents: `${JSON.stringify(doc, null, 2)}\\n`,\n };\n}\n\nasync function wireViteConfig(root: string, relPath: string): Promise<FileChange | null> {\n const file = join(root, relPath);\n const code = await readText(file);\n if (code == null) return null;\n if (code.includes('@clicksmith/unplugin')) {\n return { path: relPath, action: 'skip', contents: code, reason: 'already wired' };\n }\n const importLine = `import clicksmith from '@clicksmith/unplugin/vite';\\n`;\n // Insert plugin first in the array so it runs before the framework plugin.\n const pluginsMatch = code.match(/plugins\\s*:\\s*\\[/);\n if (!pluginsMatch) return null;\n const idx = pluginsMatch.index! + pluginsMatch[0].length;\n const next = importLine + code.slice(0, idx) + 'clicksmith(), ' + code.slice(idx);\n return { path: relPath, action: 'merge', contents: next };\n}\n\nasync function packageJsonChange(root: string, useUnplugin: boolean): Promise<FileChange> {\n const raw = await readText(join(root, 'package.json'));\n const pkg = raw ? JSON.parse(raw) : { name: 'project', version: '0.0.0' };\n pkg.devDependencies = { ...(pkg.devDependencies ?? {}) };\n pkg.devDependencies['@clicksmith/daemon'] = pkg.devDependencies['@clicksmith/daemon'] ?? 'latest';\n if (useUnplugin) {\n pkg.devDependencies['@clicksmith/unplugin'] = pkg.devDependencies['@clicksmith/unplugin'] ?? 'latest';\n }\n return {\n path: 'package.json',\n action: raw == null ? 'create' : 'merge',\n contents: `${JSON.stringify(pkg, null, 2)}\\n`,\n };\n}\n\nasync function gitignoreChange(root: string): Promise<FileChange> {\n const existing = await readText(join(root, '.gitignore'));\n if (existing?.split(/\\r?\\n/).some((l) => l.trim() === '.clicksmith/' || l.trim() === '.clicksmith')) {\n return { path: '.gitignore', action: 'skip', contents: existing };\n }\n const contents = existing\n ? `${existing.trimEnd()}\\n\\n# ClickSmith runtime state\\n.clicksmith/\\n`\n : '# ClickSmith runtime state\\n.clicksmith/\\n';\n return { path: '.gitignore', action: existing == null ? 'create' : 'merge', contents };\n}\n\nfunction installCmd(pm: PackageManager): string {\n return pm === 'npm' ? 'npm install' : `${pm} install`;\n}\n\nfunction runCmd(pm: PackageManager, script: string): string {\n switch (pm) {\n case 'pnpm':\n return `pnpm exec ${script}`;\n case 'yarn':\n return `yarn ${script}`;\n case 'bun':\n return `bunx ${script}`;\n case 'npm':\n return `npx ${script}`;\n }\n}\n\nasync function readText(file: string): Promise<string | undefined> {\n try {\n return await readFile(file, 'utf8');\n } catch {\n return undefined;\n }\n}\n"],"mappings":"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","names":["readFile","join"]}
|