behavior-wrapped 0.8.8 → 0.8.10
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/assets/index-CFnO0oEF.js +11 -0
- package/dist/assets/{index-RUb7reCR.css → index-Ctd7RCMR.css} +1 -1
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/server/analysis.mjs +9 -6
- package/server/consent.mjs +16 -1
- package/server/privacy.mjs +4 -2
- package/server/public-report-schema.mjs +1 -1
- package/dist/assets/index-CD48EtI2.js +0 -11
package/dist/index.html
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
<meta name="theme-color" content="#0d0b1b" />
|
|
7
7
|
<meta name="description" content="A local-first behavior report for you and your AI agents." />
|
|
8
8
|
<title>Behavior Wrapped</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-CFnO0oEF.js"></script>
|
|
10
|
+
<link rel="stylesheet" crossorigin href="/assets/index-Ctd7RCMR.css">
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
13
|
<div id="root"></div>
|
package/package.json
CHANGED
package/server/analysis.mjs
CHANGED
|
@@ -62,7 +62,7 @@ function proseText(value) {
|
|
|
62
62
|
.replace(/`[^`\n]+`/g, " ")
|
|
63
63
|
.replace(/^\s*>.*$/gm, " ")
|
|
64
64
|
.replace(/https?:\/\/\S+/g, " ")
|
|
65
|
-
.replace(
|
|
65
|
+
.replace(/(?:^|\s)(?:[A-Za-z]:)?[/.~][^\s]+/g, " ")
|
|
66
66
|
.replace(/<[^>]+>/g, " ")
|
|
67
67
|
.replace(/\s+/g, " ")
|
|
68
68
|
.trim();
|
|
@@ -106,7 +106,7 @@ function repeatedInstructions(sessionRecords) {
|
|
|
106
106
|
return [...instructions.values()]
|
|
107
107
|
.filter((item) => item.occurrences >= 2)
|
|
108
108
|
.sort((left, right) => right.sessions.size - left.sessions.size || right.occurrences - left.occurrences || left.instruction.localeCompare(right.instruction))
|
|
109
|
-
.slice(0,
|
|
109
|
+
.slice(0, 2)
|
|
110
110
|
.map((item) => ({ instruction: item.instruction, occurrences: item.occurrences, distinctSessions: item.sessions.size }));
|
|
111
111
|
}
|
|
112
112
|
|
|
@@ -453,6 +453,8 @@ export function analyzeSessions(sessionRecords) {
|
|
|
453
453
|
};
|
|
454
454
|
for (const [recordIndex, record] of records.entries()) {
|
|
455
455
|
const text = visibleText(record);
|
|
456
|
+
const userProse = record.type === "user" ? proseText(localOpeningPrompt(text)) : "";
|
|
457
|
+
const assistantResponseProse = record.type === "assistant" ? proseText(text) : "";
|
|
456
458
|
const declaredModel = record?.message?.model || record?.model;
|
|
457
459
|
if (typeof declaredModel === "string" && declaredModel) currentModel = declaredModel;
|
|
458
460
|
if (record.type === "system" && record.subtype === "turn_duration") {
|
|
@@ -462,10 +464,10 @@ export function analyzeSessions(sessionRecords) {
|
|
|
462
464
|
longestUninterruptedRun = { durationMs: Math.round(durationMs), agent, agentName: definition?.name || "Agent" };
|
|
463
465
|
}
|
|
464
466
|
}
|
|
465
|
-
if (record.type === "user" && !record.isMeta &&
|
|
467
|
+
if (record.type === "user" && !record.isMeta && userProse) {
|
|
466
468
|
finishResponse();
|
|
467
469
|
hasCurrentPrompt = true;
|
|
468
|
-
userInputWords += wordCount(
|
|
470
|
+
userInputWords += wordCount(userProse);
|
|
469
471
|
userInputCount++;
|
|
470
472
|
sessionTurns++;
|
|
471
473
|
if (isFrustratedMessage(text)) frustratedMessages++;
|
|
@@ -477,8 +479,8 @@ export function analyzeSessions(sessionRecords) {
|
|
|
477
479
|
location: { sessionId, recordIndex, timestamp: record.timestamp || null },
|
|
478
480
|
});
|
|
479
481
|
}
|
|
480
|
-
} else if (record.type === "assistant" && hasCurrentPrompt &&
|
|
481
|
-
currentResponseWords += wordCount(
|
|
482
|
+
} else if (record.type === "assistant" && hasCurrentPrompt && assistantResponseProse) {
|
|
483
|
+
currentResponseWords += wordCount(assistantResponseProse);
|
|
482
484
|
}
|
|
483
485
|
if (record.type === "assistant" && text) {
|
|
484
486
|
assistantProse.push(text);
|
|
@@ -554,6 +556,7 @@ export function analyzeSessions(sessionRecords) {
|
|
|
554
556
|
agentUserWordRatio: userInputWords ? Number((agentResponseWords / userInputWords).toFixed(2)) : null,
|
|
555
557
|
averageAgentResponseWords: agentResponseCount ? Math.round(agentResponseWords / agentResponseCount) : 0,
|
|
556
558
|
averageUserInputWords: userInputCount ? Math.round(userInputWords / userInputCount) : 0,
|
|
559
|
+
wordCountMethod: "Counts conversational prose only, excluding injected workspace instructions, code, URLs, paths, and markup.",
|
|
557
560
|
longestSessionTurns: Math.max(0, ...sessionTurnCounts),
|
|
558
561
|
sessionTurnCounts: [...sessionTurnCounts].sort((left, right) => left - right),
|
|
559
562
|
sessionTurnMethod: "Counts each visible, non-meta user message as one turn.",
|
package/server/consent.mjs
CHANGED
|
@@ -7,11 +7,26 @@ const reset = "\x1b[0m";
|
|
|
7
7
|
|
|
8
8
|
export const remoteAnalysisConsentText = "Behavior Wrapped will send redacted excerpts from your session history to GPT-5.6 Luna via OpenRouter using zero-data-retention providers for analysis. OK to proceed?";
|
|
9
9
|
export const localOnlyAnalysisText = "Local-only analysis keeps all session data on this device. It uses deterministic statistics and a locally counted favorite phrase, but omits AI-judged interaction tone, usage topics, and instrumental workarounds. Those omissions leave leaderboard plots 2 and 3 incomplete, so the report will not be published or included in the leaderboard.";
|
|
10
|
+
export const researchDonationIntroText = [
|
|
11
|
+
"Your Claude Code and Codex sessions from the last 30 days are read and analyzed locally.",
|
|
12
|
+
"With permission, redacted excerpts are sent to an LLM judge for additional analysis.",
|
|
13
|
+
"Your public Wrapped contains aggregate statistics only.",
|
|
14
|
+
"At the end, you may optionally review and donate select transcripts to the Susan Calvin Project (susancalvin.org). Research donation is optional and is not required to create your Wrapped.",
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
export function formatResearchDonationIntro() {
|
|
18
|
+
return [
|
|
19
|
+
`${bright}Before we begin${reset}`,
|
|
20
|
+
"",
|
|
21
|
+
...researchDonationIntroText.map((line, index) => `${purple}${index + 1}.${reset} ${line}`),
|
|
22
|
+
].join("\n");
|
|
23
|
+
}
|
|
10
24
|
|
|
11
25
|
export async function requestAnalysisMode({ input = process.stdin, output = process.stdout } = {}) {
|
|
12
26
|
const prompt = createInterface({ input, output });
|
|
13
27
|
try {
|
|
14
|
-
|
|
28
|
+
output.write(`\n${formatResearchDonationIntro()}\n\n`);
|
|
29
|
+
const question = `${lime}◇${reset} ${remoteAnalysisConsentText.replace("GPT-5.6 Luna", `${purple}${bright}GPT-5.6 Luna${reset}`)} ${bright}(Y/n)${reset} `;
|
|
15
30
|
const answer = await prompt.question(question);
|
|
16
31
|
if (/^(?:|y|yes)$/i.test(answer.trim())) return "remote";
|
|
17
32
|
output.write(`\n${localOnlyAnalysisText}\n\n`);
|
package/server/privacy.mjs
CHANGED
|
@@ -108,7 +108,7 @@ export function redactText(input, manualTerms = [], { disabledKinds = [], disabl
|
|
|
108
108
|
if (!labeledCredentialValue(match)) return match;
|
|
109
109
|
const replacement = "[REDACTED CREDENTIAL]";
|
|
110
110
|
const enabled = isEnabled("credential", match);
|
|
111
|
-
detections.push(detectionDetails({ kind: "credential", label: "
|
|
111
|
+
detections.push(detectionDetails({ kind: "credential", label: "API keys and secrets", value: match, replacement, offset, source, enabled }));
|
|
112
112
|
return enabled ? replacement : protect(match);
|
|
113
113
|
});
|
|
114
114
|
for (const [pattern, replacement] of [...SECRET_PATTERNS, ...(includeHeuristicSecrets ? HEURISTIC_SECRET_PATTERNS : []), ...PII_PATTERNS]) {
|
|
@@ -117,7 +117,9 @@ export function redactText(input, manualTerms = [], { disabledKinds = [], disabl
|
|
|
117
117
|
const enabled = isEnabled(kind, match);
|
|
118
118
|
detections.push(detectionDetails({
|
|
119
119
|
kind,
|
|
120
|
-
label:
|
|
120
|
+
label: /(?:SECRET|KEY|TOKEN|CREDENTIAL|HIGH-ENTROPY)/.test(replacement)
|
|
121
|
+
? "API keys and secrets"
|
|
122
|
+
: replacement.slice(1, -1).toLowerCase().replace(/\b\w/g, (letter) => letter.toUpperCase()),
|
|
121
123
|
value: match,
|
|
122
124
|
replacement,
|
|
123
125
|
offset,
|
|
@@ -38,7 +38,7 @@ function safeStockPhrases(value) {
|
|
|
38
38
|
|
|
39
39
|
function safeRepeatedInstructions(value) {
|
|
40
40
|
if (!Array.isArray(value)) return [];
|
|
41
|
-
return value.slice(0,
|
|
41
|
+
return value.slice(0, 2).flatMap((item) => {
|
|
42
42
|
const instruction = safeText(item?.instruction, 160).trim();
|
|
43
43
|
const occurrences = Math.round(safeNumber(item?.occurrences, 1_000_000));
|
|
44
44
|
const distinctSessions = Math.round(safeNumber(item?.distinctSessions, 1_000_000));
|