@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.
- package/CHANGELOG.md +228 -0
- package/Dockerfile.team +20 -0
- package/README.md +30 -11
- package/bin/tokenflow.js +147 -12
- package/design/tokens.yaml +330 -0
- package/docs/architecture.md +5 -4
- package/docs/cli.md +204 -0
- package/docs/configuration.md +117 -2
- package/docs/design-system.md +187 -0
- package/docs/exports-and-budgets.md +85 -0
- package/docs/guard-codex.md +132 -0
- package/docs/ledger.md +144 -0
- package/docs/live-mode.md +40 -0
- package/docs/media/overview-aurora-dark.png +0 -0
- package/docs/media/receipts-aurora-dark.png +0 -0
- package/docs/providers-otel.md +179 -0
- package/docs/providers.md +54 -1
- package/docs/receipt-schema.md +74 -0
- package/docs/roadmap.md +182 -0
- package/docs/team-server.md +170 -0
- package/docs/ui-views.md +322 -0
- package/package.json +7 -2
- package/schemas/receipt.v0.json +160 -0
- package/scripts/build-dmg.sh +11 -2
- package/scripts/build-menubar-app.sh +58 -7
- package/scripts/design-build.js +475 -0
- package/src/analytics/anatomy.js +467 -0
- package/src/analytics/branch-compare.js +159 -0
- package/src/analytics/cache-health.js +141 -0
- package/src/analytics/live-view.js +266 -0
- package/src/analytics/receipt-schema.js +214 -0
- package/src/analytics/receipt.js +709 -0
- package/src/analytics/rhythm.js +184 -0
- package/src/analytics/whatif.js +263 -0
- package/src/commands/budget-scopes.js +133 -0
- package/src/commands/doctor-checks.js +400 -0
- package/src/commands/guard.js +531 -0
- package/src/commands/hooks.js +238 -0
- package/src/commands/pricing-diff.js +316 -0
- package/src/commands/receipt.js +226 -0
- package/src/commands/team-serve.js +407 -0
- package/src/commands/week.js +86 -0
- package/src/core/annotations.js +97 -0
- package/src/core/budget.js +33 -0
- package/src/core/bundle.js +45 -2
- package/src/core/ingest.js +33 -0
- package/src/core/live-status.js +227 -2
- package/src/core/policy.js +103 -0
- package/src/core/receipt-note.js +123 -0
- package/src/core/repo.js +64 -0
- package/src/core/sync.js +163 -26
- package/src/core/team.js +0 -0
- package/src/export/html-snapshot.js +28 -1
- package/src/export/menubar.js +21 -0
- package/src/export/receipt-card.js +210 -0
- package/src/export/week-card.js +185 -0
- package/src/providers/mock/index.js +383 -52
- package/src/providers/openai/index.js +31 -1
- package/src/providers/otel/index.js +656 -0
- package/src/server/routes/annotations.js +42 -0
- package/src/server/routes/cache-health.js +95 -0
- package/src/server/routes/index.js +54 -0
- package/src/server/routes/session.js +157 -0
- package/src/server/server.js +47 -1
- package/src/ui/app.js +541 -308
- package/src/ui/charts.js +95 -0
- package/src/ui/first-run.js +144 -0
- package/src/ui/index.html +4 -1
- package/src/ui/palette.js +335 -0
- package/src/ui/styles/anatomy.css +117 -0
- package/src/ui/styles/annotations.css +40 -0
- package/src/ui/styles/branches.css +99 -0
- package/src/ui/styles/cache.css +6 -0
- package/src/ui/styles/first-run.css +31 -0
- package/src/ui/styles/live.css +100 -0
- package/src/ui/styles/palette.css +85 -0
- package/src/ui/styles/rhythm.css +8 -0
- package/src/ui/styles/whatif.css +55 -0
- package/src/ui/styles.css +303 -196
- package/src/ui/views/anatomy.js +567 -0
- package/src/ui/views/annotations.js +121 -0
- package/src/ui/views/branches.js +304 -0
- package/src/ui/views/cache.js +232 -0
- package/src/ui/views/index.js +85 -0
- package/src/ui/views/live.js +683 -0
- package/src/ui/views/rhythm.js +206 -0
- 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.
|
|
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
|
+
}
|
package/scripts/build-dmg.sh
CHANGED
|
@@ -15,8 +15,17 @@ DIST="$REPO/dist"
|
|
|
15
15
|
APP="$DIST/TokenFlow.app"
|
|
16
16
|
DMG="$DIST/TokenFlow-$VERSION.dmg"
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
8
|
-
#
|
|
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 -
|
|
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')"
|