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.
@@ -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, { stableAttrs: info.stableAttrs, daemonPort: port });
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
- let merged = DEFAULT_AGENTS_CONFIG;
142
- if (existingConfigRaw) {
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) merged = mergeAgentsConfig(DEFAULT_AGENTS_CONFIG, parsed.config);
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("No Vite config found \u2014 add the @clicksmith/unplugin plugin to your bundler manually.");
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
- "Load the ClickSmith extension, toggle AI Mode, and Alt+Click an element."
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-WEMLICDQ.js.map
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":"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","names":["readFile","join"]}
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  applyPlan,
4
4
  detectProject,
5
5
  planInstall
6
- } from "./chunk-WEMLICDQ.js";
6
+ } from "./chunk-TE3BLNLM.js";
7
7
 
8
8
  // src/cli.ts
9
9
  var HELP = `create-clicksmith
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  applyPlan,
3
3
  detectProject,
4
4
  planInstall
5
- } from "./chunk-WEMLICDQ.js";
5
+ } from "./chunk-TE3BLNLM.js";
6
6
  export {
7
7
  applyPlan,
8
8
  detectProject,
package/package.json CHANGED
@@ -1,27 +1,18 @@
1
1
  {
2
2
  "name": "create-clicksmith",
3
- "version": "0.1.0",
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"]}