@ignite-agent/agent 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,5 @@
1
+ Copyright (c) Ignite. All rights reserved.
2
+
3
+ This package is provided to Ignite customers for the sole purpose of integrating
4
+ their site with the Ignite service. Use is subject to the Ignite Terms of Service.
5
+ No other use, redistribution, or modification is permitted without written consent.
package/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # @ignite-agent/agent
2
+
3
+ Ignite for Next.js. A drop-in App Router SDK that lets your Ignite workspace drive your site's `robots.txt`,
4
+ `sitemap.xml`, JSON-LD, and page metadata, log crawler visits, and run the approve-and-apply change loop.
5
+
6
+ Requires the **App Router** (Next.js 15 or 16) with server rendering. Content and directives resolve on the
7
+ server; your site token never reaches the browser.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install @ignite-agent/agent
13
+ ```
14
+
15
+ Set two environment variables (in `.env.local` and in your host, e.g. Vercel project settings):
16
+
17
+ ```
18
+ IGNITE_SITE_ID=your-site-id
19
+ IGNITE_SITE_TOKEN=your-site-token
20
+ ```
21
+
22
+ Provision them from the CLI:
23
+
24
+ ```bash
25
+ npx ignite connect --site-url https://yourdomain.com --email you@yourdomain.com
26
+ # place the domain challenge it prints, get the code from your email, then:
27
+ npx ignite confirm --activation <activation_id> --code <email_code> # writes .env.local
28
+ ```
29
+
30
+ Or copy them from your Ignite workspace.
31
+
32
+ ## Wire it up
33
+
34
+ Three files, each a one-line re-export:
35
+
36
+ ```ts
37
+ // app/robots.ts
38
+ export { default } from '@ignite-agent/agent/robots'
39
+ ```
40
+
41
+ ```ts
42
+ // app/sitemap.ts
43
+ export { default } from '@ignite-agent/agent/sitemap'
44
+ ```
45
+
46
+ ```tsx
47
+ // app/layout.tsx
48
+ import { IgniteJsonLd } from '@ignite-agent/agent/jsonld'
49
+
50
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
51
+ return (
52
+ <html>
53
+ <head>
54
+ <IgniteJsonLd />
55
+ </head>
56
+ <body>{children}</body>
57
+ </html>
58
+ )
59
+ }
60
+ ```
61
+
62
+ Per-page metadata (optional):
63
+
64
+ ```ts
65
+ import { igniteMetadata } from '@ignite-agent/agent/metadata'
66
+
67
+ export async function generateMetadata() {
68
+ return igniteMetadata('/pricing')
69
+ }
70
+ ```
71
+
72
+ Crawler logging (optional, recommended):
73
+
74
+ ```ts
75
+ // proxy.ts (project root)
76
+ export { proxy, config } from '@ignite-agent/agent/proxy'
77
+ ```
78
+
79
+ Or let the CLI write all of the above:
80
+
81
+ ```bash
82
+ npx ignite init
83
+ ```
84
+
85
+ ## Three ways to onboard
86
+
87
+ - **Self-serve.** `npm install`, set the two env vars, run `npx ignite init`, deploy.
88
+ - **With a coding agent.** Hand `onboarding/CLAUDE-CODE-SETUP.md` to Claude Code (or similar) in your repo; it
89
+ installs, wires the files, and verifies the build.
90
+ - **Managed.** The Ignite team does the setup in your repo for you.
91
+
92
+ ## How it degrades
93
+
94
+ Ignite is additive. If the SDK is unconfigured or the Ignite API is unreachable, `robots` falls back to
95
+ allow-all, `sitemap` returns empty, JSON-LD renders nothing, and `metadata` returns `{}` — your app's own
96
+ defaults stand. It never blocks a build or a crawler by accident.
97
+
98
+ ## Environment
99
+
100
+ | Variable | Required | Default |
101
+ |---|---|---|
102
+ | `IGNITE_SITE_ID` | yes | — |
103
+ | `IGNITE_SITE_TOKEN` | yes | — |
104
+ | `IGNITE_HUB_URL` | no | `https://app.igniteagent.ai` |
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { dirname, join } from 'node:path';
4
+ const HUB_URL = (process.env.IGNITE_HUB_URL?.trim() || 'https://app.igniteagent.ai').replace(/\/+$/, '');
5
+ function write(path, contents) {
6
+ if (existsSync(path)) {
7
+ console.log(` skip ${path} (already exists)`);
8
+ return;
9
+ }
10
+ mkdirSync(dirname(path), { recursive: true });
11
+ writeFileSync(path, contents);
12
+ console.log(` write ${path}`);
13
+ }
14
+ function init() {
15
+ const cwd = process.cwd();
16
+ if (!existsSync(join(cwd, 'app'))) {
17
+ console.error('No app/ directory found. Run this from the root of a Next.js App Router project.');
18
+ process.exit(1);
19
+ }
20
+ write(join(cwd, 'app/robots.ts'), `export { default } from '@ignite-agent/agent/robots'\n`);
21
+ write(join(cwd, 'app/sitemap.ts'), `export { default } from '@ignite-agent/agent/sitemap'\n`);
22
+ write(join(cwd, 'proxy.ts'), `export { proxy, config } from '@ignite-agent/agent/proxy'\n`);
23
+ console.log('\nAdd <IgniteJsonLd /> to app/layout.tsx:');
24
+ console.log(` import { IgniteJsonLd } from '@ignite-agent/agent/jsonld' // then render <IgniteJsonLd /> in <head>`);
25
+ console.log('\nSet IGNITE_SITE_ID and IGNITE_SITE_TOKEN in .env.local and in your host, then deploy.');
26
+ }
27
+ async function doctor() {
28
+ const siteId = process.env.IGNITE_SITE_ID?.trim();
29
+ const token = process.env.IGNITE_SITE_TOKEN?.trim();
30
+ if (!siteId || !token) {
31
+ console.error('Missing IGNITE_SITE_ID or IGNITE_SITE_TOKEN. Run `ignite connect` or set them in your env.');
32
+ process.exit(1);
33
+ }
34
+ try {
35
+ const res = await fetch(`${HUB_URL}/v1/sites/${siteId}/agent-directives`, {
36
+ headers: { 'X-Ignite-Site-Token': token },
37
+ });
38
+ console.log(res.ok ? `ok reached ${HUB_URL} (HTTP ${res.status})` : `fail HTTP ${res.status} from ${HUB_URL}`);
39
+ process.exit(res.ok ? 0 : 1);
40
+ }
41
+ catch (error) {
42
+ console.error(`fail could not reach ${HUB_URL}: ${error.message}`);
43
+ process.exit(1);
44
+ }
45
+ }
46
+ // Leg one: prove domain + email. The Ignite API emails a code and returns a domain challenge to place.
47
+ async function connect() {
48
+ const args = parseFlags(process.argv.slice(3));
49
+ const siteUrl = args['site-url'] || args.domain;
50
+ const email = args.email;
51
+ if (!siteUrl || !email) {
52
+ console.error('Usage: ignite connect --site-url <https://yourdomain.com> --email <owner-email>');
53
+ process.exit(1);
54
+ }
55
+ const res = await fetch(`${HUB_URL}/v1/activate`, {
56
+ method: 'POST',
57
+ headers: { 'Content-Type': 'application/json' },
58
+ body: JSON.stringify({ site_url: siteUrl, email }),
59
+ });
60
+ const body = await res.json().catch(() => ({}));
61
+ if (!res.ok) {
62
+ console.error(`Activation failed (HTTP ${res.status}): ${JSON.stringify(body)}`);
63
+ process.exit(1);
64
+ }
65
+ console.log(`activation_id: ${body.activation_id ?? '(see response)'}`);
66
+ if (body.domain_challenge)
67
+ console.log(`domain_challenge:\n${JSON.stringify(body.domain_challenge, null, 2)}`);
68
+ console.log('\nPlace the domain challenge as instructed, check your email for the code, then run:');
69
+ console.log(` ignite confirm --activation ${body.activation_id ?? '<activation_id>'} --code <email_code>`);
70
+ }
71
+ // Leg two: exchange the code for the site id + token, and write them to .env.local.
72
+ async function confirm() {
73
+ const args = parseFlags(process.argv.slice(3));
74
+ const activationId = args.activation;
75
+ const code = args.code;
76
+ if (!activationId || !code) {
77
+ console.error('Usage: ignite confirm --activation <activation_id> --code <email_code>');
78
+ process.exit(1);
79
+ }
80
+ const res = await fetch(`${HUB_URL}/v1/activate/confirm`, {
81
+ method: 'POST',
82
+ headers: { 'Content-Type': 'application/json' },
83
+ body: JSON.stringify({ activation_id: activationId, email_code: code }),
84
+ });
85
+ const body = await res.json().catch(() => ({}));
86
+ if (!res.ok || !body.token || !body.site_id) {
87
+ console.error(`Confirmation failed (HTTP ${res.status}): ${JSON.stringify(body)}`);
88
+ process.exit(1);
89
+ }
90
+ writeEnvLocal({ IGNITE_SITE_ID: body.site_id, IGNITE_SITE_TOKEN: body.token });
91
+ console.log('Wrote IGNITE_SITE_ID and IGNITE_SITE_TOKEN to .env.local. Set the same two variables in your host, then deploy.');
92
+ }
93
+ function writeEnvLocal(vars) {
94
+ const path = join(process.cwd(), '.env.local');
95
+ let contents = existsSync(path) ? readFileSync(path, 'utf8') : '';
96
+ for (const [key, value] of Object.entries(vars)) {
97
+ const line = `${key}=${value}`;
98
+ contents = new RegExp(`^${key}=.*$`, 'm').test(contents)
99
+ ? contents.replace(new RegExp(`^${key}=.*$`, 'm'), line)
100
+ : `${contents}${contents.endsWith('\n') || contents === '' ? '' : '\n'}${line}\n`;
101
+ }
102
+ writeFileSync(path, contents);
103
+ }
104
+ function parseFlags(argv) {
105
+ const flags = {};
106
+ for (let i = 0; i < argv.length; i += 2) {
107
+ const key = argv[i]?.replace(/^--/, '');
108
+ const value = argv[i + 1];
109
+ if (key && value)
110
+ flags[key] = value;
111
+ }
112
+ return flags;
113
+ }
114
+ const command = process.argv[2];
115
+ switch (command) {
116
+ case 'init':
117
+ init();
118
+ break;
119
+ case 'doctor':
120
+ void doctor();
121
+ break;
122
+ case 'connect':
123
+ void connect();
124
+ break;
125
+ case 'confirm':
126
+ void confirm();
127
+ break;
128
+ default:
129
+ console.log('Usage: ignite <init|connect|confirm|doctor>');
130
+ console.log(' init write app/robots.ts, app/sitemap.ts, proxy.ts');
131
+ console.log(' connect start the site activation handshake (--site-url --email)');
132
+ console.log(' confirm finish activation and write .env.local (--activation --code)');
133
+ console.log(' doctor check config and reach the Ignite API');
134
+ }
135
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import { type IgniteConfig } from './env.js';
2
+ import type { AgentDirectives } from './types.js';
3
+ export declare class IgniteApiError extends Error {
4
+ readonly status: number;
5
+ readonly path: string;
6
+ constructor(status: number, path: string);
7
+ }
8
+ export type ClientOptions = {
9
+ revalidate?: number;
10
+ };
11
+ export declare class IgniteClient {
12
+ private readonly config;
13
+ private readonly options;
14
+ constructor(config: IgniteConfig, options?: ClientOptions);
15
+ static fromEnv(options?: ClientOptions): IgniteClient;
16
+ static fromEnvOrNull(options?: ClientOptions): IgniteClient | null;
17
+ directives(): Promise<AgentDirectives>;
18
+ recordCrawlEvents(events: CrawlEvent[]): Promise<void>;
19
+ private site;
20
+ private get;
21
+ private post;
22
+ }
23
+ export type CrawlEvent = {
24
+ bot_name: string;
25
+ path: string;
26
+ observed_at: string;
27
+ ip?: string;
28
+ user_agent?: string;
29
+ method?: string;
30
+ status?: number;
31
+ };
package/dist/client.js ADDED
@@ -0,0 +1,61 @@
1
+ import { readConfig, requireConfig } from './env.js';
2
+ export class IgniteApiError extends Error {
3
+ status;
4
+ path;
5
+ constructor(status, path) {
6
+ super(`[@ignite-agent/agent] ${path} returned HTTP ${status}`);
7
+ this.status = status;
8
+ this.path = path;
9
+ this.name = 'IgniteApiError';
10
+ }
11
+ }
12
+ export class IgniteClient {
13
+ config;
14
+ options;
15
+ constructor(config, options = {}) {
16
+ this.config = config;
17
+ this.options = options;
18
+ }
19
+ static fromEnv(options) {
20
+ return new IgniteClient(requireConfig(), options ?? {});
21
+ }
22
+ // Returns null when the SDK is unconfigured, so operator entry points can degrade to app defaults.
23
+ static fromEnvOrNull(options) {
24
+ const config = readConfig();
25
+ return config ? new IgniteClient(config, options ?? {}) : null;
26
+ }
27
+ async directives() {
28
+ return this.get('/agent-directives');
29
+ }
30
+ async recordCrawlEvents(events) {
31
+ if (events.length === 0)
32
+ return;
33
+ await this.post('/crawl-events', { events });
34
+ }
35
+ site(path) {
36
+ return `${this.config.hubUrl}/v1/sites/${this.config.siteId}${path}`;
37
+ }
38
+ async get(path) {
39
+ const revalidate = this.options.revalidate ?? 300;
40
+ const headers = { 'X-Ignite-Site-Token': this.config.token };
41
+ const init = revalidate > 0 ? { headers, next: { revalidate } } : { headers, cache: 'no-store' };
42
+ const res = await fetch(this.site(path), init);
43
+ if (!res.ok)
44
+ throw new IgniteApiError(res.status, path);
45
+ return (await res.json());
46
+ }
47
+ async post(path, body) {
48
+ const res = await fetch(this.site(path), {
49
+ method: 'POST',
50
+ headers: {
51
+ 'X-Ignite-Site-Token': this.config.token,
52
+ 'Content-Type': 'application/json',
53
+ },
54
+ body: JSON.stringify(body),
55
+ cache: 'no-store',
56
+ });
57
+ if (!res.ok)
58
+ throw new IgniteApiError(res.status, path);
59
+ }
60
+ }
61
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/env.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ export type IgniteConfig = {
2
+ hubUrl: string;
3
+ siteId: string;
4
+ token: string;
5
+ };
6
+ export declare class IgniteConfigError extends Error {
7
+ constructor(message: string);
8
+ }
9
+ export declare function readConfig(): IgniteConfig | null;
10
+ export declare function requireConfig(): IgniteConfig;
package/dist/env.js ADDED
@@ -0,0 +1,28 @@
1
+ const DEFAULT_HUB_URL = 'https://app.igniteagent.ai';
2
+ export class IgniteConfigError extends Error {
3
+ constructor(message) {
4
+ super(`[@ignite-agent/agent] ${message}`);
5
+ this.name = 'IgniteConfigError';
6
+ }
7
+ }
8
+ // The token can mutate the site, so it must stay server-side. Reading process.env here means a `'use client'`
9
+ // module that imports it fails to build, which is intended.
10
+ export function readConfig() {
11
+ const token = process.env.IGNITE_SITE_TOKEN?.trim();
12
+ const siteId = process.env.IGNITE_SITE_ID?.trim();
13
+ if (!token || !siteId)
14
+ return null;
15
+ return {
16
+ hubUrl: (process.env.IGNITE_HUB_URL?.trim() || DEFAULT_HUB_URL).replace(/\/+$/, ''),
17
+ siteId,
18
+ token,
19
+ };
20
+ }
21
+ export function requireConfig() {
22
+ const config = readConfig();
23
+ if (!config) {
24
+ throw new IgniteConfigError('IGNITE_SITE_TOKEN and IGNITE_SITE_ID are required. Run `npx ignite connect` to provision them.');
25
+ }
26
+ return config;
27
+ }
28
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.js","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAMA,MAAM,eAAe,GAAG,4BAA4B,CAAC;AAErD,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAC1C,YAAY,OAAe;QACzB,KAAK,CAAC,yBAAyB,OAAO,EAAE,CAAC,CAAC;QAC1C,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AAED,8GAA8G;AAC9G,4DAA4D;AAC5D,MAAM,UAAU,UAAU;IACxB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,IAAI,EAAE,CAAC;IACpD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,IAAI,EAAE,CAAC;IAClD,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEnC,OAAO;QACL,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,IAAI,EAAE,IAAI,eAAe,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;QACnF,MAAM;QACN,KAAK;KACN,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,iBAAiB,CACzB,gGAAgG,CACjG,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,7 @@
1
+ export { IgniteClient, IgniteApiError, type ClientOptions, type CrawlEvent } from './client.js';
2
+ export { readConfig, requireConfig, IgniteConfigError, type IgniteConfig } from './env.js';
3
+ export type { AgentDirectives, RobotsDirective, SitemapEntry, MetadataDirective, Measured, RateCI, } from './types.js';
4
+ export { default as robots } from './operator/robots.js';
5
+ export { default as sitemap } from './operator/sitemap.js';
6
+ export { IgniteJsonLd } from './operator/jsonld.js';
7
+ export { igniteMetadata } from './operator/metadata.js';
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export { IgniteClient, IgniteApiError } from './client.js';
2
+ export { readConfig, requireConfig, IgniteConfigError } from './env.js';
3
+ export { default as robots } from './operator/robots.js';
4
+ export { default as sitemap } from './operator/sitemap.js';
5
+ export { IgniteJsonLd } from './operator/jsonld.js';
6
+ export { igniteMetadata } from './operator/metadata.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAuC,MAAM,aAAa,CAAC;AAChG,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,iBAAiB,EAAqB,MAAM,UAAU,CAAC;AAU3F,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function IgniteJsonLd(): Promise<React.JSX.Element | null>;
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { IgniteClient } from '../client.js';
3
+ // Drop into `app/layout.tsx`: import { IgniteJsonLd } from '@ignite-agent/agent/jsonld' then render <IgniteJsonLd />
4
+ // An async server component that emits ld+json for the site. Renders nothing when unconfigured or unreachable.
5
+ export async function IgniteJsonLd() {
6
+ const client = IgniteClient.fromEnvOrNull();
7
+ if (!client)
8
+ return null;
9
+ let blocks = [];
10
+ try {
11
+ blocks = (await client.directives()).jsonLd ?? [];
12
+ }
13
+ catch {
14
+ return null;
15
+ }
16
+ if (blocks.length === 0)
17
+ return null;
18
+ return (_jsx(_Fragment, { children: blocks.map((block, i) => (_jsx("script", { type: "application/ld+json",
19
+ // JSON.stringify output is embedded as-is; escape the sequence that could close the script early.
20
+ dangerouslySetInnerHTML: { __html: JSON.stringify(block).replace(/</g, '\\u003c') } }, i))) }));
21
+ }
22
+ //# sourceMappingURL=jsonld.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonld.js","sourceRoot":"","sources":["../../src/operator/jsonld.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,uHAAuH;AACvH,+GAA+G;AAC/G,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,MAAM,MAAM,GAAG,YAAY,CAAC,aAAa,EAAE,CAAC;IAC5C,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEzB,IAAI,MAAM,GAA8B,EAAE,CAAC;IAC3C,IAAI,CAAC;QACH,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAErC,OAAO,CACL,4BACG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,CACxB,iBAEE,IAAI,EAAC,qBAAqB;YAC1B,kGAAkG;YAClG,uBAAuB,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,IAH9E,CAAC,CAIN,CACH,CAAC,GACD,CACJ,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { Metadata } from 'next';
2
+ export declare function igniteMetadata(path: string): Promise<Metadata>;
@@ -0,0 +1,34 @@
1
+ import { IgniteClient } from '../client.js';
2
+ // Use inside `generateMetadata`: export async function generateMetadata() { return igniteMetadata('/pricing') }
3
+ // Merge with your own values by spreading: { ...(await igniteMetadata(path)), title: 'override' }
4
+ // Returns {} when unconfigured, unreachable, or when there is no directive for the path.
5
+ export async function igniteMetadata(path) {
6
+ const client = IgniteClient.fromEnvOrNull();
7
+ if (!client)
8
+ return {};
9
+ let directive;
10
+ try {
11
+ directive = (await client.directives()).metadata?.[path];
12
+ }
13
+ catch {
14
+ return {};
15
+ }
16
+ if (!directive)
17
+ return {};
18
+ const metadata = {};
19
+ if (directive.title)
20
+ metadata.title = directive.title;
21
+ if (directive.description)
22
+ metadata.description = directive.description;
23
+ if (directive.canonical)
24
+ metadata.alternates = { canonical: directive.canonical };
25
+ if (directive.openGraph) {
26
+ metadata.openGraph = {
27
+ ...(directive.openGraph.title ? { title: directive.openGraph.title } : {}),
28
+ ...(directive.openGraph.description ? { description: directive.openGraph.description } : {}),
29
+ ...(directive.openGraph.images ? { images: directive.openGraph.images } : {}),
30
+ };
31
+ }
32
+ return metadata;
33
+ }
34
+ //# sourceMappingURL=metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.js","sourceRoot":"","sources":["../../src/operator/metadata.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,iHAAiH;AACjH,kGAAkG;AAClG,yFAAyF;AACzF,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAY;IAC/C,MAAM,MAAM,GAAG,YAAY,CAAC,aAAa,EAAE,CAAC;IAC5C,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IAEvB,IAAI,SAAS,CAAC;IACd,IAAI,CAAC;QACH,SAAS,GAAG,CAAC,MAAM,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,CAAC;IAC3D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,CAAC,SAAS;QAAE,OAAO,EAAE,CAAC;IAE1B,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,IAAI,SAAS,CAAC,KAAK;QAAE,QAAQ,CAAC,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC;IACtD,IAAI,SAAS,CAAC,WAAW;QAAE,QAAQ,CAAC,WAAW,GAAG,SAAS,CAAC,WAAW,CAAC;IACxE,IAAI,SAAS,CAAC,SAAS;QAAE,QAAQ,CAAC,UAAU,GAAG,EAAE,SAAS,EAAE,SAAS,CAAC,SAAS,EAAE,CAAC;IAClF,IAAI,SAAS,CAAC,SAAS,EAAE,CAAC;QACxB,QAAQ,CAAC,SAAS,GAAG;YACnB,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1E,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,SAAS,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5F,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9E,CAAC;IACJ,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { type NextRequest, NextResponse } from 'next/server';
2
+ export declare function proxy(request: NextRequest): Promise<NextResponse>;
3
+ export declare const config: {
4
+ matcher: string[];
5
+ };
@@ -0,0 +1,66 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { IgniteClient } from '../client.js';
3
+ // Well-known AI and search crawler user-agent tokens. Matching is case-insensitive substring; the first match
4
+ // is reported as the bot name. Identification only — an IP-based verification of the claimed bot happens on the
5
+ // Ignite side, since a user-agent alone can be spoofed.
6
+ const CRAWLER_TOKENS = [
7
+ 'GPTBot',
8
+ 'OAI-SearchBot',
9
+ 'ChatGPT-User',
10
+ 'ClaudeBot',
11
+ 'Claude-Web',
12
+ 'Anthropic-AI',
13
+ 'PerplexityBot',
14
+ 'Perplexity-User',
15
+ 'Google-Extended',
16
+ 'Googlebot',
17
+ 'Bingbot',
18
+ 'Applebot',
19
+ 'Amazonbot',
20
+ 'Meta-ExternalAgent',
21
+ 'Bytespider',
22
+ 'CCBot',
23
+ ];
24
+ function matchedCrawler(userAgent) {
25
+ const ua = userAgent.toLowerCase();
26
+ return CRAWLER_TOKENS.find((token) => ua.includes(token.toLowerCase())) ?? null;
27
+ }
28
+ function clientIp(request) {
29
+ const forwarded = request.headers.get('x-forwarded-for');
30
+ if (forwarded)
31
+ return forwarded.split(',')[0]?.trim() || undefined;
32
+ return request.headers.get('x-real-ip')?.trim() || undefined;
33
+ }
34
+ // Drop into `proxy.ts` at the project root: export { proxy, config } from '@ignite-agent/agent/proxy'
35
+ // Passes every request through untouched, and records a crawl event when the visitor is a known crawler.
36
+ // Recording is best-effort: a failure never affects the response.
37
+ export async function proxy(request) {
38
+ const botName = matchedCrawler(request.headers.get('user-agent') ?? '');
39
+ if (botName) {
40
+ const client = IgniteClient.fromEnvOrNull();
41
+ if (client) {
42
+ const ip = clientIp(request);
43
+ const userAgent = request.headers.get('user-agent') ?? undefined;
44
+ const event = {
45
+ bot_name: botName,
46
+ path: request.nextUrl.pathname,
47
+ observed_at: new Date().toISOString(),
48
+ method: request.method,
49
+ ...(ip ? { ip } : {}),
50
+ ...(userAgent ? { user_agent: userAgent } : {}),
51
+ };
52
+ try {
53
+ await client.recordCrawlEvents([event]);
54
+ }
55
+ catch {
56
+ // best-effort; ignore
57
+ }
58
+ }
59
+ }
60
+ return NextResponse.next();
61
+ }
62
+ // Limit the proxy to page requests; skip static assets.
63
+ export const config = {
64
+ matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
65
+ };
66
+ //# sourceMappingURL=proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../../src/operator/proxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,YAAY,EAAE,MAAM,aAAa,CAAC;AAE7D,OAAO,EAAE,YAAY,EAAmB,MAAM,cAAc,CAAC;AAE7D,8GAA8G;AAC9G,gHAAgH;AAChH,wDAAwD;AACxD,MAAM,cAAc,GAAG;IACrB,QAAQ;IACR,eAAe;IACf,cAAc;IACd,WAAW;IACX,YAAY;IACZ,cAAc;IACd,eAAe;IACf,iBAAiB;IACjB,iBAAiB;IACjB,WAAW;IACX,SAAS;IACT,UAAU;IACV,WAAW;IACX,oBAAoB;IACpB,YAAY;IACZ,OAAO;CACR,CAAC;AAEF,SAAS,cAAc,CAAC,SAAiB;IACvC,MAAM,EAAE,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC;IACnC,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC;AAClF,CAAC;AAED,SAAS,QAAQ,CAAC,OAAoB;IACpC,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACzD,IAAI,SAAS;QAAE,OAAO,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,SAAS,CAAC;IACnE,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,IAAI,SAAS,CAAC;AAC/D,CAAC;AAED,uGAAuG;AACvG,yGAAyG;AACzG,kEAAkE;AAClE,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,OAAoB;IAC9C,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC;IAExE,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,MAAM,GAAG,YAAY,CAAC,aAAa,EAAE,CAAC;QAC5C,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,EAAE,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;YAC7B,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,SAAS,CAAC;YACjE,MAAM,KAAK,GAAe;gBACxB,QAAQ,EAAE,OAAO;gBACjB,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,QAAQ;gBAC9B,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACrC,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACrB,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAChD,CAAC;YACF,IAAI,CAAC;gBACH,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;YAC1C,CAAC;YAAC,MAAM,CAAC;gBACP,sBAAsB;YACxB,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,YAAY,CAAC,IAAI,EAAE,CAAC;AAC7B,CAAC;AAED,wDAAwD;AACxD,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,OAAO,EAAE,CAAC,+CAA+C,CAAC;CAC3D,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { MetadataRoute } from 'next';
2
+ export default function robots(): Promise<MetadataRoute.Robots>;
@@ -0,0 +1,24 @@
1
+ import { IgniteClient } from '../client.js';
2
+ // Drop into `app/robots.ts`: export { default } from '@ignite-agent/agent/robots'
3
+ // Resolves rules from your Ignite workspace at request time. Falls back to allow-all when unconfigured or
4
+ // unreachable, so it never blocks crawlers by accident.
5
+ export default async function robots() {
6
+ const fallback = { rules: { userAgent: '*', allow: '/' } };
7
+ const client = IgniteClient.fromEnvOrNull();
8
+ if (!client)
9
+ return fallback;
10
+ try {
11
+ const { robots } = await client.directives();
12
+ if (!robots)
13
+ return fallback;
14
+ return {
15
+ rules: robots.rules,
16
+ ...(robots.sitemap ? { sitemap: robots.sitemap } : {}),
17
+ ...(robots.host ? { host: robots.host } : {}),
18
+ };
19
+ }
20
+ catch {
21
+ return fallback;
22
+ }
23
+ }
24
+ //# sourceMappingURL=robots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"robots.js","sourceRoot":"","sources":["../../src/operator/robots.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,mFAAmF;AACnF,0GAA0G;AAC1G,wDAAwD;AACxD,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,MAAM;IAClC,MAAM,QAAQ,GAAyB,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC;IAEjF,MAAM,MAAM,GAAG,YAAY,CAAC,aAAa,EAAE,CAAC;IAC5C,IAAI,CAAC,MAAM;QAAE,OAAO,QAAQ,CAAC;IAE7B,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,UAAU,EAAE,CAAC;QAC7C,IAAI,CAAC,MAAM;YAAE,OAAO,QAAQ,CAAC;QAC7B,OAAO;YACL,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACtD,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9C,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,QAAQ,CAAC;IAClB,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { MetadataRoute } from 'next';
2
+ export default function sitemap(): Promise<MetadataRoute.Sitemap>;
@@ -0,0 +1,23 @@
1
+ import { IgniteClient } from '../client.js';
2
+ // Drop into `app/sitemap.ts`: export { default } from '@ignite-agent/agent/sitemap'
3
+ // Returns an empty sitemap when unconfigured or unreachable so it never overrides a real one with bad data.
4
+ export default async function sitemap() {
5
+ const client = IgniteClient.fromEnvOrNull();
6
+ if (!client)
7
+ return [];
8
+ try {
9
+ const { sitemap } = await client.directives();
10
+ if (!sitemap)
11
+ return [];
12
+ return sitemap.map((entry) => ({
13
+ url: entry.url,
14
+ ...(entry.lastModified ? { lastModified: entry.lastModified } : {}),
15
+ ...(entry.changeFrequency ? { changeFrequency: entry.changeFrequency } : {}),
16
+ ...(entry.priority !== undefined ? { priority: entry.priority } : {}),
17
+ }));
18
+ }
19
+ catch {
20
+ return [];
21
+ }
22
+ }
23
+ //# sourceMappingURL=sitemap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sitemap.js","sourceRoot":"","sources":["../../src/operator/sitemap.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,qFAAqF;AACrF,4GAA4G;AAC5G,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,OAAO;IACnC,MAAM,MAAM,GAAG,YAAY,CAAC,aAAa,EAAE,CAAC;IAC5C,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IAEvB,IAAI,CAAC;QACH,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,UAAU,EAAE,CAAC;QAC9C,IAAI,CAAC,OAAO;YAAE,OAAO,EAAE,CAAC;QACxB,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAC7B,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACnE,GAAG,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,KAAK,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5E,GAAG,CAAC,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACtE,CAAC,CAAC,CAAC;IACN,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
@@ -0,0 +1,38 @@
1
+ export type Measured<T> = T | null;
2
+ export type RateCI = {
3
+ rate: number;
4
+ ciLow: number;
5
+ ciHigh: number;
6
+ } | null;
7
+ export type AgentDirectives = {
8
+ robots?: RobotsDirective;
9
+ sitemap?: SitemapEntry[];
10
+ jsonLd?: Record<string, unknown>[];
11
+ metadata?: Record<string, MetadataDirective>;
12
+ generatedAt?: string;
13
+ };
14
+ export type RobotsDirective = {
15
+ rules: Array<{
16
+ userAgent: string | string[];
17
+ allow?: string | string[];
18
+ disallow?: string | string[];
19
+ }>;
20
+ sitemap?: string | string[];
21
+ host?: string;
22
+ };
23
+ export type SitemapEntry = {
24
+ url: string;
25
+ lastModified?: string;
26
+ changeFrequency?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
27
+ priority?: number;
28
+ };
29
+ export type MetadataDirective = {
30
+ title?: string;
31
+ description?: string;
32
+ canonical?: string;
33
+ openGraph?: {
34
+ title?: string;
35
+ description?: string;
36
+ images?: string[];
37
+ };
38
+ };
package/dist/types.js ADDED
@@ -0,0 +1,4 @@
1
+ // A withheld value is `null`, never `0`. Consumers may format a value but must not compute one or turn `null`
2
+ // into a number.
3
+ export {};
4
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,8GAA8G;AAC9G,iBAAiB"}
@@ -0,0 +1,51 @@
1
+ # Set up Ignite in this Next.js app (Claude Code prompt)
2
+
3
+ Paste the block below to Claude Code (or any coding agent) from the root of your Next.js App Router project. It
4
+ wires in `@ignite-agent/agent` and verifies the build. You need your Ignite site id and site token first — get them by
5
+ running `npx ignite connect`, or from your Ignite workspace.
6
+
7
+ ---
8
+
9
+ You are setting up the `@ignite-agent/agent` package in this Next.js App Router project. Do the following, then stop
10
+ and report what changed.
11
+
12
+ 1. Confirm this is a Next.js App Router project (an `app/` directory, `next` in `package.json`). If it is a Pages
13
+ Router project, stop and tell me — this setup targets the App Router.
14
+
15
+ 2. Install the package: `npm install @ignite-agent/agent` (use the project's package manager if it isn't npm).
16
+
17
+ 3. Add the site credentials to `.env.local` (create it if missing), using the values I give you:
18
+ ```
19
+ IGNITE_SITE_ID=...
20
+ IGNITE_SITE_TOKEN=...
21
+ ```
22
+ Never hardcode these in source. If the project deploys on Vercel or similar, remind me to set the same two
23
+ environment variables there.
24
+
25
+ 4. Create `app/robots.ts` with exactly:
26
+ ```ts
27
+ export { default } from '@ignite-agent/agent/robots'
28
+ ```
29
+ If `app/robots.ts` already exists, show me its contents and ask before replacing.
30
+
31
+ 5. Create `app/sitemap.ts` with exactly:
32
+ ```ts
33
+ export { default } from '@ignite-agent/agent/sitemap'
34
+ ```
35
+ Same rule if it already exists.
36
+
37
+ 6. In `app/layout.tsx`, import and render the JSON-LD component inside `<head>` (or at the top of `<body>` if the
38
+ layout has no explicit `<head>`):
39
+ ```tsx
40
+ import { IgniteJsonLd } from '@ignite-agent/agent/jsonld'
41
+ // ...
42
+ <IgniteJsonLd />
43
+ ```
44
+
45
+ 7. (Optional, recommended) Create `proxy.ts` at the project root with:
46
+ ```ts
47
+ export { proxy, config } from '@ignite-agent/agent/proxy'
48
+ ```
49
+ If a `proxy.ts` or `middleware.ts` already exists, do NOT overwrite it — show it to me and we will merge.
50
+
51
+ 8. Run the build (`npm run build`) and report any errors. Do not change unrelated files.
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@ignite-agent/agent",
3
+ "version": "0.1.0",
4
+ "description": "Ignite for Next.js: a drop-in App Router SDK for robots, sitemap, JSON-LD, metadata, crawl logging, and the approve-and-apply change loop, driven by your Ignite workspace.",
5
+ "type": "module",
6
+ "license": "UNLICENSED",
7
+ "private": false,
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "engines": {
12
+ "node": ">=18.18.0"
13
+ },
14
+ "bin": {
15
+ "ignite": "./dist/cli/index.js"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "onboarding"
20
+ ],
21
+ "exports": {
22
+ ".": "./dist/index.js",
23
+ "./robots": "./dist/operator/robots.js",
24
+ "./sitemap": "./dist/operator/sitemap.js",
25
+ "./jsonld": "./dist/operator/jsonld.js",
26
+ "./metadata": "./dist/operator/metadata.js",
27
+ "./proxy": "./dist/operator/proxy.js",
28
+ "./client": "./dist/client.js"
29
+ },
30
+ "scripts": {
31
+ "build": "tsc -p tsconfig.json",
32
+ "prepublishOnly": "npm run build",
33
+ "typecheck": "tsc -p tsconfig.json --noEmit",
34
+ "test": "node --test dist/**/*.test.js"
35
+ },
36
+ "peerDependencies": {
37
+ "next": ">=15.0.0 <17.0.0",
38
+ "react": ">=18.2.0"
39
+ },
40
+ "peerDependenciesMeta": {
41
+ "react": { "optional": true }
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^22.0.0",
45
+ "@types/react": "^19.0.0",
46
+ "next": "^16.3.0",
47
+ "react": "^19.0.0",
48
+ "typescript": "^5.6.0"
49
+ },
50
+ "keywords": ["ignite", "nextjs", "app-router", "robots", "sitemap", "json-ld", "metadata", "ai-search", "llm"]
51
+ }