getaura 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/README.md +13 -1
- package/dist/index.js +318 -89
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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
|
|
13
|
-
import
|
|
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,
|
|
994
|
-
if (!
|
|
993
|
+
function getElementAtPath(obj, path21) {
|
|
994
|
+
if (!path21)
|
|
995
995
|
return obj;
|
|
996
|
-
return
|
|
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(
|
|
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(
|
|
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,
|
|
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 }, [...
|
|
1793
|
+
issue2.errors.map((issues) => processError({ issues }, [...path21, ...issue2.path]));
|
|
1794
1794
|
} else if (issue2.code === "invalid_key") {
|
|
1795
|
-
processError({ issues: issue2.issues }, [...
|
|
1795
|
+
processError({ issues: issue2.issues }, [...path21, ...issue2.path]);
|
|
1796
1796
|
} else if (issue2.code === "invalid_element") {
|
|
1797
|
-
processError({ issues: issue2.issues }, [...
|
|
1797
|
+
processError({ issues: issue2.issues }, [...path21, ...issue2.path]);
|
|
1798
1798
|
} else {
|
|
1799
|
-
const fullpath = [...
|
|
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,
|
|
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 }, [...
|
|
1842
|
+
issue2.errors.map((issues) => processError({ issues }, [...path21, ...issue2.path]));
|
|
1843
1843
|
} else if (issue2.code === "invalid_key") {
|
|
1844
|
-
processError({ issues: issue2.issues }, [...
|
|
1844
|
+
processError({ issues: issue2.issues }, [...path21, ...issue2.path]);
|
|
1845
1845
|
} else if (issue2.code === "invalid_element") {
|
|
1846
|
-
processError({ issues: issue2.issues }, [...
|
|
1846
|
+
processError({ issues: issue2.issues }, [...path21, ...issue2.path]);
|
|
1847
1847
|
} else {
|
|
1848
|
-
const fullpath = [...
|
|
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
|
|
1888
|
-
for (const seg of
|
|
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
|
|
18991
|
-
if (
|
|
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 (
|
|
18996
|
-
const key =
|
|
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 >=
|
|
19992
|
-
if (score >=
|
|
19993
|
-
if (score >=
|
|
19994
|
-
return { label: "Aura
|
|
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,11 +20421,11 @@ function pmCommands(pm) {
|
|
|
20400
20421
|
// package.json
|
|
20401
20422
|
var package_default = {
|
|
20402
20423
|
name: "getaura",
|
|
20403
|
-
version: "0.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: {
|
|
20407
|
-
aura: "
|
|
20428
|
+
aura: "dist/index.js"
|
|
20408
20429
|
},
|
|
20409
20430
|
files: [
|
|
20410
20431
|
"dist",
|
|
@@ -20417,7 +20438,7 @@ var package_default = {
|
|
|
20417
20438
|
license: "UNLICENSED",
|
|
20418
20439
|
repository: {
|
|
20419
20440
|
type: "git",
|
|
20420
|
-
url: "https://github.com/JohnFazio1/aura"
|
|
20441
|
+
url: "git+https://github.com/JohnFazio1/aura.git"
|
|
20421
20442
|
},
|
|
20422
20443
|
keywords: [
|
|
20423
20444
|
"vibe coding",
|
|
@@ -21516,7 +21537,7 @@ var AuraClient = class _AuraClient {
|
|
|
21516
21537
|
get hasKey() {
|
|
21517
21538
|
return Boolean(this.key);
|
|
21518
21539
|
}
|
|
21519
|
-
async request(method,
|
|
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/${
|
|
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:
|
|
21827
|
-
const
|
|
21828
|
-
const tsc =
|
|
21829
|
-
if (!
|
|
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
|
|
22730
|
-
import
|
|
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}
|
|
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**
|
|
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
|
|
23078
|
-
import
|
|
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 (
|
|
23388
|
+
if (existsSync13(file2)) {
|
|
23205
23389
|
try {
|
|
23206
|
-
const scan = JSON.parse(
|
|
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 &&
|
|
23421
|
+
if (!key && flags.paste && person) key = unwrap(await p.password({ message: "Paste your Aura pilot key", mask: "\u2022" }));
|
|
23233
23422
|
if (!key) {
|
|
23234
|
-
|
|
23235
|
-
|
|
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
|
|
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 =
|
|
23503
|
+
const dir = path15.join(templatesDir(), "guides");
|
|
23308
23504
|
return readdirSync4(dir).filter((f) => f.endsWith(".md")).map((f) => {
|
|
23309
|
-
const text =
|
|
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 &&
|
|
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 =
|
|
23429
|
-
const current2 =
|
|
23430
|
-
if (!/^\.env\*?$/m.test(current2))
|
|
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") :
|
|
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 =
|
|
23661
|
-
const checkTypes = plan.verifyTypes && scan.stack.typescript &&
|
|
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))
|
|
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
|
|
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
|
|
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 ??
|
|
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
|
|
23857
|
-
import
|
|
23858
|
-
import
|
|
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(
|
|
23861
|
-
const wt =
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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 (
|
|
23935
|
-
if (
|
|
23936
|
-
if (
|
|
23937
|
-
if (
|
|
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)
|
|
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
|
-
|
|
23986
|
-
|
|
23987
|
-
|
|
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 =
|
|
24008
|
-
} else if (
|
|
24009
|
-
brief2 =
|
|
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(
|
|
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
|
-
|
|
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
|
|
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:
|
|
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("
|
|
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()));
|