@whisperr/wizard 0.7.0 → 0.7.2

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 +182 -96
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1969,10 +1969,10 @@ var RunsClient = class {
1969
1969
  return payload;
1970
1970
  }
1971
1971
  createOrResumeRun(input) {
1972
- return this.request("POST", "/wizard/runs", { ...input });
1972
+ return this.request("POST", "/wizard/runs", { ...input }).then(normalizeRunBootstrap);
1973
1973
  }
1974
1974
  getRun(runId) {
1975
- return this.request("GET", `/wizard/runs/${runId}`);
1975
+ return this.request("GET", `/wizard/runs/${runId}`).then(normalizeRunBootstrap);
1976
1976
  }
1977
1977
  patchRun(runId, patch) {
1978
1978
  return this.request("PATCH", `/wizard/runs/${runId}`, { ...patch });
@@ -1983,7 +1983,7 @@ var RunsClient = class {
1983
1983
  idempotencyKey: itemIdempotencyKey(kind, code, idempotencyExtra),
1984
1984
  kind,
1985
1985
  payload
1986
- });
1986
+ }).then(normalizeItemWriteResult);
1987
1987
  }
1988
1988
  completeRun(runId, completion) {
1989
1989
  return this.request(
@@ -1993,6 +1993,64 @@ var RunsClient = class {
1993
1993
  );
1994
1994
  }
1995
1995
  };
1996
+ function normalizeItemWriteResult(payload) {
1997
+ const value = record(payload);
1998
+ const item = record(value?.item);
1999
+ const id = stringValue(value?.id) || stringValue(item?.id);
2000
+ if (!value || value.kind !== "event" || typeof value.created !== "boolean" || !id) {
2001
+ throw new RunsApiError("runs API returned an invalid item write response");
2002
+ }
2003
+ return {
2004
+ kind: "event",
2005
+ created: value.created,
2006
+ id,
2007
+ ...typeof value.code === "string" ? { code: value.code } : {},
2008
+ ...item && typeof item.id === "string" ? { item: { id: item.id, ...typeof item.code === "string" ? { code: item.code } : {} } } : {}
2009
+ };
2010
+ }
2011
+ function normalizeRunBootstrap(payload) {
2012
+ const value = record(payload);
2013
+ if (!value) {
2014
+ throw new RunsApiError("runs API returned an invalid run bootstrap response");
2015
+ }
2016
+ const snapshotSource = record(value.snapshot) ?? value;
2017
+ const events = Array.isArray(snapshotSource.events) ? snapshotSource.events : void 0;
2018
+ const run3 = record(value.run);
2019
+ const project = record(value.project);
2020
+ const ingestion = record(value.ingestion);
2021
+ if (!run3 || typeof run3.id !== "string" || !project || typeof project.id !== "string" || !events || events.some((event) => !isSnapshotEvent(event)) || !ingestion || typeof ingestion.apiKey !== "string" || typeof ingestion.baseUrl !== "string") {
2022
+ throw new RunsApiError("runs API returned an invalid run bootstrap response");
2023
+ }
2024
+ return {
2025
+ project: {
2026
+ id: project.id,
2027
+ repoFingerprint: stringValue(project.repoFingerprint),
2028
+ displayName: stringValue(project.displayName),
2029
+ target: stringValue(project.target),
2030
+ kind: stringValue(project.kind)
2031
+ },
2032
+ run: { id: run3.id, status: stringValue(run3.status), phase: stringValue(run3.phase) },
2033
+ resumed: value.resumed === true,
2034
+ universeMode: stringValue(value.universeMode),
2035
+ ...record(value.onboardingContext) ? { onboardingContext: record(value.onboardingContext) } : {},
2036
+ app: record(value.app) ?? {},
2037
+ snapshot: {
2038
+ setupStatus: stringValue(snapshotSource.setupStatus),
2039
+ events
2040
+ },
2041
+ ingestion: { apiKey: ingestion.apiKey, baseUrl: ingestion.baseUrl }
2042
+ };
2043
+ }
2044
+ function record(value) {
2045
+ return typeof value === "object" && value !== null ? value : void 0;
2046
+ }
2047
+ function stringValue(value) {
2048
+ return typeof value === "string" ? value : "";
2049
+ }
2050
+ function isSnapshotEvent(value) {
2051
+ const event = record(value);
2052
+ return !!event && typeof event.id === "string" && typeof event.code === "string" && typeof event.name === "string" && typeof event.reasoning === "string" && typeof event.projectId === "string" && !!record(event.payloadSchema);
2053
+ }
1996
2054
 
1997
2055
  // src/engine/selection.ts
1998
2056
  import { z } from "zod";
@@ -2172,6 +2230,7 @@ async function runSelection(provider, models, repoPath, bootstrap, surveyReport,
2172
2230
  const seen = /* @__PURE__ */ new Set();
2173
2231
  const rejected = new Set(rejectedCodes);
2174
2232
  let summary = "";
2233
+ let finished = false;
2175
2234
  const proposeTool = {
2176
2235
  name: "propose_event",
2177
2236
  description: "Propose one important, concretely-anchored product event. Returns ok:true when accepted.",
@@ -2233,6 +2292,7 @@ async function runSelection(provider, models, repoPath, bootstrap, surveyReport,
2233
2292
  },
2234
2293
  handler: async (input) => {
2235
2294
  summary = String(input.summary ?? "");
2295
+ finished = true;
2236
2296
  return { ok: true };
2237
2297
  }
2238
2298
  };
@@ -2256,6 +2316,7 @@ async function runSelection(provider, models, repoPath, bootstrap, surveyReport,
2256
2316
  );
2257
2317
  return {
2258
2318
  outcome: outcome.kind,
2319
+ finished,
2259
2320
  proposed,
2260
2321
  summary,
2261
2322
  surveyReport,
@@ -2271,8 +2332,7 @@ async function registerApprovedEvents(runs, runId, approved, sink) {
2271
2332
  reasoning: event.reasoning,
2272
2333
  ...Object.keys(event.payloadSchema).length > 0 ? { payloadSchema: event.payloadSchema } : {}
2273
2334
  });
2274
- const item = result.item;
2275
- registered.push({ ...event, eventId: item?.id ?? result.id ?? "" });
2335
+ registered.push({ ...event, eventId: result.id || result.item?.id || "" });
2276
2336
  sink.emit({ phase: "persisting", file: event.anchorFile, action: `registered ${event.code}` });
2277
2337
  }
2278
2338
  return registered;
@@ -2290,7 +2350,7 @@ function placementsFor(registered) {
2290
2350
  // src/engine/state.ts
2291
2351
  import { createHash as createHash3 } from "crypto";
2292
2352
  import { mkdirSync, readFileSync as readFileSync2, renameSync, writeFileSync } from "fs";
2293
- import { homedir } from "os";
2353
+ import { homedir, tmpdir } from "os";
2294
2354
  import { join as join4 } from "path";
2295
2355
 
2296
2356
  // src/engine/types.ts
@@ -2310,10 +2370,16 @@ var ENGINE_PHASES = [
2310
2370
  // src/engine/state.ts
2311
2371
  function stateDirectory() {
2312
2372
  const override = process.env.WHISPERR_WIZARD_STATE_DIR?.trim();
2313
- if (override) {
2314
- return override;
2373
+ const preferred = override || join4(homedir(), ".whisperr", "wizard-runs");
2374
+ if (canCreateStateDirectory(preferred)) {
2375
+ return preferred;
2315
2376
  }
2316
- return join4(homedir(), ".whisperr", "wizard-runs");
2377
+ const user = typeof process.getuid === "function" ? process.getuid() : "user";
2378
+ const fallback = join4(tmpdir(), `whisperr-${user}`, "wizard-runs");
2379
+ if (canCreateStateDirectory(fallback)) {
2380
+ return fallback;
2381
+ }
2382
+ throw new Error("Whisperr Wizard could not create a directory for run state");
2317
2383
  }
2318
2384
  function stateKey(apiBaseUrl, appId, repoFingerprint2) {
2319
2385
  return createHash3("sha256").update(`${apiBaseUrl}
@@ -2324,13 +2390,21 @@ function statePath(apiBaseUrl, appId, repoFingerprint2) {
2324
2390
  return join4(stateDirectory(), `${stateKey(apiBaseUrl, appId, repoFingerprint2)}.json`);
2325
2391
  }
2326
2392
  function saveRunState(state) {
2327
- const path = statePath(state.apiBaseUrl, state.appId, state.repoFingerprint);
2328
- mkdirSync(stateDirectory(), { recursive: true, mode: 448 });
2393
+ const directory = stateDirectory();
2394
+ const path = join4(directory, `${stateKey(state.apiBaseUrl, state.appId, state.repoFingerprint)}.json`);
2329
2395
  const payload = JSON.stringify({ ...state, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2);
2330
2396
  const temporary = `${path}.tmp`;
2331
2397
  writeFileSync(temporary, payload, { mode: 384 });
2332
2398
  renameSync(temporary, path);
2333
2399
  }
2400
+ function canCreateStateDirectory(directory) {
2401
+ try {
2402
+ mkdirSync(directory, { recursive: true, mode: 448 });
2403
+ return true;
2404
+ } catch {
2405
+ return false;
2406
+ }
2407
+ }
2334
2408
  function loadRunState(apiBaseUrl, appId, repoFingerprint2) {
2335
2409
  const path = statePath(apiBaseUrl, appId, repoFingerprint2);
2336
2410
  let raw;
@@ -2364,7 +2438,7 @@ function isEngineRunState(value) {
2364
2438
  // src/engine/worktree.ts
2365
2439
  import { spawn as spawn2 } from "child_process";
2366
2440
  import { mkdtemp, writeFile as writeFile2 } from "fs/promises";
2367
- import { tmpdir } from "os";
2441
+ import { tmpdir as tmpdir2 } from "os";
2368
2442
  import { join as join5 } from "path";
2369
2443
  var WorktreeError = class extends Error {
2370
2444
  constructor(code, message) {
@@ -2409,7 +2483,7 @@ async function createWorktree(repoPath, force = false) {
2409
2483
  );
2410
2484
  }
2411
2485
  const baseRef = (await must(repoPath, ["rev-parse", "HEAD"])).trim();
2412
- const path = await mkdtemp(join5(tmpdir(), "whisperr-worktree-"));
2486
+ const path = await mkdtemp(join5(tmpdir2(), "whisperr-worktree-"));
2413
2487
  await must(repoPath, ["worktree", "add", "--detach", "--force", path, "HEAD"]);
2414
2488
  return { path, baseRef, repoPath };
2415
2489
  }
@@ -2485,84 +2559,88 @@ async function runEngine(input) {
2485
2559
  } catch {
2486
2560
  }
2487
2561
  };
2488
- let state = loadRunState(input.config.apiBaseUrl, input.session.appId, input.repoFingerprint) ?? freshState(input, runId);
2489
- if (state.runId !== runId) {
2490
- state = freshState(input, runId);
2562
+ let state;
2563
+ try {
2564
+ state = loadRunState(input.config.apiBaseUrl, input.session.appId, input.repoFingerprint) ?? freshState(input, runId);
2565
+ if (state.runId !== runId) {
2566
+ state = freshState(input, runId);
2567
+ }
2568
+ } catch (error) {
2569
+ const reason = error instanceof Error ? error.message : String(error);
2570
+ await failRun(`state initialization failed: ${reason}`);
2571
+ return { kind: "aborted", reason: `state initialization failed: ${reason}` };
2491
2572
  }
2492
2573
  const persist = () => saveRunState(state);
2493
2574
  let registered;
2494
2575
  if (!state.designComplete) {
2495
- await patchPhase("surveying");
2496
- const survey = await runSurvey(
2497
- input.provider,
2498
- input.models,
2499
- input.repoPath,
2500
- bootstrap,
2501
- input.sink,
2502
- state.providerSessions.survey
2503
- );
2504
- if (survey.outcome.kind !== "completed") {
2505
- await failRun(`survey ${survey.outcome.kind}`);
2506
- return { kind: "aborted", reason: `survey ${survey.outcome.kind}` };
2507
- }
2508
- state.providerSessions.survey = survey.outcome.sessionId;
2509
- persist();
2510
- await patchPhase("designing");
2511
- const selection = await runSelection(
2512
- input.provider,
2513
- input.models,
2514
- input.repoPath,
2515
- bootstrap,
2516
- survey.report,
2517
- state.tombstonedCodes,
2518
- input.sink
2519
- );
2520
- if (selection.outcome !== "completed" && selection.proposed.length === 0) {
2521
- await failRun(`selection ${selection.outcome}`);
2522
- return { kind: "aborted", reason: `selection ${selection.outcome}` };
2523
- }
2524
- const approved = await input.callbacks.reviewEvents(selection.proposed);
2525
- if (approved === null) {
2526
- await failRun("selection rejected by user");
2527
- return { kind: "aborted", reason: "selection rejected by user" };
2528
- }
2529
- const approvedCodes = new Set(approved.map((event) => event.code));
2530
- for (const event of selection.proposed) {
2531
- if (!approvedCodes.has(event.code) && !state.tombstonedCodes.includes(event.code)) {
2532
- state.tombstonedCodes.push(event.code);
2533
- }
2534
- }
2535
- persist();
2536
- await patchPhase("persisting", `registering ${approved.length} events`);
2537
2576
  try {
2577
+ await patchPhase("surveying");
2578
+ const survey = await runSurvey(
2579
+ input.provider,
2580
+ input.models,
2581
+ input.repoPath,
2582
+ bootstrap,
2583
+ input.sink,
2584
+ state.providerSessions.survey
2585
+ );
2586
+ if (survey.outcome.kind !== "completed") {
2587
+ await failRun(`survey ${survey.outcome.kind}`);
2588
+ return { kind: "aborted", reason: `survey ${survey.outcome.kind}` };
2589
+ }
2590
+ state.providerSessions.survey = survey.outcome.sessionId;
2591
+ persist();
2592
+ await patchPhase("designing");
2593
+ const selection = await runSelection(
2594
+ input.provider,
2595
+ input.models,
2596
+ input.repoPath,
2597
+ bootstrap,
2598
+ survey.report,
2599
+ state.tombstonedCodes,
2600
+ input.sink
2601
+ );
2602
+ if (!selection.finished) {
2603
+ const reason = selection.outcome === "completed" ? "selection did not call finish_selection" : `selection ${selection.outcome}`;
2604
+ await failRun(reason);
2605
+ return { kind: "aborted", reason };
2606
+ }
2607
+ if (selection.proposed.length === 0) {
2608
+ await failRun("selection finished with no accepted events");
2609
+ return { kind: "aborted", reason: "selection finished with no accepted events" };
2610
+ }
2611
+ const approved = await input.callbacks.reviewEvents(selection.proposed);
2612
+ if (approved === null) {
2613
+ await failRun("selection rejected by user");
2614
+ return { kind: "aborted", reason: "selection rejected by user" };
2615
+ }
2616
+ if (approved.length === 0) {
2617
+ await failRun("no events accepted for instrumentation");
2618
+ return { kind: "aborted", reason: "no events accepted for instrumentation" };
2619
+ }
2620
+ const approvedCodes = new Set(approved.map((event) => event.code));
2621
+ for (const event of selection.proposed) {
2622
+ if (!approvedCodes.has(event.code) && !state.tombstonedCodes.includes(event.code)) {
2623
+ state.tombstonedCodes.push(event.code);
2624
+ }
2625
+ }
2626
+ persist();
2627
+ await patchPhase("persisting", `registering ${approved.length} events`);
2538
2628
  registered = await registerApprovedEvents(runs, runId, approved, input.sink);
2629
+ state.designComplete = true;
2630
+ state.clusters = deriveClusters(placementsFor(registered));
2631
+ persist();
2539
2632
  } catch (error) {
2540
2633
  const reason = error instanceof Error ? error.message : String(error);
2541
- await failRun(`event registration failed: ${reason}`);
2542
- persist();
2543
- return { kind: "aborted", reason: `event registration failed: ${reason}` };
2634
+ await failRun(`design failed: ${reason}`);
2635
+ return { kind: "aborted", reason: `design failed: ${reason}` };
2544
2636
  }
2545
- state.designComplete = true;
2546
- state.clusters = deriveClusters(placementsFor(registered));
2547
- persist();
2548
2637
  } else {
2549
2638
  registered = rebuildRegistered(state, bootstrap);
2550
2639
  input.sink.emit({ phase: "designing", action: `resumed with ${registered.length} registered events` });
2551
2640
  }
2552
2641
  if (registered.length === 0) {
2553
- await patchPhase("reporting", "no events registered for this project");
2554
- await completeRun(runs, runId, [], /* @__PURE__ */ new Map(), [], true, "unavailable", void 0);
2555
- return {
2556
- kind: "completed",
2557
- runId,
2558
- registered,
2559
- eventStatuses: /* @__PURE__ */ new Map(),
2560
- reportEvents: [],
2561
- changedFiles: [],
2562
- identifyWired: false,
2563
- applied: false,
2564
- summary: "No important events found for this project; nothing was instrumented."
2565
- };
2642
+ await failRun("no events registered for this project");
2643
+ return { kind: "aborted", reason: "no events registered for this project" };
2566
2644
  }
2567
2645
  await patchPhase("binding");
2568
2646
  let worktree;
@@ -2713,16 +2791,13 @@ function buildEvidence(registered, statuses, changedFiles2) {
2713
2791
  });
2714
2792
  }
2715
2793
  async function completeRun(runs, runId, evidence, _statuses, changedFiles2, identifyWired, verificationStatus, verificationCommand2) {
2716
- try {
2717
- await runs.completeRun(runId, {
2718
- changedFiles: changedFiles2,
2719
- identifyWired,
2720
- verificationStatus,
2721
- ...verificationCommand2 ? { verificationCommand: verificationCommand2 } : {},
2722
- events: evidence
2723
- });
2724
- } catch {
2725
- }
2794
+ await runs.completeRun(runId, {
2795
+ changedFiles: changedFiles2,
2796
+ identifyWired,
2797
+ verificationStatus,
2798
+ ...verificationCommand2 ? { verificationCommand: verificationCommand2 } : {},
2799
+ events: evidence
2800
+ });
2726
2801
  }
2727
2802
  function buildReportEvents(registered, statuses) {
2728
2803
  return registered.map((event) => {
@@ -3113,6 +3188,12 @@ var WRITE_OUTSIDE_REPO_DENIAL = "blocked: writes must stay inside the target rep
3113
3188
  var SENSITIVE_WRITE_DENIAL = "blocked: refusing to write CI/git configuration";
3114
3189
  var BASH_ALLOWLIST_DENIAL = "blocked: command is outside the Bash allowlist - use package install/add commands, mkdir, or git status/diff/log only";
3115
3190
  var CHAINED_COMMAND_DENIAL = "blocked: chained or substituted shell command - run one command at a time";
3191
+ function engineMcpToolName(toolName) {
3192
+ return `mcp__engine__${toolName}`;
3193
+ }
3194
+ function trustedEngineMcpTools(tools) {
3195
+ return new Set(tools.map((tool2) => engineMcpToolName(tool2.name)));
3196
+ }
3116
3197
  var ENV_EXAMPLES = /* @__PURE__ */ new Set([".env.example", ".env.sample", ".env.template"]);
3117
3198
  var SECRET_DIRECTORIES = /* @__PURE__ */ new Set([".aws", ".ssh", ".gnupg"]);
3118
3199
  var SECRET_EXACT_FILES = /* @__PURE__ */ new Set([".netrc", ".npmrc", ".pypirc"]);
@@ -3137,6 +3218,9 @@ var JS_PACKAGE_MANAGERS = /* @__PURE__ */ new Set(["npm", "pnpm", "yarn", "bun"]
3137
3218
  var PYTHON_PACKAGE_MANAGERS = /* @__PURE__ */ new Set(["pip", "pip3", "poetry", "uv"]);
3138
3219
  var RUBY_PACKAGE_MANAGERS = /* @__PURE__ */ new Set(["gem", "bundle"]);
3139
3220
  function evaluateToolUse(toolName, input, context) {
3221
+ if (context.trustedTools?.has(toolName)) {
3222
+ return { behavior: "allow" };
3223
+ }
3140
3224
  switch (toolName) {
3141
3225
  case "Read":
3142
3226
  return evaluateReadLike(readPathInputs(input), readPathInputs(input), context);
@@ -4168,7 +4252,7 @@ async function runPass(opts) {
4168
4252
  }
4169
4253
  return { summary, costUsd, ok, maxedOut };
4170
4254
  }
4171
- function buildToolPolicyHooks(repoPath) {
4255
+ function buildToolPolicyHooks(repoPath, trustedTools) {
4172
4256
  return {
4173
4257
  PreToolUse: [
4174
4258
  {
@@ -4176,7 +4260,8 @@ function buildToolPolicyHooks(repoPath) {
4176
4260
  async (input) => {
4177
4261
  if (input.hook_event_name !== "PreToolUse") return { continue: true };
4178
4262
  const decision = evaluateToolUse(input.tool_name, toolInputRecord(input.tool_input), {
4179
- repoPath
4263
+ repoPath,
4264
+ trustedTools
4180
4265
  });
4181
4266
  if (decision.behavior === "allow") {
4182
4267
  return { continue: true };
@@ -4194,9 +4279,9 @@ function buildToolPolicyHooks(repoPath) {
4194
4279
  ]
4195
4280
  };
4196
4281
  }
4197
- function createToolPermissionCallback(repoPath) {
4282
+ function createToolPermissionCallback(repoPath, trustedTools) {
4198
4283
  return async (toolName, input, options) => {
4199
- const decision = evaluateToolUse(toolName, input, { repoPath });
4284
+ const decision = evaluateToolUse(toolName, input, { repoPath, trustedTools });
4200
4285
  return decision.behavior === "allow" ? { behavior: "allow", toolUseID: options.toolUseID } : {
4201
4286
  behavior: "deny",
4202
4287
  message: decision.message,
@@ -4275,9 +4360,10 @@ function createClaudeProvider(config, session) {
4275
4360
  return { content: [{ type: "text", text: JSON.stringify(result) }] };
4276
4361
  })
4277
4362
  );
4363
+ const trustedTools = trustedEngineMcpTools(invocation.tools);
4278
4364
  const allowedTools = [
4279
4365
  ...invocation.allowRepoWrite ? EDIT_TOOLS : READ_ONLY_TOOLS2,
4280
- ...invocation.tools.map((engineTool) => `mcp__engine__${engineTool.name}`)
4366
+ ...trustedTools
4281
4367
  ];
4282
4368
  let sessionId = invocation.resumeSessionId;
4283
4369
  let costUsd = 0;
@@ -4295,8 +4381,8 @@ function createClaudeProvider(config, session) {
4295
4381
  ...roleConfig.effort ? { effort: roleConfig.effort } : {},
4296
4382
  tools: [...allowedTools],
4297
4383
  ...hostTools.length > 0 ? { mcpServers: { engine: createSdkMcpServer({ name: "engine", tools: hostTools }) } } : {},
4298
- hooks: buildToolPolicyHooks(invocation.cwd),
4299
- canUseTool: createToolPermissionCallback(invocation.cwd),
4384
+ hooks: buildToolPolicyHooks(invocation.cwd, trustedTools),
4385
+ canUseTool: createToolPermissionCallback(invocation.cwd, trustedTools),
4300
4386
  maxTurns: roleConfig.maxTurns,
4301
4387
  ...invocation.resumeSessionId ? { resume: invocation.resumeSessionId } : {},
4302
4388
  settingSources: []
@@ -4798,8 +4884,8 @@ async function fetchSuggestions(config, session, statuses) {
4798
4884
  }
4799
4885
  function isSuggestionRecord(value) {
4800
4886
  if (typeof value !== "object" || value === null) return false;
4801
- const record = value;
4802
- return typeof record.id === "string" && (record.kind === "event" || record.kind === "intervention") && typeof record.code === "string" && (record.status === "proposed" || record.status === "approved" || record.status === "rejected" || record.status === "integrated") && typeof record.payload === "object" && record.payload !== null;
4887
+ const record2 = value;
4888
+ return typeof record2.id === "string" && (record2.kind === "event" || record2.kind === "intervention") && typeof record2.code === "string" && (record2.status === "proposed" || record2.status === "approved" || record2.status === "rejected" || record2.status === "integrated") && typeof record2.payload === "object" && record2.payload !== null;
4803
4889
  }
4804
4890
  async function markSuggestionIntegrated(config, session, id, target9, repoFingerprint2) {
4805
4891
  if (config.offline) return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@whisperr/wizard",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "Whisperr Wizard \u2014 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",