@whisperr/wizard 0.1.4 → 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/dist/index.js +94 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -522,10 +522,11 @@ function sleep(ms) {
|
|
|
522
522
|
}
|
|
523
523
|
|
|
524
524
|
// src/core/manifest.ts
|
|
525
|
-
async function fetchManifest(config, session, targetId) {
|
|
525
|
+
async function fetchManifest(config, session, targetId, repoFingerprint2) {
|
|
526
526
|
if (config.offline) return mockManifest(session.appId, config);
|
|
527
527
|
const url = new URL(`${config.apiBaseUrl}/wizard/manifest`);
|
|
528
528
|
if (targetId) url.searchParams.set("target", targetId);
|
|
529
|
+
if (repoFingerprint2) url.searchParams.set("repo", repoFingerprint2);
|
|
529
530
|
const res = await fetch(url, {
|
|
530
531
|
headers: { authorization: `Bearer ${session.token}` }
|
|
531
532
|
});
|
|
@@ -680,10 +681,17 @@ function renderEventsBrief(m) {
|
|
|
680
681
|
lines.push(
|
|
681
682
|
`${events.length} candidate events (highest-impact first). For each: fire track('<event_type>', { ...properties }) at the real moment it happens, and attach every listed property you can source from values in scope there. Skip an event entirely if it has no clear client-side trigger.`
|
|
682
683
|
);
|
|
684
|
+
if (events.some((e) => e.coverage?.length)) {
|
|
685
|
+
lines.push("");
|
|
686
|
+
lines.push(
|
|
687
|
+
"Some events are already instrumented elsewhere (noted inline). Skip any marked [already wired here]; for [wired on <surface>], only add it if this codebase ALSO genuinely triggers it."
|
|
688
|
+
);
|
|
689
|
+
}
|
|
683
690
|
for (const e of events) {
|
|
684
691
|
lines.push("");
|
|
685
692
|
const why = e.interventions?.length ? ` \xB7 drives: ${e.interventions.map((i) => i.label ?? i.code).join(", ")}` : "";
|
|
686
|
-
|
|
693
|
+
const cov = coverageNote(e.coverage);
|
|
694
|
+
lines.push(`\u25A0 ${e.eventType}${e.label ? ` (${e.label})` : ""}${why}${cov}`);
|
|
687
695
|
if (e.description) lines.push(` ${e.description}`);
|
|
688
696
|
if (e.properties?.length) {
|
|
689
697
|
lines.push(" properties to capture if available:");
|
|
@@ -696,6 +704,16 @@ function renderEventsBrief(m) {
|
|
|
696
704
|
}
|
|
697
705
|
return lines.join("\n");
|
|
698
706
|
}
|
|
707
|
+
function coverageNote(coverage) {
|
|
708
|
+
if (!coverage?.length) return "";
|
|
709
|
+
const here = coverage.find((c) => c.sameSurface && c.status === "wired");
|
|
710
|
+
if (here) return " [already wired here]";
|
|
711
|
+
const elsewhere = coverage.filter((c) => c.status === "wired");
|
|
712
|
+
if (elsewhere.length) {
|
|
713
|
+
return ` [wired on ${elsewhere.map((c) => c.target).join(", ")}]`;
|
|
714
|
+
}
|
|
715
|
+
return "";
|
|
716
|
+
}
|
|
699
717
|
|
|
700
718
|
// src/core/agent.ts
|
|
701
719
|
async function runIntegrationAgent(opts) {
|
|
@@ -867,6 +885,9 @@ function short(s, max = 60) {
|
|
|
867
885
|
|
|
868
886
|
// src/core/git.ts
|
|
869
887
|
import { spawn } from "child_process";
|
|
888
|
+
import { createHash } from "crypto";
|
|
889
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
890
|
+
import { basename, join as join2 } from "path";
|
|
870
891
|
async function takeCheckpoint(repoPath) {
|
|
871
892
|
const isRepo = (await run(repoPath, ["rev-parse", "--is-inside-work-tree"])).ok;
|
|
872
893
|
if (!isRepo) return { isRepo: false };
|
|
@@ -897,6 +918,38 @@ function revertHint(checkpoint) {
|
|
|
897
918
|
if (!checkpoint.isRepo || !checkpoint.baseRef) return void 0;
|
|
898
919
|
return `git restore . && git clean -fd (back to ${checkpoint.baseRef.slice(0, 7)})`;
|
|
899
920
|
}
|
|
921
|
+
async function repoFingerprint(repoPath) {
|
|
922
|
+
const remote = await run(repoPath, ["remote", "get-url", "origin"]);
|
|
923
|
+
const seed = remote.ok && remote.stdout.trim() ? normalizeRemote(remote.stdout.trim()) : `dir:${basename(repoPath)}`;
|
|
924
|
+
return createHash("sha256").update(seed).digest("hex").slice(0, 16);
|
|
925
|
+
}
|
|
926
|
+
function normalizeRemote(url) {
|
|
927
|
+
return url.replace(/^git@([^:]+):/, "$1/").replace(/^https?:\/\//, "").replace(/\.git$/, "").replace(/\/+$/, "").toLowerCase();
|
|
928
|
+
}
|
|
929
|
+
async function scanWiredEvents(repoPath, files, eventTypes) {
|
|
930
|
+
const wired = /* @__PURE__ */ new Map();
|
|
931
|
+
const patterns = eventTypes.map((e) => ({
|
|
932
|
+
eventType: e,
|
|
933
|
+
re: new RegExp(`track\\s*\\(\\s*['"\`]${escapeRegExp(e)}['"\`]`)
|
|
934
|
+
}));
|
|
935
|
+
for (const file of files) {
|
|
936
|
+
let content = "";
|
|
937
|
+
try {
|
|
938
|
+
content = await readFile2(join2(repoPath, file), "utf8");
|
|
939
|
+
} catch {
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
for (const { eventType, re } of patterns) {
|
|
943
|
+
if (!wired.has(eventType) && re.test(content)) {
|
|
944
|
+
wired.set(eventType, file);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
return wired;
|
|
949
|
+
}
|
|
950
|
+
function escapeRegExp(s) {
|
|
951
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
952
|
+
}
|
|
900
953
|
function run(cwd, args) {
|
|
901
954
|
return new Promise((resolve2) => {
|
|
902
955
|
const child = spawn("git", args, { cwd });
|
|
@@ -934,6 +987,22 @@ async function pollFirstEvent(config, session, opts = {}) {
|
|
|
934
987
|
return { received: false };
|
|
935
988
|
}
|
|
936
989
|
|
|
990
|
+
// src/core/report.ts
|
|
991
|
+
async function postRunReport(config, session, report) {
|
|
992
|
+
if (config.offline) return;
|
|
993
|
+
try {
|
|
994
|
+
await fetch(`${config.apiBaseUrl}/wizard/report`, {
|
|
995
|
+
method: "POST",
|
|
996
|
+
headers: {
|
|
997
|
+
"Content-Type": "application/json",
|
|
998
|
+
Authorization: `Bearer ${session.token}`
|
|
999
|
+
},
|
|
1000
|
+
body: JSON.stringify(report)
|
|
1001
|
+
});
|
|
1002
|
+
} catch {
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
937
1006
|
// src/ui/banner.ts
|
|
938
1007
|
function banner() {
|
|
939
1008
|
const wave = theme.signal("\u223F\u223F\u223F");
|
|
@@ -997,9 +1066,10 @@ Flutter is live today; ${theme.bright(
|
|
|
997
1066
|
p.cancel(theme.alert(err.message));
|
|
998
1067
|
return 1;
|
|
999
1068
|
}
|
|
1069
|
+
const fingerprint = await repoFingerprint(repoPath);
|
|
1000
1070
|
const manifest = await withSpinner(
|
|
1001
1071
|
"Loading your onboarding context",
|
|
1002
|
-
() => fetchManifest(config, session, chosen.playbook.target.id)
|
|
1072
|
+
() => fetchManifest(config, session, chosen.playbook.target.id, fingerprint)
|
|
1003
1073
|
);
|
|
1004
1074
|
p.note(
|
|
1005
1075
|
summarizeManifest(manifest),
|
|
@@ -1055,9 +1125,28 @@ Flutter is live today; ${theme.bright(
|
|
|
1055
1125
|
if (outcome.summary.trim()) {
|
|
1056
1126
|
p.log.message(outcome.summary.trim());
|
|
1057
1127
|
}
|
|
1128
|
+
const wiredMap = await scanWiredEvents(
|
|
1129
|
+
repoPath,
|
|
1130
|
+
files,
|
|
1131
|
+
manifest.events.map((e) => e.eventType)
|
|
1132
|
+
);
|
|
1133
|
+
const reportEvents = manifest.events.map((e) => ({
|
|
1134
|
+
event_type: e.eventType,
|
|
1135
|
+
status: wiredMap.has(e.eventType) ? "wired" : "skipped",
|
|
1136
|
+
file: wiredMap.get(e.eventType)
|
|
1137
|
+
}));
|
|
1138
|
+
await postRunReport(config, session, {
|
|
1139
|
+
target: chosen.playbook.target.id,
|
|
1140
|
+
repo_fingerprint: fingerprint,
|
|
1141
|
+
identify_wired: outcome.coreOk,
|
|
1142
|
+
cost_usd: outcome.costUsd,
|
|
1143
|
+
duration_ms: outcome.durationMs,
|
|
1144
|
+
summary: outcome.summary.slice(0, 4e3),
|
|
1145
|
+
events: reportEvents
|
|
1146
|
+
});
|
|
1058
1147
|
p.log.info(
|
|
1059
1148
|
theme.muted(
|
|
1060
|
-
`${files.length} file${files.length === 1 ? "" : "s"} changed \xB7 ~$${outcome.costUsd.toFixed(2)} \xB7 ${Math.round(outcome.durationMs / 1e3)}s`
|
|
1149
|
+
`${wiredMap.size}/${manifest.events.length} events wired \xB7 ${files.length} file${files.length === 1 ? "" : "s"} changed \xB7 ~$${outcome.costUsd.toFixed(2)} \xB7 ${Math.round(outcome.durationMs / 1e3)}s`
|
|
1061
1150
|
)
|
|
1062
1151
|
);
|
|
1063
1152
|
if (!config.offline) {
|
|
@@ -1226,7 +1315,7 @@ async function main() {
|
|
|
1226
1315
|
return;
|
|
1227
1316
|
}
|
|
1228
1317
|
if ("version" in parsed) {
|
|
1229
|
-
console.log("0.1.
|
|
1318
|
+
console.log("0.1.5");
|
|
1230
1319
|
return;
|
|
1231
1320
|
}
|
|
1232
1321
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@whisperr/wizard",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
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
|
"license": "UNLICENSED",
|
|
6
6
|
"private": false,
|