getaura 0.1.7 → 0.1.9
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 +983 -275
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/guides/github-security.md +2 -0
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 existsSync19, rmSync as rmSync6 } from "fs";
|
|
13
|
+
import path20 from "path";
|
|
14
14
|
|
|
15
15
|
// src/core/agents.ts
|
|
16
16
|
var AGENT_LAYOUT = {
|
|
@@ -1009,10 +1009,10 @@ function mergeDefs(...defs) {
|
|
|
1009
1009
|
function cloneDef(schema) {
|
|
1010
1010
|
return mergeDefs(schema._zod.def);
|
|
1011
1011
|
}
|
|
1012
|
-
function getElementAtPath(obj,
|
|
1013
|
-
if (!
|
|
1012
|
+
function getElementAtPath(obj, path27) {
|
|
1013
|
+
if (!path27)
|
|
1014
1014
|
return obj;
|
|
1015
|
-
return
|
|
1015
|
+
return path27.reduce((acc, key) => acc?.[key], obj);
|
|
1016
1016
|
}
|
|
1017
1017
|
function promiseAllObject(promisesObj) {
|
|
1018
1018
|
const keys = Object.keys(promisesObj);
|
|
@@ -1352,11 +1352,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
1352
1352
|
}
|
|
1353
1353
|
return false;
|
|
1354
1354
|
}
|
|
1355
|
-
function prefixIssues(
|
|
1355
|
+
function prefixIssues(path27, issues) {
|
|
1356
1356
|
return issues.map((iss) => {
|
|
1357
1357
|
var _a3;
|
|
1358
1358
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
1359
|
-
iss.path.unshift(
|
|
1359
|
+
iss.path.unshift(path27);
|
|
1360
1360
|
return iss;
|
|
1361
1361
|
});
|
|
1362
1362
|
}
|
|
@@ -1806,16 +1806,16 @@ function flattenError(error62, mapper = (issue2) => issue2.message) {
|
|
|
1806
1806
|
}
|
|
1807
1807
|
function formatError(error62, mapper = (issue2) => issue2.message) {
|
|
1808
1808
|
const fieldErrors = { _errors: [] };
|
|
1809
|
-
const processError = (error63,
|
|
1809
|
+
const processError = (error63, path27 = []) => {
|
|
1810
1810
|
for (const issue2 of error63.issues) {
|
|
1811
1811
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
1812
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
1812
|
+
issue2.errors.map((issues) => processError({ issues }, [...path27, ...issue2.path]));
|
|
1813
1813
|
} else if (issue2.code === "invalid_key") {
|
|
1814
|
-
processError({ issues: issue2.issues }, [...
|
|
1814
|
+
processError({ issues: issue2.issues }, [...path27, ...issue2.path]);
|
|
1815
1815
|
} else if (issue2.code === "invalid_element") {
|
|
1816
|
-
processError({ issues: issue2.issues }, [...
|
|
1816
|
+
processError({ issues: issue2.issues }, [...path27, ...issue2.path]);
|
|
1817
1817
|
} else {
|
|
1818
|
-
const fullpath = [...
|
|
1818
|
+
const fullpath = [...path27, ...issue2.path];
|
|
1819
1819
|
if (fullpath.length === 0) {
|
|
1820
1820
|
fieldErrors._errors.push(mapper(issue2));
|
|
1821
1821
|
} else {
|
|
@@ -1854,17 +1854,17 @@ function formatError(error62, mapper = (issue2) => issue2.message) {
|
|
|
1854
1854
|
}
|
|
1855
1855
|
function treeifyError(error62, mapper = (issue2) => issue2.message) {
|
|
1856
1856
|
const result = { errors: [] };
|
|
1857
|
-
const processError = (error63,
|
|
1857
|
+
const processError = (error63, path27 = []) => {
|
|
1858
1858
|
var _a3;
|
|
1859
1859
|
for (const issue2 of error63.issues) {
|
|
1860
1860
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
1861
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
1861
|
+
issue2.errors.map((issues) => processError({ issues }, [...path27, ...issue2.path]));
|
|
1862
1862
|
} else if (issue2.code === "invalid_key") {
|
|
1863
|
-
processError({ issues: issue2.issues }, [...
|
|
1863
|
+
processError({ issues: issue2.issues }, [...path27, ...issue2.path]);
|
|
1864
1864
|
} else if (issue2.code === "invalid_element") {
|
|
1865
|
-
processError({ issues: issue2.issues }, [...
|
|
1865
|
+
processError({ issues: issue2.issues }, [...path27, ...issue2.path]);
|
|
1866
1866
|
} else {
|
|
1867
|
-
const fullpath = [...
|
|
1867
|
+
const fullpath = [...path27, ...issue2.path];
|
|
1868
1868
|
if (fullpath.length === 0) {
|
|
1869
1869
|
result.errors.push(mapper(issue2));
|
|
1870
1870
|
continue;
|
|
@@ -1903,8 +1903,8 @@ function treeifyError(error62, mapper = (issue2) => issue2.message) {
|
|
|
1903
1903
|
}
|
|
1904
1904
|
function toDotPath(_path) {
|
|
1905
1905
|
const segs = [];
|
|
1906
|
-
const
|
|
1907
|
-
for (const seg of
|
|
1906
|
+
const path27 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
1907
|
+
for (const seg of path27) {
|
|
1908
1908
|
if (typeof seg === "number")
|
|
1909
1909
|
segs.push(`[${seg}]`);
|
|
1910
1910
|
else if (typeof seg === "symbol")
|
|
@@ -2038,18 +2038,18 @@ var validateAsync = async (schema, value, _ctx) => {
|
|
|
2038
2038
|
return result.issues.length === 0;
|
|
2039
2039
|
};
|
|
2040
2040
|
var _encode = (_Err) => {
|
|
2041
|
-
const
|
|
2041
|
+
const parse5 = _parse(_Err);
|
|
2042
2042
|
const fn = (schema, value, _ctx, _params) => {
|
|
2043
2043
|
const ctx = _ctx ? { ..._ctx, direction: "backward" } : { direction: "backward" };
|
|
2044
|
-
return
|
|
2044
|
+
return parse5(schema, value, ctx, finalizeParams(fn, _params));
|
|
2045
2045
|
};
|
|
2046
2046
|
return fn;
|
|
2047
2047
|
};
|
|
2048
2048
|
var encode = /* @__PURE__ */ _encode($ZodRealError);
|
|
2049
2049
|
var _decode = (_Err) => {
|
|
2050
|
-
const
|
|
2050
|
+
const parse5 = _parse(_Err);
|
|
2051
2051
|
const fn = (schema, value, _ctx, _params) => {
|
|
2052
|
-
return
|
|
2052
|
+
return parse5(schema, value, _ctx, finalizeParams(fn, _params));
|
|
2053
2053
|
};
|
|
2054
2054
|
return fn;
|
|
2055
2055
|
};
|
|
@@ -19006,13 +19006,13 @@ function resolveRef(ref, ctx) {
|
|
|
19006
19006
|
if (!ref.startsWith("#")) {
|
|
19007
19007
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
19008
19008
|
}
|
|
19009
|
-
const
|
|
19010
|
-
if (
|
|
19009
|
+
const path27 = ref.slice(1).split("/").filter(Boolean);
|
|
19010
|
+
if (path27.length === 0) {
|
|
19011
19011
|
return ctx.rootSchema;
|
|
19012
19012
|
}
|
|
19013
19013
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
19014
|
-
if (
|
|
19015
|
-
const key =
|
|
19014
|
+
if (path27[0] === defsKey) {
|
|
19015
|
+
const key = path27[1] === void 0 ? void 0 : decodeJSONPointerSegment(path27[1]);
|
|
19016
19016
|
if (!key || !ctx.defs[key]) {
|
|
19017
19017
|
throw new Error(`Reference not found: ${ref}`);
|
|
19018
19018
|
}
|
|
@@ -19975,6 +19975,7 @@ var FIX_OUTCOMES = [
|
|
|
19975
19975
|
"cancelled",
|
|
19976
19976
|
"task_written",
|
|
19977
19977
|
"shown",
|
|
19978
|
+
"settings_changed",
|
|
19978
19979
|
"merged",
|
|
19979
19980
|
"closed",
|
|
19980
19981
|
"resolved",
|
|
@@ -20001,7 +20002,7 @@ var TelemetryRequest = external_exports.object({
|
|
|
20001
20002
|
actionId: external_exports.string().regex(/^[a-z0-9-]{1,80}$/).optional(),
|
|
20002
20003
|
step: external_exports.enum(SETUP_STEPS).optional(),
|
|
20003
20004
|
outcome: external_exports.enum(FIX_OUTCOMES).optional(),
|
|
20004
|
-
delivery: external_exports.enum(["pr", "agent-fix", "agent-task", "guide", "checklist"]).optional(),
|
|
20005
|
+
delivery: external_exports.enum(["pr", "agent-fix", "agent-task", "guide", "checklist", "settings"]).optional(),
|
|
20005
20006
|
/** Whether the user picked it from the next-steps checklist or ran the command. */
|
|
20006
20007
|
via: external_exports.enum(["checklist", "command"]).optional(),
|
|
20007
20008
|
durationMs: external_exports.number().int().nonnegative().max(864e5).optional(),
|
|
@@ -20077,23 +20078,9 @@ function grade(score) {
|
|
|
20077
20078
|
return { label: "Generational aura debt", sentence: "You have generational aura debt", color: "red" };
|
|
20078
20079
|
}
|
|
20079
20080
|
|
|
20080
|
-
// src/templates.ts
|
|
20081
|
-
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
20082
|
-
import path4 from "path";
|
|
20083
|
-
|
|
20084
|
-
// src/core/config.ts
|
|
20085
|
-
import { chmodSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
20086
|
-
import os from "os";
|
|
20087
|
-
import path2 from "path";
|
|
20088
|
-
|
|
20089
|
-
// src/core/repo.ts
|
|
20090
|
-
import { existsSync, readdirSync, readFileSync, statSync } from "fs";
|
|
20091
|
-
import path from "path";
|
|
20092
|
-
import { fileURLToPath } from "url";
|
|
20093
|
-
|
|
20094
20081
|
// src/core/exec.ts
|
|
20095
20082
|
import { spawn, spawnSync } from "child_process";
|
|
20096
|
-
var WINDOWS_PROGRAMS = /* @__PURE__ */ new Set(["git", "gh", "node", "tar", "where"]);
|
|
20083
|
+
var WINDOWS_PROGRAMS = /* @__PURE__ */ new Set(["git", "gh", "node", "tar", "where", "powershell"]);
|
|
20097
20084
|
function quoteForCmd(arg) {
|
|
20098
20085
|
return /^[\w@%+=:,./\\-]+$/.test(arg) ? arg : `"${arg.replace(/"/g, '\\"')}"`;
|
|
20099
20086
|
}
|
|
@@ -20139,7 +20126,14 @@ async function commandExists(cmd) {
|
|
|
20139
20126
|
return probe.code === 0;
|
|
20140
20127
|
}
|
|
20141
20128
|
|
|
20129
|
+
// src/core/git.ts
|
|
20130
|
+
import { mkdirSync, rmSync, writeFileSync } from "fs";
|
|
20131
|
+
import path2 from "path";
|
|
20132
|
+
|
|
20142
20133
|
// src/core/repo.ts
|
|
20134
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "fs";
|
|
20135
|
+
import path from "path";
|
|
20136
|
+
import { fileURLToPath } from "url";
|
|
20143
20137
|
var AURA_DIR = ".aura";
|
|
20144
20138
|
var ALWAYS_IGNORED = ["node_modules", ".next", ".git", "dist", "build", "out", ".vercel", ".turbo", "coverage", ".aura"];
|
|
20145
20139
|
async function findRepoRoot(cwd = process.cwd()) {
|
|
@@ -20204,7 +20198,558 @@ var SOURCE_EXT = /\.(?:[cm]?[jt]sx?)$/;
|
|
|
20204
20198
|
var isSourceFile = (f) => SOURCE_EXT.test(f) && !f.endsWith(".d.ts");
|
|
20205
20199
|
var isTestFile = (f) => /(?:^|\/)(?:__tests__|tests?|e2e)\/|\.(?:test|spec)\.[cm]?[jt]sx?$/.test(f);
|
|
20206
20200
|
|
|
20201
|
+
// src/core/git.ts
|
|
20202
|
+
async function git(root, ...args) {
|
|
20203
|
+
return run("git", args, { cwd: root });
|
|
20204
|
+
}
|
|
20205
|
+
function ghInstallHint(platform = process.platform) {
|
|
20206
|
+
const how = platform === "win32" ? "Install it with `winget install --id GitHub.cli`" : platform === "darwin" ? "Install it with `brew install gh` (or download it from https://cli.github.com)" : "Install it from https://cli.github.com";
|
|
20207
|
+
return `${how}, open a new terminal, then run \`gh auth login\`.`;
|
|
20208
|
+
}
|
|
20209
|
+
async function ghStatus(root) {
|
|
20210
|
+
const v = await run("gh", ["--version"]);
|
|
20211
|
+
if (v.code !== 0) return { installed: false, authed: false, hasRemote: false };
|
|
20212
|
+
const auth = await run("gh", ["auth", "status"], { cwd: root });
|
|
20213
|
+
const remote = await git(root, "remote", "get-url", "origin");
|
|
20214
|
+
return { installed: true, authed: auth.code === 0, hasRemote: remote.code === 0 && /github\.com/.test(remote.stdout) };
|
|
20215
|
+
}
|
|
20216
|
+
var AURA_OWNED = /^(?:\.aura\/|\.claude\/skills\/aura\/|\.agents\/skills\/aura(?:-(?:scan|next|fix|setup))?\/|\.cursor\/commands\/aura-[a-z]+\.md$|\.pi\/prompts\/aura-[a-z]+\.md$|\.claude\/settings\.json$)/;
|
|
20217
|
+
async function dirtyFiles(root) {
|
|
20218
|
+
const r = await git(root, "status", "--porcelain", "-uall");
|
|
20219
|
+
return r.stdout.split("\n").filter(Boolean).map((l) => l.slice(3).replace(/^"|"$/g, "").split(" -> ").pop()).filter((f) => !AURA_OWNED.test(f));
|
|
20220
|
+
}
|
|
20221
|
+
async function currentBranch(root) {
|
|
20222
|
+
return (await git(root, "rev-parse", "--abbrev-ref", "HEAD")).stdout.trim();
|
|
20223
|
+
}
|
|
20224
|
+
async function freeBranchName(root, base) {
|
|
20225
|
+
for (let i = 1; i < 50; i++) {
|
|
20226
|
+
const name = i === 1 ? base : `${base}-${i}`;
|
|
20227
|
+
const local = await git(root, "rev-parse", "--verify", "--quiet", `refs/heads/${name}`);
|
|
20228
|
+
const remote = await git(root, "ls-remote", "--exit-code", "--heads", "origin", name);
|
|
20229
|
+
if (local.code !== 0 && remote.code !== 0) return name;
|
|
20230
|
+
}
|
|
20231
|
+
return `${base}-${Date.now()}`;
|
|
20232
|
+
}
|
|
20233
|
+
function writeChanges(root, changes) {
|
|
20234
|
+
for (const c of changes) {
|
|
20235
|
+
const full = path2.join(root, c.path);
|
|
20236
|
+
if (!isInside(root, full)) throw new Error(`Refusing to write outside the repo: ${c.path}`);
|
|
20237
|
+
if (c.content === null) rmSync(full, { force: true });
|
|
20238
|
+
else {
|
|
20239
|
+
mkdirSync(path2.dirname(full), { recursive: true });
|
|
20240
|
+
writeFileSync(full, c.content);
|
|
20241
|
+
}
|
|
20242
|
+
}
|
|
20243
|
+
}
|
|
20244
|
+
async function commitPushPr(root, opts) {
|
|
20245
|
+
const commit = await git(root, "commit", "-m", opts.title, "-m", "Opened by Aura (https://github.com/JohnFazio1/aura).");
|
|
20246
|
+
const base = { branch: opts.branch, url: null };
|
|
20247
|
+
if (commit.code !== 0) {
|
|
20248
|
+
const out = commit.stderr || commit.stdout;
|
|
20249
|
+
const error62 = /tell me who you are|user\.email/i.test(out) ? 'git doesn\u2019t know your name and email yet. Run these two commands:\n git config --global user.name "Your Name"\n git config --global user.email "you@example.com"' : out;
|
|
20250
|
+
return { ...base, committed: false, pushed: false, error: error62 };
|
|
20251
|
+
}
|
|
20252
|
+
if (!opts.openPr) return { ...base, committed: true, pushed: false };
|
|
20253
|
+
const push = await git(root, "push", "-u", "origin", opts.branch);
|
|
20254
|
+
if (push.code !== 0) return { ...base, committed: true, pushed: false, error: push.stderr };
|
|
20255
|
+
const pr = await run("gh", ["pr", "create", "--base", opts.base, "--head", opts.branch, "--title", opts.title, "--body-file", "-"], {
|
|
20256
|
+
cwd: root,
|
|
20257
|
+
input: opts.body
|
|
20258
|
+
});
|
|
20259
|
+
if (pr.code !== 0) return { ...base, committed: true, pushed: true, error: pr.stderr };
|
|
20260
|
+
return { ...base, url: pr.stdout.trim().split("\n").pop() ?? null, committed: true, pushed: true };
|
|
20261
|
+
}
|
|
20262
|
+
|
|
20263
|
+
// src/connectors/github.ts
|
|
20264
|
+
var PAID_SECRETS = "needs GitHub Secret Protection (a paid add-on) on private repositories";
|
|
20265
|
+
var PAID_RULES = "needs a paid GitHub plan (Pro is about $4/month) on private repositories";
|
|
20266
|
+
var PUBLIC_ONLY = "only available on public repositories";
|
|
20267
|
+
var RULESET_NAME = "Protect main (Aura)";
|
|
20268
|
+
var parse3 = (r) => {
|
|
20269
|
+
try {
|
|
20270
|
+
return JSON.parse(r.stdout);
|
|
20271
|
+
} catch {
|
|
20272
|
+
return null;
|
|
20273
|
+
}
|
|
20274
|
+
};
|
|
20275
|
+
var needsUpgrade = (r) => /Upgrade to GitHub Pro|HTTP 403|not available|Advanced Security/i.test(r.stderr + r.stdout);
|
|
20276
|
+
var done = (r) => r.code === 0 ? { ok: true } : { ok: false };
|
|
20277
|
+
async function ciChecks(ctx) {
|
|
20278
|
+
const r = await ctx.gh([
|
|
20279
|
+
"api",
|
|
20280
|
+
`repos/${ctx.repo}/commits/${ctx.defaultBranch}/check-runs`,
|
|
20281
|
+
"--jq",
|
|
20282
|
+
'.check_runs[] | select(.app.slug == "github-actions") | .name'
|
|
20283
|
+
]);
|
|
20284
|
+
return r.code === 0 ? [
|
|
20285
|
+
...new Set(
|
|
20286
|
+
r.stdout.split("\n").map((s) => s.trim()).filter(Boolean)
|
|
20287
|
+
)
|
|
20288
|
+
] : [];
|
|
20289
|
+
}
|
|
20290
|
+
function protectMainRuleset(checks) {
|
|
20291
|
+
return {
|
|
20292
|
+
name: RULESET_NAME,
|
|
20293
|
+
target: "branch",
|
|
20294
|
+
enforcement: "active",
|
|
20295
|
+
conditions: { ref_name: { include: ["~DEFAULT_BRANCH"], exclude: [] } },
|
|
20296
|
+
rules: [
|
|
20297
|
+
{ type: "deletion" },
|
|
20298
|
+
{ type: "non_fast_forward" },
|
|
20299
|
+
{
|
|
20300
|
+
// No approvals required, so someone working alone can still merge their own pull requests.
|
|
20301
|
+
type: "pull_request",
|
|
20302
|
+
parameters: {
|
|
20303
|
+
required_approving_review_count: 0,
|
|
20304
|
+
dismiss_stale_reviews_on_push: false,
|
|
20305
|
+
require_code_owner_review: false,
|
|
20306
|
+
require_last_push_approval: false,
|
|
20307
|
+
required_review_thread_resolution: false
|
|
20308
|
+
}
|
|
20309
|
+
},
|
|
20310
|
+
...checks.length ? [
|
|
20311
|
+
{
|
|
20312
|
+
type: "required_status_checks",
|
|
20313
|
+
parameters: { strict_required_status_checks_policy: false, required_status_checks: checks.map((context) => ({ context })) }
|
|
20314
|
+
}
|
|
20315
|
+
] : []
|
|
20316
|
+
]
|
|
20317
|
+
};
|
|
20318
|
+
}
|
|
20319
|
+
var secretState = (key) => async (ctx) => {
|
|
20320
|
+
const security = parse3(await ctx.get(`repos/${ctx.repo}`))?.security_and_analysis;
|
|
20321
|
+
if (security?.[key]?.status === "enabled") return { state: "on" };
|
|
20322
|
+
return security?.[key] || !ctx.isPrivate ? { state: "off" } : { state: "unavailable", why: PAID_SECRETS };
|
|
20323
|
+
};
|
|
20324
|
+
var enableSecret = (key) => async (ctx) => {
|
|
20325
|
+
const r = await ctx.gh(
|
|
20326
|
+
["api", "-X", "PATCH", `repos/${ctx.repo}`, "--input", "-"],
|
|
20327
|
+
JSON.stringify({ security_and_analysis: { [key]: { status: "enabled" } } })
|
|
20328
|
+
);
|
|
20329
|
+
return r.code === 0 ? { ok: true } : { ok: false, unavailable: ctx.isPrivate && needsUpgrade(r) ? PAID_SECRETS : void 0 };
|
|
20330
|
+
};
|
|
20331
|
+
var github = {
|
|
20332
|
+
id: "github",
|
|
20333
|
+
name: "GitHub",
|
|
20334
|
+
actionId: "github-security",
|
|
20335
|
+
// Offered for every repo; connecting finds out whether it's on GitHub.
|
|
20336
|
+
detect: () => true,
|
|
20337
|
+
async connect(root, fake) {
|
|
20338
|
+
const cli = fake;
|
|
20339
|
+
const gh = cli ?? ((args, input2) => run("gh", args, { cwd: root, input: input2, timeoutMs: 3e4 }));
|
|
20340
|
+
if (!cli) {
|
|
20341
|
+
const status = await ghStatus(root);
|
|
20342
|
+
if (!status.installed) return { ok: false, problem: `The GitHub CLI isn\u2019t installed. ${ghInstallHint()}`, errorType: "CliNotInstalled" };
|
|
20343
|
+
if (!status.authed) return { ok: false, problem: "The GitHub CLI isn\u2019t signed in. Run `gh auth login`, then try again.", errorType: "CliNotSignedIn" };
|
|
20344
|
+
if (!status.hasRemote)
|
|
20345
|
+
return { ok: false, problem: "This repo isn\u2019t on GitHub yet. Push it to GitHub first, for example with `gh repo create`.", errorType: "NoProject" };
|
|
20346
|
+
}
|
|
20347
|
+
const info = parse3(await gh(["repo", "view", "--json", "nameWithOwner,isPrivate,defaultBranchRef,viewerPermission"]));
|
|
20348
|
+
if (!info?.nameWithOwner) return { ok: false, problem: "Couldn\u2019t read this repo from GitHub. Check `gh repo view` works here.", errorType: "NoProject" };
|
|
20349
|
+
if (info.viewerPermission !== "ADMIN")
|
|
20350
|
+
return {
|
|
20351
|
+
ok: false,
|
|
20352
|
+
problem: `Changing these settings needs admin access to ${info.nameWithOwner}. Ask the repo\u2019s owner to run \`aura apply github-security\`.`,
|
|
20353
|
+
errorType: "NotAdmin"
|
|
20354
|
+
};
|
|
20355
|
+
const cache = /* @__PURE__ */ new Map();
|
|
20356
|
+
const ctx = {
|
|
20357
|
+
repo: info.nameWithOwner,
|
|
20358
|
+
isPrivate: Boolean(info.isPrivate),
|
|
20359
|
+
defaultBranch: info.defaultBranchRef?.name || "main",
|
|
20360
|
+
gh,
|
|
20361
|
+
get: (path27) => {
|
|
20362
|
+
if (!cache.has(path27)) cache.set(path27, gh(["api", path27]));
|
|
20363
|
+
return cache.get(path27);
|
|
20364
|
+
}
|
|
20365
|
+
};
|
|
20366
|
+
return { ok: true, ctx, target: `${ctx.repo} ${ctx.isPrivate ? "(private)" : "(public)"}` };
|
|
20367
|
+
},
|
|
20368
|
+
settings: [
|
|
20369
|
+
{
|
|
20370
|
+
id: "dependabot-alerts",
|
|
20371
|
+
label: "Dependabot alerts (warns about vulnerable packages)",
|
|
20372
|
+
impact: "GitHub emails you when a package your app uses has a known security hole.",
|
|
20373
|
+
read: async (ctx) => (await ctx.get(`repos/${ctx.repo}/vulnerability-alerts`)).code === 0 ? { state: "on" } : { state: "off" },
|
|
20374
|
+
enable: async (ctx) => done(await ctx.gh(["api", "-X", "PUT", `repos/${ctx.repo}/vulnerability-alerts`]))
|
|
20375
|
+
},
|
|
20376
|
+
{
|
|
20377
|
+
id: "dependabot-fixes",
|
|
20378
|
+
label: "Dependabot security fixes (opens PRs to update them)",
|
|
20379
|
+
impact: "GitHub opens pull requests that update vulnerable packages. Nothing merges without you.",
|
|
20380
|
+
read: async (ctx) => parse3(await ctx.get(`repos/${ctx.repo}/automated-security-fixes`))?.enabled ? { state: "on" } : { state: "off" },
|
|
20381
|
+
enable: async (ctx) => done(await ctx.gh(["api", "-X", "PUT", `repos/${ctx.repo}/automated-security-fixes`]))
|
|
20382
|
+
},
|
|
20383
|
+
{
|
|
20384
|
+
id: "secret-scanning",
|
|
20385
|
+
label: "Secret scanning (finds keys pushed to GitHub)",
|
|
20386
|
+
impact: "GitHub alerts you when a key or password is pushed to the repo.",
|
|
20387
|
+
read: secretState("secret_scanning"),
|
|
20388
|
+
enable: enableSecret("secret_scanning")
|
|
20389
|
+
},
|
|
20390
|
+
{
|
|
20391
|
+
id: "push-protection",
|
|
20392
|
+
label: "Push protection (blocks pushes that contain a key)",
|
|
20393
|
+
impact: "A push that contains a key is refused until you remove it.",
|
|
20394
|
+
read: secretState("secret_scanning_push_protection"),
|
|
20395
|
+
enable: enableSecret("secret_scanning_push_protection")
|
|
20396
|
+
},
|
|
20397
|
+
{
|
|
20398
|
+
id: "private-reporting",
|
|
20399
|
+
label: "Private vulnerability reporting",
|
|
20400
|
+
impact: "Security researchers can report a problem to you privately instead of in a public issue.",
|
|
20401
|
+
read: async (ctx) => ctx.isPrivate ? { state: "unavailable", why: PUBLIC_ONLY } : parse3(await ctx.get(`repos/${ctx.repo}/private-vulnerability-reporting`))?.enabled ? { state: "on" } : { state: "off" },
|
|
20402
|
+
enable: async (ctx) => done(await ctx.gh(["api", "-X", "PUT", `repos/${ctx.repo}/private-vulnerability-reporting`]))
|
|
20403
|
+
},
|
|
20404
|
+
{
|
|
20405
|
+
id: "protect-main",
|
|
20406
|
+
label: "Protect the main branch (pull requests and passing checks required, no force pushes or deletion)",
|
|
20407
|
+
impact: "You can\u2019t push straight to main any more: changes go through pull requests (you can still merge your own) once the checks pass.",
|
|
20408
|
+
async read(ctx) {
|
|
20409
|
+
const rules = parse3(await ctx.get(`repos/${ctx.repo}/rules/branches/${ctx.defaultBranch}`));
|
|
20410
|
+
const protection = await ctx.get(`repos/${ctx.repo}/branches/${ctx.defaultBranch}/protection`);
|
|
20411
|
+
if (Array.isArray(rules) && rules.some((r) => r.type === "pull_request") || protection.code === 0) return { state: "on" };
|
|
20412
|
+
if (ctx.isPrivate && /Upgrade to GitHub Pro/i.test(protection.stderr)) return { state: "unavailable", why: PAID_RULES };
|
|
20413
|
+
return { state: "off" };
|
|
20414
|
+
},
|
|
20415
|
+
async enable(ctx) {
|
|
20416
|
+
const r = await ctx.gh(["api", "-X", "POST", `repos/${ctx.repo}/rulesets`, "--input", "-"], JSON.stringify(protectMainRuleset(await ciChecks(ctx))));
|
|
20417
|
+
return r.code === 0 ? { ok: true } : { ok: false, unavailable: ctx.isPrivate && needsUpgrade(r) ? PAID_RULES : void 0 };
|
|
20418
|
+
}
|
|
20419
|
+
}
|
|
20420
|
+
],
|
|
20421
|
+
async explain(ctx, toTurnOn) {
|
|
20422
|
+
if (!toTurnOn.includes("protect-main")) return [];
|
|
20423
|
+
const checks = await ciChecks(ctx);
|
|
20424
|
+
return checks.length ? [`Pull requests to main will need these checks to pass: ${checks.join(", ")}.`] : [];
|
|
20425
|
+
},
|
|
20426
|
+
async leftForYou(ctx, results) {
|
|
20427
|
+
const left = ["Turn on two-factor login for your GitHub account: https://github.com/settings/security"];
|
|
20428
|
+
const r = await ctx.gh(["api", `repos/${ctx.repo}/collaborators`, "--jq", '.[] | "\\(.login) (\\(.role_name))"']);
|
|
20429
|
+
const people = r.code === 0 ? r.stdout.split("\n").filter(Boolean) : [];
|
|
20430
|
+
if (people.length)
|
|
20431
|
+
left.push(`People with access: ${people.join(", ")}. Remove anyone who no longer needs it: https://github.com/${ctx.repo}/settings/access`);
|
|
20432
|
+
if (results.some((x) => x.id === "secret-scanning" && x.result === "unavailable"))
|
|
20433
|
+
left.push("Aura\u2019s own secret check still runs on every `aura scan`, and before each commit if you add the pre-commit hook.");
|
|
20434
|
+
return left;
|
|
20435
|
+
}
|
|
20436
|
+
};
|
|
20437
|
+
|
|
20438
|
+
// src/connectors/supabase.ts
|
|
20439
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
20440
|
+
import os from "os";
|
|
20441
|
+
import path3 from "path";
|
|
20442
|
+
var TOKEN = /^sbp_(?:oauth_|v0_)?[a-f0-9]{40}$/;
|
|
20443
|
+
function decodeToken(raw) {
|
|
20444
|
+
const value = raw?.trim() ?? "";
|
|
20445
|
+
const token = value.startsWith("go-keyring-base64:") ? Buffer.from(value.slice("go-keyring-base64:".length), "base64").toString("utf8").trim() : value;
|
|
20446
|
+
return TOKEN.test(token) ? token : null;
|
|
20447
|
+
}
|
|
20448
|
+
function windowsCredentialScript(target) {
|
|
20449
|
+
return `
|
|
20450
|
+
$sig = @'
|
|
20451
|
+
[DllImport("advapi32.dll", CharSet=CharSet.Unicode, SetLastError=true)] public static extern bool CredRead(string target, int type, int flags, out IntPtr cred);
|
|
20452
|
+
[DllImport("advapi32.dll")] public static extern void CredFree(IntPtr cred);
|
|
20453
|
+
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] public struct CREDENTIAL { public int Flags; public int Type; public string TargetName; public string Comment; public long LastWritten; public int CredentialBlobSize; public IntPtr CredentialBlob; public int Persist; public int AttributeCount; public IntPtr Attributes; public string TargetAlias; public string UserName; }
|
|
20454
|
+
'@
|
|
20455
|
+
Add-Type -MemberDefinition $sig -Namespace AuraCred -Name Win
|
|
20456
|
+
$p = [IntPtr]::Zero
|
|
20457
|
+
if ([AuraCred.Win]::CredRead('${target}', 1, 0, [ref]$p)) {
|
|
20458
|
+
$c = [Runtime.InteropServices.Marshal]::PtrToStructure($p, [type][AuraCred.Win+CREDENTIAL])
|
|
20459
|
+
$b = New-Object byte[] $c.CredentialBlobSize
|
|
20460
|
+
[Runtime.InteropServices.Marshal]::Copy($c.CredentialBlob, $b, 0, $b.Length)
|
|
20461
|
+
[Text.Encoding]::UTF8.GetString($b)
|
|
20462
|
+
[AuraCred.Win]::CredFree($p)
|
|
20463
|
+
}`;
|
|
20464
|
+
}
|
|
20465
|
+
async function supabaseToken() {
|
|
20466
|
+
const fromEnv = decodeToken(process.env.SUPABASE_ACCESS_TOKEN);
|
|
20467
|
+
if (fromEnv) return fromEnv;
|
|
20468
|
+
const home = path3.join(os.homedir(), ".supabase");
|
|
20469
|
+
const profile = process.env.SUPABASE_PROFILE || (existsSync2(path3.join(home, "profile")) ? readFileSync2(path3.join(home, "profile"), "utf8").trim() : "") || "supabase";
|
|
20470
|
+
for (const account of [profile, "access-token"]) {
|
|
20471
|
+
const r = process.platform === "darwin" ? await run("security", ["find-generic-password", "-s", "Supabase CLI", "-a", account, "-w"]) : process.platform === "win32" ? await run("powershell", [
|
|
20472
|
+
"-NoProfile",
|
|
20473
|
+
"-NonInteractive",
|
|
20474
|
+
"-EncodedCommand",
|
|
20475
|
+
Buffer.from(windowsCredentialScript(`Supabase CLI:${account}`), "utf16le").toString("base64")
|
|
20476
|
+
]) : await run("secret-tool", ["lookup", "service", "Supabase CLI", "username", account]);
|
|
20477
|
+
const token = r.code === 0 ? decodeToken(r.stdout) : null;
|
|
20478
|
+
if (token) return token;
|
|
20479
|
+
}
|
|
20480
|
+
const file2 = path3.join(home, "access-token");
|
|
20481
|
+
return existsSync2(file2) ? decodeToken(readFileSync2(file2, "utf8")) : null;
|
|
20482
|
+
}
|
|
20483
|
+
var realApi = (token) => async (method, endpoint, body) => {
|
|
20484
|
+
const res = await fetch(`https://api.supabase.com${endpoint}`, {
|
|
20485
|
+
method,
|
|
20486
|
+
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
20487
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
20488
|
+
signal: AbortSignal.timeout(3e4)
|
|
20489
|
+
});
|
|
20490
|
+
return { status: res.status, json: await res.json().catch(() => null) };
|
|
20491
|
+
};
|
|
20492
|
+
var PRO_ONLY = "needs a paid Supabase plan (Pro or higher)";
|
|
20493
|
+
var authConfig = (ctx) => ctx.get(`/v1/projects/${ctx.ref}/config/auth`);
|
|
20494
|
+
async function patchAuth(ctx, body) {
|
|
20495
|
+
const r = await ctx.api("PATCH", `/v1/projects/${ctx.ref}/config/auth`, body);
|
|
20496
|
+
if (r.status >= 200 && r.status < 300) return { ok: true };
|
|
20497
|
+
return { ok: false, unavailable: /plan|pro\b|upgrade|paid/i.test(JSON.stringify(r.json ?? "")) ? PRO_ONLY : void 0 };
|
|
20498
|
+
}
|
|
20499
|
+
var authFlag = (check2) => async (ctx) => check2(await authConfig(ctx) ?? {}) ? { state: "on" } : { state: "off" };
|
|
20500
|
+
async function advisorProblems(ctx) {
|
|
20501
|
+
const lints = (await ctx.get(`/v1/projects/${ctx.ref}/advisors/security`))?.lints ?? [];
|
|
20502
|
+
return lints.filter((l) => l.level === "ERROR" || l.level === "WARN");
|
|
20503
|
+
}
|
|
20504
|
+
var supabase = {
|
|
20505
|
+
id: "supabase",
|
|
20506
|
+
name: "Supabase",
|
|
20507
|
+
actionId: "supabase-security",
|
|
20508
|
+
detect: (scan) => scan.stack.services.includes("Supabase"),
|
|
20509
|
+
async connect(root, fake) {
|
|
20510
|
+
let api = fake;
|
|
20511
|
+
if (!api) {
|
|
20512
|
+
const token = await supabaseToken();
|
|
20513
|
+
if (!token)
|
|
20514
|
+
return {
|
|
20515
|
+
ok: false,
|
|
20516
|
+
problem: "Aura couldn\u2019t find your Supabase login. Install the Supabase CLI and run `supabase login`, then try again.",
|
|
20517
|
+
errorType: "CliNotSignedIn"
|
|
20518
|
+
};
|
|
20519
|
+
api = realApi(token);
|
|
20520
|
+
}
|
|
20521
|
+
const refFile = path3.join(root, "supabase", ".temp", "project-ref");
|
|
20522
|
+
const ref = process.env.SUPABASE_PROJECT_ID || (existsSync2(refFile) ? readFileSync2(refFile, "utf8").trim() : "");
|
|
20523
|
+
if (!/^[a-z]{20}$/.test(ref))
|
|
20524
|
+
return { ok: false, problem: "This folder isn\u2019t linked to a Supabase project yet. Run `supabase link`, then try again.", errorType: "NoProject" };
|
|
20525
|
+
const cache = /* @__PURE__ */ new Map();
|
|
20526
|
+
const call = api;
|
|
20527
|
+
const ctx = {
|
|
20528
|
+
ref,
|
|
20529
|
+
api: call,
|
|
20530
|
+
get: (endpoint) => {
|
|
20531
|
+
if (!cache.has(endpoint))
|
|
20532
|
+
cache.set(
|
|
20533
|
+
endpoint,
|
|
20534
|
+
call("GET", endpoint).then((r) => r.status === 200 ? r.json : null)
|
|
20535
|
+
);
|
|
20536
|
+
return cache.get(endpoint);
|
|
20537
|
+
}
|
|
20538
|
+
};
|
|
20539
|
+
const read2 = await call("GET", `/v1/projects/${ref}`);
|
|
20540
|
+
if (read2.status === 401)
|
|
20541
|
+
return { ok: false, problem: "Your Supabase login has expired. Run `npx supabase login`, then try again.", errorType: "CliNotSignedIn" };
|
|
20542
|
+
if (read2.status !== 200 || !read2.json?.name)
|
|
20543
|
+
return {
|
|
20544
|
+
ok: false,
|
|
20545
|
+
// Projects created through Vercel's Supabase integration live in a Supabase account linked to Vercel.
|
|
20546
|
+
problem: [
|
|
20547
|
+
`The Supabase account you\u2019re signed in to can\u2019t open this project (${ref}).`,
|
|
20548
|
+
"If you created the database through Vercel: in Vercel open Storage, click the database, then Open in Supabase. In that same browser, run `npx supabase login` and approve it. Then try again.",
|
|
20549
|
+
"Otherwise run `npx supabase login` with the Supabase account that owns the project."
|
|
20550
|
+
].join("\n "),
|
|
20551
|
+
errorType: "NoAccess"
|
|
20552
|
+
};
|
|
20553
|
+
const project = read2.json;
|
|
20554
|
+
return { ok: true, ctx, target: project.name };
|
|
20555
|
+
},
|
|
20556
|
+
settings: [
|
|
20557
|
+
{
|
|
20558
|
+
id: "email-confirmation",
|
|
20559
|
+
label: "Email confirmation (new users confirm their email before signing in)",
|
|
20560
|
+
impact: "New users must click a link in an email before they can sign in. People already signed up aren\u2019t affected.",
|
|
20561
|
+
read: authFlag((c) => c.mailer_autoconfirm === false),
|
|
20562
|
+
enable: (ctx) => patchAuth(ctx, { mailer_autoconfirm: false })
|
|
20563
|
+
},
|
|
20564
|
+
{
|
|
20565
|
+
id: "password-length",
|
|
20566
|
+
label: "Passwords of at least 8 characters",
|
|
20567
|
+
impact: "New and changed passwords must be at least 8 characters. Existing passwords keep working.",
|
|
20568
|
+
read: authFlag((c) => (c.password_min_length ?? 0) >= 8),
|
|
20569
|
+
enable: (ctx) => patchAuth(ctx, { password_min_length: 8 })
|
|
20570
|
+
},
|
|
20571
|
+
{
|
|
20572
|
+
id: "leaked-passwords",
|
|
20573
|
+
label: "Leaked password protection (refuses passwords found in data breaches)",
|
|
20574
|
+
impact: "Passwords that appear in known data breaches are refused when someone signs up or changes their password.",
|
|
20575
|
+
read: authFlag((c) => c.password_hibp_enabled === true),
|
|
20576
|
+
enable: (ctx) => patchAuth(ctx, { password_hibp_enabled: true })
|
|
20577
|
+
},
|
|
20578
|
+
{
|
|
20579
|
+
id: "app-mfa",
|
|
20580
|
+
label: "Authenticator app sign-in for your users (two-factor, optional for them)",
|
|
20581
|
+
impact: "Your users can protect their account with an authenticator app. It stays optional, and your app needs a screen for it before anyone can use it.",
|
|
20582
|
+
read: authFlag((c) => c.mfa_totp_enroll_enabled === true && c.mfa_totp_verify_enabled === true),
|
|
20583
|
+
enable: (ctx) => patchAuth(ctx, { mfa_totp_enroll_enabled: true, mfa_totp_verify_enabled: true })
|
|
20584
|
+
},
|
|
20585
|
+
{
|
|
20586
|
+
id: "secure-email-change",
|
|
20587
|
+
label: "Secure email change (both the old and new address confirm)",
|
|
20588
|
+
impact: "Changing an account\u2019s email needs confirming from both the old and the new address, so a stolen session can\u2019t take over the account.",
|
|
20589
|
+
read: authFlag((c) => c.mailer_secure_email_change_enabled === true),
|
|
20590
|
+
enable: (ctx) => patchAuth(ctx, { mailer_secure_email_change_enabled: true })
|
|
20591
|
+
},
|
|
20592
|
+
{
|
|
20593
|
+
id: "database-encryption",
|
|
20594
|
+
label: "Encrypted database connections only",
|
|
20595
|
+
impact: "Direct connections to your database must be encrypted. Turning this on restarts the database for a few seconds.",
|
|
20596
|
+
read: async (ctx) => (await ctx.get(`/v1/projects/${ctx.ref}/ssl-enforcement`))?.currentConfig?.database === true ? { state: "on" } : { state: "off" },
|
|
20597
|
+
async enable(ctx) {
|
|
20598
|
+
const r = await ctx.api("PUT", `/v1/projects/${ctx.ref}/ssl-enforcement`, { requestedConfig: { database: true } });
|
|
20599
|
+
return r.status >= 200 && r.status < 300 ? { ok: true } : { ok: false };
|
|
20600
|
+
}
|
|
20601
|
+
},
|
|
20602
|
+
{
|
|
20603
|
+
id: "security-advisor",
|
|
20604
|
+
label: "No problems in Supabase\u2019s security advisor",
|
|
20605
|
+
impact: "Nothing changes on its own: these are fixed in your database code, and Aura lists them below.",
|
|
20606
|
+
read: async (ctx) => (await advisorProblems(ctx)).length ? { state: "off" } : { state: "on" },
|
|
20607
|
+
enable: async () => ({ ok: false, unavailable: "fixed in your database code (see Left for you)" })
|
|
20608
|
+
}
|
|
20609
|
+
],
|
|
20610
|
+
async leftForYou(ctx, results) {
|
|
20611
|
+
const left = [];
|
|
20612
|
+
const problems = await advisorProblems(ctx);
|
|
20613
|
+
if (problems.length) {
|
|
20614
|
+
const titles = [...new Set(problems.map((p2) => p2.title ?? p2.name ?? "Security problem"))].slice(0, 5);
|
|
20615
|
+
left.push(
|
|
20616
|
+
`Supabase\u2019s security advisor found ${problems.length} problem(s): ${titles.join("; ")}. Fix them in a migration (\`aura fix turn-on-rls\` covers missing row-level security), or see https://supabase.com/dashboard/project/${ctx.ref}/advisors/security`
|
|
20617
|
+
);
|
|
20618
|
+
}
|
|
20619
|
+
if (results.some((r) => r.id === "leaked-passwords" && r.result === "unavailable"))
|
|
20620
|
+
left.push("Leaked password protection needs Supabase Pro. Until then, the 8-character minimum helps.");
|
|
20621
|
+
left.push("Turn on two-factor login for your Supabase account: Supabase dashboard \u2192 Account \u2192 Security.");
|
|
20622
|
+
return left;
|
|
20623
|
+
}
|
|
20624
|
+
};
|
|
20625
|
+
|
|
20626
|
+
// src/connectors/vercel.ts
|
|
20627
|
+
import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
20628
|
+
import path4 from "path";
|
|
20629
|
+
var PUBLIC_PREFIX = /^(?:NEXT_PUBLIC_|VITE_|EXPO_PUBLIC_|PUBLIC_)/;
|
|
20630
|
+
var SECRET_NAME = /SECRET|SERVICE_ROLE|PRIVATE|PASSWORD|_TOKEN$|API_KEY$/;
|
|
20631
|
+
var parse4 = (r) => {
|
|
20632
|
+
try {
|
|
20633
|
+
return r.code === 0 ? JSON.parse(r.stdout) : null;
|
|
20634
|
+
} catch {
|
|
20635
|
+
return null;
|
|
20636
|
+
}
|
|
20637
|
+
};
|
|
20638
|
+
async function exposedSecrets(ctx) {
|
|
20639
|
+
const envs = (await ctx.get(`/v10/projects/${ctx.projectId}/env?decrypt=true${ctx.team.replace("?", "&")}`))?.envs ?? [];
|
|
20640
|
+
return [...new Set(envs.filter((e) => PUBLIC_PREFIX.test(e.key) && (SECRET_NAME.test(e.key) || e.value && containsSecret(e.value))).map((e) => e.key))];
|
|
20641
|
+
}
|
|
20642
|
+
async function patchProject(ctx, body) {
|
|
20643
|
+
const r = await ctx.vercel(["api", `/v9/projects/${ctx.projectId}${ctx.team}`, "-X", "PATCH", "--input", "-", "--raw"], JSON.stringify(body));
|
|
20644
|
+
if (r.code === 0) return { ok: true };
|
|
20645
|
+
return { ok: false, unavailable: /plan|upgrade|pro\b|enterprise/i.test(r.stderr + r.stdout) ? "not available on your Vercel plan" : void 0 };
|
|
20646
|
+
}
|
|
20647
|
+
var vercel = {
|
|
20648
|
+
id: "vercel",
|
|
20649
|
+
name: "Vercel",
|
|
20650
|
+
actionId: "vercel-security",
|
|
20651
|
+
detect: (scan) => scan.stack.services.includes("Vercel"),
|
|
20652
|
+
async connect(root, fake) {
|
|
20653
|
+
const cli = fake ?? ((args, input2) => run("vercel", args, { cwd: root, input: input2, timeoutMs: 6e4 }));
|
|
20654
|
+
if (!fake) {
|
|
20655
|
+
if (!await commandExists("vercel"))
|
|
20656
|
+
return {
|
|
20657
|
+
ok: false,
|
|
20658
|
+
problem: "The Vercel CLI isn\u2019t installed. Install it with `npm install -g vercel`, then run `vercel login`.",
|
|
20659
|
+
errorType: "CliNotInstalled"
|
|
20660
|
+
};
|
|
20661
|
+
if ((await cli(["whoami"])).code !== 0)
|
|
20662
|
+
return { ok: false, problem: "The Vercel CLI isn\u2019t signed in. Run `vercel login`, then try again.", errorType: "CliNotSignedIn" };
|
|
20663
|
+
}
|
|
20664
|
+
const linked = [path4.join(root, ".vercel", "project.json"), ...["apps", "packages"].flatMap((d) => findLinks(path4.join(root, d)))].find(
|
|
20665
|
+
(f) => existsSync3(f)
|
|
20666
|
+
);
|
|
20667
|
+
let project = null;
|
|
20668
|
+
try {
|
|
20669
|
+
project = linked ? JSON.parse(readFileSync3(linked, "utf8")) : null;
|
|
20670
|
+
} catch {
|
|
20671
|
+
project = null;
|
|
20672
|
+
}
|
|
20673
|
+
if (!project?.projectId)
|
|
20674
|
+
return { ok: false, problem: "This folder isn\u2019t linked to a Vercel project yet. Run `vercel link`, then try again.", errorType: "NoProject" };
|
|
20675
|
+
const cache = /* @__PURE__ */ new Map();
|
|
20676
|
+
const ctx = {
|
|
20677
|
+
projectId: project.projectId,
|
|
20678
|
+
team: project.orgId?.startsWith("team_") ? `?teamId=${project.orgId}` : "",
|
|
20679
|
+
vercel: cli,
|
|
20680
|
+
get: (endpoint) => {
|
|
20681
|
+
if (!cache.has(endpoint)) cache.set(endpoint, cli(["api", endpoint, "--raw"]).then(parse4));
|
|
20682
|
+
return cache.get(endpoint);
|
|
20683
|
+
}
|
|
20684
|
+
};
|
|
20685
|
+
const name = (await ctx.get(`/v9/projects/${ctx.projectId}${ctx.team}`))?.name;
|
|
20686
|
+
if (!name)
|
|
20687
|
+
return { ok: false, problem: "Couldn\u2019t read this project from Vercel. Check you have access to it with `vercel project ls`.", errorType: "NoProject" };
|
|
20688
|
+
return { ok: true, ctx, target: name };
|
|
20689
|
+
},
|
|
20690
|
+
settings: [
|
|
20691
|
+
{
|
|
20692
|
+
id: "preview-protection",
|
|
20693
|
+
label: "Protect preview links (only your Vercel team can open them)",
|
|
20694
|
+
impact: "Preview and deployment links need a Vercel login to open. Your live site on your own domain stays public.",
|
|
20695
|
+
read: async (ctx) => (await ctx.get(`/v9/projects/${ctx.projectId}${ctx.team}`))?.ssoProtection ? { state: "on" } : { state: "off" },
|
|
20696
|
+
enable: (ctx) => patchProject(ctx, { ssoProtection: { deploymentType: "all_except_custom_domains" } })
|
|
20697
|
+
},
|
|
20698
|
+
{
|
|
20699
|
+
id: "git-fork-protection",
|
|
20700
|
+
label: "Fork protection (pull requests from forks don\u2019t deploy automatically)",
|
|
20701
|
+
impact: "A pull request from someone else\u2019s copy of your repo only deploys after you approve it, so it can\u2019t read your settings.",
|
|
20702
|
+
read: async (ctx) => (await ctx.get(`/v9/projects/${ctx.projectId}${ctx.team}`))?.gitForkProtection ? { state: "on" } : { state: "off" },
|
|
20703
|
+
enable: (ctx) => patchProject(ctx, { gitForkProtection: true })
|
|
20704
|
+
},
|
|
20705
|
+
{
|
|
20706
|
+
id: "protected-sourcemaps",
|
|
20707
|
+
label: "Protected source maps (your original code isn\u2019t served to the public)",
|
|
20708
|
+
impact: "Source maps, which reveal your original code, are only served to people signed in to your Vercel team.",
|
|
20709
|
+
read: async (ctx) => (await ctx.get(`/v9/projects/${ctx.projectId}${ctx.team}`))?.protectedSourcemaps ? { state: "on" } : { state: "off" },
|
|
20710
|
+
enable: (ctx) => patchProject(ctx, { protectedSourcemaps: true })
|
|
20711
|
+
},
|
|
20712
|
+
{
|
|
20713
|
+
id: "no-public-secrets",
|
|
20714
|
+
label: "No secret keys in browser-visible settings (NEXT_PUBLIC_\u2026 and similar)",
|
|
20715
|
+
impact: "Nothing changes on its own: Aura can\u2019t move a key for you, and lists what to do below.",
|
|
20716
|
+
read: async (ctx) => (await exposedSecrets(ctx)).length ? { state: "off" } : { state: "on" },
|
|
20717
|
+
enable: async () => ({ ok: false, unavailable: "needs you to move the key (see Left for you)" })
|
|
20718
|
+
}
|
|
20719
|
+
],
|
|
20720
|
+
async leftForYou(ctx) {
|
|
20721
|
+
const left = [];
|
|
20722
|
+
const exposed = await exposedSecrets(ctx);
|
|
20723
|
+
if (exposed.length)
|
|
20724
|
+
left.push(
|
|
20725
|
+
`These settings are visible to anyone using your site: ${exposed.join(", ")}. Rename each without the public prefix, read it only in server code, then rotate the key in its dashboard (\`aura guide rotate-generic-key\`).`
|
|
20726
|
+
);
|
|
20727
|
+
left.push("Turn on two-factor login for your Vercel account: Vercel \u2192 Account Settings \u2192 Authentication.");
|
|
20728
|
+
return left;
|
|
20729
|
+
}
|
|
20730
|
+
};
|
|
20731
|
+
function findLinks(dir) {
|
|
20732
|
+
try {
|
|
20733
|
+
return readdirSync2(dir).map((d) => path4.join(dir, d, ".vercel", "project.json"));
|
|
20734
|
+
} catch {
|
|
20735
|
+
return [];
|
|
20736
|
+
}
|
|
20737
|
+
}
|
|
20738
|
+
|
|
20739
|
+
// src/connectors/index.ts
|
|
20740
|
+
var CONNECTORS = [github, supabase, vercel];
|
|
20741
|
+
function connectorForAction(actionId) {
|
|
20742
|
+
return CONNECTORS.find((c) => c.actionId === actionId);
|
|
20743
|
+
}
|
|
20744
|
+
|
|
20745
|
+
// src/templates.ts
|
|
20746
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
20747
|
+
import path7 from "path";
|
|
20748
|
+
|
|
20207
20749
|
// src/core/config.ts
|
|
20750
|
+
import { chmodSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
20751
|
+
import os2 from "os";
|
|
20752
|
+
import path5 from "path";
|
|
20208
20753
|
var DEFAULT_CONFIG = {
|
|
20209
20754
|
version: 1,
|
|
20210
20755
|
agents: [],
|
|
@@ -20213,45 +20758,45 @@ var DEFAULT_CONFIG = {
|
|
|
20213
20758
|
ciMaxDrop: 0
|
|
20214
20759
|
};
|
|
20215
20760
|
function auraPath(root, ...parts) {
|
|
20216
|
-
return
|
|
20761
|
+
return path5.join(root, AURA_DIR, ...parts);
|
|
20217
20762
|
}
|
|
20218
20763
|
function ensureAuraDir(root) {
|
|
20219
|
-
|
|
20764
|
+
mkdirSync2(auraPath(root), { recursive: true });
|
|
20220
20765
|
}
|
|
20221
20766
|
function loadConfig(root) {
|
|
20222
20767
|
const file2 = auraPath(root, "config.json");
|
|
20223
|
-
if (!
|
|
20768
|
+
if (!existsSync4(file2)) return { ...DEFAULT_CONFIG };
|
|
20224
20769
|
try {
|
|
20225
|
-
return { ...DEFAULT_CONFIG, ...JSON.parse(
|
|
20770
|
+
return { ...DEFAULT_CONFIG, ...JSON.parse(readFileSync4(file2, "utf8")) };
|
|
20226
20771
|
} catch {
|
|
20227
20772
|
return { ...DEFAULT_CONFIG };
|
|
20228
20773
|
}
|
|
20229
20774
|
}
|
|
20230
20775
|
function saveConfig(root, config2) {
|
|
20231
20776
|
ensureAuraDir(root);
|
|
20232
|
-
|
|
20777
|
+
writeFileSync2(auraPath(root, "config.json"), JSON.stringify(config2, null, 2) + "\n");
|
|
20233
20778
|
}
|
|
20234
20779
|
function writeAuraFile(root, name, content) {
|
|
20235
20780
|
ensureAuraDir(root);
|
|
20236
20781
|
const file2 = auraPath(root, name);
|
|
20237
|
-
|
|
20238
|
-
|
|
20782
|
+
mkdirSync2(path5.dirname(file2), { recursive: true });
|
|
20783
|
+
writeFileSync2(file2, content);
|
|
20239
20784
|
return file2;
|
|
20240
20785
|
}
|
|
20241
|
-
var homeAuraDir = () =>
|
|
20242
|
-
var credentialsFile = () =>
|
|
20786
|
+
var homeAuraDir = () => path5.join(os2.homedir(), ".aura");
|
|
20787
|
+
var credentialsFile = () => path5.join(homeAuraDir(), "credentials.json");
|
|
20243
20788
|
function getApiKey() {
|
|
20244
20789
|
if (process.env.AURA_KEY) return process.env.AURA_KEY.trim();
|
|
20245
20790
|
try {
|
|
20246
|
-
const data = JSON.parse(
|
|
20791
|
+
const data = JSON.parse(readFileSync4(credentialsFile(), "utf8"));
|
|
20247
20792
|
return typeof data.key === "string" ? data.key : null;
|
|
20248
20793
|
} catch {
|
|
20249
20794
|
return null;
|
|
20250
20795
|
}
|
|
20251
20796
|
}
|
|
20252
20797
|
function saveApiKey(key) {
|
|
20253
|
-
|
|
20254
|
-
|
|
20798
|
+
mkdirSync2(homeAuraDir(), { recursive: true });
|
|
20799
|
+
writeFileSync2(credentialsFile(), JSON.stringify({ key }, null, 2) + "\n", { mode: 384 });
|
|
20255
20800
|
try {
|
|
20256
20801
|
chmodSync(credentialsFile(), 384);
|
|
20257
20802
|
} catch {
|
|
@@ -20261,7 +20806,7 @@ function getApiUrl(config2) {
|
|
|
20261
20806
|
return (process.env.AURA_API_URL || config2?.apiUrl || DEFAULT_API_URL).replace(/\/$/, "");
|
|
20262
20807
|
}
|
|
20263
20808
|
function auraBinDir() {
|
|
20264
|
-
return
|
|
20809
|
+
return path5.join(homeAuraDir(), "bin");
|
|
20265
20810
|
}
|
|
20266
20811
|
var AURA_GITIGNORE = `# Aura local files: they can describe security weaknesses, so they stay on your machine.
|
|
20267
20812
|
report.md
|
|
@@ -20273,15 +20818,15 @@ tasks/
|
|
|
20273
20818
|
`;
|
|
20274
20819
|
function ensureGitignore(root) {
|
|
20275
20820
|
const file2 = auraPath(root, ".gitignore");
|
|
20276
|
-
if (
|
|
20821
|
+
if (existsSync4(file2) && readFileSync4(file2, "utf8") === AURA_GITIGNORE) return false;
|
|
20277
20822
|
ensureAuraDir(root);
|
|
20278
|
-
|
|
20823
|
+
writeFileSync2(file2, AURA_GITIGNORE);
|
|
20279
20824
|
return true;
|
|
20280
20825
|
}
|
|
20281
20826
|
|
|
20282
20827
|
// src/core/stack.ts
|
|
20283
|
-
import { existsSync as
|
|
20284
|
-
import
|
|
20828
|
+
import { existsSync as existsSync5 } from "fs";
|
|
20829
|
+
import path6 from "path";
|
|
20285
20830
|
function allDeps(pkg) {
|
|
20286
20831
|
return { ...pkg?.dependencies ?? {}, ...pkg?.devDependencies ?? {} };
|
|
20287
20832
|
}
|
|
@@ -20405,7 +20950,7 @@ function findEnvRefs(files, read2) {
|
|
|
20405
20950
|
}
|
|
20406
20951
|
function detectStack(root, files, pkg, read2) {
|
|
20407
20952
|
const deps2 = allDeps(pkg);
|
|
20408
|
-
const has = (f) => files.includes(f) ||
|
|
20953
|
+
const has = (f) => files.includes(f) || existsSync5(path6.join(root, f));
|
|
20409
20954
|
const hasDir = (d) => files.some((f) => f.startsWith(`${d}/`));
|
|
20410
20955
|
let packageManager = null;
|
|
20411
20956
|
const pmField = pkg?.packageManager?.split("@")[0];
|
|
@@ -20430,6 +20975,7 @@ function detectStack(root, files, pkg, read2) {
|
|
|
20430
20975
|
}
|
|
20431
20976
|
if (has("vercel.json") || has(".vercel/project.json")) services.add("Vercel");
|
|
20432
20977
|
if (hasDir("supabase")) services.add("Supabase");
|
|
20978
|
+
if (has("vercel.json") || has(".vercel/project.json")) services.add("Vercel");
|
|
20433
20979
|
services.add("GitHub");
|
|
20434
20980
|
let nextjs = null;
|
|
20435
20981
|
if (deps2.next) {
|
|
@@ -20439,9 +20985,9 @@ function detectStack(root, files, pkg, read2) {
|
|
|
20439
20985
|
const pages = hasDir(`${base}pages`);
|
|
20440
20986
|
nextjs = { version: deps2.next.replace(/^[^\d]*/, ""), router: app && pages ? "both" : app ? "app" : pages ? "pages" : null, srcDir };
|
|
20441
20987
|
}
|
|
20442
|
-
let
|
|
20988
|
+
let supabase2 = null;
|
|
20443
20989
|
if (services.has("Supabase")) {
|
|
20444
|
-
|
|
20990
|
+
supabase2 = { migrationsDir: hasDir("supabase/migrations") ? "supabase/migrations" : null, hasConfig: has("supabase/config.toml") };
|
|
20445
20991
|
}
|
|
20446
20992
|
let testRunner = null;
|
|
20447
20993
|
if (deps2.vitest || files.some((f) => /^vitest\.config\./.test(f))) testRunner = "vitest";
|
|
@@ -20456,7 +21002,7 @@ function detectStack(root, files, pkg, read2) {
|
|
|
20456
21002
|
frameworks,
|
|
20457
21003
|
services: [...services].sort(),
|
|
20458
21004
|
nextjs,
|
|
20459
|
-
supabase,
|
|
21005
|
+
supabase: supabase2,
|
|
20460
21006
|
typescript: has("tsconfig.json"),
|
|
20461
21007
|
testRunner,
|
|
20462
21008
|
envVars,
|
|
@@ -20500,7 +21046,7 @@ function pmCommands(pm) {
|
|
|
20500
21046
|
// package.json
|
|
20501
21047
|
var package_default = {
|
|
20502
21048
|
name: "getaura",
|
|
20503
|
-
version: "0.1.
|
|
21049
|
+
version: "0.1.9",
|
|
20504
21050
|
description: "Aura helps you and your coding agent follow best practices while you vibe code.",
|
|
20505
21051
|
type: "module",
|
|
20506
21052
|
bin: {
|
|
@@ -20581,19 +21127,19 @@ function fill(text, vars) {
|
|
|
20581
21127
|
return text.replace(/\{\{([a-z_]+)\}\}/g, (m, name) => KNOWN.has(name) ? vars[name] ?? "" : m);
|
|
20582
21128
|
}
|
|
20583
21129
|
function readTemplate(rel) {
|
|
20584
|
-
return
|
|
21130
|
+
return readFileSync5(path7.join(templatesDir(), rel), "utf8");
|
|
20585
21131
|
}
|
|
20586
21132
|
function render(rel, vars) {
|
|
20587
21133
|
return fill(readTemplate(rel), vars);
|
|
20588
21134
|
}
|
|
20589
21135
|
function templateExists(rel) {
|
|
20590
|
-
return
|
|
21136
|
+
return existsSync6(path7.join(templatesDir(), rel));
|
|
20591
21137
|
}
|
|
20592
21138
|
function briefSummary(root, pkg) {
|
|
20593
|
-
const fallbackName = pkg?.name ? pkg.name.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()) :
|
|
21139
|
+
const fallbackName = pkg?.name ? pkg.name.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()) : path7.basename(root);
|
|
20594
21140
|
const file2 = auraPath(root, "brief.md");
|
|
20595
|
-
if (!
|
|
20596
|
-
const text =
|
|
21141
|
+
if (!existsSync6(file2)) return { name: fallbackName, summary: pkg?.description ?? "See `.aura/brief.md` once the product brief is added." };
|
|
21142
|
+
const text = readFileSync5(file2, "utf8");
|
|
20597
21143
|
const title = text.match(/^#\s+(.+)$/m)?.[1]?.replace(/[:\-–—]?\s*product brief$/i, "").trim();
|
|
20598
21144
|
const paragraph = text.split(/\n\s*\n/).map((p2) => p2.trim()).find((p2) => p2 && !p2.startsWith("#") && !p2.startsWith(">") && !p2.startsWith("-") && !p2.startsWith("|") && p2.length > 40) ?? "";
|
|
20599
21145
|
const sentences = paragraph.replace(/\s+/g, " ").match(/[^.!?]+[.!?]+/g) ?? [paragraph];
|
|
@@ -20758,7 +21304,6 @@ var ACTIONS = [
|
|
|
20758
21304
|
},
|
|
20759
21305
|
// Accounts and ownership
|
|
20760
21306
|
{ id: "inventory", title: "Complete the keys and accounts inventory", category: "security", delivery: "checklist", resolves: [] },
|
|
20761
|
-
{ id: "github-security", title: "Enable GitHub security features", category: "security", delivery: "guide", resolves: [] },
|
|
20762
21307
|
{ id: "transfer-ownership", title: "Move accounts to a company owner", category: "security", delivery: "guide", resolves: [] }
|
|
20763
21308
|
];
|
|
20764
21309
|
var FOUNDATION_BUNDLE = ["add-brief", "add-agent-rules", "install-skills", "token-efficiency", "add-ci", "add-env-example", "add-aura-workflow"];
|
|
@@ -20773,6 +21318,15 @@ function getAction(id) {
|
|
|
20773
21318
|
};
|
|
20774
21319
|
}
|
|
20775
21320
|
if (/^rotate-[a-z]+-(?:key|token)$/.test(id)) return { id, title: "Rotate an exposed key", category: "security", delivery: "guide", resolves: [] };
|
|
21321
|
+
const connector = connectorForAction(id);
|
|
21322
|
+
if (connector)
|
|
21323
|
+
return {
|
|
21324
|
+
id,
|
|
21325
|
+
title: `Turn on ${connector.name} security features`,
|
|
21326
|
+
category: "security",
|
|
21327
|
+
delivery: "guide",
|
|
21328
|
+
resolves: [`security.${connector.id}-settings`]
|
|
21329
|
+
};
|
|
20776
21330
|
return ACTIONS.find((a) => a.id === id);
|
|
20777
21331
|
}
|
|
20778
21332
|
var CATEGORY_PRIORITY = ["security", "agent-setup", "foundations", "structure"];
|
|
@@ -20808,15 +21362,16 @@ function nextSteps(scan, opts = {}) {
|
|
|
20808
21362
|
if (!action2) continue;
|
|
20809
21363
|
const { delivery, fellBack } = effectiveDelivery(action2, scan);
|
|
20810
21364
|
const worst = fs.map((f) => f.severity).sort((a, b) => SEVERITY_ORDER.indexOf(a) - SEVERITY_ORDER.indexOf(b))[0];
|
|
21365
|
+
const connected = Boolean(connectorForAction(actionId));
|
|
20811
21366
|
steps.push({
|
|
20812
21367
|
actionId,
|
|
20813
21368
|
title: action2.title,
|
|
20814
21369
|
delivery,
|
|
20815
|
-
effort: EFFORT[delivery],
|
|
21370
|
+
effort: connected ? "one-click" : EFFORT[delivery],
|
|
20816
21371
|
category: action2.category,
|
|
20817
21372
|
why: fs[0].why,
|
|
20818
21373
|
impact: impactOf(scan.checks, action2.resolves),
|
|
20819
|
-
command: commandFor(actionId, delivery),
|
|
21374
|
+
command: connected ? `aura apply ${actionId}` : commandFor(actionId, delivery),
|
|
20820
21375
|
findingIds: fs.map((f) => f.id),
|
|
20821
21376
|
fellBack,
|
|
20822
21377
|
severity: worst
|
|
@@ -20841,16 +21396,16 @@ function nextSteps(scan, opts = {}) {
|
|
|
20841
21396
|
severity: "critical"
|
|
20842
21397
|
});
|
|
20843
21398
|
}
|
|
20844
|
-
|
|
21399
|
+
for (const connector of opts.accounts ? CONNECTORS.filter((c) => c.detect(scan) && !opts.accounts.secured[c.id] && !steps.some((s) => s.actionId === c.actionId)) : []) {
|
|
20845
21400
|
steps.push({
|
|
20846
|
-
actionId:
|
|
20847
|
-
title:
|
|
21401
|
+
actionId: connector.actionId,
|
|
21402
|
+
title: `Turn on ${connector.name} security features`,
|
|
20848
21403
|
delivery: "guide",
|
|
20849
|
-
effort: "
|
|
21404
|
+
effort: "one-click",
|
|
20850
21405
|
category: "security",
|
|
20851
|
-
why:
|
|
21406
|
+
why: `Aura checks ${connector.name}\u2019s security settings and, once you agree, turns on the ones that are off.`,
|
|
20852
21407
|
impact: 0,
|
|
20853
|
-
command:
|
|
21408
|
+
command: `aura apply ${connector.actionId}`,
|
|
20854
21409
|
findingIds: [],
|
|
20855
21410
|
severity: "low"
|
|
20856
21411
|
});
|
|
@@ -20899,12 +21454,12 @@ function nextSteps(scan, opts = {}) {
|
|
|
20899
21454
|
}
|
|
20900
21455
|
|
|
20901
21456
|
// src/actions/generators.ts
|
|
20902
|
-
import { existsSync as
|
|
20903
|
-
import
|
|
21457
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
|
|
21458
|
+
import path12 from "path";
|
|
20904
21459
|
|
|
20905
21460
|
// src/checks/agent-setup.ts
|
|
20906
|
-
import { existsSync as
|
|
20907
|
-
import
|
|
21461
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "fs";
|
|
21462
|
+
import path8 from "path";
|
|
20908
21463
|
|
|
20909
21464
|
// src/checks/types.ts
|
|
20910
21465
|
import { createHash } from "crypto";
|
|
@@ -20938,18 +21493,18 @@ function lineOf(text, index) {
|
|
|
20938
21493
|
|
|
20939
21494
|
// src/checks/agent-setup.ts
|
|
20940
21495
|
function exists(ctx, file2) {
|
|
20941
|
-
return
|
|
21496
|
+
return existsSync7(path8.join(ctx.root, file2));
|
|
20942
21497
|
}
|
|
20943
21498
|
function readDisk(ctx, file2) {
|
|
20944
21499
|
try {
|
|
20945
|
-
return
|
|
21500
|
+
return readFileSync6(path8.join(ctx.root, file2), "utf8");
|
|
20946
21501
|
} catch {
|
|
20947
21502
|
return "";
|
|
20948
21503
|
}
|
|
20949
21504
|
}
|
|
20950
21505
|
function skillsIn(ctx, dir) {
|
|
20951
21506
|
try {
|
|
20952
|
-
return
|
|
21507
|
+
return readdirSync3(path8.join(ctx.root, dir), { withFileTypes: true }).filter((d) => d.isDirectory() && existsSync7(path8.join(ctx.root, dir, d.name, "SKILL.md"))).map((d) => d.name);
|
|
20953
21508
|
} catch {
|
|
20954
21509
|
return [];
|
|
20955
21510
|
}
|
|
@@ -20966,7 +21521,7 @@ function rulesFileFor(ctx, agent) {
|
|
|
20966
21521
|
if (agent === "cursor") {
|
|
20967
21522
|
if (exists(ctx, ".cursorrules")) return ".cursorrules";
|
|
20968
21523
|
try {
|
|
20969
|
-
const rules =
|
|
21524
|
+
const rules = readdirSync3(path8.join(ctx.root, ".cursor/rules")).filter((f) => /\.mdc?$/.test(f));
|
|
20970
21525
|
if (rules.length) return `.cursor/rules/${rules[0]}`;
|
|
20971
21526
|
} catch {
|
|
20972
21527
|
}
|
|
@@ -21111,7 +21666,7 @@ var brief = {
|
|
|
21111
21666
|
var agentChecks = [rulesFile, rulesCurrent, skills, auraSkill, brief];
|
|
21112
21667
|
|
|
21113
21668
|
// src/core/graph.ts
|
|
21114
|
-
import
|
|
21669
|
+
import path9 from "path";
|
|
21115
21670
|
var EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"];
|
|
21116
21671
|
var IMPORT_RE = /(?:import|export)\s+(?:type\s+)?(?:[\w*{}\s,$]+?\s+from\s+)?(['"])([^'"\n]+)\1|import\s*\(\s*(['"])([^'"\n]+)\3\s*\)|require\s*\(\s*(['"])([^'"\n]+)\5\s*\)/g;
|
|
21117
21672
|
function parseImports(text) {
|
|
@@ -21139,7 +21694,7 @@ function readAliases(read2) {
|
|
|
21139
21694
|
for (const [key, targets] of Object.entries(json2.compilerOptions?.paths ?? {})) {
|
|
21140
21695
|
const target = targets[0];
|
|
21141
21696
|
if (!key.endsWith("/*") || !target?.endsWith("/*")) continue;
|
|
21142
|
-
const resolved =
|
|
21697
|
+
const resolved = path9.posix.normalize(path9.posix.join(base || ".", target.slice(0, -1)));
|
|
21143
21698
|
entries.push([key.slice(0, -1), resolved === "." || resolved === "./" ? "" : resolved.replace(/\/?$/, "/")]);
|
|
21144
21699
|
}
|
|
21145
21700
|
} catch {
|
|
@@ -21150,10 +21705,10 @@ function readAliases(read2) {
|
|
|
21150
21705
|
}
|
|
21151
21706
|
function resolveImport(from, spec, files, aliases) {
|
|
21152
21707
|
let base = null;
|
|
21153
|
-
if (spec.startsWith(".")) base =
|
|
21708
|
+
if (spec.startsWith(".")) base = path9.posix.normalize(path9.posix.join(path9.posix.dirname(from), spec));
|
|
21154
21709
|
else {
|
|
21155
21710
|
const alias = aliases.entries.find(([prefix]) => spec.startsWith(prefix));
|
|
21156
|
-
if (alias) base =
|
|
21711
|
+
if (alias) base = path9.posix.normalize(alias[1] + spec.slice(alias[0].length));
|
|
21157
21712
|
}
|
|
21158
21713
|
if (base === null) return null;
|
|
21159
21714
|
const withoutJs = base.replace(/\.(?:js|jsx|mjs|cjs)$/, "");
|
|
@@ -21199,14 +21754,14 @@ function unreachableFiles(files, read2, pkgScripts) {
|
|
|
21199
21754
|
}
|
|
21200
21755
|
|
|
21201
21756
|
// src/actions/agent-commands.ts
|
|
21202
|
-
import { readFileSync as
|
|
21203
|
-
import
|
|
21757
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
21758
|
+
import path10 from "path";
|
|
21204
21759
|
var AURA_COMMANDS = ["scan", "next", "fix", "setup"];
|
|
21205
21760
|
function invocation(agent, command) {
|
|
21206
21761
|
return agent === "claude" ? `/aura:${command}` : agent === "codex" ? `$aura-${command}` : `/aura-${command}`;
|
|
21207
21762
|
}
|
|
21208
21763
|
function pluginCommand(command) {
|
|
21209
|
-
const text =
|
|
21764
|
+
const text = readFileSync7(path10.join(path10.dirname(templatesDir()), "plugin", "skills", command, "SKILL.md"), "utf8").replace(/\r\n/g, "\n");
|
|
21210
21765
|
const [, frontmatter = "", body = ""] = text.match(/^---\n([\s\S]*?)\n---\n+([\s\S]*)$/) ?? [];
|
|
21211
21766
|
const description = frontmatter.match(/^description: (.+)$/m)?.[1] ?? "";
|
|
21212
21767
|
return { description, body };
|
|
@@ -21251,11 +21806,11 @@ function commandList(agent) {
|
|
|
21251
21806
|
}
|
|
21252
21807
|
|
|
21253
21808
|
// src/actions/agent-files.ts
|
|
21254
|
-
import { existsSync as
|
|
21255
|
-
import
|
|
21809
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
|
|
21810
|
+
import path11 from "path";
|
|
21256
21811
|
function current(root, file2) {
|
|
21257
|
-
const full =
|
|
21258
|
-
return
|
|
21812
|
+
const full = path11.join(root, file2);
|
|
21813
|
+
return existsSync8(full) ? readFileSync8(full, "utf8") : null;
|
|
21259
21814
|
}
|
|
21260
21815
|
function upsertBlock(existing, body) {
|
|
21261
21816
|
const block = `${AURA_BLOCK_START}
|
|
@@ -21299,8 +21854,8 @@ function skillChanges(agents, names) {
|
|
|
21299
21854
|
|
|
21300
21855
|
// src/actions/generators.ts
|
|
21301
21856
|
function read(root, file2) {
|
|
21302
|
-
const full =
|
|
21303
|
-
return
|
|
21857
|
+
const full = path12.join(root, file2);
|
|
21858
|
+
return existsSync9(full) ? readFileSync9(full, "utf8") : null;
|
|
21304
21859
|
}
|
|
21305
21860
|
function withScripts(root, scripts, overwrite = false) {
|
|
21306
21861
|
const text = read(root, "package.json");
|
|
@@ -21321,13 +21876,13 @@ function findingMeta(scan, checkId, key) {
|
|
|
21321
21876
|
return scan.checks.find((c) => c.checkId === checkId)?.findings[0]?.meta?.[key];
|
|
21322
21877
|
}
|
|
21323
21878
|
var addBrief = ({ root }) => {
|
|
21324
|
-
const brief2 = BRIEF_PATHS.find((f) =>
|
|
21879
|
+
const brief2 = BRIEF_PATHS.find((f) => existsSync9(path12.join(root, f)));
|
|
21325
21880
|
if (!brief2) return { skip: "There is no product brief yet. Run `aura init` to draft and confirm it first." };
|
|
21326
21881
|
const changes = [
|
|
21327
21882
|
{ path: brief2, content: read(root, brief2) },
|
|
21328
21883
|
{ path: ".aura/.gitignore", content: AURA_GITIGNORE }
|
|
21329
21884
|
];
|
|
21330
|
-
if (
|
|
21885
|
+
if (existsSync9(auraPath(root, "config.json"))) changes.push({ path: ".aura/config.json", content: read(root, ".aura/config.json") });
|
|
21331
21886
|
return { changes, details: [`- Adds \`${brief2}\`, \`.aura/config.json\` and \`.aura/.gitignore\` (keeps Aura's local reports out of git)`] };
|
|
21332
21887
|
};
|
|
21333
21888
|
var addAgentRules = ({ root, agents, vars }) => {
|
|
@@ -21346,14 +21901,14 @@ var installSkills = ({ agents, skills: skills2 }) => {
|
|
|
21346
21901
|
};
|
|
21347
21902
|
var tokenEfficiency = (g) => {
|
|
21348
21903
|
const changes = [];
|
|
21349
|
-
if (!
|
|
21904
|
+
if (!existsSync9(path12.join(g.root, "docs/index.md"))) changes.push({ path: "docs/index.md", content: render("docs/index.md", g.vars) });
|
|
21350
21905
|
changes.push(...rulesChanges(g.root, g.agents, g.vars));
|
|
21351
21906
|
return { changes, details: ["- `docs/index.md`: a map of the repo your agent reads first", "- Lean-context rules in the agent rules file"] };
|
|
21352
21907
|
};
|
|
21353
21908
|
var addCi = ({ root, ctx, vars }) => {
|
|
21354
21909
|
const existing = ctx.files.filter((f) => /^\.github\/workflows\//.test(f));
|
|
21355
21910
|
const file2 = existing.includes(".github/workflows/ci.yml") ? ".github/workflows/checks.yml" : ".github/workflows/ci.yml";
|
|
21356
|
-
if (
|
|
21911
|
+
if (existsSync9(path12.join(root, file2))) return { skip: `${file2} already exists.` };
|
|
21357
21912
|
return {
|
|
21358
21913
|
changes: [{ path: file2, content: render("github/workflows/ci.yml", vars) }],
|
|
21359
21914
|
details: [`- \`${file2}\`: lint, type check, tests and build on every pull request`]
|
|
@@ -21377,7 +21932,7 @@ var setupTesting = ({ root, ctx, vars }) => {
|
|
|
21377
21932
|
};
|
|
21378
21933
|
};
|
|
21379
21934
|
var addEnvExample = ({ root, scan }) => {
|
|
21380
|
-
const file2 = [".env.example", ".env.sample", ".env.template"].find((f) =>
|
|
21935
|
+
const file2 = [".env.example", ".env.sample", ".env.template"].find((f) => existsSync9(path12.join(root, f))) ?? ".env.example";
|
|
21381
21936
|
const existing = read(root, file2);
|
|
21382
21937
|
const declared = new Set(
|
|
21383
21938
|
(existing ?? "").split(/\r?\n/).map((l) => l.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/)?.[1]).filter(Boolean)
|
|
@@ -21434,7 +21989,7 @@ var addLinting = ({ root, ctx }) => {
|
|
|
21434
21989
|
return { changes, devDeps, details };
|
|
21435
21990
|
};
|
|
21436
21991
|
var addPreCommit = ({ root, ctx, vars }) => {
|
|
21437
|
-
if (
|
|
21992
|
+
if (existsSync9(path12.join(root, ".husky/pre-commit"))) return { skip: "A husky pre-commit hook already exists." };
|
|
21438
21993
|
const changes = [{ path: ".husky/pre-commit", content: render("configs/husky-pre-commit", vars) }];
|
|
21439
21994
|
const scripts = withScripts(root, { prepare: "husky" }, false);
|
|
21440
21995
|
if (scripts) changes.push(scripts);
|
|
@@ -21464,7 +22019,7 @@ ${current2.trim()}
|
|
|
21464
22019
|
};
|
|
21465
22020
|
};
|
|
21466
22021
|
var enableDependencyUpdates = ({ root, vars }) => {
|
|
21467
|
-
if ([".github/dependabot.yml", ".github/dependabot.yaml", "renovate.json", ".github/renovate.json"].some((f) =>
|
|
22022
|
+
if ([".github/dependabot.yml", ".github/dependabot.yaml", "renovate.json", ".github/renovate.json"].some((f) => existsSync9(path12.join(root, f)))) {
|
|
21468
22023
|
return { skip: "Dependabot or Renovate is already configured." };
|
|
21469
22024
|
}
|
|
21470
22025
|
return {
|
|
@@ -21544,7 +22099,7 @@ function planMoves(root, ctx, moves) {
|
|
|
21544
22099
|
const alias = aliasFor(toFile);
|
|
21545
22100
|
if (alias) return alias[0] + noExt.slice(alias[1].length);
|
|
21546
22101
|
}
|
|
21547
|
-
let rel =
|
|
22102
|
+
let rel = path12.posix.relative(path12.posix.dirname(fromFile), noExt);
|
|
21548
22103
|
if (!rel.startsWith(".")) rel = `./${rel}`;
|
|
21549
22104
|
return rel;
|
|
21550
22105
|
};
|
|
@@ -21578,7 +22133,7 @@ function planMoves(root, ctx, moves) {
|
|
|
21578
22133
|
}
|
|
21579
22134
|
var moveMisplacedFiles = ({ root, ctx, scan }) => {
|
|
21580
22135
|
const raw = findingMeta(scan, "structure.layout", "moves") ?? [];
|
|
21581
|
-
const moves = raw.map((m) => m.split("=>")).filter(([, to]) => !
|
|
22136
|
+
const moves = raw.map((m) => m.split("=>")).filter(([, to]) => !existsSync9(path12.join(root, to))).slice(0, MAX_MOVES);
|
|
21582
22137
|
if (moves.length === 0) return { skip: "No files to move (or their targets already exist)." };
|
|
21583
22138
|
return { changes: planMoves(root, ctx, moves), details: moves.map(([from, to]) => `- \`${from}\` \u2192 \`${to}\``), verifyTypes: true };
|
|
21584
22139
|
};
|
|
@@ -21669,7 +22224,7 @@ var AuraClient = class _AuraClient {
|
|
|
21669
22224
|
get hasKey() {
|
|
21670
22225
|
return Boolean(this.key);
|
|
21671
22226
|
}
|
|
21672
|
-
async request(method,
|
|
22227
|
+
async request(method, path27, body, timeoutMs = 6e4) {
|
|
21673
22228
|
if (!this.key) throw new AuraApiError("no_key", "No pilot key");
|
|
21674
22229
|
let payload;
|
|
21675
22230
|
if (body !== void 0) {
|
|
@@ -21679,7 +22234,7 @@ var AuraClient = class _AuraClient {
|
|
|
21679
22234
|
}
|
|
21680
22235
|
let res;
|
|
21681
22236
|
try {
|
|
21682
|
-
res = await fetch(`${this.baseUrl}/v1/${
|
|
22237
|
+
res = await fetch(`${this.baseUrl}/v1/${path27}`, {
|
|
21683
22238
|
method,
|
|
21684
22239
|
headers: { authorization: `Bearer ${this.key}`, "content-type": "application/json", "user-agent": `getaura/${AURA_VERSION}` },
|
|
21685
22240
|
body: payload,
|
|
@@ -21743,13 +22298,13 @@ var say = {
|
|
|
21743
22298
|
};
|
|
21744
22299
|
|
|
21745
22300
|
// src/fix-tracking.ts
|
|
21746
|
-
import { existsSync as
|
|
22301
|
+
import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
|
|
21747
22302
|
var FILE = "fixes.json";
|
|
21748
22303
|
var MAX_AGE_MS = 60 * 24 * 60 * 60 * 1e3;
|
|
21749
22304
|
var MAX_CHECKS_PER_SCAN = 5;
|
|
21750
22305
|
function loadTrackedFixes(root) {
|
|
21751
22306
|
try {
|
|
21752
|
-
return
|
|
22307
|
+
return existsSync10(auraPath(root, FILE)) ? JSON.parse(readFileSync10(auraPath(root, FILE), "utf8")) : [];
|
|
21753
22308
|
} catch {
|
|
21754
22309
|
return [];
|
|
21755
22310
|
}
|
|
@@ -21857,70 +22412,6 @@ async function recordAction(root, config2, actionId, report, startedAt, via) {
|
|
|
21857
22412
|
}
|
|
21858
22413
|
}
|
|
21859
22414
|
|
|
21860
|
-
// src/core/git.ts
|
|
21861
|
-
import { mkdirSync as mkdirSync2, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
21862
|
-
import path10 from "path";
|
|
21863
|
-
async function git(root, ...args) {
|
|
21864
|
-
return run("git", args, { cwd: root });
|
|
21865
|
-
}
|
|
21866
|
-
function ghInstallHint(platform = process.platform) {
|
|
21867
|
-
const how = platform === "win32" ? "Install it with `winget install --id GitHub.cli`" : platform === "darwin" ? "Install it with `brew install gh` (or download it from https://cli.github.com)" : "Install it from https://cli.github.com";
|
|
21868
|
-
return `${how}, open a new terminal, then run \`gh auth login\`.`;
|
|
21869
|
-
}
|
|
21870
|
-
async function ghStatus(root) {
|
|
21871
|
-
const v = await run("gh", ["--version"]);
|
|
21872
|
-
if (v.code !== 0) return { installed: false, authed: false, hasRemote: false };
|
|
21873
|
-
const auth = await run("gh", ["auth", "status"], { cwd: root });
|
|
21874
|
-
const remote = await git(root, "remote", "get-url", "origin");
|
|
21875
|
-
return { installed: true, authed: auth.code === 0, hasRemote: remote.code === 0 && /github\.com/.test(remote.stdout) };
|
|
21876
|
-
}
|
|
21877
|
-
var AURA_OWNED = /^(?:\.aura\/|\.claude\/skills\/aura\/|\.agents\/skills\/aura(?:-(?:scan|next|fix|setup))?\/|\.cursor\/commands\/aura-[a-z]+\.md$|\.pi\/prompts\/aura-[a-z]+\.md$|\.claude\/settings\.json$)/;
|
|
21878
|
-
async function dirtyFiles(root) {
|
|
21879
|
-
const r = await git(root, "status", "--porcelain", "-uall");
|
|
21880
|
-
return r.stdout.split("\n").filter(Boolean).map((l) => l.slice(3).replace(/^"|"$/g, "").split(" -> ").pop()).filter((f) => !AURA_OWNED.test(f));
|
|
21881
|
-
}
|
|
21882
|
-
async function currentBranch(root) {
|
|
21883
|
-
return (await git(root, "rev-parse", "--abbrev-ref", "HEAD")).stdout.trim();
|
|
21884
|
-
}
|
|
21885
|
-
async function freeBranchName(root, base) {
|
|
21886
|
-
for (let i = 1; i < 50; i++) {
|
|
21887
|
-
const name = i === 1 ? base : `${base}-${i}`;
|
|
21888
|
-
const local = await git(root, "rev-parse", "--verify", "--quiet", `refs/heads/${name}`);
|
|
21889
|
-
const remote = await git(root, "ls-remote", "--exit-code", "--heads", "origin", name);
|
|
21890
|
-
if (local.code !== 0 && remote.code !== 0) return name;
|
|
21891
|
-
}
|
|
21892
|
-
return `${base}-${Date.now()}`;
|
|
21893
|
-
}
|
|
21894
|
-
function writeChanges(root, changes) {
|
|
21895
|
-
for (const c of changes) {
|
|
21896
|
-
const full = path10.join(root, c.path);
|
|
21897
|
-
if (!isInside(root, full)) throw new Error(`Refusing to write outside the repo: ${c.path}`);
|
|
21898
|
-
if (c.content === null) rmSync(full, { force: true });
|
|
21899
|
-
else {
|
|
21900
|
-
mkdirSync2(path10.dirname(full), { recursive: true });
|
|
21901
|
-
writeFileSync2(full, c.content);
|
|
21902
|
-
}
|
|
21903
|
-
}
|
|
21904
|
-
}
|
|
21905
|
-
async function commitPushPr(root, opts) {
|
|
21906
|
-
const commit = await git(root, "commit", "-m", opts.title, "-m", "Opened by Aura (https://github.com/JohnFazio1/aura).");
|
|
21907
|
-
const base = { branch: opts.branch, url: null };
|
|
21908
|
-
if (commit.code !== 0) {
|
|
21909
|
-
const out = commit.stderr || commit.stdout;
|
|
21910
|
-
const error62 = /tell me who you are|user\.email/i.test(out) ? 'git doesn\u2019t know your name and email yet. Run these two commands:\n git config --global user.name "Your Name"\n git config --global user.email "you@example.com"' : out;
|
|
21911
|
-
return { ...base, committed: false, pushed: false, error: error62 };
|
|
21912
|
-
}
|
|
21913
|
-
if (!opts.openPr) return { ...base, committed: true, pushed: false };
|
|
21914
|
-
const push = await git(root, "push", "-u", "origin", opts.branch);
|
|
21915
|
-
if (push.code !== 0) return { ...base, committed: true, pushed: false, error: push.stderr };
|
|
21916
|
-
const pr = await run("gh", ["pr", "create", "--base", opts.base, "--head", opts.branch, "--title", opts.title, "--body-file", "-"], {
|
|
21917
|
-
cwd: root,
|
|
21918
|
-
input: opts.body
|
|
21919
|
-
});
|
|
21920
|
-
if (pr.code !== 0) return { ...base, committed: true, pushed: true, error: pr.stderr };
|
|
21921
|
-
return { ...base, url: pr.stdout.trim().split("\n").pop() ?? null, committed: true, pushed: true };
|
|
21922
|
-
}
|
|
21923
|
-
|
|
21924
22415
|
// src/checks/foundations.ts
|
|
21925
22416
|
import YAML from "yaml";
|
|
21926
22417
|
var ciWorkflow = {
|
|
@@ -22120,10 +22611,10 @@ var typecheck = {
|
|
|
22120
22611
|
applies: (ctx) => ctx.stack.typescript,
|
|
22121
22612
|
async run(ctx) {
|
|
22122
22613
|
if (ctx.options.fast) return { status: "unknown", summary: "Skipped in fast mode." };
|
|
22123
|
-
const { existsSync:
|
|
22124
|
-
const
|
|
22125
|
-
const tsc =
|
|
22126
|
-
if (!
|
|
22614
|
+
const { existsSync: existsSync21 } = await import("fs");
|
|
22615
|
+
const path27 = await import("path");
|
|
22616
|
+
const tsc = path27.join(ctx.root, "node_modules", ".bin", process.platform === "win32" ? "tsc.cmd" : "tsc");
|
|
22617
|
+
if (!existsSync21(tsc)) return { status: "unknown", summary: "Dependencies not installed, so the type check was skipped." };
|
|
22127
22618
|
const r = await run(tsc, ["--noEmit", "--pretty", "false"], { cwd: ctx.root, timeoutMs: 18e4 });
|
|
22128
22619
|
if (r.timedOut) return { status: "unknown", summary: "Type check timed out." };
|
|
22129
22620
|
if (r.code === 0) return { status: "pass", summary: "No type errors.", engine: "tsc" };
|
|
@@ -22143,6 +22634,73 @@ var typecheck = {
|
|
|
22143
22634
|
};
|
|
22144
22635
|
var foundationChecks = [ciWorkflow, tests, readme, envExample, lockfile, lintFormat, typecheck];
|
|
22145
22636
|
|
|
22637
|
+
// src/connectors/snapshot.ts
|
|
22638
|
+
import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
|
|
22639
|
+
var REFUSAL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
22640
|
+
function withRefusals(states, snap, now = Date.now()) {
|
|
22641
|
+
return Object.fromEntries(
|
|
22642
|
+
Object.entries(states).map(([id, s]) => {
|
|
22643
|
+
const refusal = snap?.refused?.[id];
|
|
22644
|
+
return [id, s.state === "off" && refusal && now - Date.parse(refusal.at) < REFUSAL_MS ? { state: "unavailable", why: refusal.why } : s];
|
|
22645
|
+
})
|
|
22646
|
+
);
|
|
22647
|
+
}
|
|
22648
|
+
var FILE2 = "cache/connectors.json";
|
|
22649
|
+
function loadSnapshots(root) {
|
|
22650
|
+
try {
|
|
22651
|
+
return existsSync11(auraPath(root, FILE2)) ? JSON.parse(readFileSync11(auraPath(root, FILE2), "utf8")) : {};
|
|
22652
|
+
} catch {
|
|
22653
|
+
return {};
|
|
22654
|
+
}
|
|
22655
|
+
}
|
|
22656
|
+
function saveSnapshot(root, toolId, states, refusedNow = {}) {
|
|
22657
|
+
const all = loadSnapshots(root);
|
|
22658
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
22659
|
+
const refused = { ...all[toolId]?.refused };
|
|
22660
|
+
for (const [id, why] of Object.entries(refusedNow)) refused[id] = { why, at: now };
|
|
22661
|
+
for (const [id, s] of Object.entries(states)) if (s.state === "on") delete refused[id];
|
|
22662
|
+
all[toolId] = {
|
|
22663
|
+
checkedAt: now,
|
|
22664
|
+
settings: Object.fromEntries(Object.entries(states).map(([id, s]) => [id, s.state])),
|
|
22665
|
+
...Object.keys(refused).length ? { refused } : {}
|
|
22666
|
+
};
|
|
22667
|
+
writeAuraFile(root, FILE2, `${JSON.stringify(all, null, 2)}
|
|
22668
|
+
`);
|
|
22669
|
+
}
|
|
22670
|
+
var isStale = (snap, now = Date.now()) => !snap || now - Date.parse(snap.checkedAt) > 24 * 60 * 60 * 1e3;
|
|
22671
|
+
|
|
22672
|
+
// src/checks/security/tool-settings.ts
|
|
22673
|
+
function toolSettingsCheck(connector) {
|
|
22674
|
+
return {
|
|
22675
|
+
id: `security.${connector.id}-settings`,
|
|
22676
|
+
category: "security",
|
|
22677
|
+
title: `${connector.name} security settings`,
|
|
22678
|
+
weight: 3,
|
|
22679
|
+
applies: (ctx) => {
|
|
22680
|
+
const snap = loadSnapshots(ctx.root)[connector.id];
|
|
22681
|
+
return Boolean(snap && Object.values(snap.settings).some((s) => s !== "unavailable"));
|
|
22682
|
+
},
|
|
22683
|
+
run(ctx) {
|
|
22684
|
+
const snap = loadSnapshots(ctx.root)[connector.id];
|
|
22685
|
+
const available = connector.settings.filter((s) => snap.settings[s.id] === "on" || snap.settings[s.id] === "off");
|
|
22686
|
+
const off = available.filter((s) => snap.settings[s.id] === "off");
|
|
22687
|
+
const engine = `${connector.name}, checked ${snap.checkedAt.slice(0, 10)}`;
|
|
22688
|
+
if (off.length === 0) return { status: "pass", summary: `All ${available.length} available ${connector.name} security settings are on.`, engine };
|
|
22689
|
+
const finding = makeFinding(this, {
|
|
22690
|
+
severity: off.some((s) => /protect|push|secret/.test(s.id)) ? "medium" : "low",
|
|
22691
|
+
title: `${off.length} ${connector.name} security setting(s) off`,
|
|
22692
|
+
detail: `Off in ${connector.name}: ${off.map((s) => s.label).join("; ")}.`,
|
|
22693
|
+
why: "These settings catch leaked keys, vulnerable packages and risky changes before they cause harm, and they cost nothing to turn on.",
|
|
22694
|
+
locations: [],
|
|
22695
|
+
actionId: connector.actionId,
|
|
22696
|
+
key: `${connector.id}-settings`
|
|
22697
|
+
});
|
|
22698
|
+
const score = (available.length - off.length) / available.length;
|
|
22699
|
+
return { status: score === 0 ? "fail" : "partial", score, summary: finding.title, findings: [finding], engine };
|
|
22700
|
+
}
|
|
22701
|
+
};
|
|
22702
|
+
}
|
|
22703
|
+
|
|
22146
22704
|
// src/checks/security/api-routes.ts
|
|
22147
22705
|
var AUTH_SIGNALS = [
|
|
22148
22706
|
/\bauth\.getUser\s*\(/,
|
|
@@ -22249,9 +22807,9 @@ var apiRoutes = {
|
|
|
22249
22807
|
|
|
22250
22808
|
// src/core/tools.ts
|
|
22251
22809
|
import { createHash as createHash3 } from "crypto";
|
|
22252
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
22253
|
-
import
|
|
22254
|
-
import
|
|
22810
|
+
import { chmodSync as chmodSync2, existsSync as existsSync12, mkdirSync as mkdirSync3, mkdtempSync, readdirSync as readdirSync4, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
22811
|
+
import os3 from "os";
|
|
22812
|
+
import path13 from "path";
|
|
22255
22813
|
var GITLEAKS_SHA = {
|
|
22256
22814
|
darwin_arm64: "b40ab0ae55c505963e365f271a8d3846efbc170aa17f2607f13df610a9aeb6a5",
|
|
22257
22815
|
darwin_x64: "dfe101a4db2255fc85120ac7f3d25e4342c3c20cf749f2c20a18081af1952709",
|
|
@@ -22269,7 +22827,7 @@ var OSV_SHA = {
|
|
|
22269
22827
|
windows_arm64: "ca1379da0e408279ef2c85cde0846a903495738a91c7b065a208048437c1686c"
|
|
22270
22828
|
};
|
|
22271
22829
|
function tarCommand(platform = process.platform, systemRoot = process.env.SystemRoot) {
|
|
22272
|
-
return platform === "win32" ?
|
|
22830
|
+
return platform === "win32" ? path13.win32.join(systemRoot || "C:\\Windows", "System32", "tar.exe") : "tar";
|
|
22273
22831
|
}
|
|
22274
22832
|
var TOOLS = {
|
|
22275
22833
|
gitleaks: {
|
|
@@ -22305,8 +22863,8 @@ function ensureTool(name, noDownload = false) {
|
|
|
22305
22863
|
async function resolveTool(name, noDownload) {
|
|
22306
22864
|
const spec = TOOLS[name];
|
|
22307
22865
|
const exe = process.platform === "win32" ? `${name}.exe` : name;
|
|
22308
|
-
const cached2 =
|
|
22309
|
-
if (
|
|
22866
|
+
const cached2 = path13.join(auraBinDir(), `${name}-${spec.version}`, exe);
|
|
22867
|
+
if (existsSync12(cached2)) return { path: cached2, version: spec.version };
|
|
22310
22868
|
if (await commandExists(name)) {
|
|
22311
22869
|
const v = await run(name, ["version"]);
|
|
22312
22870
|
return { path: name, version: (v.stdout.match(/\d+\.\d+\.\d+/)?.[0] ?? "system").trim() };
|
|
@@ -22326,20 +22884,20 @@ async function download(name, spec, target) {
|
|
|
22326
22884
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
22327
22885
|
const digest = createHash3("sha256").update(buf).digest("hex");
|
|
22328
22886
|
if (digest !== asset.sha256) throw new Error(`Checksum mismatch for ${asset.file}`);
|
|
22329
|
-
const dir =
|
|
22887
|
+
const dir = path13.dirname(target);
|
|
22330
22888
|
mkdirSync3(dir, { recursive: true });
|
|
22331
|
-
const tmp = mkdtempSync(
|
|
22889
|
+
const tmp = mkdtempSync(path13.join(os3.tmpdir(), `aura-${name}-`));
|
|
22332
22890
|
try {
|
|
22333
22891
|
if (asset.archive === null) {
|
|
22334
22892
|
writeFileSync3(target, buf);
|
|
22335
22893
|
} else {
|
|
22336
|
-
const archivePath =
|
|
22894
|
+
const archivePath = path13.join(tmp, asset.file);
|
|
22337
22895
|
writeFileSync3(archivePath, buf);
|
|
22338
22896
|
const r = await run(tarCommand(), ["-xf", archivePath, "-C", tmp]);
|
|
22339
22897
|
if (r.code !== 0) return null;
|
|
22340
|
-
const exe =
|
|
22898
|
+
const exe = readdirSync4(tmp).find((f) => f === name || f === `${name}.exe`);
|
|
22341
22899
|
if (!exe) return null;
|
|
22342
|
-
renameSync(
|
|
22900
|
+
renameSync(path13.join(tmp, exe), target);
|
|
22343
22901
|
}
|
|
22344
22902
|
chmodSync2(target, 493);
|
|
22345
22903
|
return { path: target, version: spec.version };
|
|
@@ -22610,9 +23168,9 @@ var rls = {
|
|
|
22610
23168
|
};
|
|
22611
23169
|
|
|
22612
23170
|
// src/checks/security/secrets.ts
|
|
22613
|
-
import { existsSync as
|
|
22614
|
-
import
|
|
22615
|
-
import
|
|
23171
|
+
import { existsSync as existsSync13, mkdtempSync as mkdtempSync2, readFileSync as readFileSync12, rmSync as rmSync3 } from "fs";
|
|
23172
|
+
import os4 from "os";
|
|
23173
|
+
import path14 from "path";
|
|
22616
23174
|
|
|
22617
23175
|
// src/core/ignore.ts
|
|
22618
23176
|
function globToRegex(glob) {
|
|
@@ -22650,15 +23208,15 @@ async function gitleaksHistory(ctx) {
|
|
|
22650
23208
|
if (!ctx.tracked) return null;
|
|
22651
23209
|
const bin = await ensureTool("gitleaks", ctx.options.noDownload);
|
|
22652
23210
|
if (!bin) return null;
|
|
22653
|
-
const dir = mkdtempSync2(
|
|
22654
|
-
const report =
|
|
23211
|
+
const dir = mkdtempSync2(path14.join(os4.tmpdir(), "aura-gitleaks-"));
|
|
23212
|
+
const report = path14.join(dir, "report.json");
|
|
22655
23213
|
try {
|
|
22656
23214
|
const r = await run(bin.path, ["git", "--no-banner", "--redact", "--report-format", "json", "--report-path", report, "--exit-code", "0", "."], {
|
|
22657
23215
|
cwd: ctx.root,
|
|
22658
23216
|
timeoutMs: 18e4
|
|
22659
23217
|
});
|
|
22660
|
-
if (r.code !== 0 || !
|
|
22661
|
-
return { hits: JSON.parse(
|
|
23218
|
+
if (r.code !== 0 || !existsSync13(report)) return null;
|
|
23219
|
+
return { hits: JSON.parse(readFileSync12(report, "utf8")), engine: `gitleaks ${bin.version}` };
|
|
22662
23220
|
} catch {
|
|
22663
23221
|
return null;
|
|
22664
23222
|
} finally {
|
|
@@ -22770,10 +23328,10 @@ var envFiles = {
|
|
|
22770
23328
|
};
|
|
22771
23329
|
|
|
22772
23330
|
// src/checks/security.ts
|
|
22773
|
-
var securityChecks = [secrets, envFiles, deps, rls, apiRoutes, headers];
|
|
23331
|
+
var securityChecks = [secrets, envFiles, deps, rls, apiRoutes, headers, ...CONNECTORS.map(toolSettingsCheck)];
|
|
22774
23332
|
|
|
22775
23333
|
// src/checks/structure.ts
|
|
22776
|
-
import
|
|
23334
|
+
import path15 from "path";
|
|
22777
23335
|
var GENERATED = /(?:^|\/)(?:supabase\/migrations|generated|__generated__|\.generated)\/|\.(?:d\.ts|min\.js)$|(?:^|\/)(?:database|supabase)\.types\.ts$|(?:^|\/)components\/ui\//;
|
|
22778
23336
|
function appSource(ctx) {
|
|
22779
23337
|
return ctx.files.filter((f) => isSourceFile(f) && !GENERATED.test(f));
|
|
@@ -22862,7 +23420,7 @@ var duplicates = {
|
|
|
22862
23420
|
const findings = pairs.map(
|
|
22863
23421
|
([a, b, sim]) => makeFinding(this, {
|
|
22864
23422
|
severity: "low",
|
|
22865
|
-
title: `${
|
|
23423
|
+
title: `${path15.basename(a)} and ${path15.basename(b)} are near-duplicates`,
|
|
22866
23424
|
detail: `${a} and ${b} are ${Math.round(sim * 100)}% the same.`,
|
|
22867
23425
|
why: "Duplicates drift apart over time: a bug gets fixed in one copy and not the other, and your agent can\u2019t tell which one is right.",
|
|
22868
23426
|
locations: [{ file: a }, { file: b }],
|
|
@@ -22876,7 +23434,7 @@ var duplicates = {
|
|
|
22876
23434
|
};
|
|
22877
23435
|
var ROOT_ALLOWED = /^(?:[^/]+\.config\.[cm]?[jt]s|middleware\.[cm]?[jt]s|proxy\.[cm]?[jt]s|instrumentation(?:-client)?\.[cm]?[jt]s|next-env\.d\.ts|global\.d\.ts|env\.d\.ts|sentry\.[\w.]+\.[cm]?[jt]s)$/;
|
|
22878
23436
|
function suggestTarget(file2, srcDir) {
|
|
22879
|
-
const base =
|
|
23437
|
+
const base = path15.posix.basename(file2);
|
|
22880
23438
|
const prefix = srcDir ? "src/" : "";
|
|
22881
23439
|
const isComponent = /^[A-Z][A-Za-z0-9]*\.(?:tsx|jsx)$/.test(base);
|
|
22882
23440
|
if (isComponent) return `${prefix}components/${base}`;
|
|
@@ -22989,21 +23547,21 @@ async function runScan(root, options, onProgress) {
|
|
|
22989
23547
|
}
|
|
22990
23548
|
|
|
22991
23549
|
// src/commands/fix.ts
|
|
22992
|
-
import { existsSync as
|
|
22993
|
-
import
|
|
23550
|
+
import { existsSync as existsSync18, readFileSync as readFileSync19, writeFileSync as writeFileSync6 } from "fs";
|
|
23551
|
+
import path19 from "path";
|
|
22994
23552
|
|
|
22995
23553
|
// src/agent/tools.ts
|
|
22996
|
-
import { existsSync as
|
|
22997
|
-
import
|
|
23554
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync4, readFileSync as readFileSync13, rmSync as rmSync4, statSync as statSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
23555
|
+
import path16 from "path";
|
|
22998
23556
|
var MAX_OUTPUT = 12e3;
|
|
22999
23557
|
var MAX_FILE_LINES = 800;
|
|
23000
23558
|
var FORBIDDEN = /^(?:\.git|node_modules|\.next|\.vercel)(?:\/|$)|(?:^|\/)\.env(?:\.(?!example$|sample$|template$)[^/]+)?$/;
|
|
23001
23559
|
function safePath(root, rel) {
|
|
23002
23560
|
if (typeof rel !== "string" || !rel.trim()) throw new Error("A file path is required.");
|
|
23003
|
-
const normalized =
|
|
23004
|
-
if (normalized.startsWith("../") || normalized === ".." ||
|
|
23561
|
+
const normalized = path16.posix.normalize(rel.replace(/\\/g, "/").replace(/^\.\//, ""));
|
|
23562
|
+
if (normalized.startsWith("../") || normalized === ".." || path16.isAbsolute(normalized)) throw new Error(`Path is outside the repo: ${rel}`);
|
|
23005
23563
|
if (FORBIDDEN.test(normalized)) throw new Error(`Aura doesn\u2019t let the agent touch ${normalized}.`);
|
|
23006
|
-
const full =
|
|
23564
|
+
const full = path16.join(root, normalized);
|
|
23007
23565
|
if (!isInside(root, full)) throw new Error(`Path is outside the repo: ${rel}`);
|
|
23008
23566
|
return normalized;
|
|
23009
23567
|
}
|
|
@@ -23024,10 +23582,10 @@ async function runTool(root, call, allowedCommands2) {
|
|
|
23024
23582
|
}
|
|
23025
23583
|
case "read_file": {
|
|
23026
23584
|
const rel = safePath(root, input2.path);
|
|
23027
|
-
const full =
|
|
23028
|
-
if (!
|
|
23585
|
+
const full = path16.join(root, rel);
|
|
23586
|
+
if (!existsSync14(full)) return { output: `File not found: ${rel}` };
|
|
23029
23587
|
if (statSync2(full).size > 1e6) return { output: `${rel} is too large to read.` };
|
|
23030
|
-
const lines =
|
|
23588
|
+
const lines = readFileSync13(full, "utf8").split("\n");
|
|
23031
23589
|
const start = Math.max(1, Number(input2.startLine) || 1);
|
|
23032
23590
|
const end = Math.min(lines.length, Number(input2.endLine) || start + MAX_FILE_LINES - 1);
|
|
23033
23591
|
const body = lines.slice(start - 1, end).map((l, i) => `${start + i}: ${l}`).join("\n");
|
|
@@ -23044,7 +23602,7 @@ async function runTool(root, call, allowedCommands2) {
|
|
|
23044
23602
|
if (FORBIDDEN.test(f) || glob && !glob.test(f)) continue;
|
|
23045
23603
|
let text;
|
|
23046
23604
|
try {
|
|
23047
|
-
text =
|
|
23605
|
+
text = readFileSync13(path16.join(root, f), "utf8");
|
|
23048
23606
|
} catch {
|
|
23049
23607
|
continue;
|
|
23050
23608
|
}
|
|
@@ -23063,14 +23621,14 @@ async function runTool(root, call, allowedCommands2) {
|
|
|
23063
23621
|
output: "Refused: the content contains a [REDACTED:\u2026] placeholder. Secrets are hidden from you on purpose. Read the value from an environment variable (process.env.NAME) instead of writing it."
|
|
23064
23622
|
};
|
|
23065
23623
|
}
|
|
23066
|
-
mkdirSync4(
|
|
23067
|
-
writeFileSync4(
|
|
23624
|
+
mkdirSync4(path16.dirname(path16.join(root, rel)), { recursive: true });
|
|
23625
|
+
writeFileSync4(path16.join(root, rel), content);
|
|
23068
23626
|
return { output: `Wrote ${rel} (${content.split("\n").length} lines).`, touched: rel };
|
|
23069
23627
|
}
|
|
23070
23628
|
case "delete_file": {
|
|
23071
23629
|
const rel = safePath(root, input2.path);
|
|
23072
|
-
if (!
|
|
23073
|
-
rmSync4(
|
|
23630
|
+
if (!existsSync14(path16.join(root, rel))) return { output: `File not found: ${rel}` };
|
|
23631
|
+
rmSync4(path16.join(root, rel));
|
|
23074
23632
|
return { output: `Deleted ${rel}.`, touched: rel };
|
|
23075
23633
|
}
|
|
23076
23634
|
case "run_command": {
|
|
@@ -23092,7 +23650,7 @@ ${(r.stdout + "\n" + r.stderr).trim().slice(-6e3)}`;
|
|
|
23092
23650
|
|
|
23093
23651
|
// src/api/explain.ts
|
|
23094
23652
|
import { createHash as createHash4 } from "crypto";
|
|
23095
|
-
import { existsSync as
|
|
23653
|
+
import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
|
|
23096
23654
|
|
|
23097
23655
|
// src/report.ts
|
|
23098
23656
|
import pc2 from "picocolors";
|
|
@@ -23281,7 +23839,7 @@ var CACHE_FILE = "cache/explanations.json";
|
|
|
23281
23839
|
var MAX_EXPLAIN = 25;
|
|
23282
23840
|
function loadCache(root) {
|
|
23283
23841
|
try {
|
|
23284
|
-
return JSON.parse(
|
|
23842
|
+
return JSON.parse(readFileSync14(auraPath(root, CACHE_FILE), "utf8"));
|
|
23285
23843
|
} catch {
|
|
23286
23844
|
return {};
|
|
23287
23845
|
}
|
|
@@ -23302,8 +23860,8 @@ function excerptFor(root, f, context = 4) {
|
|
|
23302
23860
|
}
|
|
23303
23861
|
function productContext(root) {
|
|
23304
23862
|
const file2 = auraPath(root, "brief.md");
|
|
23305
|
-
if (!
|
|
23306
|
-
return redact(
|
|
23863
|
+
if (!existsSync15(file2)) return void 0;
|
|
23864
|
+
return redact(readFileSync14(file2, "utf8")).slice(0, 1800);
|
|
23307
23865
|
}
|
|
23308
23866
|
async function addExplanations(root, scan, client) {
|
|
23309
23867
|
const cache = loadCache(root);
|
|
@@ -23347,19 +23905,22 @@ async function addExplanations(root, scan, client) {
|
|
|
23347
23905
|
|
|
23348
23906
|
// src/commands/simple.ts
|
|
23349
23907
|
import { execFileSync } from "child_process";
|
|
23350
|
-
import { existsSync as
|
|
23351
|
-
import
|
|
23908
|
+
import { existsSync as existsSync17, readdirSync as readdirSync5, readFileSync as readFileSync18 } from "fs";
|
|
23909
|
+
import path18 from "path";
|
|
23352
23910
|
import YAML2 from "yaml";
|
|
23353
23911
|
|
|
23354
23912
|
// src/commands/inventory.ts
|
|
23355
|
-
import { readFileSync as
|
|
23913
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
23356
23914
|
function accountState(root, services) {
|
|
23357
23915
|
const inv = loadInventory(root);
|
|
23358
|
-
return {
|
|
23916
|
+
return {
|
|
23917
|
+
inventoryComplete: services.every((s) => inv.services[s]?.owner) && Boolean(inv.githubOrg),
|
|
23918
|
+
secured: { ...inv.secured, github: Boolean(inv.githubSecurity || inv.secured?.github) }
|
|
23919
|
+
};
|
|
23359
23920
|
}
|
|
23360
23921
|
function loadInventory(root) {
|
|
23361
23922
|
try {
|
|
23362
|
-
return JSON.parse(
|
|
23923
|
+
return JSON.parse(readFileSync15(auraPath(root, "inventory.json"), "utf8"));
|
|
23363
23924
|
} catch {
|
|
23364
23925
|
return { version: 1, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), services: {} };
|
|
23365
23926
|
}
|
|
@@ -23446,10 +24007,10 @@ async function inventoryCommand(flags) {
|
|
|
23446
24007
|
}
|
|
23447
24008
|
|
|
23448
24009
|
// src/history.ts
|
|
23449
|
-
import { readFileSync as
|
|
24010
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
23450
24011
|
function loadHistory(root) {
|
|
23451
24012
|
try {
|
|
23452
|
-
const data = JSON.parse(
|
|
24013
|
+
const data = JSON.parse(readFileSync16(auraPath(root, "history.json"), "utf8"));
|
|
23453
24014
|
return Array.isArray(data) ? data : [];
|
|
23454
24015
|
} catch {
|
|
23455
24016
|
return [];
|
|
@@ -23497,10 +24058,10 @@ function banner(colors = pc3.isColorSupported) {
|
|
|
23497
24058
|
// src/api/connect.ts
|
|
23498
24059
|
import { spawn as spawn2 } from "child_process";
|
|
23499
24060
|
import { constants, generateKeyPairSync, privateDecrypt } from "crypto";
|
|
23500
|
-
import { existsSync as
|
|
23501
|
-
import
|
|
23502
|
-
import
|
|
23503
|
-
var pendingFile = () =>
|
|
24061
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync5, readFileSync as readFileSync17, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
24062
|
+
import os5 from "os";
|
|
24063
|
+
import path17 from "path";
|
|
24064
|
+
var pendingFile = () => path17.join(os5.homedir(), ".aura", "pending-connect.json");
|
|
23504
24065
|
async function postJson(apiUrl, route, body) {
|
|
23505
24066
|
let res;
|
|
23506
24067
|
try {
|
|
@@ -23519,14 +24080,14 @@ async function postJson(apiUrl, route, body) {
|
|
|
23519
24080
|
}
|
|
23520
24081
|
function loadPending(apiUrl, now) {
|
|
23521
24082
|
try {
|
|
23522
|
-
const p2 = JSON.parse(
|
|
24083
|
+
const p2 = JSON.parse(readFileSync17(pendingFile(), "utf8"));
|
|
23523
24084
|
if (p2.apiUrl === apiUrl && Date.parse(p2.expiresAt) - now > 6e4) return p2;
|
|
23524
24085
|
} catch {
|
|
23525
24086
|
}
|
|
23526
24087
|
return null;
|
|
23527
24088
|
}
|
|
23528
24089
|
function clearPendingConnect() {
|
|
23529
|
-
if (
|
|
24090
|
+
if (existsSync16(pendingFile())) rmSync5(pendingFile(), { force: true });
|
|
23530
24091
|
}
|
|
23531
24092
|
async function startOrResumeConnect(apiUrl, now = Date.now()) {
|
|
23532
24093
|
const existing = loadPending(apiUrl, now);
|
|
@@ -23538,7 +24099,7 @@ async function startOrResumeConnect(apiUrl, now = Date.now()) {
|
|
|
23538
24099
|
});
|
|
23539
24100
|
const res = ConnectStartResponse.parse(await postJson(apiUrl, "connect", { publicKey }));
|
|
23540
24101
|
const pending = { apiUrl, privateKey, ...res };
|
|
23541
|
-
mkdirSync5(
|
|
24102
|
+
mkdirSync5(path17.dirname(pendingFile()), { recursive: true });
|
|
23542
24103
|
writeFileSync5(pendingFile(), JSON.stringify(pending, null, 2) + "\n", { mode: 384 });
|
|
23543
24104
|
return { ...pending, resumed: false };
|
|
23544
24105
|
}
|
|
@@ -23631,7 +24192,7 @@ function stepRun(step, hasKey) {
|
|
|
23631
24192
|
}
|
|
23632
24193
|
function hint(step, hasKey) {
|
|
23633
24194
|
const run2 = stepRun(step, hasKey);
|
|
23634
|
-
const what = run2.kind === "apply" ? "opens a pull request" : run2.kind === "fix" ? run2.withOwnAgent ? "writes a task for your coding agent" : "Aura\u2019s agent fixes it in a pull request" : run2.kind === "guide" ? "shows step-by-step instructions" : "a few questions";
|
|
24195
|
+
const what = connectorForAction(step.actionId) ? `turns on ${connectorForAction(step.actionId).name}\u2019s security settings, after you agree` : run2.kind === "apply" ? "opens a pull request" : run2.kind === "fix" ? run2.withOwnAgent ? "writes a task for your coding agent" : "Aura\u2019s agent fixes it in a pull request" : run2.kind === "guide" ? "shows step-by-step instructions" : "a few questions";
|
|
23635
24196
|
return [what, step.impact > 0 ? `+${step.impact} points` : "", effortLabel(step.effort)].filter(Boolean).join(" \xB7 ");
|
|
23636
24197
|
}
|
|
23637
24198
|
async function pickAndRunSteps(steps, limit = 10) {
|
|
@@ -23657,7 +24218,10 @@ async function pickAndRunSteps(steps, limit = 10) {
|
|
|
23657
24218
|
console.log(`
|
|
23658
24219
|
${pc.magenta("\u2726")} ${pc.bold(step.title)}`);
|
|
23659
24220
|
const run2 = stepRun(step, hasKey);
|
|
23660
|
-
const code = run2.kind === "apply" ?
|
|
24221
|
+
const code = run2.kind === "apply" ? (
|
|
24222
|
+
// Settings changes always ask first, even when picked here; pull requests don't need to.
|
|
24223
|
+
await applyCommand(run2.actionId, { yes: !connectorForAction(run2.actionId), via: "checklist" })
|
|
24224
|
+
) : run2.kind === "fix" ? await fixCommand(run2.actionId, run2.withOwnAgent ? { agent: true, via: "checklist" } : { yes: true, via: "checklist" }) : run2.kind === "guide" ? await guideCommand(run2.topic, {}) : await inventoryCommand({});
|
|
23661
24225
|
if (run2.kind === "guide" || run2.kind === "inventory") {
|
|
23662
24226
|
const root = await findRepoRoot();
|
|
23663
24227
|
await recordUsage(root, loadConfig(root), {
|
|
@@ -23681,9 +24245,9 @@ ${pc.bold(" Done")}`);
|
|
|
23681
24245
|
// src/commands/simple.ts
|
|
23682
24246
|
async function latestScan(root) {
|
|
23683
24247
|
const file2 = auraPath(root, "scan.json");
|
|
23684
|
-
if (
|
|
24248
|
+
if (existsSync17(file2)) {
|
|
23685
24249
|
try {
|
|
23686
|
-
const scan = JSON.parse(
|
|
24250
|
+
const scan = JSON.parse(readFileSync18(file2, "utf8"));
|
|
23687
24251
|
const head = execFileSync("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
23688
24252
|
const dirty = execFileSync("git", ["status", "--porcelain"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
23689
24253
|
if (scan.commit === head && !dirty && Date.now() - Date.parse(scan.scannedAt) < 24 * 36e5) return scan;
|
|
@@ -23798,9 +24362,9 @@ async function configCommand(key, value) {
|
|
|
23798
24362
|
return 0;
|
|
23799
24363
|
}
|
|
23800
24364
|
function loadGuides() {
|
|
23801
|
-
const dir =
|
|
23802
|
-
return
|
|
23803
|
-
const text =
|
|
24365
|
+
const dir = path18.join(templatesDir(), "guides");
|
|
24366
|
+
return readdirSync5(dir).filter((f) => f.endsWith(".md")).map((f) => {
|
|
24367
|
+
const text = readFileSync18(path18.join(dir, f), "utf8");
|
|
23804
24368
|
const fm = text.match(/^---\n([\s\S]*?)\n---\n?/);
|
|
23805
24369
|
const meta3 = fm ? YAML2.parse(fm[1]) : {};
|
|
23806
24370
|
return { id: f.replace(/\.md$/, ""), title: meta3.title ?? f, summary: meta3.summary ?? "", body: fm ? text.slice(fm[0].length) : text };
|
|
@@ -23868,7 +24432,7 @@ function allowedCommands(root, pm, typescript) {
|
|
|
23868
24432
|
const pkg = readJson(root, "package.json");
|
|
23869
24433
|
const run2 = pmCommands(pm).run;
|
|
23870
24434
|
const cmds = ["test", "lint", "typecheck", "build"].filter((s) => pkg?.scripts?.[s]).map((s) => `${run2} ${s}`);
|
|
23871
|
-
if (typescript && !pkg?.scripts?.typecheck &&
|
|
24435
|
+
if (typescript && !pkg?.scripts?.typecheck && existsSync18(path19.join(root, "node_modules/.bin/tsc"))) cmds.push(`${pmCommands(pm).exec} tsc --noEmit`);
|
|
23872
24436
|
return cmds;
|
|
23873
24437
|
}
|
|
23874
24438
|
function taskFor(root, scan, action2, findings) {
|
|
@@ -23919,8 +24483,8 @@ async function untrackEnvFiles(root, findings) {
|
|
|
23919
24483
|
const files = findings.filter((f) => f.checkId === "security.env-files").map((f) => f.locations[0].file);
|
|
23920
24484
|
for (const f of files) await git(root, "rm", "-q", "--cached", "--", f);
|
|
23921
24485
|
if (files.length) {
|
|
23922
|
-
const gi =
|
|
23923
|
-
const current2 =
|
|
24486
|
+
const gi = path19.join(root, ".gitignore");
|
|
24487
|
+
const current2 = existsSync18(gi) ? readFileSync19(gi, "utf8") : "";
|
|
23924
24488
|
if (!/^\.env\*?$/m.test(current2)) writeFileSync6(gi, `${current2.trimEnd()}
|
|
23925
24489
|
|
|
23926
24490
|
# Environment files hold secrets
|
|
@@ -24097,6 +24661,128 @@ ${pc.bold(action2.title)} ${pc.dim(`${findings.length} finding(s)`)}`);
|
|
|
24097
24661
|
}
|
|
24098
24662
|
}
|
|
24099
24663
|
|
|
24664
|
+
// src/connectors/engine.ts
|
|
24665
|
+
async function readSettings(connector, ctx) {
|
|
24666
|
+
const states = await Promise.all(connector.settings.map((s) => s.read(ctx).catch(() => ({ state: "off" }))));
|
|
24667
|
+
return Object.fromEntries(connector.settings.map((s, i) => [s.id, states[i]]));
|
|
24668
|
+
}
|
|
24669
|
+
async function enableSettings(connector, ctx, states) {
|
|
24670
|
+
const results = [];
|
|
24671
|
+
for (const setting of connector.settings) {
|
|
24672
|
+
const s = states[setting.id];
|
|
24673
|
+
if (s.state === "on") results.push({ id: setting.id, result: "already-on" });
|
|
24674
|
+
else if (s.state === "unavailable") results.push({ id: setting.id, result: "unavailable", why: s.why });
|
|
24675
|
+
else {
|
|
24676
|
+
const out = await setting.enable(ctx).catch(() => ({ ok: false }));
|
|
24677
|
+
results.push(
|
|
24678
|
+
out.ok ? { id: setting.id, result: "turned-on" } : out.unavailable ? { id: setting.id, result: "unavailable", why: out.unavailable } : { id: setting.id, result: "failed", why: `${connector.name} refused the change` }
|
|
24679
|
+
);
|
|
24680
|
+
}
|
|
24681
|
+
}
|
|
24682
|
+
return results;
|
|
24683
|
+
}
|
|
24684
|
+
var securityDone = (results) => results.every((r) => r.result !== "failed");
|
|
24685
|
+
async function secureCommand(connector, flags, report = {}, fake) {
|
|
24686
|
+
const root = await findRepoRoot();
|
|
24687
|
+
report.delivery = "settings";
|
|
24688
|
+
const connection = await connector.connect(root, fake);
|
|
24689
|
+
if (!connection.ok) {
|
|
24690
|
+
say.error(connection.problem);
|
|
24691
|
+
Object.assign(report, { outcome: "failed", errorType: connection.errorType });
|
|
24692
|
+
return 1;
|
|
24693
|
+
}
|
|
24694
|
+
const { ctx, target } = connection;
|
|
24695
|
+
const states = withRefusals(await readSettings(connector, ctx), loadSnapshots(root)[connector.id]);
|
|
24696
|
+
saveSnapshot(root, connector.id, states);
|
|
24697
|
+
const toTurnOn = connector.settings.filter((s) => states[s.id].state === "off").map((s) => s.id);
|
|
24698
|
+
console.log(`
|
|
24699
|
+
${pc.magenta("\u2726")} ${pc.bold(`${connector.name} security for ${target}`)}`);
|
|
24700
|
+
for (const setting of connector.settings) {
|
|
24701
|
+
const s = states[setting.id];
|
|
24702
|
+
const line = s.state === "on" ? pc.green("\u2714 already on") : s.state === "off" ? pc.cyan("\u2192 Aura will turn this on") : pc.dim(`\u2013 not available: ${s.why}`);
|
|
24703
|
+
console.log(` ${setting.label}
|
|
24704
|
+
${line}`);
|
|
24705
|
+
}
|
|
24706
|
+
for (const line of await connector.explain?.(ctx, toTurnOn) ?? []) say.dim(` ${line}`);
|
|
24707
|
+
const label = (id) => connector.settings.find((s) => s.id === id).label;
|
|
24708
|
+
const impact = (id) => connector.settings.find((s) => s.id === id).impact;
|
|
24709
|
+
if (toTurnOn.length && !flags.dryRun) {
|
|
24710
|
+
console.log(`
|
|
24711
|
+
${pc.bold(" What changes for you")}`);
|
|
24712
|
+
for (const id of toTurnOn) console.log(` \u2022 ${impact(id)}`);
|
|
24713
|
+
}
|
|
24714
|
+
if (flags.dryRun) {
|
|
24715
|
+
say.dim(toTurnOn.length ? "\nDry run: nothing was changed." : "\nEverything Aura can turn on here is already on.");
|
|
24716
|
+
return 0;
|
|
24717
|
+
}
|
|
24718
|
+
let chosen = toTurnOn;
|
|
24719
|
+
if (toTurnOn.length && !flags.yes) {
|
|
24720
|
+
if (!interactive()) {
|
|
24721
|
+
say.info(`
|
|
24722
|
+
Nothing was changed. Show the user the changes above, and once they agree run: aura apply ${connector.actionId} --yes`);
|
|
24723
|
+
report.outcome = "shown";
|
|
24724
|
+
return 0;
|
|
24725
|
+
}
|
|
24726
|
+
chosen = unwrap(
|
|
24727
|
+
await p.multiselect({
|
|
24728
|
+
message: `Which should Aura turn on in ${connector.name}? (untick any you don\u2019t want)`,
|
|
24729
|
+
options: toTurnOn.map((id) => ({ value: id, label: label(id) })),
|
|
24730
|
+
initialValues: toTurnOn,
|
|
24731
|
+
required: false
|
|
24732
|
+
})
|
|
24733
|
+
);
|
|
24734
|
+
if (chosen.length === 0) {
|
|
24735
|
+
say.dim("Nothing was changed.");
|
|
24736
|
+
report.outcome = "cancelled";
|
|
24737
|
+
return 0;
|
|
24738
|
+
}
|
|
24739
|
+
}
|
|
24740
|
+
const skipped = toTurnOn.filter((id) => !chosen.includes(id));
|
|
24741
|
+
let results;
|
|
24742
|
+
if (toTurnOn.length === 0) {
|
|
24743
|
+
say.ok("Everything Aura can turn on here is already on.");
|
|
24744
|
+
results = await enableSettings(connector, ctx, states);
|
|
24745
|
+
report.outcome = "no_changes";
|
|
24746
|
+
} else {
|
|
24747
|
+
const attempt = Object.fromEntries(
|
|
24748
|
+
Object.entries(states).map(([id, s]) => [id, skipped.includes(id) ? { state: "unavailable", why: "you chose to leave it off" } : s])
|
|
24749
|
+
);
|
|
24750
|
+
results = await enableSettings(connector, ctx, attempt);
|
|
24751
|
+
console.log("");
|
|
24752
|
+
for (const r of results.filter((x) => chosen.includes(x.id))) {
|
|
24753
|
+
if (r.result === "turned-on") say.ok(`${label(r.id)}: on`);
|
|
24754
|
+
else if (r.result === "unavailable") say.dim(`\u2013 ${label(r.id)}: ${r.why}`);
|
|
24755
|
+
else say.warn(`${label(r.id)}: ${r.why}.`);
|
|
24756
|
+
}
|
|
24757
|
+
report.outcome = results.some((r) => r.result === "failed") ? "failed" : "settings_changed";
|
|
24758
|
+
}
|
|
24759
|
+
const refusedNow = Object.fromEntries(results.filter((r) => r.result === "unavailable" && states[r.id].state === "off" && r.why).map((r) => [r.id, r.why]));
|
|
24760
|
+
saveSnapshot(
|
|
24761
|
+
root,
|
|
24762
|
+
connector.id,
|
|
24763
|
+
Object.fromEntries(
|
|
24764
|
+
Object.entries(states).map(([id, s]) => {
|
|
24765
|
+
const r = results.find((x) => x.id === id);
|
|
24766
|
+
return [id, r?.result === "turned-on" ? { state: "on" } : refusedNow[id] ? { state: "unavailable", why: refusedNow[id] } : s];
|
|
24767
|
+
})
|
|
24768
|
+
),
|
|
24769
|
+
refusedNow
|
|
24770
|
+
);
|
|
24771
|
+
const left = await connector.leftForYou(ctx, results);
|
|
24772
|
+
if (left.length) {
|
|
24773
|
+
console.log(`
|
|
24774
|
+
${pc.bold(" Left for you")}`);
|
|
24775
|
+
for (const line of left) console.log(` \u2022 ${line}`);
|
|
24776
|
+
}
|
|
24777
|
+
const inv = loadInventory(root);
|
|
24778
|
+
inv.secured = { ...inv.secured, [connector.id]: securityDone(results) };
|
|
24779
|
+
if (connector.id === "github") inv.githubSecurity = inv.secured.github;
|
|
24780
|
+
inv.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
24781
|
+
writeAuraFile(root, "inventory.json", `${JSON.stringify(inv, null, 2)}
|
|
24782
|
+
`);
|
|
24783
|
+
return results.some((r) => r.result === "failed") ? 1 : 0;
|
|
24784
|
+
}
|
|
24785
|
+
|
|
24100
24786
|
// src/commands/apply.ts
|
|
24101
24787
|
var LOCKFILES = ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lock", "bun.lockb"];
|
|
24102
24788
|
async function applyCommand(actionId, flags) {
|
|
@@ -24116,6 +24802,8 @@ async function applyAction(actionId, flags, report) {
|
|
|
24116
24802
|
}
|
|
24117
24803
|
const scan = await latestScan(root);
|
|
24118
24804
|
const { delivery, fellBack } = effectiveDelivery(action2, scan);
|
|
24805
|
+
const connector = connectorForAction(actionId);
|
|
24806
|
+
if (connector) return secureCommand(connector, flags, report);
|
|
24119
24807
|
if (delivery === "guide") return guideCommand(actionId, {});
|
|
24120
24808
|
if (delivery === "agent-fix") {
|
|
24121
24809
|
if (fellBack) say.info(pc.dim(`Aura only opens template PRs on stacks it recognizes (Next.js). Handing "${actionId}" to Aura\u2019s agent instead.`));
|
|
@@ -24149,7 +24837,7 @@ async function applyAction(actionId, flags, report) {
|
|
|
24149
24837
|
console.log(`
|
|
24150
24838
|
${pc.bold(action2.title)}`);
|
|
24151
24839
|
for (const c of plan.changes)
|
|
24152
|
-
console.log(` ${c.content === null ? pc.red("delete") :
|
|
24840
|
+
console.log(` ${c.content === null ? pc.red("delete") : existsSync19(path20.join(root, c.path)) ? pc.yellow("update") : pc.green("add ")} ${c.path}`);
|
|
24153
24841
|
const pm = pmCommands(scan.stack.packageManager);
|
|
24154
24842
|
if (plan.devDeps?.length) console.log(` ${pc.cyan("install")} ${plan.devDeps.join(", ")} ${pc.dim(`(${pm.addDev})`)}`);
|
|
24155
24843
|
for (const cmd of plan.commands ?? []) console.log(` ${pc.cyan("run ")} ${cmd.join(" ")}`);
|
|
@@ -24181,8 +24869,8 @@ ${pc.bold(action2.title)}`);
|
|
|
24181
24869
|
report.outcome = "cancelled";
|
|
24182
24870
|
return 0;
|
|
24183
24871
|
}
|
|
24184
|
-
const tsc =
|
|
24185
|
-
const checkTypes = plan.verifyTypes && scan.stack.typescript &&
|
|
24872
|
+
const tsc = path20.join(root, "node_modules", ".bin", process.platform === "win32" ? "tsc.cmd" : "tsc");
|
|
24873
|
+
const checkTypes = plan.verifyTypes && scan.stack.typescript && existsSync19(tsc);
|
|
24186
24874
|
const typeErrors = async () => {
|
|
24187
24875
|
const r = await run(tsc, ["--noEmit", "--pretty", "false"], { cwd: root, timeoutMs: 3e5 });
|
|
24188
24876
|
return (r.stdout.match(/error TS\d+/g) ?? []).length;
|
|
@@ -24291,17 +24979,17 @@ async function undoPlan(root, plan) {
|
|
|
24291
24979
|
for (const file2 of touched) {
|
|
24292
24980
|
const inHead = (await git(root, "cat-file", "-e", `HEAD:${file2}`)).code === 0;
|
|
24293
24981
|
if (inHead) await git(root, "checkout", "HEAD", "--", file2);
|
|
24294
|
-
else if (plan.changes.some((c) => c.path === file2)) rmSync6(
|
|
24982
|
+
else if (plan.changes.some((c) => c.path === file2)) rmSync6(path20.join(root, file2), { force: true });
|
|
24295
24983
|
}
|
|
24296
24984
|
}
|
|
24297
24985
|
|
|
24298
24986
|
// src/commands/init.ts
|
|
24299
|
-
import { existsSync as
|
|
24987
|
+
import { existsSync as existsSync20, readFileSync as readFileSync21 } from "fs";
|
|
24300
24988
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
24301
|
-
import
|
|
24989
|
+
import path24 from "path";
|
|
24302
24990
|
|
|
24303
24991
|
// src/brief.ts
|
|
24304
|
-
import
|
|
24992
|
+
import path21 from "path";
|
|
24305
24993
|
function repoSummary(ctx) {
|
|
24306
24994
|
const routes = ctx.files.filter((f) => /^(?:src\/)?app\/(?:.+\/)?(?:page|route)\.[cm]?[jt]sx?$/.test(f) || /^(?:src\/)?pages\/.+\.[cm]?[jt]sx?$/.test(f)).map(
|
|
24307
24995
|
(f) => "/" + f.replace(/^(?:src\/)?(?:app|pages)\//, "").replace(/(?:^|\/)(?:page|route|index)\.[cm]?[jt]sx?$|\.[cm]?[jt]sx?$/, "").replace(/\([^)]+\)\//g, "")
|
|
@@ -24317,7 +25005,7 @@ function repoSummary(ctx) {
|
|
|
24317
25005
|
const readme2 = ctx.files.find((f) => /^readme\.mdx?$/i.test(f));
|
|
24318
25006
|
const readmeText = readme2 ? ctx.read(readme2) ?? "" : "";
|
|
24319
25007
|
return {
|
|
24320
|
-
name: ctx.pkg?.name ??
|
|
25008
|
+
name: ctx.pkg?.name ?? path21.basename(ctx.root),
|
|
24321
25009
|
packageJson: ctx.pkg ? {
|
|
24322
25010
|
name: ctx.pkg.name,
|
|
24323
25011
|
description: ctx.pkg.description,
|
|
@@ -24410,10 +25098,10 @@ function installFallback(result, platform = process.platform) {
|
|
|
24410
25098
|
|
|
24411
25099
|
// src/core/claude-plugin.ts
|
|
24412
25100
|
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
24413
|
-
import
|
|
24414
|
-
import
|
|
25101
|
+
import os6 from "os";
|
|
25102
|
+
import path22 from "path";
|
|
24415
25103
|
var CLAUDE_PLUGIN_ID = "aura@aura";
|
|
24416
|
-
var marketplaceDir = () =>
|
|
25104
|
+
var marketplaceDir = () => path22.join(os6.homedir(), ".aura", "claude-marketplace");
|
|
24417
25105
|
var MARKETPLACE = {
|
|
24418
25106
|
name: "aura",
|
|
24419
25107
|
description: "Aura for Claude Code",
|
|
@@ -24428,8 +25116,8 @@ var MARKETPLACE = {
|
|
|
24428
25116
|
};
|
|
24429
25117
|
function writeMarketplace() {
|
|
24430
25118
|
const dir = marketplaceDir();
|
|
24431
|
-
mkdirSync6(
|
|
24432
|
-
writeFileSync7(
|
|
25119
|
+
mkdirSync6(path22.join(dir, ".claude-plugin"), { recursive: true });
|
|
25120
|
+
writeFileSync7(path22.join(dir, ".claude-plugin", "marketplace.json"), `${JSON.stringify(MARKETPLACE, null, 2)}
|
|
24433
25121
|
`);
|
|
24434
25122
|
return dir;
|
|
24435
25123
|
}
|
|
@@ -24532,11 +25220,30 @@ function describeInferred(inferred) {
|
|
|
24532
25220
|
|
|
24533
25221
|
// src/commands/scan.ts
|
|
24534
25222
|
import { mkdtempSync as mkdtempSync3, rmSync as rmSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
24535
|
-
import
|
|
24536
|
-
import
|
|
25223
|
+
import os7 from "os";
|
|
25224
|
+
import path23 from "path";
|
|
25225
|
+
|
|
25226
|
+
// src/connectors/refresh.ts
|
|
25227
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
25228
|
+
async function refreshToolSettings(root) {
|
|
25229
|
+
if (process.env.AURA_NO_CONNECTORS) return;
|
|
25230
|
+
let previous = null;
|
|
25231
|
+
try {
|
|
25232
|
+
previous = JSON.parse(readFileSync20(auraPath(root, "scan.json"), "utf8"));
|
|
25233
|
+
} catch {
|
|
25234
|
+
}
|
|
25235
|
+
const snapshots = loadSnapshots(root);
|
|
25236
|
+
for (const connector of CONNECTORS) {
|
|
25237
|
+
if (!(previous ? connector.detect(previous) : connector.id === "github") || !isStale(snapshots[connector.id])) continue;
|
|
25238
|
+
const connection = await connector.connect(root).catch(() => null);
|
|
25239
|
+
if (connection?.ok) saveSnapshot(root, connector.id, withRefusals(await readSettings(connector, connection.ctx), snapshots[connector.id]));
|
|
25240
|
+
}
|
|
25241
|
+
}
|
|
25242
|
+
|
|
25243
|
+
// src/commands/scan.ts
|
|
24537
25244
|
async function scanRef(root, ref, opts) {
|
|
24538
|
-
const dir = mkdtempSync3(
|
|
24539
|
-
const wt =
|
|
25245
|
+
const dir = mkdtempSync3(path23.join(os7.tmpdir(), "aura-base-"));
|
|
25246
|
+
const wt = path23.join(dir, "tree");
|
|
24540
25247
|
const add = await run("git", ["worktree", "add", "--detach", wt, ref], { cwd: root });
|
|
24541
25248
|
if (add.code !== 0) {
|
|
24542
25249
|
rmSync7(dir, { recursive: true, force: true });
|
|
@@ -24557,6 +25264,7 @@ async function scanCommand(flags) {
|
|
|
24557
25264
|
const opts = { fast: Boolean(flags.fast || flags.base), noDownload: flags.download === false };
|
|
24558
25265
|
const s = spin(!quiet && !flags.ci);
|
|
24559
25266
|
s.start("Scanning");
|
|
25267
|
+
if (!flags.ci) await refreshToolSettings(root);
|
|
24560
25268
|
const scan = await runScan(root, opts, (m) => s.message(`Checking: ${m}`));
|
|
24561
25269
|
s.stop(`Scanned ${scan.checks.filter((c) => c.status !== "not-applicable").length} checks`);
|
|
24562
25270
|
const client = AuraClient.from(config2);
|
|
@@ -24625,7 +25333,7 @@ async function scanCommand(flags) {
|
|
|
24625
25333
|
// src/commands/init.ts
|
|
24626
25334
|
var AGENT_IDS = ["claude", "codex", "cursor", "pi"];
|
|
24627
25335
|
function detectAgents(root) {
|
|
24628
|
-
return detectAgentsFrom((rel) =>
|
|
25336
|
+
return detectAgentsFrom((rel) => existsSync20(path24.join(root, rel)));
|
|
24629
25337
|
}
|
|
24630
25338
|
function openEditor(file2) {
|
|
24631
25339
|
const fallback = process.platform === "win32" ? "notepad" : spawnSync3("sh", ["-c", "command -v nano"]).status === 0 ? "nano" : "vi";
|
|
@@ -24740,13 +25448,13 @@ async function initCommand(flags) {
|
|
|
24740
25448
|
let brief2 = null;
|
|
24741
25449
|
let keptBrief = false;
|
|
24742
25450
|
if (flags.briefFile) {
|
|
24743
|
-
brief2 =
|
|
24744
|
-
} else if (!flags.answers &&
|
|
25451
|
+
brief2 = readFileSync21(flags.briefFile, "utf8");
|
|
25452
|
+
} else if (!flags.answers && existsSync20(briefPath) && !(tty && unwrap(await p.confirm({ message: "A product brief already exists. Draft a new one?", initialValue: false })))) {
|
|
24745
25453
|
keptBrief = true;
|
|
24746
25454
|
} else {
|
|
24747
25455
|
let answers = null;
|
|
24748
25456
|
if (flags.answers) {
|
|
24749
|
-
answers = OnboardingAnswersSchema.parse(JSON.parse(
|
|
25457
|
+
answers = OnboardingAnswersSchema.parse(JSON.parse(readFileSync21(flags.answers, "utf8")));
|
|
24750
25458
|
} else if (tty) {
|
|
24751
25459
|
const inferred = inferAnswers(ctx);
|
|
24752
25460
|
p.note(describeInferred(inferred).join("\n"), "Here\u2019s what Aura found about your app");
|
|
@@ -24878,7 +25586,7 @@ ${pc.magenta("\u2726")} ${pc.bold("Give the user the aura command")}`);
|
|
|
24878
25586
|
}
|
|
24879
25587
|
|
|
24880
25588
|
// src/commands/summary.ts
|
|
24881
|
-
import
|
|
25589
|
+
import path25 from "path";
|
|
24882
25590
|
async function activity(root, since) {
|
|
24883
25591
|
const log = await git(root, "log", `--since=${since.toISOString()}`, "--no-merges", "--format=%H%x09%an%x09%s");
|
|
24884
25592
|
const commits = log.stdout.split("\n").filter(Boolean).slice(0, 300).map((l) => {
|
|
@@ -24935,7 +25643,7 @@ async function summaryCommand(flags) {
|
|
|
24935
25643
|
const beforeIds = new Set(before ? allFindings(before).map((f) => f.id) : []);
|
|
24936
25644
|
const nowIds = new Set(allFindings(now).map((f) => f.id));
|
|
24937
25645
|
const req = {
|
|
24938
|
-
repo:
|
|
25646
|
+
repo: path25.basename(root),
|
|
24939
25647
|
since: since.toISOString(),
|
|
24940
25648
|
until: until.toISOString(),
|
|
24941
25649
|
product: productContext(root),
|
|
@@ -24983,11 +25691,11 @@ ${body}`);
|
|
|
24983
25691
|
import pc4 from "picocolors";
|
|
24984
25692
|
|
|
24985
25693
|
// src/core/update-check.ts
|
|
24986
|
-
import { mkdirSync as mkdirSync7, readFileSync as
|
|
24987
|
-
import
|
|
24988
|
-
import
|
|
25694
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync22, writeFileSync as writeFileSync9 } from "fs";
|
|
25695
|
+
import os8 from "os";
|
|
25696
|
+
import path26 from "path";
|
|
24989
25697
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
24990
|
-
var cacheFile = () =>
|
|
25698
|
+
var cacheFile = () => path26.join(os8.homedir(), ".aura", "update-check.json");
|
|
24991
25699
|
function isNewer(a, b) {
|
|
24992
25700
|
if (!/^\d+\.\d+\.\d+$/.test(a) || !/^\d+\.\d+\.\d+$/.test(b)) return false;
|
|
24993
25701
|
const [x, y] = [a, b].map((v) => v.split(".").map(Number));
|
|
@@ -25007,13 +25715,13 @@ async function newerVersion(opts = {}) {
|
|
|
25007
25715
|
const now = opts.now ?? Date.now();
|
|
25008
25716
|
let latest = null;
|
|
25009
25717
|
try {
|
|
25010
|
-
const cached2 = JSON.parse(
|
|
25718
|
+
const cached2 = JSON.parse(readFileSync22(cacheFile(), "utf8"));
|
|
25011
25719
|
if (now - cached2.checkedAt < DAY_MS) latest = cached2.latest;
|
|
25012
25720
|
else throw new Error("stale");
|
|
25013
25721
|
} catch {
|
|
25014
25722
|
latest = await (opts.fetchLatest ?? fetchLatest)();
|
|
25015
25723
|
try {
|
|
25016
|
-
mkdirSync7(
|
|
25724
|
+
mkdirSync7(path26.dirname(cacheFile()), { recursive: true });
|
|
25017
25725
|
writeFileSync9(cacheFile(), JSON.stringify({ checkedAt: now, latest }));
|
|
25018
25726
|
} catch {
|
|
25019
25727
|
}
|