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
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// Credential storage for egaki.
|
|
2
|
+
// Reads and writes API keys from ~/.config/egaki/credentials.json.
|
|
3
|
+
// Keys are stored per-provider and injected as env vars at runtime.
|
|
4
|
+
import fs from 'node:fs'
|
|
5
|
+
import path from 'node:path'
|
|
6
|
+
import os from 'node:os'
|
|
7
|
+
|
|
8
|
+
// Maps provider names to their expected env var names.
|
|
9
|
+
// When a key is stored, we set the corresponding env var before
|
|
10
|
+
// calling the AI SDK so the provider picks it up automatically.
|
|
11
|
+
export const PROVIDERS: Record<
|
|
12
|
+
string,
|
|
13
|
+
{ envVar: string; label: string; hint: string }
|
|
14
|
+
> = {
|
|
15
|
+
egaki: {
|
|
16
|
+
envVar: 'EGAKI_API_KEY',
|
|
17
|
+
label: 'Egaki (all models, one subscription)',
|
|
18
|
+
hint: 'Subscribe at https://egaki.org/buy or run: egaki subscribe',
|
|
19
|
+
},
|
|
20
|
+
google: {
|
|
21
|
+
envVar: 'GOOGLE_GENERATIVE_AI_API_KEY',
|
|
22
|
+
label: 'Google AI (Gemini, Imagen)',
|
|
23
|
+
hint: 'Get your key at https://aistudio.google.com/apikey',
|
|
24
|
+
},
|
|
25
|
+
openai: {
|
|
26
|
+
envVar: 'OPENAI_API_KEY',
|
|
27
|
+
label: 'OpenAI (DALL-E)',
|
|
28
|
+
hint: 'Get your key at https://platform.openai.com/api-keys',
|
|
29
|
+
},
|
|
30
|
+
replicate: {
|
|
31
|
+
envVar: 'REPLICATE_API_TOKEN',
|
|
32
|
+
label: 'Replicate (Flux, SDXL)',
|
|
33
|
+
hint: 'Get your token at https://replicate.com/account/api-tokens',
|
|
34
|
+
},
|
|
35
|
+
fal: {
|
|
36
|
+
envVar: 'FAL_KEY',
|
|
37
|
+
label: 'fal.ai (Flux, SDXL, Recraft)',
|
|
38
|
+
hint: 'Get your key at https://fal.ai/dashboard/keys',
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const EGAKI_GATEWAY_URL = 'https://egaki.org/v1/ai'
|
|
43
|
+
|
|
44
|
+
function getConfigDir(): string {
|
|
45
|
+
// XDG_CONFIG_HOME or ~/.config/egaki
|
|
46
|
+
const xdg = process.env['XDG_CONFIG_HOME']
|
|
47
|
+
const base = xdg || path.join(os.homedir(), '.config')
|
|
48
|
+
return path.join(base, 'egaki')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getCredentialsPath(): string {
|
|
52
|
+
return path.join(getConfigDir(), 'credentials.json')
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type Credentials = Record<string, string>
|
|
56
|
+
|
|
57
|
+
export function readCredentials(): Credentials {
|
|
58
|
+
const filePath = getCredentialsPath()
|
|
59
|
+
if (!fs.existsSync(filePath)) {
|
|
60
|
+
return {}
|
|
61
|
+
}
|
|
62
|
+
const raw = fs.readFileSync(filePath, 'utf-8')
|
|
63
|
+
return JSON.parse(raw) as Credentials
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function writeCredentials(creds: Credentials): void {
|
|
67
|
+
const dir = getConfigDir()
|
|
68
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
69
|
+
const filePath = getCredentialsPath()
|
|
70
|
+
fs.writeFileSync(filePath, JSON.stringify(creds, null, 2) + '\n', {
|
|
71
|
+
mode: 0o600,
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function saveProviderKey(provider: string, key: string): void {
|
|
76
|
+
const creds = readCredentials()
|
|
77
|
+
creds[provider] = key
|
|
78
|
+
writeCredentials(creds)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function removeProviderKey(provider: string): void {
|
|
82
|
+
const creds = readCredentials()
|
|
83
|
+
delete creds[provider]
|
|
84
|
+
writeCredentials(creds)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function getProviderKey(provider: string): string | undefined {
|
|
88
|
+
const creds = readCredentials()
|
|
89
|
+
return creds[provider]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Inject all stored credentials as env vars so the AI SDK
|
|
93
|
+
// providers pick them up automatically. Env vars already set
|
|
94
|
+
// by the user take precedence (we don't overwrite them).
|
|
95
|
+
export function injectCredentialsToEnv(): void {
|
|
96
|
+
const creds = readCredentials()
|
|
97
|
+
for (const [provider, key] of Object.entries(creds)) {
|
|
98
|
+
const info = PROVIDERS[provider]
|
|
99
|
+
if (!info) {
|
|
100
|
+
continue
|
|
101
|
+
}
|
|
102
|
+
if (!process.env[info.envVar]) {
|
|
103
|
+
process.env[info.envVar] = key
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check if a provider has a key available (from env or stored credentials).
|
|
109
|
+
// Returns the source of the key for display purposes.
|
|
110
|
+
export function getKeyStatus(provider: string): {
|
|
111
|
+
available: boolean
|
|
112
|
+
source: 'env' | 'stored' | 'none'
|
|
113
|
+
} {
|
|
114
|
+
const info = PROVIDERS[provider]
|
|
115
|
+
if (!info) {
|
|
116
|
+
return { available: false, source: 'none' }
|
|
117
|
+
}
|
|
118
|
+
if (process.env[info.envVar]) {
|
|
119
|
+
return { available: true, source: 'env' }
|
|
120
|
+
}
|
|
121
|
+
const stored = getProviderKey(provider)
|
|
122
|
+
if (stored) {
|
|
123
|
+
return { available: true, source: 'stored' }
|
|
124
|
+
}
|
|
125
|
+
return { available: false, source: 'none' }
|
|
126
|
+
}
|
package/src/login.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// Login command — interactive and non-interactive API key management.
|
|
2
|
+
// Interactive mode uses clack prompts for provider selection and key input.
|
|
3
|
+
// Non-interactive mode supports --provider + --key flags and stdin piping.
|
|
4
|
+
import {
|
|
5
|
+
intro,
|
|
6
|
+
outro,
|
|
7
|
+
select,
|
|
8
|
+
password,
|
|
9
|
+
isCancel,
|
|
10
|
+
cancel,
|
|
11
|
+
log,
|
|
12
|
+
note,
|
|
13
|
+
} from '@clack/prompts'
|
|
14
|
+
import pc from 'picocolors'
|
|
15
|
+
import {
|
|
16
|
+
PROVIDERS,
|
|
17
|
+
saveProviderKey,
|
|
18
|
+
removeProviderKey,
|
|
19
|
+
getKeyStatus,
|
|
20
|
+
readCredentials,
|
|
21
|
+
} from './credentials.js'
|
|
22
|
+
|
|
23
|
+
export async function loginInteractive(): Promise<void> {
|
|
24
|
+
intro(pc.bold('egaki login'))
|
|
25
|
+
|
|
26
|
+
const providerOptions = Object.entries(PROVIDERS).map(([key, info]) => {
|
|
27
|
+
const status = getKeyStatus(key)
|
|
28
|
+
const statusLabel = (() => {
|
|
29
|
+
if (status.source === 'env') {
|
|
30
|
+
return pc.green('(set via env)')
|
|
31
|
+
}
|
|
32
|
+
if (status.source === 'stored') {
|
|
33
|
+
return pc.green('(saved)')
|
|
34
|
+
}
|
|
35
|
+
return pc.dim('(not configured)')
|
|
36
|
+
})()
|
|
37
|
+
// Highlight the egaki option as recommended
|
|
38
|
+
const label = key === 'egaki'
|
|
39
|
+
? `${pc.bold(info.label)} ${pc.cyan('← recommended')} ${statusLabel}`
|
|
40
|
+
: `${info.label} ${statusLabel}`
|
|
41
|
+
return {
|
|
42
|
+
value: key,
|
|
43
|
+
label,
|
|
44
|
+
hint: info.hint,
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const provider = await select({
|
|
49
|
+
message: 'Select a provider to configure',
|
|
50
|
+
options: providerOptions,
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
if (isCancel(provider)) {
|
|
54
|
+
cancel('Login cancelled.')
|
|
55
|
+
process.exit(0)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const info = PROVIDERS[provider]
|
|
59
|
+
if (!info) {
|
|
60
|
+
log.error(`Unknown provider: ${provider}`)
|
|
61
|
+
process.exit(1)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
log.info(
|
|
65
|
+
`${pc.dim('Env var:')} ${info.envVar} ${pc.dim('(also used by the CLI when set)')}`,
|
|
66
|
+
)
|
|
67
|
+
log.info(`${pc.dim('Get key:')} ${info.hint}`)
|
|
68
|
+
|
|
69
|
+
const key = await password({
|
|
70
|
+
message: `Paste your ${info.label} API key`,
|
|
71
|
+
validate: (value) => {
|
|
72
|
+
if (!value || value.trim().length === 0) {
|
|
73
|
+
return 'API key cannot be empty'
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
if (isCancel(key)) {
|
|
79
|
+
cancel('Login cancelled.')
|
|
80
|
+
process.exit(0)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
saveProviderKey(provider, key.trim())
|
|
84
|
+
|
|
85
|
+
log.success(`${info.label} key saved`)
|
|
86
|
+
note(
|
|
87
|
+
`You can also set ${pc.bold(info.envVar)} as an env var.\nThe CLI reads it automatically, so you can pass it inline too:\n ${pc.dim(`${info.envVar}=... egaki generate "prompt"`)}`,
|
|
88
|
+
'Tip',
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
outro('Done')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function loginNonInteractive({
|
|
95
|
+
provider,
|
|
96
|
+
key,
|
|
97
|
+
}: {
|
|
98
|
+
provider: string
|
|
99
|
+
key: string
|
|
100
|
+
}): void {
|
|
101
|
+
const info = PROVIDERS[provider]
|
|
102
|
+
if (!info) {
|
|
103
|
+
const available = Object.keys(PROVIDERS).join(', ')
|
|
104
|
+
console.error(
|
|
105
|
+
pc.red(`Unknown provider: ${provider}. Available: ${available}`),
|
|
106
|
+
)
|
|
107
|
+
process.exit(1)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!key || key.trim().length === 0) {
|
|
111
|
+
console.error(pc.red('API key cannot be empty'))
|
|
112
|
+
process.exit(1)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
saveProviderKey(provider, key.trim())
|
|
116
|
+
console.log(pc.green(`${info.label} key saved`))
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function showLoginStatus(): void {
|
|
120
|
+
console.log(pc.bold('Configured providers:\n'))
|
|
121
|
+
|
|
122
|
+
for (const [key, info] of Object.entries(PROVIDERS)) {
|
|
123
|
+
const status = getKeyStatus(key)
|
|
124
|
+
const icon = status.available ? pc.green('*') : pc.dim('-')
|
|
125
|
+
const source = (() => {
|
|
126
|
+
if (status.source === 'env') {
|
|
127
|
+
return pc.cyan('(from env)')
|
|
128
|
+
}
|
|
129
|
+
if (status.source === 'stored') {
|
|
130
|
+
return pc.green('(saved)')
|
|
131
|
+
}
|
|
132
|
+
return pc.dim('(not set)')
|
|
133
|
+
})()
|
|
134
|
+
const envHint = pc.dim(
|
|
135
|
+
` env: ${info.envVar} (set in shell or pass inline to CLI)`,
|
|
136
|
+
)
|
|
137
|
+
console.log(`${icon} ${info.label} ${source}`)
|
|
138
|
+
console.log(envHint)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function removeLogin(provider: string): void {
|
|
143
|
+
const info = PROVIDERS[provider]
|
|
144
|
+
if (!info) {
|
|
145
|
+
const available = Object.keys(PROVIDERS).join(', ')
|
|
146
|
+
console.error(
|
|
147
|
+
pc.red(`Unknown provider: ${provider}. Available: ${available}`),
|
|
148
|
+
)
|
|
149
|
+
process.exit(1)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
removeProviderKey(provider)
|
|
153
|
+
console.log(pc.green(`${info.label} key removed`))
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Read API key from stdin (for piping: echo "sk-xxx" | egaki login --provider google)
|
|
157
|
+
export async function readKeyFromStdin(): Promise<string> {
|
|
158
|
+
const chunks: Buffer[] = []
|
|
159
|
+
for await (const chunk of process.stdin) {
|
|
160
|
+
chunks.push(chunk as Buffer)
|
|
161
|
+
}
|
|
162
|
+
return Buffer.concat(chunks).toString('utf-8').trim()
|
|
163
|
+
}
|