How we built a local-first AI wizard for tag installation

The Contentsquare tracking tag is the small but powerful JavaScript snippet that connects your website to Contentsquare: it fuels the whole set of Contentsquare features with performance and privacy at its core. Installing it should be straightforward. In practice, it often depends on the shape of the application: the framework entry point, routing model, Content Security Policy, local development setup, and how pageviews are triggered in single-page applications.

We built a local-first AI wizard to make this easier.

The wizard installs agent instructions into the developer’s project, then lets their own coding agent apply and verify the Contentsquare setup. The customer’s source code stays on their machine. Contentsquare does not need access to the repository, and the developer remains in control of every change before it is committed or pushed.

This post explains the engineering decisions that shaped the wizard, what we changed along the way, and why we ended up with a hybrid model: agents for adaptation, deterministic commands for setup and verification.

Keep the customer’s code on their machine

A common way to build an AI installation flow is to upload the codebase to a hosted service, run inference there, then return suggested changes. That can work well for some products, but it raises an immediate question for customers concerned with privacy: where does the source code go?

We chose a local-first model.

The wizard installs skill files into the developer’s project and lets their existing coding agent — such as Claude Code, GitHub Copilot, or Cursor — read and execute them. Contentsquare does not need access to the repository, and the customer’s codebase is never sent to our infrastructure.

This matters for customers with strict security, privacy, or data residency requirements. It also matches how developers already work. They can run the wizard in the project they already have open, using the agent they already use, with the same permissions, repository access, and review process they normally rely on.

The developer also stays in control of the generated changes. The agent proposes and applies modifications locally, but nothing is automatically pushed. The user can inspect the diff, review the files, run tests, adjust the implementation, and only then commit the result. That review step is an important part of the experience: AI can help with the setup without taking ownership of the codebase.

This local-first model also simplified our architecture. We did not need to host an inference endpoint, manage repository access, or build another code editing interface. We could focus on writing clear, testable instructions and making the deterministic parts of the flow reliable.

Start with one general skill, then specialize

Our first version was deliberately simple: one instruction set that told the agent how to detect the framework, install @contentsquare/tag-sdk, and inject injectContentsquareScript at the right entry point.

That worked for the simplest cases. Then real applications started to expose the next layer of complexity.

Single-page applications were the first to expose it. Navigation between routes does not always trigger a new pageview automatically. The fix is known, but the location depends on the framework and router. A plain React app is not the same as Next.js App Router. Vue Router is not the same as Angular Router. SvelteKit has its own conventions.

For example, in a classic React application, the agent may need to update the application entry point or attach tracking to React Router navigation. In a Next.js App Router project, it has to respect the distinction between server and client components, and place the initialization where browser-side execution is allowed.

So we added a dedicated step for artificial pageviews: detect the SPA type, find the router, and write the correct hook. Then we added more framework-specific instructions. Then we added CSP guidance.

The pattern stayed the same each time: start with one focused instruction, test it against real project structures, identify where generic guidance breaks down, and add explicit cases only where they are needed.

The skill files now cover multiple framework variants for tag installation and SPA pageview tracking. But we did not try to design the complete instruction set up front. Starting with one general-purpose skill helped us keep the system understandable, then grow it from observed failures rather than speculation.

Use agents for adaptation, not deterministic checks

The biggest design change came from verification.

At first, we used browser automation through a Playwright MCP server. The agent could open a browser, navigate to the customer’s local application, inspect network requests, and decide whether the tag was installed correctly.

It worked often enough to be promising, but not consistently enough to be a reliable product experience. On smaller models, the agent sometimes navigated too early, picked the wrong URL, missed the relevant network request, or got stuck debugging the page instead of verifying the tag.

The issue was not Playwright. The issue was that we were asking the agent to reason about something deterministic.

For Contentsquare tag verification, we know exactly what to check:

  • The tag script is loaded from the expected Contentsquare domain.
  • An initial pageview is sent.
  • SPA navigation can trigger artificial pageviews where needed.
  • CSP violations do not block the required domains.

So we moved that logic into the wizard.

npx @contentsquare/wizard verify runs a bundled browser session and returns a structured verification report. The agent no longer decides how to test the tag. It runs the command, reads the result, and uses that output to continue.

For example, the verification output looks like this:

tagScriptLoaded: true
initialPageview: true
cspViolations: []

This made failures easier to understand. If verification fails, we know whether the script did not load, the pageview was missing, or CSP blocked a request. We are no longer debugging an LLM browser session and the tag installation at the same time.

It also improved the developer experience. Instead of trusting that the agent “probably” installed the tag correctly, the developer gets a verification result based on the running application.

The lesson was simple: use agents for the parts that require adaptation, such as understanding a project structure or editing code in the right place. Use deterministic code for checks where the expected behavior is already known.

Prefer portable wizard commands over agent-specific protocols

Once verification moved into the wizard, we looked again at the rest of the setup.

Our initial approach used MCP for more than browser automation. It helped with setup tasks, state management, and project configuration. MCP is powerful, but it also adds a dependency on agent support and local configuration. Not every coding agent supports it in the same way, and not every developer wants another integration layer before they can start.

We moved to a simpler model.

npx @contentsquare/wizard install bootstraps the setup. It installs the right instructions, configures the supported agent environment, and prepares the project for AI-assisted installation.

npx @contentsquare/wizard verify validates the result against the running application.

The agent can invoke both as shell commands. No dedicated MCP server is required. The same commands are also useful without an agent. A developer can run verify manually at any time and get a human-readable report of what the wizard found.

This made the wizard more portable. The agent ecosystem changes quickly, but almost every coding agent can read files and run shell commands. That became our compatibility layer. Instead of depending on one integration protocol, we made the wizard usable from any environment that can execute npx.

The wizard is intentionally scoped: bootstrap the agent setup, then verify the installation. We kept more advanced configuration workflows out of the initial surface area until we could make them as predictable as verification.

That scope gives us a path to grow. Future configuration steps — such as page masking settings, artificial pageview configuration, custom variables, or CSP adjustments — can become discrete wizard commands. The agent can still decide where a change belongs in the codebase, but the product logic can stay in deterministic tooling.

Make the setup reusable, not one-shot

Tag installation is not always a one-time event.

Applications change. Frameworks get upgraded. Routing changes. CSP rules evolve. A tag that was correctly installed six months ago can become incomplete after a migration, a refactor, or a new deployment constraint.

Because the wizard is local and command-driven, it can be run again later. Developers can relaunch it to detect changes in the project and adjust the configuration when needed. The same applies after introducing a new router, changing the application entry point, moving to a stricter CSP, or adding new pages that require specific tracking behavior.

This is another reason we did not want the wizard to be a hosted, one-off generation flow. The useful product is not only “install the tag once”. It is “help keep the implementation aligned with the application as it evolves”.

Over time, the wizard can become a maintenance assistant for the Contentsquare setup: detect what changed, verify what still works, and guide the agent toward the smallest required update.

What it looks like today

The setup has two parts.

The first part is the public agent repository. Today, it lives in ContentSquare/skills, and we plan to rename it to agents. It contains the skill files and plugins that coding agents use to understand how to install and configure Contentsquare.

Keeping those instructions public is intentional. Developers should be able to inspect what their agent is being asked to do. The instructions are written in Markdown, reviewed like code, and versioned in Git.

Using Markdown turned out to be a practical choice. It made the behavior reviewable in pull requests, easy to test on fixture applications, and transparent to developers installing it. There is no hidden prompt pipeline. The instructions are part of the repository.

The second part is the wizard, distributed on npm as @contentsquare/wizard. It handles the deterministic parts of the flow: bootstrapping the setup, preparing the agent environment, storing state where needed, and verifying the installation.

Developers can start with:

npx @contentsquare/wizard install

Then, once the skills are installed, they can interact with their agent naturally:

Add Contentsquare to my website using tag ID YOUR_TAG_ID

And they can validate the result with:

npx @contentsquare/wizard verify

The wizard is generally available. We are actively extending coverage for more installation and configuration scenarios, including dynamic IDs, TMS-based setups, hybrid mobile and web applications, stricter CSP configurations, and recurring validation after project changes.

What we learned

The main lesson is that “agentic” does not mean “let the agent do everything”.

Agents are useful when the task requires adaptation: reading an unfamiliar codebase, understanding framework conventions, finding the right file, and applying a change in context. They are less useful when the workflow has a known expected result and can be expressed as code.

For us, the best design was a hybrid one:

  • The agent handles project-specific reasoning.
  • The wizard handles deterministic setup and verification.
  • The developer keeps control of the code and reviews every change locally.

That boundary made the system more reliable, easier to debug, and easier to adopt across different coding agents.

It also gave us a cleaner product direction. The wizard is not only a way to install the Contentsquare tag once. It is becoming a local assistant that can help developers install, verify, and maintain their Contentsquare configuration as their application changes.

Thanks to the Tag team for all the hard work on this project 💪