@vimoxshah/tokenflow 1.1.1 → 1.2.0

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 (87) hide show
  1. package/CHANGELOG.md +228 -0
  2. package/Dockerfile.team +20 -0
  3. package/README.md +30 -11
  4. package/bin/tokenflow.js +147 -12
  5. package/design/tokens.yaml +330 -0
  6. package/docs/architecture.md +5 -4
  7. package/docs/cli.md +204 -0
  8. package/docs/configuration.md +117 -2
  9. package/docs/design-system.md +187 -0
  10. package/docs/exports-and-budgets.md +85 -0
  11. package/docs/guard-codex.md +132 -0
  12. package/docs/ledger.md +144 -0
  13. package/docs/live-mode.md +40 -0
  14. package/docs/media/overview-aurora-dark.png +0 -0
  15. package/docs/media/receipts-aurora-dark.png +0 -0
  16. package/docs/providers-otel.md +179 -0
  17. package/docs/providers.md +54 -1
  18. package/docs/receipt-schema.md +74 -0
  19. package/docs/roadmap.md +182 -0
  20. package/docs/team-server.md +170 -0
  21. package/docs/ui-views.md +322 -0
  22. package/package.json +7 -2
  23. package/schemas/receipt.v0.json +160 -0
  24. package/scripts/build-dmg.sh +11 -2
  25. package/scripts/build-menubar-app.sh +58 -7
  26. package/scripts/design-build.js +475 -0
  27. package/src/analytics/anatomy.js +467 -0
  28. package/src/analytics/branch-compare.js +159 -0
  29. package/src/analytics/cache-health.js +141 -0
  30. package/src/analytics/live-view.js +266 -0
  31. package/src/analytics/receipt-schema.js +214 -0
  32. package/src/analytics/receipt.js +709 -0
  33. package/src/analytics/rhythm.js +184 -0
  34. package/src/analytics/whatif.js +263 -0
  35. package/src/commands/budget-scopes.js +133 -0
  36. package/src/commands/doctor-checks.js +400 -0
  37. package/src/commands/guard.js +531 -0
  38. package/src/commands/hooks.js +238 -0
  39. package/src/commands/pricing-diff.js +316 -0
  40. package/src/commands/receipt.js +226 -0
  41. package/src/commands/team-serve.js +407 -0
  42. package/src/commands/week.js +86 -0
  43. package/src/core/annotations.js +97 -0
  44. package/src/core/budget.js +33 -0
  45. package/src/core/bundle.js +45 -2
  46. package/src/core/ingest.js +33 -0
  47. package/src/core/live-status.js +227 -2
  48. package/src/core/policy.js +103 -0
  49. package/src/core/receipt-note.js +123 -0
  50. package/src/core/repo.js +64 -0
  51. package/src/core/sync.js +163 -26
  52. package/src/core/team.js +0 -0
  53. package/src/export/html-snapshot.js +28 -1
  54. package/src/export/menubar.js +21 -0
  55. package/src/export/receipt-card.js +210 -0
  56. package/src/export/week-card.js +185 -0
  57. package/src/providers/mock/index.js +383 -52
  58. package/src/providers/openai/index.js +31 -1
  59. package/src/providers/otel/index.js +656 -0
  60. package/src/server/routes/annotations.js +42 -0
  61. package/src/server/routes/cache-health.js +95 -0
  62. package/src/server/routes/index.js +54 -0
  63. package/src/server/routes/session.js +157 -0
  64. package/src/server/server.js +47 -1
  65. package/src/ui/app.js +541 -308
  66. package/src/ui/charts.js +95 -0
  67. package/src/ui/first-run.js +144 -0
  68. package/src/ui/index.html +4 -1
  69. package/src/ui/palette.js +335 -0
  70. package/src/ui/styles/anatomy.css +117 -0
  71. package/src/ui/styles/annotations.css +40 -0
  72. package/src/ui/styles/branches.css +99 -0
  73. package/src/ui/styles/cache.css +6 -0
  74. package/src/ui/styles/first-run.css +31 -0
  75. package/src/ui/styles/live.css +100 -0
  76. package/src/ui/styles/palette.css +85 -0
  77. package/src/ui/styles/rhythm.css +8 -0
  78. package/src/ui/styles/whatif.css +55 -0
  79. package/src/ui/styles.css +303 -196
  80. package/src/ui/views/anatomy.js +567 -0
  81. package/src/ui/views/annotations.js +121 -0
  82. package/src/ui/views/branches.js +304 -0
  83. package/src/ui/views/cache.js +232 -0
  84. package/src/ui/views/index.js +85 -0
  85. package/src/ui/views/live.js +683 -0
  86. package/src/ui/views/rhythm.js +206 -0
  87. package/src/ui/views/whatif.js +196 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vimoxshah/tokenflow",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "Local-first, provider-agnostic analytics for the AI tokens you actually spend. Zero dependencies, nothing leaves your machine.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -18,16 +18,19 @@
18
18
  "files": [
19
19
  "bin",
20
20
  "src",
21
+ "design",
21
22
  "docs",
22
23
  "skills",
23
24
  "examples",
24
25
  "scripts",
26
+ "schemas",
25
27
  "README.md",
26
28
  "CHANGELOG.md",
27
29
  "LICENSE",
28
30
  "SECURITY.md",
29
31
  "CONTRIBUTING.md",
30
- "Refresh & Open Dashboard.command"
32
+ "Refresh & Open Dashboard.command",
33
+ "Dockerfile.team"
31
34
  ],
32
35
  "scripts": {
33
36
  "setup": "node bin/tokenflow.js setup",
@@ -39,6 +42,8 @@
39
42
  "typecheck": "tsc --noEmit -p jsconfig.json",
40
43
  "typecheck:deps": "npm i --no-save --no-package-lock typescript@5 @types/node@22",
41
44
  "lint": "node scripts/lint.js",
45
+ "design": "node scripts/design-build.js",
46
+ "design:check": "node scripts/design-build.js --check",
42
47
  "validate": "node scripts/validate-install.js",
43
48
  "start": "node bin/tokenflow.js up",
44
49
  "up": "node bin/tokenflow.js up"
@@ -0,0 +1,160 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/vimoxshah/tokenflow/schemas/receipt.v0.json",
4
+ "title": "TokenFlow branch receipt (v0)",
5
+ "description": "A portable, self-contained record of what one branch cost — designed to travel with the code as a git note (refs/notes/tokenflow) rather than live on a server. Every cost figure is estimated locally from token counts against a local price table, never measured billing, unless the underlying records say otherwise; see docs/receipt-schema.md.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": [
9
+ "schemaVersion",
10
+ "generatedAt",
11
+ "toolVersion",
12
+ "repo",
13
+ "branch",
14
+ "headSha",
15
+ "window",
16
+ "costUsd",
17
+ "contextShare",
18
+ "turns",
19
+ "sessions",
20
+ "subagentTurns",
21
+ "models",
22
+ "coverage",
23
+ "largestPromptTokens",
24
+ "changedLines",
25
+ "pr",
26
+ "longLived",
27
+ "costPer100Lines",
28
+ "notes"
29
+ ],
30
+ "properties": {
31
+ "schemaVersion": {
32
+ "const": 0,
33
+ "description": "Receipt schema version. This document describes version 0."
34
+ },
35
+ "generatedAt": {
36
+ "type": "string",
37
+ "format": "date-time",
38
+ "description": "When this receipt was computed, in UTC ISO-8601."
39
+ },
40
+ "toolVersion": {
41
+ "type": "string",
42
+ "description": "The TokenFlow package version that computed this receipt (package.json version)."
43
+ },
44
+ "repo": {
45
+ "type": "string",
46
+ "minLength": 1,
47
+ "description": "The repository's name — the basename of its main checkout, resolved through git worktrees. Never a full path."
48
+ },
49
+ "branch": {
50
+ "type": "string",
51
+ "minLength": 1,
52
+ "description": "The branch this receipt attributes spend to."
53
+ },
54
+ "headSha": {
55
+ "type": "string",
56
+ "pattern": "^[0-9a-f]{7,40}$",
57
+ "description": "The commit sha this receipt is attached to (the local sha being pushed)."
58
+ },
59
+ "window": {
60
+ "description": "The first and last timestamp of a priced turn attributed to this branch, or null when there were none.",
61
+ "oneOf": [
62
+ { "type": "null" },
63
+ {
64
+ "type": "object",
65
+ "additionalProperties": false,
66
+ "required": ["first", "last"],
67
+ "properties": {
68
+ "first": { "type": ["string", "null"], "format": "date-time" },
69
+ "last": { "type": ["string", "null"], "format": "date-time" }
70
+ }
71
+ }
72
+ ]
73
+ },
74
+ "costUsd": {
75
+ "type": ["number", "null"],
76
+ "minimum": 0,
77
+ "description": "Estimated spend attributed to this branch, in US dollars. null when no turn on this branch had a priced model — never 0."
78
+ },
79
+ "contextShare": {
80
+ "type": ["number", "null"],
81
+ "minimum": 0,
82
+ "maximum": 1,
83
+ "description": "Share of costUsd that paid to re-send prior context (cache reads + writes) rather than fresh input/output. A high share is the price of a long session, not necessarily waste."
84
+ },
85
+ "turns": {
86
+ "type": "integer",
87
+ "minimum": 0,
88
+ "description": "Number of primary-measurement turns attributed to this branch."
89
+ },
90
+ "sessions": {
91
+ "type": "integer",
92
+ "minimum": 0,
93
+ "description": "Number of distinct sessions that touched this branch."
94
+ },
95
+ "subagentTurns": {
96
+ "type": "integer",
97
+ "minimum": 0,
98
+ "description": "Of turns, how many ran as a subagent."
99
+ },
100
+ "models": {
101
+ "type": "array",
102
+ "description": "Spend broken down by model family, most expensive first.",
103
+ "items": {
104
+ "type": "object",
105
+ "additionalProperties": false,
106
+ "required": ["model", "costUsd", "share"],
107
+ "properties": {
108
+ "model": { "type": "string" },
109
+ "costUsd": { "type": ["number", "null"], "minimum": 0 },
110
+ "share": { "type": ["number", "null"], "minimum": 0, "maximum": 1 }
111
+ }
112
+ }
113
+ },
114
+ "coverage": {
115
+ "type": ["number", "null"],
116
+ "minimum": 0,
117
+ "maximum": 1,
118
+ "description": "Share of this branch's turns that used a model with a configured price. Turns outside this coverage are counted but excluded from costUsd."
119
+ },
120
+ "largestPromptTokens": {
121
+ "type": ["integer", "null"],
122
+ "minimum": 0,
123
+ "description": "The largest single prompt (input + cache read + cache write tokens) seen on this branch."
124
+ },
125
+ "changedLines": {
126
+ "type": ["integer", "null"],
127
+ "minimum": 0,
128
+ "description": "Additions + deletions of the matched pull request, when one was supplied. null with no matched PR."
129
+ },
130
+ "pr": {
131
+ "description": "The pull request this receipt's headline window is scoped to (turns up to its merge), or null when none was supplied/matched.",
132
+ "oneOf": [
133
+ { "type": "null" },
134
+ {
135
+ "type": "object",
136
+ "additionalProperties": false,
137
+ "required": ["number", "mergedAt"],
138
+ "properties": {
139
+ "number": { "type": "integer", "minimum": 1 },
140
+ "mergedAt": { "type": ["string", "null"], "format": "date-time" }
141
+ }
142
+ }
143
+ ]
144
+ },
145
+ "longLived": {
146
+ "type": "boolean",
147
+ "description": "True for a long-lived branch name (main, staging, …): this receipt is for a period of work on it, not one change."
148
+ },
149
+ "costPer100Lines": {
150
+ "type": ["number", "null"],
151
+ "minimum": 0,
152
+ "description": "costUsd divided by changedLines, scaled to 100 lines. null with no matched PR, no changed lines, or no priced turns."
153
+ },
154
+ "notes": {
155
+ "type": "array",
156
+ "description": "Honesty caveats in plain sentences: e.g. unpriced turns excluded from costUsd, a long-lived branch covering a period, work that predates the matched PR, that costs are estimated locally and no prompt or code content was read.",
157
+ "items": { "type": "string" }
158
+ }
159
+ }
160
+ }
@@ -15,8 +15,17 @@ DIST="$REPO/dist"
15
15
  APP="$DIST/TokenFlow.app"
16
16
  DMG="$DIST/TokenFlow-$VERSION.dmg"
17
17
 
18
- "$REPO/scripts/build-menubar-app.sh" "$DIST" "$VERSION" >/dev/null
19
- echo "built TokenFlow.app"
18
+ # A DMG is for other people's machines, so it must carry no path from this one.
19
+ TOKENFLOW_PORTABLE=1 "$REPO/scripts/build-menubar-app.sh" "$DIST" "$VERSION" >/dev/null
20
+ echo "built TokenFlow.app (portable)"
21
+
22
+ # Fail here rather than shipping an app that points at the build host.
23
+ for KEY in TokenFlowCLIPath TokenFlowNodePath; do
24
+ if /usr/libexec/PlistBuddy -c "Print :$KEY" "$APP/Contents/Info.plist" >/dev/null 2>&1; then
25
+ echo "error: $KEY is embedded in a distributable build" >&2
26
+ exit 1
27
+ fi
28
+ done
20
29
 
21
30
  STAGING="$(mktemp -d)"
22
31
  trap 'rm -rf "$STAGING"' EXIT
@@ -4,8 +4,24 @@
4
4
  # scripts/build-menubar-app.sh [output-dir]
5
5
  #
6
6
  # Compiles menubar/TokenFlow/main.swift with swiftc (Xcode Command Line Tools)
7
- # into a minimal .app bundle, embedding the absolute paths of this clone's
8
- # node binary and CLI so the app can drive refresh/watch actions.
7
+ # into a minimal .app bundle.
8
+ #
9
+ # The CLI is ALWAYS bundled into Contents/Resources/cli, packed with `npm pack`
10
+ # so the copy inside the app is byte-for-byte the published package rather than
11
+ # a hand-picked subset of the working tree. The app drives that copy, which is
12
+ # the only one guaranteed to match the binary it ships beside: the app and the
13
+ # CLI share a contract (the status file, the watcher lock format, /api/ping),
14
+ # and an unrelated CLI version next door is a mismatch nobody can reason about.
15
+ #
16
+ # Two build flavours:
17
+ #
18
+ # local (default) also embeds this clone's absolute node + CLI paths, so
19
+ # a developer's installed app drives the checkout they
20
+ # are editing.
21
+ # TOKENFLOW_PORTABLE=1 embeds NO absolute paths. Anything built for
22
+ # distribution must use this: a release built on CI
23
+ # otherwise ships /Users/runner/... in its Info.plist,
24
+ # which exists on no user's machine.
9
25
  set -euo pipefail
10
26
 
11
27
  REPO="$(cd "$(dirname "$0")/.." && pwd)"
@@ -25,14 +41,40 @@ command -v swiftc >/dev/null 2>&1 || {
25
41
  NODE_BIN="$(command -v node)"
26
42
  CLI_JS="$REPO/bin/tokenflow.js"
27
43
  [ -f "$CLI_JS" ] || { echo "error: $CLI_JS missing" >&2; exit 1; }
44
+ PORTABLE="${TOKENFLOW_PORTABLE:-0}"
28
45
 
29
46
  TMP="$(mktemp -d)"
30
47
  trap 'rm -rf "$TMP"' EXIT
31
48
 
49
+ rm -rf "$APP"
32
50
  mkdir -p "$APP/Contents/MacOS"
33
51
  mkdir -p "$APP/Contents/Resources"
34
52
  cp "$REPO/menubar/TokenFlow/AppIcon.icns" "$APP/Contents/Resources/AppIcon.icns"
35
53
 
54
+ # ---- bundle the CLI ---------------------------------------------------------
55
+ # `npm pack` rather than copying bin/ and src/: the tarball is what npm
56
+ # publishes, filtered by package.json "files", so the app can never ship a file
57
+ # the package does not.
58
+ echo "packing the CLI into the bundle"
59
+ ( cd "$REPO" && npm pack --silent --pack-destination "$TMP" >/dev/null )
60
+ TGZ="$(ls "$TMP"/*.tgz | head -1)"
61
+ [ -f "$TGZ" ] || { echo "error: npm pack produced no tarball" >&2; exit 1; }
62
+ mkdir -p "$APP/Contents/Resources/cli"
63
+ tar -xzf "$TGZ" -C "$APP/Contents/Resources/cli"
64
+ BUNDLED_CLI="$APP/Contents/Resources/cli/package/bin/tokenflow.js"
65
+ [ -f "$BUNDLED_CLI" ] || { echo "error: bundled CLI missing at $BUNDLED_CLI" >&2; exit 1; }
66
+
67
+ # Keep only what the CLI actually executes. docs/, skills/, examples/ and
68
+ # scripts/ are never read at runtime — they appear in printed hints and nothing
69
+ # opens them — and they are four fifths of the tarball. Whatever remains still
70
+ # came from `npm pack`, so the bundle is a subset of the published package and
71
+ # never a file npm does not ship.
72
+ ( cd "$APP/Contents/Resources/cli/package" \
73
+ && rm -rf docs skills examples scripts \
74
+ README.md CONTRIBUTING.md SECURITY.md CHANGELOG.md "Refresh & Open Dashboard.command" )
75
+ [ -f "$BUNDLED_CLI" ] || { echo "error: pruning removed the CLI" >&2; exit 1; }
76
+ [ -d "$APP/Contents/Resources/cli/package/src" ] || { echo "error: pruning removed src/" >&2; exit 1; }
77
+
36
78
  cat > "$APP/Contents/Info.plist" <<PLIST
37
79
  <?xml version="1.0" encoding="UTF-8"?>
38
80
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -50,18 +92,27 @@ cat > "$APP/Contents/Info.plist" <<PLIST
50
92
  <key>LSUIElement</key> <true/>
51
93
  <key>NSHighResolutionCapable</key> <true/>
52
94
  <key>NSHumanReadableCopyright</key> <string>MIT — local-first, nothing leaves your machine.</string>
53
- <key>TokenFlowNodePath</key> <string>$NODE_BIN</string>
54
- <key>TokenFlowCLIPath</key> <string>$CLI_JS</string>
55
95
  </dict>
56
96
  </plist>
57
97
  PLIST
58
98
 
99
+ # A distributable build embeds no machine-specific path. A local one does, so
100
+ # the developer's installed app drives the clone they are working in.
101
+ if [ "$PORTABLE" != "1" ]; then
102
+ /usr/libexec/PlistBuddy \
103
+ -c "Add :TokenFlowNodePath string $NODE_BIN" \
104
+ -c "Add :TokenFlowCLIPath string $CLI_JS" \
105
+ "$APP/Contents/Info.plist" >/dev/null
106
+ fi
107
+
59
108
  echo "compiling with $(swiftc --version | head -1)"
109
+ # DesignTokens.swift is generated from design/tokens.yaml (`npm run design`);
110
+ # the app is compiled from both files so it cannot drift from the dashboard.
60
111
  swiftc -O -swift-version 5 \
61
112
  -o "$APP/Contents/MacOS/TokenFlow" \
62
- "$SRC" 2>&1 | head -40
113
+ "$SRC" "$REPO/menubar/TokenFlow/DesignTokens.swift" 2>&1 | head -40
63
114
 
64
115
  codesign --force --sign - "$APP" >/dev/null 2>&1 || true
65
116
 
66
- SIZE=$(du -h "$APP" | cut -f1 | tr -d ' ')
67
- echo "built: $APP ($SIZE)"
117
+ SIZE=$(du -sh "$APP" | cut -f1 | tr -d ' ')
118
+ echo "built: $APP ($SIZE)$([ "$PORTABLE" = "1" ] && echo ' · portable, no embedded paths')"