copilot-usage-tui 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +83 -0
- package/package.json +27 -0
- package/src/api/auth.ts +148 -0
- package/src/api/github.ts +92 -0
- package/src/config/config.ts +70 -0
- package/src/index.ts +13 -0
- package/src/types.ts +90 -0
- package/src/ui/app.ts +215 -0
- package/src/ui/components/chart.ts +141 -0
- package/src/ui/components/progressBar.ts +114 -0
- package/src/ui/components/table.ts +136 -0
- package/src/ui/screens/auth.ts +107 -0
- package/src/ui/screens/dashboard.ts +275 -0
- package/src/ui/screens/setup.ts +188 -0
- package/src/utils/format.ts +61 -0
- package/src/utils/prediction.ts +62 -0
- package/tsconfig.json +19 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alexander Bisov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# copilot-usage-tui
|
|
2
|
+
|
|
3
|
+
A terminal UI for viewing your GitHub Copilot premium request usage.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- View your monthly premium request usage with a visual progress bar
|
|
10
|
+
- Model breakdown chart showing usage across different AI models
|
|
11
|
+
- Cost summary (gross, discount, net)
|
|
12
|
+
- End-of-month usage prediction with overage cost estimation
|
|
13
|
+
- Interactive plan/quota setup on first run
|
|
14
|
+
- Manual refresh with `r` key
|
|
15
|
+
|
|
16
|
+
## Requirements
|
|
17
|
+
|
|
18
|
+
- [Bun](https://bun.sh/) runtime
|
|
19
|
+
- [GitHub CLI](https://cli.github.com/) (`gh`) authenticated with the `user` scope
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
### Quick run (no install)
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
bunx copilot-usage-tui
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Install globally
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
bun install -g copilot-usage-tui
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Then run:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
copilot-usage-tui
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### From source
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
git clone https://github.com/abisov/copilot-usage-tui.git
|
|
45
|
+
cd copilot-usage-tui
|
|
46
|
+
bun install
|
|
47
|
+
bun run start
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Setup
|
|
51
|
+
|
|
52
|
+
Ensure your GitHub CLI has the required `user` scope:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
gh auth refresh -h github.com -s user
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Usage
|
|
59
|
+
|
|
60
|
+
On first run, you'll be prompted to select your Copilot plan to set your monthly quota.
|
|
61
|
+
|
|
62
|
+
### Keybindings
|
|
63
|
+
|
|
64
|
+
| Key | Action |
|
|
65
|
+
|-----|--------|
|
|
66
|
+
| `r` | Refresh usage data |
|
|
67
|
+
| `s` | Open settings (change plan) |
|
|
68
|
+
| `q` | Quit |
|
|
69
|
+
|
|
70
|
+
## Configuration
|
|
71
|
+
|
|
72
|
+
Config is stored at `~/.copilot-usage.json` and contains your selected plan and quota.
|
|
73
|
+
|
|
74
|
+
## How it works
|
|
75
|
+
|
|
76
|
+
Uses an undocumented GitHub billing API endpoint accessed via the `gh` CLI:
|
|
77
|
+
```
|
|
78
|
+
gh api users/{username}/settings/billing/premium_request/usage
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "copilot-usage-tui",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Terminal UI for viewing GitHub Copilot premium request usage",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"copilot-usage-tui": "./src/index.ts"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/abisov/copilot-usage-tui.git"
|
|
12
|
+
},
|
|
13
|
+
"keywords": ["github", "copilot", "tui", "terminal", "cli", "usage"],
|
|
14
|
+
"author": "abisov",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"scripts": {
|
|
17
|
+
"start": "bun run src/index.ts",
|
|
18
|
+
"dev": "bun --watch run src/index.ts"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@opentui/core": "^0.1.74"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/bun": "latest",
|
|
25
|
+
"typescript": "^5.0.0"
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/api/auth.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { AuthStatus } from "../types.ts"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Check if gh CLI is installed
|
|
5
|
+
*/
|
|
6
|
+
async function isGhInstalled(): Promise<boolean> {
|
|
7
|
+
try {
|
|
8
|
+
const proc = Bun.spawn(["which", "gh"], {
|
|
9
|
+
stdout: "pipe",
|
|
10
|
+
stderr: "pipe",
|
|
11
|
+
})
|
|
12
|
+
const exitCode = await proc.exited
|
|
13
|
+
return exitCode === 0
|
|
14
|
+
} catch {
|
|
15
|
+
return false
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Run gh auth status and parse the output
|
|
21
|
+
*/
|
|
22
|
+
async function getGhAuthStatus(): Promise<{
|
|
23
|
+
authenticated: boolean
|
|
24
|
+
hasUserScope: boolean
|
|
25
|
+
username?: string
|
|
26
|
+
}> {
|
|
27
|
+
try {
|
|
28
|
+
const proc = Bun.spawn(["gh", "auth", "status"], {
|
|
29
|
+
stdout: "pipe",
|
|
30
|
+
stderr: "pipe",
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
// gh auth status outputs to stderr
|
|
34
|
+
const stderr = await new Response(proc.stderr).text()
|
|
35
|
+
const stdout = await new Response(proc.stdout).text()
|
|
36
|
+
const output = stderr + stdout
|
|
37
|
+
const exitCode = await proc.exited
|
|
38
|
+
|
|
39
|
+
if (exitCode !== 0) {
|
|
40
|
+
return { authenticated: false, hasUserScope: false }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Parse username from "Logged in to github.com account USERNAME"
|
|
44
|
+
const usernameMatch = output.match(/Logged in to github\.com account (\w+)/)
|
|
45
|
+
const username = usernameMatch?.[1]
|
|
46
|
+
|
|
47
|
+
// Check for 'user' scope in token scopes
|
|
48
|
+
// Format: Token scopes: 'gist', 'read:org', 'repo', 'user', 'workflow'
|
|
49
|
+
const scopesLine = output.match(/Token scopes:\s*(.+)/)
|
|
50
|
+
const scopesStr = scopesLine?.[1] || ""
|
|
51
|
+
// Extract all quoted scope names
|
|
52
|
+
const scopeMatches = scopesStr.match(/'([^']+)'/g) || []
|
|
53
|
+
const scopes = scopeMatches.map(s => s.replace(/'/g, ""))
|
|
54
|
+
const hasUserScope = scopes.includes("user")
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
authenticated: true,
|
|
58
|
+
hasUserScope,
|
|
59
|
+
username,
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
return { authenticated: false, hasUserScope: false }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Check full authentication status
|
|
68
|
+
*/
|
|
69
|
+
export async function checkAuth(): Promise<AuthStatus> {
|
|
70
|
+
const ghInstalled = await isGhInstalled()
|
|
71
|
+
|
|
72
|
+
if (!ghInstalled) {
|
|
73
|
+
return {
|
|
74
|
+
ghInstalled: false,
|
|
75
|
+
authenticated: false,
|
|
76
|
+
hasUserScope: false,
|
|
77
|
+
error: "GitHub CLI (gh) is not installed",
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const { authenticated, hasUserScope, username } = await getGhAuthStatus()
|
|
82
|
+
|
|
83
|
+
if (!authenticated) {
|
|
84
|
+
return {
|
|
85
|
+
ghInstalled: true,
|
|
86
|
+
authenticated: false,
|
|
87
|
+
hasUserScope: false,
|
|
88
|
+
error: "Not authenticated with GitHub CLI",
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!hasUserScope) {
|
|
93
|
+
return {
|
|
94
|
+
ghInstalled: true,
|
|
95
|
+
authenticated: true,
|
|
96
|
+
hasUserScope: false,
|
|
97
|
+
username,
|
|
98
|
+
error: "Missing 'user' scope - run: gh auth refresh -s user",
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
ghInstalled: true,
|
|
104
|
+
authenticated: true,
|
|
105
|
+
hasUserScope: true,
|
|
106
|
+
username,
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get auth instructions for the user
|
|
112
|
+
*/
|
|
113
|
+
export function getAuthInstructions(status: AuthStatus): string[] {
|
|
114
|
+
if (!status.ghInstalled) {
|
|
115
|
+
return [
|
|
116
|
+
"GitHub CLI is not installed.",
|
|
117
|
+
"",
|
|
118
|
+
"Install it from: https://cli.github.com/",
|
|
119
|
+
"",
|
|
120
|
+
"Or use Homebrew:",
|
|
121
|
+
" brew install gh",
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!status.authenticated) {
|
|
126
|
+
return [
|
|
127
|
+
"GitHub CLI is not authenticated.",
|
|
128
|
+
"",
|
|
129
|
+
"Run this command in your terminal:",
|
|
130
|
+
" gh auth login",
|
|
131
|
+
"",
|
|
132
|
+
"Then press [r] to retry.",
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!status.hasUserScope) {
|
|
137
|
+
return [
|
|
138
|
+
"GitHub CLI is missing the required 'user' scope.",
|
|
139
|
+
"",
|
|
140
|
+
"Run this command in your terminal:",
|
|
141
|
+
" gh auth refresh -h github.com -s user",
|
|
142
|
+
"",
|
|
143
|
+
"Then press [r] to retry.",
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return ["Authentication successful!"]
|
|
148
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { UsageResponse, UsageSummary } from "../types.ts"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Get the current authenticated username
|
|
5
|
+
*/
|
|
6
|
+
export async function getUsername(): Promise<string | null> {
|
|
7
|
+
try {
|
|
8
|
+
const proc = Bun.spawn(["gh", "api", "user", "--jq", ".login"], {
|
|
9
|
+
stdout: "pipe",
|
|
10
|
+
stderr: "pipe",
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
const stdout = await new Response(proc.stdout).text()
|
|
14
|
+
const exitCode = await proc.exited
|
|
15
|
+
|
|
16
|
+
if (exitCode !== 0) {
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return stdout.trim()
|
|
21
|
+
} catch {
|
|
22
|
+
return null
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Fetch usage data from GitHub API
|
|
28
|
+
*/
|
|
29
|
+
export async function fetchUsage(username: string): Promise<UsageResponse | null> {
|
|
30
|
+
try {
|
|
31
|
+
const proc = Bun.spawn(
|
|
32
|
+
["gh", "api", `users/${username}/settings/billing/premium_request/usage`],
|
|
33
|
+
{
|
|
34
|
+
stdout: "pipe",
|
|
35
|
+
stderr: "pipe",
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
const stdout = await new Response(proc.stdout).text()
|
|
40
|
+
const exitCode = await proc.exited
|
|
41
|
+
|
|
42
|
+
if (exitCode !== 0) {
|
|
43
|
+
return null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return JSON.parse(stdout) as UsageResponse
|
|
47
|
+
} catch {
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Parse usage response into a summary
|
|
54
|
+
*/
|
|
55
|
+
export function parseUsageSummary(response: UsageResponse): UsageSummary {
|
|
56
|
+
let totalRequests = 0
|
|
57
|
+
let grossAmount = 0
|
|
58
|
+
let discountAmount = 0
|
|
59
|
+
let netAmount = 0
|
|
60
|
+
|
|
61
|
+
for (const item of response.usageItems) {
|
|
62
|
+
totalRequests += item.grossQuantity
|
|
63
|
+
grossAmount += item.grossAmount
|
|
64
|
+
discountAmount += item.discountAmount
|
|
65
|
+
netAmount += item.netAmount
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Sort items by grossQuantity descending
|
|
69
|
+
const sortedItems = [...response.usageItems].sort(
|
|
70
|
+
(a, b) => b.grossQuantity - a.grossQuantity
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
user: response.user,
|
|
75
|
+
year: response.timePeriod.year,
|
|
76
|
+
month: response.timePeriod.month,
|
|
77
|
+
totalRequests: Math.round(totalRequests),
|
|
78
|
+
grossAmount,
|
|
79
|
+
discountAmount,
|
|
80
|
+
netAmount,
|
|
81
|
+
items: sortedItems,
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Fetch and parse usage data
|
|
87
|
+
*/
|
|
88
|
+
export async function getUsageSummary(username: string): Promise<UsageSummary | null> {
|
|
89
|
+
const response = await fetchUsage(username)
|
|
90
|
+
if (!response) return null
|
|
91
|
+
return parseUsageSummary(response)
|
|
92
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { homedir } from "os"
|
|
2
|
+
import { join } from "path"
|
|
3
|
+
import type { Config } from "../types.ts"
|
|
4
|
+
|
|
5
|
+
const CONFIG_FILE = join(homedir(), ".copilot-usage.json")
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Check if config file exists
|
|
9
|
+
*/
|
|
10
|
+
export async function hasConfig(): Promise<boolean> {
|
|
11
|
+
try {
|
|
12
|
+
const file = Bun.file(CONFIG_FILE)
|
|
13
|
+
return await file.exists()
|
|
14
|
+
} catch {
|
|
15
|
+
return false
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Load config from file
|
|
21
|
+
*/
|
|
22
|
+
export async function loadConfig(): Promise<Config | null> {
|
|
23
|
+
try {
|
|
24
|
+
const file = Bun.file(CONFIG_FILE)
|
|
25
|
+
if (!(await file.exists())) {
|
|
26
|
+
return null
|
|
27
|
+
}
|
|
28
|
+
const text = await file.text()
|
|
29
|
+
const config = JSON.parse(text) as Config
|
|
30
|
+
|
|
31
|
+
// Validate config
|
|
32
|
+
if (typeof config.quota !== "number" || config.quota <= 0) {
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
if (typeof config.plan !== "string") {
|
|
36
|
+
return null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return config
|
|
40
|
+
} catch {
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Save config to file
|
|
47
|
+
*/
|
|
48
|
+
export async function saveConfig(config: Config): Promise<void> {
|
|
49
|
+
const text = JSON.stringify(config, null, 2)
|
|
50
|
+
await Bun.write(CONFIG_FILE, text)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Delete config file
|
|
55
|
+
*/
|
|
56
|
+
export async function deleteConfig(): Promise<void> {
|
|
57
|
+
try {
|
|
58
|
+
const { unlink } = await import("fs/promises")
|
|
59
|
+
await unlink(CONFIG_FILE)
|
|
60
|
+
} catch {
|
|
61
|
+
// File might not exist, that's fine
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get config file path (for display purposes)
|
|
67
|
+
*/
|
|
68
|
+
export function getConfigPath(): string {
|
|
69
|
+
return CONFIG_FILE
|
|
70
|
+
}
|
package/src/index.ts
ADDED
package/src/types.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// GitHub API Response Types
|
|
2
|
+
|
|
3
|
+
export interface UsageItem {
|
|
4
|
+
product: string
|
|
5
|
+
sku: string
|
|
6
|
+
model: string
|
|
7
|
+
unitType: string
|
|
8
|
+
pricePerUnit: number
|
|
9
|
+
grossQuantity: number
|
|
10
|
+
grossAmount: number
|
|
11
|
+
discountQuantity: number
|
|
12
|
+
discountAmount: number
|
|
13
|
+
netQuantity: number
|
|
14
|
+
netAmount: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface UsageResponse {
|
|
18
|
+
timePeriod: {
|
|
19
|
+
year: number
|
|
20
|
+
month: number
|
|
21
|
+
}
|
|
22
|
+
user: string
|
|
23
|
+
usageItems: UsageItem[]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Aggregated usage data for display
|
|
27
|
+
export interface UsageSummary {
|
|
28
|
+
user: string
|
|
29
|
+
year: number
|
|
30
|
+
month: number
|
|
31
|
+
totalRequests: number
|
|
32
|
+
grossAmount: number
|
|
33
|
+
discountAmount: number
|
|
34
|
+
netAmount: number
|
|
35
|
+
items: UsageItem[]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Config stored in ~/.copilot-usage.json
|
|
39
|
+
export interface Config {
|
|
40
|
+
quota: number
|
|
41
|
+
plan: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Copilot plan options
|
|
45
|
+
export interface PlanOption {
|
|
46
|
+
name: string
|
|
47
|
+
label: string
|
|
48
|
+
quota: number
|
|
49
|
+
description: string
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const PLAN_OPTIONS: PlanOption[] = [
|
|
53
|
+
{ name: "free", label: "Free", quota: 50, description: "50 requests/month" },
|
|
54
|
+
{ name: "pro", label: "Pro", quota: 300, description: "300 requests/month" },
|
|
55
|
+
{ name: "pro_plus", label: "Pro+", quota: 1500, description: "1,500 requests/month" },
|
|
56
|
+
{ name: "business", label: "Business", quota: 300, description: "300 requests/month" },
|
|
57
|
+
{ name: "enterprise", label: "Enterprise", quota: 1000, description: "1,000 requests/month" },
|
|
58
|
+
{ name: "custom", label: "Custom", quota: 0, description: "Enter custom value" },
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
// Application view states
|
|
62
|
+
export type ViewState = "loading" | "setup" | "auth" | "dashboard" | "settings"
|
|
63
|
+
|
|
64
|
+
// Auth status
|
|
65
|
+
export interface AuthStatus {
|
|
66
|
+
ghInstalled: boolean
|
|
67
|
+
authenticated: boolean
|
|
68
|
+
hasUserScope: boolean
|
|
69
|
+
username?: string
|
|
70
|
+
error?: string
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Color theme (Tokyo Night)
|
|
74
|
+
export const THEME = {
|
|
75
|
+
bg: "#1a1b26",
|
|
76
|
+
bgDark: "#16161e",
|
|
77
|
+
bgHighlight: "#292e42",
|
|
78
|
+
border: "#3b4261",
|
|
79
|
+
borderHighlight: "#545c7e",
|
|
80
|
+
fg: "#c0caf5",
|
|
81
|
+
fgDark: "#a9b1d6",
|
|
82
|
+
fgMuted: "#565f89",
|
|
83
|
+
blue: "#7aa2f7",
|
|
84
|
+
cyan: "#7dcfff",
|
|
85
|
+
green: "#9ece6a",
|
|
86
|
+
yellow: "#e0af68",
|
|
87
|
+
red: "#f7768e",
|
|
88
|
+
magenta: "#bb9af7",
|
|
89
|
+
orange: "#ff9e64",
|
|
90
|
+
} as const
|