@relvehq/agent-ready 0.0.0-stage → 0.3.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.
Files changed (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +364 -3
  3. package/SETUP.md +354 -0
  4. package/dist/bin/agent-ready.d.ts +23 -0
  5. package/dist/bin/agent-ready.js +444 -0
  6. package/dist/cli/init.d.ts +70 -0
  7. package/dist/cli/init.js +366 -0
  8. package/dist/core/accept.d.ts +8 -0
  9. package/dist/core/accept.js +76 -0
  10. package/dist/core/config.d.ts +145 -0
  11. package/dist/core/config.js +103 -0
  12. package/dist/core/convert.d.ts +22 -0
  13. package/dist/core/convert.js +334 -0
  14. package/dist/core/detect.d.ts +30 -0
  15. package/dist/core/detect.js +82 -0
  16. package/dist/core/easy.d.ts +19 -0
  17. package/dist/core/easy.js +117 -0
  18. package/dist/core/enrich.d.ts +30 -0
  19. package/dist/core/enrich.js +155 -0
  20. package/dist/core/llms.d.ts +11 -0
  21. package/dist/core/llms.js +71 -0
  22. package/dist/core/report.d.ts +15 -0
  23. package/dist/core/report.js +46 -0
  24. package/dist/core/resolve.d.ts +43 -0
  25. package/dist/core/resolve.js +204 -0
  26. package/dist/core/respond.d.ts +18 -0
  27. package/dist/core/respond.js +59 -0
  28. package/dist/core/sitemap.d.ts +31 -0
  29. package/dist/core/sitemap.js +140 -0
  30. package/dist/index.d.ts +14 -0
  31. package/dist/index.js +76 -0
  32. package/dist/next/dashboard.d.ts +14 -0
  33. package/dist/next/dashboard.js +56 -0
  34. package/dist/next/index.d.ts +42 -0
  35. package/dist/next/index.js +100 -0
  36. package/dist/next/route.d.ts +26 -0
  37. package/dist/next/route.js +167 -0
  38. package/dist/node/index.d.ts +8 -0
  39. package/dist/node/index.js +191 -0
  40. package/dist/proof/audit.d.ts +47 -0
  41. package/dist/proof/audit.js +253 -0
  42. package/dist/proof/bots.d.ts +24 -0
  43. package/dist/proof/bots.js +60 -0
  44. package/dist/proof/dashboard.d.ts +8 -0
  45. package/dist/proof/dashboard.js +77 -0
  46. package/dist/proof/hits.d.ts +18 -0
  47. package/dist/proof/hits.js +51 -0
  48. package/dist/proof/jsonl.d.ts +5 -0
  49. package/dist/proof/jsonl.js +80 -0
  50. package/dist/proof/stats.d.ts +39 -0
  51. package/dist/proof/stats.js +127 -0
  52. package/dist/refine/check.d.ts +17 -0
  53. package/dist/refine/check.js +134 -0
  54. package/dist/refine/extract.d.ts +7 -0
  55. package/dist/refine/extract.js +120 -0
  56. package/dist/refine/file-store.d.ts +6 -0
  57. package/dist/refine/file-store.js +87 -0
  58. package/dist/refine/model.d.ts +30 -0
  59. package/dist/refine/model.js +105 -0
  60. package/dist/refine/prompts.d.ts +49 -0
  61. package/dist/refine/prompts.js +178 -0
  62. package/dist/refine/refine.d.ts +38 -0
  63. package/dist/refine/refine.js +75 -0
  64. package/dist/refine/remote.d.ts +57 -0
  65. package/dist/refine/remote.js +238 -0
  66. package/dist/refine/run.d.ts +52 -0
  67. package/dist/refine/run.js +66 -0
  68. package/dist/refine/types.d.ts +17 -0
  69. package/dist/refine/types.js +8 -0
  70. package/dist/wp/relve-agent-ready/includes/class-relve-ar-admin.php +550 -0
  71. package/dist/wp/relve-agent-ready/includes/class-relve-ar-converter.php +762 -0
  72. package/dist/wp/relve-agent-ready/includes/class-relve-ar-llms.php +192 -0
  73. package/dist/wp/relve-agent-ready/includes/class-relve-ar-markdown-builder.php +455 -0
  74. package/dist/wp/relve-agent-ready/includes/class-relve-ar-negotiation.php +334 -0
  75. package/dist/wp/relve-agent-ready/includes/class-relve-ar-refiner.php +894 -0
  76. package/dist/wp/relve-agent-ready/includes/class-relve-ar-response.php +226 -0
  77. package/dist/wp/relve-agent-ready/includes/class-relve-ar-router.php +421 -0
  78. package/dist/wp/relve-agent-ready/includes/class-relve-ar-settings.php +201 -0
  79. package/dist/wp/relve-agent-ready/includes/class-relve-ar-store.php +583 -0
  80. package/dist/wp/relve-agent-ready/includes/index.php +2 -0
  81. package/dist/wp/relve-agent-ready/index.php +2 -0
  82. package/dist/wp/relve-agent-ready/readme.txt +108 -0
  83. package/dist/wp/relve-agent-ready/relve-agent-ready.php +81 -0
  84. package/dist/wp/relve-agent-ready/uninstall.php +35 -0
  85. package/package.json +64 -6
@@ -0,0 +1,444 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ // `agent-ready` command line.
4
+ //
5
+ // agent-ready init [--stack next|node|wordpress] [--dry-run] [--force] [--cwd <dir>] [--pkg <name>]
6
+ // agent-ready check <url> [--json]
7
+ // agent-ready stats --log <hits.jsonl> [--days <n>] [--json]
8
+ // agent-ready refine --site <origin> (--paths a,b | --sitemap) [--model <id>]
9
+ // [--site-name <name>] [--out <dir>] [--limit <n>] [--force]
10
+ // [--concurrency <n>] [--dry-run] [--prompts <dir>[,<dir>]]
11
+ //
12
+ // The package ships no prompt. By default refine sends each page to the Relve
13
+ // API (refine/remote.ts) with the caller's OPENAI_API_KEY; Relve's prompt runs
14
+ // there. With --prompts or AGENT_READY_PROMPTS it runs those prompt files
15
+ // locally instead (the original mode).
16
+ //
17
+ // Thin: argument parsing and the real file system; the work is in cli/init.ts
18
+ // and refine/*. NODE ONLY.
19
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ var desc = Object.getOwnPropertyDescriptor(m, k);
22
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
23
+ desc = { enumerable: true, get: function() { return m[k]; } };
24
+ }
25
+ Object.defineProperty(o, k2, desc);
26
+ }) : (function(o, m, k, k2) {
27
+ if (k2 === undefined) k2 = k;
28
+ o[k2] = m[k];
29
+ }));
30
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
31
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
32
+ }) : function(o, v) {
33
+ o["default"] = v;
34
+ });
35
+ var __importStar = (this && this.__importStar) || (function () {
36
+ var ownKeys = function(o) {
37
+ ownKeys = Object.getOwnPropertyNames || function (o) {
38
+ var ar = [];
39
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
40
+ return ar;
41
+ };
42
+ return ownKeys(o);
43
+ };
44
+ return function (mod) {
45
+ if (mod && mod.__esModule) return mod;
46
+ var result = {};
47
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
48
+ __setModuleDefault(result, mod);
49
+ return result;
50
+ };
51
+ })();
52
+ Object.defineProperty(exports, "__esModule", { value: true });
53
+ exports.nodeFs = nodeFs;
54
+ exports.bundledPluginDir = bundledPluginDir;
55
+ exports.listPluginFiles = listPluginFiles;
56
+ exports.run = run;
57
+ exports.loadEnvFiles = loadEnvFiles;
58
+ const node_fs_1 = require("node:fs");
59
+ const node_path_1 = require("node:path");
60
+ const init_1 = require("../cli/init");
61
+ const USAGE = [
62
+ "Usage:",
63
+ " agent-ready init [--stack next|node|wordpress] [--dry-run] [--force] [--cwd <dir>] [--pkg <name>]",
64
+ " detects Next.js, Node servers (Express, Fastify, Nest, Nitro...) and WordPress;",
65
+ " --stack skips detection, --force replaces an installed WordPress plugin",
66
+ ` guide: ${init_1.ONBOARDING_URL}`,
67
+ " agent-ready refine --site <origin> (--paths /a,/b | --sitemap) [--model gpt-4o|gpt-4o-mini|gpt-4.1|gpt-4.1-mini]",
68
+ " [--site-name <name>] [--out <dir>] [--limit <n>] [--force] [--concurrency <n>] [--dry-run]",
69
+ " better Markdown files in .agent-ready/md, written with Relve's prompt and your own OpenAI key",
70
+ "",
71
+ " agent-ready check <url> [--json] how agent-ready is a site? (read-only)",
72
+ " agent-ready stats --log <hits.jsonl> [--days <n>] [--json] agent visits summary",
73
+ "",
74
+ "refine needs OPENAI_API_KEY (read from .env.local / .env or the shell) unless --dry-run. Each page goes to",
75
+ "relvehq.com with the key; the key is used for that call only and never stored. Unchanged pages are free.",
76
+ "AGENT_READY_REFINE_ENDPOINT overrides the API URL.",
77
+ "",
78
+ "Advanced: --prompts <dir>[,<dir>] (or AGENT_READY_PROMPTS) runs your own prompt files locally instead, with",
79
+ "OPENAI_API_KEY or ANTHROPIC_API_KEY; AGENT_READY_MODEL picks the model (default gpt-4o / claude-opus-5-5).",
80
+ ].join("\n");
81
+ function nodeFs(root) {
82
+ const abs = (p) => (0, node_path_1.resolve)(root, p);
83
+ return {
84
+ exists: (p) => (0, node_fs_1.existsSync)(abs(p)),
85
+ read: (p) => (0, node_fs_1.readFileSync)(abs(p), "utf8"),
86
+ write: (p, content) => {
87
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(abs(p)), { recursive: true });
88
+ (0, node_fs_1.writeFileSync)(abs(p), content, "utf8");
89
+ },
90
+ append: (p, content) => (0, node_fs_1.appendFileSync)(abs(p), content, "utf8"),
91
+ copy: (from, to) => {
92
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(abs(to)), { recursive: true });
93
+ (0, node_fs_1.copyFileSync)(from, abs(to));
94
+ },
95
+ };
96
+ }
97
+ /**
98
+ * The WordPress plugin folder: the copy built into the package
99
+ * (dist/wp/relve-agent-ready, next to dist/bin and dist/cli), else the source
100
+ * folder when running from the repo with tsx. null when neither is there.
101
+ */
102
+ function bundledPluginDir(here = __dirname) {
103
+ const candidates = [(0, node_path_1.resolve)(here, "..", "wp", "relve-agent-ready"), (0, node_path_1.resolve)(here, "..", "..", "agent-ready-wp", "relve-agent-ready")];
104
+ return candidates.find((dir) => (0, node_fs_1.existsSync)((0, node_path_1.join)(dir, "relve-agent-ready.php"))) ?? null;
105
+ }
106
+ /** Every file under the plugin folder, relative with forward slashes; no tests/, no dotfiles. */
107
+ function listPluginFiles(dir) {
108
+ const out = [];
109
+ const walk = (rel) => {
110
+ for (const e of (0, node_fs_1.readdirSync)((0, node_path_1.join)(dir, rel), { withFileTypes: true })) {
111
+ if (e.name.startsWith("."))
112
+ continue;
113
+ const p = rel ? `${rel}/${e.name}` : e.name;
114
+ if (e.isDirectory()) {
115
+ if (p !== "tests")
116
+ walk(p);
117
+ }
118
+ else if (e.isFile())
119
+ out.push(p);
120
+ }
121
+ };
122
+ walk("");
123
+ return out.sort();
124
+ }
125
+ function pluginBundle() {
126
+ const dir = bundledPluginDir();
127
+ return dir ? { dir, files: listPluginFiles(dir) } : null;
128
+ }
129
+ async function run(argv, deps = {}) {
130
+ const d = typeof deps === "function" ? { log: deps } : deps;
131
+ const log = d.log ?? console.log;
132
+ const [cmd, ...rest] = argv;
133
+ const flag = (name) => {
134
+ const i = rest.indexOf(name);
135
+ return i >= 0 ? rest[i + 1] : undefined;
136
+ };
137
+ if (cmd === "init") {
138
+ const dryRun = rest.includes("--dry-run");
139
+ const fs = nodeFs((0, node_path_1.resolve)(flag("--cwd") ?? process.cwd()));
140
+ let detected;
141
+ if (rest.includes("--stack")) {
142
+ const forced = flag("--stack");
143
+ if (!forced || !init_1.STACKS.includes(forced)) {
144
+ log(`agent-ready init: --stack must be one of ${init_1.STACKS.join(", ")}`);
145
+ return 1;
146
+ }
147
+ detected = { stack: forced, framework: forced === "node" ? (0, init_1.nodeFramework)(fs) ?? undefined : undefined, why: `--stack ${forced}` };
148
+ }
149
+ else {
150
+ detected = (0, init_1.detectStack)(fs);
151
+ }
152
+ if (detected.stack === "next") {
153
+ const plan = (0, init_1.planInit)(fs, { pkg: flag("--pkg") });
154
+ if (plan.ok && !dryRun)
155
+ (0, init_1.applyInit)(plan, fs);
156
+ log((0, init_1.formatPlan)(plan, dryRun));
157
+ return plan.ok ? 0 : 1;
158
+ }
159
+ if (detected.stack === "wordpress") {
160
+ const plan = (0, init_1.planWordPress)(fs, pluginBundle(), { force: rest.includes("--force") });
161
+ if (plan.ok && !dryRun)
162
+ (0, init_1.applyInit)(plan, fs);
163
+ log((0, init_1.formatWordPress)(plan, dryRun));
164
+ return plan.ok ? 0 : 1;
165
+ }
166
+ if (detected.stack === "node") {
167
+ log((0, init_1.formatNode)(detected.framework ?? null, flag("--pkg")));
168
+ return 0;
169
+ }
170
+ log((0, init_1.formatUnknown)());
171
+ return 1;
172
+ }
173
+ if (cmd === "refine")
174
+ return refineCommand(rest, flag, { ...d, log });
175
+ if (cmd === "check") {
176
+ // Read-only: a handful of GETs to the site, like an agent would make.
177
+ const target = rest.find((a) => !a.startsWith("--"));
178
+ if (!target) {
179
+ log(USAGE);
180
+ return 1;
181
+ }
182
+ const [{ auditSite, formatAudit }, { htmlToMarkdown }] = await Promise.all([Promise.resolve().then(() => __importStar(require("../proof/audit"))), Promise.resolve().then(() => __importStar(require("../core/convert")))]);
183
+ let report;
184
+ try {
185
+ report = await auditSite(target, { fetch: d.fetch, convert: (html, o) => htmlToMarkdown(html, { url: o.url }) });
186
+ }
187
+ catch (err) {
188
+ log(`agent-ready check: ${err.message}`);
189
+ return 1;
190
+ }
191
+ log(rest.includes("--json") ? JSON.stringify(report, null, 2) : formatAudit(report));
192
+ return report.ok ? 0 : 1;
193
+ }
194
+ if (cmd === "stats") {
195
+ const file = flag("--log");
196
+ if (!file) {
197
+ log(USAGE);
198
+ return 1;
199
+ }
200
+ const [{ jsonlHitStore }, { summarize, formatSummary }] = await Promise.all([Promise.resolve().then(() => __importStar(require("../proof/jsonl"))), Promise.resolve().then(() => __importStar(require("../proof/stats")))]);
201
+ const days = Number(flag("--days") ?? "0");
202
+ const since = days > 0 ? new Date(Date.now() - days * 86400000).toISOString() : undefined;
203
+ const summary = summarize(await jsonlHitStore((0, node_path_1.resolve)(file)).list({ since }));
204
+ log(rest.includes("--json") ? JSON.stringify(summary, null, 2) : formatSummary(summary));
205
+ return 0;
206
+ }
207
+ log(USAGE);
208
+ return cmd === undefined || cmd === "--help" || cmd === "-h" ? 0 : 1;
209
+ }
210
+ async function refineCommand(rest, flag, d) {
211
+ const { log } = d;
212
+ const env = d.env ?? process.env;
213
+ const f = d.fetch ?? fetch;
214
+ const site = flag("--site")?.replace(/\/+$/, "");
215
+ if (!site || !/^https?:\/\//.test(site)) {
216
+ log(USAGE);
217
+ return 1;
218
+ }
219
+ // Prompts come only from folders the user names; the package ships none.
220
+ // Several folders may be given ("a,b"). Checked before any page is fetched.
221
+ const promptSource = flag("--prompts") ?? env.AGENT_READY_PROMPTS;
222
+ const promptDirs = (promptSource ?? "").split(",").map((p) => p.trim()).filter(Boolean);
223
+ if (rest.includes("--prompts") && !promptDirs.length) {
224
+ log("agent-ready refine: --prompts needs a folder of prompt files (--prompts <dir>[,<dir>]).");
225
+ return 1;
226
+ }
227
+ // No prompt folder: Relve's prompt, run on relvehq.com with the caller's key.
228
+ if (!promptDirs.length)
229
+ return remoteRefineCommand(rest, flag, site, env, f, log);
230
+ if (!flag("--prompts"))
231
+ log(`agent-ready refine: prompts from AGENT_READY_PROMPTS (${promptDirs.join(", ")})`);
232
+ const siteName = flag("--site-name");
233
+ const dryRun = rest.includes("--dry-run");
234
+ // Lazy: `init` never loads linkedom.
235
+ const [{ parsePrompt, promptsByType, promptFor, fillPrompt, guessPageKind }, { runRefine }, { fileStore }, { sitemapEntries }, { extractInput }, { modelFromEnv }, { estimateTokens }] = await Promise.all([
236
+ Promise.resolve().then(() => __importStar(require("../refine/prompts"))),
237
+ Promise.resolve().then(() => __importStar(require("../refine/run"))),
238
+ Promise.resolve().then(() => __importStar(require("../refine/file-store"))),
239
+ Promise.resolve().then(() => __importStar(require("../core/sitemap"))),
240
+ Promise.resolve().then(() => __importStar(require("../refine/extract"))),
241
+ Promise.resolve().then(() => __importStar(require("../refine/model"))),
242
+ Promise.resolve().then(() => __importStar(require("../core/enrich"))),
243
+ ]);
244
+ let prompts;
245
+ try {
246
+ prompts = promptsByType(promptDirs.flatMap((dir) => (0, node_fs_1.readdirSync)(dir)
247
+ .filter((n) => n.endsWith(".md"))
248
+ .map((n) => parsePrompt((0, node_fs_1.readFileSync)((0, node_path_1.join)(dir, n), "utf8")))));
249
+ }
250
+ catch (err) {
251
+ log(`agent-ready refine: ${err.message}`);
252
+ return 1;
253
+ }
254
+ if (!Object.keys(prompts).length) {
255
+ log(`agent-ready refine: no prompt files in ${promptDirs.join(", ")}`);
256
+ return 1;
257
+ }
258
+ let paths = [];
259
+ if (flag("--paths"))
260
+ paths = flag("--paths").split(",").map((p) => p.trim()).filter(Boolean);
261
+ else if (rest.includes("--sitemap")) {
262
+ const entries = await sitemapEntries({ fetch: f, limit: 5000 })({ request: new Request(site + "/llms.txt") });
263
+ paths = entries.map((e) => e.url);
264
+ }
265
+ paths = paths.filter((p) => p.startsWith("/") && promptFor(p, prompts));
266
+ const limit = Number(flag("--limit") ?? "0");
267
+ if (limit > 0)
268
+ paths = paths.slice(0, limit);
269
+ if (!paths.length) {
270
+ log("agent-ready refine: no pages with a matching prompt (" + Object.keys(prompts).join(", ") + ").");
271
+ return 1;
272
+ }
273
+ const store = fileStore((0, node_path_1.resolve)(flag("--out") ?? ".agent-ready/md"));
274
+ if (dryRun) {
275
+ // Read-only: fetch and extract, show what would be sent. No model call, no writes.
276
+ log(`agent-ready refine --dry-run: ${paths.length} page(s), prompts ${Object.values(prompts).map((p) => p.id).join(", ")}`);
277
+ for (const path of paths) {
278
+ try {
279
+ const res = await f(site + path, { headers: { accept: "text/html" } });
280
+ if (!res.ok) {
281
+ log(` error ${path} HTTP ${res.status}`);
282
+ continue;
283
+ }
284
+ const input = await extractInput(await res.text(), { url: site + path });
285
+ const prompt = promptFor(path, prompts);
286
+ const filled = fillPrompt(prompt, input, { generatedAt: new Date().toISOString(), siteName });
287
+ const changed = (await store.lastHash(path)) !== input.sourceHash;
288
+ const kind = prompt.settings.pageType === "page" ? ` kind: ${guessPageKind(input)}` : "";
289
+ log(` ${(changed ? "would send" : "unchanged").padEnd(10)} ${path} ${prompt.id}${kind} ~${estimateTokens(filled.system + filled.user)} input tokens`);
290
+ }
291
+ catch (err) {
292
+ log(` error ${path} ${err.message}`);
293
+ }
294
+ }
295
+ return 0;
296
+ }
297
+ let model;
298
+ try {
299
+ model = modelFromEnv(env, f);
300
+ }
301
+ catch (err) {
302
+ log(err.message);
303
+ return 1;
304
+ }
305
+ if (!model) {
306
+ log("agent-ready refine: set OPENAI_API_KEY or ANTHROPIC_API_KEY (in .env or the shell), or use --dry-run.");
307
+ return 1;
308
+ }
309
+ const report = await runRefine({
310
+ site,
311
+ paths,
312
+ prompts,
313
+ model,
314
+ store,
315
+ force: rest.includes("--force"),
316
+ siteName,
317
+ concurrency: Number(flag("--concurrency") ?? "2") || 2,
318
+ fetch: f,
319
+ onResult: (it) => {
320
+ const why = it.status === "rejected" ? ` ${it.issues?.map((i) => `${i.kind}: ${i.detail}`).slice(0, 5).join("; ")}` : it.error ? ` ${it.error}` : "";
321
+ log(` ${it.status.padEnd(10)} ${it.path}${it.tokens ? ` ${it.tokens} tokens` : ""}${why}`);
322
+ },
323
+ });
324
+ const c = report.counts;
325
+ log(`done: ${c.ok} written, ${c.unchanged} unchanged, ${c.rejected} rejected, ${c.error} errors -> ${store.dir}`);
326
+ log(`model usage: ${report.usage.inputTokens} input + ${report.usage.outputTokens} output tokens`);
327
+ return c.error || c.rejected ? 2 : 0;
328
+ }
329
+ /**
330
+ * `refine` without --prompts: each page's HTML goes to the Relve API with the
331
+ * caller's OPENAI_API_KEY; Relve's prompt runs there. Checks happen before any
332
+ * fetch; the key is never printed.
333
+ */
334
+ async function remoteRefineCommand(rest, flag, site, env, f, log) {
335
+ const { refineRemote, stopMessage, savedFingerprint, DEFAULT_REFINE_ENDPOINT, REFINE_MODELS } = await Promise.resolve().then(() => __importStar(require("../refine/remote")));
336
+ const dryRun = rest.includes("--dry-run");
337
+ const model = flag("--model");
338
+ if (model !== undefined && !REFINE_MODELS.includes(model)) {
339
+ log(`agent-ready refine: --model must be one of ${REFINE_MODELS.join(", ")}`);
340
+ return 1;
341
+ }
342
+ const endpoint = env.AGENT_READY_REFINE_ENDPOINT?.trim() || DEFAULT_REFINE_ENDPOINT;
343
+ if (!/^https?:\/\//.test(endpoint)) {
344
+ log("agent-ready refine: AGENT_READY_REFINE_ENDPOINT must be an http(s) URL.");
345
+ return 1;
346
+ }
347
+ const apiKey = env.OPENAI_API_KEY?.trim();
348
+ if (!apiKey && !dryRun) {
349
+ log("agent-ready refine: set OPENAI_API_KEY (in .env.local, .env or the shell) to your OpenAI key, or use --dry-run." +
350
+ " Advanced: --prompts <dir> runs your own prompt files locally.");
351
+ return 1;
352
+ }
353
+ let paths = [];
354
+ if (flag("--paths"))
355
+ paths = flag("--paths").split(",").map((p) => p.trim()).filter(Boolean);
356
+ else if (rest.includes("--sitemap")) {
357
+ const { sitemapEntries } = await Promise.resolve().then(() => __importStar(require("../core/sitemap")));
358
+ const entries = await sitemapEntries({ fetch: f, limit: 5000 })({ request: new Request(site + "/llms.txt") });
359
+ paths = entries.map((e) => e.url);
360
+ }
361
+ else {
362
+ log(USAGE);
363
+ return 1;
364
+ }
365
+ paths = paths.filter((p) => p.startsWith("/"));
366
+ const limit = Number(flag("--limit") ?? "0");
367
+ if (limit > 0)
368
+ paths = paths.slice(0, limit);
369
+ if (!paths.length) {
370
+ log("agent-ready refine: no pages (check --paths, or that the site has a /sitemap.xml).");
371
+ return 1;
372
+ }
373
+ const outDir = (0, node_path_1.resolve)(flag("--out") ?? ".agent-ready/md");
374
+ const force = rest.includes("--force");
375
+ if (dryRun) {
376
+ // No API call, no page fetch, nothing written.
377
+ log(`agent-ready refine --dry-run: ${paths.length} page(s) would go to ${endpoint}${model ? ` (model ${model})` : ""}`);
378
+ for (const path of paths) {
379
+ const saved = force ? null : await savedFingerprint(outDir, path);
380
+ log(` ${(saved ? "would check" : "would send").padEnd(11)} ${path}${saved ? " (saved file; free if unchanged)" : ""}`);
381
+ }
382
+ if (!apiKey)
383
+ log("Set OPENAI_API_KEY to run it for real.");
384
+ return 0;
385
+ }
386
+ log("Rewriting with Relve's prompt via relvehq.com using your OpenAI key (the key is used for each call only; Relve does not store it).");
387
+ const report = await refineRemote({
388
+ site,
389
+ paths,
390
+ apiKey: apiKey,
391
+ outDir,
392
+ endpoint,
393
+ model,
394
+ siteName: flag("--site-name"),
395
+ force,
396
+ concurrency: Number(flag("--concurrency") ?? "2") || 2,
397
+ fetch: f,
398
+ onResult: (it) => {
399
+ const extra = it.status === "ok"
400
+ ? ` ${it.tokens ?? 0} tokens${it.kind ? ` kind: ${it.kind}` : ""}`
401
+ : it.status === "rejected"
402
+ ? ` ${(it.issues ?? []).slice(0, 5).join("; ")}`
403
+ : it.status === "error"
404
+ ? ` ${it.code}${it.message ? `: ${it.message}` : ""}`
405
+ : "";
406
+ log(` ${it.status.padEnd(10)} ${it.path}${extra}`);
407
+ },
408
+ });
409
+ const c = report.counts;
410
+ if (report.stopped)
411
+ log(`agent-ready refine: stopped early. ${stopMessage(report.stopped)}`);
412
+ log(`done: ${c.ok} written, ${c.unchanged} unchanged, ${c.rejected} rejected, ${c.error} errors${c.skipped ? `, ${c.skipped} not run` : ""} -> ${report.dir}`);
413
+ log(`tokens: ${report.tokens} in the written files (OpenAI bills its usage for each call to your account)`);
414
+ return c.error || c.rejected || report.stopped ? 1 : 0;
415
+ }
416
+ /**
417
+ * Read `.env.local` then `.env` from the working directory, like Next.js, so
418
+ * the keys a project already has (OPENAI_API_KEY, ANTHROPIC_API_KEY) just
419
+ * work. A variable already set in the shell wins; values are never printed.
420
+ */
421
+ function loadEnvFiles(cwd = process.cwd()) {
422
+ const loaded = [];
423
+ const load = process.loadEnvFile;
424
+ if (typeof load !== "function")
425
+ return loaded;
426
+ for (const name of [".env.local", ".env"]) {
427
+ const p = (0, node_path_1.join)(cwd, name);
428
+ if (!(0, node_fs_1.existsSync)(p))
429
+ continue;
430
+ try {
431
+ load(p);
432
+ loaded.push(name);
433
+ }
434
+ catch {
435
+ // A file Node can't parse is skipped; the shell env still applies.
436
+ }
437
+ }
438
+ return loaded;
439
+ }
440
+ // Run when executed directly (not when imported by the tests).
441
+ if (process.argv[1] && /agent-ready(\.ts|\.js)?$/.test(process.argv[1].replace(/\\/g, "/"))) {
442
+ loadEnvFiles();
443
+ run(process.argv.slice(2)).then((code) => (process.exitCode = code));
444
+ }
@@ -0,0 +1,70 @@
1
+ export interface InitFs {
2
+ exists(path: string): boolean;
3
+ read(path: string): string;
4
+ write(path: string, content: string): void;
5
+ append(path: string, content: string): void;
6
+ /** Copy a file byte for byte: `from` absolute, `to` project-relative. Needed for WordPress only. */
7
+ copy?(from: string, to: string): void;
8
+ }
9
+ export type ActionKind = "create" | "append" | "skip" | "manual" | "copy";
10
+ export interface InitAction {
11
+ kind: ActionKind;
12
+ /** Project-relative path, forward slashes. */
13
+ path: string;
14
+ /** Absolute source file (copy). */
15
+ from?: string;
16
+ /** File content (create), text to append (append), or the snippet to paste (manual). */
17
+ content?: string;
18
+ /** One line for the person running the command. */
19
+ note: string;
20
+ }
21
+ export interface InitPlan {
22
+ ok: boolean;
23
+ /** Set when the project can't be set up (e.g. no app/ folder). */
24
+ error?: string;
25
+ actions: InitAction[];
26
+ }
27
+ export interface InitOptions {
28
+ /** Import name of the package. Default "@relvehq/agent-ready". */
29
+ pkg?: string;
30
+ }
31
+ export declare function planInit(fs: Pick<InitFs, "exists" | "read">, opts?: InitOptions): InitPlan;
32
+ /** Write what the plan says. Manual and skip actions are left to the person. */
33
+ export declare function applyInit(plan: InitPlan, fs: Pick<InitFs, "write" | "append" | "copy">): string[];
34
+ /** A plan as lines for the terminal. */
35
+ export declare function formatPlan(plan: InitPlan, dryRun: boolean): string;
36
+ export declare function configTemplate(pkg: string): string;
37
+ export declare function middlewareTemplate(pkg: string): string;
38
+ export declare function middlewarePatch(pkg: string): string;
39
+ export declare function routeTemplate(pkg: string): string;
40
+ /** Where a venture starts: the onboarding page. */
41
+ export declare const ONBOARDING_URL = "https://relvehq.com/agent-ready";
42
+ export type Stack = "next" | "node" | "wordpress";
43
+ export declare const STACKS: readonly Stack[];
44
+ export type NodeFramework = "express" | "connect" | "polka" | "fastify" | "nest" | "h3" | "nitro";
45
+ export interface StackDetection {
46
+ stack: Stack | null;
47
+ /** Set for `node` when package.json names a known framework. */
48
+ framework?: NodeFramework;
49
+ /** One line: what decided it. */
50
+ why: string;
51
+ }
52
+ /** What detection looks for, in order (printed when nothing matches). */
53
+ export declare const DETECTION_RULES: readonly string[];
54
+ /** The Node server framework package.json names, or null. */
55
+ export declare function nodeFramework(fs: Pick<InitFs, "exists" | "read">): NodeFramework | null;
56
+ export declare function detectStack(fs: Pick<InitFs, "exists" | "read">): StackDetection;
57
+ export declare const WP_PLUGIN_PATH = "wp-content/plugins/relve-agent-ready";
58
+ /** The plugin shipped in the package: its folder (absolute) and its files (relative, forward slashes). */
59
+ export interface PluginBundle {
60
+ dir: string;
61
+ files: string[];
62
+ }
63
+ export declare function planWordPress(fs: Pick<InitFs, "exists">, plugin: PluginBundle | null, opts?: {
64
+ force?: boolean;
65
+ }): InitPlan;
66
+ export declare function formatWordPress(plan: InitPlan, dryRun: boolean): string;
67
+ /** The exact lines to add for a framework (null: the Express form, which also fits Connect, Polka and Nest). */
68
+ export declare function nodeSnippet(framework: NodeFramework | null, pkg?: string): string;
69
+ export declare function formatNode(framework: NodeFramework | null, pkg?: string): string;
70
+ export declare function formatUnknown(): string;