---
title: Agents and automation
description: Read JSON results from the c15t CLI, call it from another program
  with runCli, and give a coding agent a v3 setup or migration task.
group: cli
lastModified: "2026-10-10T16:01:45+01:00"
---
## Read JSON results

```bash
npx @c15t/cli@alpha setup offline --plan --json --no-telemetry
```

`--json` writes one JSON document to stdout. Human diagnostics go to stderr. Check `success` before consuming `data`; a completed process alone does not mean the command succeeded.

```json
{
  "schemaVersion": 1,
  "success": false,
  "command": "setup",
  "exitCode": 1,
  "error": {
    "code": "INPUT_REQUIRED",
    "message": "Required command input is missing"
  }
}
```

Commands return their own data under `data`, such as generated edits, hosted projects, or a migration report. Do not parse terminal messages for those values.

## Call the CLI from code

```ts
import { runCli } from '@c15t/cli';

const result = await runCli(['setup', 'offline', '--plan', '--json'], {
	cwd: projectDirectory,
});

if (!result.success) {
	throw new Error(result.error?.message);
}
```

Importing the package does not execute a command. `runCli` returns a result without changing process arguments, the working directory, or the exit status. Prompts and telemetry are disabled by default for library callers. Supply a `logger` to route diagnostics into the host CLI. The executable adapter owns JSON serialization and process exit status.

CommonJS callers on Node versions that support `require(esm)` can use `const { runCli } = require('@c15t/cli')`.

For Inth or another host, use explicit project inputs and inspect the returned result. Standalone account commands delegate to the pinned Inth executable and use Inth's existing session and organization context. The runner never reads Inth credentials. Hosts that already own account operations should use the independent frontend exports below.

## Reuse generation in another CLI

Install `@c15t/cli@alpha` in the host project. Import the generation entry point to create a framework's quickstart files without loading the interactive runner:

```ts
import { generate, runGenerateCommand } from '@c15t/cli/generate';

const plan = generate({
	framework: 'react',
	mode: 'hosted',
	backendURL: projectBackendURL,
	scripts: ['google-tag'],
});

// Forward arguments after `inth c15t generate`:
const forwardedPlan = runGenerateCommand([
	'hosted',
	'--framework',
	'react',
	'--backend-url',
	projectBackendURL,
]);
```

Both functions return `{ files, merge, dependencies, instructions }` synchronously. `files` maps paths relative to the project root to the contents a project without them gets, such as `.env`, `c15t.config.ts`, `next.config.ts` and `app/layout.tsx` for `next-app`. They are the files of the framework's quickstart. Hosted mode writes the backend URL to `.env` under the framework's public env var, such as `NEXT_PUBLIC_C15T_BACKEND_URL` or `VITE_C15T_BACKEND_URL`. No other file holds the URL, and no file is a `.gitignore`.

`merge` says how to apply a file the project already has: `env` adds the generated `KEY=value` lines to an existing `.env` and keeps its other keys, but skips a `*_C15T_BACKEND_URL` line when the file already sets the matching `*_INTH_PROJECT_URL`, `insert` adds a snippet such as the privacy settings link to an existing `index.html`, and `keep` leaves the file alone. `mergeFile(existing, generated, merge)` applies one. Replace other existing files only when the user asks.

`dependencies` contains installation arguments on the CLI's release line, such as `c15t@alpha` from an alpha CLI or `c15t@3` from a stable v3 CLI. `getInstallSpecifier` applies the same rule as the CLI's own installs to bare c15t package names and preserves explicit specifiers and external package names. The published source records the CLI version it shipped with, so vendored source keeps that release line. `generateBoilerplateTemplate` exposes the lower-level template with bare dependency names.

The host CLI owns writing files, checking existing contents and symlinks, installing dependencies, diagnostics, and authentication. These functions do not read the application, detect its framework, prompt, write files, install packages, or access the network. Use the project's provisioned backend URL for hosted mode.

`runGenerateCommand` accepts `hosted` or `offline`, `--framework`, and optional `--backend-url` and `--scripts` values. Value flags accept `--flag value` or `--flag=value`. A second argument supplies host defaults for these inputs. Without defaults, mode and framework are required. Explicit arguments override defaults; selecting offline clears an inherited backend URL. Repeated flags are rejected. `parseGenerateOptions` exposes the same parser without generating files. It supports the [quickstart framework targets](./commands/boilerplate). Hosted mode requires an absolute HTTP or HTTPS URL without embedded credentials, whitespace or control characters. Offline mode rejects a backend URL. Invalid frameworks, integrations and flags, including the removed `--output`, throw an `Error`. Runner flags such as `--apply`, `--json`, and `--package-source` belong to the host rather than this parser.

For Node hosts that need the full CLI command metadata and actions, `import { commands } from '@c15t/cli/commands'` exports the same registry used by the runner. Actions accept a c15t `CliContext`; use `runCli` when you need the runner to create that context.

## Frontend commands with host state

Use `@c15t/cli/frontend` when Inth or another host owns authentication and the selected project. The dispatcher accepts arguments after the `c15t` namespace and returns `{ command, data }` synchronously:

```ts
import { runFrontendCommand } from '@c15t/cli/frontend';

const result = runFrontendCommand(['setup', '--framework', 'react'], {
	generation: { backendURL: projectBackendURL },
});
```

`setup` and `generate` return the standalone file plan. The frontend dispatcher defaults to hosted mode. Supply a framework through arguments or `generation.framework`; no detection runs. Explicit flags override `generation` defaults. `setup offline` ignores the host's inherited backend URL, while an explicit offline `--backend-url` is an error.

A host can supply its already-fetched projects and current selection instead of a backend URL:

```ts
import { runFrontendCommand } from '@c15t/cli/frontend';

const context = {
	generation: { framework: 'react' as const },
	projects: availableProjects,
	selectedProject: selectedProjectId,
};

const plan = runFrontendCommand(['generate'], context);
const selection = runFrontendCommand(
	['projects', 'select', 'organization/project'],
	context
);
```

Projects use `{ id, name, organizationSlug?, status, url }`. `status` is `active`, `pending`, or `inactive`; `url` is the provisioned consent backend URL. Generation resolves the selected ID or unambiguous `organization/name` and rejects an unavailable or invalid backend. An explicit backend URL takes precedence over the selected project.

|Command|Returned `data`|Host responsibility|
|--|--|--|
|`setup`, `generate`|`{ files, dependencies, instructions }`|Review and write files, handle conflicts and symlinks, install dependencies, wire the application.|
|`projects`, `projects list`|`{ projects, selectedProject }`|Fetch projects using the host's authenticated client.|
|`projects select <id\|organization/name>`|`{ project, selectedProject }`|Persist the returned project ID and refresh the generation context.|
|`status`|`{ authenticated, status, expiresAt?, origin?, selectedProject? }`|Resolve session state and expiry in the host.|

`status` requires `context.authentication` with `isLoggedIn` and `isExpired`. Optional metadata is limited to `expiresAt`, `origin`, and `selectedProject`. It never returns access or refresh tokens. Project commands require `context.projects`, including an empty array when there are no projects. Missing host state is an error rather than a network request or implicit login.

The host owns prompts, project creation, login, logout, token refresh, and persistence. Selection returns data and does not update the context. These commands have no database dependencies. Codemods, self-hosting commands, and runner flags such as `--apply` or `--json` are rejected by the frontend dispatcher. The standalone Node CLI uses the same project resolver, backend URL validation, and account status logic with its own runtime adapters.

## Plan and apply frontend files

Use `@c15t/cli/frontend/runtime` when the host needs filesystem operations and
dependency installation. It uses Node built-ins supported by scriptc 0.2.0:

```ts
import { runGenerationWorkflow } from '@c15t/cli/frontend/runtime';

const result = await runGenerationWorkflow(
	['generate', '--framework', 'react', '--apply'],
	{ generation: { backendURL: projectBackendURL } },
	{
		cwd: projectDirectory,
		packageManager: 'bun',
		signal: abortController.signal,
	}
);
```

The application directory must exist and contain a regular `package.json`.
Generation defaults to a plan without writes or installation. `--plan` and
`--dry-run` make that choice explicit. `--apply` creates files and requires a
host-supplied package manager or `--skip-install`. These flags belong to the
runtime; the pure frontend dispatcher continues to reject them. The host still
owns help, `--json`, working-directory options, framework detection, authentication
and application wiring.

The result contains `{ command, applied, created, installed, plan, recovered }`.
`plan` contains the canonical application `root`, `files` with `path`, `content`
and `exists`, release-line `dependencies`, and `instructions`. Existing files
must already match, except files the plan merges: when an existing `.env` or
`index.html` lacks the generated lines, the runtime leaves it alone and adds an
instruction with the lines to add. Apply checks the reviewed files again before
writing and refuses conflicts, symlink targets and symlink ancestors, including
dangling symlinks.
The application root itself resolves to its real directory.

Files stage in `.c15t-native-generation` and publish with exclusive hard links.
Where hard links are unavailable, apply creates each file exclusively, so existing
files are never replaced, and records the copy's identity after writing it. A
copied file that the filesystem cannot tell apart from an identical replacement
may be removed during recovery and written again on resume. An interrupted
apply blocks further generation. Use `--resume --apply` with the original command
to recover and regenerate. Recovery checks every record and generated file before
removing files it published. Files it cannot prove it published, including
replacements with identical contents, stay in place. Edited generated files,
unexpected staging contents, and malformed records stop recovery for inspection.
Recovery removes generated directories only when they are empty. This supports
process interruption; it does not promise recovery from filesystem corruption or
power loss. Without a journal, recovery deletes leftover staged files and leaves
application files untouched. A partial journal with staged files requires
inspection. The Node runner's `.c15t-generation.json` uses its own recovery
path and cannot be resumed by this runtime.

Dependency installation runs after file apply commits. npm, pnpm, yarn, and Bun
run in the application directory with installer output on stderr. Cancellation
stops the direct installer process. Cancellation before or during installation
reports that generated files remain, and workflow errors list the created files.
Installer failure leaves generated files in
place and reports a retry command; package-manager changes to manifests, lockfiles
and `node_modules` do not roll back. For manual installation, use `--skip-install`
and the returned dependencies. `planGeneration`, `applyGeneration`,
`recoverGeneration`, `installGenerationDependencies`, and
`parseGenerationWorkflowArguments` expose the same steps separately.

Native dependency installation is supported on macOS and Linux. On Windows,
use `--skip-install` and install the returned dependencies manually. A workflow
that requests installation on Windows fails before writing application files.

## Compile native frontend commands with scriptc

The published package includes dependency-free generation and frontend TypeScript sources. With scriptc 0.2.0, copy both source directories into the host's vendor directory before compilation. scriptc treats imports under `node_modules` as npm dependencies, so directly importing the npm entry points does not establish static native compilation.

Create `scripts/vendor-c15t.mjs` in the host project and run it with Node during the build:

```js
import { cpSync, mkdirSync, rmSync } from 'node:fs';
import { createRequire } from 'node:module';
import { dirname, resolve } from 'node:path';

const require = createRequire(import.meta.url);
for (const entry of ['generate', 'frontend']) {
	const source = dirname(require.resolve(`@c15t/cli/${entry}/source`));
	const destination = resolve(`vendor/c15t/${entry}`);
	mkdirSync(dirname(destination), { recursive: true });
	rmSync(destination, { recursive: true, force: true });
	cpSync(source, destination, { recursive: true });
}
```

Copy both complete directories and keep them as siblings. Preserve the source contents; NodeNext hosts may add explicit `.ts` or `/index.ts` extensions to relative import specifiers during vendoring. The frontend source imports the generation source through a relative path. Regenerate them when updating the locked `@c15t/cli@alpha` dependency. The published source paths exist for this build step. Generation-only hosts can copy just the generation directory.

In the host's `cli.ts`, route the generation subcommand to the vendored parser. This example prints the plan; integrate it with Inth's own plan/apply and installation behavior to create application files:

```ts
import { runGenerateCommand } from './vendor/c15t/generate/index.ts';

try {
	const args = process.argv.slice(2);
	if (args[0] !== 'c15t' || args[1] !== 'generate') {
		throw new Error('Usage: inth c15t generate <mode> --framework <framework>');
	}
	const plan = runGenerateCommand(args.slice(2));
	process.stdout.write(`${JSON.stringify(plan)}\n`);
} catch (error) {
	process.stderr.write(
		`${error instanceof Error ? error.message : String(error)}\n`
	);
	process.exitCode = 1;
}
```

Build with scriptc 0.2.0 in its default static mode, without `--dynamic`:

```bash
node scripts/vendor-c15t.mjs
scriptc build cli.ts --out inth
./inth c15t generate hosted --framework react --backend-url https://your-project.inth.app
```

Replace the example URL with the exact endpoint provisioned for the project. The result contains the generated source and release-line installation arguments. To route the frontend command set, import `runFrontendCommand` from `./vendor/c15t/frontend/index.ts`, forward arguments after the `c15t` namespace, and supply the host context described above. Native verification covers hosted and offline boilerplate for all targets, generation defaults, project list/select, account status, filesystem apply, conflicts, symlinks, recovery, and release-line installer arguments. Import `runGenerationWorkflow` from `./vendor/c15t/frontend/runtime/index.ts` to plan and apply standalone frontend files natively. Interactive application-root editing, codemods, and database migrations continue to use the Node runner.

## Agent setup and v3 migration workflow

Launching Codex through the CLI is supported on macOS and Linux. On Windows,
use `--plan` to copy the prompt and run it in Codex manually.

Use `c15t setup --codex --plan` to print the setup prompt and copy it to your
clipboard without launching Codex. The prompt goes to stdout as plain text.
The copy confirmation or failure notice is diagnostic output, so
`c15t setup --codex --plan > prompt.txt` saves only the prompt. On Linux the
CLI tries `wl-copy`, `xclip` and `xsel`, then `clip.exe` under WSL. `--dry-run`
does the same. Add `--json` to export the prompt without clipboard access.

Launch your installed Codex CLI from the application directory:

```bash
c15t setup --codex
c15t setup --codex hosted --backend-url https://your-project.inth.app
```

Replace the example URL with the provisioned consent backend URL. With an Inth
connection, `--project <id|name>` resolves that project's backend URL.
Use either `--project` or `--backend-url`. `--framework` and `--scripts` provide
optional hints. Explicitly select `offline` or `custom` when needed. Without a
mode, the task asks the agent to confirm it with you. The prompt includes a
public inputs section only when you supply configuration.

Codex receives the default c15t v3 frontend task and named public inputs. The
agent inventories the application and its analytics, pixels and embeds, then
takes the install, upgrade or replace path. It resolves exact package versions
from the CLI's npm dist-tag, reads the version-matched bundled docs, runs the
upgrade guide's codemods, moves every tool behind consent, and verifies consent
in a browser. Docs links in the task point at the site for the CLI's release
line: `https://v3.c15t.com` for v3 prereleases, `https://c15t.com` otherwise.
It does not provision a backend or migrate a database. The prompt includes the parsed
backend URL, and setup rejects URLs containing whitespace or control
characters. Review the resulting diff and the agent's verification report
before deploying.

Launch requires an interactive terminal and a `codex` executable on `PATH`.
When Codex cannot start, setup fails with `AGENT_NOT_STARTED` and leaves the
project unchanged. `AGENT_FAILED` means Codex ran and exited unsuccessfully, so
review its edits.
The CLI inherits Codex's approval and sandbox settings; `--yes` does not
change them. A successful exit means the agent session ended successfully,
not that the CLI independently verified the application's behavior. Interrupting
setup stops the direct child process and leaves any edits for review.

Preview or export the complete task without launching an agent:

```bash
c15t setup --codex --plan
c15t setup --codex --plan --json
```

`--dry-run` also previews the task. Live agent launch rejects `--json` and
`--non-interactive`. Scaffold options such as `--boilerplate`, `--overwrite`,
`--apply`, `--resume`, and `--skip-install` are not supported with `--codex`.
Discuss styling, SSR, proxying, and other frontend preferences in the agent
session. `generate` retains deterministic generation and rejects `--codex`.

Hosts can reuse the same prompt and launcher:

```ts
import {
	createAgentSetupPlan,
	launchAgentSetup,
} from '@c15t/cli/frontend/agent';

const plan = createAgentSetupPlan({ backendURL: provisionedBackendURL });
// Show plan.prompt for review, or hand it to another coding agent.
const exitCode = await launchAgentSetup(projectDirectory, plan, abortSignal);
```

`createAgentSetupPlan` performs no file or network operations. Its options are
`mode`, `backendURL`, `framework`, and `scripts`; it copies only those fields
into the task. Hosts keep authentication and project selection in their own
code. The module also exports `DEFAULT_C15T_SETUP_PROMPT`, `AgentSetupOptions`,
and `AgentSetupPlan`.

Hosts that write their own task can reuse the c15t steps alone.
`createC15tSetupInstructions` returns the inventory, install, upgrade and
replace paths, consent-gating rules, browser checks and handoff as Markdown,
without account or backend provisioning steps:

```ts
import { createC15tSetupInstructions } from '@c15t/cli/frontend/agent';

const task = `${hostAccountSteps}\n\n${createC15tSetupInstructions({
	firstStep: 4,
	mode: 'hosted',
})}`;
```

Its options are `origin`, the docs site the agent reads; `distTag`, the npm
dist-tag it resolves exact versions from; `mode`, which is `hosted`, `offline`
or `custom`, or omitted so the agent asks; and `firstStep`, the number of the
first c15t step when the host puts its own steps first. Steps refer to each
other by name, so renumbering them breaks no reference. `origin` and `distTag`
default to the CLI's release line. Invalid values throw.

`createC15tIntegrationGuidance({ origin })` returns only the rules for moving
analytics, pixels, tag managers and embeds behind consent, including the
replacements for framework vendor packages such as `@next/third-parties` and
`@nuxt/scripts`. Use it to embed those rules in another prompt or skill.

A missing executable produces an installation hint;
`launchAgentSetup` returns the agent's exit code and rejects on caller
cancellation or launch failure. `isAgentNotStartedError` identifies rejections
raised before Codex ran.

For a scriptc host, vendor both source directories as described above and
import from `./vendor/c15t/frontend/agent/index.ts`. The prompt and launcher
compile statically with scriptc 0.2.0 without `--dynamic`. This compiles the
handoff; Codex still needs its own installed executable, authentication, and
network access to perform the task.

For v3, source code and installed documentation determine the migration work. The legacy codemod collection targets v2, apart from the named `use-consent-manager-to-hooks` and `scripts-to-integrations` transforms. It does not update dependencies, convert a v2 backend configuration to v3, or migrate a database.

`skills` delegates to an external interactive installer and does not support JSON output. Use installed bundled docs directly when building unattended automation.

Environment-file edits in setup results contain only `path`, `operation` (`create` or `update`), and `redacted: true`. This also applies when either a symlink's name or its target is an environment file. Their original and proposed contents stay out of JSON output. Setup keeps the full contents internally to apply edits and restore files if generation fails.
