getaura 0.1.1 → 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
@@ -9,8 +9,8 @@ var __export = (target, all) => {
9
9
  import { Command, Option } from "commander";
10
10
 
11
11
  // src/commands/apply.ts
12
- import { existsSync as existsSync14, rmSync as rmSync5 } from "fs";
13
- import path16 from "path";
12
+ import { existsSync as existsSync15, rmSync as rmSync6 } from "fs";
13
+ import path17 from "path";
14
14
 
15
15
  // src/core/agents.ts
16
16
  var AGENT_LAYOUT = {
@@ -990,10 +990,10 @@ function mergeDefs(...defs) {
990
990
  function cloneDef(schema) {
991
991
  return mergeDefs(schema._zod.def);
992
992
  }
993
- function getElementAtPath(obj, path20) {
994
- if (!path20)
993
+ function getElementAtPath(obj, path21) {
994
+ if (!path21)
995
995
  return obj;
996
- return path20.reduce((acc, key) => acc?.[key], obj);
996
+ return path21.reduce((acc, key) => acc?.[key], obj);
997
997
  }
998
998
  function promiseAllObject(promisesObj) {
999
999
  const keys = Object.keys(promisesObj);
@@ -1333,11 +1333,11 @@ function explicitlyAborted(x, startIndex = 0) {
1333
1333
  }
1334
1334
  return false;
1335
1335
  }
1336
- function prefixIssues(path20, issues) {
1336
+ function prefixIssues(path21, issues) {
1337
1337
  return issues.map((iss) => {
1338
1338
  var _a3;
1339
1339
  (_a3 = iss).path ?? (_a3.path = []);
1340
- iss.path.unshift(path20);
1340
+ iss.path.unshift(path21);
1341
1341
  return iss;
1342
1342
  });
1343
1343
  }
@@ -1787,16 +1787,16 @@ function flattenError(error62, mapper = (issue2) => issue2.message) {
1787
1787
  }
1788
1788
  function formatError(error62, mapper = (issue2) => issue2.message) {
1789
1789
  const fieldErrors = { _errors: [] };
1790
- const processError = (error63, path20 = []) => {
1790
+ const processError = (error63, path21 = []) => {
1791
1791
  for (const issue2 of error63.issues) {
1792
1792
  if (issue2.code === "invalid_union" && issue2.errors.length) {
1793
- issue2.errors.map((issues) => processError({ issues }, [...path20, ...issue2.path]));
1793
+ issue2.errors.map((issues) => processError({ issues }, [...path21, ...issue2.path]));
1794
1794
  } else if (issue2.code === "invalid_key") {
1795
- processError({ issues: issue2.issues }, [...path20, ...issue2.path]);
1795
+ processError({ issues: issue2.issues }, [...path21, ...issue2.path]);
1796
1796
  } else if (issue2.code === "invalid_element") {
1797
- processError({ issues: issue2.issues }, [...path20, ...issue2.path]);
1797
+ processError({ issues: issue2.issues }, [...path21, ...issue2.path]);
1798
1798
  } else {
1799
- const fullpath = [...path20, ...issue2.path];
1799
+ const fullpath = [...path21, ...issue2.path];
1800
1800
  if (fullpath.length === 0) {
1801
1801
  fieldErrors._errors.push(mapper(issue2));
1802
1802
  } else {
@@ -1835,17 +1835,17 @@ function formatError(error62, mapper = (issue2) => issue2.message) {
1835
1835
  }
1836
1836
  function treeifyError(error62, mapper = (issue2) => issue2.message) {
1837
1837
  const result = { errors: [] };
1838
- const processError = (error63, path20 = []) => {
1838
+ const processError = (error63, path21 = []) => {
1839
1839
  var _a3;
1840
1840
  for (const issue2 of error63.issues) {
1841
1841
  if (issue2.code === "invalid_union" && issue2.errors.length) {
1842
- issue2.errors.map((issues) => processError({ issues }, [...path20, ...issue2.path]));
1842
+ issue2.errors.map((issues) => processError({ issues }, [...path21, ...issue2.path]));
1843
1843
  } else if (issue2.code === "invalid_key") {
1844
- processError({ issues: issue2.issues }, [...path20, ...issue2.path]);
1844
+ processError({ issues: issue2.issues }, [...path21, ...issue2.path]);
1845
1845
  } else if (issue2.code === "invalid_element") {
1846
- processError({ issues: issue2.issues }, [...path20, ...issue2.path]);
1846
+ processError({ issues: issue2.issues }, [...path21, ...issue2.path]);
1847
1847
  } else {
1848
- const fullpath = [...path20, ...issue2.path];
1848
+ const fullpath = [...path21, ...issue2.path];
1849
1849
  if (fullpath.length === 0) {
1850
1850
  result.errors.push(mapper(issue2));
1851
1851
  continue;
@@ -1884,8 +1884,8 @@ function treeifyError(error62, mapper = (issue2) => issue2.message) {
1884
1884
  }
1885
1885
  function toDotPath(_path) {
1886
1886
  const segs = [];
1887
- const path20 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
1888
- for (const seg of path20) {
1887
+ const path21 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
1888
+ for (const seg of path21) {
1889
1889
  if (typeof seg === "number")
1890
1890
  segs.push(`[${seg}]`);
1891
1891
  else if (typeof seg === "symbol")
@@ -18987,13 +18987,13 @@ function resolveRef(ref, ctx) {
18987
18987
  if (!ref.startsWith("#")) {
18988
18988
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
18989
18989
  }
18990
- const path20 = ref.slice(1).split("/").filter(Boolean);
18991
- if (path20.length === 0) {
18990
+ const path21 = ref.slice(1).split("/").filter(Boolean);
18991
+ if (path21.length === 0) {
18992
18992
  return ctx.rootSchema;
18993
18993
  }
18994
18994
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
18995
- if (path20[0] === defsKey) {
18996
- const key = path20[1] === void 0 ? void 0 : decodeJSONPointerSegment(path20[1]);
18995
+ if (path21[0] === defsKey) {
18996
+ const key = path21[1] === void 0 ? void 0 : decodeJSONPointerSegment(path21[1]);
18997
18997
  if (!key || !ctx.defs[key]) {
18998
18998
  throw new Error(`Reference not found: ${ref}`);
18999
18999
  }
@@ -19954,6 +19954,26 @@ var MeResponse = external_exports.object({
19954
19954
  capCents: external_exports.number(),
19955
19955
  periodStart: external_exports.string()
19956
19956
  });
19957
+ var ConnectStartRequest = external_exports.object({
19958
+ /** SPKI PEM of a 2048-bit (or larger) RSA key the CLI generated for this session. */
19959
+ publicKey: external_exports.string().min(200).max(2e3)
19960
+ });
19961
+ var ConnectStartResponse = external_exports.object({
19962
+ code: external_exports.string(),
19963
+ url: external_exports.string(),
19964
+ /** Proves the poller is the CLI that started the session. Never shown to the user. */
19965
+ deviceSecret: external_exports.string(),
19966
+ expiresAt: external_exports.string(),
19967
+ intervalSeconds: external_exports.number()
19968
+ });
19969
+ var ConnectPollRequest = external_exports.object({ deviceSecret: external_exports.string().min(20).max(200) });
19970
+ var ConnectPollResponse = external_exports.discriminatedUnion("status", [
19971
+ external_exports.object({ status: external_exports.literal("pending") }),
19972
+ external_exports.object({ status: external_exports.literal("skipped") }),
19973
+ external_exports.object({ status: external_exports.literal("expired") }),
19974
+ /** `encryptedKey` is base64 RSA-OAEP (SHA-256) ciphertext of the pilot key. */
19975
+ external_exports.object({ status: external_exports.literal("complete"), encryptedKey: external_exports.string(), name: external_exports.string() })
19976
+ ]);
19957
19977
  var ApiError = external_exports.object({ error: external_exports.object({ code: external_exports.string(), message: external_exports.string() }) });
19958
19978
 
19959
19979
  // src/scoring.ts
@@ -19988,10 +20008,11 @@ function impactOf(checks, fixedCheckIds) {
19988
20008
  return Math.max(0, after.score - before.score);
19989
20009
  }
19990
20010
  function grade(score) {
19991
- if (score >= 85) return { label: "Strong aura", color: "green" };
19992
- if (score >= 65) return { label: "Decent aura", color: "yellow" };
19993
- if (score >= 40) return { label: "Aura at risk", color: "red" };
19994
- return { label: "Aura loss", color: "red" };
20011
+ if (score >= 95) return { label: "Generational aura", sentence: "You have generational aura", color: "green" };
20012
+ if (score >= 85) return { label: "Strong aura", sentence: "You have strong aura", color: "green" };
20013
+ if (score >= 65) return { label: "Decent aura", sentence: "You have decent aura", color: "yellow" };
20014
+ if (score >= 40) return { label: "Aura at risk", sentence: "Your aura is at risk", color: "red" };
20015
+ return { label: "Generational aura debt", sentence: "You have generational aura debt", color: "red" };
19995
20016
  }
19996
20017
 
19997
20018
  // src/templates.ts
@@ -20400,7 +20421,7 @@ function pmCommands(pm) {
20400
20421
  // package.json
20401
20422
  var package_default = {
20402
20423
  name: "getaura",
20403
- version: "0.1.1",
20424
+ version: "0.1.2",
20404
20425
  description: "Aura helps you and your coding agent follow best practices while you vibe code.",
20405
20426
  type: "module",
20406
20427
  bin: {
@@ -21516,7 +21537,7 @@ var AuraClient = class _AuraClient {
21516
21537
  get hasKey() {
21517
21538
  return Boolean(this.key);
21518
21539
  }
21519
- async request(method, path20, body, timeoutMs = 6e4) {
21540
+ async request(method, path21, body, timeoutMs = 6e4) {
21520
21541
  if (!this.key) throw new AuraApiError("no_key", "No pilot key");
21521
21542
  let payload;
21522
21543
  if (body !== void 0) {
@@ -21526,7 +21547,7 @@ var AuraClient = class _AuraClient {
21526
21547
  }
21527
21548
  let res;
21528
21549
  try {
21529
- res = await fetch(`${this.baseUrl}/v1/${path20}`, {
21550
+ res = await fetch(`${this.baseUrl}/v1/${path21}`, {
21530
21551
  method,
21531
21552
  headers: { authorization: `Bearer ${this.key}`, "content-type": "application/json", "user-agent": `getaura/${AURA_VERSION}` },
21532
21553
  body: payload,
@@ -21823,10 +21844,10 @@ var typecheck = {
21823
21844
  applies: (ctx) => ctx.stack.typescript,
21824
21845
  async run(ctx) {
21825
21846
  if (ctx.options.fast) return { status: "unknown", summary: "Skipped in fast mode." };
21826
- const { existsSync: existsSync16 } = await import("fs");
21827
- const path20 = await import("path");
21828
- const tsc = path20.join(ctx.root, "node_modules", ".bin", process.platform === "win32" ? "tsc.cmd" : "tsc");
21829
- if (!existsSync16(tsc)) return { status: "unknown", summary: "Dependencies not installed, so the type check was skipped." };
21847
+ const { existsSync: existsSync17 } = await import("fs");
21848
+ const path21 = await import("path");
21849
+ const tsc = path21.join(ctx.root, "node_modules", ".bin", process.platform === "win32" ? "tsc.cmd" : "tsc");
21850
+ if (!existsSync17(tsc)) return { status: "unknown", summary: "Dependencies not installed, so the type check was skipped." };
21830
21851
  const r = await run(tsc, ["--noEmit", "--pretty", "false"], { cwd: ctx.root, timeoutMs: 18e4 });
21831
21852
  if (r.timedOut) return { status: "unknown", summary: "Type check timed out." };
21832
21853
  if (r.code === 0) return { status: "pass", summary: "No type errors.", engine: "tsc" };
@@ -22726,8 +22747,8 @@ var say = {
22726
22747
  };
22727
22748
 
22728
22749
  // src/commands/fix.ts
22729
- import { existsSync as existsSync13, readFileSync as readFileSync13, writeFileSync as writeFileSync5 } from "fs";
22730
- import path15 from "path";
22750
+ import { existsSync as existsSync14, readFileSync as readFileSync14, writeFileSync as writeFileSync6 } from "fs";
22751
+ import path16 from "path";
22731
22752
 
22732
22753
  // src/agent/tools.ts
22733
22754
  import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync8, rmSync as rmSync4, statSync as statSync2, writeFileSync as writeFileSync4 } from "fs";
@@ -22850,6 +22871,15 @@ function loc(f) {
22850
22871
  if (!l) return "";
22851
22872
  return l.line ? `${l.file}:${l.line}` : l.file;
22852
22873
  }
22874
+ function auraBadge(g, colors = pc2.isColorSupported) {
22875
+ const text = `\u2726 ${g.sentence.toUpperCase()} \u2726`;
22876
+ if (!colors) return [` ${text}`];
22877
+ const c = pc2.createColors(true);
22878
+ const bg = g.color === "green" ? c.bgGreen : g.color === "yellow" ? c.bgYellow : c.bgRed;
22879
+ const fg = g.color === "red" ? c.white : c.black;
22880
+ const pad = " ".repeat(text.length + 6);
22881
+ return [` ${bg(pad)}`, ` ${bg(fg(c.bold(` ${text} `)))}`, ` ${bg(pad)}`];
22882
+ }
22853
22883
  function renderTerminal(scan, opts = {}) {
22854
22884
  const g = grade(scan.score);
22855
22885
  const color = g.color === "green" ? pc2.green : g.color === "yellow" ? pc2.yellow : pc2.red;
@@ -22857,13 +22887,14 @@ function renderTerminal(scan, opts = {}) {
22857
22887
  const delta = opts.previous != null ? scan.score - opts.previous : null;
22858
22888
  const deltaText = delta === null || delta === 0 ? "" : delta > 0 ? pc2.green(` \u25B2 ${delta}`) : pc2.red(` \u25BC ${-delta}`);
22859
22889
  lines.push("");
22860
- lines.push(` ${pc2.bold("Aura score")} ${color(pc2.bold(String(scan.score)))}${pc2.dim("/100")}${deltaText} ${color(g.label)}`);
22890
+ lines.push(` ${pc2.bold("Aura score")} ${color(pc2.bold(String(scan.score)))}${pc2.dim("/100")}${deltaText}`);
22861
22891
  if (scan.cappedBy) lines.push(pc2.red(` Capped at ${scan.score} until this is fixed: ${scan.cappedBy}`));
22862
22892
  lines.push("");
22863
22893
  for (const c of scan.categories) {
22864
22894
  const label = CATEGORY_LABELS[c.category].padEnd(12);
22865
22895
  lines.push(c.score === null ? ` ${label} ${pc2.dim("not applicable")}` : ` ${label} ${bar(c.score)} ${String(c.score).padStart(3)}`);
22866
22896
  }
22897
+ lines.push("", ...auraBadge(g));
22867
22898
  const findings = allFindings(scan).filter((f) => f.severity !== "info");
22868
22899
  const top = findings.slice(0, opts.top ?? 5);
22869
22900
  if (top.length) {
@@ -22908,7 +22939,7 @@ function renderMarkdown(scan, steps) {
22908
22939
  const g = grade(scan.score);
22909
22940
  const md = [];
22910
22941
  md.push(`# Aura report`, "");
22911
- md.push(`**Aura score: ${scan.score}/100** (${g.label}) `);
22942
+ md.push(`**Aura score: ${scan.score}/100** `);
22912
22943
  md.push(
22913
22944
  `Scanned ${new Date(scan.scannedAt).toUTCString()}${scan.commit ? ` at commit \`${scan.commit.slice(0, 7)}\`` : ""} with Aura ${scan.auraVersion}.`,
22914
22945
  ""
@@ -22916,7 +22947,7 @@ function renderMarkdown(scan, steps) {
22916
22947
  if (scan.cappedBy) md.push(`> The score is capped at ${scan.score} until this critical issue is fixed: ${scan.cappedBy}`, "");
22917
22948
  md.push("| Category | Score |", "| --- | --- |");
22918
22949
  for (const c of scan.categories) md.push(`| ${CATEGORY_LABELS[c.category]} | ${c.score ?? "n/a"} |`);
22919
- md.push("");
22950
+ md.push("", `## \u2726 ${g.sentence} \u2726`, "");
22920
22951
  if (steps.length) {
22921
22952
  md.push("## Next steps", "");
22922
22953
  for (const [i, s] of steps.entries())
@@ -23074,8 +23105,8 @@ async function addExplanations(root, scan, client) {
23074
23105
 
23075
23106
  // src/commands/simple.ts
23076
23107
  import { execFileSync } from "child_process";
23077
- import { existsSync as existsSync12, readdirSync as readdirSync4, readFileSync as readFileSync12 } from "fs";
23078
- import path14 from "path";
23108
+ import { existsSync as existsSync13, readdirSync as readdirSync4, readFileSync as readFileSync13 } from "fs";
23109
+ import path15 from "path";
23079
23110
  import YAML2 from "yaml";
23080
23111
 
23081
23112
  // src/commands/inventory.ts
@@ -23198,12 +23229,165 @@ function appendHistory(root, scan) {
23198
23229
  return trimmed;
23199
23230
  }
23200
23231
 
23232
+ // src/banner.ts
23233
+ import pc3 from "picocolors";
23234
+ var SPARKLE = [" \u2597\u2596 ", " \u2588\u2588 ", " \u2581\u2582\u2584\u2588\u2588\u2588\u2588\u2584\u2582\u2581 ", " \u2594\u2580\u2580\u2588\u2588\u2588\u2588\u2580\u2580\u2594 ", " \u2588\u2588 ", " \u259D\u2598 "];
23235
+ var WORDMARK = [
23236
+ " \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 ",
23237
+ "\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557",
23238
+ "\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551",
23239
+ "\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551",
23240
+ "\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551",
23241
+ "\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D"
23242
+ ];
23243
+ var LOGO = SPARKLE.map((s, i) => `${s} ${WORDMARK[i]}`);
23244
+ var GLOW = [99, 135, 171, 207, 206, 205];
23245
+ function glow(line) {
23246
+ const width = LOGO[0].length;
23247
+ return [...line].map((ch, i) => ch === " " ? ch : `\x1B[38;5;${GLOW[Math.floor(i / width * GLOW.length)]}m${ch}`).join("") + "\x1B[39m";
23248
+ }
23249
+ function banner(colors = pc3.isColorSupported) {
23250
+ const lines = LOGO.map((l) => ` ${colors ? glow(l) : l}`);
23251
+ lines[lines.length - 1] += ` ${colors ? pc3.dim(`v${AURA_VERSION}`) : `v${AURA_VERSION}`}`;
23252
+ return ["", ...lines, ""].join("\n");
23253
+ }
23254
+
23255
+ // src/api/connect.ts
23256
+ import { spawn as spawn2 } from "child_process";
23257
+ import { constants, generateKeyPairSync, privateDecrypt } from "crypto";
23258
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync12, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
23259
+ import os4 from "os";
23260
+ import path14 from "path";
23261
+ var pendingFile = () => path14.join(os4.homedir(), ".aura", "pending-connect.json");
23262
+ async function postJson(apiUrl, route, body) {
23263
+ let res;
23264
+ try {
23265
+ res = await fetch(`${apiUrl}/v1/${route}`, {
23266
+ method: "POST",
23267
+ headers: { "content-type": "application/json", "user-agent": `getaura/${AURA_VERSION}` },
23268
+ body: JSON.stringify(body),
23269
+ signal: AbortSignal.timeout(2e4)
23270
+ });
23271
+ } catch (err) {
23272
+ throw new AuraApiError("network", err.message);
23273
+ }
23274
+ const json2 = await res.json().catch(() => null);
23275
+ if (!res.ok) throw new AuraApiError(json2?.error?.code ?? "model_error", json2?.error?.message ?? `HTTP ${res.status}`);
23276
+ return json2;
23277
+ }
23278
+ function loadPending(apiUrl, now) {
23279
+ try {
23280
+ const p2 = JSON.parse(readFileSync12(pendingFile(), "utf8"));
23281
+ if (p2.apiUrl === apiUrl && Date.parse(p2.expiresAt) - now > 6e4) return p2;
23282
+ } catch {
23283
+ }
23284
+ return null;
23285
+ }
23286
+ function clearPendingConnect() {
23287
+ if (existsSync12(pendingFile())) rmSync5(pendingFile(), { force: true });
23288
+ }
23289
+ async function startOrResumeConnect(apiUrl, now = Date.now()) {
23290
+ const existing = loadPending(apiUrl, now);
23291
+ if (existing) return { ...existing, resumed: true };
23292
+ const { publicKey, privateKey } = generateKeyPairSync("rsa", {
23293
+ modulusLength: 2048,
23294
+ publicKeyEncoding: { type: "spki", format: "pem" },
23295
+ privateKeyEncoding: { type: "pkcs8", format: "pem" }
23296
+ });
23297
+ const res = ConnectStartResponse.parse(await postJson(apiUrl, "connect", { publicKey }));
23298
+ const pending = { apiUrl, privateKey, ...res };
23299
+ mkdirSync5(path14.dirname(pendingFile()), { recursive: true });
23300
+ writeFileSync5(pendingFile(), JSON.stringify(pending, null, 2) + "\n", { mode: 384 });
23301
+ return { ...pending, resumed: false };
23302
+ }
23303
+ async function waitForConnect(pending, waitSeconds, sleep = (ms) => new Promise((r) => setTimeout(r, ms))) {
23304
+ const deadline = Date.now() + waitSeconds * 1e3;
23305
+ for (; ; ) {
23306
+ const res = ConnectPollResponse.parse(await postJson(pending.apiUrl, "connect/poll", { deviceSecret: pending.deviceSecret }));
23307
+ if (res.status === "complete") {
23308
+ clearPendingConnect();
23309
+ const key = privateDecrypt(
23310
+ { key: pending.privateKey, padding: constants.RSA_PKCS1_OAEP_PADDING, oaepHash: "sha256" },
23311
+ Buffer.from(res.encryptedKey, "base64")
23312
+ ).toString("utf8");
23313
+ return { status: "complete", key, name: res.name };
23314
+ }
23315
+ if (res.status !== "pending") {
23316
+ clearPendingConnect();
23317
+ return { status: res.status };
23318
+ }
23319
+ if (Date.now() + pending.intervalSeconds * 1e3 > deadline) return { status: "pending" };
23320
+ await sleep(pending.intervalSeconds * 1e3);
23321
+ }
23322
+ }
23323
+ function openInBrowser(url2) {
23324
+ if (process.env.AURA_NO_BROWSER || process.env.CI) return;
23325
+ const [cmd, args] = process.platform === "darwin" ? ["open", [url2]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url2]] : ["xdg-open", [url2]];
23326
+ try {
23327
+ const child = spawn2(cmd, args, { stdio: "ignore", detached: true });
23328
+ child.on("error", () => {
23329
+ });
23330
+ child.unref();
23331
+ } catch {
23332
+ }
23333
+ }
23334
+
23335
+ // src/commands/connect.ts
23336
+ var AGENT_WAIT_SECONDS = 90;
23337
+ function parseWait(value) {
23338
+ const n = Number(value);
23339
+ return value !== void 0 && Number.isFinite(n) && n >= 0 ? n : void 0;
23340
+ }
23341
+ async function signInWithBrowser(apiUrl, opts) {
23342
+ try {
23343
+ const pending = await startOrResumeConnect(apiUrl);
23344
+ if (!pending.resumed) openInBrowser(pending.url);
23345
+ const wait = opts.waitSeconds ?? (opts.person ? Math.max(0, (Date.parse(pending.expiresAt) - Date.now()) / 1e3) : AGENT_WAIT_SECONDS);
23346
+ console.log(`
23347
+ ${pc.magenta("\u2726")} ${pc.bold("Connect your Aura pilot key")}`);
23348
+ if (opts.person) {
23349
+ console.log(` ${pending.resumed ? "Open" : "Your browser should open. If it doesn\u2019t, go to"}: ${pc.cyan(pending.url)}`);
23350
+ console.log(pc.dim(` Check the page shows the code ${pending.code}. Press Ctrl+C to stop.
23351
+ `));
23352
+ } else {
23353
+ console.log(` Ask the user to open this link and paste their pilot key there (or choose to continue without one):`);
23354
+ console.log(` ${pending.url}`);
23355
+ console.log(` Don\u2019t ask for the key in chat. The page sends it to this computer encrypted. Code: ${pending.code}`);
23356
+ console.log(` Waiting up to ${Math.round(wait)} seconds\u2026
23357
+ `);
23358
+ }
23359
+ const s = spin(opts.person);
23360
+ s.start("Waiting for you to finish in the browser");
23361
+ const outcome = await waitForConnect(pending, wait);
23362
+ s.stop(outcome.status === "complete" ? "Key received" : outcome.status === "skipped" ? "Continuing without a key" : "Stopped waiting");
23363
+ return outcome;
23364
+ } catch (err) {
23365
+ return { status: "error", message: friendlyApiError(err) };
23366
+ }
23367
+ }
23368
+ function explainSignIn(outcome) {
23369
+ switch (outcome.status) {
23370
+ case "skipped":
23371
+ say.dim("No pilot key: Aura will still score your repo, with standard explanations. Add one later with `aura login`.");
23372
+ return true;
23373
+ case "pending":
23374
+ say.warn("Still waiting for the user to finish in the browser. When they have, run the same command again: it carries on with the same link.");
23375
+ return false;
23376
+ case "expired":
23377
+ say.warn("The sign-in link expired. Run the same command again for a new one.");
23378
+ return false;
23379
+ case "error":
23380
+ say.warn(`Couldn\u2019t start sign-in: ${outcome.message}`);
23381
+ return false;
23382
+ }
23383
+ }
23384
+
23201
23385
  // src/commands/simple.ts
23202
23386
  async function latestScan(root) {
23203
23387
  const file2 = auraPath(root, "scan.json");
23204
- if (existsSync12(file2)) {
23388
+ if (existsSync13(file2)) {
23205
23389
  try {
23206
- const scan = JSON.parse(readFileSync12(file2, "utf8"));
23390
+ const scan = JSON.parse(readFileSync13(file2, "utf8"));
23207
23391
  const head = execFileSync("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
23208
23392
  const dirty = execFileSync("git", ["status", "--porcelain"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
23209
23393
  if (scan.commit === head && !dirty && Date.now() - Date.parse(scan.scannedAt) < 24 * 36e5) return scan;
@@ -23228,19 +23412,31 @@ async function historyCommand(flags) {
23228
23412
  return 0;
23229
23413
  }
23230
23414
  async function loginCommand(flags) {
23415
+ const person = interactive();
23416
+ if (person) process.stdout.write(`${banner()}
23417
+ `);
23418
+ const root = await findRepoRoot();
23419
+ const apiUrl = getApiUrl(loadConfig(root));
23231
23420
  let key = flags.key ?? process.env.AURA_KEY;
23232
- if (!key && interactive()) key = unwrap(await p.password({ message: "Paste your Aura pilot key", mask: "\u2022" }));
23421
+ if (!key && flags.paste && person) key = unwrap(await p.password({ message: "Paste your Aura pilot key", mask: "\u2022" }));
23233
23422
  if (!key) {
23234
- say.error("Pass your key with --key or set AURA_KEY.");
23235
- return 1;
23423
+ if (process.env.CI) {
23424
+ say.error("Pass your key with --key or set AURA_KEY.");
23425
+ return 1;
23426
+ }
23427
+ const outcome = await signInWithBrowser(apiUrl, { person, waitSeconds: parseWait(flags.wait) });
23428
+ if (outcome.status !== "complete") {
23429
+ explainSignIn(outcome);
23430
+ return outcome.status === "skipped" ? 0 : 1;
23431
+ }
23432
+ key = outcome.key;
23236
23433
  }
23237
23434
  key = key.trim();
23238
23435
  if (!/^aura_(?:live|dev)_[A-Za-z0-9]{16,}$/.test(key)) {
23239
23436
  say.error("That doesn\u2019t look like an Aura pilot key (they start with aura_live_).");
23240
23437
  return 1;
23241
23438
  }
23242
- const root = await findRepoRoot();
23243
- const client = new AuraClient(getApiUrl(loadConfig(root)), key);
23439
+ const client = new AuraClient(apiUrl, key);
23244
23440
  try {
23245
23441
  const me = await client.request("GET", "me");
23246
23442
  saveApiKey(key);
@@ -23304,9 +23500,9 @@ async function configCommand(key, value) {
23304
23500
  return 0;
23305
23501
  }
23306
23502
  function loadGuides() {
23307
- const dir = path14.join(templatesDir(), "guides");
23503
+ const dir = path15.join(templatesDir(), "guides");
23308
23504
  return readdirSync4(dir).filter((f) => f.endsWith(".md")).map((f) => {
23309
- const text = readFileSync12(path14.join(dir, f), "utf8");
23505
+ const text = readFileSync13(path15.join(dir, f), "utf8");
23310
23506
  const fm = text.match(/^---\n([\s\S]*?)\n---\n?/);
23311
23507
  const meta3 = fm ? YAML2.parse(fm[1]) : {};
23312
23508
  return { id: f.replace(/\.md$/, ""), title: meta3.title ?? f, summary: meta3.summary ?? "", body: fm ? text.slice(fm[0].length) : text };
@@ -23374,7 +23570,7 @@ function allowedCommands(root, pm, typescript) {
23374
23570
  const pkg = readJson(root, "package.json");
23375
23571
  const run2 = pmCommands(pm).run;
23376
23572
  const cmds = ["test", "lint", "typecheck", "build"].filter((s) => pkg?.scripts?.[s]).map((s) => `${run2} ${s}`);
23377
- if (typescript && !pkg?.scripts?.typecheck && existsSync13(path15.join(root, "node_modules/.bin/tsc"))) cmds.push(`${pmCommands(pm).exec} tsc --noEmit`);
23573
+ if (typescript && !pkg?.scripts?.typecheck && existsSync14(path16.join(root, "node_modules/.bin/tsc"))) cmds.push(`${pmCommands(pm).exec} tsc --noEmit`);
23378
23574
  return cmds;
23379
23575
  }
23380
23576
  function taskFor(root, scan, action2, findings) {
@@ -23425,9 +23621,9 @@ async function untrackEnvFiles(root, findings) {
23425
23621
  const files = findings.filter((f) => f.checkId === "security.env-files").map((f) => f.locations[0].file);
23426
23622
  for (const f of files) await git(root, "rm", "-q", "--cached", "--", f);
23427
23623
  if (files.length) {
23428
- const gi = path15.join(root, ".gitignore");
23429
- const current2 = existsSync13(gi) ? readFileSync13(gi, "utf8") : "";
23430
- if (!/^\.env\*?$/m.test(current2)) writeFileSync5(gi, `${current2.trimEnd()}
23624
+ const gi = path16.join(root, ".gitignore");
23625
+ const current2 = existsSync14(gi) ? readFileSync14(gi, "utf8") : "";
23626
+ if (!/^\.env\*?$/m.test(current2)) writeFileSync6(gi, `${current2.trimEnd()}
23431
23627
 
23432
23628
  # Environment files hold secrets
23433
23629
  .env*
@@ -23629,7 +23825,7 @@ async function applyCommand(actionId, flags) {
23629
23825
  console.log(`
23630
23826
  ${pc.bold(action2.title)}`);
23631
23827
  for (const c of plan.changes)
23632
- console.log(` ${c.content === null ? pc.red("delete") : existsSync14(path16.join(root, c.path)) ? pc.yellow("update") : pc.green("add ")} ${c.path}`);
23828
+ console.log(` ${c.content === null ? pc.red("delete") : existsSync15(path17.join(root, c.path)) ? pc.yellow("update") : pc.green("add ")} ${c.path}`);
23633
23829
  const pm = pmCommands(scan.stack.packageManager);
23634
23830
  if (plan.devDeps?.length) console.log(` ${pc.cyan("install")} ${plan.devDeps.join(", ")} ${pc.dim(`(${pm.addDev})`)}`);
23635
23831
  for (const cmd of plan.commands ?? []) console.log(` ${pc.cyan("run ")} ${cmd.join(" ")}`);
@@ -23657,8 +23853,8 @@ ${pc.bold(action2.title)}`);
23657
23853
  }
23658
23854
  if (interactive() && !flags.yes && !unwrap(await p.confirm({ message: openPr ? "Create a branch and open a pull request?" : "Create a branch and commit?" })))
23659
23855
  return 0;
23660
- const tsc = path16.join(root, "node_modules", ".bin", process.platform === "win32" ? "tsc.cmd" : "tsc");
23661
- const checkTypes = plan.verifyTypes && scan.stack.typescript && existsSync14(tsc);
23856
+ const tsc = path17.join(root, "node_modules", ".bin", process.platform === "win32" ? "tsc.cmd" : "tsc");
23857
+ const checkTypes = plan.verifyTypes && scan.stack.typescript && existsSync15(tsc);
23662
23858
  const typeErrors = async () => {
23663
23859
  const r = await run(tsc, ["--noEmit", "--pretty", "false"], { cwd: root, timeoutMs: 3e5 });
23664
23860
  return (r.stdout.match(/error TS\d+/g) ?? []).length;
@@ -23756,16 +23952,16 @@ async function undoPlan(root, plan) {
23756
23952
  for (const file2 of touched) {
23757
23953
  const inHead = (await git(root, "cat-file", "-e", `HEAD:${file2}`)).code === 0;
23758
23954
  if (inHead) await git(root, "checkout", "HEAD", "--", file2);
23759
- else if (plan.changes.some((c) => c.path === file2)) rmSync5(path16.join(root, file2), { force: true });
23955
+ else if (plan.changes.some((c) => c.path === file2)) rmSync6(path17.join(root, file2), { force: true });
23760
23956
  }
23761
23957
  }
23762
23958
 
23763
23959
  // src/commands/init.ts
23764
- import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
23960
+ import { existsSync as existsSync16, readFileSync as readFileSync15 } from "fs";
23765
23961
  import { spawnSync as spawnSync2 } from "child_process";
23766
23962
 
23767
23963
  // src/brief.ts
23768
- import path17 from "path";
23964
+ import path18 from "path";
23769
23965
  function repoSummary(ctx) {
23770
23966
  const routes = ctx.files.filter((f) => /^(?:src\/)?app\/(?:.+\/)?(?:page|route)\.[cm]?[jt]sx?$/.test(f) || /^(?:src\/)?pages\/.+\.[cm]?[jt]sx?$/.test(f)).map(
23771
23967
  (f) => "/" + f.replace(/^(?:src\/)?(?:app|pages)\//, "").replace(/(?:^|\/)(?:page|route|index)\.[cm]?[jt]sx?$|\.[cm]?[jt]sx?$/, "").replace(/\([^)]+\)\//g, "")
@@ -23781,7 +23977,7 @@ function repoSummary(ctx) {
23781
23977
  const readme2 = ctx.files.find((f) => /^readme\.mdx?$/i.test(f));
23782
23978
  const readmeText = readme2 ? ctx.read(readme2) ?? "" : "";
23783
23979
  return {
23784
- name: ctx.pkg?.name ?? path17.basename(ctx.root),
23980
+ name: ctx.pkg?.name ?? path18.basename(ctx.root),
23785
23981
  packageJson: ctx.pkg ? {
23786
23982
  name: ctx.pkg.name,
23787
23983
  description: ctx.pkg.description,
@@ -23853,22 +24049,22 @@ async function draftBrief(client, answers, summary) {
23853
24049
  }
23854
24050
 
23855
24051
  // src/commands/scan.ts
23856
- import { mkdtempSync as mkdtempSync3, rmSync as rmSync6, writeFileSync as writeFileSync6 } from "fs";
23857
- import os4 from "os";
23858
- import path18 from "path";
24052
+ import { mkdtempSync as mkdtempSync3, rmSync as rmSync7, writeFileSync as writeFileSync7 } from "fs";
24053
+ import os5 from "os";
24054
+ import path19 from "path";
23859
24055
  async function scanRef(root, ref, opts) {
23860
- const dir = mkdtempSync3(path18.join(os4.tmpdir(), "aura-base-"));
23861
- const wt = path18.join(dir, "tree");
24056
+ const dir = mkdtempSync3(path19.join(os5.tmpdir(), "aura-base-"));
24057
+ const wt = path19.join(dir, "tree");
23862
24058
  const add = await run("git", ["worktree", "add", "--detach", wt, ref], { cwd: root });
23863
24059
  if (add.code !== 0) {
23864
- rmSync6(dir, { recursive: true, force: true });
24060
+ rmSync7(dir, { recursive: true, force: true });
23865
24061
  return null;
23866
24062
  }
23867
24063
  try {
23868
24064
  return await runScan(wt, { ...opts, fast: true });
23869
24065
  } finally {
23870
24066
  await run("git", ["worktree", "remove", "--force", wt], { cwd: root });
23871
- rmSync6(dir, { recursive: true, force: true });
24067
+ rmSync7(dir, { recursive: true, force: true });
23872
24068
  }
23873
24069
  }
23874
24070
  async function scanCommand(flags) {
@@ -23911,7 +24107,7 @@ async function scanCommand(flags) {
23911
24107
  const baseIds = new Set(base ? base.checks.flatMap((c) => c.findings.map((f) => f.id)) : []);
23912
24108
  const newCritical = base ? scan.checks.flatMap((c) => c.findings).filter((f) => f.severity === "critical" && !baseIds.has(f.id)) : [];
23913
24109
  if (base && (base.score - scan.score > maxDrop || newCritical.length > 0)) exitCode = 1;
23914
- if (flags.commentFile) writeFileSync6(flags.commentFile, renderPrComment(scan, base, maxDrop));
24110
+ if (flags.commentFile) writeFileSync7(flags.commentFile, renderPrComment(scan, base, maxDrop));
23915
24111
  if (quiet) {
23916
24112
  printJson({ ...scan, nextSteps: steps, base: base ? { score: base.score, categories: base.categories } : void 0 });
23917
24113
  return exitCode;
@@ -23931,10 +24127,10 @@ async function scanCommand(flags) {
23931
24127
  var AGENT_IDS = ["claude", "codex", "cursor", "pi"];
23932
24128
  function detectAgents(root) {
23933
24129
  const found = [];
23934
- if (existsSync15(`${root}/CLAUDE.md`) || existsSync15(`${root}/.claude`)) found.push("claude");
23935
- if (existsSync15(`${root}/AGENTS.md`)) found.push("codex");
23936
- if (existsSync15(`${root}/.cursor`) || existsSync15(`${root}/.cursorrules`)) found.push("cursor");
23937
- if (existsSync15(`${root}/.pi`)) found.push("pi");
24130
+ if (existsSync16(`${root}/CLAUDE.md`) || existsSync16(`${root}/.claude`)) found.push("claude");
24131
+ if (existsSync16(`${root}/AGENTS.md`)) found.push("codex");
24132
+ if (existsSync16(`${root}/.cursor`) || existsSync16(`${root}/.cursorrules`)) found.push("cursor");
24133
+ if (existsSync16(`${root}/.pi`)) found.push("pi");
23938
24134
  return found;
23939
24135
  }
23940
24136
  function openEditor(file2) {
@@ -23942,10 +24138,31 @@ function openEditor(file2) {
23942
24138
  const r = spawnSync2(editor, [file2], { stdio: "inherit", shell: process.platform === "win32" });
23943
24139
  return r.status === 0;
23944
24140
  }
24141
+ var EXAMPLE_ANSWERS = '{"whatItDoes": "\u2026", "audience": "\u2026", "chargesMoney": false, "dataHandled": ["personal"], "userRegions": ["Canada"]}';
24142
+ function briefHandoff() {
24143
+ return [
24144
+ "",
24145
+ `${pc.magenta("\u2726")} ${pc.bold("Next: write the product brief")}`,
24146
+ " Ask the user these questions. If you are a coding agent, suggest answers from the code and let the user confirm or correct them:",
24147
+ " 1. What does the app do?",
24148
+ " 2. Who is it for?",
24149
+ " 3. Does it charge money?",
24150
+ " 4. What data does it handle? Any of: personal, payments, health, legal, children, none",
24151
+ " 5. Where are its users? (countries or regions)",
24152
+ " Save the answers to .aura/answers.json in this shape:",
24153
+ ` ${EXAMPLE_ANSWERS}`,
24154
+ " Then run: npx getaura@latest init --yes --answers .aura/answers.json",
24155
+ ""
24156
+ ].join("\n");
24157
+ }
23945
24158
  async function initCommand(flags) {
23946
24159
  const tty = interactive() && !flags.yes;
23947
24160
  const cwd = process.cwd();
23948
- if (tty) p.intro(pc.bold(" Aura ") + pc.dim(" set up your repo and your coding agent"));
24161
+ if (tty) {
24162
+ process.stdout.write(`${banner()}
24163
+ `);
24164
+ p.intro(pc.dim("Set up your repo and your coding agent"));
24165
+ }
23949
24166
  if (!await isGitRepo(cwd)) {
23950
24167
  say.error("This folder isn\u2019t a git repository. Aura works on a git repo so every change can be a pull request.");
23951
24168
  if (tty && unwrap(await p.confirm({ message: "Run `git init` here now?" }))) {
@@ -23982,9 +24199,20 @@ async function initCommand(flags) {
23982
24199
  }
23983
24200
  config2.agents = agents;
23984
24201
  let key = flags.key ?? getApiKey();
23985
- if (!key && tty) {
23986
- const entered = unwrap(await p.password({ message: "Paste your Aura pilot key (leave empty to skip for now)", mask: "\u2022" }));
23987
- key = entered?.trim() || null;
24202
+ let explained = false;
24203
+ if (!key && !process.env.CI && !config2.noKey) {
24204
+ const outcome = await signInWithBrowser(getApiUrl(config2), { person: interactive(), waitSeconds: parseWait(flags.wait) });
24205
+ if (outcome.status === "complete") {
24206
+ key = outcome.key;
24207
+ delete config2.noKey;
24208
+ } else {
24209
+ if (outcome.status === "skipped") config2.noKey = true;
24210
+ if (!explainSignIn(outcome)) {
24211
+ if (outcome.status === "pending") return 1;
24212
+ say.dim("Continuing without a pilot key for now. Connect one later with `aura login`.");
24213
+ }
24214
+ explained = true;
24215
+ }
23988
24216
  }
23989
24217
  let client = AuraClient.from(config2);
23990
24218
  if (key) {
@@ -23997,20 +24225,20 @@ async function initCommand(flags) {
23997
24225
  say.warn(`${friendlyApiError(err)} Continuing without Aura\u2019s agent.`);
23998
24226
  client = new AuraClient(getApiUrl(config2), null);
23999
24227
  }
24000
- } else {
24228
+ } else if (!explained) {
24001
24229
  say.dim("No pilot key: Aura will still score your repo, with standard explanations.");
24002
24230
  }
24003
24231
  const ctx = await buildContext(root, { fast: true, noDownload: true }, config2);
24004
24232
  const briefPath = auraPath(root, "brief.md");
24005
24233
  let brief2 = null;
24006
24234
  if (flags.briefFile) {
24007
- brief2 = readFileSync14(flags.briefFile, "utf8");
24008
- } else if (existsSync15(briefPath) && !(tty && unwrap(await p.confirm({ message: "A product brief already exists. Draft a new one?", initialValue: false })))) {
24009
- brief2 = readFileSync14(briefPath, "utf8");
24235
+ brief2 = readFileSync15(flags.briefFile, "utf8");
24236
+ } else if (existsSync16(briefPath) && !(tty && unwrap(await p.confirm({ message: "A product brief already exists. Draft a new one?", initialValue: false })))) {
24237
+ brief2 = readFileSync15(briefPath, "utf8");
24010
24238
  } else {
24011
24239
  let answers = null;
24012
24240
  if (flags.answers) {
24013
- answers = OnboardingAnswersSchema.parse(JSON.parse(readFileSync14(flags.answers, "utf8")));
24241
+ answers = OnboardingAnswersSchema.parse(JSON.parse(readFileSync15(flags.answers, "utf8")));
24014
24242
  } else if (tty) {
24015
24243
  p.note("A few questions so Aura understands your app. Your answers go into the product brief.", "About your app");
24016
24244
  const whatItDoes = unwrap(await p.text({ message: "What does your app do?", placeholder: "e.g. Lets dog owners book and pay for walks" }));
@@ -24055,7 +24283,7 @@ async function initCommand(flags) {
24055
24283
  }
24056
24284
  brief2 ??= offlineBrief(answers, summary, ctx);
24057
24285
  } else {
24058
- say.warn("No answers given (non-interactive). Pass --answers <file.json> or --brief-file <path>, or run `aura init` in a terminal to write the brief.");
24286
+ console.log(briefHandoff());
24059
24287
  }
24060
24288
  }
24061
24289
  if (brief2) {
@@ -24094,7 +24322,7 @@ async function initCommand(flags) {
24094
24322
  }
24095
24323
 
24096
24324
  // src/commands/summary.ts
24097
- import path19 from "path";
24325
+ import path20 from "path";
24098
24326
  async function activity(root, since) {
24099
24327
  const log = await git(root, "log", `--since=${since.toISOString()}`, "--no-merges", "--format=%H%x09%an%x09%s");
24100
24328
  const commits = log.stdout.split("\n").filter(Boolean).slice(0, 300).map((l) => {
@@ -24151,7 +24379,7 @@ async function summaryCommand(flags) {
24151
24379
  const beforeIds = new Set(before ? allFindings(before).map((f) => f.id) : []);
24152
24380
  const nowIds = new Set(allFindings(now).map((f) => f.id));
24153
24381
  const req = {
24154
- repo: path19.basename(root),
24382
+ repo: path20.basename(root),
24155
24383
  since: since.toISOString(),
24156
24384
  until: until.toISOString(),
24157
24385
  product: productContext(root),
@@ -24198,6 +24426,7 @@ ${body}`);
24198
24426
  // src/index.ts
24199
24427
  var program = new Command();
24200
24428
  program.name("aura").description("Aura helps you and your coding agent follow best practices while you vibe code.").version(AURA_VERSION).showHelpAfterError();
24429
+ program.addHelpText("before", () => interactive() ? banner() : "");
24201
24430
  function action(fn) {
24202
24431
  return async (...args) => {
24203
24432
  try {
@@ -24213,7 +24442,7 @@ function action(fn) {
24213
24442
  }
24214
24443
  };
24215
24444
  }
24216
- program.command("init").description("Set up Aura: pick your agent, write the product brief, install the Aura skill and run the first scan").option("--agent <id...>", "coding agent(s): claude, codex, cursor, pi").option("-y, --yes", "no prompts; use detected defaults").option("--key <key>", "Aura pilot key").option("--brief-file <path>", "use this file as the product brief").option("--answers <path>", "JSON file with the setup answers").option("--no-scan", "skip the first scan").addOption(new Option("--telemetry", "share scores and action names with the Aura team")).addOption(new Option("--no-telemetry", "don\u2019t share anything")).action(action((flags) => initCommand(flags)));
24445
+ program.command("init").description("Set up Aura: pick your agent, write the product brief, install the Aura skill and run the first scan").option("--agent <id...>", "coding agent(s): claude, codex, cursor, pi").option("-y, --yes", "no prompts; use detected defaults").option("--key <key>", "Aura pilot key, for CI and scripts (otherwise a browser page asks for it)").option("--wait <seconds>", "how long to wait for the browser sign-in (default: until the link expires; 90 for agents)").option("--brief-file <path>", "use this file as the product brief").option("--answers <path>", "JSON file with the setup answers").option("--no-scan", "skip the first scan").addOption(new Option("--telemetry", "share scores and action names with the Aura team")).addOption(new Option("--no-telemetry", "don\u2019t share anything")).action(action((flags) => initCommand(flags)));
24217
24446
  program.command("scan").description("Run every check, show the Aura score and write .aura/report.md").option("--json", "print the full result as JSON").option("--no-explain", "skip plain-language explanations from Aura\u2019s agent").option("--fast", "skip type checking and git-history secret scanning").option("--ci", "CI mode: no spinners, PR comment output, exit 1 on a score drop").option("--base <ref>", "also scan this git ref and compare").option("--max-drop <n>", "allowed score drop before failing (default from .aura/config.json)").option("--comment-file <path>", "write a PR comment in markdown to this file").option("--no-download", "offline: don\u2019t download scanners or vulnerability data").action(action((flags) => scanCommand(flags)));
24218
24447
  program.command("next").description("Recommended next steps in priority order").option("--json", "print as JSON").option("--limit <n>", "how many to show").action(action((flags) => nextCommand(flags)));
24219
24448
  program.command("apply").argument("<action>", "action id, e.g. foundation, add-ci, install-skills").description("Make a template change on a new branch and open a pull request").option("--dry-run", "show what would change without changing anything").option("--no-pr", "commit on a local branch without pushing or opening a PR").option("-y, --yes", "don\u2019t ask for confirmation").option("--skills <names>", "for install-skills: comma-separated skill names").action(action((id, flags) => applyCommand(id, flags)));
@@ -24222,7 +24451,7 @@ program.command("inventory").description("Services and environment variables the
24222
24451
  program.command("guide").argument("[topic]", "guide to show, e.g. rotate-stripe-key").description("Step-by-step guides for things outside the code").option("--list", "list all guides").action(action((topic, flags) => guideCommand(topic, flags)));
24223
24452
  program.command("history").description("Aura score trend from past scans").option("--json", "print as JSON").action(action((flags) => historyCommand(flags)));
24224
24453
  program.command("summary").description("Plain-language summary of recent changes and the score trend").option("--days <n>", "how many days to cover", "7").option("--post", "post it as a GitHub issue").option("--json", "print as JSON").action(action((flags) => summaryCommand(flags)));
24225
- program.command("login").description("Save your Aura pilot key").option("--key <key>", "pilot key").action(action((flags) => loginCommand(flags)));
24454
+ program.command("login").description("Connect your Aura pilot key (opens a browser page to paste it)").option("--key <key>", "pilot key, for CI and scripts (AURA_KEY also works)").option("--paste", "paste the key in this terminal instead of the browser").option("--wait <seconds>", "how long to wait for the browser (default: until the link expires; 90 for agents)").action(action((flags) => loginCommand(flags)));
24226
24455
  program.command("whoami").description("Show your pilot key and agent budget").action(action(() => whoamiCommand()));
24227
24456
  program.command("config").argument("[key]", "telemetry, ciMaxDrop, ignore, unignore, apiUrl").argument("[value]").description("Read or change settings in .aura/config.json").action(action((key, value) => configCommand(key, value)));
24228
24457
  program.command("check-staged").description("Block a commit that adds secrets (used by the pre-commit hook)").action(action(() => checkStagedCommand()));