getaura 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,7 +25,7 @@ To install the `aura` command yourself: `npm install -g getaura`. Without it, us
25
25
  ## What you get
26
26
 
27
27
  - **An Aura score** from deterministic checks across security, agent setup, foundations and structure, explained in plain language.
28
- - **Next steps** in priority order: `aura next`.
28
+ - **Next steps** in priority order. After a scan (or with `aura next`), tick the ones you want in the terminal and Aura does them.
29
29
  - **Fixes as pull requests you approve**: `aura apply foundation` puts your brief, agent rules, skills, CI and env example in place in one PR, and `aura fix <finding>` has Aura's agent make code changes on a branch.
30
30
  - **An agent that keeps it that way**: the rules and skills Aura installs teach your coding agent your standards, and GitHub Actions re-scans every push.
31
31
 
package/dist/index.js CHANGED
@@ -990,10 +990,10 @@ function mergeDefs(...defs) {
990
990
  function cloneDef(schema) {
991
991
  return mergeDefs(schema._zod.def);
992
992
  }
993
- function getElementAtPath(obj, path21) {
994
- if (!path21)
993
+ function getElementAtPath(obj, path22) {
994
+ if (!path22)
995
995
  return obj;
996
- return path21.reduce((acc, key) => acc?.[key], obj);
996
+ return path22.reduce((acc, key) => acc?.[key], obj);
997
997
  }
998
998
  function promiseAllObject(promisesObj) {
999
999
  const keys = Object.keys(promisesObj);
@@ -1333,11 +1333,11 @@ function explicitlyAborted(x, startIndex = 0) {
1333
1333
  }
1334
1334
  return false;
1335
1335
  }
1336
- function prefixIssues(path21, issues) {
1336
+ function prefixIssues(path22, issues) {
1337
1337
  return issues.map((iss) => {
1338
1338
  var _a3;
1339
1339
  (_a3 = iss).path ?? (_a3.path = []);
1340
- iss.path.unshift(path21);
1340
+ iss.path.unshift(path22);
1341
1341
  return iss;
1342
1342
  });
1343
1343
  }
@@ -1787,16 +1787,16 @@ function flattenError(error62, mapper = (issue2) => issue2.message) {
1787
1787
  }
1788
1788
  function formatError(error62, mapper = (issue2) => issue2.message) {
1789
1789
  const fieldErrors = { _errors: [] };
1790
- const processError = (error63, path21 = []) => {
1790
+ const processError = (error63, path22 = []) => {
1791
1791
  for (const issue2 of error63.issues) {
1792
1792
  if (issue2.code === "invalid_union" && issue2.errors.length) {
1793
- issue2.errors.map((issues) => processError({ issues }, [...path21, ...issue2.path]));
1793
+ issue2.errors.map((issues) => processError({ issues }, [...path22, ...issue2.path]));
1794
1794
  } else if (issue2.code === "invalid_key") {
1795
- processError({ issues: issue2.issues }, [...path21, ...issue2.path]);
1795
+ processError({ issues: issue2.issues }, [...path22, ...issue2.path]);
1796
1796
  } else if (issue2.code === "invalid_element") {
1797
- processError({ issues: issue2.issues }, [...path21, ...issue2.path]);
1797
+ processError({ issues: issue2.issues }, [...path22, ...issue2.path]);
1798
1798
  } else {
1799
- const fullpath = [...path21, ...issue2.path];
1799
+ const fullpath = [...path22, ...issue2.path];
1800
1800
  if (fullpath.length === 0) {
1801
1801
  fieldErrors._errors.push(mapper(issue2));
1802
1802
  } else {
@@ -1835,17 +1835,17 @@ function formatError(error62, mapper = (issue2) => issue2.message) {
1835
1835
  }
1836
1836
  function treeifyError(error62, mapper = (issue2) => issue2.message) {
1837
1837
  const result = { errors: [] };
1838
- const processError = (error63, path21 = []) => {
1838
+ const processError = (error63, path22 = []) => {
1839
1839
  var _a3;
1840
1840
  for (const issue2 of error63.issues) {
1841
1841
  if (issue2.code === "invalid_union" && issue2.errors.length) {
1842
- issue2.errors.map((issues) => processError({ issues }, [...path21, ...issue2.path]));
1842
+ issue2.errors.map((issues) => processError({ issues }, [...path22, ...issue2.path]));
1843
1843
  } else if (issue2.code === "invalid_key") {
1844
- processError({ issues: issue2.issues }, [...path21, ...issue2.path]);
1844
+ processError({ issues: issue2.issues }, [...path22, ...issue2.path]);
1845
1845
  } else if (issue2.code === "invalid_element") {
1846
- processError({ issues: issue2.issues }, [...path21, ...issue2.path]);
1846
+ processError({ issues: issue2.issues }, [...path22, ...issue2.path]);
1847
1847
  } else {
1848
- const fullpath = [...path21, ...issue2.path];
1848
+ const fullpath = [...path22, ...issue2.path];
1849
1849
  if (fullpath.length === 0) {
1850
1850
  result.errors.push(mapper(issue2));
1851
1851
  continue;
@@ -1884,8 +1884,8 @@ function treeifyError(error62, mapper = (issue2) => issue2.message) {
1884
1884
  }
1885
1885
  function toDotPath(_path) {
1886
1886
  const segs = [];
1887
- const path21 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
1888
- for (const seg of path21) {
1887
+ const path22 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
1888
+ for (const seg of path22) {
1889
1889
  if (typeof seg === "number")
1890
1890
  segs.push(`[${seg}]`);
1891
1891
  else if (typeof seg === "symbol")
@@ -18987,13 +18987,13 @@ function resolveRef(ref, ctx) {
18987
18987
  if (!ref.startsWith("#")) {
18988
18988
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
18989
18989
  }
18990
- const path21 = ref.slice(1).split("/").filter(Boolean);
18991
- if (path21.length === 0) {
18990
+ const path22 = ref.slice(1).split("/").filter(Boolean);
18991
+ if (path22.length === 0) {
18992
18992
  return ctx.rootSchema;
18993
18993
  }
18994
18994
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
18995
- if (path21[0] === defsKey) {
18996
- const key = path21[1] === void 0 ? void 0 : decodeJSONPointerSegment(path21[1]);
18995
+ if (path22[0] === defsKey) {
18996
+ const key = path22[1] === void 0 ? void 0 : decodeJSONPointerSegment(path22[1]);
18997
18997
  if (!key || !ctx.defs[key]) {
18998
18998
  throw new Error(`Reference not found: ${ref}`);
18999
18999
  }
@@ -20429,7 +20429,7 @@ function pmCommands(pm) {
20429
20429
  // package.json
20430
20430
  var package_default = {
20431
20431
  name: "getaura",
20432
- version: "0.1.4",
20432
+ version: "0.1.6",
20433
20433
  description: "Aura helps you and your coding agent follow best practices while you vibe code.",
20434
20434
  type: "module",
20435
20435
  bin: {
@@ -21545,7 +21545,7 @@ var AuraClient = class _AuraClient {
21545
21545
  get hasKey() {
21546
21546
  return Boolean(this.key);
21547
21547
  }
21548
- async request(method, path21, body, timeoutMs = 6e4) {
21548
+ async request(method, path22, body, timeoutMs = 6e4) {
21549
21549
  if (!this.key) throw new AuraApiError("no_key", "No pilot key");
21550
21550
  let payload;
21551
21551
  if (body !== void 0) {
@@ -21555,7 +21555,7 @@ var AuraClient = class _AuraClient {
21555
21555
  }
21556
21556
  let res;
21557
21557
  try {
21558
- res = await fetch(`${this.baseUrl}/v1/${path21}`, {
21558
+ res = await fetch(`${this.baseUrl}/v1/${path22}`, {
21559
21559
  method,
21560
21560
  headers: { authorization: `Bearer ${this.key}`, "content-type": "application/json", "user-agent": `getaura/${AURA_VERSION}` },
21561
21561
  body: payload,
@@ -21857,8 +21857,8 @@ var typecheck = {
21857
21857
  async run(ctx) {
21858
21858
  if (ctx.options.fast) return { status: "unknown", summary: "Skipped in fast mode." };
21859
21859
  const { existsSync: existsSync17 } = await import("fs");
21860
- const path21 = await import("path");
21861
- const tsc = path21.join(ctx.root, "node_modules", ".bin", process.platform === "win32" ? "tsc.cmd" : "tsc");
21860
+ const path22 = await import("path");
21861
+ const tsc = path22.join(ctx.root, "node_modules", ".bin", process.platform === "win32" ? "tsc.cmd" : "tsc");
21862
21862
  if (!existsSync17(tsc)) return { status: "unknown", summary: "Dependencies not installed, so the type check was skipped." };
21863
21863
  const r = await run(tsc, ["--noEmit", "--pretty", "false"], { cwd: ctx.root, timeoutMs: 18e4 });
21864
21864
  if (r.timedOut) return { status: "unknown", summary: "Type check timed out." };
@@ -23394,6 +23394,53 @@ function explainSignIn(outcome) {
23394
23394
  }
23395
23395
  }
23396
23396
 
23397
+ // src/commands/pick-steps.ts
23398
+ function stepRun(step, hasKey) {
23399
+ const [, sub, arg = step.actionId] = step.command.split(" ");
23400
+ if (sub === "apply") return { kind: "apply", actionId: arg };
23401
+ if (sub === "fix") return { kind: "fix", actionId: arg, withOwnAgent: !hasKey };
23402
+ if (sub === "guide") return { kind: "guide", topic: arg };
23403
+ return { kind: "inventory" };
23404
+ }
23405
+ function hint(step, hasKey) {
23406
+ const run2 = stepRun(step, hasKey);
23407
+ 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";
23408
+ return [what, step.impact > 0 ? `+${step.impact} points` : "", effortLabel(step.effort)].filter(Boolean).join(" \xB7 ");
23409
+ }
23410
+ async function pickAndRunSteps(steps, limit = 10) {
23411
+ if (steps.length === 0) {
23412
+ console.log(pc.green("\n Nothing to do right now."));
23413
+ return 0;
23414
+ }
23415
+ const hasKey = Boolean(getApiKey());
23416
+ console.log("");
23417
+ const chosen = unwrap(
23418
+ await p.multiselect({
23419
+ message: "Next steps: pick what Aura should do now (space to select, enter to confirm, enter with none to skip)",
23420
+ options: steps.slice(0, limit).map((s) => ({ value: s.actionId, label: s.title, hint: hint(s, hasKey) })),
23421
+ required: false
23422
+ })
23423
+ );
23424
+ if (chosen.length === 0) {
23425
+ say.dim("Nothing selected. Run `aura next` any time to pick again.");
23426
+ return 0;
23427
+ }
23428
+ const results = [];
23429
+ for (const step of steps.filter((s) => chosen.includes(s.actionId))) {
23430
+ console.log(`
23431
+ ${pc.magenta("\u2726")} ${pc.bold(step.title)}`);
23432
+ const run2 = stepRun(step, hasKey);
23433
+ const code = run2.kind === "apply" ? await applyCommand(run2.actionId, { yes: true }) : run2.kind === "fix" ? await fixCommand(run2.actionId, run2.withOwnAgent ? { agent: true } : { yes: true }) : run2.kind === "guide" ? await guideCommand(run2.topic, {}) : await inventoryCommand({});
23434
+ results.push({ title: step.title, ok: code === 0 });
23435
+ }
23436
+ if (results.length > 1) {
23437
+ console.log(`
23438
+ ${pc.bold(" Done")}`);
23439
+ for (const r of results) console.log(` ${r.ok ? pc.green("\u2714") : pc.red("\u2716")} ${r.title}`);
23440
+ }
23441
+ return results.every((r) => r.ok) ? 0 : 1;
23442
+ }
23443
+
23397
23444
  // src/commands/simple.ts
23398
23445
  async function latestScan(root) {
23399
23446
  const file2 = auraPath(root, "scan.json");
@@ -23412,8 +23459,10 @@ async function nextCommand(flags) {
23412
23459
  const root = await findRepoRoot();
23413
23460
  const scan = await latestScan(root);
23414
23461
  const steps = nextSteps(scan, { includeFoundationBundle: true, accounts: accountState(root, scan.stack.services) });
23462
+ const limit = flags.limit ? Number(flags.limit) : 10;
23415
23463
  if (flags.json) printJson({ score: scan.score, nextSteps: steps });
23416
- else console.log(renderNextSteps(steps, flags.limit ? Number(flags.limit) : 10));
23464
+ else if (interactive()) return pickAndRunSteps(steps, limit);
23465
+ else console.log(renderNextSteps(steps, limit));
23417
23466
  return 0;
23418
23467
  }
23419
23468
  async function historyCommand(flags) {
@@ -23970,7 +24019,7 @@ async function undoPlan(root, plan) {
23970
24019
 
23971
24020
  // src/commands/init.ts
23972
24021
  import { existsSync as existsSync16, readFileSync as readFileSync15 } from "fs";
23973
- import { spawnSync as spawnSync2 } from "child_process";
24022
+ import { spawnSync as spawnSync3 } from "child_process";
23974
24023
 
23975
24024
  // src/brief.ts
23976
24025
  import path18 from "path";
@@ -24061,6 +24110,7 @@ async function draftBrief(client, answers, summary) {
24061
24110
  }
24062
24111
 
24063
24112
  // src/core/aura-command.ts
24113
+ import { spawnSync as spawnSync2 } from "child_process";
24064
24114
  function hasAuraCommand() {
24065
24115
  return commandExists("aura");
24066
24116
  }
@@ -24070,6 +24120,10 @@ async function installAuraCommand() {
24070
24120
  if (r.code !== 0) return { ok: false, needsAdmin: /EACCES|EPERM|permission denied/i.test(r.stderr + r.stdout) };
24071
24121
  return { ok: true, onPath: await hasAuraCommand() };
24072
24122
  }
24123
+ async function installWithSudo() {
24124
+ const r = spawnSync2("sudo", ["npm", "install", "-g", `getaura@${AURA_VERSION}`], { stdio: "inherit" });
24125
+ return r.status === 0 && await hasAuraCommand();
24126
+ }
24073
24127
  function installFallback(result, platform = process.platform) {
24074
24128
  const admin = result.needsAdmin && platform !== "win32" ? `Run \`sudo ${INSTALL_AURA}\` and enter your computer password, or use` : "Use";
24075
24129
  return `${admin} \`npx getaura <command>\` in place of \`aura <command>\` (for example \`npx getaura scan\`).`;
@@ -24140,13 +24194,15 @@ async function scanCommand(flags) {
24140
24194
  return exitCode;
24141
24195
  }
24142
24196
  console.log(renderTerminal(scan, { previous: flags.ci ? base?.score ?? null : previous }));
24143
- if (!flags.ci) console.log(renderNextSteps(steps, 3));
24197
+ const pick2 = !flags.ci && interactive();
24198
+ if (!flags.ci && !pick2) console.log(renderNextSteps(steps, 3));
24144
24199
  say.dim(
24145
24200
  `
24146
24201
  Full report: .aura/report.md${client.hasKey || flags.explain === false ? "" : " \xB7 Add a pilot key (aura login) for plain-language explanations."}`
24147
24202
  );
24148
24203
  if (base) say.info(`
24149
24204
  Compared with ${flags.base}: ${base.score} \u2192 ${scan.score}${exitCode ? ` (dropped more than ${maxDrop})` : ""}`);
24205
+ if (pick2) await pickAndRunSteps(steps);
24150
24206
  return exitCode;
24151
24207
  }
24152
24208
 
@@ -24162,7 +24218,7 @@ function detectAgents(root) {
24162
24218
  }
24163
24219
  function openEditor(file2) {
24164
24220
  const editor = process.env.VISUAL || process.env.EDITOR || (process.platform === "win32" ? "notepad" : "nano");
24165
- const r = spawnSync2(editor, [file2], { stdio: "inherit", shell: process.platform === "win32" });
24221
+ const r = spawnSync3(editor, [file2], { stdio: "inherit", shell: process.platform === "win32" });
24166
24222
  return r.status === 0;
24167
24223
  }
24168
24224
  var EXAMPLE_ANSWERS = '{"whatItDoes": "\u2026", "audience": "\u2026", "chargesMoney": false, "dataHandled": ["personal"], "userRegions": ["Canada"]}';
@@ -24354,7 +24410,10 @@ async function initCommand(flags) {
24354
24410
  s.stop(installed.onPath ? "Installed. Run `aura scan` from any folder." : "Installed. Open a new terminal window, then run `aura scan`.");
24355
24411
  else {
24356
24412
  s.stop("Couldn\u2019t install the aura command");
24357
- say.warn(installFallback(installed));
24413
+ const useSudo = installed.needsAdmin && process.platform !== "win32" && unwrap(await p.confirm({ message: "Your computer needs your password to install it. Enter your password now?", initialValue: true }));
24414
+ if (!useSudo) say.warn(installFallback(installed));
24415
+ else if (await installWithSudo()) say.ok("Installed. Run `aura scan` from any folder.");
24416
+ else say.warn(installFallback({ needsAdmin: false }));
24358
24417
  }
24359
24418
  } else say.dim(`Use \`npx getaura <command>\` instead, or install it later with \`${INSTALL_AURA}\`.`);
24360
24419
  } else {
@@ -24474,10 +24533,62 @@ ${body}`);
24474
24533
  return 0;
24475
24534
  }
24476
24535
 
24536
+ // src/index.ts
24537
+ import pc4 from "picocolors";
24538
+
24539
+ // src/core/update-check.ts
24540
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync16, writeFileSync as writeFileSync8 } from "fs";
24541
+ import os6 from "os";
24542
+ import path21 from "path";
24543
+ var DAY_MS = 24 * 60 * 60 * 1e3;
24544
+ var cacheFile = () => path21.join(os6.homedir(), ".aura", "update-check.json");
24545
+ function isNewer(a, b) {
24546
+ if (!/^\d+\.\d+\.\d+$/.test(a) || !/^\d+\.\d+\.\d+$/.test(b)) return false;
24547
+ const [x, y] = [a, b].map((v) => v.split(".").map(Number));
24548
+ for (let i = 0; i < 3; i++) if (x[i] !== y[i]) return x[i] > y[i];
24549
+ return false;
24550
+ }
24551
+ async function fetchLatest() {
24552
+ try {
24553
+ const res = await fetch("https://registry.npmjs.org/getaura/latest", { signal: AbortSignal.timeout(1500) });
24554
+ const body = await res.json();
24555
+ return res.ok && typeof body.version === "string" ? body.version : null;
24556
+ } catch {
24557
+ return null;
24558
+ }
24559
+ }
24560
+ async function newerVersion(opts = {}) {
24561
+ const now = opts.now ?? Date.now();
24562
+ let latest = null;
24563
+ try {
24564
+ const cached2 = JSON.parse(readFileSync16(cacheFile(), "utf8"));
24565
+ if (now - cached2.checkedAt < DAY_MS) latest = cached2.latest;
24566
+ else throw new Error("stale");
24567
+ } catch {
24568
+ latest = await (opts.fetchLatest ?? fetchLatest)();
24569
+ try {
24570
+ mkdirSync6(path21.dirname(cacheFile()), { recursive: true });
24571
+ writeFileSync8(cacheFile(), JSON.stringify({ checkedAt: now, latest }));
24572
+ } catch {
24573
+ }
24574
+ }
24575
+ return latest && isNewer(latest, opts.current ?? AURA_VERSION) ? latest : null;
24576
+ }
24577
+ function startUpdateCheck() {
24578
+ if (!process.stdout.isTTY || process.env.CI || process.env.AURA_NO_UPDATE_CHECK) return Promise.resolve(null);
24579
+ return newerVersion().catch(() => null);
24580
+ }
24581
+ function updateNotice(latest, argv1 = process.argv[1] ?? "") {
24582
+ const viaNpx = /[\\/]_npx[\\/]/.test(argv1);
24583
+ const how = viaNpx ? "use `npx getaura@latest`" : "update with `npm install -g getaura@latest`";
24584
+ return `Aura ${latest} is available (you have ${AURA_VERSION}): ${how}.`;
24585
+ }
24586
+
24477
24587
  // src/index.ts
24478
24588
  var program = new Command();
24479
24589
  program.name("aura").description("Aura helps you and your coding agent follow best practices while you vibe code.").version(AURA_VERSION).showHelpAfterError();
24480
24590
  program.addHelpText("before", () => interactive() ? banner() : "");
24591
+ var update = startUpdateCheck();
24481
24592
  function action(fn) {
24482
24593
  return async (...args) => {
24483
24594
  try {
@@ -24491,6 +24602,9 @@ function action(fn) {
24491
24602
  if (process.env.AURA_DEBUG) console.error(err);
24492
24603
  process.exitCode = 1;
24493
24604
  }
24605
+ const latest = await update;
24606
+ if (latest) console.error(pc4.dim(`
24607
+ ${updateNotice(latest)}`));
24494
24608
  };
24495
24609
  }
24496
24610
  program.command("init").description("Set up Aura: pick your agent, write the product brief, install the Aura skill and run the first scan").option("--agent <id...>", "coding agent(s): claude, codex, cursor, pi").option("-y, --yes", "no prompts; use detected defaults").option("--key <key>", "Aura pilot key, for CI and scripts (otherwise a browser page asks for it)").option("--wait <seconds>", "how long to wait for the browser sign-in (default: until the link expires; 90 for agents)").option("--brief-file <path>", "use this file as the product brief").option("--answers <path>", "JSON file with the setup answers").option("--no-scan", "skip the first scan").addOption(new Option("--telemetry", "share scores and action names with the Aura team")).addOption(new Option("--no-telemetry", "don\u2019t share anything")).action(action((flags) => initCommand(flags)));