@relvehq/agent-ready 0.0.0-stage → 0.2.1
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 +21 -0
- package/README.md +338 -3
- package/SETUP.md +337 -0
- package/dist/bin/agent-ready.d.ts +23 -0
- package/dist/bin/agent-ready.js +350 -0
- package/dist/cli/init.d.ts +70 -0
- package/dist/cli/init.js +366 -0
- package/dist/core/accept.d.ts +8 -0
- package/dist/core/accept.js +76 -0
- package/dist/core/config.d.ts +145 -0
- package/dist/core/config.js +103 -0
- package/dist/core/convert.d.ts +22 -0
- package/dist/core/convert.js +334 -0
- package/dist/core/detect.d.ts +30 -0
- package/dist/core/detect.js +82 -0
- package/dist/core/easy.d.ts +19 -0
- package/dist/core/easy.js +117 -0
- package/dist/core/enrich.d.ts +30 -0
- package/dist/core/enrich.js +155 -0
- package/dist/core/llms.d.ts +11 -0
- package/dist/core/llms.js +71 -0
- package/dist/core/report.d.ts +15 -0
- package/dist/core/report.js +46 -0
- package/dist/core/resolve.d.ts +43 -0
- package/dist/core/resolve.js +204 -0
- package/dist/core/respond.d.ts +18 -0
- package/dist/core/respond.js +59 -0
- package/dist/core/sitemap.d.ts +31 -0
- package/dist/core/sitemap.js +140 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +76 -0
- package/dist/next/dashboard.d.ts +14 -0
- package/dist/next/dashboard.js +56 -0
- package/dist/next/index.d.ts +42 -0
- package/dist/next/index.js +100 -0
- package/dist/next/route.d.ts +26 -0
- package/dist/next/route.js +167 -0
- package/dist/node/index.d.ts +8 -0
- package/dist/node/index.js +191 -0
- package/dist/proof/audit.d.ts +47 -0
- package/dist/proof/audit.js +253 -0
- package/dist/proof/bots.d.ts +24 -0
- package/dist/proof/bots.js +60 -0
- package/dist/proof/dashboard.d.ts +8 -0
- package/dist/proof/dashboard.js +77 -0
- package/dist/proof/hits.d.ts +18 -0
- package/dist/proof/hits.js +51 -0
- package/dist/proof/jsonl.d.ts +5 -0
- package/dist/proof/jsonl.js +80 -0
- package/dist/proof/stats.d.ts +39 -0
- package/dist/proof/stats.js +127 -0
- package/dist/refine/check.d.ts +17 -0
- package/dist/refine/check.js +134 -0
- package/dist/refine/extract.d.ts +7 -0
- package/dist/refine/extract.js +120 -0
- package/dist/refine/file-store.d.ts +6 -0
- package/dist/refine/file-store.js +87 -0
- package/dist/refine/model.d.ts +30 -0
- package/dist/refine/model.js +105 -0
- package/dist/refine/prompts.d.ts +49 -0
- package/dist/refine/prompts.js +178 -0
- package/dist/refine/refine.d.ts +38 -0
- package/dist/refine/refine.js +75 -0
- package/dist/refine/run.d.ts +52 -0
- package/dist/refine/run.js +66 -0
- package/dist/refine/types.d.ts +17 -0
- package/dist/refine/types.js +8 -0
- package/dist/wp/relve-agent-ready/includes/class-relve-ar-admin.php +363 -0
- package/dist/wp/relve-agent-ready/includes/class-relve-ar-converter.php +762 -0
- package/dist/wp/relve-agent-ready/includes/class-relve-ar-llms.php +192 -0
- package/dist/wp/relve-agent-ready/includes/class-relve-ar-markdown-builder.php +447 -0
- package/dist/wp/relve-agent-ready/includes/class-relve-ar-negotiation.php +334 -0
- package/dist/wp/relve-agent-ready/includes/class-relve-ar-response.php +226 -0
- package/dist/wp/relve-agent-ready/includes/class-relve-ar-router.php +421 -0
- package/dist/wp/relve-agent-ready/includes/class-relve-ar-settings.php +115 -0
- package/dist/wp/relve-agent-ready/includes/class-relve-ar-store.php +543 -0
- package/dist/wp/relve-agent-ready/includes/index.php +2 -0
- package/dist/wp/relve-agent-ready/index.php +2 -0
- package/dist/wp/relve-agent-ready/readme.txt +75 -0
- package/dist/wp/relve-agent-ready/relve-agent-ready.php +70 -0
- package/dist/wp/relve-agent-ready/uninstall.php +24 -0
- package/package.json +64 -6
|
@@ -0,0 +1,350 @@
|
|
|
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) --prompts <dir>[,<dir>]
|
|
9
|
+
// [--site-name <name>] [--out <dir>] [--limit <n>] [--force]
|
|
10
|
+
// [--concurrency <n>] [--dry-run]
|
|
11
|
+
//
|
|
12
|
+
// The package ships no prompt. refine reads the folders in --prompts, else in
|
|
13
|
+
// AGENT_READY_PROMPTS, and stops before fetching anything when neither is set.
|
|
14
|
+
//
|
|
15
|
+
// Thin: argument parsing and the real file system; the work is in cli/init.ts
|
|
16
|
+
// and refine/*. NODE ONLY.
|
|
17
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
20
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
21
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
22
|
+
}
|
|
23
|
+
Object.defineProperty(o, k2, desc);
|
|
24
|
+
}) : (function(o, m, k, k2) {
|
|
25
|
+
if (k2 === undefined) k2 = k;
|
|
26
|
+
o[k2] = m[k];
|
|
27
|
+
}));
|
|
28
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
29
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
30
|
+
}) : function(o, v) {
|
|
31
|
+
o["default"] = v;
|
|
32
|
+
});
|
|
33
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
34
|
+
var ownKeys = function(o) {
|
|
35
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
36
|
+
var ar = [];
|
|
37
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
38
|
+
return ar;
|
|
39
|
+
};
|
|
40
|
+
return ownKeys(o);
|
|
41
|
+
};
|
|
42
|
+
return function (mod) {
|
|
43
|
+
if (mod && mod.__esModule) return mod;
|
|
44
|
+
var result = {};
|
|
45
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
46
|
+
__setModuleDefault(result, mod);
|
|
47
|
+
return result;
|
|
48
|
+
};
|
|
49
|
+
})();
|
|
50
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
51
|
+
exports.nodeFs = nodeFs;
|
|
52
|
+
exports.bundledPluginDir = bundledPluginDir;
|
|
53
|
+
exports.listPluginFiles = listPluginFiles;
|
|
54
|
+
exports.run = run;
|
|
55
|
+
exports.loadEnvFiles = loadEnvFiles;
|
|
56
|
+
const node_fs_1 = require("node:fs");
|
|
57
|
+
const node_path_1 = require("node:path");
|
|
58
|
+
const init_1 = require("../cli/init");
|
|
59
|
+
const USAGE = [
|
|
60
|
+
"Usage:",
|
|
61
|
+
" agent-ready init [--stack next|node|wordpress] [--dry-run] [--force] [--cwd <dir>] [--pkg <name>]",
|
|
62
|
+
" detects Next.js, Node servers (Express, Fastify, Nest, Nitro...) and WordPress;",
|
|
63
|
+
" --stack skips detection, --force replaces an installed WordPress plugin",
|
|
64
|
+
` guide: ${init_1.ONBOARDING_URL}`,
|
|
65
|
+
" agent-ready refine --site <origin> (--paths /a,/b | --sitemap) --prompts <dir>[,<dir>]",
|
|
66
|
+
" [--site-name <name>] [--out <dir>] [--limit <n>] [--force] [--concurrency <n>] [--dry-run]",
|
|
67
|
+
"",
|
|
68
|
+
" agent-ready check <url> [--json] how agent-ready is a site? (read-only)",
|
|
69
|
+
" agent-ready stats --log <hits.jsonl> [--days <n>] [--json] agent visits summary",
|
|
70
|
+
"",
|
|
71
|
+
"refine needs a folder of prompt files: --prompts, or AGENT_READY_PROMPTS. The package ships none.",
|
|
72
|
+
"",
|
|
73
|
+
"refine needs OPENAI_API_KEY or ANTHROPIC_API_KEY (read from .env.local / .env) unless --dry-run.",
|
|
74
|
+
"AGENT_READY_MODEL picks the model (default gpt-4o with OpenAI, claude-opus-5-5 with Claude).",
|
|
75
|
+
].join("\n");
|
|
76
|
+
function nodeFs(root) {
|
|
77
|
+
const abs = (p) => (0, node_path_1.resolve)(root, p);
|
|
78
|
+
return {
|
|
79
|
+
exists: (p) => (0, node_fs_1.existsSync)(abs(p)),
|
|
80
|
+
read: (p) => (0, node_fs_1.readFileSync)(abs(p), "utf8"),
|
|
81
|
+
write: (p, content) => {
|
|
82
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(abs(p)), { recursive: true });
|
|
83
|
+
(0, node_fs_1.writeFileSync)(abs(p), content, "utf8");
|
|
84
|
+
},
|
|
85
|
+
append: (p, content) => (0, node_fs_1.appendFileSync)(abs(p), content, "utf8"),
|
|
86
|
+
copy: (from, to) => {
|
|
87
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(abs(to)), { recursive: true });
|
|
88
|
+
(0, node_fs_1.copyFileSync)(from, abs(to));
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The WordPress plugin folder: the copy built into the package
|
|
94
|
+
* (dist/wp/relve-agent-ready, next to dist/bin and dist/cli), else the source
|
|
95
|
+
* folder when running from the repo with tsx. null when neither is there.
|
|
96
|
+
*/
|
|
97
|
+
function bundledPluginDir(here = __dirname) {
|
|
98
|
+
const candidates = [(0, node_path_1.resolve)(here, "..", "wp", "relve-agent-ready"), (0, node_path_1.resolve)(here, "..", "..", "agent-ready-wp", "relve-agent-ready")];
|
|
99
|
+
return candidates.find((dir) => (0, node_fs_1.existsSync)((0, node_path_1.join)(dir, "relve-agent-ready.php"))) ?? null;
|
|
100
|
+
}
|
|
101
|
+
/** Every file under the plugin folder, relative with forward slashes; no tests/, no dotfiles. */
|
|
102
|
+
function listPluginFiles(dir) {
|
|
103
|
+
const out = [];
|
|
104
|
+
const walk = (rel) => {
|
|
105
|
+
for (const e of (0, node_fs_1.readdirSync)((0, node_path_1.join)(dir, rel), { withFileTypes: true })) {
|
|
106
|
+
if (e.name.startsWith("."))
|
|
107
|
+
continue;
|
|
108
|
+
const p = rel ? `${rel}/${e.name}` : e.name;
|
|
109
|
+
if (e.isDirectory()) {
|
|
110
|
+
if (p !== "tests")
|
|
111
|
+
walk(p);
|
|
112
|
+
}
|
|
113
|
+
else if (e.isFile())
|
|
114
|
+
out.push(p);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
walk("");
|
|
118
|
+
return out.sort();
|
|
119
|
+
}
|
|
120
|
+
function pluginBundle() {
|
|
121
|
+
const dir = bundledPluginDir();
|
|
122
|
+
return dir ? { dir, files: listPluginFiles(dir) } : null;
|
|
123
|
+
}
|
|
124
|
+
async function run(argv, deps = {}) {
|
|
125
|
+
const d = typeof deps === "function" ? { log: deps } : deps;
|
|
126
|
+
const log = d.log ?? console.log;
|
|
127
|
+
const [cmd, ...rest] = argv;
|
|
128
|
+
const flag = (name) => {
|
|
129
|
+
const i = rest.indexOf(name);
|
|
130
|
+
return i >= 0 ? rest[i + 1] : undefined;
|
|
131
|
+
};
|
|
132
|
+
if (cmd === "init") {
|
|
133
|
+
const dryRun = rest.includes("--dry-run");
|
|
134
|
+
const fs = nodeFs((0, node_path_1.resolve)(flag("--cwd") ?? process.cwd()));
|
|
135
|
+
let detected;
|
|
136
|
+
if (rest.includes("--stack")) {
|
|
137
|
+
const forced = flag("--stack");
|
|
138
|
+
if (!forced || !init_1.STACKS.includes(forced)) {
|
|
139
|
+
log(`agent-ready init: --stack must be one of ${init_1.STACKS.join(", ")}`);
|
|
140
|
+
return 1;
|
|
141
|
+
}
|
|
142
|
+
detected = { stack: forced, framework: forced === "node" ? (0, init_1.nodeFramework)(fs) ?? undefined : undefined, why: `--stack ${forced}` };
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
detected = (0, init_1.detectStack)(fs);
|
|
146
|
+
}
|
|
147
|
+
if (detected.stack === "next") {
|
|
148
|
+
const plan = (0, init_1.planInit)(fs, { pkg: flag("--pkg") });
|
|
149
|
+
if (plan.ok && !dryRun)
|
|
150
|
+
(0, init_1.applyInit)(plan, fs);
|
|
151
|
+
log((0, init_1.formatPlan)(plan, dryRun));
|
|
152
|
+
return plan.ok ? 0 : 1;
|
|
153
|
+
}
|
|
154
|
+
if (detected.stack === "wordpress") {
|
|
155
|
+
const plan = (0, init_1.planWordPress)(fs, pluginBundle(), { force: rest.includes("--force") });
|
|
156
|
+
if (plan.ok && !dryRun)
|
|
157
|
+
(0, init_1.applyInit)(plan, fs);
|
|
158
|
+
log((0, init_1.formatWordPress)(plan, dryRun));
|
|
159
|
+
return plan.ok ? 0 : 1;
|
|
160
|
+
}
|
|
161
|
+
if (detected.stack === "node") {
|
|
162
|
+
log((0, init_1.formatNode)(detected.framework ?? null, flag("--pkg")));
|
|
163
|
+
return 0;
|
|
164
|
+
}
|
|
165
|
+
log((0, init_1.formatUnknown)());
|
|
166
|
+
return 1;
|
|
167
|
+
}
|
|
168
|
+
if (cmd === "refine")
|
|
169
|
+
return refineCommand(rest, flag, { ...d, log });
|
|
170
|
+
if (cmd === "check") {
|
|
171
|
+
// Read-only: a handful of GETs to the site, like an agent would make.
|
|
172
|
+
const target = rest.find((a) => !a.startsWith("--"));
|
|
173
|
+
if (!target) {
|
|
174
|
+
log(USAGE);
|
|
175
|
+
return 1;
|
|
176
|
+
}
|
|
177
|
+
const [{ auditSite, formatAudit }, { htmlToMarkdown }] = await Promise.all([Promise.resolve().then(() => __importStar(require("../proof/audit"))), Promise.resolve().then(() => __importStar(require("../core/convert")))]);
|
|
178
|
+
let report;
|
|
179
|
+
try {
|
|
180
|
+
report = await auditSite(target, { fetch: d.fetch, convert: (html, o) => htmlToMarkdown(html, { url: o.url }) });
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
log(`agent-ready check: ${err.message}`);
|
|
184
|
+
return 1;
|
|
185
|
+
}
|
|
186
|
+
log(rest.includes("--json") ? JSON.stringify(report, null, 2) : formatAudit(report));
|
|
187
|
+
return report.ok ? 0 : 1;
|
|
188
|
+
}
|
|
189
|
+
if (cmd === "stats") {
|
|
190
|
+
const file = flag("--log");
|
|
191
|
+
if (!file) {
|
|
192
|
+
log(USAGE);
|
|
193
|
+
return 1;
|
|
194
|
+
}
|
|
195
|
+
const [{ jsonlHitStore }, { summarize, formatSummary }] = await Promise.all([Promise.resolve().then(() => __importStar(require("../proof/jsonl"))), Promise.resolve().then(() => __importStar(require("../proof/stats")))]);
|
|
196
|
+
const days = Number(flag("--days") ?? "0");
|
|
197
|
+
const since = days > 0 ? new Date(Date.now() - days * 86400000).toISOString() : undefined;
|
|
198
|
+
const summary = summarize(await jsonlHitStore((0, node_path_1.resolve)(file)).list({ since }));
|
|
199
|
+
log(rest.includes("--json") ? JSON.stringify(summary, null, 2) : formatSummary(summary));
|
|
200
|
+
return 0;
|
|
201
|
+
}
|
|
202
|
+
log(USAGE);
|
|
203
|
+
return cmd === undefined || cmd === "--help" || cmd === "-h" ? 0 : 1;
|
|
204
|
+
}
|
|
205
|
+
async function refineCommand(rest, flag, d) {
|
|
206
|
+
const { log } = d;
|
|
207
|
+
const env = d.env ?? process.env;
|
|
208
|
+
const f = d.fetch ?? fetch;
|
|
209
|
+
const site = flag("--site")?.replace(/\/+$/, "");
|
|
210
|
+
if (!site || !/^https?:\/\//.test(site)) {
|
|
211
|
+
log(USAGE);
|
|
212
|
+
return 1;
|
|
213
|
+
}
|
|
214
|
+
// Prompts come only from folders the user names; the package ships none.
|
|
215
|
+
// Several folders may be given ("a,b"). Checked before any page is fetched.
|
|
216
|
+
const promptSource = flag("--prompts") ?? env.AGENT_READY_PROMPTS;
|
|
217
|
+
const promptDirs = (promptSource ?? "").split(",").map((p) => p.trim()).filter(Boolean);
|
|
218
|
+
if (!promptDirs.length) {
|
|
219
|
+
log("agent-ready refine: no prompts. The package ships none: name a folder of prompt files with --prompts <dir>[,<dir>]" +
|
|
220
|
+
" or AGENT_READY_PROMPTS (in .env.local, .env or the shell). Each file needs ## SYSTEM, ## USER and ## Settings.");
|
|
221
|
+
return 1;
|
|
222
|
+
}
|
|
223
|
+
if (!flag("--prompts"))
|
|
224
|
+
log(`agent-ready refine: prompts from AGENT_READY_PROMPTS (${promptDirs.join(", ")})`);
|
|
225
|
+
const siteName = flag("--site-name");
|
|
226
|
+
const dryRun = rest.includes("--dry-run");
|
|
227
|
+
// Lazy: `init` never loads linkedom.
|
|
228
|
+
const [{ parsePrompt, promptsByType, promptFor, fillPrompt, guessPageKind }, { runRefine }, { fileStore }, { sitemapEntries }, { extractInput }, { modelFromEnv }, { estimateTokens }] = await Promise.all([
|
|
229
|
+
Promise.resolve().then(() => __importStar(require("../refine/prompts"))),
|
|
230
|
+
Promise.resolve().then(() => __importStar(require("../refine/run"))),
|
|
231
|
+
Promise.resolve().then(() => __importStar(require("../refine/file-store"))),
|
|
232
|
+
Promise.resolve().then(() => __importStar(require("../core/sitemap"))),
|
|
233
|
+
Promise.resolve().then(() => __importStar(require("../refine/extract"))),
|
|
234
|
+
Promise.resolve().then(() => __importStar(require("../refine/model"))),
|
|
235
|
+
Promise.resolve().then(() => __importStar(require("../core/enrich"))),
|
|
236
|
+
]);
|
|
237
|
+
let prompts;
|
|
238
|
+
try {
|
|
239
|
+
prompts = promptsByType(promptDirs.flatMap((dir) => (0, node_fs_1.readdirSync)(dir)
|
|
240
|
+
.filter((n) => n.endsWith(".md"))
|
|
241
|
+
.map((n) => parsePrompt((0, node_fs_1.readFileSync)((0, node_path_1.join)(dir, n), "utf8")))));
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
log(`agent-ready refine: ${err.message}`);
|
|
245
|
+
return 1;
|
|
246
|
+
}
|
|
247
|
+
if (!Object.keys(prompts).length) {
|
|
248
|
+
log(`agent-ready refine: no prompt files in ${promptDirs.join(", ")}`);
|
|
249
|
+
return 1;
|
|
250
|
+
}
|
|
251
|
+
let paths = [];
|
|
252
|
+
if (flag("--paths"))
|
|
253
|
+
paths = flag("--paths").split(",").map((p) => p.trim()).filter(Boolean);
|
|
254
|
+
else if (rest.includes("--sitemap")) {
|
|
255
|
+
const entries = await sitemapEntries({ fetch: f, limit: 5000 })({ request: new Request(site + "/llms.txt") });
|
|
256
|
+
paths = entries.map((e) => e.url);
|
|
257
|
+
}
|
|
258
|
+
paths = paths.filter((p) => p.startsWith("/") && promptFor(p, prompts));
|
|
259
|
+
const limit = Number(flag("--limit") ?? "0");
|
|
260
|
+
if (limit > 0)
|
|
261
|
+
paths = paths.slice(0, limit);
|
|
262
|
+
if (!paths.length) {
|
|
263
|
+
log("agent-ready refine: no pages with a matching prompt (" + Object.keys(prompts).join(", ") + ").");
|
|
264
|
+
return 1;
|
|
265
|
+
}
|
|
266
|
+
const store = fileStore((0, node_path_1.resolve)(flag("--out") ?? ".agent-ready/md"));
|
|
267
|
+
if (dryRun) {
|
|
268
|
+
// Read-only: fetch and extract, show what would be sent. No model call, no writes.
|
|
269
|
+
log(`agent-ready refine --dry-run: ${paths.length} page(s), prompts ${Object.values(prompts).map((p) => p.id).join(", ")}`);
|
|
270
|
+
for (const path of paths) {
|
|
271
|
+
try {
|
|
272
|
+
const res = await f(site + path, { headers: { accept: "text/html" } });
|
|
273
|
+
if (!res.ok) {
|
|
274
|
+
log(` error ${path} HTTP ${res.status}`);
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
const input = await extractInput(await res.text(), { url: site + path });
|
|
278
|
+
const prompt = promptFor(path, prompts);
|
|
279
|
+
const filled = fillPrompt(prompt, input, { generatedAt: new Date().toISOString(), siteName });
|
|
280
|
+
const changed = (await store.lastHash(path)) !== input.sourceHash;
|
|
281
|
+
const kind = prompt.settings.pageType === "page" ? ` kind: ${guessPageKind(input)}` : "";
|
|
282
|
+
log(` ${(changed ? "would send" : "unchanged").padEnd(10)} ${path} ${prompt.id}${kind} ~${estimateTokens(filled.system + filled.user)} input tokens`);
|
|
283
|
+
}
|
|
284
|
+
catch (err) {
|
|
285
|
+
log(` error ${path} ${err.message}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return 0;
|
|
289
|
+
}
|
|
290
|
+
let model;
|
|
291
|
+
try {
|
|
292
|
+
model = modelFromEnv(env, f);
|
|
293
|
+
}
|
|
294
|
+
catch (err) {
|
|
295
|
+
log(err.message);
|
|
296
|
+
return 1;
|
|
297
|
+
}
|
|
298
|
+
if (!model) {
|
|
299
|
+
log("agent-ready refine: set OPENAI_API_KEY or ANTHROPIC_API_KEY (in .env or the shell), or use --dry-run.");
|
|
300
|
+
return 1;
|
|
301
|
+
}
|
|
302
|
+
const report = await runRefine({
|
|
303
|
+
site,
|
|
304
|
+
paths,
|
|
305
|
+
prompts,
|
|
306
|
+
model,
|
|
307
|
+
store,
|
|
308
|
+
force: rest.includes("--force"),
|
|
309
|
+
siteName,
|
|
310
|
+
concurrency: Number(flag("--concurrency") ?? "2") || 2,
|
|
311
|
+
fetch: f,
|
|
312
|
+
onResult: (it) => {
|
|
313
|
+
const why = it.status === "rejected" ? ` ${it.issues?.map((i) => `${i.kind}: ${i.detail}`).slice(0, 5).join("; ")}` : it.error ? ` ${it.error}` : "";
|
|
314
|
+
log(` ${it.status.padEnd(10)} ${it.path}${it.tokens ? ` ${it.tokens} tokens` : ""}${why}`);
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
const c = report.counts;
|
|
318
|
+
log(`done: ${c.ok} written, ${c.unchanged} unchanged, ${c.rejected} rejected, ${c.error} errors -> ${store.dir}`);
|
|
319
|
+
log(`model usage: ${report.usage.inputTokens} input + ${report.usage.outputTokens} output tokens`);
|
|
320
|
+
return c.error || c.rejected ? 2 : 0;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Read `.env.local` then `.env` from the working directory, like Next.js, so
|
|
324
|
+
* the keys a project already has (OPENAI_API_KEY, ANTHROPIC_API_KEY) just
|
|
325
|
+
* work. A variable already set in the shell wins; values are never printed.
|
|
326
|
+
*/
|
|
327
|
+
function loadEnvFiles(cwd = process.cwd()) {
|
|
328
|
+
const loaded = [];
|
|
329
|
+
const load = process.loadEnvFile;
|
|
330
|
+
if (typeof load !== "function")
|
|
331
|
+
return loaded;
|
|
332
|
+
for (const name of [".env.local", ".env"]) {
|
|
333
|
+
const p = (0, node_path_1.join)(cwd, name);
|
|
334
|
+
if (!(0, node_fs_1.existsSync)(p))
|
|
335
|
+
continue;
|
|
336
|
+
try {
|
|
337
|
+
load(p);
|
|
338
|
+
loaded.push(name);
|
|
339
|
+
}
|
|
340
|
+
catch {
|
|
341
|
+
// A file Node can't parse is skipped; the shell env still applies.
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return loaded;
|
|
345
|
+
}
|
|
346
|
+
// Run when executed directly (not when imported by the tests).
|
|
347
|
+
if (process.argv[1] && /agent-ready(\.ts|\.js)?$/.test(process.argv[1].replace(/\\/g, "/"))) {
|
|
348
|
+
loadEnvFiles();
|
|
349
|
+
run(process.argv.slice(2)).then((code) => (process.exitCode = code));
|
|
350
|
+
}
|
|
@@ -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;
|