@whisperr/wizard 0.3.0 → 0.3.1

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.
Files changed (2) hide show
  1. package/dist/index.js +78 -7
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1148,7 +1148,9 @@ function mockManifest(appId, config) {
1148
1148
  }
1149
1149
 
1150
1150
  // src/core/agent.ts
1151
- import { query } from "@anthropic-ai/claude-agent-sdk";
1151
+ import {
1152
+ query
1153
+ } from "@anthropic-ai/claude-agent-sdk";
1152
1154
 
1153
1155
  // src/core/git.ts
1154
1156
  import { spawn } from "child_process";
@@ -1730,11 +1732,20 @@ function coverageNote(coverage) {
1730
1732
  import { isAbsolute, relative, resolve } from "path";
1731
1733
  var SECRET_MATERIAL_DENIAL = "blocked: secret material - reference the variable name in .env.example instead of reading the real file";
1732
1734
  var WRITE_OUTSIDE_REPO_DENIAL = "blocked: writes must stay inside the target repository";
1735
+ var SENSITIVE_WRITE_DENIAL = "blocked: refusing to write CI/git configuration";
1733
1736
  var BASH_ALLOWLIST_DENIAL = "blocked: command is outside the Bash allowlist - use package install/add commands, mkdir, or git status/diff/log only";
1734
1737
  var CHAINED_COMMAND_DENIAL = "blocked: chained or substituted shell command - run one command at a time";
1735
1738
  var ENV_EXAMPLES = /* @__PURE__ */ new Set([".env.example", ".env.sample", ".env.template"]);
1736
1739
  var SECRET_DIRECTORIES = /* @__PURE__ */ new Set([".aws", ".ssh", ".gnupg"]);
1737
1740
  var SECRET_EXACT_FILES = /* @__PURE__ */ new Set([".netrc", ".npmrc", ".pypirc"]);
1741
+ var SENSITIVE_WRITE_DIRECTORIES = /* @__PURE__ */ new Set([".git", ".circleci", ".buildkite"]);
1742
+ var SENSITIVE_WRITE_EXACT_FILES = /* @__PURE__ */ new Set([
1743
+ ".gitlab-ci.yml",
1744
+ "azure-pipelines.yml",
1745
+ "bitbucket-pipelines.yml",
1746
+ ".drone.yml",
1747
+ "jenkinsfile"
1748
+ ]);
1738
1749
  var SECRET_SUFFIXES = [
1739
1750
  ".pem",
1740
1751
  ".key",
@@ -1818,6 +1829,9 @@ function evaluateWrite(input, context) {
1818
1829
  if (!isPathInsideRepo(pathValue, context.repoPath)) {
1819
1830
  return { behavior: "deny", message: WRITE_OUTSIDE_REPO_DENIAL };
1820
1831
  }
1832
+ if (isSensitiveWritePath(pathValue, context.repoPath)) {
1833
+ return { behavior: "deny", message: SENSITIVE_WRITE_DENIAL };
1834
+ }
1821
1835
  return { behavior: "allow" };
1822
1836
  }
1823
1837
  function evaluateBash(input) {
@@ -1907,6 +1921,19 @@ function firstString(input, keys) {
1907
1921
  function normalizePathLike(value) {
1908
1922
  return stripOuterQuotes(value.trim()).replace(/\\/g, "/");
1909
1923
  }
1924
+ function repoRelativePath(pathValue, repoPath) {
1925
+ const repoRoot = resolve(repoPath);
1926
+ const candidate = isAbsolute(pathValue) ? resolve(pathValue) : resolve(repoRoot, pathValue);
1927
+ return normalizePathLike(relative(repoRoot, candidate));
1928
+ }
1929
+ function isSensitiveWritePath(pathValue, repoPath) {
1930
+ const parts = repoRelativePath(pathValue, repoPath).split("/").map((part) => stripOuterQuotes(part.trim().toLowerCase())).filter(Boolean);
1931
+ const [first, second] = parts;
1932
+ if (!first) return false;
1933
+ if (SENSITIVE_WRITE_DIRECTORIES.has(first)) return true;
1934
+ if (first === ".github" && second === "workflows") return true;
1935
+ return parts.length === 1 && SENSITIVE_WRITE_EXACT_FILES.has(first);
1936
+ }
1910
1937
  function stripOuterQuotes(value) {
1911
1938
  if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1912
1939
  return value.slice(1, -1);
@@ -2672,6 +2699,7 @@ async function runPass(opts) {
2672
2699
  thinking: { type: "adaptive" },
2673
2700
  effort,
2674
2701
  tools: [...allowedTools],
2702
+ hooks: buildToolPolicyHooks(repoPath),
2675
2703
  canUseTool: createToolPermissionCallback(repoPath),
2676
2704
  maxTurns,
2677
2705
  // Native SDK hard cost cap. Stops this pass with an `error_max_budget_usd`
@@ -2708,6 +2736,32 @@ async function runPass(opts) {
2708
2736
  }
2709
2737
  return { summary, costUsd, ok, maxedOut };
2710
2738
  }
2739
+ function buildToolPolicyHooks(repoPath) {
2740
+ return {
2741
+ PreToolUse: [
2742
+ {
2743
+ hooks: [
2744
+ async (input) => {
2745
+ if (input.hook_event_name !== "PreToolUse") return { continue: true };
2746
+ const decision = evaluateToolUse(input.tool_name, toolInputRecord(input.tool_input), {
2747
+ repoPath
2748
+ });
2749
+ if (decision.behavior === "allow") {
2750
+ return { continue: true };
2751
+ }
2752
+ return {
2753
+ hookSpecificOutput: {
2754
+ hookEventName: "PreToolUse",
2755
+ permissionDecision: "deny",
2756
+ permissionDecisionReason: decision.message
2757
+ }
2758
+ };
2759
+ }
2760
+ ]
2761
+ }
2762
+ ]
2763
+ };
2764
+ }
2711
2765
  function createToolPermissionCallback(repoPath) {
2712
2766
  return async (toolName, input, options) => {
2713
2767
  const decision = evaluateToolUse(toolName, input, { repoPath });
@@ -2718,6 +2772,12 @@ function createToolPermissionCallback(repoPath) {
2718
2772
  };
2719
2773
  };
2720
2774
  }
2775
+ function toolInputRecord(input) {
2776
+ if (input && typeof input === "object" && !Array.isArray(input)) {
2777
+ return input;
2778
+ }
2779
+ return {};
2780
+ }
2721
2781
  function applyModelAuthEnv(config, session) {
2722
2782
  if (config.directAnthropicKey) {
2723
2783
  process.env.ANTHROPIC_API_KEY = config.directAnthropicKey;
@@ -2844,6 +2904,12 @@ function tail2(s) {
2844
2904
  }
2845
2905
 
2846
2906
  // src/core/report.ts
2907
+ function scrubSummary(text) {
2908
+ return scrubTerminalControls(text).replace(/sk-ant-[A-Za-z0-9_-]+/g, "[redacted]").replace(/sk-[A-Za-z0-9]{16,}/g, "[redacted]").replace(/AKIA[0-9A-Z]{16}/g, "[redacted]").replace(/Bearer\s+[A-Za-z0-9._-]+/g, "[redacted]").replace(
2909
+ /\b(ANTHROPIC_(?:AUTH_TOKEN|API_KEY))\s*=\s*["']?[^"'\s]+["']?/gi,
2910
+ "$1=[redacted]"
2911
+ );
2912
+ }
2847
2913
  async function postRunReport(config, session, report) {
2848
2914
  if (config.offline) return { ok: true };
2849
2915
  try {
@@ -2879,6 +2945,9 @@ function detailSlice(detail) {
2879
2945
  const sliced = detail.slice(0, 200).trim();
2880
2946
  return sliced || void 0;
2881
2947
  }
2948
+ function scrubTerminalControls(value) {
2949
+ return value.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "").replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)?/g, "").replace(/[\x00-\x1f\x7f]/g, " ").replace(/ {2,}/g, " ").trim();
2950
+ }
2882
2951
 
2883
2952
  // src/ui/banner.ts
2884
2953
  function banner() {
@@ -3071,9 +3140,7 @@ Flutter is live today; ${theme.bright(
3071
3140
  if (files.length) {
3072
3141
  p.note(files.map((f) => theme.muted("\u2022 ") + f).join("\n"), "Files changed");
3073
3142
  }
3074
- if (outcome.summary.trim()) {
3075
- p.log.message(outcome.summary.trim());
3076
- }
3143
+ logAgentSummary(outcome.summary);
3077
3144
  if (!outcome.coreOk) {
3078
3145
  const reverted = await maybeRevert(
3079
3146
  repoPath,
@@ -3124,7 +3191,7 @@ Flutter is live today; ${theme.bright(
3124
3191
  if (repair.summary.trim()) {
3125
3192
  outcome.summary = [outcome.summary, `Repair:
3126
3193
  ${repair.summary}`].filter(Boolean).join("\n\n");
3127
- p.log.message(repair.summary.trim());
3194
+ logAgentSummary(repair.summary);
3128
3195
  }
3129
3196
  repairSpin.stop(theme.success("Repair pass finished"));
3130
3197
  } catch (err) {
@@ -3243,7 +3310,7 @@ ${repair.summary}`].filter(Boolean).join("\n\n");
3243
3310
  verified,
3244
3311
  cost_usd: outcome.costUsd,
3245
3312
  duration_ms: outcome.durationMs,
3246
- summary: outcome.summary.slice(0, 4e3),
3313
+ summary: scrubSummary(outcome.summary).slice(0, 4e3),
3247
3314
  events: reportEvents
3248
3315
  });
3249
3316
  if (!reportResult.ok) {
@@ -3435,7 +3502,7 @@ async function offerOpportunities(opts) {
3435
3502
  });
3436
3503
  if (pass.ran) {
3437
3504
  spin.stop(theme.success("New events instrumented"));
3438
- if (pass.summary.trim()) p.log.message(pass.summary.trim());
3505
+ logAgentSummary(pass.summary);
3439
3506
  } else {
3440
3507
  spin.stop(
3441
3508
  theme.warn("Skipped instrumenting the new events") + theme.muted(
@@ -3588,6 +3655,10 @@ function renderEventOutcomeLines(outcomes, wiredMap) {
3588
3655
  if (remainder > 0) lines.push(theme.muted(`\u2026 ${remainder} more events`));
3589
3656
  return lines.join("\n");
3590
3657
  }
3658
+ function logAgentSummary(summary) {
3659
+ const cleaned = scrubSummary(summary).trim();
3660
+ if (cleaned) p.log.message(cleaned);
3661
+ }
3591
3662
  function shortPhaseLabel(phase) {
3592
3663
  switch (phase) {
3593
3664
  case "Mapping your codebase":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@whisperr/wizard",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Whisperr Wizard — one command to integrate the Whisperr SDK into your app. Authenticates with your onboarded account and uses an AI coding agent to wire up identify() and your business events automatically.",
5
5
  "repository": {
6
6
  "type": "git",