From dc5a999b13fee2d75cad2482acb17ffc18d073ba Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 16 Jun 2026 21:30:23 +0000 Subject: [PATCH 01/11] Replace TUI with native macOS menu bar app Remove the Bun/React terminal dashboard and replace it with a native SwiftUI menu bar app (MenuBarExtra + Swift Charts) that shows Claude and Codex usage limits as burndown charts, matching the requested design. - Reads existing CLI credentials (Claude Code keychain item, ~/.codex/auth.json) - Fetches the same Anthropic OAuth usage and Codex usage endpoints - Renders ideal-vs-actual burndown per window with over/under pace badges - Persists usage samples to draw the real usage curve over time - Auto-refreshes every 60s; provider picker for Codex/Claude - build.sh assembles a menu-bar-only (LSUIElement) .app bundle Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_019cA8oiGrNxkFE4JbALbPgw --- .github/workflows/publish.yml | 70 - .gitignore | 10 +- AGENTS.md | 17 - App/Info.plist | 28 + CHANGELOG.md | 120 -- LICENSE | 21 + Package.swift | 15 + README.md | 147 +-- Sources/AgentLimit/AgentLimitApp.swift | 38 + Sources/AgentLimit/Burndown.swift | 85 ++ Sources/AgentLimit/BurndownChartView.swift | 128 ++ Sources/AgentLimit/ContentView.swift | 146 +++ Sources/AgentLimit/Credentials.swift | 101 ++ Sources/AgentLimit/Models.swift | 81 ++ Sources/AgentLimit/Providers.swift | 147 +++ Sources/AgentLimit/UsageHistory.swift | 77 ++ Sources/AgentLimit/UsageViewModel.swift | 79 ++ bin/cli.js | 25 - bin/cli.tsx | 97 -- build.sh | 29 + bun.lock | 232 ---- package-lock.json | 1336 -------------------- package.json | 61 - src/App.tsx | 63 - src/components/Dashboard.tsx | 24 - src/components/Footer.tsx | 21 - src/components/Header.tsx | 43 - src/components/ProgressBar.tsx | 82 -- src/components/ProviderCard.tsx | 82 -- src/components/index.ts | 5 - src/index.tsx | 42 - src/providers/claude.ts | 111 -- src/providers/codex.ts | 113 -- src/providers/index.ts | 16 - src/providers/types.ts | 24 - src/utils/colors.ts | 30 - src/utils/keychain.ts | 86 -- src/utils/time.ts | 94 -- tsconfig.json | 24 - 39 files changed, 1029 insertions(+), 2921 deletions(-) delete mode 100644 .github/workflows/publish.yml delete mode 100644 AGENTS.md create mode 100644 App/Info.plist delete mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 Package.swift create mode 100644 Sources/AgentLimit/AgentLimitApp.swift create mode 100644 Sources/AgentLimit/Burndown.swift create mode 100644 Sources/AgentLimit/BurndownChartView.swift create mode 100644 Sources/AgentLimit/ContentView.swift create mode 100644 Sources/AgentLimit/Credentials.swift create mode 100644 Sources/AgentLimit/Models.swift create mode 100644 Sources/AgentLimit/Providers.swift create mode 100644 Sources/AgentLimit/UsageHistory.swift create mode 100644 Sources/AgentLimit/UsageViewModel.swift delete mode 100755 bin/cli.js delete mode 100755 bin/cli.tsx create mode 100755 build.sh delete mode 100644 bun.lock delete mode 100644 package-lock.json delete mode 100644 package.json delete mode 100644 src/App.tsx delete mode 100644 src/components/Dashboard.tsx delete mode 100644 src/components/Footer.tsx delete mode 100644 src/components/Header.tsx delete mode 100644 src/components/ProgressBar.tsx delete mode 100644 src/components/ProviderCard.tsx delete mode 100644 src/components/index.ts delete mode 100644 src/index.tsx delete mode 100644 src/providers/claude.ts delete mode 100644 src/providers/codex.ts delete mode 100644 src/providers/index.ts delete mode 100644 src/providers/types.ts delete mode 100644 src/utils/colors.ts delete mode 100644 src/utils/keychain.ts delete mode 100644 src/utils/time.ts delete mode 100644 tsconfig.json diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index fda41c9..0000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: Publish to npm - -on: - push: - branches: - - main - -jobs: - publish: - runs-on: ubuntu-latest - permissions: - contents: write - id-token: write - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Update npm and configure - run: | - npm install -g npm@latest - echo "npm version: $(npm --version)" - echo "node version: $(node --version)" - echo "OIDC URL set: ${ACTIONS_ID_TOKEN_REQUEST_URL:+yes}" - cat ~/.npmrc 2>/dev/null || echo "No .npmrc" - - - name: Setup Bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - - - name: Install dependencies - run: bun install - - - name: Check if version changed - id: version - run: | - PACKAGE_NAME=$(jq -r .name package.json) - LOCAL_VERSION=$(jq -r .version package.json) - NPM_VERSION=$(npm view "$PACKAGE_NAME" version 2>/dev/null || echo "0.0.0") - - echo "local=$LOCAL_VERSION" >> $GITHUB_OUTPUT - echo "npm=$NPM_VERSION" >> $GITHUB_OUTPUT - - if [ "$LOCAL_VERSION" != "$NPM_VERSION" ]; then - echo "changed=true" >> $GITHUB_OUTPUT - echo "Version changed: $NPM_VERSION -> $LOCAL_VERSION" - else - echo "changed=false" >> $GITHUB_OUTPUT - echo "Version unchanged: $LOCAL_VERSION" - fi - - - name: Publish to npm - if: steps.version.outputs.changed == 'true' - run: | - # Ensure no auth config exists (let npm use OIDC) - rm -f ~/.npmrc - npm publish --access public --provenance - - - name: Create GitHub Release - if: steps.version.outputs.changed == 'true' - uses: softprops/action-gh-release@v2 - with: - tag_name: v${{ steps.version.outputs.local }} - name: v${{ steps.version.outputs.local }} - generate_release_notes: true diff --git a/.gitignore b/.gitignore index 0808bc9..35e3cd0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ -node_modules/ -dist/ .DS_Store -*.log -bun.lockb -.npm-cache +.build/ +dist/ +*.xcodeproj +*.xcworkspace +.swiftpm/ diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 76ab860..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,17 +0,0 @@ -# Agent Instructions - -Guidelines for AI agents working on this codebase. - -## Version Management - -When you complete a task that changes functionality: - -1. **Bump the version** in `package.json` following [Semantic Versioning](https://semver.org/): - - **MAJOR** (1.0.0): Breaking changes - - **MINOR** (0.1.0): New features, backwards compatible - - **PATCH** (0.0.1): Bug fixes, backwards compatible - -2. **Update the changelog** in `CHANGELOG.md`: - - Add entry under `## [X.Y.Z] - YYYY-MM-DD` - - Use appropriate section: `Added`, `Changed`, `Fixed`, `Removed` - - Update the version comparison links at the bottom diff --git a/App/Info.plist b/App/Info.plist new file mode 100644 index 0000000..49e6852 --- /dev/null +++ b/App/Info.plist @@ -0,0 +1,28 @@ + + + + + CFBundleName + AgentLimit + CFBundleDisplayName + Agent Limit + CFBundleExecutable + AgentLimit + CFBundleIdentifier + com.agentworkforce.agentlimit + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0.0 + CFBundleVersion + 1 + LSMinimumSystemVersion + 13.0 + LSUIElement + + NSPrincipalClass + NSApplication + NSHumanReadableCopyright + MIT License + + diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index d0ef58e..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,120 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [0.7.2] - 2026-01-03 - -### Removed - -- Automatic OAuth token refresh for Claude - was corrupting tokens stored in Keychain - -## [0.7.1] - 2026-01-02 - -### Fixed - -- Terminal cleanup on exit - raw ANSI escape codes no longer dumped when pressing q or Ctrl+C - -## [0.7.0] - 2026-01-02 - -### Added - -- `update` command to check for and install latest version (`agent-limit update`) - -## [0.6.2] - 2026-01-02 - -### Fixed - -- "React is not defined" error on global npm installs. The published package doesn't include tsconfig.json, so Bun can't read jsxImportSource and falls back to classic JSX transform. Added explicit React imports to all TSX files. - -## [0.6.0] - 2026-01-02 - -### Added - -- `version` command to display current version (`agent-limit version`, `-v`, `--version`) - -## [0.5.1] - 2026-01-02 - -### Fixed - -- JSX compilation error in CLI entry point ("React is not defined") - -## [0.5.0] - 2026-01-02 - -### Added - -- Automatic OAuth token refresh for Claude Code credentials -- Proactive token refresh before expiration (5-minute buffer) -- Retry with refreshed token on 401 responses - -## [0.4.1] - 2026-01-02 - -### Removed - -- Gemini CLI provider support - -## [0.4.0] - 2025-01-02 - -### Added - -- Bun as npm dependency - no global Bun installation required -- Node.js wrapper script for npm distribution - -### Fixed - -- CLI now uses correct @opentui/core API (createCliRenderer) - -### Changed - -- Renamed package from `agent-monitor` to `agent-limit` - -## [0.3.0] - 2025-01-02 - -### Added - -- GitHub Actions workflow for automatic npm publishing on version bump -- Automatic GitHub release creation with release notes - -## [0.2.0] - 2025-01-02 - -### Added - -- CHANGELOG.md for tracking version history - -### Changed - -- Improved error handling for missing credentials - -## [0.1.0] - 2025-01-02 - -### Added - -- Initial release -- Terminal dashboard for monitoring AI agent usage limits -- Support for Claude Code usage tracking via macOS Keychain -- Support for Codex usage tracking via `~/.codex/auth.json` -- Support for Gemini CLI usage display via `~/.gemini/settings.json` -- Real-time progress bars with color-coded usage indicators -- Trajectory markers showing pace against reset period -- Auto-refresh every 60 seconds -- Keyboard controls: `q` to quit, `r` to refresh -- Standalone binary builds for macOS (arm64 and x64) -- npm package distribution - -[Unreleased]: https://github.com/AgentWorkforce/limit/compare/v0.7.2...HEAD -[0.7.2]: https://github.com/AgentWorkforce/limit/compare/v0.7.1...v0.7.2 -[0.7.1]: https://github.com/AgentWorkforce/limit/compare/v0.7.0...v0.7.1 -[0.7.0]: https://github.com/AgentWorkforce/limit/compare/v0.6.2...v0.7.0 -[0.6.2]: https://github.com/AgentWorkforce/limit/compare/v0.6.0...v0.6.2 -[0.6.0]: https://github.com/AgentWorkforce/limit/compare/v0.5.1...v0.6.0 -[0.5.1]: https://github.com/AgentWorkforce/limit/compare/v0.5.0...v0.5.1 -[0.5.0]: https://github.com/AgentWorkforce/limit/compare/v0.4.1...v0.5.0 -[0.4.1]: https://github.com/AgentWorkforce/limit/compare/v0.4.0...v0.4.1 -[0.4.0]: https://github.com/AgentWorkforce/limit/compare/v0.3.0...v0.4.0 -[0.3.0]: https://github.com/AgentWorkforce/limit/compare/v0.2.0...v0.3.0 -[0.2.0]: https://github.com/AgentWorkforce/limit/compare/v0.1.0...v0.2.0 -[0.1.0]: https://github.com/AgentWorkforce/limit/releases/tag/v0.1.0 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..47f5361 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Will Washburn + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..07409b9 --- /dev/null +++ b/Package.swift @@ -0,0 +1,15 @@ +// swift-tools-version:5.9 +import PackageDescription + +let package = Package( + name: "AgentLimit", + platforms: [ + .macOS(.v13) + ], + targets: [ + .executableTarget( + name: "AgentLimit", + path: "Sources/AgentLimit" + ) + ] +) diff --git a/README.md b/README.md index e23b3a1..8816fd3 100644 --- a/README.md +++ b/README.md @@ -1,126 +1,77 @@ -# agent-limit +# Agent Limit -Terminal dashboard to monitor Claude Code and Codex usage limits. +A native macOS **menu bar app** that shows your **Claude Code** and **Codex** +usage limits as burndown charts — so you can see at a glance whether you're +ahead of or behind your usage pace before you hit a wall. -## Quickstart +## What it does -```bash -npm install -g agent-limit -agent-limit usage -``` - -## Example - -``` -[███████░░░░|░░░░░░░░░] 30% ↓12% - ^ you should be at 42%, but you're at 30% (12% under pace) -``` - -- `↓X%` (green) = under pace, you have headroom -- `↑X%` (red) = over pace, might hit limits early - -## Features - -- Real-time usage tracking for Claude Code and Codex -- Trajectory markers showing if you're ahead or behind your usage pace -- Auto-refresh every 60 seconds -- Color-coded usage indicators - -## CLI Reference - -| Command | Description | -|---------|-------------| -| `agent-limit usage` | Show usage dashboard | -| `agent-limit version` | Show version | -| `agent-limit help` | Show help message | - -### Dashboard Controls - -| Key | Action | -|-----|--------| -| `q` | Quit | -| `r` | Refresh | - -## Supported Providers - -| Provider | Status | Data Source | -|----------|--------|-------------| -| Claude Code | Full support | macOS Keychain + Anthropic API | -| Codex | Full support | `~/.codex/auth.json` + OpenAI API | +- Lives in the menu bar and shows your highest current usage as a percentage. +- Click it to open a popover with a **burndown chart** for each limit window + (e.g. 5-hour and weekly): + - A dashed line shows the *ideal* pace (a straight burn from 100% to 0% over + the window). + - A solid blue line shows your *actual* remaining quota over time. + - The gap between them is shaded **green** when you're **under pace** (you + have headroom) or **red** when you're **over pace** (you'll hit the limit + early). +- Switch between **Codex** and **Claude** from the provider picker. +- Auto-refreshes every 60 seconds; refresh manually any time. -## How It Works +## How it reads your usage -agent-limit reads credentials from standard locations: +The app reuses the credentials the official CLIs already store on your Mac — it +never asks you to log in again: -- **Claude Code**: macOS Keychain (`Claude Code-credentials`) -- **Codex**: `~/.codex/auth.json` +| Provider | Credentials | Endpoint | +|----------|-------------|----------| +| Claude | `Claude Code-credentials` login-keychain item (written by Claude Code) | `https://api.anthropic.com/api/oauth/usage` | +| Codex | `~/.codex/auth.json` (written by the Codex CLI) | `https://chatgpt.com/backend-api/wham/usage` | -It then fetches usage data from each provider's API and displays it in a unified dashboard. +If a provider isn't authenticated, run `claude` or `codex` once to log in. -## Installation Options +The first time it reads the Claude keychain item, macOS may prompt you to allow +access — choose **Always Allow**. -### Via npm +## Build & run -```bash -npm install -g agent-limit -``` - -### Standalone Binary (no dependencies) - -Download from [GitHub Releases](https://github.com/AgentWorkforce/limit/releases): +Requires macOS 13+ and the Swift toolchain (Xcode or the Command Line Tools). ```bash -# Apple Silicon -curl -L https://github.com/AgentWorkforce/limit/releases/latest/download/agent-limit-darwin-arm64 -o /usr/local/bin/agent-limit -chmod +x /usr/local/bin/agent-limit - -# Intel Mac -curl -L https://github.com/AgentWorkforce/limit/releases/latest/download/agent-limit-darwin-x64 -o /usr/local/bin/agent-limit -chmod +x /usr/local/bin/agent-limit +./build.sh +open dist/AgentLimit.app ``` -## Requirements - -- macOS (uses Keychain for credential storage) -- Active CLI authentication for providers you want to monitor - -## Development +To install it permanently: ```bash -git clone https://github.com/AgentWorkforce/limit.git -cd monitor -bun install +cp -R dist/AgentLimit.app /Applications/ ``` -Run in development mode with hot reload: +For development you can also run straight from the package: ```bash -bun run dev +swift run ``` -Run directly: +## Project layout -```bash -bun run start ``` - -> **Note:** In dev mode, use `q` to quit cleanly. If you Ctrl-C and see garbled output, run `reset` to restore your terminal. - -### Building Standalone Binaries - -Build binaries that don't require Bun: - -```bash -# Build for all macOS architectures -bun run build - -# Build for specific architecture -bun run build:arm64 # Apple Silicon -bun run build:x64 # Intel +Package.swift Swift package manifest +App/Info.plist Bundle metadata (LSUIElement → menu-bar-only app) +build.sh Builds AgentLimit.app +Sources/AgentLimit/ + AgentLimitApp.swift App entry point + menu bar label + ContentView.swift Popover UI + BurndownChartView.swift Swift Charts burndown rendering + UsageViewModel.swift Loading, refresh timer, view state + Providers.swift Claude + Codex usage fetchers + Credentials.swift Reads keychain / auth.json + UsageHistory.swift Persists samples for the usage curve + Burndown.swift Turns samples into chart data + Models.swift Shared types ``` -Binaries are output to `dist/`. - ## License -MIT +MIT — see [LICENSE](LICENSE). diff --git a/Sources/AgentLimit/AgentLimitApp.swift b/Sources/AgentLimit/AgentLimitApp.swift new file mode 100644 index 0000000..6703df7 --- /dev/null +++ b/Sources/AgentLimit/AgentLimitApp.swift @@ -0,0 +1,38 @@ +import SwiftUI + +@main +struct AgentLimitApp: App { + @StateObject private var viewModel = UsageViewModel() + + var body: some Scene { + MenuBarExtra { + ContentView(viewModel: viewModel) + } label: { + MenuBarLabel(viewModel: viewModel) + } + .menuBarExtraStyle(.window) + } +} + +/// The label shown in the menu bar: a gauge icon plus the highest current usage. +struct MenuBarLabel: View { + @ObservedObject var viewModel: UsageViewModel + + var body: some View { + Label(text, systemImage: symbol) + } + + private var text: String { + guard let usage = viewModel.headlineUsage else { return "—" } + return "\(usage)%" + } + + private var symbol: String { + guard let status = viewModel.status else { return "gauge.with.dots.needle.0percent" } + switch status.status { + case .warning: return "gauge.with.dots.needle.67percent" + case .ok: return "gauge.with.dots.needle.33percent" + default: return "gauge.with.dots.needle.0percent" + } + } +} diff --git a/Sources/AgentLimit/Burndown.swift b/Sources/AgentLimit/Burndown.swift new file mode 100644 index 0000000..2e8a511 --- /dev/null +++ b/Sources/AgentLimit/Burndown.swift @@ -0,0 +1,85 @@ +import Foundation + +/// A point on the actual-usage curve, paired with the ideal value at that time +/// so the chart can shade the gap between them. +struct AreaPoint: Identifiable { + let id = UUID() + let date: Date + let actual: Double + let ideal: Double +} + +/// A point on a simple two-point line (the ideal burndown). +struct LinePoint: Identifiable { + let id = UUID() + let date: Date + let value: Double +} + +/// Everything the chart needs to render one limit window as a burndown. +/// +/// Values are expressed as *remaining* quota (100% at the start of the window, +/// 0% when exhausted), matching the visual in the menu bar popover. +struct BurndownData { + let actualPoints: [AreaPoint] // window start ... now + let idealLine: [LinePoint] // window start ... window end + let windowStart: Date + let windowEnd: Date + let now: Date + let actualRemaining: Double // at now + let idealRemaining: Double // at now + /// True when the window is short enough to label the axis with times only. + let compact: Bool + + /// Positive when you have less remaining than the ideal pace (burning too + /// fast → "over pace"); negative when you have headroom ("under pace"). + var paceDelta: Double { idealRemaining - actualRemaining } + var isOverPace: Bool { paceDelta > 0 } +} + +enum BurndownBuilder { + /// Builds chart data for a metric, or `nil` if the window has no reset time + /// (in which case the caller should fall back to a simple bar). + static func build(metric: UsageMetric, samples: [UsageSample], now: Date = Date()) -> BurndownData? { + guard let resetsAt = metric.resetsAt, metric.periodSeconds > 0 else { return nil } + + let windowEnd = resetsAt + let windowStart = resetsAt.addingTimeInterval(-metric.periodSeconds) + let total = windowEnd.timeIntervalSince(windowStart) + guard total > 0 else { return nil } + + func idealRemaining(at date: Date) -> Double { + let fraction = max(0, min(1, windowEnd.timeIntervalSince(date) / total)) + return fraction * 100 + } + + // Actual remaining curve: starts full at the window start, walks through + // recorded samples, and ends at the current value. + var series: [(date: Date, remaining: Double)] = [(windowStart, 100)] + for sample in samples where sample.date > windowStart && sample.date < now { + series.append((sample.date, max(0, 100 - sample.percentage))) + } + series.append((now, max(0, 100 - metric.percentage))) + series.sort { $0.date < $1.date } + + let actualPoints = series.map { + AreaPoint(date: $0.date, actual: $0.remaining, ideal: idealRemaining(at: $0.date)) + } + + let idealLine = [ + LinePoint(date: windowStart, value: 100), + LinePoint(date: windowEnd, value: 0), + ] + + return BurndownData( + actualPoints: actualPoints, + idealLine: idealLine, + windowStart: windowStart, + windowEnd: windowEnd, + now: now, + actualRemaining: max(0, 100 - metric.percentage), + idealRemaining: idealRemaining(at: now), + compact: metric.periodSeconds <= 24 * 3600 + ) + } +} diff --git a/Sources/AgentLimit/BurndownChartView.swift b/Sources/AgentLimit/BurndownChartView.swift new file mode 100644 index 0000000..0c10f47 --- /dev/null +++ b/Sources/AgentLimit/BurndownChartView.swift @@ -0,0 +1,128 @@ +import SwiftUI +import Charts + +/// Renders one limit window as a burndown chart: a dashed ideal line, the real +/// usage curve, the shaded gap between them, and pace badges. +struct BurndownChartView: View { + let title: String + let data: BurndownData + + private var accent: Color { data.isOverPace ? .red : .green } + + var body: some View { + VStack(alignment: .leading, spacing: 8) { + Text(title) + .font(.title3.weight(.bold)) + + chart + .frame(height: 150) + + HStack { + Text(data.windowStart, format: axisFormat) + Spacer() + Text(data.windowEnd, format: axisFormat) + } + .font(.caption) + .foregroundStyle(.secondary) + } + } + + private var axisFormat: Date.FormatStyle { + data.compact + ? .dateTime.hour().minute() + : .dateTime.month(.abbreviated).day().hour().minute() + } + + private var chart: some View { + Chart { + // Shaded gap between the actual and ideal curves. + ForEach(data.actualPoints) { point in + AreaMark( + x: .value("Time", point.date), + yStart: .value("Ideal", point.ideal), + yEnd: .value("Actual", point.actual) + ) + .foregroundStyle(accent.opacity(0.18)) + .interpolationMethod(.linear) + } + + // Ideal burndown (dashed, full window). + ForEach(data.idealLine) { point in + LineMark( + x: .value("Time", point.date), + y: .value("Remaining", point.value), + series: .value("Series", "ideal") + ) + .foregroundStyle(Color.accentColor.opacity(0.55)) + .lineStyle(StrokeStyle(lineWidth: 1.5, dash: [5, 4])) + .interpolationMethod(.linear) + } + + // Actual usage curve. + ForEach(data.actualPoints) { point in + LineMark( + x: .value("Time", point.date), + y: .value("Remaining", point.actual), + series: .value("Series", "actual") + ) + .foregroundStyle(Color.blue) + .lineStyle(StrokeStyle(lineWidth: 2.5, lineJoin: .round)) + .interpolationMethod(.linear) + } + + // Vertical connector at "now" showing the pace gap. + RuleMark( + x: .value("Now", data.now), + yStart: .value("Remaining", min(data.actualRemaining, data.idealRemaining)), + yEnd: .value("Remaining", max(data.actualRemaining, data.idealRemaining)) + ) + .foregroundStyle(accent) + .lineStyle(StrokeStyle(lineWidth: 2)) + + // Current position marker. + PointMark( + x: .value("Now", data.now), + y: .value("Remaining", data.actualRemaining) + ) + .foregroundStyle(Color.blue) + .symbolSize(70) + } + .chartYScale(domain: 0.0...100.0) + .chartXScale(domain: data.windowStart...data.windowEnd) + .chartXAxis(.hidden) + .chartYAxis { + AxisMarks(position: .leading, values: [0.0, 50.0, 100.0]) { value in + AxisGridLine() + AxisValueLabel { + if let v = value.as(Double.self) { + Text("\(Int(v))%") + } + } + } + } + .overlay(alignment: .topTrailing) { paceBadges } + } + + private var paceBadges: some View { + VStack(alignment: .trailing, spacing: 3) { + Text("\(Int(data.actualRemaining.rounded()))%") + .font(.caption.weight(.bold)) + .foregroundStyle(.white) + .padding(.horizontal, 6) + .padding(.vertical, 2) + .background(Color.blue, in: RoundedRectangle(cornerRadius: 5)) + + Text("\(abs(Int(data.paceDelta.rounded())))% \(data.isOverPace ? "Over pace" : "Under pace")") + .font(.caption.weight(.bold)) + .foregroundStyle(accent) + + Text("\(Int(data.idealRemaining.rounded()))%") + .font(.caption) + .foregroundStyle(.secondary) + .padding(.horizontal, 6) + .padding(.vertical, 2) + .background(Color(nsColor: .controlBackgroundColor), in: RoundedRectangle(cornerRadius: 5)) + } + .padding(6) + } +} diff --git a/Sources/AgentLimit/ContentView.swift b/Sources/AgentLimit/ContentView.swift new file mode 100644 index 0000000..7b20b7e --- /dev/null +++ b/Sources/AgentLimit/ContentView.swift @@ -0,0 +1,146 @@ +import SwiftUI + +/// The popover shown when the menu bar item is clicked. +struct ContentView: View { + @ObservedObject var viewModel: UsageViewModel + + var body: some View { + VStack(alignment: .leading, spacing: 14) { + header + Divider() + content + } + .padding(16) + .frame(width: 380) + } + + // MARK: Header + + private var header: some View { + VStack(alignment: .leading, spacing: 6) { + HStack(spacing: 8) { + Text("Provider:") + .foregroundStyle(.secondary) + + Picker("Provider", selection: providerBinding) { + ForEach(ProviderName.allCases) { provider in + Text(provider.displayName).tag(provider) + } + } + .labelsHidden() + .fixedSize() + + Spacer() + + Button { + Task { await viewModel.refresh() } + } label: { + Image(systemName: "arrow.clockwise") + } + .buttonStyle(.borderless) + .help("Refresh now") + + Button { + NSApp.terminate(nil) + } label: { + Image(systemName: "power") + } + .buttonStyle(.borderless) + .help("Quit Agent Limit") + } + + Text(subtitle) + .font(.caption) + .foregroundStyle(.secondary) + } + } + + private var providerBinding: Binding { + Binding( + get: { viewModel.selectedProvider }, + set: { viewModel.select($0) } + ) + } + + private var subtitle: String { + let updated: String + if let last = viewModel.lastUpdated { + let seconds = Int(Date().timeIntervalSince(last)) + switch seconds { + case ..<5: updated = "just now" + case ..<60: updated = "\(seconds)s ago" + default: updated = "\(seconds / 60)m ago" + } + } else { + updated = viewModel.isLoading ? "loading…" : "never" + } + return "Updated \(updated) · Source: \(viewModel.selectedProvider.sourceLabel)" + } + + // MARK: Content + + @ViewBuilder + private var content: some View { + if let status = viewModel.status { + switch status.status { + case .unavailable, .error: + messageView(status.message ?? "Usage is unavailable.") + default: + if status.metrics.isEmpty { + messageView("No active limit windows reported.") + } else { + chartsView(plan: status.plan) + } + } + } else { + ProgressView() + .frame(maxWidth: .infinity) + .padding(.vertical, 36) + } + } + + private func chartsView(plan: String?) -> some View { + VStack(alignment: .leading, spacing: 20) { + if let plan { + Text("\(plan) plan") + .font(.caption) + .foregroundStyle(.secondary) + } + ForEach(Array(viewModel.charts.enumerated()), id: \.offset) { _, item in + if let data = item.data { + BurndownChartView(title: item.metric.name, data: data) + } else { + SimpleUsageRow(metric: item.metric) + } + } + } + } + + private func messageView(_ message: String) -> some View { + HStack(alignment: .top, spacing: 8) { + Image(systemName: "exclamationmark.triangle.fill") + .foregroundStyle(.orange) + Text(message) + .font(.callout) + .fixedSize(horizontal: false, vertical: true) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.vertical, 24) + } +} + +/// Fallback row for windows that don't report a reset time (no burndown). +struct SimpleUsageRow: View { + let metric: UsageMetric + + var body: some View { + VStack(alignment: .leading, spacing: 6) { + Text(metric.name) + .font(.title3.weight(.bold)) + ProgressView(value: min(metric.percentage, 100), total: 100) + Text("\(Int(metric.percentage.rounded()))% used") + .font(.caption) + .foregroundStyle(.secondary) + } + } +} diff --git a/Sources/AgentLimit/Credentials.swift b/Sources/AgentLimit/Credentials.swift new file mode 100644 index 0000000..24baff9 --- /dev/null +++ b/Sources/AgentLimit/Credentials.swift @@ -0,0 +1,101 @@ +import Foundation + +struct ClaudeCredentials { + let accessToken: String + let subscriptionType: String? +} + +struct CodexCredentials { + let accessToken: String + let accountId: String + let planType: String? +} + +/// Reads locally-stored credentials written by the Claude Code and Codex CLIs. +/// +/// - Claude Code stores an OAuth blob in the login keychain under the service +/// name `"Claude Code-credentials"`. +/// - Codex stores tokens in `~/.codex/auth.json`. +enum Credentials { + + // MARK: Claude + + static func claude() -> ClaudeCredentials? { + guard let raw = keychainPassword(service: "Claude Code-credentials"), + let data = raw.data(using: .utf8), + let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], + let oauth = json["claudeAiOauth"] as? [String: Any], + let token = oauth["accessToken"] as? String + else { + return nil + } + let subscription = oauth["subscriptionType"] as? String + return ClaudeCredentials(accessToken: token, subscriptionType: subscription) + } + + /// Shells out to `/usr/bin/security` to read a generic password. This mirrors + /// what the original CLI did and avoids needing a matching keychain access + /// group. The user may be prompted to allow access the first time. + private static func keychainPassword(service: String) -> String? { + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/security") + process.arguments = ["find-generic-password", "-s", service, "-w"] + + let outPipe = Pipe() + process.standardOutput = outPipe + process.standardError = Pipe() + + do { + try process.run() + } catch { + return nil + } + + let data = outPipe.fileHandleForReading.readDataToEndOfFile() + process.waitUntilExit() + guard process.terminationStatus == 0 else { return nil } + + let value = String(data: data, encoding: .utf8)? + .trimmingCharacters(in: .whitespacesAndNewlines) + return (value?.isEmpty == false) ? value : nil + } + + // MARK: Codex + + static func codex() -> CodexCredentials? { + let url = FileManager.default.homeDirectoryForCurrentUser + .appendingPathComponent(".codex/auth.json") + + guard let data = try? Data(contentsOf: url), + let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], + let tokens = json["tokens"] as? [String: Any], + let access = tokens["access_token"] as? String, + let accountId = tokens["account_id"] as? String + else { + return nil + } + + let plan = chatGPTPlan(fromJWT: access) + return CodexCredentials(accessToken: access, accountId: accountId, planType: plan) + } + + /// Extracts `chatgpt_plan_type` from the OpenAI auth claim of a JWT payload. + private static func chatGPTPlan(fromJWT jwt: String) -> String? { + let parts = jwt.split(separator: ".") + guard parts.count == 3 else { return nil } + + var base64 = String(parts[1]) + .replacingOccurrences(of: "-", with: "+") + .replacingOccurrences(of: "_", with: "/") + while base64.count % 4 != 0 { base64 += "=" } + + guard let data = Data(base64Encoded: base64), + let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], + let auth = json["https://api.openai.com/auth"] as? [String: Any], + let plan = auth["chatgpt_plan_type"] as? String + else { + return nil + } + return plan + } +} diff --git a/Sources/AgentLimit/Models.swift b/Sources/AgentLimit/Models.swift new file mode 100644 index 0000000..1e460c1 --- /dev/null +++ b/Sources/AgentLimit/Models.swift @@ -0,0 +1,81 @@ +import Foundation + +/// Supported usage providers. +enum ProviderName: String, CaseIterable, Identifiable { + case codex + case claude + + var id: String { rawValue } + + var displayName: String { + switch self { + case .claude: return "Claude" + case .codex: return "Codex" + } + } + + /// Short description of where the data comes from (shown in the header). + var sourceLabel: String { + switch self { + case .claude: return "Anthropic OAuth" + case .codex: return "Codex RPC" + } + } +} + +enum ProviderStatusType { + case ok + case warning + case error + case unavailable + case loading +} + +/// A single usage window (e.g. the 5-hour or weekly limit). +struct UsageMetric: Identifiable { + let id = UUID() + /// Human readable window name, e.g. "5-hour" or "Weekly". + let name: String + /// Percentage of the limit that has been *used* (0...100). + let percentage: Double + /// When this window resets, if known. + let resetsAt: Date? + /// Total duration of the window in seconds. + let periodSeconds: Double +} + +struct ProviderStatus { + let provider: ProviderName + let status: ProviderStatusType + var plan: String? + var metrics: [UsageMetric] + var message: String? + + static func unavailable(_ provider: ProviderName, _ message: String) -> ProviderStatus { + ProviderStatus(provider: provider, status: .unavailable, plan: nil, metrics: [], message: message) + } + + static func failure(_ provider: ProviderName, _ message: String) -> ProviderStatus { + ProviderStatus(provider: provider, status: .error, plan: nil, metrics: [], message: message) + } +} + +/// ISO-8601 parsing that tolerates fractional seconds (Anthropic) and plain +/// internet date-time strings. +enum DateParsing { + private static let fractional: ISO8601DateFormatter = { + let f = ISO8601DateFormatter() + f.formatOptions = [.withInternetDateTime, .withFractionalSeconds] + return f + }() + + private static let plain: ISO8601DateFormatter = { + let f = ISO8601DateFormatter() + f.formatOptions = [.withInternetDateTime] + return f + }() + + static func date(from string: String) -> Date? { + fractional.date(from: string) ?? plain.date(from: string) + } +} diff --git a/Sources/AgentLimit/Providers.swift b/Sources/AgentLimit/Providers.swift new file mode 100644 index 0000000..d821a7b --- /dev/null +++ b/Sources/AgentLimit/Providers.swift @@ -0,0 +1,147 @@ +import Foundation + +protocol UsageProvider { + var name: ProviderName { get } + func fetch() async -> ProviderStatus +} + +// MARK: - Claude + +struct ClaudeProvider: UsageProvider { + let name: ProviderName = .claude + + func fetch() async -> ProviderStatus { + guard let credentials = Credentials.claude() else { + return .unavailable(.claude, "Not logged in. Run 'claude' to authenticate.") + } + + var request = URLRequest(url: URL(string: "https://api.anthropic.com/api/oauth/usage")!) + request.httpMethod = "GET" + request.setValue("application/json", forHTTPHeaderField: "Accept") + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.setValue("AgentLimit/1.0", forHTTPHeaderField: "User-Agent") + request.setValue("Bearer \(credentials.accessToken)", forHTTPHeaderField: "Authorization") + request.setValue("oauth-2025-04-20", forHTTPHeaderField: "anthropic-beta") + + do { + let (data, response) = try await URLSession.shared.data(for: request) + guard let http = response as? HTTPURLResponse else { + return .failure(.claude, "No response from server.") + } + if http.statusCode == 401 { + return .failure(.claude, "Token expired. Run 'claude' to re-authenticate.") + } + guard http.statusCode == 200 else { + return .failure(.claude, "API error: \(http.statusCode)") + } + + let json = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any] ?? [:] + var metrics: [UsageMetric] = [] + + func window(_ key: String, name: String, period: Double) -> UsageMetric? { + guard let w = json[key] as? [String: Any], + let utilization = (w["utilization"] as? NSNumber)?.doubleValue + else { return nil } + let resetsAt = (w["resets_at"] as? String).flatMap(DateParsing.date(from:)) + return UsageMetric(name: name, percentage: utilization, resetsAt: resetsAt, periodSeconds: period) + } + + if let m = window("five_hour", name: "5-hour", period: 5 * 3600) { metrics.append(m) } + if let m = window("seven_day", name: "Weekly", period: 7 * 24 * 3600) { metrics.append(m) } + if let m = window("seven_day_opus", name: "Opus", period: 7 * 24 * 3600), m.percentage > 0 { + metrics.append(m) + } + + let maxUsage = metrics.map(\.percentage).max() ?? 0 + return ProviderStatus( + provider: .claude, + status: maxUsage >= 80 ? .warning : .ok, + plan: credentials.subscriptionType ?? "Pro", + metrics: metrics, + message: nil + ) + } catch { + return .failure(.claude, error.localizedDescription) + } + } +} + +// MARK: - Codex + +struct CodexProvider: UsageProvider { + let name: ProviderName = .codex + + func fetch() async -> ProviderStatus { + guard let credentials = Credentials.codex() else { + return .unavailable(.codex, "Not logged in. Run 'codex' to authenticate.") + } + + var request = URLRequest(url: URL(string: "https://chatgpt.com/backend-api/wham/usage")!) + request.setValue("Bearer \(credentials.accessToken)", forHTTPHeaderField: "Authorization") + request.setValue(credentials.accountId, forHTTPHeaderField: "ChatGPT-Account-Id") + request.setValue("codex_cli_rs", forHTTPHeaderField: "originator") + request.setValue("codex_cli_rs/0.77.0", forHTTPHeaderField: "User-Agent") + + do { + let (data, response) = try await URLSession.shared.data(for: request) + guard let http = response as? HTTPURLResponse else { + return .failure(.codex, "No response from server.") + } + if http.statusCode == 401 { + return .failure(.codex, "Token expired. Run 'codex' to re-authenticate.") + } + guard http.statusCode == 200 else { + return .failure(.codex, "API error: \(http.statusCode)") + } + + let json = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any] ?? [:] + let rateLimit = json["rate_limit"] as? [String: Any] + + var metrics: [UsageMetric] = [] + + func parse(_ w: [String: Any], primary: Bool) -> UsageMetric? { + guard let used = (w["used_percent"] as? NSNumber)?.doubleValue, + let windowSeconds = (w["limit_window_seconds"] as? NSNumber)?.doubleValue + else { return nil } + + let resetsAt = (w["reset_at"] as? NSNumber) + .map { Date(timeIntervalSince1970: $0.doubleValue) } + + let name: String + if primary { + let hours = Int((windowSeconds / 3600).rounded()) + name = "\(hours)-hour" + } else { + let days = Int((windowSeconds / 86400).rounded()) + name = days >= 7 ? "Weekly" : "\(days)-day" + } + return UsageMetric(name: name, percentage: used, resetsAt: resetsAt, periodSeconds: windowSeconds) + } + + if let primary = rateLimit?["primary_window"] as? [String: Any], + let m = parse(primary, primary: true) { + metrics.append(m) + } + if let secondary = rateLimit?["secondary_window"] as? [String: Any], + let m = parse(secondary, primary: false) { + metrics.append(m) + } + + let limitReached = (rateLimit?["limit_reached"] as? Bool) ?? false + let maxUsage = metrics.map(\.percentage).max() ?? 0 + let planType = (json["plan_type"] as? String).map { + $0.prefix(1).uppercased() + $0.dropFirst() + } ?? "Unknown" + + return ProviderStatus( + provider: .codex, + status: (limitReached || maxUsage >= 80) ? .warning : .ok, + plan: planType, + metrics: metrics, + message: nil + ) + } catch { + return .failure(.codex, error.localizedDescription) + } + } +} diff --git a/Sources/AgentLimit/UsageHistory.swift b/Sources/AgentLimit/UsageHistory.swift new file mode 100644 index 0000000..65d1da4 --- /dev/null +++ b/Sources/AgentLimit/UsageHistory.swift @@ -0,0 +1,77 @@ +import Foundation + +/// One recorded observation of a usage window. +struct UsageSample: Codable { + let date: Date + /// Percentage *used* at the time of the sample (0...100). + let percentage: Double +} + +/// Persists a rolling history of usage samples so the burndown chart can draw +/// the real, jagged usage curve instead of a single point. Samples are keyed by +/// provider + window + reset time, so each limit window gets its own series and +/// old windows are pruned after they reset. +final class UsageHistoryStore { + static let shared = UsageHistoryStore() + + private let queue = DispatchQueue(label: "com.agentworkforce.agentlimit.history") + private var cache: [String: [UsageSample]] = [:] + private let fileURL: URL + + private init() { + let dir = FileManager.default + .urls(for: .applicationSupportDirectory, in: .userDomainMask)[0] + .appendingPathComponent("AgentLimit", isDirectory: true) + try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) + fileURL = dir.appendingPathComponent("history.json") + + if let data = try? Data(contentsOf: fileURL), + let decoded = try? JSONDecoder().decode([String: [UsageSample]].self, from: data) { + cache = decoded + } + } + + private func key(provider: ProviderName, metric: UsageMetric) -> String { + let reset = metric.resetsAt.map { String(Int($0.timeIntervalSince1970)) } ?? "none" + return "\(provider.rawValue)|\(metric.name)|\(reset)" + } + + /// Records the current value of a metric and returns the full sample series + /// for the metric's current window. + @discardableResult + func record(provider: ProviderName, metric: UsageMetric, at date: Date = Date()) -> [UsageSample] { + queue.sync { + let k = key(provider: provider, metric: metric) + var samples = cache[k] ?? [] + + // Collapse rapid duplicate samples (e.g. manual refreshes). + if let last = samples.last, date.timeIntervalSince(last.date) < 1 { + samples[samples.count - 1] = UsageSample(date: date, percentage: metric.percentage) + } else { + samples.append(UsageSample(date: date, percentage: metric.percentage)) + } + + cache[k] = samples + pruneStaleWindows(reference: date) + persist() + return samples + } + } + + /// Drops series for windows that reset more than an hour ago. + private func pruneStaleWindows(reference: Date) { + for k in cache.keys { + let parts = k.split(separator: "|") + guard parts.count == 3, let ts = Double(parts[2]), ts != 0 else { continue } + let resetDate = Date(timeIntervalSince1970: ts) + if resetDate < reference.addingTimeInterval(-3600) { + cache.removeValue(forKey: k) + } + } + } + + private func persist() { + guard let data = try? JSONEncoder().encode(cache) else { return } + try? data.write(to: fileURL) + } +} diff --git a/Sources/AgentLimit/UsageViewModel.swift b/Sources/AgentLimit/UsageViewModel.swift new file mode 100644 index 0000000..d1c419b --- /dev/null +++ b/Sources/AgentLimit/UsageViewModel.swift @@ -0,0 +1,79 @@ +import SwiftUI + +/// Drives data loading and exposes view state to SwiftUI. Refreshes on a timer +/// so the menu bar label stays current even while the popover is closed. +@MainActor +final class UsageViewModel: ObservableObject { + @Published private(set) var selectedProvider: ProviderName + @Published private(set) var status: ProviderStatus? + @Published private(set) var charts: [(metric: UsageMetric, data: BurndownData?)] = [] + @Published private(set) var lastUpdated: Date? + @Published private(set) var isLoading = false + + let refreshInterval: TimeInterval = 60 + + private var timer: Timer? + private let providers: [ProviderName: UsageProvider] = [ + .claude: ClaudeProvider(), + .codex: CodexProvider(), + ] + + init() { + if let raw = UserDefaults.standard.string(forKey: "selectedProvider"), + let provider = ProviderName(rawValue: raw) { + selectedProvider = provider + } else { + selectedProvider = .codex + } + start() + } + + private func start() { + Task { await refresh() } + timer = Timer.scheduledTimer(withTimeInterval: refreshInterval, repeats: true) { [weak self] _ in + Task { await self?.refresh() } + } + } + + func select(_ provider: ProviderName) { + guard provider != selectedProvider else { return } + selectedProvider = provider + UserDefaults.standard.set(provider.rawValue, forKey: "selectedProvider") + status = nil + charts = [] + Task { await refresh() } + } + + func refresh() async { + guard let provider = providers[selectedProvider] else { return } + isLoading = true + let result = await provider.fetch() + let now = Date() + + var built: [(metric: UsageMetric, data: BurndownData?)] = [] + if result.status == .ok || result.status == .warning { + for metric in result.metrics { + let samples = UsageHistoryStore.shared.record(provider: result.provider, metric: metric, at: now) + let data = BurndownBuilder.build(metric: metric, samples: samples, now: now) + built.append((metric, data)) + } + } + + // Ignore results for a provider the user switched away from mid-flight. + guard result.provider == selectedProvider else { + isLoading = false + return + } + + status = result + charts = built + lastUpdated = now + isLoading = false + } + + /// Highest used percentage across windows, for the menu bar label. + var headlineUsage: Int? { + guard let metrics = status?.metrics, let max = metrics.map(\.percentage).max() else { return nil } + return Int(max.rounded()) + } +} diff --git a/bin/cli.js b/bin/cli.js deleted file mode 100755 index 1403ed9..0000000 --- a/bin/cli.js +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env node - -import { execFileSync } from "child_process"; -import { dirname, join } from "path"; -import { fileURLToPath } from "url"; -import { existsSync } from "fs"; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const bunPath = join(__dirname, "..", "node_modules", ".bin", "bun"); -const cliPath = join(__dirname, "cli.tsx"); - -if (!existsSync(bunPath)) { - console.error("Error: bun not found at", bunPath); - console.error("Try running: npm install"); - process.exit(1); -} - -try { - execFileSync(bunPath, [cliPath, ...process.argv.slice(2)], { - stdio: "inherit", - env: { ...process.env, FORCE_COLOR: "1" }, - }); -} catch (err) { - process.exit(err.status ?? 1); -} diff --git a/bin/cli.tsx b/bin/cli.tsx deleted file mode 100755 index 2d5c9c8..0000000 --- a/bin/cli.tsx +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/env bun - -import React from "react"; -import { createCliRenderer } from "@opentui/core"; -import { createRoot } from "@opentui/react"; -import { App } from "../src/App"; - -const command = process.argv[2]; - -if (command === "usage") { - const renderer = await createCliRenderer({ - exitOnCtrlC: false, - }); - - const root = createRoot(renderer); - - const cleanup = async () => { - root.unmount(); - await renderer.destroy(); - process.stdout.write("\x1b[?25h"); // Ensure cursor is visible - process.exit(0); - }; - - process.on("SIGINT", cleanup); - process.on("SIGTERM", cleanup); - - root.render(); -} else if (command === "version" || command === "--version" || command === "-v") { - const pkg = await import("../package.json"); - console.log(pkg.version); -} else if (command === "update") { - const pkg = await import("../package.json"); - const currentVersion = pkg.version; - - console.log(`Current version: ${currentVersion}`); - console.log("Checking for updates..."); - - try { - const response = await fetch("https://registry.npmjs.org/agent-limit/latest"); - if (!response.ok) { - throw new Error(`Failed to fetch: ${response.status}`); - } - const data = await response.json() as { version: string }; - const latestVersion = data.version; - - if (latestVersion === currentVersion) { - console.log(`✓ You're on the latest version (${currentVersion})`); - } else { - console.log(`New version available: ${latestVersion}`); - console.log("\nUpdating..."); - - const proc = Bun.spawn(["npm", "install", "-g", "agent-limit@latest"], { - stdout: "inherit", - stderr: "inherit", - }); - - const exitCode = await proc.exited; - - if (exitCode === 0) { - console.log(`\n✓ Updated to ${latestVersion}`); - } else { - console.error("\n✗ Update failed. Try running manually:"); - console.error(" npm install -g agent-limit@latest"); - process.exit(1); - } - } - } catch (error) { - console.error("Failed to check for updates:", error instanceof Error ? error.message : error); - process.exit(1); - } -} else if (command === "help" || command === "--help" || command === "-h" || !command) { - console.log(` -agent-limit - -Monitor AI agent CLI usage limits in real-time. - -Install: - npm install -g agent-limit - -Quick Start: - agent-limit usage - -CLI: - agent-limit usage Show usage dashboard - agent-limit update Update to latest version - agent-limit version Show version - agent-limit help Show this help message - -Dashboard Controls: - q Quit - r Refresh -`); -} else { - console.error(`Unknown command: ${command}`); - console.error(`Run 'agent-limit help' for usage.`); - process.exit(1); -} diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..885dfd8 --- /dev/null +++ b/build.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# +# Builds AgentLimit.app — a macOS menu bar app — from the Swift package. +# Requires macOS with the Swift toolchain (Xcode or Command Line Tools). +# +set -euo pipefail + +APP_NAME="AgentLimit" +CONFIG="release" + +cd "$(dirname "$0")" + +echo "Building $APP_NAME ($CONFIG)…" +swift build -c "$CONFIG" + +BIN_PATH="$(swift build -c "$CONFIG" --show-bin-path)" +APP_DIR="dist/${APP_NAME}.app" + +rm -rf "$APP_DIR" +mkdir -p "$APP_DIR/Contents/MacOS" +mkdir -p "$APP_DIR/Contents/Resources" + +cp "$BIN_PATH/$APP_NAME" "$APP_DIR/Contents/MacOS/$APP_NAME" +cp "App/Info.plist" "$APP_DIR/Contents/Info.plist" + +echo "Built $APP_DIR" +echo +echo "Launch it with: open \"$APP_DIR\"" +echo "Install it with: cp -R \"$APP_DIR\" /Applications/" diff --git a/bun.lock b/bun.lock deleted file mode 100644 index f1969a7..0000000 --- a/bun.lock +++ /dev/null @@ -1,232 +0,0 @@ -{ - "lockfileVersion": 1, - "configVersion": 1, - "workspaces": { - "": { - "name": "monitor", - "dependencies": { - "@opentui/core": "^0.1.67", - "@opentui/react": "^0.1.63", - "react": "^19.0.0", - }, - "devDependencies": { - "@types/bun": "latest", - "@types/react": "^19.0.0", - "typescript": "^5.0.0", - }, - }, - }, - "packages": { - "@dimforge/rapier2d-simd-compat": ["@dimforge/rapier2d-simd-compat@0.17.3", "", {}, "sha512-bijvwWz6NHsNj5e5i1vtd3dU2pDhthSaTUZSh14DUGGKJfw8eMnlWZsxwHBxB/a3AXVNDjL9abuHw1k9FGR+jg=="], - - "@jimp/core": ["@jimp/core@1.6.0", "", { "dependencies": { "@jimp/file-ops": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "await-to-js": "^3.0.0", "exif-parser": "^0.1.12", "file-type": "^16.0.0", "mime": "3" } }, "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w=="], - - "@jimp/diff": ["@jimp/diff@1.6.0", "", { "dependencies": { "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "pixelmatch": "^5.3.0" } }, "sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw=="], - - "@jimp/file-ops": ["@jimp/file-ops@1.6.0", "", {}, "sha512-Dx/bVDmgnRe1AlniRpCKrGRm5YvGmUwbDzt+MAkgmLGf+jvBT75hmMEZ003n9HQI/aPnm/YKnXjg/hOpzNCpHQ=="], - - "@jimp/js-bmp": ["@jimp/js-bmp@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "bmp-ts": "^1.0.9" } }, "sha512-FU6Q5PC/e3yzLyBDXupR3SnL3htU7S3KEs4e6rjDP6gNEOXRFsWs6YD3hXuXd50jd8ummy+q2WSwuGkr8wi+Gw=="], - - "@jimp/js-gif": ["@jimp/js-gif@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "gifwrap": "^0.10.1", "omggif": "^1.0.10" } }, "sha512-N9CZPHOrJTsAUoWkWZstLPpwT5AwJ0wge+47+ix3++SdSL/H2QzyMqxbcDYNFe4MoI5MIhATfb0/dl/wmX221g=="], - - "@jimp/js-jpeg": ["@jimp/js-jpeg@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "jpeg-js": "^0.4.4" } }, "sha512-6vgFDqeusblf5Pok6B2DUiMXplH8RhIKAryj1yn+007SIAQ0khM1Uptxmpku/0MfbClx2r7pnJv9gWpAEJdMVA=="], - - "@jimp/js-png": ["@jimp/js-png@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "pngjs": "^7.0.0" } }, "sha512-AbQHScy3hDDgMRNfG0tPjL88AV6qKAILGReIa3ATpW5QFjBKpisvUaOqhzJ7Reic1oawx3Riyv152gaPfqsBVg=="], - - "@jimp/js-tiff": ["@jimp/js-tiff@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "utif2": "^4.1.0" } }, "sha512-zhReR8/7KO+adijj3h0ZQUOiun3mXUv79zYEAKvE0O+rP7EhgtKvWJOZfRzdZSNv0Pu1rKtgM72qgtwe2tFvyw=="], - - "@jimp/plugin-blit": ["@jimp/plugin-blit@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-M+uRWl1csi7qilnSK8uxK4RJMSuVeBiO1AY0+7APnfUbQNZm6hCe0CCFv1Iyw1D/Dhb8ph8fQgm5mwM0eSxgVA=="], - - "@jimp/plugin-blur": ["@jimp/plugin-blur@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/utils": "1.6.0" } }, "sha512-zrM7iic1OTwUCb0g/rN5y+UnmdEsT3IfuCXCJJNs8SZzP0MkZ1eTvuwK9ZidCuMo4+J3xkzCidRwYXB5CyGZTw=="], - - "@jimp/plugin-circle": ["@jimp/plugin-circle@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-xt1Gp+LtdMKAXfDp3HNaG30SPZW6AQ7dtAtTnoRKorRi+5yCJjKqXRgkewS5bvj8DEh87Ko1ydJfzqS3P2tdWw=="], - - "@jimp/plugin-color": ["@jimp/plugin-color@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "tinycolor2": "^1.6.0", "zod": "^3.23.8" } }, "sha512-J5q8IVCpkBsxIXM+45XOXTrsyfblyMZg3a9eAo0P7VPH4+CrvyNQwaYatbAIamSIN1YzxmO3DkIZXzRjFSz1SA=="], - - "@jimp/plugin-contain": ["@jimp/plugin-contain@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-oN/n+Vdq/Qg9bB4yOBOxtY9IPAtEfES8J1n9Ddx+XhGBYT1/QTU/JYkGaAkIGoPnyYvmLEDqMz2SGihqlpqfzQ=="], - - "@jimp/plugin-cover": ["@jimp/plugin-cover@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-Iow0h6yqSC269YUJ8HC3Q/MpCi2V55sMlbkkTTx4zPvd8mWZlC0ykrNDeAy9IJegrQ7v5E99rJwmQu25lygKLA=="], - - "@jimp/plugin-crop": ["@jimp/plugin-crop@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-KqZkEhvs+21USdySCUDI+GFa393eDIzbi1smBqkUPTE+pRwSWMAf01D5OC3ZWB+xZsNla93BDS9iCkLHA8wang=="], - - "@jimp/plugin-displace": ["@jimp/plugin-displace@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-4Y10X9qwr5F+Bo5ME356XSACEF55485j5nGdiyJ9hYzjQP9nGgxNJaZ4SAOqpd+k5sFaIeD7SQ0Occ26uIng5Q=="], - - "@jimp/plugin-dither": ["@jimp/plugin-dither@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0" } }, "sha512-600d1RxY0pKwgyU0tgMahLNKsqEcxGdbgXadCiVCoGd6V6glyCvkNrnnwC0n5aJ56Htkj88PToSdF88tNVZEEQ=="], - - "@jimp/plugin-fisheye": ["@jimp/plugin-fisheye@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-E5QHKWSCBFtpgZarlmN3Q6+rTQxjirFqo44ohoTjzYVrDI6B6beXNnPIThJgPr0Y9GwfzgyarKvQuQuqCnnfbA=="], - - "@jimp/plugin-flip": ["@jimp/plugin-flip@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-/+rJVDuBIVOgwoyVkBjUFHtP+wmW0r+r5OQ2GpatQofToPVbJw1DdYWXlwviSx7hvixTWLKVgRWQ5Dw862emDg=="], - - "@jimp/plugin-hash": ["@jimp/plugin-hash@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/js-bmp": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/js-tiff": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "any-base": "^1.1.0" } }, "sha512-wWzl0kTpDJgYVbZdajTf+4NBSKvmI3bRI8q6EH9CVeIHps9VWVsUvEyb7rpbcwVLWYuzDtP2R0lTT6WeBNQH9Q=="], - - "@jimp/plugin-mask": ["@jimp/plugin-mask@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-Cwy7ExSJMZszvkad8NV8o/Z92X2kFUFM8mcDAhNVxU0Q6tA0op2UKRJY51eoK8r6eds/qak3FQkXakvNabdLnA=="], - - "@jimp/plugin-print": ["@jimp/plugin-print@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/types": "1.6.0", "parse-bmfont-ascii": "^1.0.6", "parse-bmfont-binary": "^1.0.6", "parse-bmfont-xml": "^1.1.6", "simple-xml-to-json": "^1.2.2", "zod": "^3.23.8" } }, "sha512-zarTIJi8fjoGMSI/M3Xh5yY9T65p03XJmPsuNet19K/Q7mwRU6EV2pfj+28++2PV2NJ+htDF5uecAlnGyxFN2A=="], - - "@jimp/plugin-quantize": ["@jimp/plugin-quantize@1.6.0", "", { "dependencies": { "image-q": "^4.0.0", "zod": "^3.23.8" } }, "sha512-EmzZ/s9StYQwbpG6rUGBCisc3f64JIhSH+ncTJd+iFGtGo0YvSeMdAd+zqgiHpfZoOL54dNavZNjF4otK+mvlg=="], - - "@jimp/plugin-resize": ["@jimp/plugin-resize@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA=="], - - "@jimp/plugin-rotate": ["@jimp/plugin-rotate@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-JagdjBLnUZGSG4xjCLkIpQOZZ3Mjbg8aGCCi4G69qR+OjNpOeGI7N2EQlfK/WE8BEHOW5vdjSyglNqcYbQBWRw=="], - - "@jimp/plugin-threshold": ["@jimp/plugin-threshold@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-hash": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-M59m5dzLoHOVWdM41O8z9SyySzcDn43xHseOH0HavjsfQsT56GGCC4QzU1banJidbUrePhzoEdS42uFE8Fei8w=="], - - "@jimp/types": ["@jimp/types@1.6.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg=="], - - "@jimp/utils": ["@jimp/utils@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "tinycolor2": "^1.6.0" } }, "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA=="], - - "@opentui/core": ["@opentui/core@0.1.67", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.67", "@opentui/core-darwin-x64": "0.1.67", "@opentui/core-linux-arm64": "0.1.67", "@opentui/core-linux-x64": "0.1.67", "@opentui/core-win32-arm64": "0.1.67", "@opentui/core-win32-x64": "0.1.67", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-zmfyA10QUbzT6ohacPoHmGiYzuJrDSCfQWRWrKtao0BrHj9bii73qWy3V/eR4ibVueoRREwxJs5GlBOSvK6IoA=="], - - "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.67", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LtOcTlFD+kO7neItmkiF77H8cnjTYzBOZe8JQGwRSt9aaCke3UzMvLxmQnj4BP/kPC3hi9V6NRnFdptz0sJZIQ=="], - - "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.67", "", { "os": "darwin", "cpu": "x64" }, "sha512-9i+awVWgpEVqZhFLaLq8usNGyCiyT5QxMLy6eH7JmRic79S34u23HfxiniGRtdYh3aqpm9SbLzo60v0nRIUkCA=="], - - "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.67", "", { "os": "linux", "cpu": "arm64" }, "sha512-WLjnTM3Ig//SRo0FUZYZJ5TITVbR6dKDVg6axU2D+sMoUzJMBP/Xo04q/TvZ3wP764Yca9l7oVMKWDxHlygyjQ=="], - - "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.67", "", { "os": "linux", "cpu": "x64" }, "sha512-5UbZ/TqWi/DAmHIZL4NvhdpgTwglszRiddkRiQ8cT0IbnE4lutd4XxWUWcLKwsNT1YJv32TtcGWkuthluLiriQ=="], - - "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.67", "", { "os": "win32", "cpu": "arm64" }, "sha512-KNam5rObhN8/U9+GVVuvtAlGXp3MfdMHnw4W2P6YH7xp8HTsLvABUT91SJEyJ/ktVe9e1itLDG2fDHSoA5NbUg=="], - - "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.67", "", { "os": "win32", "cpu": "x64" }, "sha512-740lkOw42zLNh9YfahXjCwV2DS/amH2uMDh3tCADDCLckrMhemIhqArXDiMlalDxDqYspoaZCpBsFVsG9dMS6A=="], - - "@opentui/react": ["@opentui/react@0.1.67", "", { "dependencies": { "@opentui/core": "0.1.67", "react-reconciler": "^0.32.0" }, "peerDependencies": { "react": ">=19.0.0", "react-devtools-core": "^7.0.1", "ws": "^8.18.0" } }, "sha512-gmK+O5xFUWypjE2LuYAs4o/qg/Bh2iG5Fu7WAqla3+ZtYVoDFPcTrZqN1Jj4dOcaXlmmK1e82zZ/dCknsXof7w=="], - - "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], - - "@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="], - - "@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="], - - "@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="], - - "@webgpu/types": ["@webgpu/types@0.1.68", "", {}, "sha512-3ab1B59Ojb6RwjOspYLsTpCzbNB3ZaamIAxBMmvnNkiDoLTZUOBXZ9p5nAYVEkQlDdf6qAZWi1pqj9+ypiqznA=="], - - "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], - - "any-base": ["any-base@1.1.0", "", {}, "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg=="], - - "await-to-js": ["await-to-js@3.0.0", "", {}, "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g=="], - - "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], - - "bmp-ts": ["bmp-ts@1.0.9", "", {}, "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw=="], - - "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], - - "bun-ffi-structs": ["bun-ffi-structs@0.1.2", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-Lh1oQAYHDcnesJauieA4UNkWGXY9hYck7OA5IaRwE3Bp6K2F2pJSNYqq+hIy7P3uOvo3km3oxS8304g5gDMl/w=="], - - "bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="], - - "bun-webgpu": ["bun-webgpu@0.1.4", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.4", "bun-webgpu-darwin-x64": "^0.1.4", "bun-webgpu-linux-x64": "^0.1.4", "bun-webgpu-win32-x64": "^0.1.4" } }, "sha512-Kw+HoXl1PMWJTh9wvh63SSRofTA8vYBFCw0XEP1V1fFdQEDhI8Sgf73sdndE/oDpN/7CMx0Yv/q8FCvO39ROMQ=="], - - "bun-webgpu-darwin-arm64": ["bun-webgpu-darwin-arm64@0.1.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-eDgLN9teKTfmvrCqgwwmWNsNszxYs7IZdCqk0S1DCarvMhr4wcajoSBlA/nQA0/owwLduPTS8xxCnQp4/N/gDg=="], - - "bun-webgpu-darwin-x64": ["bun-webgpu-darwin-x64@0.1.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-X+PjwJUWenUmdQBP8EtdItMyieQ6Nlpn+BH518oaouDiSnWj5+b0Y7DNDZJq7Ezom4EaxmqL/uGYZK3aCQ7CXg=="], - - "bun-webgpu-linux-x64": ["bun-webgpu-linux-x64@0.1.4", "", { "os": "linux", "cpu": "x64" }, "sha512-zMLs2YIGB+/jxrYFXaFhVKX/GBt05UTF45lc9srcHc9JXGjEj+12CIo1CHLTAWatXMTqt0Jsu6ukWEoWVT/ayA=="], - - "bun-webgpu-win32-x64": ["bun-webgpu-win32-x64@0.1.4", "", { "os": "win32", "cpu": "x64" }, "sha512-Z5yAK28xrcm8Wb5k7TZ8FJKpOI/r+aVCRdlHYAqI2SDJFN3nD4mJs900X6kNVmG/xFzb5yOuKVYWGg+6ZXWbyA=="], - - "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], - - "diff": ["diff@8.0.2", "", {}, "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg=="], - - "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], - - "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], - - "exif-parser": ["exif-parser@0.1.12", "", {}, "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="], - - "file-type": ["file-type@16.5.4", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="], - - "gifwrap": ["gifwrap@0.10.1", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="], - - "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], - - "image-q": ["image-q@4.0.0", "", { "dependencies": { "@types/node": "16.9.1" } }, "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw=="], - - "jimp": ["jimp@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/diff": "1.6.0", "@jimp/js-bmp": "1.6.0", "@jimp/js-gif": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/js-tiff": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/plugin-blur": "1.6.0", "@jimp/plugin-circle": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-contain": "1.6.0", "@jimp/plugin-cover": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-displace": "1.6.0", "@jimp/plugin-dither": "1.6.0", "@jimp/plugin-fisheye": "1.6.0", "@jimp/plugin-flip": "1.6.0", "@jimp/plugin-hash": "1.6.0", "@jimp/plugin-mask": "1.6.0", "@jimp/plugin-print": "1.6.0", "@jimp/plugin-quantize": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/plugin-rotate": "1.6.0", "@jimp/plugin-threshold": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0" } }, "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg=="], - - "jpeg-js": ["jpeg-js@0.4.4", "", {}, "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg=="], - - "mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="], - - "omggif": ["omggif@1.0.10", "", {}, "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw=="], - - "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], - - "parse-bmfont-ascii": ["parse-bmfont-ascii@1.0.6", "", {}, "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA=="], - - "parse-bmfont-binary": ["parse-bmfont-binary@1.0.6", "", {}, "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA=="], - - "parse-bmfont-xml": ["parse-bmfont-xml@1.1.6", "", { "dependencies": { "xml-parse-from-string": "^1.0.0", "xml2js": "^0.5.0" } }, "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA=="], - - "peek-readable": ["peek-readable@4.1.0", "", {}, "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg=="], - - "pixelmatch": ["pixelmatch@5.3.0", "", { "dependencies": { "pngjs": "^6.0.0" }, "bin": { "pixelmatch": "bin/pixelmatch" } }, "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q=="], - - "planck": ["planck@1.4.2", "", { "peerDependencies": { "stage-js": "^1.0.0-alpha.12" } }, "sha512-mNbhnV3g8X2rwGxzcesjmN8BDA6qfXgQxXVMkWau9MCRlQY0RLNEkyHlVp6yFy/X6qrzAXyNONCnZ1cGDLrNew=="], - - "pngjs": ["pngjs@7.0.0", "", {}, "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow=="], - - "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="], - - "react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="], - - "react-devtools-core": ["react-devtools-core@7.0.1", "", { "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" } }, "sha512-C3yNvRHaizlpiASzy7b9vbnBGLrhvdhl1CbdU6EnZgxPNbai60szdLtl+VL76UNOt5bOoVTOz5rNWZxgGt+Gsw=="], - - "react-reconciler": ["react-reconciler@0.32.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-2NPMOzgTlG0ZWdIf3qG+dcbLSoAc/uLfOwckc3ofy5sSK0pLJqnQLpUFxvGcN2rlXSjnVtGeeFLNimCQEj5gOQ=="], - - "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="], - - "readable-web-to-node-stream": ["readable-web-to-node-stream@3.0.4", "", { "dependencies": { "readable-stream": "^4.7.0" } }, "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw=="], - - "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], - - "sax": ["sax@1.4.3", "", {}, "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ=="], - - "scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="], - - "shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="], - - "simple-xml-to-json": ["simple-xml-to-json@1.2.3", "", {}, "sha512-kWJDCr9EWtZ+/EYYM5MareWj2cRnZGF93YDNpH4jQiHB+hBIZnfPFSQiVMzZOdk+zXWqTZ/9fTeQNu2DqeiudA=="], - - "stage-js": ["stage-js@1.0.0-alpha.17", "", {}, "sha512-AzlMO+t51v6cFvKZ+Oe9DJnL1OXEH5s9bEy6di5aOrUpcP7PCzI/wIeXF0u3zg0L89gwnceoKxrLId0ZpYnNXw=="], - - "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], - - "strtok3": ["strtok3@6.3.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^4.1.0" } }, "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw=="], - - "three": ["three@0.177.0", "", {}, "sha512-EiXv5/qWAaGI+Vz2A+JfavwYCMdGjxVsrn3oBwllUoqYeaBO75J63ZfyaQKoiLrqNHoTlUc6PFgMXnS0kI45zg=="], - - "tinycolor2": ["tinycolor2@1.6.0", "", {}, "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="], - - "token-types": ["token-types@4.2.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ=="], - - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - - "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], - - "utif2": ["utif2@4.1.0", "", { "dependencies": { "pako": "^1.0.11" } }, "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w=="], - - "web-tree-sitter": ["web-tree-sitter@0.25.10", "", { "peerDependencies": { "@types/emscripten": "^1.40.0" }, "optionalPeers": ["@types/emscripten"] }, "sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA=="], - - "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], - - "xml-parse-from-string": ["xml-parse-from-string@1.0.1", "", {}, "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g=="], - - "xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="], - - "xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="], - - "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], - - "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - - "image-q/@types/node": ["@types/node@16.9.1", "", {}, "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g=="], - - "pixelmatch/pngjs": ["pngjs@6.0.0", "", {}, "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg=="], - - "react-devtools-core/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="], - } -} diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 0289f8e..0000000 --- a/package-lock.json +++ /dev/null @@ -1,1336 +0,0 @@ -{ - "name": "agent-limit", - "version": "0.4.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "agent-limit", - "version": "0.4.0", - "license": "MIT", - "os": [ - "darwin" - ], - "dependencies": { - "@opentui/core": "^0.1.67", - "@opentui/react": "^0.1.63", - "bun": "^1.2.0", - "react": "^19.0.0" - }, - "bin": { - "agent-limit": "bin/cli.js" - }, - "devDependencies": { - "@types/bun": "latest", - "@types/react": "^19.0.0", - "typescript": "^5.0.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@dimforge/rapier2d-simd-compat": { - "version": "0.17.3", - "license": "Apache-2.0", - "optional": true - }, - "node_modules/@jimp/core": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "@jimp/file-ops": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "await-to-js": "^3.0.0", - "exif-parser": "^0.1.12", - "file-type": "^16.0.0", - "mime": "3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/diff": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "@jimp/plugin-resize": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "pixelmatch": "^5.3.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/file-ops": { - "version": "1.6.0", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/js-bmp": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "bmp-ts": "^1.0.9" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/js-gif": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "gifwrap": "^0.10.1", - "omggif": "^1.0.10" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/js-jpeg": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "jpeg-js": "^0.4.4" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/js-png": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "pngjs": "^7.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/js-tiff": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "utif2": "^4.1.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-blit": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-blur": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/utils": "1.6.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-circle": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "@jimp/types": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-color": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "tinycolor2": "^1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-contain": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/plugin-blit": "1.6.0", - "@jimp/plugin-resize": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-cover": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/plugin-crop": "1.6.0", - "@jimp/plugin-resize": "1.6.0", - "@jimp/types": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-crop": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-displace": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-dither": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "@jimp/types": "1.6.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-fisheye": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-flip": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "@jimp/types": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-hash": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/js-bmp": "1.6.0", - "@jimp/js-jpeg": "1.6.0", - "@jimp/js-png": "1.6.0", - "@jimp/js-tiff": "1.6.0", - "@jimp/plugin-color": "1.6.0", - "@jimp/plugin-resize": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "any-base": "^1.1.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-mask": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "@jimp/types": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-print": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/js-jpeg": "1.6.0", - "@jimp/js-png": "1.6.0", - "@jimp/plugin-blit": "1.6.0", - "@jimp/types": "1.6.0", - "parse-bmfont-ascii": "^1.0.6", - "parse-bmfont-binary": "^1.0.6", - "parse-bmfont-xml": "^1.1.6", - "simple-xml-to-json": "^1.2.2", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-quantize": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "image-q": "^4.0.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-resize": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-rotate": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/plugin-crop": "1.6.0", - "@jimp/plugin-resize": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-threshold": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/plugin-color": "1.6.0", - "@jimp/plugin-hash": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/types": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/utils": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "@jimp/types": "1.6.0", - "tinycolor2": "^1.6.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@opentui/core": { - "version": "0.1.67", - "license": "MIT", - "dependencies": { - "bun-ffi-structs": "0.1.2", - "diff": "8.0.2", - "jimp": "1.6.0", - "yoga-layout": "3.2.1" - }, - "optionalDependencies": { - "@dimforge/rapier2d-simd-compat": "^0.17.3", - "@opentui/core-darwin-arm64": "0.1.67", - "@opentui/core-darwin-x64": "0.1.67", - "@opentui/core-linux-arm64": "0.1.67", - "@opentui/core-linux-x64": "0.1.67", - "@opentui/core-win32-arm64": "0.1.67", - "@opentui/core-win32-x64": "0.1.67", - "bun-webgpu": "0.1.4", - "planck": "^1.4.2", - "three": "0.177.0" - }, - "peerDependencies": { - "web-tree-sitter": "0.25.10" - } - }, - "node_modules/@opentui/core-darwin-arm64": { - "version": "0.1.67", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@opentui/core-darwin-x64": { - "version": "0.1.67", - "resolved": "https://registry.npmjs.org/@opentui/core-darwin-x64/-/core-darwin-x64-0.1.67.tgz", - "integrity": "sha512-9i+awVWgpEVqZhFLaLq8usNGyCiyT5QxMLy6eH7JmRic79S34u23HfxiniGRtdYh3aqpm9SbLzo60v0nRIUkCA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@opentui/core-linux-arm64": { - "version": "0.1.67", - "resolved": "https://registry.npmjs.org/@opentui/core-linux-arm64/-/core-linux-arm64-0.1.67.tgz", - "integrity": "sha512-WLjnTM3Ig//SRo0FUZYZJ5TITVbR6dKDVg6axU2D+sMoUzJMBP/Xo04q/TvZ3wP764Yca9l7oVMKWDxHlygyjQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@opentui/core-linux-x64": { - "version": "0.1.67", - "resolved": "https://registry.npmjs.org/@opentui/core-linux-x64/-/core-linux-x64-0.1.67.tgz", - "integrity": "sha512-5UbZ/TqWi/DAmHIZL4NvhdpgTwglszRiddkRiQ8cT0IbnE4lutd4XxWUWcLKwsNT1YJv32TtcGWkuthluLiriQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@opentui/core-win32-arm64": { - "version": "0.1.67", - "resolved": "https://registry.npmjs.org/@opentui/core-win32-arm64/-/core-win32-arm64-0.1.67.tgz", - "integrity": "sha512-KNam5rObhN8/U9+GVVuvtAlGXp3MfdMHnw4W2P6YH7xp8HTsLvABUT91SJEyJ/ktVe9e1itLDG2fDHSoA5NbUg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@opentui/core-win32-x64": { - "version": "0.1.67", - "resolved": "https://registry.npmjs.org/@opentui/core-win32-x64/-/core-win32-x64-0.1.67.tgz", - "integrity": "sha512-740lkOw42zLNh9YfahXjCwV2DS/amH2uMDh3tCADDCLckrMhemIhqArXDiMlalDxDqYspoaZCpBsFVsG9dMS6A==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@opentui/react": { - "version": "0.1.67", - "license": "MIT", - "dependencies": { - "@opentui/core": "0.1.67", - "react-reconciler": "^0.32.0" - }, - "peerDependencies": { - "react": ">=19.0.0", - "react-devtools-core": "^7.0.1", - "ws": "^8.18.0" - } - }, - "node_modules/@oven/bun-darwin-aarch64": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@oven/bun-darwin-aarch64/-/bun-darwin-aarch64-1.3.5.tgz", - "integrity": "sha512-8GvNtMo0NINM7Emk9cNAviCG3teEgr3BUX9be0+GD029zIagx2Sf54jMui1Eu1IpFm7nWHODuLEefGOQNaJ0gQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@oven/bun-darwin-x64": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64/-/bun-darwin-x64-1.3.5.tgz", - "integrity": "sha512-r33eHQOHAwkuiBJIwmkXIyqONQOQMnd1GMTpDzaxx9vf9+svby80LZO9Hcm1ns6KT/TBRFyODC/0loA7FAaffg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@oven/bun-darwin-x64-baseline": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64-baseline/-/bun-darwin-x64-baseline-1.3.5.tgz", - "integrity": "sha512-p5q3rJk48qhLuLBOFehVc+kqCE03YrswTc6NCxbwsxiwfySXwcAvpF2KWKF/ZZObvvR8hCCvqe1F81b2p5r2dg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@oven/bun-linux-aarch64": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64/-/bun-linux-aarch64-1.3.5.tgz", - "integrity": "sha512-zkcHPI23QxJ1TdqafhgkXt1NOEN8o5C460sVeNnrhfJ43LwZgtfcvcQE39x/pBedu67fatY8CU0iY00nOh46ZQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oven/bun-linux-aarch64-musl": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64-musl/-/bun-linux-aarch64-musl-1.3.5.tgz", - "integrity": "sha512-HKBeUlJdNduRkzJKZ5DXM+pPqntfC50/Hu2X65jVX0Y7hu/6IC8RaUTqpr8FtCZqqmc9wDK0OTL+Mbi9UQIKYQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oven/bun-linux-x64": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64/-/bun-linux-x64-1.3.5.tgz", - "integrity": "sha512-n7zhKTSDZS0yOYg5Rq8easZu5Y/o47sv0c7yGr2ciFdcie9uYV55fZ7QMqhWMGK33ezCSikh5EDkUMCIvfWpjA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oven/bun-linux-x64-baseline": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-baseline/-/bun-linux-x64-baseline-1.3.5.tgz", - "integrity": "sha512-FeCQyBU62DMuB0nn01vPnf3McXrKOsrK9p7sHaBFYycw0mmoU8kCq/WkBkGMnLuvQljJSyen8QBTx+fXdNupWg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oven/bun-linux-x64-musl": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-musl/-/bun-linux-x64-musl-1.3.5.tgz", - "integrity": "sha512-XkCCHkByYn8BIDvoxnny898znju4xnW2kvFE8FT5+0Y62cWdcBGMZ9RdsEUTeRz16k8hHtJpaSfLcEmNTFIwRQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oven/bun-linux-x64-musl-baseline": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-musl-baseline/-/bun-linux-x64-musl-baseline-1.3.5.tgz", - "integrity": "sha512-TJiYC7KCr0XxFTsxgwQOeE7dncrEL/RSyL0EzSL3xRkrxJMWBCvCSjQn7LV1i6T7hFst0+3KoN3VWvD5BinqHA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oven/bun-windows-x64": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@oven/bun-windows-x64/-/bun-windows-x64-1.3.5.tgz", - "integrity": "sha512-T3xkODItb/0ftQPFsZDc7EAX2D6A4TEazQ2YZyofZToO8Q7y8YT8ooWdhd0BQiTCd66uEvgE1DCZetynwg2IoA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@oven/bun-windows-x64-baseline": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@oven/bun-windows-x64-baseline/-/bun-windows-x64-baseline-1.3.5.tgz", - "integrity": "sha512-rtVQB9/1XK8FWJgFtsOthbPifRMYypgJwxu+pK3NHx8WvFKmq7HcPDqNr8xLzGULjQEO7eAo2aOZfONOwYz+5g==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@tokenizer/token": { - "version": "0.3.0", - "license": "MIT" - }, - "node_modules/@types/bun": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.3.5.tgz", - "integrity": "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w==", - "dev": true, - "license": "MIT", - "dependencies": { - "bun-types": "1.3.5" - } - }, - "node_modules/@types/node": { - "version": "25.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@types/react": { - "version": "19.2.7", - "dev": true, - "license": "MIT", - "dependencies": { - "csstype": "^3.2.2" - } - }, - "node_modules/@webgpu/types": { - "version": "0.1.68", - "license": "BSD-3-Clause", - "optional": true - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "node_modules/any-base": { - "version": "1.1.0", - "license": "MIT" - }, - "node_modules/await-to-js": { - "version": "3.0.0", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/bmp-ts": { - "version": "1.0.9", - "license": "MIT" - }, - "node_modules/buffer": { - "version": "6.0.3", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/bun": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/bun/-/bun-1.3.5.tgz", - "integrity": "sha512-c1YHIGUfgvYPJmLug5QiLzNWlX2Dg7X/67JWu1Va+AmMXNXzC/KQn2lgQ7rD+n1u1UqDpJMowVGGxTNpbPydNw==", - "cpu": [ - "arm64", - "x64" - ], - "hasInstallScript": true, - "license": "MIT", - "os": [ - "darwin", - "linux", - "win32" - ], - "bin": { - "bun": "bin/bun.exe", - "bunx": "bin/bunx.exe" - }, - "optionalDependencies": { - "@oven/bun-darwin-aarch64": "1.3.5", - "@oven/bun-darwin-x64": "1.3.5", - "@oven/bun-darwin-x64-baseline": "1.3.5", - "@oven/bun-linux-aarch64": "1.3.5", - "@oven/bun-linux-aarch64-musl": "1.3.5", - "@oven/bun-linux-x64": "1.3.5", - "@oven/bun-linux-x64-baseline": "1.3.5", - "@oven/bun-linux-x64-musl": "1.3.5", - "@oven/bun-linux-x64-musl-baseline": "1.3.5", - "@oven/bun-windows-x64": "1.3.5", - "@oven/bun-windows-x64-baseline": "1.3.5" - } - }, - "node_modules/bun-ffi-structs": { - "version": "0.1.2", - "license": "MIT", - "peerDependencies": { - "typescript": "^5" - } - }, - "node_modules/bun-types": { - "version": "1.3.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/bun-webgpu": { - "version": "0.1.4", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@webgpu/types": "^0.1.60" - }, - "optionalDependencies": { - "bun-webgpu-darwin-arm64": "^0.1.4", - "bun-webgpu-darwin-x64": "^0.1.4", - "bun-webgpu-linux-x64": "^0.1.4", - "bun-webgpu-win32-x64": "^0.1.4" - } - }, - "node_modules/bun-webgpu-darwin-arm64": { - "version": "0.1.4", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/bun-webgpu-darwin-x64": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/bun-webgpu-darwin-x64/-/bun-webgpu-darwin-x64-0.1.4.tgz", - "integrity": "sha512-X+PjwJUWenUmdQBP8EtdItMyieQ6Nlpn+BH518oaouDiSnWj5+b0Y7DNDZJq7Ezom4EaxmqL/uGYZK3aCQ7CXg==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/bun-webgpu-linux-x64": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/bun-webgpu-linux-x64/-/bun-webgpu-linux-x64-0.1.4.tgz", - "integrity": "sha512-zMLs2YIGB+/jxrYFXaFhVKX/GBt05UTF45lc9srcHc9JXGjEj+12CIo1CHLTAWatXMTqt0Jsu6ukWEoWVT/ayA==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/bun-webgpu-win32-x64": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/bun-webgpu-win32-x64/-/bun-webgpu-win32-x64-0.1.4.tgz", - "integrity": "sha512-Z5yAK28xrcm8Wb5k7TZ8FJKpOI/r+aVCRdlHYAqI2SDJFN3nD4mJs900X6kNVmG/xFzb5yOuKVYWGg+6ZXWbyA==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/csstype": { - "version": "3.2.3", - "dev": true, - "license": "MIT" - }, - "node_modules/diff": { - "version": "8.0.2", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/events": { - "version": "3.3.0", - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/exif-parser": { - "version": "0.1.12" - }, - "node_modules/file-type": { - "version": "16.5.4", - "license": "MIT", - "dependencies": { - "readable-web-to-node-stream": "^3.0.0", - "strtok3": "^6.2.4", - "token-types": "^4.1.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/file-type?sponsor=1" - } - }, - "node_modules/gifwrap": { - "version": "0.10.1", - "license": "MIT", - "dependencies": { - "image-q": "^4.0.0", - "omggif": "^1.0.10" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/image-q": { - "version": "4.0.0", - "license": "MIT", - "dependencies": { - "@types/node": "16.9.1" - } - }, - "node_modules/image-q/node_modules/@types/node": { - "version": "16.9.1", - "license": "MIT" - }, - "node_modules/jimp": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/diff": "1.6.0", - "@jimp/js-bmp": "1.6.0", - "@jimp/js-gif": "1.6.0", - "@jimp/js-jpeg": "1.6.0", - "@jimp/js-png": "1.6.0", - "@jimp/js-tiff": "1.6.0", - "@jimp/plugin-blit": "1.6.0", - "@jimp/plugin-blur": "1.6.0", - "@jimp/plugin-circle": "1.6.0", - "@jimp/plugin-color": "1.6.0", - "@jimp/plugin-contain": "1.6.0", - "@jimp/plugin-cover": "1.6.0", - "@jimp/plugin-crop": "1.6.0", - "@jimp/plugin-displace": "1.6.0", - "@jimp/plugin-dither": "1.6.0", - "@jimp/plugin-fisheye": "1.6.0", - "@jimp/plugin-flip": "1.6.0", - "@jimp/plugin-hash": "1.6.0", - "@jimp/plugin-mask": "1.6.0", - "@jimp/plugin-print": "1.6.0", - "@jimp/plugin-quantize": "1.6.0", - "@jimp/plugin-resize": "1.6.0", - "@jimp/plugin-rotate": "1.6.0", - "@jimp/plugin-threshold": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/jpeg-js": { - "version": "0.4.4", - "license": "BSD-3-Clause" - }, - "node_modules/mime": { - "version": "3.0.0", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/omggif": { - "version": "1.0.10", - "license": "MIT" - }, - "node_modules/pako": { - "version": "1.0.11", - "license": "(MIT AND Zlib)" - }, - "node_modules/parse-bmfont-ascii": { - "version": "1.0.6", - "license": "MIT" - }, - "node_modules/parse-bmfont-binary": { - "version": "1.0.6", - "license": "MIT" - }, - "node_modules/parse-bmfont-xml": { - "version": "1.1.6", - "license": "MIT", - "dependencies": { - "xml-parse-from-string": "^1.0.0", - "xml2js": "^0.5.0" - } - }, - "node_modules/peek-readable": { - "version": "4.1.0", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/pixelmatch": { - "version": "5.3.0", - "license": "ISC", - "dependencies": { - "pngjs": "^6.0.0" - }, - "bin": { - "pixelmatch": "bin/pixelmatch" - } - }, - "node_modules/pixelmatch/node_modules/pngjs": { - "version": "6.0.0", - "license": "MIT", - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/planck": { - "version": "1.4.2", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14.0" - }, - "peerDependencies": { - "stage-js": "^1.0.0-alpha.12" - } - }, - "node_modules/pngjs": { - "version": "7.0.0", - "license": "MIT", - "engines": { - "node": ">=14.19.0" - } - }, - "node_modules/process": { - "version": "0.11.10", - "license": "MIT", - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/react": { - "version": "19.2.3", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-devtools-core": { - "version": "7.0.1", - "license": "MIT", - "peer": true, - "dependencies": { - "shell-quote": "^1.6.1", - "ws": "^7" - } - }, - "node_modules/react-devtools-core/node_modules/ws": { - "version": "7.5.10", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/react-reconciler": { - "version": "0.32.0", - "license": "MIT", - "dependencies": { - "scheduler": "^0.26.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "peerDependencies": { - "react": "^19.1.0" - } - }, - "node_modules/readable-stream": { - "version": "4.7.0", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/readable-web-to-node-stream": { - "version": "3.0.4", - "license": "MIT", - "dependencies": { - "readable-stream": "^4.7.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/sax": { - "version": "1.4.3", - "license": "BlueOak-1.0.0" - }, - "node_modules/scheduler": { - "version": "0.26.0", - "license": "MIT" - }, - "node_modules/shell-quote": { - "version": "1.8.3", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/simple-xml-to-json": { - "version": "1.2.3", - "license": "MIT", - "engines": { - "node": ">=20.12.2" - } - }, - "node_modules/stage-js": { - "version": "1.0.0-alpha.17", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=18.0" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/strtok3": { - "version": "6.3.0", - "license": "MIT", - "dependencies": { - "@tokenizer/token": "^0.3.0", - "peek-readable": "^4.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/three": { - "version": "0.177.0", - "license": "MIT", - "optional": true - }, - "node_modules/tinycolor2": { - "version": "1.6.0", - "license": "MIT" - }, - "node_modules/token-types": { - "version": "4.2.1", - "license": "MIT", - "dependencies": { - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "7.16.0", - "dev": true, - "license": "MIT" - }, - "node_modules/utif2": { - "version": "4.1.0", - "license": "MIT", - "dependencies": { - "pako": "^1.0.11" - } - }, - "node_modules/web-tree-sitter": { - "version": "0.25.10", - "license": "MIT", - "peer": true, - "peerDependencies": { - "@types/emscripten": "^1.40.0" - }, - "peerDependenciesMeta": { - "@types/emscripten": { - "optional": true - } - } - }, - "node_modules/ws": { - "version": "8.18.3", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xml-parse-from-string": { - "version": "1.0.1", - "license": "MIT" - }, - "node_modules/xml2js": { - "version": "0.5.0", - "license": "MIT", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xmlbuilder": { - "version": "11.0.1", - "license": "MIT", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/yoga-layout": { - "version": "3.2.1", - "license": "MIT" - }, - "node_modules/zod": { - "version": "3.25.76", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index c259d5a..0000000 --- a/package.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "name": "agent-limit", - "version": "0.7.2", - "description": "Terminal dashboard to monitor Claude Code, Codex, and other agent usage limits", - "type": "module", - "main": "src/index.tsx", - "bin": { - "agent-limit": "bin/cli.js" - }, - "scripts": { - "start": "bun run src/index.tsx", - "dev": "bun --watch run src/index.tsx", - "build": "bun build --compile --target=bun-darwin-arm64 ./bin/cli.tsx --outfile=dist/agent-limit-darwin-arm64 && bun build --compile --target=bun-darwin-x64 ./bin/cli.tsx --outfile=dist/agent-limit-darwin-x64", - "build:arm64": "bun build --compile --target=bun-darwin-arm64 ./bin/cli.tsx --outfile=dist/agent-limit-darwin-arm64", - "build:x64": "bun build --compile --target=bun-darwin-x64 ./bin/cli.tsx --outfile=dist/agent-limit-darwin-x64" - }, - "files": [ - "bin", - "src" - ], - "keywords": [ - "cli", - "terminal", - "dashboard", - "claude", - "codex", - "ai", - "agent", - "usage", - "limits", - "monitor", - "tui" - ], - "author": "Will Washburn", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/AgentWorkforce/limit.git" - }, - "bugs": { - "url": "https://github.com/AgentWorkforce/limit/issues" - }, - "homepage": "https://github.com/AgentWorkforce/limit", - "engines": { - "node": ">=18.0.0" - }, - "os": [ - "darwin" - ], - "dependencies": { - "@opentui/core": "^0.1.67", - "@opentui/react": "^0.1.63", - "bun": "^1.2.0", - "react": "^19.0.0" - }, - "devDependencies": { - "@types/bun": "latest", - "@types/react": "^19.0.0", - "typescript": "^5.0.0" - } -} diff --git a/src/App.tsx b/src/App.tsx deleted file mode 100644 index 359544e..0000000 --- a/src/App.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React, { useEffect, useState, useCallback } from "react"; -import { useKeyboard } from "@opentui/react"; -import { Header, Footer, Dashboard } from "./components"; -import { fetchAllProviders, type ProviderStatus } from "./providers"; - -const REFRESH_INTERVAL = 60000; - -interface AppProps { - onExit?: () => void; -} - -export function App({ onExit }: AppProps) { - const [providers, setProviders] = useState([ - { provider: "claude", status: "loading", metrics: [] }, - { provider: "codex", status: "loading", metrics: [] }, - ]); - const [lastRefresh, setLastRefresh] = useState(null); - const [isLoading, setIsLoading] = useState(true); - - const refresh = useCallback(async () => { - setIsLoading(true); - try { - const results = await fetchAllProviders(); - setProviders(results); - setLastRefresh(new Date()); - } catch (err) { - console.error("Failed to fetch providers:", err); - } finally { - setIsLoading(false); - } - }, []); - - useEffect(() => { - refresh(); - const interval = setInterval(refresh, REFRESH_INTERVAL); - return () => clearInterval(interval); - }, [refresh]); - - useKeyboard((key) => { - if (key.name === "q" || (key.ctrl && key.name === "c")) { - onExit?.(); - } - - if (key.name === "r") { - refresh(); - } - }); - - return ( - -
- -