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 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, apiKey, (msg) => {
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
- formData.append("_replyto", "support@periderm.dev");
377
- const marker = "\n\n---\n[System Info: Sent from Periderm CLI]";
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", {
@@ -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(apiKey, system, user) {
16
- const res = await fetch("https://api.groq.com/openai/v1/chat/completions", {
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
- throw new Error(`Groq API error ${res.status}: ${(await res.text()).slice(0, 200)}`);
34
- const data = (await res.json());
35
- return data.choices?.[0]?.message?.content ?? "{}";
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, apiKey, onStatus) {
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(apiKey, system, transcript);
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.0",
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"