periderm-cli 0.1.0 → 0.1.2
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/dist/index.js +22 -8
- package/dist/review/deep.js +14 -18
- package/dist/update-notifier.js +41 -0
- package/package.json +1 -2
package/dist/index.js
CHANGED
|
@@ -15,6 +15,8 @@ import ora from "ora";
|
|
|
15
15
|
import { PERIDERM_ASCII } from "./constants.js";
|
|
16
16
|
import { PRE_SCAN_MESSAGES, UPLOAD_MESSAGES, startRotatingMessages, finishRotatingMessages } from "./loading-messages.js";
|
|
17
17
|
import { formatScanPath } from "./scanner/progress.js";
|
|
18
|
+
import { checkUpdate } from "./update-notifier.js";
|
|
19
|
+
const updatePromise = checkUpdate();
|
|
18
20
|
const program = new Command();
|
|
19
21
|
program
|
|
20
22
|
.name("periderm")
|
|
@@ -131,6 +133,7 @@ program
|
|
|
131
133
|
}
|
|
132
134
|
}
|
|
133
135
|
}
|
|
136
|
+
await updatePromise;
|
|
134
137
|
});
|
|
135
138
|
program
|
|
136
139
|
.command("review")
|
|
@@ -148,11 +151,6 @@ program
|
|
|
148
151
|
console.error(chalk.red("Error: You must be logged in. Run `periderm login` first."));
|
|
149
152
|
process.exit(1);
|
|
150
153
|
}
|
|
151
|
-
const apiKey = process.env.GROQ_API_KEY?.trim();
|
|
152
|
-
if (!apiKey) {
|
|
153
|
-
console.error(chalk.red("Error: Set GROQ_API_KEY in your environment to run deep review."));
|
|
154
|
-
process.exit(1);
|
|
155
|
-
}
|
|
156
154
|
const reportPath = path.join(root, ".periderm", "last-report.json");
|
|
157
155
|
let scanResult;
|
|
158
156
|
try {
|
|
@@ -175,7 +173,7 @@ program
|
|
|
175
173
|
}
|
|
176
174
|
verifySpinner.succeed("Plan verified");
|
|
177
175
|
const reviewSpinner = ora({ text: "Starting deep review agent…", color: "cyan" }).start();
|
|
178
|
-
const { markdown, findings } = await runDeepReview(root, scanResult,
|
|
176
|
+
const { markdown, findings } = await runDeepReview(root, scanResult, cfg.token, cfg.apiUrl, (msg) => {
|
|
179
177
|
reviewSpinner.text = msg;
|
|
180
178
|
});
|
|
181
179
|
scanResult.findings.push(...findings);
|
|
@@ -190,6 +188,7 @@ program
|
|
|
190
188
|
await fs.writeFile(reportPath, JSON.stringify(scanResult, null, 2), "utf8");
|
|
191
189
|
reviewSpinner.succeed(`Deep review complete · ${findings.length} additional finding${findings.length === 1 ? "" : "s"}`);
|
|
192
190
|
console.info(chalk.white(`\nAppended to ${mdPath}\n`));
|
|
191
|
+
await updatePromise;
|
|
193
192
|
});
|
|
194
193
|
program
|
|
195
194
|
.command("watch")
|
|
@@ -353,6 +352,7 @@ program
|
|
|
353
352
|
console.info(`user: ${r.user_id}`);
|
|
354
353
|
console.info(`plan: ${r.plan}`);
|
|
355
354
|
console.info(`quota: ${r.scans_remaining} scans remaining`);
|
|
355
|
+
await updatePromise;
|
|
356
356
|
});
|
|
357
357
|
program
|
|
358
358
|
.command("logout")
|
|
@@ -367,14 +367,28 @@ program
|
|
|
367
367
|
console.info("");
|
|
368
368
|
console.info(chalk.white("Run ") + chalk.white("$ periderm login") + chalk.white(" to re-authenticate."));
|
|
369
369
|
console.info("");
|
|
370
|
+
await updatePromise;
|
|
370
371
|
});
|
|
371
372
|
program.parseAsync().catch(async (e) => {
|
|
372
373
|
console.error(e);
|
|
373
374
|
try {
|
|
374
375
|
const formData = new FormData();
|
|
375
376
|
formData.append("name", "Periderm CLI Crash Reporter");
|
|
376
|
-
|
|
377
|
-
|
|
377
|
+
let userEmail = "support@periderm.dev";
|
|
378
|
+
try {
|
|
379
|
+
const cfg = readConfig();
|
|
380
|
+
if (cfg.token) {
|
|
381
|
+
const v = await verifyToken(cfg.token);
|
|
382
|
+
if (v.valid && v.email) {
|
|
383
|
+
userEmail = v.email;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
catch {
|
|
388
|
+
// ignore token verification errors during crash
|
|
389
|
+
}
|
|
390
|
+
formData.append("_replyto", userEmail);
|
|
391
|
+
const marker = `\n\n---\n[System Info: Sent from Periderm CLI]\n[User Email: ${userEmail === "support@periderm.dev" ? "Unknown" : userEmail}]`;
|
|
378
392
|
const errMsg = e instanceof Error ? `${e.message}\n${e.stack}` : String(e);
|
|
379
393
|
formData.append("message", `CLI Crash:\n\n${errMsg}${marker}`);
|
|
380
394
|
await fetch("https://formspree.io/f/mqakppnn", {
|
package/dist/review/deep.js
CHANGED
|
@@ -12,27 +12,23 @@ function stripCodeFences(text) {
|
|
|
12
12
|
const m = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
13
13
|
return (m?.[1] ?? text).trim();
|
|
14
14
|
}
|
|
15
|
-
async function groqJson(
|
|
16
|
-
const res = await fetch(
|
|
15
|
+
async function groqJson(token, apiUrl, system, user) {
|
|
16
|
+
const res = await fetch(`${apiUrl}/api/public/deep-review`, {
|
|
17
17
|
method: "POST",
|
|
18
18
|
headers: {
|
|
19
|
-
Authorization: `Bearer ${apiKey}`,
|
|
20
19
|
"Content-Type": "application/json",
|
|
20
|
+
"x-periderm-token": token,
|
|
21
21
|
},
|
|
22
|
-
body: JSON.stringify({
|
|
23
|
-
model: MODEL,
|
|
24
|
-
temperature: 0.2,
|
|
25
|
-
response_format: { type: "json_object" },
|
|
26
|
-
messages: [
|
|
27
|
-
{ role: "system", content: system },
|
|
28
|
-
{ role: "user", content: user },
|
|
29
|
-
],
|
|
30
|
-
}),
|
|
22
|
+
body: JSON.stringify({ system, user }),
|
|
31
23
|
});
|
|
32
|
-
if (!res.ok)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
24
|
+
if (!res.ok) {
|
|
25
|
+
const text = await res.text();
|
|
26
|
+
throw new Error(`Deep review API error ${res.status}: ${text.slice(0, 200)}`);
|
|
27
|
+
}
|
|
28
|
+
const data = await res.json();
|
|
29
|
+
if (!data.valid)
|
|
30
|
+
throw new Error(`Deep review failed: ${data.error}`);
|
|
31
|
+
return data.content ?? "{}";
|
|
36
32
|
}
|
|
37
33
|
async function readFileSafe(root, rel) {
|
|
38
34
|
const safe = rel.replace(/^(\.\/|\.\.\/)+/, "").replace(/\.\./g, "");
|
|
@@ -87,7 +83,7 @@ function parseAction(raw) {
|
|
|
87
83
|
return null;
|
|
88
84
|
}
|
|
89
85
|
}
|
|
90
|
-
export async function runDeepReview(root, scan,
|
|
86
|
+
export async function runDeepReview(root, scan, token, apiUrl, onStatus) {
|
|
91
87
|
const system = `You are Periderm's deep launch reviewer. Respond with JSON only.
|
|
92
88
|
|
|
93
89
|
Existing static scan found ${scan.findings.length} issues. Your job: find ADDITIONAL nuanced launch risks the deterministic scanner missed — legal/UX/business logic edge cases, contradictions, deceptive flows, subtle security gaps.
|
|
@@ -114,7 +110,7 @@ Rules: max 5 new findings, be specific, cite files, no hallucinated files.`;
|
|
|
114
110
|
const toolLog = [];
|
|
115
111
|
for (let i = 0; i < MAX_ITERATIONS; i++) {
|
|
116
112
|
onStatus?.(`Deep review · pass ${i + 1}/${MAX_ITERATIONS}…`);
|
|
117
|
-
const raw = await groqJson(
|
|
113
|
+
const raw = await groqJson(token, apiUrl, system, transcript);
|
|
118
114
|
const action = parseAction(raw);
|
|
119
115
|
if (!action) {
|
|
120
116
|
transcript += `\n\nInvalid JSON. Respond with valid JSON only.\n`;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const pkgPath = path.join(__dirname, "..", "package.json");
|
|
7
|
+
export async function checkUpdate() {
|
|
8
|
+
try {
|
|
9
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
10
|
+
const currentVersion = pkg.version;
|
|
11
|
+
const name = pkg.name;
|
|
12
|
+
// Timeout fetch after 1 second so we never hang the CLI
|
|
13
|
+
const controller = new AbortController();
|
|
14
|
+
const timeoutId = setTimeout(() => controller.abort(), 1000);
|
|
15
|
+
const res = await fetch(`https://registry.npmjs.org/${name}/latest`, {
|
|
16
|
+
signal: controller.signal,
|
|
17
|
+
});
|
|
18
|
+
clearTimeout(timeoutId);
|
|
19
|
+
if (!res.ok)
|
|
20
|
+
return;
|
|
21
|
+
const data = await res.json();
|
|
22
|
+
const latestVersion = data.version;
|
|
23
|
+
if (latestVersion && latestVersion !== currentVersion) {
|
|
24
|
+
// Basic semver check (only notifies if latest > current simply by string comparison or simple parsing)
|
|
25
|
+
// Since it's x.y.z, simple comparison works for basic stuff, but a strict semver check is better.
|
|
26
|
+
// We will just check if they are not equal, since latest is usually higher.
|
|
27
|
+
const box = `
|
|
28
|
+
╭──────────────────────────────────────────────────────────╮
|
|
29
|
+
│ │
|
|
30
|
+
│ A new version of Periderm CLI is available: ${chalk.dim(currentVersion)} → ${chalk.green(latestVersion)} │
|
|
31
|
+
│ Run ${chalk.cyan(`npm install -g ${name}`)} to update. │
|
|
32
|
+
│ │
|
|
33
|
+
╰──────────────────────────────────────────────────────────╯
|
|
34
|
+
`;
|
|
35
|
+
console.log(box);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
// Ignore any network/parsing errors silently
|
|
40
|
+
}
|
|
41
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "periderm-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "A pre-launch checklist for your codebase.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
"@babel/types": "^7.25.0",
|
|
22
22
|
"chalk": "^5.3.0",
|
|
23
23
|
"commander": "^12.1.0",
|
|
24
|
-
"dotenv": "^17.4.2",
|
|
25
24
|
"fast-glob": "^3.3.2",
|
|
26
25
|
"open": "^10.1.0",
|
|
27
26
|
"ora": "^9.4.1"
|