@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.
- package/dist/index.js +78 -7
- 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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|