behavior-wrapped 0.8.9 → 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/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-CD48EtI2.js"></script>
10
- <link rel="stylesheet" crossorigin href="/assets/index-RUb7reCR.css">
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "behavior-wrapped",
3
- "version": "0.8.9",
3
+ "version": "0.8.10",
4
4
  "description": "A private, local-first Wrapped report for Claude Code, Cowork, and Codex behavior.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -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(/\b(?:[A-Za-z]:)?[/.~][^\s]+/g, " ")
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, 4)
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 && text) {
467
+ if (record.type === "user" && !record.isMeta && userProse) {
466
468
  finishResponse();
467
469
  hasCurrentPrompt = true;
468
- userInputWords += wordCount(text);
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 && text) {
481
- currentResponseWords += wordCount(text);
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.",
@@ -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: "Credential", value: match, replacement, offset, source, enabled }));
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: replacement.slice(1, -1).toLowerCase().replace(/\b\w/g, (letter) => letter.toUpperCase()),
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, 4).flatMap((item) => {
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));