@axplusb/kepler 0.0.1 → 1.0.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/README.md +82 -0
- package/package.json +36 -4
- package/pulse/app/activity/page.tsx +190 -0
- package/pulse/app/api/activity/route.ts +138 -0
- package/pulse/app/api/costs/route.ts +88 -0
- package/pulse/app/api/export/route.ts +77 -0
- package/pulse/app/api/history/route.ts +11 -0
- package/pulse/app/api/import/route.ts +31 -0
- package/pulse/app/api/memory/route.ts +52 -0
- package/pulse/app/api/plans/route.ts +9 -0
- package/pulse/app/api/projects/[slug]/route.ts +96 -0
- package/pulse/app/api/projects/route.ts +121 -0
- package/pulse/app/api/sessions/[id]/replay/route.ts +20 -0
- package/pulse/app/api/sessions/[id]/route.ts +31 -0
- package/pulse/app/api/sessions/route.ts +112 -0
- package/pulse/app/api/settings/route.ts +14 -0
- package/pulse/app/api/stats/route.ts +143 -0
- package/pulse/app/api/todos/route.ts +9 -0
- package/pulse/app/api/tools/route.ts +160 -0
- package/pulse/app/costs/page.tsx +179 -0
- package/pulse/app/export/page.tsx +465 -0
- package/pulse/app/favicon.ico +0 -0
- package/pulse/app/globals.css +263 -0
- package/pulse/app/help/page.tsx +142 -0
- package/pulse/app/history/page.tsx +157 -0
- package/pulse/app/layout.tsx +46 -0
- package/pulse/app/memory/page.tsx +365 -0
- package/pulse/app/overview-client.tsx +393 -0
- package/pulse/app/page.tsx +14 -0
- package/pulse/app/plans/page.tsx +308 -0
- package/pulse/app/projects/[slug]/page.tsx +390 -0
- package/pulse/app/projects/page.tsx +110 -0
- package/pulse/app/sessions/[id]/page.tsx +243 -0
- package/pulse/app/sessions/page.tsx +39 -0
- package/pulse/app/settings/page.tsx +188 -0
- package/pulse/app/todos/page.tsx +211 -0
- package/pulse/app/tools/page.tsx +249 -0
- package/pulse/cli.js +159 -0
- package/pulse/components/activity/day-of-week-chart.tsx +35 -0
- package/pulse/components/activity/streak-card.tsx +36 -0
- package/pulse/components/costs/cache-efficiency-panel.tsx +76 -0
- package/pulse/components/costs/cost-by-project-chart.tsx +48 -0
- package/pulse/components/costs/cost-over-time-chart.tsx +95 -0
- package/pulse/components/costs/model-token-table.tsx +60 -0
- package/pulse/components/global-search.tsx +193 -0
- package/pulse/components/keyboard-nav-provider.tsx +23 -0
- package/pulse/components/layout/bottom-nav.tsx +52 -0
- package/pulse/components/layout/client-layout.tsx +31 -0
- package/pulse/components/layout/sidebar-context.tsx +50 -0
- package/pulse/components/layout/sidebar.tsx +182 -0
- package/pulse/components/layout/top-bar.tsx +121 -0
- package/pulse/components/overview/activity-heatmap.tsx +107 -0
- package/pulse/components/overview/conversation-table.tsx +148 -0
- package/pulse/components/overview/model-breakdown-donut.tsx +95 -0
- package/pulse/components/overview/peak-hours-chart.tsx +87 -0
- package/pulse/components/overview/project-activity-donut.tsx +96 -0
- package/pulse/components/overview/stat-card.tsx +102 -0
- package/pulse/components/overview/usage-over-time-chart.tsx +166 -0
- package/pulse/components/projects/project-card.tsx +175 -0
- package/pulse/components/sessions/replay/assistant-markdown.tsx +94 -0
- package/pulse/components/sessions/replay/compaction-card.tsx +25 -0
- package/pulse/components/sessions/replay/session-sidebar.tsx +231 -0
- package/pulse/components/sessions/replay/token-accumulation-chart.tsx +98 -0
- package/pulse/components/sessions/replay/tool-call-badge.tsx +127 -0
- package/pulse/components/sessions/replay/turn-cards.tsx +220 -0
- package/pulse/components/sessions/replay/user-tool-result.tsx +158 -0
- package/pulse/components/sessions/session-badges.tsx +49 -0
- package/pulse/components/sessions/session-table.tsx +299 -0
- package/pulse/components/theme-provider.tsx +44 -0
- package/pulse/components/tools/feature-adoption-table.tsx +58 -0
- package/pulse/components/tools/mcp-server-panel.tsx +45 -0
- package/pulse/components/tools/tool-ranking-chart.tsx +57 -0
- package/pulse/components/tools/version-history-table.tsx +32 -0
- package/pulse/components/ui/alert.tsx +66 -0
- package/pulse/components/ui/badge.tsx +48 -0
- package/pulse/components/ui/breadcrumb.tsx +109 -0
- package/pulse/components/ui/button.tsx +64 -0
- package/pulse/components/ui/calendar.tsx +220 -0
- package/pulse/components/ui/card.tsx +92 -0
- package/pulse/components/ui/command.tsx +158 -0
- package/pulse/components/ui/dialog.tsx +158 -0
- package/pulse/components/ui/input.tsx +21 -0
- package/pulse/components/ui/popover.tsx +89 -0
- package/pulse/components/ui/progress.tsx +31 -0
- package/pulse/components/ui/select.tsx +190 -0
- package/pulse/components/ui/separator.tsx +28 -0
- package/pulse/components/ui/sheet.tsx +143 -0
- package/pulse/components/ui/skeleton.tsx +13 -0
- package/pulse/components/ui/table.tsx +116 -0
- package/pulse/components/ui/tabs.tsx +91 -0
- package/pulse/components/ui/tooltip.tsx +57 -0
- package/pulse/components/use-global-keyboard-nav.ts +79 -0
- package/pulse/components.json +23 -0
- package/pulse/eslint.config.mjs +18 -0
- package/pulse/lib/claude-reader.ts +594 -0
- package/pulse/lib/decode.ts +129 -0
- package/pulse/lib/pricing.ts +102 -0
- package/pulse/lib/replay-parser.ts +165 -0
- package/pulse/lib/tool-categories.ts +127 -0
- package/pulse/lib/utils.ts +6 -0
- package/pulse/next-env.d.ts +6 -0
- package/pulse/next.config.ts +16 -0
- package/pulse/package.json +45 -0
- package/pulse/postcss.config.mjs +7 -0
- package/pulse/public/activity.png +0 -0
- package/pulse/public/cc-lens.png +0 -0
- package/pulse/public/command-k.png +0 -0
- package/pulse/public/costs.png +0 -0
- package/pulse/public/dashboard-dark.png +0 -0
- package/pulse/public/dashboard-white.png +0 -0
- package/pulse/public/export.png +0 -0
- package/pulse/public/file.svg +1 -0
- package/pulse/public/globe.svg +1 -0
- package/pulse/public/next.svg +1 -0
- package/pulse/public/projects.png +0 -0
- package/pulse/public/session-chat.png +0 -0
- package/pulse/public/todos.png +0 -0
- package/pulse/public/tools.png +0 -0
- package/pulse/public/vercel.svg +1 -0
- package/pulse/public/window.svg +1 -0
- package/pulse/tsconfig.json +34 -0
- package/pulse/types/claude.ts +294 -0
- package/src/agents/loader.mjs +89 -0
- package/src/agents/parser.mjs +98 -0
- package/src/agents/teams.mjs +123 -0
- package/src/auth/oauth.mjs +220 -0
- package/src/auth/tarang-auth.mjs +277 -0
- package/src/config/cli-args.mjs +173 -0
- package/src/config/env.mjs +263 -0
- package/src/config/settings.mjs +132 -0
- package/src/context/ast-parser.mjs +298 -0
- package/src/context/bm25.mjs +85 -0
- package/src/context/retriever.mjs +270 -0
- package/src/context/skeleton.mjs +134 -0
- package/src/core/agent-loop.mjs +480 -0
- package/src/core/approval.mjs +273 -0
- package/src/core/backend-url.mjs +57 -0
- package/src/core/cache.mjs +105 -0
- package/src/core/callback-client.mjs +149 -0
- package/src/core/checkpoints.mjs +142 -0
- package/src/core/context-manager.mjs +198 -0
- package/src/core/headless.mjs +168 -0
- package/src/core/hooks-manager.mjs +87 -0
- package/src/core/jsonl-writer.mjs +351 -0
- package/src/core/local-agent.mjs +429 -0
- package/src/core/local-store.mjs +325 -0
- package/src/core/mode-selector.mjs +51 -0
- package/src/core/output-filter.mjs +177 -0
- package/src/core/paths.mjs +98 -0
- package/src/core/pricing.mjs +314 -0
- package/src/core/providers.mjs +219 -0
- package/src/core/rate-limiter.mjs +119 -0
- package/src/core/safety.mjs +200 -0
- package/src/core/scheduler.mjs +173 -0
- package/src/core/session-manager.mjs +317 -0
- package/src/core/session.mjs +143 -0
- package/src/core/settings-sync.mjs +85 -0
- package/src/core/stagnation.mjs +57 -0
- package/src/core/stream-client.mjs +367 -0
- package/src/core/streaming.mjs +182 -0
- package/src/core/system-prompt.mjs +135 -0
- package/src/core/tool-executor.mjs +725 -0
- package/src/hooks/engine.mjs +162 -0
- package/src/index.mjs +370 -0
- package/src/mcp/client.mjs +253 -0
- package/src/mcp/transport-shttp.mjs +130 -0
- package/src/mcp/transport-sse.mjs +131 -0
- package/src/mcp/transport-ws.mjs +134 -0
- package/src/permissions/checker.mjs +57 -0
- package/src/permissions/command-classifier.mjs +573 -0
- package/src/permissions/injection-check.mjs +60 -0
- package/src/permissions/path-check.mjs +102 -0
- package/src/permissions/prompt.mjs +73 -0
- package/src/permissions/sandbox.mjs +112 -0
- package/src/plugins/loader.mjs +138 -0
- package/src/skills/loader.mjs +147 -0
- package/src/skills/runner.mjs +55 -0
- package/src/telemetry/index.mjs +96 -0
- package/src/terminal/agents.mjs +177 -0
- package/src/terminal/analytics.mjs +292 -0
- package/src/terminal/ansi.mjs +421 -0
- package/src/terminal/main.mjs +150 -0
- package/src/terminal/repl.mjs +1484 -0
- package/src/terminal/tool-display.mjs +58 -0
- package/src/tools/agent.mjs +137 -0
- package/src/tools/ask-user.mjs +61 -0
- package/src/tools/bash.mjs +148 -0
- package/src/tools/cron-create.mjs +120 -0
- package/src/tools/cron-delete.mjs +49 -0
- package/src/tools/cron-list.mjs +37 -0
- package/src/tools/edit.mjs +82 -0
- package/src/tools/enter-worktree.mjs +69 -0
- package/src/tools/exit-worktree.mjs +57 -0
- package/src/tools/glob.mjs +117 -0
- package/src/tools/grep.mjs +129 -0
- package/src/tools/lint.mjs +71 -0
- package/src/tools/ls.mjs +58 -0
- package/src/tools/lsp.mjs +115 -0
- package/src/tools/multi-edit.mjs +94 -0
- package/src/tools/notebook-edit.mjs +96 -0
- package/src/tools/read-mcp-resource.mjs +57 -0
- package/src/tools/read.mjs +138 -0
- package/src/tools/registry.mjs +132 -0
- package/src/tools/remote-trigger.mjs +84 -0
- package/src/tools/send-message.mjs +64 -0
- package/src/tools/skill.mjs +52 -0
- package/src/tools/test-runner.mjs +49 -0
- package/src/tools/todo-write.mjs +68 -0
- package/src/tools/tool-search.mjs +77 -0
- package/src/tools/web-fetch.mjs +65 -0
- package/src/tools/web-search.mjs +89 -0
- package/src/tools/write.mjs +55 -0
- package/src/ui/banner.mjs +237 -0
- package/src/ui/commands.mjs +499 -0
- package/src/ui/formatter.mjs +379 -0
- package/src/ui/markdown.mjs +278 -0
- package/src/ui/slash-commands.mjs +258 -0
- package/index.js +0 -1
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// ─── Slug / Path helpers ─────────────────────────────────────────────────────
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Decode a project directory slug back to a filesystem path.
|
|
5
|
+
* e.g. "-Users-foo-bar-myproject" → "/Users/foo/bar/myproject"
|
|
6
|
+
*/
|
|
7
|
+
export function slugToPath(slug: string): string {
|
|
8
|
+
// Slugs start with a leading dash representing the root /
|
|
9
|
+
return slug.replace(/-/g, '/')
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Encode a filesystem path to the slug format used by Orca.
|
|
14
|
+
*/
|
|
15
|
+
export function pathToSlug(path: string): string {
|
|
16
|
+
return path.replace(/\//g, '-')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get a human-readable display name from a project path.
|
|
21
|
+
* "/Users/foo/Developer/JavaScript/studio1" → "studio1"
|
|
22
|
+
*/
|
|
23
|
+
export function projectDisplayName(projectPath: string): string {
|
|
24
|
+
if (!projectPath) return 'Unknown'
|
|
25
|
+
const parts = projectPath.split(/[\\/]/)
|
|
26
|
+
return parts[parts.length - 1] || parts[parts.length - 2] || projectPath
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get a short display name (last 2 path segments) for longer context.
|
|
31
|
+
*/
|
|
32
|
+
export function projectShortPath(projectPath: string): string {
|
|
33
|
+
if (!projectPath) return 'Unknown'
|
|
34
|
+
const parts = projectPath.split(/[\\/]/).filter(Boolean)
|
|
35
|
+
if (parts.length <= 2) return projectPath
|
|
36
|
+
return `.../${parts.slice(-2).join('/')}`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ─── Number formatters ───────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
export function formatTokens(n: number): string {
|
|
42
|
+
if (n >= 1_000_000_000) return `${(n / 1_000_000_000).toFixed(1)}B`
|
|
43
|
+
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`
|
|
44
|
+
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`
|
|
45
|
+
return String(n)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function formatTokensExact(n: number): string {
|
|
49
|
+
return n.toLocaleString()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function formatCost(usd: number): string {
|
|
53
|
+
if (usd === 0) return '$0.00'
|
|
54
|
+
if (usd < 0.01) return `$${usd.toFixed(4)}`
|
|
55
|
+
if (usd < 1) return `$${usd.toFixed(3)}`
|
|
56
|
+
return `$${usd.toFixed(2)}`
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function formatBytes(bytes: number): string {
|
|
60
|
+
if (bytes >= 1_073_741_824) return `${(bytes / 1_073_741_824).toFixed(2)} GB`
|
|
61
|
+
if (bytes >= 1_048_576) return `${(bytes / 1_048_576).toFixed(2)} MB`
|
|
62
|
+
if (bytes >= 1_024) return `${(bytes / 1_024).toFixed(1)} KB`
|
|
63
|
+
return `${bytes} B`
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ─── Duration formatters ─────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
export function formatDuration(minutes: number): string {
|
|
69
|
+
if (minutes < 1) return '<1m'
|
|
70
|
+
if (minutes < 60) return `${Math.round(minutes)}m`
|
|
71
|
+
const h = Math.floor(minutes / 60)
|
|
72
|
+
const m = Math.round(minutes % 60)
|
|
73
|
+
if (m === 0) return `${h}h`
|
|
74
|
+
return `${h}h ${m}m`
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function formatDurationMs(ms: number): string {
|
|
78
|
+
if (ms < 1000) return `${ms}ms`
|
|
79
|
+
if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`
|
|
80
|
+
return formatDuration(ms / 60_000)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ─── Date formatters ─────────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
export function formatRelativeDate(dateStr: string): string {
|
|
86
|
+
const date = new Date(dateStr)
|
|
87
|
+
const now = new Date()
|
|
88
|
+
const diffMs = now.getTime() - date.getTime()
|
|
89
|
+
const diffDays = Math.floor(diffMs / 86_400_000)
|
|
90
|
+
const diffHours = Math.floor(diffMs / 3_600_000)
|
|
91
|
+
const diffMinutes = Math.floor(diffMs / 60_000)
|
|
92
|
+
|
|
93
|
+
if (diffMinutes < 1) return 'just now'
|
|
94
|
+
if (diffMinutes < 60) return `${diffMinutes}m ago`
|
|
95
|
+
if (diffHours < 24) return `${diffHours}h ago`
|
|
96
|
+
if (diffDays === 1) return '1 day ago'
|
|
97
|
+
if (diffDays < 7) return `${diffDays} days ago`
|
|
98
|
+
if (diffDays < 30) return `${Math.floor(diffDays / 7)}w ago`
|
|
99
|
+
if (diffDays < 365) return `${Math.floor(diffDays / 30)}mo ago`
|
|
100
|
+
return `${Math.floor(diffDays / 365)}y ago`
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function formatDate(dateStr: string): string {
|
|
104
|
+
return new Date(dateStr).toLocaleDateString('en-US', {
|
|
105
|
+
month: 'short',
|
|
106
|
+
day: 'numeric',
|
|
107
|
+
year: 'numeric',
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function formatDateTime(dateStr: string): string {
|
|
112
|
+
return new Date(dateStr).toLocaleString('en-US', {
|
|
113
|
+
month: 'short',
|
|
114
|
+
day: 'numeric',
|
|
115
|
+
hour: '2-digit',
|
|
116
|
+
minute: '2-digit',
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function formatTimestamp(ts: number): string {
|
|
121
|
+
return formatDateTime(new Date(ts).toISOString())
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ─── Percentage ──────────────────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
export function formatPct(value: number, total: number): string {
|
|
127
|
+
if (total === 0) return '0%'
|
|
128
|
+
return `${((value / total) * 100).toFixed(1)}%`
|
|
129
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { TurnUsage, ModelUsage } from '@/types/claude'
|
|
2
|
+
|
|
3
|
+
interface ModelPricing {
|
|
4
|
+
input: number
|
|
5
|
+
output: number
|
|
6
|
+
cacheWrite: number
|
|
7
|
+
cacheRead: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const PRICING: Record<string, ModelPricing> = {
|
|
11
|
+
'claude-opus-4-6': {
|
|
12
|
+
input: 15.00 / 1_000_000,
|
|
13
|
+
output: 75.00 / 1_000_000,
|
|
14
|
+
cacheWrite: 18.75 / 1_000_000,
|
|
15
|
+
cacheRead: 1.50 / 1_000_000,
|
|
16
|
+
},
|
|
17
|
+
'claude-opus-4-5-20251101': {
|
|
18
|
+
input: 15.00 / 1_000_000,
|
|
19
|
+
output: 75.00 / 1_000_000,
|
|
20
|
+
cacheWrite: 18.75 / 1_000_000,
|
|
21
|
+
cacheRead: 1.50 / 1_000_000,
|
|
22
|
+
},
|
|
23
|
+
'claude-sonnet-4-6': {
|
|
24
|
+
input: 3.00 / 1_000_000,
|
|
25
|
+
output: 15.00 / 1_000_000,
|
|
26
|
+
cacheWrite: 3.75 / 1_000_000,
|
|
27
|
+
cacheRead: 0.30 / 1_000_000,
|
|
28
|
+
},
|
|
29
|
+
'claude-haiku-4-5': {
|
|
30
|
+
input: 0.80 / 1_000_000,
|
|
31
|
+
output: 4.00 / 1_000_000,
|
|
32
|
+
cacheWrite: 1.00 / 1_000_000,
|
|
33
|
+
cacheRead: 0.08 / 1_000_000,
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getPricing(model: string): ModelPricing {
|
|
38
|
+
if (PRICING[model]) return PRICING[model]
|
|
39
|
+
// fuzzy match on prefix
|
|
40
|
+
for (const key of Object.keys(PRICING)) {
|
|
41
|
+
if (model.startsWith(key) || key.startsWith(model.split('-').slice(0, 3).join('-'))) {
|
|
42
|
+
return PRICING[key]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return PRICING['claude-opus-4-6']
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function estimateCostFromUsage(model: string, usage: TurnUsage): number {
|
|
49
|
+
const p = getPricing(model)
|
|
50
|
+
return (
|
|
51
|
+
(usage.input_tokens ?? 0) * p.input +
|
|
52
|
+
(usage.output_tokens ?? 0) * p.output +
|
|
53
|
+
(usage.cache_creation_input_tokens ?? 0) * p.cacheWrite +
|
|
54
|
+
(usage.cache_read_input_tokens ?? 0) * p.cacheRead
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function estimateCostFromSessionMeta(
|
|
59
|
+
model: string,
|
|
60
|
+
inputTokens: number,
|
|
61
|
+
outputTokens: number,
|
|
62
|
+
): number {
|
|
63
|
+
const p = getPricing(model)
|
|
64
|
+
return inputTokens * p.input + outputTokens * p.output
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface CacheEfficiencyResult {
|
|
68
|
+
savedUSD: number
|
|
69
|
+
hitRate: number
|
|
70
|
+
wouldHavePaidUSD: number
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function cacheEfficiency(
|
|
74
|
+
model: string,
|
|
75
|
+
usage: ModelUsage,
|
|
76
|
+
): CacheEfficiencyResult {
|
|
77
|
+
const p = getPricing(model)
|
|
78
|
+
const savedPerToken = p.input - p.cacheRead
|
|
79
|
+
const savedUSD = usage.cacheReadInputTokens * savedPerToken
|
|
80
|
+
const totalContext = usage.inputTokens + usage.cacheReadInputTokens
|
|
81
|
+
const hitRate = totalContext > 0
|
|
82
|
+
? usage.cacheReadInputTokens / totalContext
|
|
83
|
+
: 0
|
|
84
|
+
const wouldHavePaidUSD =
|
|
85
|
+
(usage.inputTokens + usage.cacheReadInputTokens) * p.input +
|
|
86
|
+
usage.outputTokens * p.output +
|
|
87
|
+
usage.cacheCreationInputTokens * p.cacheWrite
|
|
88
|
+
return { savedUSD, hitRate, wouldHavePaidUSD }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function estimateTotalCostFromModel(model: string, usage: ModelUsage): number {
|
|
92
|
+
const p = getPricing(model)
|
|
93
|
+
return (
|
|
94
|
+
(usage.inputTokens ?? 0) * p.input +
|
|
95
|
+
(usage.outputTokens ?? 0) * p.output +
|
|
96
|
+
(usage.cacheCreationInputTokens ?? 0) * p.cacheWrite +
|
|
97
|
+
(usage.cacheReadInputTokens ?? 0) * p.cacheRead
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export { getPricing }
|
|
102
|
+
export type { ModelPricing }
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ReplayData,
|
|
3
|
+
ReplayTurn,
|
|
4
|
+
CompactionEvent,
|
|
5
|
+
SummaryEvent,
|
|
6
|
+
TurnUsage,
|
|
7
|
+
ToolCall,
|
|
8
|
+
} from '@/types/claude'
|
|
9
|
+
import { estimateCostFromUsage } from '@/lib/pricing'
|
|
10
|
+
import { readJSONLLines } from '@/lib/claude-reader'
|
|
11
|
+
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
type AnyLine = Record<string, any>
|
|
14
|
+
|
|
15
|
+
export async function parseSessionReplay(
|
|
16
|
+
jsonlPath: string,
|
|
17
|
+
sessionId: string,
|
|
18
|
+
): Promise<ReplayData> {
|
|
19
|
+
const lines: AnyLine[] = []
|
|
20
|
+
await readJSONLLines(jsonlPath, line => lines.push(line))
|
|
21
|
+
|
|
22
|
+
const turns: ReplayTurn[] = []
|
|
23
|
+
const compactions: CompactionEvent[] = []
|
|
24
|
+
const summaries: SummaryEvent[] = []
|
|
25
|
+
|
|
26
|
+
let slug: string | undefined
|
|
27
|
+
let version: string | undefined
|
|
28
|
+
let gitBranch: string | undefined
|
|
29
|
+
let totalCost = 0
|
|
30
|
+
|
|
31
|
+
// Build a map of turn_duration events keyed by parentUuid
|
|
32
|
+
const turnDurations: Map<string, number> = new Map()
|
|
33
|
+
for (const l of lines) {
|
|
34
|
+
if (l.type === 'system' && l.subtype === 'turn_duration' && l.parentUuid) {
|
|
35
|
+
turnDurations.set(l.parentUuid, l.durationMs ?? 0)
|
|
36
|
+
}
|
|
37
|
+
// Grab metadata from any line
|
|
38
|
+
if (!slug && l.slug) slug = l.slug
|
|
39
|
+
if (!version && l.version) version = l.version
|
|
40
|
+
if (!gitBranch && l.gitBranch && l.gitBranch !== 'HEAD') gitBranch = l.gitBranch
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Track previous assistant timestamp for response-time calculation
|
|
44
|
+
const timestamps: Map<string, number> = new Map()
|
|
45
|
+
for (const l of lines) {
|
|
46
|
+
if (l.uuid && l.timestamp) timestamps.set(l.uuid, new Date(l.timestamp).getTime())
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let turnIndex = 0
|
|
50
|
+
|
|
51
|
+
for (const l of lines) {
|
|
52
|
+
// ─── Summary event
|
|
53
|
+
if (l.type === 'summary') {
|
|
54
|
+
summaries.push({ uuid: l.uuid ?? '', summary: l.summary ?? '', leaf_uuid: l.leafUuid ?? '' })
|
|
55
|
+
continue
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ─── Compaction boundary
|
|
59
|
+
if (l.type === 'system' && l.subtype === 'compact_boundary') {
|
|
60
|
+
const meta = l.compactMetadata ?? {}
|
|
61
|
+
compactions.push({
|
|
62
|
+
uuid: l.uuid ?? '',
|
|
63
|
+
timestamp: l.timestamp ?? '',
|
|
64
|
+
trigger: meta.trigger ?? 'auto',
|
|
65
|
+
pre_tokens: meta.preTokens ?? 0,
|
|
66
|
+
turn_index: turnIndex,
|
|
67
|
+
summary: summaries.length > 0 ? summaries[summaries.length - 1].summary : undefined,
|
|
68
|
+
})
|
|
69
|
+
continue
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ─── User turn
|
|
73
|
+
if (l.type === 'user') {
|
|
74
|
+
const msg = l.message ?? {}
|
|
75
|
+
const content = msg.content
|
|
76
|
+
let text = ''
|
|
77
|
+
const tool_results: ReplayTurn['tool_results'] = []
|
|
78
|
+
|
|
79
|
+
if (typeof content === 'string') {
|
|
80
|
+
text = content
|
|
81
|
+
} else if (Array.isArray(content)) {
|
|
82
|
+
for (const c of content) {
|
|
83
|
+
if (c.type === 'text') text += c.text ?? ''
|
|
84
|
+
if (c.type === 'tool_result') {
|
|
85
|
+
const resultContent = Array.isArray(c.content)
|
|
86
|
+
? c.content.map((x: AnyLine) => x.text ?? '').join('')
|
|
87
|
+
: (typeof c.content === 'string' ? c.content : '')
|
|
88
|
+
tool_results.push({
|
|
89
|
+
tool_use_id: c.tool_use_id ?? '',
|
|
90
|
+
content: resultContent.slice(0, 2000),
|
|
91
|
+
is_error: c.is_error ?? false,
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
turns.push({
|
|
98
|
+
uuid: l.uuid ?? '',
|
|
99
|
+
parentUuid: l.parentUuid ?? null,
|
|
100
|
+
type: 'user',
|
|
101
|
+
timestamp: l.timestamp ?? '',
|
|
102
|
+
text: text.trim(),
|
|
103
|
+
tool_results: tool_results.length > 0 ? tool_results : undefined,
|
|
104
|
+
})
|
|
105
|
+
turnIndex++
|
|
106
|
+
continue
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ─── Assistant turn
|
|
110
|
+
if (l.type === 'assistant') {
|
|
111
|
+
const msg = l.message ?? {}
|
|
112
|
+
const usage = msg.usage as TurnUsage | undefined
|
|
113
|
+
const model = msg.model as string | undefined
|
|
114
|
+
const content = msg.content ?? []
|
|
115
|
+
|
|
116
|
+
let text = ''
|
|
117
|
+
let has_thinking = false
|
|
118
|
+
let thinking_text = ''
|
|
119
|
+
const tool_calls: ToolCall[] = []
|
|
120
|
+
|
|
121
|
+
if (Array.isArray(content)) {
|
|
122
|
+
for (const c of content) {
|
|
123
|
+
if (c.type === 'text') text += c.text ?? ''
|
|
124
|
+
if (c.type === 'thinking') {
|
|
125
|
+
has_thinking = true
|
|
126
|
+
thinking_text += c.thinking ?? ''
|
|
127
|
+
}
|
|
128
|
+
if (c.type === 'tool_use') {
|
|
129
|
+
tool_calls.push({
|
|
130
|
+
id: c.id ?? '',
|
|
131
|
+
name: c.name ?? '',
|
|
132
|
+
input: c.input ?? {},
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const estimated_cost = model && usage
|
|
139
|
+
? estimateCostFromUsage(model, usage)
|
|
140
|
+
: 0
|
|
141
|
+
|
|
142
|
+
totalCost += estimated_cost
|
|
143
|
+
|
|
144
|
+
const turn_duration_ms = l.uuid ? turnDurations.get(l.uuid) : undefined
|
|
145
|
+
|
|
146
|
+
turns.push({
|
|
147
|
+
uuid: l.uuid ?? '',
|
|
148
|
+
parentUuid: l.parentUuid ?? null,
|
|
149
|
+
type: 'assistant',
|
|
150
|
+
timestamp: l.timestamp ?? '',
|
|
151
|
+
model,
|
|
152
|
+
usage,
|
|
153
|
+
text: text.trim(),
|
|
154
|
+
tool_calls: tool_calls.length > 0 ? tool_calls : undefined,
|
|
155
|
+
has_thinking,
|
|
156
|
+
thinking_text: thinking_text.trim() || undefined,
|
|
157
|
+
estimated_cost,
|
|
158
|
+
turn_duration_ms,
|
|
159
|
+
})
|
|
160
|
+
turnIndex++
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return { session_id: sessionId, slug, version, git_branch: gitBranch, turns, compactions, summaries, total_cost: totalCost }
|
|
165
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
export type ToolCategory =
|
|
2
|
+
| 'file-io'
|
|
3
|
+
| 'shell'
|
|
4
|
+
| 'agent'
|
|
5
|
+
| 'web'
|
|
6
|
+
| 'planning'
|
|
7
|
+
| 'todo'
|
|
8
|
+
| 'skill'
|
|
9
|
+
| 'mcp'
|
|
10
|
+
| 'other'
|
|
11
|
+
|
|
12
|
+
export const TOOL_CATEGORIES: Record<string, ToolCategory> = {
|
|
13
|
+
Read: 'file-io',
|
|
14
|
+
Write: 'file-io',
|
|
15
|
+
Edit: 'file-io',
|
|
16
|
+
Glob: 'file-io',
|
|
17
|
+
Grep: 'file-io',
|
|
18
|
+
NotebookEdit: 'file-io',
|
|
19
|
+
|
|
20
|
+
Bash: 'shell',
|
|
21
|
+
|
|
22
|
+
Task: 'agent',
|
|
23
|
+
TaskCreate: 'agent',
|
|
24
|
+
TaskUpdate: 'agent',
|
|
25
|
+
TaskList: 'agent',
|
|
26
|
+
TaskOutput: 'agent',
|
|
27
|
+
TaskStop: 'agent',
|
|
28
|
+
TaskGet: 'agent',
|
|
29
|
+
|
|
30
|
+
WebSearch: 'web',
|
|
31
|
+
WebFetch: 'web',
|
|
32
|
+
|
|
33
|
+
EnterPlanMode: 'planning',
|
|
34
|
+
ExitPlanMode: 'planning',
|
|
35
|
+
AskUserQuestion:'planning',
|
|
36
|
+
|
|
37
|
+
TodoWrite: 'todo',
|
|
38
|
+
|
|
39
|
+
Skill: 'skill',
|
|
40
|
+
ToolSearch: 'skill',
|
|
41
|
+
ListMcpResourcesTool: 'skill',
|
|
42
|
+
ReadMcpResourceTool: 'skill',
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Theme tokens from app/globals.css — work in light & dark */
|
|
46
|
+
export const CATEGORY_COLORS: Record<ToolCategory, string> = {
|
|
47
|
+
'file-io': 'var(--viz-tool-file-io)',
|
|
48
|
+
'shell': 'var(--viz-tool-shell)',
|
|
49
|
+
'agent': 'var(--viz-tool-agent)',
|
|
50
|
+
'web': 'var(--viz-tool-web)',
|
|
51
|
+
'planning': 'var(--viz-tool-planning)',
|
|
52
|
+
'todo': 'var(--viz-tool-todo)',
|
|
53
|
+
'skill': 'var(--viz-tool-skill)',
|
|
54
|
+
'mcp': 'var(--viz-tool-mcp)',
|
|
55
|
+
'other': 'var(--viz-tool-other)',
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Per-tool bar colors so Read / Write / Edit / … stay distinct on project cards */
|
|
59
|
+
const TOOL_BAR_OVERRIDES: Record<string, string> = {
|
|
60
|
+
Read: 'var(--viz-tool-read)',
|
|
61
|
+
Write: 'var(--viz-tool-write)',
|
|
62
|
+
Edit: 'var(--viz-tool-edit)',
|
|
63
|
+
Grep: 'var(--viz-tool-grep)',
|
|
64
|
+
Glob: 'var(--viz-tool-glob)',
|
|
65
|
+
NotebookEdit: 'var(--viz-tool-edit)',
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function categorizeTool(name: string): ToolCategory {
|
|
69
|
+
if (name.startsWith('mcp__')) return 'mcp'
|
|
70
|
+
return TOOL_CATEGORIES[name] ?? 'other'
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function toolBarColor(toolName: string): string {
|
|
74
|
+
return TOOL_BAR_OVERRIDES[toolName] ?? CATEGORY_COLORS[categorizeTool(toolName)]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Alpha that works for both hex and `var(--…)` (unlike string concatenation).
|
|
79
|
+
* @param opacityPercent 0–100 portion of the base color
|
|
80
|
+
*/
|
|
81
|
+
export function categoryColorMix(base: string, opacityPercent: number): string {
|
|
82
|
+
return `color-mix(in srgb, ${base} ${opacityPercent}%, transparent)`
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const CATEGORY_LABELS: Record<ToolCategory, string> = {
|
|
86
|
+
'file-io': 'File I/O',
|
|
87
|
+
'shell': 'Shell',
|
|
88
|
+
'agent': 'Agents',
|
|
89
|
+
'web': 'Web',
|
|
90
|
+
'planning': 'Planning',
|
|
91
|
+
'todo': 'Todo',
|
|
92
|
+
'skill': 'Skills',
|
|
93
|
+
'mcp': 'MCP',
|
|
94
|
+
'other': 'Other',
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function isMcpTool(name: string): boolean {
|
|
98
|
+
return name.startsWith('mcp__')
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function parseMcpTool(name: string): { server: string; tool: string } | null {
|
|
102
|
+
if (!name.startsWith('mcp__')) return null
|
|
103
|
+
const parts = name.split('__')
|
|
104
|
+
if (parts.length < 3) return null
|
|
105
|
+
return {
|
|
106
|
+
server: parts[1],
|
|
107
|
+
tool: parts.slice(2).join('__'),
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function toolDisplayName(name: string): string {
|
|
112
|
+
const mcp = parseMcpTool(name)
|
|
113
|
+
if (mcp) return `${mcp.server} · ${mcp.tool}`
|
|
114
|
+
return name
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export const TOOL_ICONS: Record<ToolCategory, string> = {
|
|
118
|
+
'file-io': '📄',
|
|
119
|
+
'shell': '⚡',
|
|
120
|
+
'agent': '🤖',
|
|
121
|
+
'web': '🌐',
|
|
122
|
+
'planning': '📋',
|
|
123
|
+
'todo': '✅',
|
|
124
|
+
'skill': '🎯',
|
|
125
|
+
'mcp': '🔌',
|
|
126
|
+
'other': '🔧',
|
|
127
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { NextConfig } from "next";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
|
|
5
|
+
// When run via `npx`, a lockfile also exists above this package (npx cache root).
|
|
6
|
+
// Without an explicit root, Turbopack can pick the wrong workspace and fail to resolve
|
|
7
|
+
// `.next/dev/.../build-manifest.json` for the app.
|
|
8
|
+
const packageRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
|
|
10
|
+
const nextConfig: NextConfig = {
|
|
11
|
+
turbopack: {
|
|
12
|
+
root: packageRoot,
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default nextConfig;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "orca-pulse",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Orca Pulse — real-time analytics dashboard for Orca AI agent sessions",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "next dev",
|
|
8
|
+
"build": "next build",
|
|
9
|
+
"start": "next start",
|
|
10
|
+
"lint": "eslint"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@tailwindcss/postcss": "^4",
|
|
14
|
+
"@types/node": "^20",
|
|
15
|
+
"@types/react": "^19",
|
|
16
|
+
"@types/react-dom": "^19",
|
|
17
|
+
"class-variance-authority": "^0.7.1",
|
|
18
|
+
"clsx": "^2.1.1",
|
|
19
|
+
"cmdk": "^1.1.1",
|
|
20
|
+
"date-fns": "^4.1.0",
|
|
21
|
+
"jszip": "^3.10.1",
|
|
22
|
+
"lucide-react": "^0.575.0",
|
|
23
|
+
"next": "16.2.2",
|
|
24
|
+
"radix-ui": "^1.4.3",
|
|
25
|
+
"react": "19.2.3",
|
|
26
|
+
"react-day-picker": "^9.14.0",
|
|
27
|
+
"react-dom": "19.2.3",
|
|
28
|
+
"react-markdown": "^10.1.0",
|
|
29
|
+
"recharts": "^3.7.0",
|
|
30
|
+
"remark-gfm": "^4.0.1",
|
|
31
|
+
"swr": "^2.4.1",
|
|
32
|
+
"tailwind-merge": "^3.5.0",
|
|
33
|
+
"tailwindcss": "^4",
|
|
34
|
+
"tw-animate-css": "^1.4.0",
|
|
35
|
+
"typescript": "^5"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"eslint": "9.39.3",
|
|
39
|
+
"eslint-config-next": "16.2.2",
|
|
40
|
+
"shadcn": "^4.2.0"
|
|
41
|
+
},
|
|
42
|
+
"overrides": {
|
|
43
|
+
"@eslint/eslintrc": "3.3.4"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|