egaki 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +166 -0
- package/dist/chatgpt-auth.js +294 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +471 -0
- package/dist/credentials.d.ts +18 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +107 -0
- package/dist/login.d.ts +9 -0
- package/dist/login.d.ts.map +1 -0
- package/dist/login.js +112 -0
- package/dist/model-catalog.d.ts +42 -0
- package/dist/model-catalog.d.ts.map +1 -0
- package/dist/model-catalog.js +729 -0
- package/dist/models.d.ts +10 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +147 -0
- package/dist/subscription.d.ts +5 -0
- package/dist/subscription.d.ts.map +1 -0
- package/dist/subscription.js +192 -0
- package/package.json +68 -5
- package/src/cli.ts +679 -0
- package/src/credentials.ts +126 -0
- package/src/login.ts +163 -0
- package/src/model-catalog.ts +793 -0
- package/src/models.ts +197 -0
- package/src/subscription.ts +271 -0
- package/index.js +0 -1
package/src/models.ts
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// Model registry for egaki.
|
|
2
|
+
// Maps model IDs to their provider and generation strategy. The enum of
|
|
3
|
+
// supported models is derived from this registry so there's a single source
|
|
4
|
+
// of truth for both CLI validation and runtime provider resolution.
|
|
5
|
+
//
|
|
6
|
+
// Two generation strategies:
|
|
7
|
+
// - 'image': uses provider.image(modelId) + generateImage()
|
|
8
|
+
// - 'text': uses provider(modelId) + generateText() with responseModalities
|
|
9
|
+
//
|
|
10
|
+
// Provider resolution priority:
|
|
11
|
+
// 1. Provider-specific key (e.g. GOOGLE_GENERATIVE_AI_API_KEY) → direct SDK
|
|
12
|
+
// 2. Egaki API key (EGAKI_API_KEY) → route through egaki gateway
|
|
13
|
+
// 3. No key → error with subscription recommendation
|
|
14
|
+
//
|
|
15
|
+
// Model IDs are sourced from the TypeScript types exported by each @ai-sdk/*
|
|
16
|
+
// package. To add new models, check the *ModelId types in each package's
|
|
17
|
+
// dist/index.d.ts and add entries here.
|
|
18
|
+
import type { ImageModel, LanguageModel } from 'ai'
|
|
19
|
+
import pc from 'picocolors'
|
|
20
|
+
import { PROVIDERS, EGAKI_GATEWAY_URL } from './credentials.js'
|
|
21
|
+
import { CATALOG, findModel } from './model-catalog.js'
|
|
22
|
+
import type { ModelEntry } from './model-catalog.js'
|
|
23
|
+
|
|
24
|
+
export type { ModelEntry }
|
|
25
|
+
|
|
26
|
+
export const IMAGE_MODELS = CATALOG.map((m) => m.id) as [string, ...string[]]
|
|
27
|
+
|
|
28
|
+
export const DEFAULT_MODEL = 'nano-banana-pro-preview'
|
|
29
|
+
|
|
30
|
+
export function getModelConfig(modelId: string): ModelEntry {
|
|
31
|
+
const entry = findModel(modelId)
|
|
32
|
+
if (!entry) {
|
|
33
|
+
console.error(pc.red(`Unknown model: ${modelId}`))
|
|
34
|
+
process.exit(1)
|
|
35
|
+
}
|
|
36
|
+
return entry
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Check if the egaki gateway API key is available (from env or stored credentials).
|
|
41
|
+
*/
|
|
42
|
+
function hasEgakiKey(): boolean {
|
|
43
|
+
const info = PROVIDERS['egaki']
|
|
44
|
+
if (!info) return false
|
|
45
|
+
return Boolean(process.env[info.envVar])
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Check if a direct provider key is available for the given provider.
|
|
50
|
+
*/
|
|
51
|
+
function hasDirectProviderKey(providerName: string): boolean {
|
|
52
|
+
const info = PROVIDERS[providerName]
|
|
53
|
+
if (!info) return false
|
|
54
|
+
return Boolean(process.env[info.envVar])
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Check that the provider's API key is available before making API calls.
|
|
58
|
+
// Prints a user-friendly error with instructions on how to configure it.
|
|
59
|
+
// Prioritizes egaki subscription over individual provider keys.
|
|
60
|
+
export function ensureProviderKey(providerName: string): void {
|
|
61
|
+
// If direct provider key exists, we're good
|
|
62
|
+
if (hasDirectProviderKey(providerName)) return
|
|
63
|
+
|
|
64
|
+
// If egaki key exists, we'll route through the gateway
|
|
65
|
+
if (hasEgakiKey()) return
|
|
66
|
+
|
|
67
|
+
const info = PROVIDERS[providerName]
|
|
68
|
+
|
|
69
|
+
console.error('')
|
|
70
|
+
console.error(pc.red(pc.bold(`Missing API key for ${info?.label || providerName}`)))
|
|
71
|
+
console.error('')
|
|
72
|
+
console.error(` ${pc.bold('Recommended:')} Use Egaki subscription (all models, one key)`)
|
|
73
|
+
console.error('')
|
|
74
|
+
console.error(
|
|
75
|
+
` ${pc.cyan('egaki subscribe')} get started in 30 seconds`,
|
|
76
|
+
)
|
|
77
|
+
console.error('')
|
|
78
|
+
console.error(` ${pc.dim('Or configure a provider key directly:')}`)
|
|
79
|
+
console.error('')
|
|
80
|
+
console.error(
|
|
81
|
+
` ${pc.cyan('egaki login')} interactive setup`,
|
|
82
|
+
)
|
|
83
|
+
if (info) {
|
|
84
|
+
console.error(
|
|
85
|
+
` ${pc.cyan(`${info.envVar}=...`)} egaki image ... inline env var`,
|
|
86
|
+
)
|
|
87
|
+
console.error(
|
|
88
|
+
` ${pc.cyan(`egaki login --provider ${providerName} --key <key>`)}`,
|
|
89
|
+
)
|
|
90
|
+
console.error('')
|
|
91
|
+
console.error(` ${pc.dim(info.hint)}`)
|
|
92
|
+
}
|
|
93
|
+
console.error('')
|
|
94
|
+
process.exit(1)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Create a gateway-backed image model using createGateway from the AI SDK.
|
|
99
|
+
* The model ID is sent as provider/model format to the gateway.
|
|
100
|
+
*/
|
|
101
|
+
async function createGatewayImageModel(modelId: string, provider: string): Promise<ImageModel> {
|
|
102
|
+
const { createGateway } = await import('ai')
|
|
103
|
+
const gateway = createGateway({
|
|
104
|
+
apiKey: process.env['EGAKI_API_KEY']!,
|
|
105
|
+
baseURL: EGAKI_GATEWAY_URL,
|
|
106
|
+
})
|
|
107
|
+
// Gateway expects provider/model format for routing
|
|
108
|
+
const gatewayModelId = `${provider}/${modelId}`
|
|
109
|
+
return gateway.image(gatewayModelId)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Create a gateway-backed text model using createGateway from the AI SDK.
|
|
114
|
+
*/
|
|
115
|
+
async function createGatewayTextModel(modelId: string, provider: string): Promise<LanguageModel> {
|
|
116
|
+
const { createGateway } = await import('ai')
|
|
117
|
+
const gateway = createGateway({
|
|
118
|
+
apiKey: process.env['EGAKI_API_KEY']!,
|
|
119
|
+
baseURL: EGAKI_GATEWAY_URL,
|
|
120
|
+
})
|
|
121
|
+
const gatewayModelId = `${provider}/${modelId}`
|
|
122
|
+
return gateway(gatewayModelId)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Lazily import the provider and create the right model instance.
|
|
126
|
+
// This avoids loading all provider SDKs upfront — only the one needed
|
|
127
|
+
// for the selected model gets imported.
|
|
128
|
+
//
|
|
129
|
+
// Priority: direct provider key > egaki gateway > error
|
|
130
|
+
export async function createImageModel(modelId: string): Promise<ImageModel> {
|
|
131
|
+
const config = getModelConfig(modelId)
|
|
132
|
+
ensureProviderKey(config.provider)
|
|
133
|
+
|
|
134
|
+
// If the user has a direct provider key, use the provider SDK directly
|
|
135
|
+
if (hasDirectProviderKey(config.provider)) {
|
|
136
|
+
switch (config.provider) {
|
|
137
|
+
case 'google': {
|
|
138
|
+
const { google } = await import('@ai-sdk/google')
|
|
139
|
+
return google.image(modelId)
|
|
140
|
+
}
|
|
141
|
+
case 'openai': {
|
|
142
|
+
const { openai } = await import('@ai-sdk/openai')
|
|
143
|
+
return openai.image(modelId)
|
|
144
|
+
}
|
|
145
|
+
case 'replicate': {
|
|
146
|
+
const { replicate } = await import('@ai-sdk/replicate')
|
|
147
|
+
return replicate.image(modelId)
|
|
148
|
+
}
|
|
149
|
+
case 'fal': {
|
|
150
|
+
const { fal } = await import('@ai-sdk/fal')
|
|
151
|
+
return fal.image(modelId)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Fall back to egaki gateway
|
|
157
|
+
if (hasEgakiKey()) {
|
|
158
|
+
return createGatewayImageModel(modelId, config.provider)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Should not reach here — ensureProviderKey would have exited
|
|
162
|
+
console.error(pc.red(`No API key available for provider: ${config.provider}`))
|
|
163
|
+
process.exit(1)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export async function createTextModel(
|
|
167
|
+
modelId: string,
|
|
168
|
+
): Promise<LanguageModel> {
|
|
169
|
+
const config = getModelConfig(modelId)
|
|
170
|
+
ensureProviderKey(config.provider)
|
|
171
|
+
|
|
172
|
+
// Direct provider key takes priority
|
|
173
|
+
if (hasDirectProviderKey(config.provider)) {
|
|
174
|
+
switch (config.provider) {
|
|
175
|
+
case 'google': {
|
|
176
|
+
const { google } = await import('@ai-sdk/google')
|
|
177
|
+
return google(modelId)
|
|
178
|
+
}
|
|
179
|
+
default:
|
|
180
|
+
// Only Google supports generateText with responseModalities for images
|
|
181
|
+
console.error(
|
|
182
|
+
pc.red(
|
|
183
|
+
`Text+image generation is only supported for Google models, got provider: ${config.provider}`,
|
|
184
|
+
),
|
|
185
|
+
)
|
|
186
|
+
process.exit(1)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Fall back to egaki gateway
|
|
191
|
+
if (hasEgakiKey()) {
|
|
192
|
+
return createGatewayTextModel(modelId, config.provider)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
console.error(pc.red(`No API key available for provider: ${config.provider}`))
|
|
196
|
+
process.exit(1)
|
|
197
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
// Subscription management for egaki.
|
|
2
|
+
// Handles the subscribe, unsubscribe, and usage flows.
|
|
3
|
+
// Communicates with the egaki gateway API at egaki.org.
|
|
4
|
+
|
|
5
|
+
import pc from 'picocolors'
|
|
6
|
+
import {
|
|
7
|
+
intro,
|
|
8
|
+
outro,
|
|
9
|
+
text,
|
|
10
|
+
select,
|
|
11
|
+
confirm,
|
|
12
|
+
isCancel,
|
|
13
|
+
cancel,
|
|
14
|
+
log,
|
|
15
|
+
note,
|
|
16
|
+
spinner,
|
|
17
|
+
} from '@clack/prompts'
|
|
18
|
+
import { EGAKI_GATEWAY_URL, saveProviderKey, getProviderKey } from './credentials.js'
|
|
19
|
+
|
|
20
|
+
// Strip /v1/ai suffix to get the base URL for non-proxy endpoints (/buy, /api/*)
|
|
21
|
+
const GATEWAY_BASE = EGAKI_GATEWAY_URL.replace(/\/v1\/ai$/, '')
|
|
22
|
+
|
|
23
|
+
type PlanInfo = {
|
|
24
|
+
id: string
|
|
25
|
+
name: string
|
|
26
|
+
price: number
|
|
27
|
+
spendingCap: number
|
|
28
|
+
markup: number
|
|
29
|
+
priceFormatted: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type UsageInfo = {
|
|
33
|
+
plan: string
|
|
34
|
+
planName: string
|
|
35
|
+
dollarsUsed: number
|
|
36
|
+
spendingCap: number
|
|
37
|
+
dollarsRemaining: number
|
|
38
|
+
periodStart: number
|
|
39
|
+
status: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type StatusInfo = {
|
|
43
|
+
status: string
|
|
44
|
+
plan: string
|
|
45
|
+
planName: string
|
|
46
|
+
email?: string
|
|
47
|
+
dollarsUsed: number
|
|
48
|
+
spendingCap: number
|
|
49
|
+
createdAt: number
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ── Subscribe ─────────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
export async function subscribeInteractive(): Promise<void> {
|
|
55
|
+
intro(pc.bold('egaki subscribe'))
|
|
56
|
+
|
|
57
|
+
// Fetch available plans
|
|
58
|
+
const s = spinner()
|
|
59
|
+
s.start('Fetching plans...')
|
|
60
|
+
|
|
61
|
+
let plans: PlanInfo[]
|
|
62
|
+
try {
|
|
63
|
+
const res = await fetch(`${GATEWAY_BASE}/api/plans`)
|
|
64
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
65
|
+
const data = (await res.json()) as { plans: PlanInfo[] }
|
|
66
|
+
plans = data.plans
|
|
67
|
+
s.stop('Plans loaded')
|
|
68
|
+
} catch {
|
|
69
|
+
s.stop('Failed to fetch plans')
|
|
70
|
+
log.error('Could not connect to egaki.org. Check your internet connection.')
|
|
71
|
+
process.exit(1)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Select plan
|
|
75
|
+
const planId = await select({
|
|
76
|
+
message: 'Choose a plan',
|
|
77
|
+
options: plans.map((p) => ({
|
|
78
|
+
value: p.id,
|
|
79
|
+
label: `${p.name} — ${p.priceFormatted}`,
|
|
80
|
+
hint: `$${p.spendingCap} spending cap, all models`,
|
|
81
|
+
})),
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
if (isCancel(planId)) {
|
|
85
|
+
cancel('Subscribe cancelled.')
|
|
86
|
+
process.exit(0)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Get email
|
|
90
|
+
const email = await text({
|
|
91
|
+
message: 'Email for the subscription',
|
|
92
|
+
placeholder: 'you@example.com',
|
|
93
|
+
validate: (value) => {
|
|
94
|
+
if (!value || !value.includes('@')) {
|
|
95
|
+
return 'Please enter a valid email'
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
if (isCancel(email)) {
|
|
101
|
+
cancel('Subscribe cancelled.')
|
|
102
|
+
process.exit(0)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Show checkout URL
|
|
106
|
+
const checkoutUrl = `${GATEWAY_BASE}/buy?plan=${planId}&email=${encodeURIComponent(email)}`
|
|
107
|
+
|
|
108
|
+
log.info('')
|
|
109
|
+
note(
|
|
110
|
+
[
|
|
111
|
+
`Open this URL to complete payment:`,
|
|
112
|
+
'',
|
|
113
|
+
pc.cyan(checkoutUrl),
|
|
114
|
+
'',
|
|
115
|
+
`After payment, you'll receive your API key via email.`,
|
|
116
|
+
`Then run:`,
|
|
117
|
+
'',
|
|
118
|
+
` ${pc.cyan('egaki login --provider egaki --key egaki_...')}`,
|
|
119
|
+
].join('\n'),
|
|
120
|
+
'Checkout',
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
// Ask if they want to enter the key now
|
|
124
|
+
const hasKey = await confirm({
|
|
125
|
+
message: 'Do you already have your API key?',
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
if (isCancel(hasKey)) {
|
|
129
|
+
outro('You can enter your key later with: egaki login --provider egaki --key <key>')
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (hasKey) {
|
|
134
|
+
const key = await text({
|
|
135
|
+
message: 'Paste your egaki API key',
|
|
136
|
+
placeholder: 'egaki_...',
|
|
137
|
+
validate: (value) => {
|
|
138
|
+
if (!value || !value.startsWith('egaki_')) {
|
|
139
|
+
return 'API key should start with egaki_'
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
if (isCancel(key)) {
|
|
145
|
+
outro('You can enter your key later with: egaki login --provider egaki --key <key>')
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
saveProviderKey('egaki', key.trim())
|
|
150
|
+
log.success('API key saved')
|
|
151
|
+
outro('You can now generate images with any model: egaki image "your prompt"')
|
|
152
|
+
} else {
|
|
153
|
+
outro('After payment, run: egaki login --provider egaki --key <your-key>')
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function subscribeNonInteractive(email?: string, plan?: string): void {
|
|
158
|
+
const emailParam = email ? `&email=${encodeURIComponent(email)}` : ''
|
|
159
|
+
|
|
160
|
+
if (plan) {
|
|
161
|
+
// Specific plan requested
|
|
162
|
+
const checkoutUrl = `${GATEWAY_BASE}/buy?plan=${plan}${emailParam}`
|
|
163
|
+
console.log(pc.bold('Egaki Subscription'))
|
|
164
|
+
console.log('')
|
|
165
|
+
console.log('Open this URL to complete payment:')
|
|
166
|
+
console.log('')
|
|
167
|
+
console.log(pc.cyan(checkoutUrl))
|
|
168
|
+
} else {
|
|
169
|
+
// No plan specified — show all options
|
|
170
|
+
console.log(pc.bold('Egaki Subscription'))
|
|
171
|
+
console.log('')
|
|
172
|
+
console.log('Choose a plan:')
|
|
173
|
+
console.log('')
|
|
174
|
+
console.log(` ${pc.bold('Plus')} $29/mo ${pc.cyan(`${GATEWAY_BASE}/buy?plan=plus${emailParam}`)}`)
|
|
175
|
+
console.log(` ${pc.bold('Pro')} $99/mo ${pc.cyan(`${GATEWAY_BASE}/buy?plan=pro${emailParam}`)}`)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
console.log('')
|
|
179
|
+
console.log('After payment, you\'ll receive your API key via email.')
|
|
180
|
+
console.log(`Then run: ${pc.cyan('egaki login --provider egaki --key egaki_...')}`)
|
|
181
|
+
console.log('')
|
|
182
|
+
console.log('Tip: egaki supports both BYOK and subscription mode.')
|
|
183
|
+
console.log('BYOK = add provider keys (google/openai/replicate/fal) with egaki login.')
|
|
184
|
+
console.log('Subscription = one egaki key to access all models without per-provider key setup.')
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ── Unsubscribe ───────────────────────────────────────────────────────────
|
|
188
|
+
|
|
189
|
+
export async function unsubscribe(): Promise<void> {
|
|
190
|
+
const apiKey = getProviderKey('egaki') || process.env['EGAKI_API_KEY']
|
|
191
|
+
|
|
192
|
+
if (!apiKey) {
|
|
193
|
+
console.error(pc.red('No egaki API key found.'))
|
|
194
|
+
console.error(
|
|
195
|
+
`If you have a key, set it first: ${pc.cyan('egaki login --provider egaki --key <key>')}`,
|
|
196
|
+
)
|
|
197
|
+
process.exit(1)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
console.error(pc.dim('Canceling subscription...'))
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
const res = await fetch(`${GATEWAY_BASE}/api/cancel`, {
|
|
204
|
+
method: 'POST',
|
|
205
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
const data = (await res.json()) as { success?: boolean; message?: string; error?: string }
|
|
209
|
+
|
|
210
|
+
if (!res.ok) {
|
|
211
|
+
console.error(pc.red(data.error || 'Failed to cancel subscription'))
|
|
212
|
+
process.exit(1)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
console.log(pc.green(data.message || 'Subscription canceled.'))
|
|
216
|
+
console.log(pc.dim('You can resubscribe anytime with: egaki subscribe'))
|
|
217
|
+
} catch {
|
|
218
|
+
console.error(pc.red('Could not connect to egaki.org. Check your internet connection.'))
|
|
219
|
+
process.exit(1)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ── Usage ─────────────────────────────────────────────────────────────────
|
|
224
|
+
|
|
225
|
+
export async function showUsage(): Promise<void> {
|
|
226
|
+
const apiKey = getProviderKey('egaki') || process.env['EGAKI_API_KEY']
|
|
227
|
+
|
|
228
|
+
if (!apiKey) {
|
|
229
|
+
console.error(pc.red('No egaki API key found.'))
|
|
230
|
+
console.error(
|
|
231
|
+
`Set your key first: ${pc.cyan('egaki login --provider egaki --key <key>')}`,
|
|
232
|
+
)
|
|
233
|
+
console.error(
|
|
234
|
+
`Or subscribe: ${pc.cyan('egaki subscribe')}`,
|
|
235
|
+
)
|
|
236
|
+
process.exit(1)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
const res = await fetch(`${GATEWAY_BASE}/api/usage`, {
|
|
241
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
if (!res.ok) {
|
|
245
|
+
const data = (await res.json()) as { error?: string }
|
|
246
|
+
console.error(pc.red(data.error || 'Failed to fetch usage'))
|
|
247
|
+
process.exit(1)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const usage = (await res.json()) as UsageInfo
|
|
251
|
+
const pct = usage.spendingCap > 0
|
|
252
|
+
? Math.round((usage.dollarsUsed / usage.spendingCap) * 100)
|
|
253
|
+
: 0
|
|
254
|
+
const remaining = Math.max(0, usage.spendingCap - usage.dollarsUsed)
|
|
255
|
+
|
|
256
|
+
console.log(pc.bold(`Egaki ${usage.planName} Plan\n`))
|
|
257
|
+
console.log(` Status: ${usage.status === 'active' ? pc.green('active') : pc.red(usage.status)}`)
|
|
258
|
+
console.log(` Spent: $${usage.dollarsUsed.toFixed(2)} / $${usage.spendingCap.toFixed(2)} (${pct}%)`)
|
|
259
|
+
console.log(` Remaining: $${remaining.toFixed(2)}`)
|
|
260
|
+
console.log(` Period: started ${new Date(usage.periodStart).toLocaleDateString()}`)
|
|
261
|
+
|
|
262
|
+
if (pct >= 90) {
|
|
263
|
+
console.log('')
|
|
264
|
+
console.log(pc.yellow(' Running low on budget! Consider upgrading your plan.'))
|
|
265
|
+
console.log(pc.dim(` Visit: https://egaki.org/buy`))
|
|
266
|
+
}
|
|
267
|
+
} catch {
|
|
268
|
+
console.error(pc.red('Could not connect to egaki.org. Check your internet connection.'))
|
|
269
|
+
process.exit(1)
|
|
270
|
+
}
|
|
271
|
+
}
|
package/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
// placeholder
|