getaura 0.1.7 → 0.1.8
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 +946 -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,545 @@ 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 project = await ctx.get(`/v1/projects/${ref}`);
|
|
20540
|
+
if (!project?.name) return { ok: false, problem: `Couldn\u2019t read the Supabase project ${ref}. Check your account can open it.`, errorType: "NoProject" };
|
|
20541
|
+
return { ok: true, ctx, target: project.name };
|
|
20542
|
+
},
|
|
20543
|
+
settings: [
|
|
20544
|
+
{
|
|
20545
|
+
id: "email-confirmation",
|
|
20546
|
+
label: "Email confirmation (new users confirm their email before signing in)",
|
|
20547
|
+
impact: "New users must click a link in an email before they can sign in. People already signed up aren\u2019t affected.",
|
|
20548
|
+
read: authFlag((c) => c.mailer_autoconfirm === false),
|
|
20549
|
+
enable: (ctx) => patchAuth(ctx, { mailer_autoconfirm: false })
|
|
20550
|
+
},
|
|
20551
|
+
{
|
|
20552
|
+
id: "password-length",
|
|
20553
|
+
label: "Passwords of at least 8 characters",
|
|
20554
|
+
impact: "New and changed passwords must be at least 8 characters. Existing passwords keep working.",
|
|
20555
|
+
read: authFlag((c) => (c.password_min_length ?? 0) >= 8),
|
|
20556
|
+
enable: (ctx) => patchAuth(ctx, { password_min_length: 8 })
|
|
20557
|
+
},
|
|
20558
|
+
{
|
|
20559
|
+
id: "leaked-passwords",
|
|
20560
|
+
label: "Leaked password protection (refuses passwords found in data breaches)",
|
|
20561
|
+
impact: "Passwords that appear in known data breaches are refused when someone signs up or changes their password.",
|
|
20562
|
+
read: authFlag((c) => c.password_hibp_enabled === true),
|
|
20563
|
+
enable: (ctx) => patchAuth(ctx, { password_hibp_enabled: true })
|
|
20564
|
+
},
|
|
20565
|
+
{
|
|
20566
|
+
id: "app-mfa",
|
|
20567
|
+
label: "Authenticator app sign-in for your users (two-factor, optional for them)",
|
|
20568
|
+
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.",
|
|
20569
|
+
read: authFlag((c) => c.mfa_totp_enroll_enabled === true && c.mfa_totp_verify_enabled === true),
|
|
20570
|
+
enable: (ctx) => patchAuth(ctx, { mfa_totp_enroll_enabled: true, mfa_totp_verify_enabled: true })
|
|
20571
|
+
},
|
|
20572
|
+
{
|
|
20573
|
+
id: "secure-email-change",
|
|
20574
|
+
label: "Secure email change (both the old and new address confirm)",
|
|
20575
|
+
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.",
|
|
20576
|
+
read: authFlag((c) => c.mailer_secure_email_change_enabled === true),
|
|
20577
|
+
enable: (ctx) => patchAuth(ctx, { mailer_secure_email_change_enabled: true })
|
|
20578
|
+
},
|
|
20579
|
+
{
|
|
20580
|
+
id: "database-encryption",
|
|
20581
|
+
label: "Encrypted database connections only",
|
|
20582
|
+
impact: "Direct connections to your database must be encrypted. Turning this on restarts the database for a few seconds.",
|
|
20583
|
+
read: async (ctx) => (await ctx.get(`/v1/projects/${ctx.ref}/ssl-enforcement`))?.currentConfig?.database === true ? { state: "on" } : { state: "off" },
|
|
20584
|
+
async enable(ctx) {
|
|
20585
|
+
const r = await ctx.api("PUT", `/v1/projects/${ctx.ref}/ssl-enforcement`, { requestedConfig: { database: true } });
|
|
20586
|
+
return r.status >= 200 && r.status < 300 ? { ok: true } : { ok: false };
|
|
20587
|
+
}
|
|
20588
|
+
},
|
|
20589
|
+
{
|
|
20590
|
+
id: "security-advisor",
|
|
20591
|
+
label: "No problems in Supabase\u2019s security advisor",
|
|
20592
|
+
impact: "Nothing changes on its own: these are fixed in your database code, and Aura lists them below.",
|
|
20593
|
+
read: async (ctx) => (await advisorProblems(ctx)).length ? { state: "off" } : { state: "on" },
|
|
20594
|
+
enable: async () => ({ ok: false, unavailable: "fixed in your database code (see Left for you)" })
|
|
20595
|
+
}
|
|
20596
|
+
],
|
|
20597
|
+
async leftForYou(ctx, results) {
|
|
20598
|
+
const left = [];
|
|
20599
|
+
const problems = await advisorProblems(ctx);
|
|
20600
|
+
if (problems.length) {
|
|
20601
|
+
const titles = [...new Set(problems.map((p2) => p2.title ?? p2.name ?? "Security problem"))].slice(0, 5);
|
|
20602
|
+
left.push(
|
|
20603
|
+
`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`
|
|
20604
|
+
);
|
|
20605
|
+
}
|
|
20606
|
+
if (results.some((r) => r.id === "leaked-passwords" && r.result === "unavailable"))
|
|
20607
|
+
left.push("Leaked password protection needs Supabase Pro. Until then, the 8-character minimum helps.");
|
|
20608
|
+
left.push("Turn on two-factor login for your Supabase account: Supabase dashboard \u2192 Account \u2192 Security.");
|
|
20609
|
+
return left;
|
|
20610
|
+
}
|
|
20611
|
+
};
|
|
20612
|
+
|
|
20613
|
+
// src/connectors/vercel.ts
|
|
20614
|
+
import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
20615
|
+
import path4 from "path";
|
|
20616
|
+
var PUBLIC_PREFIX = /^(?:NEXT_PUBLIC_|VITE_|EXPO_PUBLIC_|PUBLIC_)/;
|
|
20617
|
+
var SECRET_NAME = /SECRET|SERVICE_ROLE|PRIVATE|PASSWORD|_TOKEN$|API_KEY$/;
|
|
20618
|
+
var parse4 = (r) => {
|
|
20619
|
+
try {
|
|
20620
|
+
return r.code === 0 ? JSON.parse(r.stdout) : null;
|
|
20621
|
+
} catch {
|
|
20622
|
+
return null;
|
|
20623
|
+
}
|
|
20624
|
+
};
|
|
20625
|
+
async function exposedSecrets(ctx) {
|
|
20626
|
+
const envs = (await ctx.get(`/v10/projects/${ctx.projectId}/env?decrypt=true${ctx.team.replace("?", "&")}`))?.envs ?? [];
|
|
20627
|
+
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))];
|
|
20628
|
+
}
|
|
20629
|
+
async function patchProject(ctx, body) {
|
|
20630
|
+
const r = await ctx.vercel(["api", `/v9/projects/${ctx.projectId}${ctx.team}`, "-X", "PATCH", "--input", "-", "--raw"], JSON.stringify(body));
|
|
20631
|
+
if (r.code === 0) return { ok: true };
|
|
20632
|
+
return { ok: false, unavailable: /plan|upgrade|pro\b|enterprise/i.test(r.stderr + r.stdout) ? "not available on your Vercel plan" : void 0 };
|
|
20633
|
+
}
|
|
20634
|
+
var vercel = {
|
|
20635
|
+
id: "vercel",
|
|
20636
|
+
name: "Vercel",
|
|
20637
|
+
actionId: "vercel-security",
|
|
20638
|
+
detect: (scan) => scan.stack.services.includes("Vercel"),
|
|
20639
|
+
async connect(root, fake) {
|
|
20640
|
+
const cli = fake ?? ((args, input2) => run("vercel", args, { cwd: root, input: input2, timeoutMs: 6e4 }));
|
|
20641
|
+
if (!fake) {
|
|
20642
|
+
if (!await commandExists("vercel"))
|
|
20643
|
+
return {
|
|
20644
|
+
ok: false,
|
|
20645
|
+
problem: "The Vercel CLI isn\u2019t installed. Install it with `npm install -g vercel`, then run `vercel login`.",
|
|
20646
|
+
errorType: "CliNotInstalled"
|
|
20647
|
+
};
|
|
20648
|
+
if ((await cli(["whoami"])).code !== 0)
|
|
20649
|
+
return { ok: false, problem: "The Vercel CLI isn\u2019t signed in. Run `vercel login`, then try again.", errorType: "CliNotSignedIn" };
|
|
20650
|
+
}
|
|
20651
|
+
const linked = [path4.join(root, ".vercel", "project.json"), ...["apps", "packages"].flatMap((d) => findLinks(path4.join(root, d)))].find(
|
|
20652
|
+
(f) => existsSync3(f)
|
|
20653
|
+
);
|
|
20654
|
+
let project = null;
|
|
20655
|
+
try {
|
|
20656
|
+
project = linked ? JSON.parse(readFileSync3(linked, "utf8")) : null;
|
|
20657
|
+
} catch {
|
|
20658
|
+
project = null;
|
|
20659
|
+
}
|
|
20660
|
+
if (!project?.projectId)
|
|
20661
|
+
return { ok: false, problem: "This folder isn\u2019t linked to a Vercel project yet. Run `vercel link`, then try again.", errorType: "NoProject" };
|
|
20662
|
+
const cache = /* @__PURE__ */ new Map();
|
|
20663
|
+
const ctx = {
|
|
20664
|
+
projectId: project.projectId,
|
|
20665
|
+
team: project.orgId?.startsWith("team_") ? `?teamId=${project.orgId}` : "",
|
|
20666
|
+
vercel: cli,
|
|
20667
|
+
get: (endpoint) => {
|
|
20668
|
+
if (!cache.has(endpoint)) cache.set(endpoint, cli(["api", endpoint, "--raw"]).then(parse4));
|
|
20669
|
+
return cache.get(endpoint);
|
|
20670
|
+
}
|
|
20671
|
+
};
|
|
20672
|
+
const name = (await ctx.get(`/v9/projects/${ctx.projectId}${ctx.team}`))?.name;
|
|
20673
|
+
if (!name)
|
|
20674
|
+
return { ok: false, problem: "Couldn\u2019t read this project from Vercel. Check you have access to it with `vercel project ls`.", errorType: "NoProject" };
|
|
20675
|
+
return { ok: true, ctx, target: name };
|
|
20676
|
+
},
|
|
20677
|
+
settings: [
|
|
20678
|
+
{
|
|
20679
|
+
id: "preview-protection",
|
|
20680
|
+
label: "Protect preview links (only your Vercel team can open them)",
|
|
20681
|
+
impact: "Preview and deployment links need a Vercel login to open. Your live site on your own domain stays public.",
|
|
20682
|
+
read: async (ctx) => (await ctx.get(`/v9/projects/${ctx.projectId}${ctx.team}`))?.ssoProtection ? { state: "on" } : { state: "off" },
|
|
20683
|
+
enable: (ctx) => patchProject(ctx, { ssoProtection: { deploymentType: "all_except_custom_domains" } })
|
|
20684
|
+
},
|
|
20685
|
+
{
|
|
20686
|
+
id: "git-fork-protection",
|
|
20687
|
+
label: "Fork protection (pull requests from forks don\u2019t deploy automatically)",
|
|
20688
|
+
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.",
|
|
20689
|
+
read: async (ctx) => (await ctx.get(`/v9/projects/${ctx.projectId}${ctx.team}`))?.gitForkProtection ? { state: "on" } : { state: "off" },
|
|
20690
|
+
enable: (ctx) => patchProject(ctx, { gitForkProtection: true })
|
|
20691
|
+
},
|
|
20692
|
+
{
|
|
20693
|
+
id: "protected-sourcemaps",
|
|
20694
|
+
label: "Protected source maps (your original code isn\u2019t served to the public)",
|
|
20695
|
+
impact: "Source maps, which reveal your original code, are only served to people signed in to your Vercel team.",
|
|
20696
|
+
read: async (ctx) => (await ctx.get(`/v9/projects/${ctx.projectId}${ctx.team}`))?.protectedSourcemaps ? { state: "on" } : { state: "off" },
|
|
20697
|
+
enable: (ctx) => patchProject(ctx, { protectedSourcemaps: true })
|
|
20698
|
+
},
|
|
20699
|
+
{
|
|
20700
|
+
id: "no-public-secrets",
|
|
20701
|
+
label: "No secret keys in browser-visible settings (NEXT_PUBLIC_\u2026 and similar)",
|
|
20702
|
+
impact: "Nothing changes on its own: Aura can\u2019t move a key for you, and lists what to do below.",
|
|
20703
|
+
read: async (ctx) => (await exposedSecrets(ctx)).length ? { state: "off" } : { state: "on" },
|
|
20704
|
+
enable: async () => ({ ok: false, unavailable: "needs you to move the key (see Left for you)" })
|
|
20705
|
+
}
|
|
20706
|
+
],
|
|
20707
|
+
async leftForYou(ctx) {
|
|
20708
|
+
const left = [];
|
|
20709
|
+
const exposed = await exposedSecrets(ctx);
|
|
20710
|
+
if (exposed.length)
|
|
20711
|
+
left.push(
|
|
20712
|
+
`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\`).`
|
|
20713
|
+
);
|
|
20714
|
+
left.push("Turn on two-factor login for your Vercel account: Vercel \u2192 Account Settings \u2192 Authentication.");
|
|
20715
|
+
return left;
|
|
20716
|
+
}
|
|
20717
|
+
};
|
|
20718
|
+
function findLinks(dir) {
|
|
20719
|
+
try {
|
|
20720
|
+
return readdirSync2(dir).map((d) => path4.join(dir, d, ".vercel", "project.json"));
|
|
20721
|
+
} catch {
|
|
20722
|
+
return [];
|
|
20723
|
+
}
|
|
20724
|
+
}
|
|
20725
|
+
|
|
20726
|
+
// src/connectors/index.ts
|
|
20727
|
+
var CONNECTORS = [github, supabase, vercel];
|
|
20728
|
+
function connectorForAction(actionId) {
|
|
20729
|
+
return CONNECTORS.find((c) => c.actionId === actionId);
|
|
20730
|
+
}
|
|
20731
|
+
|
|
20732
|
+
// src/templates.ts
|
|
20733
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
20734
|
+
import path7 from "path";
|
|
20735
|
+
|
|
20207
20736
|
// src/core/config.ts
|
|
20737
|
+
import { chmodSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
20738
|
+
import os2 from "os";
|
|
20739
|
+
import path5 from "path";
|
|
20208
20740
|
var DEFAULT_CONFIG = {
|
|
20209
20741
|
version: 1,
|
|
20210
20742
|
agents: [],
|
|
@@ -20213,45 +20745,45 @@ var DEFAULT_CONFIG = {
|
|
|
20213
20745
|
ciMaxDrop: 0
|
|
20214
20746
|
};
|
|
20215
20747
|
function auraPath(root, ...parts) {
|
|
20216
|
-
return
|
|
20748
|
+
return path5.join(root, AURA_DIR, ...parts);
|
|
20217
20749
|
}
|
|
20218
20750
|
function ensureAuraDir(root) {
|
|
20219
|
-
|
|
20751
|
+
mkdirSync2(auraPath(root), { recursive: true });
|
|
20220
20752
|
}
|
|
20221
20753
|
function loadConfig(root) {
|
|
20222
20754
|
const file2 = auraPath(root, "config.json");
|
|
20223
|
-
if (!
|
|
20755
|
+
if (!existsSync4(file2)) return { ...DEFAULT_CONFIG };
|
|
20224
20756
|
try {
|
|
20225
|
-
return { ...DEFAULT_CONFIG, ...JSON.parse(
|
|
20757
|
+
return { ...DEFAULT_CONFIG, ...JSON.parse(readFileSync4(file2, "utf8")) };
|
|
20226
20758
|
} catch {
|
|
20227
20759
|
return { ...DEFAULT_CONFIG };
|
|
20228
20760
|
}
|
|
20229
20761
|
}
|
|
20230
20762
|
function saveConfig(root, config2) {
|
|
20231
20763
|
ensureAuraDir(root);
|
|
20232
|
-
|
|
20764
|
+
writeFileSync2(auraPath(root, "config.json"), JSON.stringify(config2, null, 2) + "\n");
|
|
20233
20765
|
}
|
|
20234
20766
|
function writeAuraFile(root, name, content) {
|
|
20235
20767
|
ensureAuraDir(root);
|
|
20236
20768
|
const file2 = auraPath(root, name);
|
|
20237
|
-
|
|
20238
|
-
|
|
20769
|
+
mkdirSync2(path5.dirname(file2), { recursive: true });
|
|
20770
|
+
writeFileSync2(file2, content);
|
|
20239
20771
|
return file2;
|
|
20240
20772
|
}
|
|
20241
|
-
var homeAuraDir = () =>
|
|
20242
|
-
var credentialsFile = () =>
|
|
20773
|
+
var homeAuraDir = () => path5.join(os2.homedir(), ".aura");
|
|
20774
|
+
var credentialsFile = () => path5.join(homeAuraDir(), "credentials.json");
|
|
20243
20775
|
function getApiKey() {
|
|
20244
20776
|
if (process.env.AURA_KEY) return process.env.AURA_KEY.trim();
|
|
20245
20777
|
try {
|
|
20246
|
-
const data = JSON.parse(
|
|
20778
|
+
const data = JSON.parse(readFileSync4(credentialsFile(), "utf8"));
|
|
20247
20779
|
return typeof data.key === "string" ? data.key : null;
|
|
20248
20780
|
} catch {
|
|
20249
20781
|
return null;
|
|
20250
20782
|
}
|
|
20251
20783
|
}
|
|
20252
20784
|
function saveApiKey(key) {
|
|
20253
|
-
|
|
20254
|
-
|
|
20785
|
+
mkdirSync2(homeAuraDir(), { recursive: true });
|
|
20786
|
+
writeFileSync2(credentialsFile(), JSON.stringify({ key }, null, 2) + "\n", { mode: 384 });
|
|
20255
20787
|
try {
|
|
20256
20788
|
chmodSync(credentialsFile(), 384);
|
|
20257
20789
|
} catch {
|
|
@@ -20261,7 +20793,7 @@ function getApiUrl(config2) {
|
|
|
20261
20793
|
return (process.env.AURA_API_URL || config2?.apiUrl || DEFAULT_API_URL).replace(/\/$/, "");
|
|
20262
20794
|
}
|
|
20263
20795
|
function auraBinDir() {
|
|
20264
|
-
return
|
|
20796
|
+
return path5.join(homeAuraDir(), "bin");
|
|
20265
20797
|
}
|
|
20266
20798
|
var AURA_GITIGNORE = `# Aura local files: they can describe security weaknesses, so they stay on your machine.
|
|
20267
20799
|
report.md
|
|
@@ -20273,15 +20805,15 @@ tasks/
|
|
|
20273
20805
|
`;
|
|
20274
20806
|
function ensureGitignore(root) {
|
|
20275
20807
|
const file2 = auraPath(root, ".gitignore");
|
|
20276
|
-
if (
|
|
20808
|
+
if (existsSync4(file2) && readFileSync4(file2, "utf8") === AURA_GITIGNORE) return false;
|
|
20277
20809
|
ensureAuraDir(root);
|
|
20278
|
-
|
|
20810
|
+
writeFileSync2(file2, AURA_GITIGNORE);
|
|
20279
20811
|
return true;
|
|
20280
20812
|
}
|
|
20281
20813
|
|
|
20282
20814
|
// src/core/stack.ts
|
|
20283
|
-
import { existsSync as
|
|
20284
|
-
import
|
|
20815
|
+
import { existsSync as existsSync5 } from "fs";
|
|
20816
|
+
import path6 from "path";
|
|
20285
20817
|
function allDeps(pkg) {
|
|
20286
20818
|
return { ...pkg?.dependencies ?? {}, ...pkg?.devDependencies ?? {} };
|
|
20287
20819
|
}
|
|
@@ -20405,7 +20937,7 @@ function findEnvRefs(files, read2) {
|
|
|
20405
20937
|
}
|
|
20406
20938
|
function detectStack(root, files, pkg, read2) {
|
|
20407
20939
|
const deps2 = allDeps(pkg);
|
|
20408
|
-
const has = (f) => files.includes(f) ||
|
|
20940
|
+
const has = (f) => files.includes(f) || existsSync5(path6.join(root, f));
|
|
20409
20941
|
const hasDir = (d) => files.some((f) => f.startsWith(`${d}/`));
|
|
20410
20942
|
let packageManager = null;
|
|
20411
20943
|
const pmField = pkg?.packageManager?.split("@")[0];
|
|
@@ -20430,6 +20962,7 @@ function detectStack(root, files, pkg, read2) {
|
|
|
20430
20962
|
}
|
|
20431
20963
|
if (has("vercel.json") || has(".vercel/project.json")) services.add("Vercel");
|
|
20432
20964
|
if (hasDir("supabase")) services.add("Supabase");
|
|
20965
|
+
if (has("vercel.json") || has(".vercel/project.json")) services.add("Vercel");
|
|
20433
20966
|
services.add("GitHub");
|
|
20434
20967
|
let nextjs = null;
|
|
20435
20968
|
if (deps2.next) {
|
|
@@ -20439,9 +20972,9 @@ function detectStack(root, files, pkg, read2) {
|
|
|
20439
20972
|
const pages = hasDir(`${base}pages`);
|
|
20440
20973
|
nextjs = { version: deps2.next.replace(/^[^\d]*/, ""), router: app && pages ? "both" : app ? "app" : pages ? "pages" : null, srcDir };
|
|
20441
20974
|
}
|
|
20442
|
-
let
|
|
20975
|
+
let supabase2 = null;
|
|
20443
20976
|
if (services.has("Supabase")) {
|
|
20444
|
-
|
|
20977
|
+
supabase2 = { migrationsDir: hasDir("supabase/migrations") ? "supabase/migrations" : null, hasConfig: has("supabase/config.toml") };
|
|
20445
20978
|
}
|
|
20446
20979
|
let testRunner = null;
|
|
20447
20980
|
if (deps2.vitest || files.some((f) => /^vitest\.config\./.test(f))) testRunner = "vitest";
|
|
@@ -20456,7 +20989,7 @@ function detectStack(root, files, pkg, read2) {
|
|
|
20456
20989
|
frameworks,
|
|
20457
20990
|
services: [...services].sort(),
|
|
20458
20991
|
nextjs,
|
|
20459
|
-
supabase,
|
|
20992
|
+
supabase: supabase2,
|
|
20460
20993
|
typescript: has("tsconfig.json"),
|
|
20461
20994
|
testRunner,
|
|
20462
20995
|
envVars,
|
|
@@ -20500,7 +21033,7 @@ function pmCommands(pm) {
|
|
|
20500
21033
|
// package.json
|
|
20501
21034
|
var package_default = {
|
|
20502
21035
|
name: "getaura",
|
|
20503
|
-
version: "0.1.
|
|
21036
|
+
version: "0.1.8",
|
|
20504
21037
|
description: "Aura helps you and your coding agent follow best practices while you vibe code.",
|
|
20505
21038
|
type: "module",
|
|
20506
21039
|
bin: {
|
|
@@ -20581,19 +21114,19 @@ function fill(text, vars) {
|
|
|
20581
21114
|
return text.replace(/\{\{([a-z_]+)\}\}/g, (m, name) => KNOWN.has(name) ? vars[name] ?? "" : m);
|
|
20582
21115
|
}
|
|
20583
21116
|
function readTemplate(rel) {
|
|
20584
|
-
return
|
|
21117
|
+
return readFileSync5(path7.join(templatesDir(), rel), "utf8");
|
|
20585
21118
|
}
|
|
20586
21119
|
function render(rel, vars) {
|
|
20587
21120
|
return fill(readTemplate(rel), vars);
|
|
20588
21121
|
}
|
|
20589
21122
|
function templateExists(rel) {
|
|
20590
|
-
return
|
|
21123
|
+
return existsSync6(path7.join(templatesDir(), rel));
|
|
20591
21124
|
}
|
|
20592
21125
|
function briefSummary(root, pkg) {
|
|
20593
|
-
const fallbackName = pkg?.name ? pkg.name.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()) :
|
|
21126
|
+
const fallbackName = pkg?.name ? pkg.name.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()) : path7.basename(root);
|
|
20594
21127
|
const file2 = auraPath(root, "brief.md");
|
|
20595
|
-
if (!
|
|
20596
|
-
const text =
|
|
21128
|
+
if (!existsSync6(file2)) return { name: fallbackName, summary: pkg?.description ?? "See `.aura/brief.md` once the product brief is added." };
|
|
21129
|
+
const text = readFileSync5(file2, "utf8");
|
|
20597
21130
|
const title = text.match(/^#\s+(.+)$/m)?.[1]?.replace(/[:\-–—]?\s*product brief$/i, "").trim();
|
|
20598
21131
|
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
21132
|
const sentences = paragraph.replace(/\s+/g, " ").match(/[^.!?]+[.!?]+/g) ?? [paragraph];
|
|
@@ -20758,7 +21291,6 @@ var ACTIONS = [
|
|
|
20758
21291
|
},
|
|
20759
21292
|
// Accounts and ownership
|
|
20760
21293
|
{ 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
21294
|
{ id: "transfer-ownership", title: "Move accounts to a company owner", category: "security", delivery: "guide", resolves: [] }
|
|
20763
21295
|
];
|
|
20764
21296
|
var FOUNDATION_BUNDLE = ["add-brief", "add-agent-rules", "install-skills", "token-efficiency", "add-ci", "add-env-example", "add-aura-workflow"];
|
|
@@ -20773,6 +21305,15 @@ function getAction(id) {
|
|
|
20773
21305
|
};
|
|
20774
21306
|
}
|
|
20775
21307
|
if (/^rotate-[a-z]+-(?:key|token)$/.test(id)) return { id, title: "Rotate an exposed key", category: "security", delivery: "guide", resolves: [] };
|
|
21308
|
+
const connector = connectorForAction(id);
|
|
21309
|
+
if (connector)
|
|
21310
|
+
return {
|
|
21311
|
+
id,
|
|
21312
|
+
title: `Turn on ${connector.name} security features`,
|
|
21313
|
+
category: "security",
|
|
21314
|
+
delivery: "guide",
|
|
21315
|
+
resolves: [`security.${connector.id}-settings`]
|
|
21316
|
+
};
|
|
20776
21317
|
return ACTIONS.find((a) => a.id === id);
|
|
20777
21318
|
}
|
|
20778
21319
|
var CATEGORY_PRIORITY = ["security", "agent-setup", "foundations", "structure"];
|
|
@@ -20808,15 +21349,16 @@ function nextSteps(scan, opts = {}) {
|
|
|
20808
21349
|
if (!action2) continue;
|
|
20809
21350
|
const { delivery, fellBack } = effectiveDelivery(action2, scan);
|
|
20810
21351
|
const worst = fs.map((f) => f.severity).sort((a, b) => SEVERITY_ORDER.indexOf(a) - SEVERITY_ORDER.indexOf(b))[0];
|
|
21352
|
+
const connected = Boolean(connectorForAction(actionId));
|
|
20811
21353
|
steps.push({
|
|
20812
21354
|
actionId,
|
|
20813
21355
|
title: action2.title,
|
|
20814
21356
|
delivery,
|
|
20815
|
-
effort: EFFORT[delivery],
|
|
21357
|
+
effort: connected ? "one-click" : EFFORT[delivery],
|
|
20816
21358
|
category: action2.category,
|
|
20817
21359
|
why: fs[0].why,
|
|
20818
21360
|
impact: impactOf(scan.checks, action2.resolves),
|
|
20819
|
-
command: commandFor(actionId, delivery),
|
|
21361
|
+
command: connected ? `aura apply ${actionId}` : commandFor(actionId, delivery),
|
|
20820
21362
|
findingIds: fs.map((f) => f.id),
|
|
20821
21363
|
fellBack,
|
|
20822
21364
|
severity: worst
|
|
@@ -20841,16 +21383,16 @@ function nextSteps(scan, opts = {}) {
|
|
|
20841
21383
|
severity: "critical"
|
|
20842
21384
|
});
|
|
20843
21385
|
}
|
|
20844
|
-
|
|
21386
|
+
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
21387
|
steps.push({
|
|
20846
|
-
actionId:
|
|
20847
|
-
title:
|
|
21388
|
+
actionId: connector.actionId,
|
|
21389
|
+
title: `Turn on ${connector.name} security features`,
|
|
20848
21390
|
delivery: "guide",
|
|
20849
|
-
effort: "
|
|
21391
|
+
effort: "one-click",
|
|
20850
21392
|
category: "security",
|
|
20851
|
-
why:
|
|
21393
|
+
why: `Aura checks ${connector.name}\u2019s security settings and, once you agree, turns on the ones that are off.`,
|
|
20852
21394
|
impact: 0,
|
|
20853
|
-
command:
|
|
21395
|
+
command: `aura apply ${connector.actionId}`,
|
|
20854
21396
|
findingIds: [],
|
|
20855
21397
|
severity: "low"
|
|
20856
21398
|
});
|
|
@@ -20899,12 +21441,12 @@ function nextSteps(scan, opts = {}) {
|
|
|
20899
21441
|
}
|
|
20900
21442
|
|
|
20901
21443
|
// src/actions/generators.ts
|
|
20902
|
-
import { existsSync as
|
|
20903
|
-
import
|
|
21444
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
|
|
21445
|
+
import path12 from "path";
|
|
20904
21446
|
|
|
20905
21447
|
// src/checks/agent-setup.ts
|
|
20906
|
-
import { existsSync as
|
|
20907
|
-
import
|
|
21448
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "fs";
|
|
21449
|
+
import path8 from "path";
|
|
20908
21450
|
|
|
20909
21451
|
// src/checks/types.ts
|
|
20910
21452
|
import { createHash } from "crypto";
|
|
@@ -20938,18 +21480,18 @@ function lineOf(text, index) {
|
|
|
20938
21480
|
|
|
20939
21481
|
// src/checks/agent-setup.ts
|
|
20940
21482
|
function exists(ctx, file2) {
|
|
20941
|
-
return
|
|
21483
|
+
return existsSync7(path8.join(ctx.root, file2));
|
|
20942
21484
|
}
|
|
20943
21485
|
function readDisk(ctx, file2) {
|
|
20944
21486
|
try {
|
|
20945
|
-
return
|
|
21487
|
+
return readFileSync6(path8.join(ctx.root, file2), "utf8");
|
|
20946
21488
|
} catch {
|
|
20947
21489
|
return "";
|
|
20948
21490
|
}
|
|
20949
21491
|
}
|
|
20950
21492
|
function skillsIn(ctx, dir) {
|
|
20951
21493
|
try {
|
|
20952
|
-
return
|
|
21494
|
+
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
21495
|
} catch {
|
|
20954
21496
|
return [];
|
|
20955
21497
|
}
|
|
@@ -20966,7 +21508,7 @@ function rulesFileFor(ctx, agent) {
|
|
|
20966
21508
|
if (agent === "cursor") {
|
|
20967
21509
|
if (exists(ctx, ".cursorrules")) return ".cursorrules";
|
|
20968
21510
|
try {
|
|
20969
|
-
const rules =
|
|
21511
|
+
const rules = readdirSync3(path8.join(ctx.root, ".cursor/rules")).filter((f) => /\.mdc?$/.test(f));
|
|
20970
21512
|
if (rules.length) return `.cursor/rules/${rules[0]}`;
|
|
20971
21513
|
} catch {
|
|
20972
21514
|
}
|
|
@@ -21111,7 +21653,7 @@ var brief = {
|
|
|
21111
21653
|
var agentChecks = [rulesFile, rulesCurrent, skills, auraSkill, brief];
|
|
21112
21654
|
|
|
21113
21655
|
// src/core/graph.ts
|
|
21114
|
-
import
|
|
21656
|
+
import path9 from "path";
|
|
21115
21657
|
var EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"];
|
|
21116
21658
|
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
21659
|
function parseImports(text) {
|
|
@@ -21139,7 +21681,7 @@ function readAliases(read2) {
|
|
|
21139
21681
|
for (const [key, targets] of Object.entries(json2.compilerOptions?.paths ?? {})) {
|
|
21140
21682
|
const target = targets[0];
|
|
21141
21683
|
if (!key.endsWith("/*") || !target?.endsWith("/*")) continue;
|
|
21142
|
-
const resolved =
|
|
21684
|
+
const resolved = path9.posix.normalize(path9.posix.join(base || ".", target.slice(0, -1)));
|
|
21143
21685
|
entries.push([key.slice(0, -1), resolved === "." || resolved === "./" ? "" : resolved.replace(/\/?$/, "/")]);
|
|
21144
21686
|
}
|
|
21145
21687
|
} catch {
|
|
@@ -21150,10 +21692,10 @@ function readAliases(read2) {
|
|
|
21150
21692
|
}
|
|
21151
21693
|
function resolveImport(from, spec, files, aliases) {
|
|
21152
21694
|
let base = null;
|
|
21153
|
-
if (spec.startsWith(".")) base =
|
|
21695
|
+
if (spec.startsWith(".")) base = path9.posix.normalize(path9.posix.join(path9.posix.dirname(from), spec));
|
|
21154
21696
|
else {
|
|
21155
21697
|
const alias = aliases.entries.find(([prefix]) => spec.startsWith(prefix));
|
|
21156
|
-
if (alias) base =
|
|
21698
|
+
if (alias) base = path9.posix.normalize(alias[1] + spec.slice(alias[0].length));
|
|
21157
21699
|
}
|
|
21158
21700
|
if (base === null) return null;
|
|
21159
21701
|
const withoutJs = base.replace(/\.(?:js|jsx|mjs|cjs)$/, "");
|
|
@@ -21199,14 +21741,14 @@ function unreachableFiles(files, read2, pkgScripts) {
|
|
|
21199
21741
|
}
|
|
21200
21742
|
|
|
21201
21743
|
// src/actions/agent-commands.ts
|
|
21202
|
-
import { readFileSync as
|
|
21203
|
-
import
|
|
21744
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
21745
|
+
import path10 from "path";
|
|
21204
21746
|
var AURA_COMMANDS = ["scan", "next", "fix", "setup"];
|
|
21205
21747
|
function invocation(agent, command) {
|
|
21206
21748
|
return agent === "claude" ? `/aura:${command}` : agent === "codex" ? `$aura-${command}` : `/aura-${command}`;
|
|
21207
21749
|
}
|
|
21208
21750
|
function pluginCommand(command) {
|
|
21209
|
-
const text =
|
|
21751
|
+
const text = readFileSync7(path10.join(path10.dirname(templatesDir()), "plugin", "skills", command, "SKILL.md"), "utf8").replace(/\r\n/g, "\n");
|
|
21210
21752
|
const [, frontmatter = "", body = ""] = text.match(/^---\n([\s\S]*?)\n---\n+([\s\S]*)$/) ?? [];
|
|
21211
21753
|
const description = frontmatter.match(/^description: (.+)$/m)?.[1] ?? "";
|
|
21212
21754
|
return { description, body };
|
|
@@ -21251,11 +21793,11 @@ function commandList(agent) {
|
|
|
21251
21793
|
}
|
|
21252
21794
|
|
|
21253
21795
|
// src/actions/agent-files.ts
|
|
21254
|
-
import { existsSync as
|
|
21255
|
-
import
|
|
21796
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
|
|
21797
|
+
import path11 from "path";
|
|
21256
21798
|
function current(root, file2) {
|
|
21257
|
-
const full =
|
|
21258
|
-
return
|
|
21799
|
+
const full = path11.join(root, file2);
|
|
21800
|
+
return existsSync8(full) ? readFileSync8(full, "utf8") : null;
|
|
21259
21801
|
}
|
|
21260
21802
|
function upsertBlock(existing, body) {
|
|
21261
21803
|
const block = `${AURA_BLOCK_START}
|
|
@@ -21299,8 +21841,8 @@ function skillChanges(agents, names) {
|
|
|
21299
21841
|
|
|
21300
21842
|
// src/actions/generators.ts
|
|
21301
21843
|
function read(root, file2) {
|
|
21302
|
-
const full =
|
|
21303
|
-
return
|
|
21844
|
+
const full = path12.join(root, file2);
|
|
21845
|
+
return existsSync9(full) ? readFileSync9(full, "utf8") : null;
|
|
21304
21846
|
}
|
|
21305
21847
|
function withScripts(root, scripts, overwrite = false) {
|
|
21306
21848
|
const text = read(root, "package.json");
|
|
@@ -21321,13 +21863,13 @@ function findingMeta(scan, checkId, key) {
|
|
|
21321
21863
|
return scan.checks.find((c) => c.checkId === checkId)?.findings[0]?.meta?.[key];
|
|
21322
21864
|
}
|
|
21323
21865
|
var addBrief = ({ root }) => {
|
|
21324
|
-
const brief2 = BRIEF_PATHS.find((f) =>
|
|
21866
|
+
const brief2 = BRIEF_PATHS.find((f) => existsSync9(path12.join(root, f)));
|
|
21325
21867
|
if (!brief2) return { skip: "There is no product brief yet. Run `aura init` to draft and confirm it first." };
|
|
21326
21868
|
const changes = [
|
|
21327
21869
|
{ path: brief2, content: read(root, brief2) },
|
|
21328
21870
|
{ path: ".aura/.gitignore", content: AURA_GITIGNORE }
|
|
21329
21871
|
];
|
|
21330
|
-
if (
|
|
21872
|
+
if (existsSync9(auraPath(root, "config.json"))) changes.push({ path: ".aura/config.json", content: read(root, ".aura/config.json") });
|
|
21331
21873
|
return { changes, details: [`- Adds \`${brief2}\`, \`.aura/config.json\` and \`.aura/.gitignore\` (keeps Aura's local reports out of git)`] };
|
|
21332
21874
|
};
|
|
21333
21875
|
var addAgentRules = ({ root, agents, vars }) => {
|
|
@@ -21346,14 +21888,14 @@ var installSkills = ({ agents, skills: skills2 }) => {
|
|
|
21346
21888
|
};
|
|
21347
21889
|
var tokenEfficiency = (g) => {
|
|
21348
21890
|
const changes = [];
|
|
21349
|
-
if (!
|
|
21891
|
+
if (!existsSync9(path12.join(g.root, "docs/index.md"))) changes.push({ path: "docs/index.md", content: render("docs/index.md", g.vars) });
|
|
21350
21892
|
changes.push(...rulesChanges(g.root, g.agents, g.vars));
|
|
21351
21893
|
return { changes, details: ["- `docs/index.md`: a map of the repo your agent reads first", "- Lean-context rules in the agent rules file"] };
|
|
21352
21894
|
};
|
|
21353
21895
|
var addCi = ({ root, ctx, vars }) => {
|
|
21354
21896
|
const existing = ctx.files.filter((f) => /^\.github\/workflows\//.test(f));
|
|
21355
21897
|
const file2 = existing.includes(".github/workflows/ci.yml") ? ".github/workflows/checks.yml" : ".github/workflows/ci.yml";
|
|
21356
|
-
if (
|
|
21898
|
+
if (existsSync9(path12.join(root, file2))) return { skip: `${file2} already exists.` };
|
|
21357
21899
|
return {
|
|
21358
21900
|
changes: [{ path: file2, content: render("github/workflows/ci.yml", vars) }],
|
|
21359
21901
|
details: [`- \`${file2}\`: lint, type check, tests and build on every pull request`]
|
|
@@ -21377,7 +21919,7 @@ var setupTesting = ({ root, ctx, vars }) => {
|
|
|
21377
21919
|
};
|
|
21378
21920
|
};
|
|
21379
21921
|
var addEnvExample = ({ root, scan }) => {
|
|
21380
|
-
const file2 = [".env.example", ".env.sample", ".env.template"].find((f) =>
|
|
21922
|
+
const file2 = [".env.example", ".env.sample", ".env.template"].find((f) => existsSync9(path12.join(root, f))) ?? ".env.example";
|
|
21381
21923
|
const existing = read(root, file2);
|
|
21382
21924
|
const declared = new Set(
|
|
21383
21925
|
(existing ?? "").split(/\r?\n/).map((l) => l.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/)?.[1]).filter(Boolean)
|
|
@@ -21434,7 +21976,7 @@ var addLinting = ({ root, ctx }) => {
|
|
|
21434
21976
|
return { changes, devDeps, details };
|
|
21435
21977
|
};
|
|
21436
21978
|
var addPreCommit = ({ root, ctx, vars }) => {
|
|
21437
|
-
if (
|
|
21979
|
+
if (existsSync9(path12.join(root, ".husky/pre-commit"))) return { skip: "A husky pre-commit hook already exists." };
|
|
21438
21980
|
const changes = [{ path: ".husky/pre-commit", content: render("configs/husky-pre-commit", vars) }];
|
|
21439
21981
|
const scripts = withScripts(root, { prepare: "husky" }, false);
|
|
21440
21982
|
if (scripts) changes.push(scripts);
|
|
@@ -21464,7 +22006,7 @@ ${current2.trim()}
|
|
|
21464
22006
|
};
|
|
21465
22007
|
};
|
|
21466
22008
|
var enableDependencyUpdates = ({ root, vars }) => {
|
|
21467
|
-
if ([".github/dependabot.yml", ".github/dependabot.yaml", "renovate.json", ".github/renovate.json"].some((f) =>
|
|
22009
|
+
if ([".github/dependabot.yml", ".github/dependabot.yaml", "renovate.json", ".github/renovate.json"].some((f) => existsSync9(path12.join(root, f)))) {
|
|
21468
22010
|
return { skip: "Dependabot or Renovate is already configured." };
|
|
21469
22011
|
}
|
|
21470
22012
|
return {
|
|
@@ -21544,7 +22086,7 @@ function planMoves(root, ctx, moves) {
|
|
|
21544
22086
|
const alias = aliasFor(toFile);
|
|
21545
22087
|
if (alias) return alias[0] + noExt.slice(alias[1].length);
|
|
21546
22088
|
}
|
|
21547
|
-
let rel =
|
|
22089
|
+
let rel = path12.posix.relative(path12.posix.dirname(fromFile), noExt);
|
|
21548
22090
|
if (!rel.startsWith(".")) rel = `./${rel}`;
|
|
21549
22091
|
return rel;
|
|
21550
22092
|
};
|
|
@@ -21578,7 +22120,7 @@ function planMoves(root, ctx, moves) {
|
|
|
21578
22120
|
}
|
|
21579
22121
|
var moveMisplacedFiles = ({ root, ctx, scan }) => {
|
|
21580
22122
|
const raw = findingMeta(scan, "structure.layout", "moves") ?? [];
|
|
21581
|
-
const moves = raw.map((m) => m.split("=>")).filter(([, to]) => !
|
|
22123
|
+
const moves = raw.map((m) => m.split("=>")).filter(([, to]) => !existsSync9(path12.join(root, to))).slice(0, MAX_MOVES);
|
|
21582
22124
|
if (moves.length === 0) return { skip: "No files to move (or their targets already exist)." };
|
|
21583
22125
|
return { changes: planMoves(root, ctx, moves), details: moves.map(([from, to]) => `- \`${from}\` \u2192 \`${to}\``), verifyTypes: true };
|
|
21584
22126
|
};
|
|
@@ -21669,7 +22211,7 @@ var AuraClient = class _AuraClient {
|
|
|
21669
22211
|
get hasKey() {
|
|
21670
22212
|
return Boolean(this.key);
|
|
21671
22213
|
}
|
|
21672
|
-
async request(method,
|
|
22214
|
+
async request(method, path27, body, timeoutMs = 6e4) {
|
|
21673
22215
|
if (!this.key) throw new AuraApiError("no_key", "No pilot key");
|
|
21674
22216
|
let payload;
|
|
21675
22217
|
if (body !== void 0) {
|
|
@@ -21679,7 +22221,7 @@ var AuraClient = class _AuraClient {
|
|
|
21679
22221
|
}
|
|
21680
22222
|
let res;
|
|
21681
22223
|
try {
|
|
21682
|
-
res = await fetch(`${this.baseUrl}/v1/${
|
|
22224
|
+
res = await fetch(`${this.baseUrl}/v1/${path27}`, {
|
|
21683
22225
|
method,
|
|
21684
22226
|
headers: { authorization: `Bearer ${this.key}`, "content-type": "application/json", "user-agent": `getaura/${AURA_VERSION}` },
|
|
21685
22227
|
body: payload,
|
|
@@ -21743,13 +22285,13 @@ var say = {
|
|
|
21743
22285
|
};
|
|
21744
22286
|
|
|
21745
22287
|
// src/fix-tracking.ts
|
|
21746
|
-
import { existsSync as
|
|
22288
|
+
import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
|
|
21747
22289
|
var FILE = "fixes.json";
|
|
21748
22290
|
var MAX_AGE_MS = 60 * 24 * 60 * 60 * 1e3;
|
|
21749
22291
|
var MAX_CHECKS_PER_SCAN = 5;
|
|
21750
22292
|
function loadTrackedFixes(root) {
|
|
21751
22293
|
try {
|
|
21752
|
-
return
|
|
22294
|
+
return existsSync10(auraPath(root, FILE)) ? JSON.parse(readFileSync10(auraPath(root, FILE), "utf8")) : [];
|
|
21753
22295
|
} catch {
|
|
21754
22296
|
return [];
|
|
21755
22297
|
}
|
|
@@ -21857,70 +22399,6 @@ async function recordAction(root, config2, actionId, report, startedAt, via) {
|
|
|
21857
22399
|
}
|
|
21858
22400
|
}
|
|
21859
22401
|
|
|
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
22402
|
// src/checks/foundations.ts
|
|
21925
22403
|
import YAML from "yaml";
|
|
21926
22404
|
var ciWorkflow = {
|
|
@@ -22120,10 +22598,10 @@ var typecheck = {
|
|
|
22120
22598
|
applies: (ctx) => ctx.stack.typescript,
|
|
22121
22599
|
async run(ctx) {
|
|
22122
22600
|
if (ctx.options.fast) return { status: "unknown", summary: "Skipped in fast mode." };
|
|
22123
|
-
const { existsSync:
|
|
22124
|
-
const
|
|
22125
|
-
const tsc =
|
|
22126
|
-
if (!
|
|
22601
|
+
const { existsSync: existsSync21 } = await import("fs");
|
|
22602
|
+
const path27 = await import("path");
|
|
22603
|
+
const tsc = path27.join(ctx.root, "node_modules", ".bin", process.platform === "win32" ? "tsc.cmd" : "tsc");
|
|
22604
|
+
if (!existsSync21(tsc)) return { status: "unknown", summary: "Dependencies not installed, so the type check was skipped." };
|
|
22127
22605
|
const r = await run(tsc, ["--noEmit", "--pretty", "false"], { cwd: ctx.root, timeoutMs: 18e4 });
|
|
22128
22606
|
if (r.timedOut) return { status: "unknown", summary: "Type check timed out." };
|
|
22129
22607
|
if (r.code === 0) return { status: "pass", summary: "No type errors.", engine: "tsc" };
|
|
@@ -22143,6 +22621,56 @@ var typecheck = {
|
|
|
22143
22621
|
};
|
|
22144
22622
|
var foundationChecks = [ciWorkflow, tests, readme, envExample, lockfile, lintFormat, typecheck];
|
|
22145
22623
|
|
|
22624
|
+
// src/connectors/snapshot.ts
|
|
22625
|
+
import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
|
|
22626
|
+
var FILE2 = "cache/connectors.json";
|
|
22627
|
+
function loadSnapshots(root) {
|
|
22628
|
+
try {
|
|
22629
|
+
return existsSync11(auraPath(root, FILE2)) ? JSON.parse(readFileSync11(auraPath(root, FILE2), "utf8")) : {};
|
|
22630
|
+
} catch {
|
|
22631
|
+
return {};
|
|
22632
|
+
}
|
|
22633
|
+
}
|
|
22634
|
+
function saveSnapshot(root, toolId, states) {
|
|
22635
|
+
const all = loadSnapshots(root);
|
|
22636
|
+
all[toolId] = { checkedAt: (/* @__PURE__ */ new Date()).toISOString(), settings: Object.fromEntries(Object.entries(states).map(([id, s]) => [id, s.state])) };
|
|
22637
|
+
writeAuraFile(root, FILE2, `${JSON.stringify(all, null, 2)}
|
|
22638
|
+
`);
|
|
22639
|
+
}
|
|
22640
|
+
var isStale = (snap, now = Date.now()) => !snap || now - Date.parse(snap.checkedAt) > 24 * 60 * 60 * 1e3;
|
|
22641
|
+
|
|
22642
|
+
// src/checks/security/tool-settings.ts
|
|
22643
|
+
function toolSettingsCheck(connector) {
|
|
22644
|
+
return {
|
|
22645
|
+
id: `security.${connector.id}-settings`,
|
|
22646
|
+
category: "security",
|
|
22647
|
+
title: `${connector.name} security settings`,
|
|
22648
|
+
weight: 3,
|
|
22649
|
+
applies: (ctx) => {
|
|
22650
|
+
const snap = loadSnapshots(ctx.root)[connector.id];
|
|
22651
|
+
return Boolean(snap && Object.values(snap.settings).some((s) => s !== "unavailable"));
|
|
22652
|
+
},
|
|
22653
|
+
run(ctx) {
|
|
22654
|
+
const snap = loadSnapshots(ctx.root)[connector.id];
|
|
22655
|
+
const available = connector.settings.filter((s) => snap.settings[s.id] === "on" || snap.settings[s.id] === "off");
|
|
22656
|
+
const off = available.filter((s) => snap.settings[s.id] === "off");
|
|
22657
|
+
const engine = `${connector.name}, checked ${snap.checkedAt.slice(0, 10)}`;
|
|
22658
|
+
if (off.length === 0) return { status: "pass", summary: `All ${available.length} available ${connector.name} security settings are on.`, engine };
|
|
22659
|
+
const finding = makeFinding(this, {
|
|
22660
|
+
severity: off.some((s) => /protect|push|secret/.test(s.id)) ? "medium" : "low",
|
|
22661
|
+
title: `${off.length} ${connector.name} security setting(s) off`,
|
|
22662
|
+
detail: `Off in ${connector.name}: ${off.map((s) => s.label).join("; ")}.`,
|
|
22663
|
+
why: "These settings catch leaked keys, vulnerable packages and risky changes before they cause harm, and they cost nothing to turn on.",
|
|
22664
|
+
locations: [],
|
|
22665
|
+
actionId: connector.actionId,
|
|
22666
|
+
key: `${connector.id}-settings`
|
|
22667
|
+
});
|
|
22668
|
+
const score = (available.length - off.length) / available.length;
|
|
22669
|
+
return { status: score === 0 ? "fail" : "partial", score, summary: finding.title, findings: [finding], engine };
|
|
22670
|
+
}
|
|
22671
|
+
};
|
|
22672
|
+
}
|
|
22673
|
+
|
|
22146
22674
|
// src/checks/security/api-routes.ts
|
|
22147
22675
|
var AUTH_SIGNALS = [
|
|
22148
22676
|
/\bauth\.getUser\s*\(/,
|
|
@@ -22249,9 +22777,9 @@ var apiRoutes = {
|
|
|
22249
22777
|
|
|
22250
22778
|
// src/core/tools.ts
|
|
22251
22779
|
import { createHash as createHash3 } from "crypto";
|
|
22252
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
22253
|
-
import
|
|
22254
|
-
import
|
|
22780
|
+
import { chmodSync as chmodSync2, existsSync as existsSync12, mkdirSync as mkdirSync3, mkdtempSync, readdirSync as readdirSync4, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
22781
|
+
import os3 from "os";
|
|
22782
|
+
import path13 from "path";
|
|
22255
22783
|
var GITLEAKS_SHA = {
|
|
22256
22784
|
darwin_arm64: "b40ab0ae55c505963e365f271a8d3846efbc170aa17f2607f13df610a9aeb6a5",
|
|
22257
22785
|
darwin_x64: "dfe101a4db2255fc85120ac7f3d25e4342c3c20cf749f2c20a18081af1952709",
|
|
@@ -22269,7 +22797,7 @@ var OSV_SHA = {
|
|
|
22269
22797
|
windows_arm64: "ca1379da0e408279ef2c85cde0846a903495738a91c7b065a208048437c1686c"
|
|
22270
22798
|
};
|
|
22271
22799
|
function tarCommand(platform = process.platform, systemRoot = process.env.SystemRoot) {
|
|
22272
|
-
return platform === "win32" ?
|
|
22800
|
+
return platform === "win32" ? path13.win32.join(systemRoot || "C:\\Windows", "System32", "tar.exe") : "tar";
|
|
22273
22801
|
}
|
|
22274
22802
|
var TOOLS = {
|
|
22275
22803
|
gitleaks: {
|
|
@@ -22305,8 +22833,8 @@ function ensureTool(name, noDownload = false) {
|
|
|
22305
22833
|
async function resolveTool(name, noDownload) {
|
|
22306
22834
|
const spec = TOOLS[name];
|
|
22307
22835
|
const exe = process.platform === "win32" ? `${name}.exe` : name;
|
|
22308
|
-
const cached2 =
|
|
22309
|
-
if (
|
|
22836
|
+
const cached2 = path13.join(auraBinDir(), `${name}-${spec.version}`, exe);
|
|
22837
|
+
if (existsSync12(cached2)) return { path: cached2, version: spec.version };
|
|
22310
22838
|
if (await commandExists(name)) {
|
|
22311
22839
|
const v = await run(name, ["version"]);
|
|
22312
22840
|
return { path: name, version: (v.stdout.match(/\d+\.\d+\.\d+/)?.[0] ?? "system").trim() };
|
|
@@ -22326,20 +22854,20 @@ async function download(name, spec, target) {
|
|
|
22326
22854
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
22327
22855
|
const digest = createHash3("sha256").update(buf).digest("hex");
|
|
22328
22856
|
if (digest !== asset.sha256) throw new Error(`Checksum mismatch for ${asset.file}`);
|
|
22329
|
-
const dir =
|
|
22857
|
+
const dir = path13.dirname(target);
|
|
22330
22858
|
mkdirSync3(dir, { recursive: true });
|
|
22331
|
-
const tmp = mkdtempSync(
|
|
22859
|
+
const tmp = mkdtempSync(path13.join(os3.tmpdir(), `aura-${name}-`));
|
|
22332
22860
|
try {
|
|
22333
22861
|
if (asset.archive === null) {
|
|
22334
22862
|
writeFileSync3(target, buf);
|
|
22335
22863
|
} else {
|
|
22336
|
-
const archivePath =
|
|
22864
|
+
const archivePath = path13.join(tmp, asset.file);
|
|
22337
22865
|
writeFileSync3(archivePath, buf);
|
|
22338
22866
|
const r = await run(tarCommand(), ["-xf", archivePath, "-C", tmp]);
|
|
22339
22867
|
if (r.code !== 0) return null;
|
|
22340
|
-
const exe =
|
|
22868
|
+
const exe = readdirSync4(tmp).find((f) => f === name || f === `${name}.exe`);
|
|
22341
22869
|
if (!exe) return null;
|
|
22342
|
-
renameSync(
|
|
22870
|
+
renameSync(path13.join(tmp, exe), target);
|
|
22343
22871
|
}
|
|
22344
22872
|
chmodSync2(target, 493);
|
|
22345
22873
|
return { path: target, version: spec.version };
|
|
@@ -22610,9 +23138,9 @@ var rls = {
|
|
|
22610
23138
|
};
|
|
22611
23139
|
|
|
22612
23140
|
// src/checks/security/secrets.ts
|
|
22613
|
-
import { existsSync as
|
|
22614
|
-
import
|
|
22615
|
-
import
|
|
23141
|
+
import { existsSync as existsSync13, mkdtempSync as mkdtempSync2, readFileSync as readFileSync12, rmSync as rmSync3 } from "fs";
|
|
23142
|
+
import os4 from "os";
|
|
23143
|
+
import path14 from "path";
|
|
22616
23144
|
|
|
22617
23145
|
// src/core/ignore.ts
|
|
22618
23146
|
function globToRegex(glob) {
|
|
@@ -22650,15 +23178,15 @@ async function gitleaksHistory(ctx) {
|
|
|
22650
23178
|
if (!ctx.tracked) return null;
|
|
22651
23179
|
const bin = await ensureTool("gitleaks", ctx.options.noDownload);
|
|
22652
23180
|
if (!bin) return null;
|
|
22653
|
-
const dir = mkdtempSync2(
|
|
22654
|
-
const report =
|
|
23181
|
+
const dir = mkdtempSync2(path14.join(os4.tmpdir(), "aura-gitleaks-"));
|
|
23182
|
+
const report = path14.join(dir, "report.json");
|
|
22655
23183
|
try {
|
|
22656
23184
|
const r = await run(bin.path, ["git", "--no-banner", "--redact", "--report-format", "json", "--report-path", report, "--exit-code", "0", "."], {
|
|
22657
23185
|
cwd: ctx.root,
|
|
22658
23186
|
timeoutMs: 18e4
|
|
22659
23187
|
});
|
|
22660
|
-
if (r.code !== 0 || !
|
|
22661
|
-
return { hits: JSON.parse(
|
|
23188
|
+
if (r.code !== 0 || !existsSync13(report)) return null;
|
|
23189
|
+
return { hits: JSON.parse(readFileSync12(report, "utf8")), engine: `gitleaks ${bin.version}` };
|
|
22662
23190
|
} catch {
|
|
22663
23191
|
return null;
|
|
22664
23192
|
} finally {
|
|
@@ -22770,10 +23298,10 @@ var envFiles = {
|
|
|
22770
23298
|
};
|
|
22771
23299
|
|
|
22772
23300
|
// src/checks/security.ts
|
|
22773
|
-
var securityChecks = [secrets, envFiles, deps, rls, apiRoutes, headers];
|
|
23301
|
+
var securityChecks = [secrets, envFiles, deps, rls, apiRoutes, headers, ...CONNECTORS.map(toolSettingsCheck)];
|
|
22774
23302
|
|
|
22775
23303
|
// src/checks/structure.ts
|
|
22776
|
-
import
|
|
23304
|
+
import path15 from "path";
|
|
22777
23305
|
var GENERATED = /(?:^|\/)(?:supabase\/migrations|generated|__generated__|\.generated)\/|\.(?:d\.ts|min\.js)$|(?:^|\/)(?:database|supabase)\.types\.ts$|(?:^|\/)components\/ui\//;
|
|
22778
23306
|
function appSource(ctx) {
|
|
22779
23307
|
return ctx.files.filter((f) => isSourceFile(f) && !GENERATED.test(f));
|
|
@@ -22862,7 +23390,7 @@ var duplicates = {
|
|
|
22862
23390
|
const findings = pairs.map(
|
|
22863
23391
|
([a, b, sim]) => makeFinding(this, {
|
|
22864
23392
|
severity: "low",
|
|
22865
|
-
title: `${
|
|
23393
|
+
title: `${path15.basename(a)} and ${path15.basename(b)} are near-duplicates`,
|
|
22866
23394
|
detail: `${a} and ${b} are ${Math.round(sim * 100)}% the same.`,
|
|
22867
23395
|
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
23396
|
locations: [{ file: a }, { file: b }],
|
|
@@ -22876,7 +23404,7 @@ var duplicates = {
|
|
|
22876
23404
|
};
|
|
22877
23405
|
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
23406
|
function suggestTarget(file2, srcDir) {
|
|
22879
|
-
const base =
|
|
23407
|
+
const base = path15.posix.basename(file2);
|
|
22880
23408
|
const prefix = srcDir ? "src/" : "";
|
|
22881
23409
|
const isComponent = /^[A-Z][A-Za-z0-9]*\.(?:tsx|jsx)$/.test(base);
|
|
22882
23410
|
if (isComponent) return `${prefix}components/${base}`;
|
|
@@ -22989,21 +23517,21 @@ async function runScan(root, options, onProgress) {
|
|
|
22989
23517
|
}
|
|
22990
23518
|
|
|
22991
23519
|
// src/commands/fix.ts
|
|
22992
|
-
import { existsSync as
|
|
22993
|
-
import
|
|
23520
|
+
import { existsSync as existsSync18, readFileSync as readFileSync19, writeFileSync as writeFileSync6 } from "fs";
|
|
23521
|
+
import path19 from "path";
|
|
22994
23522
|
|
|
22995
23523
|
// src/agent/tools.ts
|
|
22996
|
-
import { existsSync as
|
|
22997
|
-
import
|
|
23524
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync4, readFileSync as readFileSync13, rmSync as rmSync4, statSync as statSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
23525
|
+
import path16 from "path";
|
|
22998
23526
|
var MAX_OUTPUT = 12e3;
|
|
22999
23527
|
var MAX_FILE_LINES = 800;
|
|
23000
23528
|
var FORBIDDEN = /^(?:\.git|node_modules|\.next|\.vercel)(?:\/|$)|(?:^|\/)\.env(?:\.(?!example$|sample$|template$)[^/]+)?$/;
|
|
23001
23529
|
function safePath(root, rel) {
|
|
23002
23530
|
if (typeof rel !== "string" || !rel.trim()) throw new Error("A file path is required.");
|
|
23003
|
-
const normalized =
|
|
23004
|
-
if (normalized.startsWith("../") || normalized === ".." ||
|
|
23531
|
+
const normalized = path16.posix.normalize(rel.replace(/\\/g, "/").replace(/^\.\//, ""));
|
|
23532
|
+
if (normalized.startsWith("../") || normalized === ".." || path16.isAbsolute(normalized)) throw new Error(`Path is outside the repo: ${rel}`);
|
|
23005
23533
|
if (FORBIDDEN.test(normalized)) throw new Error(`Aura doesn\u2019t let the agent touch ${normalized}.`);
|
|
23006
|
-
const full =
|
|
23534
|
+
const full = path16.join(root, normalized);
|
|
23007
23535
|
if (!isInside(root, full)) throw new Error(`Path is outside the repo: ${rel}`);
|
|
23008
23536
|
return normalized;
|
|
23009
23537
|
}
|
|
@@ -23024,10 +23552,10 @@ async function runTool(root, call, allowedCommands2) {
|
|
|
23024
23552
|
}
|
|
23025
23553
|
case "read_file": {
|
|
23026
23554
|
const rel = safePath(root, input2.path);
|
|
23027
|
-
const full =
|
|
23028
|
-
if (!
|
|
23555
|
+
const full = path16.join(root, rel);
|
|
23556
|
+
if (!existsSync14(full)) return { output: `File not found: ${rel}` };
|
|
23029
23557
|
if (statSync2(full).size > 1e6) return { output: `${rel} is too large to read.` };
|
|
23030
|
-
const lines =
|
|
23558
|
+
const lines = readFileSync13(full, "utf8").split("\n");
|
|
23031
23559
|
const start = Math.max(1, Number(input2.startLine) || 1);
|
|
23032
23560
|
const end = Math.min(lines.length, Number(input2.endLine) || start + MAX_FILE_LINES - 1);
|
|
23033
23561
|
const body = lines.slice(start - 1, end).map((l, i) => `${start + i}: ${l}`).join("\n");
|
|
@@ -23044,7 +23572,7 @@ async function runTool(root, call, allowedCommands2) {
|
|
|
23044
23572
|
if (FORBIDDEN.test(f) || glob && !glob.test(f)) continue;
|
|
23045
23573
|
let text;
|
|
23046
23574
|
try {
|
|
23047
|
-
text =
|
|
23575
|
+
text = readFileSync13(path16.join(root, f), "utf8");
|
|
23048
23576
|
} catch {
|
|
23049
23577
|
continue;
|
|
23050
23578
|
}
|
|
@@ -23063,14 +23591,14 @@ async function runTool(root, call, allowedCommands2) {
|
|
|
23063
23591
|
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
23592
|
};
|
|
23065
23593
|
}
|
|
23066
|
-
mkdirSync4(
|
|
23067
|
-
writeFileSync4(
|
|
23594
|
+
mkdirSync4(path16.dirname(path16.join(root, rel)), { recursive: true });
|
|
23595
|
+
writeFileSync4(path16.join(root, rel), content);
|
|
23068
23596
|
return { output: `Wrote ${rel} (${content.split("\n").length} lines).`, touched: rel };
|
|
23069
23597
|
}
|
|
23070
23598
|
case "delete_file": {
|
|
23071
23599
|
const rel = safePath(root, input2.path);
|
|
23072
|
-
if (!
|
|
23073
|
-
rmSync4(
|
|
23600
|
+
if (!existsSync14(path16.join(root, rel))) return { output: `File not found: ${rel}` };
|
|
23601
|
+
rmSync4(path16.join(root, rel));
|
|
23074
23602
|
return { output: `Deleted ${rel}.`, touched: rel };
|
|
23075
23603
|
}
|
|
23076
23604
|
case "run_command": {
|
|
@@ -23092,7 +23620,7 @@ ${(r.stdout + "\n" + r.stderr).trim().slice(-6e3)}`;
|
|
|
23092
23620
|
|
|
23093
23621
|
// src/api/explain.ts
|
|
23094
23622
|
import { createHash as createHash4 } from "crypto";
|
|
23095
|
-
import { existsSync as
|
|
23623
|
+
import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
|
|
23096
23624
|
|
|
23097
23625
|
// src/report.ts
|
|
23098
23626
|
import pc2 from "picocolors";
|
|
@@ -23281,7 +23809,7 @@ var CACHE_FILE = "cache/explanations.json";
|
|
|
23281
23809
|
var MAX_EXPLAIN = 25;
|
|
23282
23810
|
function loadCache(root) {
|
|
23283
23811
|
try {
|
|
23284
|
-
return JSON.parse(
|
|
23812
|
+
return JSON.parse(readFileSync14(auraPath(root, CACHE_FILE), "utf8"));
|
|
23285
23813
|
} catch {
|
|
23286
23814
|
return {};
|
|
23287
23815
|
}
|
|
@@ -23302,8 +23830,8 @@ function excerptFor(root, f, context = 4) {
|
|
|
23302
23830
|
}
|
|
23303
23831
|
function productContext(root) {
|
|
23304
23832
|
const file2 = auraPath(root, "brief.md");
|
|
23305
|
-
if (!
|
|
23306
|
-
return redact(
|
|
23833
|
+
if (!existsSync15(file2)) return void 0;
|
|
23834
|
+
return redact(readFileSync14(file2, "utf8")).slice(0, 1800);
|
|
23307
23835
|
}
|
|
23308
23836
|
async function addExplanations(root, scan, client) {
|
|
23309
23837
|
const cache = loadCache(root);
|
|
@@ -23347,19 +23875,22 @@ async function addExplanations(root, scan, client) {
|
|
|
23347
23875
|
|
|
23348
23876
|
// src/commands/simple.ts
|
|
23349
23877
|
import { execFileSync } from "child_process";
|
|
23350
|
-
import { existsSync as
|
|
23351
|
-
import
|
|
23878
|
+
import { existsSync as existsSync17, readdirSync as readdirSync5, readFileSync as readFileSync18 } from "fs";
|
|
23879
|
+
import path18 from "path";
|
|
23352
23880
|
import YAML2 from "yaml";
|
|
23353
23881
|
|
|
23354
23882
|
// src/commands/inventory.ts
|
|
23355
|
-
import { readFileSync as
|
|
23883
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
23356
23884
|
function accountState(root, services) {
|
|
23357
23885
|
const inv = loadInventory(root);
|
|
23358
|
-
return {
|
|
23886
|
+
return {
|
|
23887
|
+
inventoryComplete: services.every((s) => inv.services[s]?.owner) && Boolean(inv.githubOrg),
|
|
23888
|
+
secured: { ...inv.secured, github: Boolean(inv.githubSecurity || inv.secured?.github) }
|
|
23889
|
+
};
|
|
23359
23890
|
}
|
|
23360
23891
|
function loadInventory(root) {
|
|
23361
23892
|
try {
|
|
23362
|
-
return JSON.parse(
|
|
23893
|
+
return JSON.parse(readFileSync15(auraPath(root, "inventory.json"), "utf8"));
|
|
23363
23894
|
} catch {
|
|
23364
23895
|
return { version: 1, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), services: {} };
|
|
23365
23896
|
}
|
|
@@ -23446,10 +23977,10 @@ async function inventoryCommand(flags) {
|
|
|
23446
23977
|
}
|
|
23447
23978
|
|
|
23448
23979
|
// src/history.ts
|
|
23449
|
-
import { readFileSync as
|
|
23980
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
23450
23981
|
function loadHistory(root) {
|
|
23451
23982
|
try {
|
|
23452
|
-
const data = JSON.parse(
|
|
23983
|
+
const data = JSON.parse(readFileSync16(auraPath(root, "history.json"), "utf8"));
|
|
23453
23984
|
return Array.isArray(data) ? data : [];
|
|
23454
23985
|
} catch {
|
|
23455
23986
|
return [];
|
|
@@ -23497,10 +24028,10 @@ function banner(colors = pc3.isColorSupported) {
|
|
|
23497
24028
|
// src/api/connect.ts
|
|
23498
24029
|
import { spawn as spawn2 } from "child_process";
|
|
23499
24030
|
import { constants, generateKeyPairSync, privateDecrypt } from "crypto";
|
|
23500
|
-
import { existsSync as
|
|
23501
|
-
import
|
|
23502
|
-
import
|
|
23503
|
-
var pendingFile = () =>
|
|
24031
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync5, readFileSync as readFileSync17, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
24032
|
+
import os5 from "os";
|
|
24033
|
+
import path17 from "path";
|
|
24034
|
+
var pendingFile = () => path17.join(os5.homedir(), ".aura", "pending-connect.json");
|
|
23504
24035
|
async function postJson(apiUrl, route, body) {
|
|
23505
24036
|
let res;
|
|
23506
24037
|
try {
|
|
@@ -23519,14 +24050,14 @@ async function postJson(apiUrl, route, body) {
|
|
|
23519
24050
|
}
|
|
23520
24051
|
function loadPending(apiUrl, now) {
|
|
23521
24052
|
try {
|
|
23522
|
-
const p2 = JSON.parse(
|
|
24053
|
+
const p2 = JSON.parse(readFileSync17(pendingFile(), "utf8"));
|
|
23523
24054
|
if (p2.apiUrl === apiUrl && Date.parse(p2.expiresAt) - now > 6e4) return p2;
|
|
23524
24055
|
} catch {
|
|
23525
24056
|
}
|
|
23526
24057
|
return null;
|
|
23527
24058
|
}
|
|
23528
24059
|
function clearPendingConnect() {
|
|
23529
|
-
if (
|
|
24060
|
+
if (existsSync16(pendingFile())) rmSync5(pendingFile(), { force: true });
|
|
23530
24061
|
}
|
|
23531
24062
|
async function startOrResumeConnect(apiUrl, now = Date.now()) {
|
|
23532
24063
|
const existing = loadPending(apiUrl, now);
|
|
@@ -23538,7 +24069,7 @@ async function startOrResumeConnect(apiUrl, now = Date.now()) {
|
|
|
23538
24069
|
});
|
|
23539
24070
|
const res = ConnectStartResponse.parse(await postJson(apiUrl, "connect", { publicKey }));
|
|
23540
24071
|
const pending = { apiUrl, privateKey, ...res };
|
|
23541
|
-
mkdirSync5(
|
|
24072
|
+
mkdirSync5(path17.dirname(pendingFile()), { recursive: true });
|
|
23542
24073
|
writeFileSync5(pendingFile(), JSON.stringify(pending, null, 2) + "\n", { mode: 384 });
|
|
23543
24074
|
return { ...pending, resumed: false };
|
|
23544
24075
|
}
|
|
@@ -23631,7 +24162,7 @@ function stepRun(step, hasKey) {
|
|
|
23631
24162
|
}
|
|
23632
24163
|
function hint(step, hasKey) {
|
|
23633
24164
|
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";
|
|
24165
|
+
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
24166
|
return [what, step.impact > 0 ? `+${step.impact} points` : "", effortLabel(step.effort)].filter(Boolean).join(" \xB7 ");
|
|
23636
24167
|
}
|
|
23637
24168
|
async function pickAndRunSteps(steps, limit = 10) {
|
|
@@ -23657,7 +24188,10 @@ async function pickAndRunSteps(steps, limit = 10) {
|
|
|
23657
24188
|
console.log(`
|
|
23658
24189
|
${pc.magenta("\u2726")} ${pc.bold(step.title)}`);
|
|
23659
24190
|
const run2 = stepRun(step, hasKey);
|
|
23660
|
-
const code = run2.kind === "apply" ?
|
|
24191
|
+
const code = run2.kind === "apply" ? (
|
|
24192
|
+
// Settings changes always ask first, even when picked here; pull requests don't need to.
|
|
24193
|
+
await applyCommand(run2.actionId, { yes: !connectorForAction(run2.actionId), via: "checklist" })
|
|
24194
|
+
) : 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
24195
|
if (run2.kind === "guide" || run2.kind === "inventory") {
|
|
23662
24196
|
const root = await findRepoRoot();
|
|
23663
24197
|
await recordUsage(root, loadConfig(root), {
|
|
@@ -23681,9 +24215,9 @@ ${pc.bold(" Done")}`);
|
|
|
23681
24215
|
// src/commands/simple.ts
|
|
23682
24216
|
async function latestScan(root) {
|
|
23683
24217
|
const file2 = auraPath(root, "scan.json");
|
|
23684
|
-
if (
|
|
24218
|
+
if (existsSync17(file2)) {
|
|
23685
24219
|
try {
|
|
23686
|
-
const scan = JSON.parse(
|
|
24220
|
+
const scan = JSON.parse(readFileSync18(file2, "utf8"));
|
|
23687
24221
|
const head = execFileSync("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
23688
24222
|
const dirty = execFileSync("git", ["status", "--porcelain"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
23689
24223
|
if (scan.commit === head && !dirty && Date.now() - Date.parse(scan.scannedAt) < 24 * 36e5) return scan;
|
|
@@ -23798,9 +24332,9 @@ async function configCommand(key, value) {
|
|
|
23798
24332
|
return 0;
|
|
23799
24333
|
}
|
|
23800
24334
|
function loadGuides() {
|
|
23801
|
-
const dir =
|
|
23802
|
-
return
|
|
23803
|
-
const text =
|
|
24335
|
+
const dir = path18.join(templatesDir(), "guides");
|
|
24336
|
+
return readdirSync5(dir).filter((f) => f.endsWith(".md")).map((f) => {
|
|
24337
|
+
const text = readFileSync18(path18.join(dir, f), "utf8");
|
|
23804
24338
|
const fm = text.match(/^---\n([\s\S]*?)\n---\n?/);
|
|
23805
24339
|
const meta3 = fm ? YAML2.parse(fm[1]) : {};
|
|
23806
24340
|
return { id: f.replace(/\.md$/, ""), title: meta3.title ?? f, summary: meta3.summary ?? "", body: fm ? text.slice(fm[0].length) : text };
|
|
@@ -23868,7 +24402,7 @@ function allowedCommands(root, pm, typescript) {
|
|
|
23868
24402
|
const pkg = readJson(root, "package.json");
|
|
23869
24403
|
const run2 = pmCommands(pm).run;
|
|
23870
24404
|
const cmds = ["test", "lint", "typecheck", "build"].filter((s) => pkg?.scripts?.[s]).map((s) => `${run2} ${s}`);
|
|
23871
|
-
if (typescript && !pkg?.scripts?.typecheck &&
|
|
24405
|
+
if (typescript && !pkg?.scripts?.typecheck && existsSync18(path19.join(root, "node_modules/.bin/tsc"))) cmds.push(`${pmCommands(pm).exec} tsc --noEmit`);
|
|
23872
24406
|
return cmds;
|
|
23873
24407
|
}
|
|
23874
24408
|
function taskFor(root, scan, action2, findings) {
|
|
@@ -23919,8 +24453,8 @@ async function untrackEnvFiles(root, findings) {
|
|
|
23919
24453
|
const files = findings.filter((f) => f.checkId === "security.env-files").map((f) => f.locations[0].file);
|
|
23920
24454
|
for (const f of files) await git(root, "rm", "-q", "--cached", "--", f);
|
|
23921
24455
|
if (files.length) {
|
|
23922
|
-
const gi =
|
|
23923
|
-
const current2 =
|
|
24456
|
+
const gi = path19.join(root, ".gitignore");
|
|
24457
|
+
const current2 = existsSync18(gi) ? readFileSync19(gi, "utf8") : "";
|
|
23924
24458
|
if (!/^\.env\*?$/m.test(current2)) writeFileSync6(gi, `${current2.trimEnd()}
|
|
23925
24459
|
|
|
23926
24460
|
# Environment files hold secrets
|
|
@@ -24097,6 +24631,121 @@ ${pc.bold(action2.title)} ${pc.dim(`${findings.length} finding(s)`)}`);
|
|
|
24097
24631
|
}
|
|
24098
24632
|
}
|
|
24099
24633
|
|
|
24634
|
+
// src/connectors/engine.ts
|
|
24635
|
+
async function readSettings(connector, ctx) {
|
|
24636
|
+
const states = await Promise.all(connector.settings.map((s) => s.read(ctx).catch(() => ({ state: "off" }))));
|
|
24637
|
+
return Object.fromEntries(connector.settings.map((s, i) => [s.id, states[i]]));
|
|
24638
|
+
}
|
|
24639
|
+
async function enableSettings(connector, ctx, states) {
|
|
24640
|
+
const results = [];
|
|
24641
|
+
for (const setting of connector.settings) {
|
|
24642
|
+
const s = states[setting.id];
|
|
24643
|
+
if (s.state === "on") results.push({ id: setting.id, result: "already-on" });
|
|
24644
|
+
else if (s.state === "unavailable") results.push({ id: setting.id, result: "unavailable", why: s.why });
|
|
24645
|
+
else {
|
|
24646
|
+
const out = await setting.enable(ctx).catch(() => ({ ok: false }));
|
|
24647
|
+
results.push(
|
|
24648
|
+
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` }
|
|
24649
|
+
);
|
|
24650
|
+
}
|
|
24651
|
+
}
|
|
24652
|
+
return results;
|
|
24653
|
+
}
|
|
24654
|
+
var securityDone = (results) => results.every((r) => r.result !== "failed");
|
|
24655
|
+
async function secureCommand(connector, flags, report = {}, fake) {
|
|
24656
|
+
const root = await findRepoRoot();
|
|
24657
|
+
report.delivery = "settings";
|
|
24658
|
+
const connection = await connector.connect(root, fake);
|
|
24659
|
+
if (!connection.ok) {
|
|
24660
|
+
say.error(connection.problem);
|
|
24661
|
+
Object.assign(report, { outcome: "failed", errorType: connection.errorType });
|
|
24662
|
+
return 1;
|
|
24663
|
+
}
|
|
24664
|
+
const { ctx, target } = connection;
|
|
24665
|
+
const states = await readSettings(connector, ctx);
|
|
24666
|
+
saveSnapshot(root, connector.id, states);
|
|
24667
|
+
const toTurnOn = connector.settings.filter((s) => states[s.id].state === "off").map((s) => s.id);
|
|
24668
|
+
console.log(`
|
|
24669
|
+
${pc.magenta("\u2726")} ${pc.bold(`${connector.name} security for ${target}`)}`);
|
|
24670
|
+
for (const setting of connector.settings) {
|
|
24671
|
+
const s = states[setting.id];
|
|
24672
|
+
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}`);
|
|
24673
|
+
console.log(` ${setting.label}
|
|
24674
|
+
${line}`);
|
|
24675
|
+
}
|
|
24676
|
+
for (const line of await connector.explain?.(ctx, toTurnOn) ?? []) say.dim(` ${line}`);
|
|
24677
|
+
const label = (id) => connector.settings.find((s) => s.id === id).label;
|
|
24678
|
+
const impact = (id) => connector.settings.find((s) => s.id === id).impact;
|
|
24679
|
+
if (toTurnOn.length && !flags.dryRun) {
|
|
24680
|
+
console.log(`
|
|
24681
|
+
${pc.bold(" What changes for you")}`);
|
|
24682
|
+
for (const id of toTurnOn) console.log(` \u2022 ${impact(id)}`);
|
|
24683
|
+
}
|
|
24684
|
+
if (flags.dryRun) {
|
|
24685
|
+
say.dim(toTurnOn.length ? "\nDry run: nothing was changed." : "\nEverything Aura can turn on here is already on.");
|
|
24686
|
+
return 0;
|
|
24687
|
+
}
|
|
24688
|
+
let chosen = toTurnOn;
|
|
24689
|
+
if (toTurnOn.length && !flags.yes) {
|
|
24690
|
+
if (!interactive()) {
|
|
24691
|
+
say.info(`
|
|
24692
|
+
Nothing was changed. Show the user the changes above, and once they agree run: aura apply ${connector.actionId} --yes`);
|
|
24693
|
+
report.outcome = "shown";
|
|
24694
|
+
return 0;
|
|
24695
|
+
}
|
|
24696
|
+
chosen = unwrap(
|
|
24697
|
+
await p.multiselect({
|
|
24698
|
+
message: `Which should Aura turn on in ${connector.name}? (untick any you don\u2019t want)`,
|
|
24699
|
+
options: toTurnOn.map((id) => ({ value: id, label: label(id) })),
|
|
24700
|
+
initialValues: toTurnOn,
|
|
24701
|
+
required: false
|
|
24702
|
+
})
|
|
24703
|
+
);
|
|
24704
|
+
if (chosen.length === 0) {
|
|
24705
|
+
say.dim("Nothing was changed.");
|
|
24706
|
+
report.outcome = "cancelled";
|
|
24707
|
+
return 0;
|
|
24708
|
+
}
|
|
24709
|
+
}
|
|
24710
|
+
const skipped = toTurnOn.filter((id) => !chosen.includes(id));
|
|
24711
|
+
let results;
|
|
24712
|
+
if (toTurnOn.length === 0) {
|
|
24713
|
+
say.ok("Everything Aura can turn on here is already on.");
|
|
24714
|
+
results = await enableSettings(connector, ctx, states);
|
|
24715
|
+
report.outcome = "no_changes";
|
|
24716
|
+
} else {
|
|
24717
|
+
const attempt = Object.fromEntries(
|
|
24718
|
+
Object.entries(states).map(([id, s]) => [id, skipped.includes(id) ? { state: "unavailable", why: "you chose to leave it off" } : s])
|
|
24719
|
+
);
|
|
24720
|
+
results = await enableSettings(connector, ctx, attempt);
|
|
24721
|
+
console.log("");
|
|
24722
|
+
for (const r of results.filter((x) => chosen.includes(x.id))) {
|
|
24723
|
+
if (r.result === "turned-on") say.ok(`${label(r.id)}: on`);
|
|
24724
|
+
else if (r.result === "unavailable") say.dim(`\u2013 ${label(r.id)}: ${r.why}`);
|
|
24725
|
+
else say.warn(`${label(r.id)}: ${r.why}.`);
|
|
24726
|
+
}
|
|
24727
|
+
report.outcome = results.some((r) => r.result === "failed") ? "failed" : "settings_changed";
|
|
24728
|
+
}
|
|
24729
|
+
saveSnapshot(
|
|
24730
|
+
root,
|
|
24731
|
+
connector.id,
|
|
24732
|
+
Object.fromEntries(Object.entries(states).map(([id, s]) => [id, results.find((r) => r.id === id)?.result === "turned-on" ? { state: "on" } : s]))
|
|
24733
|
+
);
|
|
24734
|
+
const left = await connector.leftForYou(ctx, results);
|
|
24735
|
+
if (left.length) {
|
|
24736
|
+
console.log(`
|
|
24737
|
+
${pc.bold(" Left for you")}`);
|
|
24738
|
+
for (const line of left) console.log(` \u2022 ${line}`);
|
|
24739
|
+
}
|
|
24740
|
+
const inv = loadInventory(root);
|
|
24741
|
+
inv.secured = { ...inv.secured, [connector.id]: securityDone(results) };
|
|
24742
|
+
if (connector.id === "github") inv.githubSecurity = inv.secured.github;
|
|
24743
|
+
inv.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
24744
|
+
writeAuraFile(root, "inventory.json", `${JSON.stringify(inv, null, 2)}
|
|
24745
|
+
`);
|
|
24746
|
+
return results.some((r) => r.result === "failed") ? 1 : 0;
|
|
24747
|
+
}
|
|
24748
|
+
|
|
24100
24749
|
// src/commands/apply.ts
|
|
24101
24750
|
var LOCKFILES = ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lock", "bun.lockb"];
|
|
24102
24751
|
async function applyCommand(actionId, flags) {
|
|
@@ -24116,6 +24765,8 @@ async function applyAction(actionId, flags, report) {
|
|
|
24116
24765
|
}
|
|
24117
24766
|
const scan = await latestScan(root);
|
|
24118
24767
|
const { delivery, fellBack } = effectiveDelivery(action2, scan);
|
|
24768
|
+
const connector = connectorForAction(actionId);
|
|
24769
|
+
if (connector) return secureCommand(connector, flags, report);
|
|
24119
24770
|
if (delivery === "guide") return guideCommand(actionId, {});
|
|
24120
24771
|
if (delivery === "agent-fix") {
|
|
24121
24772
|
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 +24800,7 @@ async function applyAction(actionId, flags, report) {
|
|
|
24149
24800
|
console.log(`
|
|
24150
24801
|
${pc.bold(action2.title)}`);
|
|
24151
24802
|
for (const c of plan.changes)
|
|
24152
|
-
console.log(` ${c.content === null ? pc.red("delete") :
|
|
24803
|
+
console.log(` ${c.content === null ? pc.red("delete") : existsSync19(path20.join(root, c.path)) ? pc.yellow("update") : pc.green("add ")} ${c.path}`);
|
|
24153
24804
|
const pm = pmCommands(scan.stack.packageManager);
|
|
24154
24805
|
if (plan.devDeps?.length) console.log(` ${pc.cyan("install")} ${plan.devDeps.join(", ")} ${pc.dim(`(${pm.addDev})`)}`);
|
|
24155
24806
|
for (const cmd of plan.commands ?? []) console.log(` ${pc.cyan("run ")} ${cmd.join(" ")}`);
|
|
@@ -24181,8 +24832,8 @@ ${pc.bold(action2.title)}`);
|
|
|
24181
24832
|
report.outcome = "cancelled";
|
|
24182
24833
|
return 0;
|
|
24183
24834
|
}
|
|
24184
|
-
const tsc =
|
|
24185
|
-
const checkTypes = plan.verifyTypes && scan.stack.typescript &&
|
|
24835
|
+
const tsc = path20.join(root, "node_modules", ".bin", process.platform === "win32" ? "tsc.cmd" : "tsc");
|
|
24836
|
+
const checkTypes = plan.verifyTypes && scan.stack.typescript && existsSync19(tsc);
|
|
24186
24837
|
const typeErrors = async () => {
|
|
24187
24838
|
const r = await run(tsc, ["--noEmit", "--pretty", "false"], { cwd: root, timeoutMs: 3e5 });
|
|
24188
24839
|
return (r.stdout.match(/error TS\d+/g) ?? []).length;
|
|
@@ -24291,17 +24942,17 @@ async function undoPlan(root, plan) {
|
|
|
24291
24942
|
for (const file2 of touched) {
|
|
24292
24943
|
const inHead = (await git(root, "cat-file", "-e", `HEAD:${file2}`)).code === 0;
|
|
24293
24944
|
if (inHead) await git(root, "checkout", "HEAD", "--", file2);
|
|
24294
|
-
else if (plan.changes.some((c) => c.path === file2)) rmSync6(
|
|
24945
|
+
else if (plan.changes.some((c) => c.path === file2)) rmSync6(path20.join(root, file2), { force: true });
|
|
24295
24946
|
}
|
|
24296
24947
|
}
|
|
24297
24948
|
|
|
24298
24949
|
// src/commands/init.ts
|
|
24299
|
-
import { existsSync as
|
|
24950
|
+
import { existsSync as existsSync20, readFileSync as readFileSync21 } from "fs";
|
|
24300
24951
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
24301
|
-
import
|
|
24952
|
+
import path24 from "path";
|
|
24302
24953
|
|
|
24303
24954
|
// src/brief.ts
|
|
24304
|
-
import
|
|
24955
|
+
import path21 from "path";
|
|
24305
24956
|
function repoSummary(ctx) {
|
|
24306
24957
|
const routes = ctx.files.filter((f) => /^(?:src\/)?app\/(?:.+\/)?(?:page|route)\.[cm]?[jt]sx?$/.test(f) || /^(?:src\/)?pages\/.+\.[cm]?[jt]sx?$/.test(f)).map(
|
|
24307
24958
|
(f) => "/" + f.replace(/^(?:src\/)?(?:app|pages)\//, "").replace(/(?:^|\/)(?:page|route|index)\.[cm]?[jt]sx?$|\.[cm]?[jt]sx?$/, "").replace(/\([^)]+\)\//g, "")
|
|
@@ -24317,7 +24968,7 @@ function repoSummary(ctx) {
|
|
|
24317
24968
|
const readme2 = ctx.files.find((f) => /^readme\.mdx?$/i.test(f));
|
|
24318
24969
|
const readmeText = readme2 ? ctx.read(readme2) ?? "" : "";
|
|
24319
24970
|
return {
|
|
24320
|
-
name: ctx.pkg?.name ??
|
|
24971
|
+
name: ctx.pkg?.name ?? path21.basename(ctx.root),
|
|
24321
24972
|
packageJson: ctx.pkg ? {
|
|
24322
24973
|
name: ctx.pkg.name,
|
|
24323
24974
|
description: ctx.pkg.description,
|
|
@@ -24410,10 +25061,10 @@ function installFallback(result, platform = process.platform) {
|
|
|
24410
25061
|
|
|
24411
25062
|
// src/core/claude-plugin.ts
|
|
24412
25063
|
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
24413
|
-
import
|
|
24414
|
-
import
|
|
25064
|
+
import os6 from "os";
|
|
25065
|
+
import path22 from "path";
|
|
24415
25066
|
var CLAUDE_PLUGIN_ID = "aura@aura";
|
|
24416
|
-
var marketplaceDir = () =>
|
|
25067
|
+
var marketplaceDir = () => path22.join(os6.homedir(), ".aura", "claude-marketplace");
|
|
24417
25068
|
var MARKETPLACE = {
|
|
24418
25069
|
name: "aura",
|
|
24419
25070
|
description: "Aura for Claude Code",
|
|
@@ -24428,8 +25079,8 @@ var MARKETPLACE = {
|
|
|
24428
25079
|
};
|
|
24429
25080
|
function writeMarketplace() {
|
|
24430
25081
|
const dir = marketplaceDir();
|
|
24431
|
-
mkdirSync6(
|
|
24432
|
-
writeFileSync7(
|
|
25082
|
+
mkdirSync6(path22.join(dir, ".claude-plugin"), { recursive: true });
|
|
25083
|
+
writeFileSync7(path22.join(dir, ".claude-plugin", "marketplace.json"), `${JSON.stringify(MARKETPLACE, null, 2)}
|
|
24433
25084
|
`);
|
|
24434
25085
|
return dir;
|
|
24435
25086
|
}
|
|
@@ -24532,11 +25183,30 @@ function describeInferred(inferred) {
|
|
|
24532
25183
|
|
|
24533
25184
|
// src/commands/scan.ts
|
|
24534
25185
|
import { mkdtempSync as mkdtempSync3, rmSync as rmSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
24535
|
-
import
|
|
24536
|
-
import
|
|
25186
|
+
import os7 from "os";
|
|
25187
|
+
import path23 from "path";
|
|
25188
|
+
|
|
25189
|
+
// src/connectors/refresh.ts
|
|
25190
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
25191
|
+
async function refreshToolSettings(root) {
|
|
25192
|
+
if (process.env.AURA_NO_CONNECTORS) return;
|
|
25193
|
+
let previous = null;
|
|
25194
|
+
try {
|
|
25195
|
+
previous = JSON.parse(readFileSync20(auraPath(root, "scan.json"), "utf8"));
|
|
25196
|
+
} catch {
|
|
25197
|
+
}
|
|
25198
|
+
const snapshots = loadSnapshots(root);
|
|
25199
|
+
for (const connector of CONNECTORS) {
|
|
25200
|
+
if (!(previous ? connector.detect(previous) : connector.id === "github") || !isStale(snapshots[connector.id])) continue;
|
|
25201
|
+
const connection = await connector.connect(root).catch(() => null);
|
|
25202
|
+
if (connection?.ok) saveSnapshot(root, connector.id, await readSettings(connector, connection.ctx));
|
|
25203
|
+
}
|
|
25204
|
+
}
|
|
25205
|
+
|
|
25206
|
+
// src/commands/scan.ts
|
|
24537
25207
|
async function scanRef(root, ref, opts) {
|
|
24538
|
-
const dir = mkdtempSync3(
|
|
24539
|
-
const wt =
|
|
25208
|
+
const dir = mkdtempSync3(path23.join(os7.tmpdir(), "aura-base-"));
|
|
25209
|
+
const wt = path23.join(dir, "tree");
|
|
24540
25210
|
const add = await run("git", ["worktree", "add", "--detach", wt, ref], { cwd: root });
|
|
24541
25211
|
if (add.code !== 0) {
|
|
24542
25212
|
rmSync7(dir, { recursive: true, force: true });
|
|
@@ -24557,6 +25227,7 @@ async function scanCommand(flags) {
|
|
|
24557
25227
|
const opts = { fast: Boolean(flags.fast || flags.base), noDownload: flags.download === false };
|
|
24558
25228
|
const s = spin(!quiet && !flags.ci);
|
|
24559
25229
|
s.start("Scanning");
|
|
25230
|
+
if (!flags.ci) await refreshToolSettings(root);
|
|
24560
25231
|
const scan = await runScan(root, opts, (m) => s.message(`Checking: ${m}`));
|
|
24561
25232
|
s.stop(`Scanned ${scan.checks.filter((c) => c.status !== "not-applicable").length} checks`);
|
|
24562
25233
|
const client = AuraClient.from(config2);
|
|
@@ -24625,7 +25296,7 @@ async function scanCommand(flags) {
|
|
|
24625
25296
|
// src/commands/init.ts
|
|
24626
25297
|
var AGENT_IDS = ["claude", "codex", "cursor", "pi"];
|
|
24627
25298
|
function detectAgents(root) {
|
|
24628
|
-
return detectAgentsFrom((rel) =>
|
|
25299
|
+
return detectAgentsFrom((rel) => existsSync20(path24.join(root, rel)));
|
|
24629
25300
|
}
|
|
24630
25301
|
function openEditor(file2) {
|
|
24631
25302
|
const fallback = process.platform === "win32" ? "notepad" : spawnSync3("sh", ["-c", "command -v nano"]).status === 0 ? "nano" : "vi";
|
|
@@ -24740,13 +25411,13 @@ async function initCommand(flags) {
|
|
|
24740
25411
|
let brief2 = null;
|
|
24741
25412
|
let keptBrief = false;
|
|
24742
25413
|
if (flags.briefFile) {
|
|
24743
|
-
brief2 =
|
|
24744
|
-
} else if (!flags.answers &&
|
|
25414
|
+
brief2 = readFileSync21(flags.briefFile, "utf8");
|
|
25415
|
+
} else if (!flags.answers && existsSync20(briefPath) && !(tty && unwrap(await p.confirm({ message: "A product brief already exists. Draft a new one?", initialValue: false })))) {
|
|
24745
25416
|
keptBrief = true;
|
|
24746
25417
|
} else {
|
|
24747
25418
|
let answers = null;
|
|
24748
25419
|
if (flags.answers) {
|
|
24749
|
-
answers = OnboardingAnswersSchema.parse(JSON.parse(
|
|
25420
|
+
answers = OnboardingAnswersSchema.parse(JSON.parse(readFileSync21(flags.answers, "utf8")));
|
|
24750
25421
|
} else if (tty) {
|
|
24751
25422
|
const inferred = inferAnswers(ctx);
|
|
24752
25423
|
p.note(describeInferred(inferred).join("\n"), "Here\u2019s what Aura found about your app");
|
|
@@ -24878,7 +25549,7 @@ ${pc.magenta("\u2726")} ${pc.bold("Give the user the aura command")}`);
|
|
|
24878
25549
|
}
|
|
24879
25550
|
|
|
24880
25551
|
// src/commands/summary.ts
|
|
24881
|
-
import
|
|
25552
|
+
import path25 from "path";
|
|
24882
25553
|
async function activity(root, since) {
|
|
24883
25554
|
const log = await git(root, "log", `--since=${since.toISOString()}`, "--no-merges", "--format=%H%x09%an%x09%s");
|
|
24884
25555
|
const commits = log.stdout.split("\n").filter(Boolean).slice(0, 300).map((l) => {
|
|
@@ -24935,7 +25606,7 @@ async function summaryCommand(flags) {
|
|
|
24935
25606
|
const beforeIds = new Set(before ? allFindings(before).map((f) => f.id) : []);
|
|
24936
25607
|
const nowIds = new Set(allFindings(now).map((f) => f.id));
|
|
24937
25608
|
const req = {
|
|
24938
|
-
repo:
|
|
25609
|
+
repo: path25.basename(root),
|
|
24939
25610
|
since: since.toISOString(),
|
|
24940
25611
|
until: until.toISOString(),
|
|
24941
25612
|
product: productContext(root),
|
|
@@ -24983,11 +25654,11 @@ ${body}`);
|
|
|
24983
25654
|
import pc4 from "picocolors";
|
|
24984
25655
|
|
|
24985
25656
|
// src/core/update-check.ts
|
|
24986
|
-
import { mkdirSync as mkdirSync7, readFileSync as
|
|
24987
|
-
import
|
|
24988
|
-
import
|
|
25657
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync22, writeFileSync as writeFileSync9 } from "fs";
|
|
25658
|
+
import os8 from "os";
|
|
25659
|
+
import path26 from "path";
|
|
24989
25660
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
24990
|
-
var cacheFile = () =>
|
|
25661
|
+
var cacheFile = () => path26.join(os8.homedir(), ".aura", "update-check.json");
|
|
24991
25662
|
function isNewer(a, b) {
|
|
24992
25663
|
if (!/^\d+\.\d+\.\d+$/.test(a) || !/^\d+\.\d+\.\d+$/.test(b)) return false;
|
|
24993
25664
|
const [x, y] = [a, b].map((v) => v.split(".").map(Number));
|
|
@@ -25007,13 +25678,13 @@ async function newerVersion(opts = {}) {
|
|
|
25007
25678
|
const now = opts.now ?? Date.now();
|
|
25008
25679
|
let latest = null;
|
|
25009
25680
|
try {
|
|
25010
|
-
const cached2 = JSON.parse(
|
|
25681
|
+
const cached2 = JSON.parse(readFileSync22(cacheFile(), "utf8"));
|
|
25011
25682
|
if (now - cached2.checkedAt < DAY_MS) latest = cached2.latest;
|
|
25012
25683
|
else throw new Error("stale");
|
|
25013
25684
|
} catch {
|
|
25014
25685
|
latest = await (opts.fetchLatest ?? fetchLatest)();
|
|
25015
25686
|
try {
|
|
25016
|
-
mkdirSync7(
|
|
25687
|
+
mkdirSync7(path26.dirname(cacheFile()), { recursive: true });
|
|
25017
25688
|
writeFileSync9(cacheFile(), JSON.stringify({ checkedAt: now, latest }));
|
|
25018
25689
|
} catch {
|
|
25019
25690
|
}
|