activate-agentmd 2.4.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/README.md +158 -0
- package/bin/agentmd.js +186 -0
- package/package.json +55 -0
- package/src/commands/agents.js +44 -0
- package/src/commands/analytics.js +128 -0
- package/src/commands/auth.js +144 -0
- package/src/commands/ci.js +296 -0
- package/src/commands/extract.js +254 -0
- package/src/commands/info.js +105 -0
- package/src/commands/init.js +166 -0
- package/src/commands/install.js +216 -0
- package/src/commands/link.js +206 -0
- package/src/commands/list.js +99 -0
- package/src/commands/outdated.js +92 -0
- package/src/commands/remove.js +72 -0
- package/src/commands/review.js +293 -0
- package/src/commands/search.js +110 -0
- package/src/commands/sync.js +245 -0
- package/src/commands/telemetry.js +82 -0
- package/src/commands/test.js +226 -0
- package/src/commands/update.js +115 -0
- package/src/commands/validate.js +125 -0
- package/src/config/license-public-keys.json +17 -0
- package/src/detect.json +452 -0
- package/src/lib/agents.js +301 -0
- package/src/lib/analytics.js +137 -0
- package/src/lib/coordinates.js +103 -0
- package/src/lib/credentials.js +115 -0
- package/src/lib/detect.js +352 -0
- package/src/lib/diff.js +142 -0
- package/src/lib/enterprise.js +140 -0
- package/src/lib/fetcher.js +174 -0
- package/src/lib/invocation.js +47 -0
- package/src/lib/license.js +234 -0
- package/src/lib/manifest.js +191 -0
- package/src/lib/patterns.js +135 -0
- package/src/lib/pro.js +149 -0
- package/src/lib/ranking.js +287 -0
- package/src/lib/registry.js +207 -0
- package/src/lib/star.js +105 -0
- package/src/lib/status.js +75 -0
- package/src/lib/telemetry.js +169 -0
- package/src/lib/versions.js +190 -0
- package/src/registry.json +33907 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* auth.js
|
|
3
|
+
*
|
|
4
|
+
* Handles: agentmd login [--key <key>] · agentmd logout · agentmd whoami
|
|
5
|
+
*
|
|
6
|
+
* There is no OAuth dance here on purpose. A device-code flow needs a server
|
|
7
|
+
* holding short-lived codes, one more thing to run and to keep up; a license
|
|
8
|
+
* key is a signed claim the CLI can check by itself (see lib/license.js), so
|
|
9
|
+
* the whole flow is: open the page, copy the key, paste it. That works over
|
|
10
|
+
* SSH, in a container, and on a machine with no browser — the three places a
|
|
11
|
+
* device-code flow is most annoying.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
"use strict";
|
|
15
|
+
|
|
16
|
+
const readline = require("readline");
|
|
17
|
+
const pc = require("picocolors");
|
|
18
|
+
const { verify, fingerprint, daysLeft } = require("../lib/license");
|
|
19
|
+
const { write, clear, resolveKey, credentialsPath, permissions } = require("../lib/credentials");
|
|
20
|
+
const { status, openBrowser, UPGRADE_URL } = require("../lib/pro");
|
|
21
|
+
const { cmd } = require("../lib/invocation");
|
|
22
|
+
|
|
23
|
+
const A = cmd();
|
|
24
|
+
|
|
25
|
+
/** Read one line. Echo is disabled while a key is being pasted. */
|
|
26
|
+
function ask(question, { secret = false } = {}) {
|
|
27
|
+
if (!process.stdin.isTTY) return Promise.resolve(null);
|
|
28
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
29
|
+
|
|
30
|
+
if (secret) {
|
|
31
|
+
// A license key is a credential: keep it out of the scrollback and out of
|
|
32
|
+
// any screen share. readline still needs a writable output, so the prompt
|
|
33
|
+
// is written once and everything after it is swallowed.
|
|
34
|
+
let prompted = false;
|
|
35
|
+
rl.output.write(question);
|
|
36
|
+
rl._writeToOutput = (text) => {
|
|
37
|
+
if (!prompted) { prompted = true; return; }
|
|
38
|
+
if (text.includes("\n")) rl.output.write("\n");
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return new Promise((resolve) => {
|
|
43
|
+
rl.question(secret ? "" : question, (answer) => {
|
|
44
|
+
rl.close();
|
|
45
|
+
resolve(answer.trim());
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const flagValue = (args, name) => {
|
|
51
|
+
const inline = (args || []).find((a) => a.startsWith(`--${name}=`));
|
|
52
|
+
if (inline) return inline.slice(name.length + 3);
|
|
53
|
+
const i = (args || []).indexOf(`--${name}`);
|
|
54
|
+
return i !== -1 && args[i + 1] && !args[i + 1].startsWith("--") ? args[i + 1] : null;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** Print what a verified key entitles you to. */
|
|
58
|
+
function describe(claims) {
|
|
59
|
+
const left = daysLeft(claims);
|
|
60
|
+
const lines = [
|
|
61
|
+
["Plan", claims.plan],
|
|
62
|
+
["Licensed to", claims.sub || claims.email || "—"],
|
|
63
|
+
["Seats", claims.seats ? String(claims.seats) : "—"],
|
|
64
|
+
["Expires", claims.exp ? `${new Date(claims.exp * 1000).toISOString().slice(0, 10)}${left !== null ? pc.dim(` (${left} days)`) : ""}` : "never"],
|
|
65
|
+
];
|
|
66
|
+
for (const [k, v] of lines) console.log(` ${pc.dim(k.padEnd(13))} ${pc.white(v)}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function login(args) {
|
|
70
|
+
args = args || [];
|
|
71
|
+
let key = flagValue(args, "key");
|
|
72
|
+
|
|
73
|
+
console.log(pc.bold("\nAgent.md Pro\n"));
|
|
74
|
+
|
|
75
|
+
if (!key) {
|
|
76
|
+
if (!process.stdin.isTTY) {
|
|
77
|
+
console.error(pc.red(" No key given and stdin is not a terminal."));
|
|
78
|
+
console.error(pc.dim(` In CI, set AGENTMD_PRO_KEY instead of logging in, or run '${A} login --key <key>'.\n`));
|
|
79
|
+
process.exitCode = 1;
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
console.log(` Your key is on ${pc.underline(UPGRADE_URL)}`);
|
|
83
|
+
console.log(pc.dim(" Opening that page… paste the key below (it will not be shown).\n"));
|
|
84
|
+
openBrowser(UPGRADE_URL);
|
|
85
|
+
key = await ask(" License key: ", { secret: true });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!key) {
|
|
89
|
+
console.error(pc.red("\n No key entered. Nothing was saved.\n"));
|
|
90
|
+
process.exitCode = 1;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const result = verify(key);
|
|
95
|
+
if (!result.valid) {
|
|
96
|
+
console.error(`\n ${pc.red("✗")} That key ${pc.red(result.reason)}.`);
|
|
97
|
+
console.error(pc.dim(` Nothing was saved. Check for a truncated paste, or get a fresh key at ${UPGRADE_URL}\n`));
|
|
98
|
+
process.exitCode = 1;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const file = write({ key, plan: result.claims.plan, sub: result.claims.sub ?? null });
|
|
103
|
+
console.log(`\n ${pc.green("✓")} Signed in. Key ${pc.white(fingerprint(key))} saved to ${pc.white(file)} ${pc.dim(`(${permissions()})`)}\n`);
|
|
104
|
+
describe(result.claims);
|
|
105
|
+
console.log(pc.dim(`\n Pro commands: ${A} extract · ${A} sync --private · ${A} analytics · ${A} ci --cloud\n`));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function logout() {
|
|
109
|
+
const { source } = resolveKey();
|
|
110
|
+
|
|
111
|
+
if (source && source !== credentialsPath()) {
|
|
112
|
+
// Nothing to delete: the key is coming from somewhere logout does not own.
|
|
113
|
+
// Saying "signed out" here would be a lie the next command exposes.
|
|
114
|
+
console.log(`\n ${pc.yellow("!")} The key in use comes from ${pc.white(source)}, not from the credentials file.`);
|
|
115
|
+
console.log(pc.dim(` Unset it there to sign out.\n`));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const removed = clear();
|
|
120
|
+
console.log(
|
|
121
|
+
removed
|
|
122
|
+
? `\n ${pc.green("✓")} Signed out. ${pc.dim(credentialsPath() + " deleted.")}\n`
|
|
123
|
+
: `\n ${pc.dim("Not signed in — nothing to do.")}\n`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function whoami() {
|
|
128
|
+
const state = status();
|
|
129
|
+
|
|
130
|
+
console.log("");
|
|
131
|
+
if (state.pro) {
|
|
132
|
+
console.log(` ${pc.green("✓")} Agent.md ${pc.bold(state.plan)} ${pc.dim(`· key ${fingerprint(state.key)} from ${state.source}`)}\n`);
|
|
133
|
+
describe(state.claims);
|
|
134
|
+
} else if (state.reason) {
|
|
135
|
+
console.log(` ${pc.red("✗")} The key from ${pc.white(state.source)} ${pc.red(state.reason)}.`);
|
|
136
|
+
console.log(pc.dim(` Run '${A} login' with a current key.`));
|
|
137
|
+
} else {
|
|
138
|
+
console.log(` ${pc.white("Free")} ${pc.dim("— not signed in.")}`);
|
|
139
|
+
console.log(pc.dim(` Everything local is free. Pro adds private sync, extract, analytics and cloud CI: ${UPGRADE_URL}`));
|
|
140
|
+
}
|
|
141
|
+
console.log("");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = { login, logout, whoami, ask };
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ci.js
|
|
3
|
+
*
|
|
4
|
+
* Handles: agentmd ci [--write] [--check] free — the workflow file
|
|
5
|
+
* agentmd ci --cloud [--register] Pro — hosted enforcement
|
|
6
|
+
*
|
|
7
|
+
* The free half writes a GitHub Actions workflow that runs `review --fast` on
|
|
8
|
+
* every pull request: deterministic pattern checks, no API key, no model, so
|
|
9
|
+
* it costs nothing and cannot leak a diff to anyone. That is the version most
|
|
10
|
+
* teams should run, and it is why this command is not gated.
|
|
11
|
+
*
|
|
12
|
+
* `--cloud` is **private preview**, not a shipped feature. It is behind a
|
|
13
|
+
* second flag on purpose: a command that quietly posts a repository's diff to
|
|
14
|
+
* an endpoint whose contract is still moving is the kind of thing that ends up
|
|
15
|
+
* in someone's incident review. So `--cloud` alone explains and stops; only
|
|
16
|
+
* `--cloud --preview`, with the endpoint and token configured, sends anything.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
"use strict";
|
|
20
|
+
|
|
21
|
+
const fs = require("fs");
|
|
22
|
+
const path = require("path");
|
|
23
|
+
const https = require("https");
|
|
24
|
+
const pc = require("picocolors");
|
|
25
|
+
const { load, FILE: ENTERPRISE_FILE } = require("../lib/enterprise");
|
|
26
|
+
const { resolveInProject } = require("../lib/manifest");
|
|
27
|
+
const { status } = require("../lib/pro");
|
|
28
|
+
const { cmd } = require("../lib/invocation");
|
|
29
|
+
|
|
30
|
+
const A = cmd();
|
|
31
|
+
const WORKFLOW = path.join(".github", "workflows", "agentmd.yml");
|
|
32
|
+
|
|
33
|
+
const has = (args, name) => (args || []).includes(`--${name}`);
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The workflow we recommend, tuned to what is actually in the repo.
|
|
37
|
+
*
|
|
38
|
+
* Caching matters more here than it looks: without it every pull request pays
|
|
39
|
+
* a cold `npx` download of the CLI before it can lint a three-line diff.
|
|
40
|
+
*/
|
|
41
|
+
function workflow({ nodeVersion = "20", packageManager = "npm", pro = false } = {}) {
|
|
42
|
+
const cacheKey = { npm: "package-lock.json", pnpm: "pnpm-lock.yaml", yarn: "yarn.lock" }[packageManager] || "package-lock.json";
|
|
43
|
+
return `# Generated by \`${A} ci --write\`. Safe to edit — it is not regenerated unless you ask.
|
|
44
|
+
#
|
|
45
|
+
# Runs the deterministic pattern review on every pull request: no API key, no
|
|
46
|
+
# model, nothing leaves the runner. Add \`--fail-on=medium\` to tighten it.
|
|
47
|
+
name: agentmd
|
|
48
|
+
|
|
49
|
+
on:
|
|
50
|
+
pull_request:
|
|
51
|
+
|
|
52
|
+
permissions:
|
|
53
|
+
contents: read
|
|
54
|
+
pull-requests: write
|
|
55
|
+
|
|
56
|
+
concurrency:
|
|
57
|
+
group: agentmd-\${{ github.ref }}
|
|
58
|
+
cancel-in-progress: true
|
|
59
|
+
|
|
60
|
+
jobs:
|
|
61
|
+
standards:
|
|
62
|
+
name: Standards check
|
|
63
|
+
runs-on: ubuntu-latest
|
|
64
|
+
steps:
|
|
65
|
+
- uses: actions/checkout@v4
|
|
66
|
+
with:
|
|
67
|
+
# review --base needs the history to diff against the target branch.
|
|
68
|
+
fetch-depth: 0
|
|
69
|
+
|
|
70
|
+
- uses: actions/setup-node@v4
|
|
71
|
+
with:
|
|
72
|
+
node-version: "${nodeVersion}"
|
|
73
|
+
cache: ${packageManager}
|
|
74
|
+
cache-dependency-path: ${cacheKey}
|
|
75
|
+
|
|
76
|
+
# Cache the CLI itself so a small diff does not pay a cold npx download.
|
|
77
|
+
- name: Cache agentmd
|
|
78
|
+
uses: actions/cache@v4
|
|
79
|
+
with:
|
|
80
|
+
path: ~/.npm/_npx
|
|
81
|
+
key: agentmd-\${{ runner.os }}-\${{ hashFiles('.agentmd/manifest.json') }}
|
|
82
|
+
restore-keys: agentmd-\${{ runner.os }}-
|
|
83
|
+
|
|
84
|
+
- name: Review the diff against the installed standards
|
|
85
|
+
run: npx --yes activate-agentmd review --base origin/\${{ github.base_ref }} --fast --fail-on=high --github
|
|
86
|
+
env:
|
|
87
|
+
GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}${pro ? `
|
|
88
|
+
# Pro: unlocks extract, private sync and cloud enforcement in CI.
|
|
89
|
+
# Repository → Settings → Secrets → Actions → AGENTMD_PRO_KEY
|
|
90
|
+
AGENTMD_PRO_KEY: \${{ secrets.AGENTMD_PRO_KEY }}` : ""}
|
|
91
|
+
`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** What the repo tells us about how it builds. */
|
|
95
|
+
function detectProject(cwd) {
|
|
96
|
+
const exists = (f) => fs.existsSync(path.join(cwd, f));
|
|
97
|
+
const packageManager = exists("pnpm-lock.yaml") ? "pnpm" : exists("yarn.lock") ? "yarn" : "npm";
|
|
98
|
+
|
|
99
|
+
let nodeVersion = "20";
|
|
100
|
+
try {
|
|
101
|
+
const engines = JSON.parse(fs.readFileSync(path.join(cwd, "package.json"), "utf-8")).engines?.node;
|
|
102
|
+
const major = engines && /(\d+)/.exec(engines)?.[1];
|
|
103
|
+
if (major && Number(major) >= 18) nodeVersion = major;
|
|
104
|
+
} catch {
|
|
105
|
+
// no package.json, or no engines field — 20 is a safe floor
|
|
106
|
+
}
|
|
107
|
+
if (exists(".nvmrc")) {
|
|
108
|
+
const major = /(\d+)/.exec(fs.readFileSync(path.join(cwd, ".nvmrc"), "utf-8"))?.[1];
|
|
109
|
+
if (major && Number(major) >= 18) nodeVersion = major;
|
|
110
|
+
}
|
|
111
|
+
return { packageManager, nodeVersion };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** POST JSON, resolving the parsed body. Bounded like every other request. */
|
|
115
|
+
function postJson(url, body, headers = {}) {
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
let parsed;
|
|
118
|
+
try { parsed = new URL(url); } catch { return reject(new Error(`Invalid cloud URL: ${url}`)); }
|
|
119
|
+
if (parsed.protocol !== "https:") return reject(new Error("The cloud endpoint must be https"));
|
|
120
|
+
|
|
121
|
+
const payload = Buffer.from(JSON.stringify(body));
|
|
122
|
+
const req = https.request(
|
|
123
|
+
parsed,
|
|
124
|
+
{ method: "POST", headers: { "content-type": "application/json", "content-length": payload.length, "user-agent": "activate-agentmd", ...headers } },
|
|
125
|
+
(res) => {
|
|
126
|
+
const chunks = [];
|
|
127
|
+
res.on("data", (c) => chunks.push(c));
|
|
128
|
+
res.on("end", () => {
|
|
129
|
+
const text = Buffer.concat(chunks).toString("utf-8");
|
|
130
|
+
if (res.statusCode >= 400) return reject(new Error(`HTTP ${res.statusCode}: ${text.slice(0, 200)}`));
|
|
131
|
+
try { resolve(JSON.parse(text)); } catch { resolve({ raw: text }); }
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
req.setTimeout(15000, () => req.destroy(new Error("Timed out talking to the cloud endpoint")));
|
|
136
|
+
req.on("error", reject);
|
|
137
|
+
req.end(payload);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Free: write or check the workflow. */
|
|
142
|
+
function local(args, cwd) {
|
|
143
|
+
const file = path.join(cwd, WORKFLOW);
|
|
144
|
+
const pro = status(cwd).pro;
|
|
145
|
+
const wanted = workflow({ ...detectProject(cwd), pro });
|
|
146
|
+
|
|
147
|
+
if (has(args, "check")) {
|
|
148
|
+
if (!fs.existsSync(file)) {
|
|
149
|
+
console.error(pc.red(`\n ${WORKFLOW} is missing. Run '${A} ci --write'.\n`));
|
|
150
|
+
process.exitCode = 1;
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const current = fs.readFileSync(file, "utf-8");
|
|
154
|
+
// Both names are accepted: a workflow written before the package moved to
|
|
155
|
+
// the @agentmd scope still runs a real review, and `ci --check` failing on
|
|
156
|
+
// it would send people chasing a problem they do not have.
|
|
157
|
+
const runsReview = /(?:activate-agentmd|agentmd-cli)\s+review/.test(current);
|
|
158
|
+
const hasDepth = /fetch-depth:\s*0/.test(current);
|
|
159
|
+
console.log("");
|
|
160
|
+
console.log(` ${runsReview ? pc.green("✓") : pc.red("✗")} runs ${A} review`);
|
|
161
|
+
console.log(` ${hasDepth ? pc.green("✓") : pc.yellow("!")} fetch-depth: 0 ${pc.dim("— review --base needs the history")}`);
|
|
162
|
+
console.log("");
|
|
163
|
+
if (!runsReview) process.exitCode = 1;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (!has(args, "write")) {
|
|
168
|
+
console.log(pc.bold(`\n ${WORKFLOW}\n`));
|
|
169
|
+
console.log(wanted.split("\n").map((l) => " " + pc.dim(l)).join("\n"));
|
|
170
|
+
console.log(pc.dim(`\n '${A} ci --write' to save it · '--check' to validate the one you have\n`));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (fs.existsSync(file) && !has(args, "force")) {
|
|
175
|
+
console.error(pc.yellow(`\n ${WORKFLOW} already exists. Pass --force to overwrite it.\n`));
|
|
176
|
+
process.exitCode = 1;
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const dest = resolveInProject(cwd, WORKFLOW);
|
|
180
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
181
|
+
fs.writeFileSync(dest, wanted, "utf-8");
|
|
182
|
+
console.log(`\n ${pc.green("✓")} ${pc.white(WORKFLOW)} written ${pc.dim(`— node ${detectProject(cwd).nodeVersion}, ${detectProject(cwd).packageManager}`)}`);
|
|
183
|
+
console.log(pc.dim(` Commit it, open a pull request, and the pattern review runs on the diff.\n`));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const WAITLIST_URL = process.env.AGENTMD_PREVIEW_URL || "https://agent-dot-md.vercel.app/pro#cloud-ci";
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* What `--cloud` prints without `--preview`.
|
|
190
|
+
*
|
|
191
|
+
* This is the whole gate. Hosted CI has no stable contract, no published SLA
|
|
192
|
+
* and no migration promise yet, and the honest thing to do with a command in
|
|
193
|
+
* that state is refuse to run it and say why — not queue a job against an
|
|
194
|
+
* endpoint that may answer differently next week.
|
|
195
|
+
*/
|
|
196
|
+
function previewNotice(cwd) {
|
|
197
|
+
const { config } = load(cwd);
|
|
198
|
+
console.log("");
|
|
199
|
+
console.log(` ${pc.yellow("◆")} ${pc.bold("Cloud CI is in private preview.")}`);
|
|
200
|
+
console.log("");
|
|
201
|
+
console.log(` Hosted pull-request enforcement runs the review on our infrastructure, so`);
|
|
202
|
+
console.log(` no model API key ever enters your repository secrets.`);
|
|
203
|
+
console.log("");
|
|
204
|
+
console.log(` ${pc.dim("Join the waitlist:")} ${pc.underline(WAITLIST_URL)}`);
|
|
205
|
+
console.log("");
|
|
206
|
+
console.log(` ${pc.bold("Available today, free, on your own runner:")}`);
|
|
207
|
+
console.log(` ${pc.white(`${A} ci --write`)} ${pc.dim("writes a workflow that runs review --fast on every PR")}`);
|
|
208
|
+
console.log(` ${pc.dim("No API key, no model, nothing leaves the runner.")}`);
|
|
209
|
+
console.log("");
|
|
210
|
+
console.log(pc.dim(` Already in the preview? Re-run with --preview. You will need:`));
|
|
211
|
+
console.log(pc.dim(` · cloudUrl in ${ENTERPRISE_FILE}, or AGENTMD_CLOUD_URL${config.cloudUrl ? pc.green(" ✓ set") : ""}`));
|
|
212
|
+
console.log(pc.dim(` · AGENTMD_CLOUD_TOKEN, issued with your preview invite${process.env.AGENTMD_CLOUD_TOKEN ? pc.green(" ✓ set") : ""}`));
|
|
213
|
+
console.log("");
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Pro + preview: hand the repository to the hosted runner. */
|
|
217
|
+
async function cloud(args, cwd) {
|
|
218
|
+
const { config } = load(cwd);
|
|
219
|
+
const endpoint = config.cloudUrl;
|
|
220
|
+
|
|
221
|
+
// The preview endpoint's request and response shapes are not stable. Saying
|
|
222
|
+
// so at the point of use is the difference between a preview and a promise.
|
|
223
|
+
console.log("");
|
|
224
|
+
console.log(pc.yellow(` ! Private preview — the endpoint, its payload and its response may change`));
|
|
225
|
+
console.log(pc.yellow(` without notice, and it is not covered by any support commitment.`));
|
|
226
|
+
|
|
227
|
+
const token = process.env.AGENTMD_CLOUD_TOKEN;
|
|
228
|
+
if (!token) {
|
|
229
|
+
console.error(pc.red("\n AGENTMD_CLOUD_TOKEN is not set."));
|
|
230
|
+
console.error(pc.dim(" The preview needs the token issued with your invite. It is separate from"));
|
|
231
|
+
console.error(pc.dim(` your license key, and it is what the waitlist gets you: ${WAITLIST_URL}\n`));
|
|
232
|
+
process.exitCode = 1;
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (!endpoint) {
|
|
237
|
+
console.error(pc.red("\n No cloud endpoint configured."));
|
|
238
|
+
console.error(pc.dim(` Add one to ${ENTERPRISE_FILE}:\n`));
|
|
239
|
+
console.error(pc.dim(` { "cloudUrl": "https://ci.your-company.example/agentmd" }\n`));
|
|
240
|
+
console.error(pc.dim(" or set AGENTMD_CLOUD_URL. Self-hosted runners speak the same endpoint.\n"));
|
|
241
|
+
process.exitCode = 1;
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const state = status(cwd);
|
|
246
|
+
const repo = process.env.GITHUB_REPOSITORY || path.basename(cwd);
|
|
247
|
+
const body = {
|
|
248
|
+
action: has(args, "register") ? "register" : "review",
|
|
249
|
+
repo,
|
|
250
|
+
ref: process.env.GITHUB_REF || null,
|
|
251
|
+
sha: process.env.GITHUB_SHA || null,
|
|
252
|
+
base: process.env.GITHUB_BASE_REF || null,
|
|
253
|
+
plan: state.plan,
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
console.log(pc.bold(`\n ${body.action === "register" ? "Registering" : "Queueing a review for"} ${pc.white(repo)}`));
|
|
257
|
+
console.log(pc.dim(` ${endpoint}\n`));
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
const result = await postJson(endpoint, body, {
|
|
261
|
+
authorization: `Bearer ${token}`,
|
|
262
|
+
// Lets the endpoint reject a client built against an older shape rather
|
|
263
|
+
// than misinterpreting it, which is the failure mode that makes previews
|
|
264
|
+
// painful for the people in them.
|
|
265
|
+
"x-agentmd-preview": "1",
|
|
266
|
+
"x-agentmd-cli": require("../../package.json").version,
|
|
267
|
+
});
|
|
268
|
+
console.log(` ${pc.green("✓")} ${result.message || result.status || "accepted"}`);
|
|
269
|
+
if (result.url) console.log(pc.dim(` ${result.url}`));
|
|
270
|
+
console.log("");
|
|
271
|
+
} catch (err) {
|
|
272
|
+
console.error(` ${pc.red("✗")} ${err.message}`);
|
|
273
|
+
console.error(pc.dim(`\n The endpoint in ${ENTERPRISE_FILE} did not accept the request.`));
|
|
274
|
+
console.error(pc.dim(` Hosted enforcement is in preview — '${A} ci --write' runs the same`));
|
|
275
|
+
console.error(pc.dim(" checks on your own runner today, free and with no key.\n"));
|
|
276
|
+
process.exitCode = 1;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async function ci(args) {
|
|
281
|
+
if (!has(args, "cloud")) return local(args || [], process.cwd());
|
|
282
|
+
|
|
283
|
+
// Two gates, in this order. The preview notice comes first because it
|
|
284
|
+
// applies to everyone — telling a paying customer their license is fine and
|
|
285
|
+
// then that the feature does not exist yet is a worse sequence than saying
|
|
286
|
+
// up front that it is a preview.
|
|
287
|
+
if (!has(args, "preview")) return previewNotice(process.cwd());
|
|
288
|
+
|
|
289
|
+
// Only the hosted half is paid for, so the Pro gate is here rather than on
|
|
290
|
+
// the whole command: `ci --write` has to stay free or nobody gets the free
|
|
291
|
+
// enforcement that makes the paid version worth wanting.
|
|
292
|
+
const { requirePro } = require("../lib/pro");
|
|
293
|
+
return requirePro("ci:cloud", () => cloud(args || [], process.cwd()))();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
module.exports = { ci, workflow, detectProject, previewNotice, WORKFLOW, WAITLIST_URL };
|