getaura 0.1.3 → 0.1.5

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
@@ -18,12 +18,14 @@ Your agent runs the setup and sends you a link to connect your pilot key in the
18
18
  npx getaura init
19
19
  ```
20
20
 
21
- `init` checks git and the GitHub CLI, asks which coding agent you use, opens your browser to connect your pilot key, drafts your product brief, installs the Aura skill and runs your first scan.
21
+ `init` checks git and the GitHub CLI, asks which coding agent you use, opens your browser to connect your pilot key, drafts your product brief, installs the Aura skill, offers to install the `aura` command and runs your first scan. After that, run `aura scan` from any folder.
22
+
23
+ To install the `aura` command yourself: `npm install -g getaura`. Without it, use `npx getaura <command>` (for example `npx getaura scan`).
22
24
 
23
25
  ## What you get
24
26
 
25
27
  - **An Aura score** from deterministic checks across security, agent setup, foundations and structure, explained in plain language.
26
- - **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.
27
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.
28
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.
29
31
 
package/dist/index.js CHANGED
@@ -20031,16 +20031,20 @@ import { fileURLToPath } from "url";
20031
20031
 
20032
20032
  // src/core/exec.ts
20033
20033
  import { spawn, spawnSync } from "child_process";
20034
+ var WINDOWS_PROGRAMS = /* @__PURE__ */ new Set(["git", "gh", "node", "tar", "where"]);
20035
+ function quoteForCmd(arg) {
20036
+ return /^[\w@%+=:,./\\-]+$/.test(arg) ? arg : `"${arg.replace(/"/g, '\\"')}"`;
20037
+ }
20038
+ function spawnPlan(cmd, args, platform = process.platform) {
20039
+ const direct = platform !== "win32" || cmd.includes("/") || cmd.includes("\\") || WINDOWS_PROGRAMS.has(cmd.toLowerCase());
20040
+ return direct ? { args, shell: false } : { args: args.map(quoteForCmd), shell: true };
20041
+ }
20034
20042
  function run(cmd, args, opts = {}) {
20035
20043
  return new Promise((resolve) => {
20036
20044
  let child;
20037
20045
  try {
20038
- child = spawn(cmd, args, {
20039
- cwd: opts.cwd,
20040
- env: opts.env ?? process.env,
20041
- stdio: ["pipe", "pipe", "pipe"],
20042
- shell: process.platform === "win32" && !cmd.includes("/") && !cmd.includes("\\")
20043
- });
20046
+ const plan = spawnPlan(cmd, args);
20047
+ child = spawn(cmd, plan.args, { cwd: opts.cwd, env: opts.env ?? process.env, stdio: ["pipe", "pipe", "pipe"], shell: plan.shell });
20044
20048
  } catch (err) {
20045
20049
  resolve({ code: 127, stdout: "", stderr: String(err) });
20046
20050
  return;
@@ -20076,7 +20080,11 @@ var AURA_DIR = ".aura";
20076
20080
  var ALWAYS_IGNORED = ["node_modules", ".next", ".git", "dist", "build", "out", ".vercel", ".turbo", "coverage", ".aura"];
20077
20081
  async function findRepoRoot(cwd = process.cwd()) {
20078
20082
  const r = await run("git", ["rev-parse", "--show-toplevel"], { cwd });
20079
- return r.code === 0 ? r.stdout.trim() : cwd;
20083
+ return path.resolve(r.code === 0 ? r.stdout.trim() : cwd);
20084
+ }
20085
+ function isInside(root, full, p2 = path) {
20086
+ const rel = p2.relative(p2.resolve(root), p2.resolve(full));
20087
+ return rel !== "" && rel !== ".." && !rel.startsWith(`..${p2.sep}`) && !p2.isAbsolute(rel);
20080
20088
  }
20081
20089
  async function isGitRepo(cwd) {
20082
20090
  return (await run("git", ["rev-parse", "--is-inside-work-tree"], { cwd })).code === 0;
@@ -20421,7 +20429,7 @@ function pmCommands(pm) {
20421
20429
  // package.json
20422
20430
  var package_default = {
20423
20431
  name: "getaura",
20424
- version: "0.1.3",
20432
+ version: "0.1.5",
20425
20433
  description: "Aura helps you and your coding agent follow best practices while you vibe code.",
20426
20434
  type: "module",
20427
20435
  bin: {
@@ -21591,6 +21599,10 @@ import path9 from "path";
21591
21599
  async function git(root, ...args) {
21592
21600
  return run("git", args, { cwd: root });
21593
21601
  }
21602
+ function ghInstallHint(platform = process.platform) {
21603
+ 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";
21604
+ return `${how}, open a new terminal, then run \`gh auth login\`.`;
21605
+ }
21594
21606
  async function ghStatus(root) {
21595
21607
  const v = await run("gh", ["--version"]);
21596
21608
  if (v.code !== 0) return { installed: false, authed: false, hasRemote: false };
@@ -21618,7 +21630,7 @@ async function freeBranchName(root, base) {
21618
21630
  function writeChanges(root, changes) {
21619
21631
  for (const c of changes) {
21620
21632
  const full = path9.join(root, c.path);
21621
- if (!full.startsWith(root + path9.sep)) throw new Error(`Refusing to write outside the repo: ${c.path}`);
21633
+ if (!isInside(root, full)) throw new Error(`Refusing to write outside the repo: ${c.path}`);
21622
21634
  if (c.content === null) rmSync(full, { force: true });
21623
21635
  else {
21624
21636
  mkdirSync2(path9.dirname(full), { recursive: true });
@@ -22762,7 +22774,7 @@ function safePath(root, rel) {
22762
22774
  if (normalized.startsWith("../") || normalized === ".." || path13.isAbsolute(normalized)) throw new Error(`Path is outside the repo: ${rel}`);
22763
22775
  if (FORBIDDEN.test(normalized)) throw new Error(`Aura doesn\u2019t let the agent touch ${normalized}.`);
22764
22776
  const full = path13.join(root, normalized);
22765
- if (!full.startsWith(root + path13.sep)) throw new Error(`Path is outside the repo: ${rel}`);
22777
+ if (!isInside(root, full)) throw new Error(`Path is outside the repo: ${rel}`);
22766
22778
  return normalized;
22767
22779
  }
22768
22780
  function clip(text) {
@@ -23382,6 +23394,53 @@ function explainSignIn(outcome) {
23382
23394
  }
23383
23395
  }
23384
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
+
23385
23444
  // src/commands/simple.ts
23386
23445
  async function latestScan(root) {
23387
23446
  const file2 = auraPath(root, "scan.json");
@@ -23400,8 +23459,10 @@ async function nextCommand(flags) {
23400
23459
  const root = await findRepoRoot();
23401
23460
  const scan = await latestScan(root);
23402
23461
  const steps = nextSteps(scan, { includeFoundationBundle: true, accounts: accountState(root, scan.stack.services) });
23462
+ const limit = flags.limit ? Number(flags.limit) : 10;
23403
23463
  if (flags.json) printJson({ score: scan.score, nextSteps: steps });
23404
- 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));
23405
23466
  return 0;
23406
23467
  }
23407
23468
  async function historyCommand(flags) {
@@ -23845,7 +23906,7 @@ ${pc.bold(action2.title)}`);
23845
23906
  const gh = await ghStatus(root);
23846
23907
  if (!gh.installed || !gh.authed || !gh.hasRemote) {
23847
23908
  say.error(
23848
- !gh.installed ? "The GitHub CLI isn\u2019t installed. Run `aura guide install-github-cli`." : !gh.authed ? "Run `gh auth login` first." : "This repo has no GitHub remote. Push it to GitHub first."
23909
+ !gh.installed ? `The GitHub CLI isn\u2019t installed. ${ghInstallHint()}` : !gh.authed ? "Run `gh auth login` first." : "This repo has no GitHub remote. Push it to GitHub first."
23849
23910
  );
23850
23911
  say.dim("Or run with --no-pr to commit the change on a local branch.");
23851
23912
  return 1;
@@ -24048,6 +24109,21 @@ async function draftBrief(client, answers, summary) {
24048
24109
  return res.brief.trim() + "\n";
24049
24110
  }
24050
24111
 
24112
+ // src/core/aura-command.ts
24113
+ function hasAuraCommand() {
24114
+ return commandExists("aura");
24115
+ }
24116
+ var INSTALL_AURA = `npm install -g getaura@${AURA_VERSION}`;
24117
+ async function installAuraCommand() {
24118
+ const r = await run("npm", ["install", "-g", `getaura@${AURA_VERSION}`], { timeoutMs: 18e4 });
24119
+ if (r.code !== 0) return { ok: false, needsAdmin: /EACCES|EPERM|permission denied/i.test(r.stderr + r.stdout) };
24120
+ return { ok: true, onPath: await hasAuraCommand() };
24121
+ }
24122
+ function installFallback(result, platform = process.platform) {
24123
+ const admin = result.needsAdmin && platform !== "win32" ? `Run \`sudo ${INSTALL_AURA}\` and enter your computer password, or use` : "Use";
24124
+ return `${admin} \`npx getaura <command>\` in place of \`aura <command>\` (for example \`npx getaura scan\`).`;
24125
+ }
24126
+
24051
24127
  // src/commands/scan.ts
24052
24128
  import { mkdtempSync as mkdtempSync3, rmSync as rmSync7, writeFileSync as writeFileSync7 } from "fs";
24053
24129
  import os5 from "os";
@@ -24113,13 +24189,15 @@ async function scanCommand(flags) {
24113
24189
  return exitCode;
24114
24190
  }
24115
24191
  console.log(renderTerminal(scan, { previous: flags.ci ? base?.score ?? null : previous }));
24116
- if (!flags.ci) console.log(renderNextSteps(steps, 3));
24192
+ const pick2 = !flags.ci && interactive();
24193
+ if (!flags.ci && !pick2) console.log(renderNextSteps(steps, 3));
24117
24194
  say.dim(
24118
24195
  `
24119
24196
  Full report: .aura/report.md${client.hasKey || flags.explain === false ? "" : " \xB7 Add a pilot key (aura login) for plain-language explanations."}`
24120
24197
  );
24121
24198
  if (base) say.info(`
24122
24199
  Compared with ${flags.base}: ${base.score} \u2192 ${scan.score}${exitCode ? ` (dropped more than ${maxDrop})` : ""}`);
24200
+ if (pick2) await pickAndRunSteps(steps);
24123
24201
  return exitCode;
24124
24202
  }
24125
24203
 
@@ -24174,8 +24252,7 @@ async function initCommand(flags) {
24174
24252
  }
24175
24253
  const root = await findRepoRoot(cwd);
24176
24254
  const gh = await ghStatus(root);
24177
- if (!gh.installed)
24178
- say.warn("The GitHub CLI (gh) isn\u2019t installed. Aura needs it to open pull requests. Run `aura guide install-github-cli`. You can keep going and scan now.");
24255
+ if (!gh.installed) say.warn(`The GitHub CLI (gh) isn\u2019t installed. Aura needs it to open pull requests. ${ghInstallHint()} You can keep going and scan now.`);
24179
24256
  else if (!gh.authed) say.warn("The GitHub CLI isn\u2019t signed in. Run `gh auth login` before applying fixes.");
24180
24257
  else if (!gh.hasRemote) say.warn("This repo has no GitHub remote yet. Push it to GitHub before applying fixes (`gh repo create`).");
24181
24258
  else say.ok("git and the GitHub CLI are ready.");
@@ -24318,6 +24395,26 @@ async function initCommand(flags) {
24318
24395
  }
24319
24396
  saveConfig(root, config2);
24320
24397
  ensureGitignore(root);
24398
+ if (!process.env.CI && !await hasAuraCommand()) {
24399
+ if (tty) {
24400
+ if (unwrap(await p.confirm({ message: "Install the `aura` command so you can run `aura scan` any time?", initialValue: true }))) {
24401
+ const s = spin(true);
24402
+ s.start("Installing the aura command");
24403
+ const installed = await installAuraCommand();
24404
+ if (installed.ok)
24405
+ s.stop(installed.onPath ? "Installed. Run `aura scan` from any folder." : "Installed. Open a new terminal window, then run `aura scan`.");
24406
+ else {
24407
+ s.stop("Couldn\u2019t install the aura command");
24408
+ say.warn(installFallback(installed));
24409
+ }
24410
+ } else say.dim(`Use \`npx getaura <command>\` instead, or install it later with \`${INSTALL_AURA}\`.`);
24411
+ } else {
24412
+ console.log(`
24413
+ ${pc.magenta("\u2726")} ${pc.bold("Give the user the aura command")}`);
24414
+ console.log(` Run \`${INSTALL_AURA}\` so they can type \`aura scan\` from now on. Until then, use \`npx getaura <command>\`.
24415
+ `);
24416
+ }
24417
+ }
24321
24418
  if (flags.scan === false) {
24322
24419
  if (tty) p.outro("Done. Run `aura scan` when you\u2019re ready.");
24323
24420
  return 0;