egaki 0.9.0 → 0.11.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/CHANGELOG.md +72 -0
- package/dist/cli/chatgpt-auth.d.ts +30 -5
- package/dist/cli/chatgpt-auth.d.ts.map +1 -1
- package/dist/cli/chatgpt-auth.js +257 -79
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +132 -28
- package/dist/cli/dev-server.d.ts +30 -0
- package/dist/cli/dev-server.d.ts.map +1 -0
- package/dist/cli/dev-server.js +266 -0
- package/dist/cli/dev-server.test.d.ts +2 -0
- package/dist/cli/dev-server.test.d.ts.map +1 -0
- package/dist/cli/dev-server.test.js +95 -0
- package/dist/cli/generate.d.ts +8 -0
- package/dist/cli/generate.d.ts.map +1 -1
- package/dist/cli/generate.js +170 -58
- package/dist/cli/generate.test.js +50 -1
- package/dist/cli/login.d.ts +38 -3
- package/dist/cli/login.d.ts.map +1 -1
- package/dist/cli/login.js +44 -17
- package/dist/cli/login.test.d.ts +2 -0
- package/dist/cli/login.test.d.ts.map +1 -0
- package/dist/cli/login.test.js +158 -0
- package/dist/cli/open-browser.d.ts +3 -1
- package/dist/cli/open-browser.d.ts.map +1 -1
- package/dist/cli/open-browser.js +2 -2
- package/dist/cli/xai-auth.d.ts +3 -1
- package/dist/cli/xai-auth.d.ts.map +1 -1
- package/dist/cli/xai-auth.js +13 -3
- package/dist/vite/angled-screen-shader.d.ts +5 -0
- package/dist/vite/angled-screen-shader.d.ts.map +1 -1
- package/dist/vite/angled-screen-shader.js +77 -34
- package/dist/vite/generated-media-client.d.ts.map +1 -1
- package/dist/vite/generated-media-client.js +2 -2
- package/dist/vite/vite-plugin.d.ts.map +1 -1
- package/dist/vite/vite-plugin.js +128 -10
- package/package.json +6 -10
- package/skills/egaki/SKILL.md +14 -0
- package/src/cli/chatgpt-auth.ts +379 -89
- package/src/cli/cli.ts +161 -27
- package/src/cli/dev-server.test.ts +109 -0
- package/src/cli/dev-server.ts +303 -0
- package/src/cli/generate.test.ts +57 -0
- package/src/cli/generate.ts +191 -61
- package/src/cli/login.test.ts +185 -0
- package/src/cli/login.ts +68 -16
- package/src/cli/open-browser.ts +7 -6
- package/src/cli/xai-auth.ts +15 -3
- package/src/vite/angled-screen-shader.tsx +81 -34
- package/src/vite/generated-media-client.tsx +6 -1
- package/src/vite/vite-plugin.ts +137 -10
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/cli/generate.ts"],"names":[],"mappings":"AAgCA,OAAO,EAQL,eAAe,EAChB,MAAM,aAAa,CAAA;AAEpB,OAAO,EAAE,eAAe,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/cli/generate.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/generate.js
CHANGED
|
@@ -19,7 +19,7 @@ import { createParser } from 'eventsource-parser';
|
|
|
19
19
|
import { injectCredentialsToEnv, getChatGptAuth, saveChatGptAuth, } from './credentials.js';
|
|
20
20
|
import { getModelConfig, createImageModel, createTextModel, createVideoModel, shouldUseResponsesApi, DEFAULT_MODEL, DEFAULT_VIDEO_MODEL, ValidationError, } from './models.js';
|
|
21
21
|
export { ValidationError };
|
|
22
|
-
import { getValidChatGptAuth } from './chatgpt-auth.js';
|
|
22
|
+
import { chatGptReLoginMessage, getValidChatGptAuth, isRevokedChatGptAccessToken, refreshChatGptToken, resolveChatGptResponsesModel, } from './chatgpt-auth.js';
|
|
23
23
|
export function buildImageProviderOptions(provider, opts) {
|
|
24
24
|
switch (provider) {
|
|
25
25
|
case 'google': {
|
|
@@ -181,21 +181,70 @@ function imageBytesToDataUrl(image) {
|
|
|
181
181
|
const mediaType = inferImageMediaType(image);
|
|
182
182
|
return `data:${mediaType};base64,${Buffer.from(image).toString('base64')}`;
|
|
183
183
|
}
|
|
184
|
-
function chatGptImageSizeFromAspectRatio(aspectRatio) {
|
|
185
|
-
switch (aspectRatio) {
|
|
186
|
-
case '1:1':
|
|
187
|
-
return '1024x1024';
|
|
188
|
-
case '3:2':
|
|
189
|
-
return '1536x1024';
|
|
190
|
-
case '2:3':
|
|
191
|
-
return '1024x1536';
|
|
192
|
-
default:
|
|
193
|
-
return undefined;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
184
|
function isAspectRatio(input) {
|
|
197
185
|
return /^\d+:\d+$/.test(input);
|
|
198
186
|
}
|
|
187
|
+
// ─── aspect ratio fallback for models without native support ─────────────────
|
|
188
|
+
//
|
|
189
|
+
// OpenAI image models (gpt-image-*, dall-e-*) reject `aspectRatio`: the AI SDK
|
|
190
|
+
// pushes an "unsupported: aspectRatio" warning and the request falls back to a
|
|
191
|
+
// square image. They accept a fixed `size` list instead, and they also honour a
|
|
192
|
+
// plain-language ratio statement in the prompt text when picking the framing.
|
|
193
|
+
// So for these models we do both: snap to the closest supported size AND state
|
|
194
|
+
// the ratio in the prompt. The prompt hint matters because the supported sizes
|
|
195
|
+
// are coarse (only 3:2 / 1:1 / 2:3), so a request like 16:9 has no exact match
|
|
196
|
+
// and the composition has to come from the prompt.
|
|
197
|
+
//
|
|
198
|
+
// Models with a non-empty `features.aspectRatios` keep the native flag.
|
|
199
|
+
const OPENAI_SIZES = ['1024x1024', '1536x1024', '1024x1536'];
|
|
200
|
+
function ratioValue(aspectRatio) {
|
|
201
|
+
const [w, h] = aspectRatio.split(':').map(Number);
|
|
202
|
+
if (!w || !h)
|
|
203
|
+
return undefined;
|
|
204
|
+
return w / h;
|
|
205
|
+
}
|
|
206
|
+
function orientationLabel(ratio) {
|
|
207
|
+
if (ratio > 1.02)
|
|
208
|
+
return 'landscape';
|
|
209
|
+
if (ratio < 0.98)
|
|
210
|
+
return 'portrait';
|
|
211
|
+
return 'square';
|
|
212
|
+
}
|
|
213
|
+
/** Pick the supported WIDTHxHEIGHT whose ratio is closest to the requested one. */
|
|
214
|
+
function closestSize(aspectRatio, sizes) {
|
|
215
|
+
const target = ratioValue(aspectRatio);
|
|
216
|
+
if (target === undefined || sizes.length === 0)
|
|
217
|
+
return undefined;
|
|
218
|
+
let best;
|
|
219
|
+
let bestDistance = Infinity;
|
|
220
|
+
for (const size of sizes) {
|
|
221
|
+
const [w, h] = size.split('x').map(Number);
|
|
222
|
+
if (!w || !h)
|
|
223
|
+
continue;
|
|
224
|
+
// Compare in log space so 2x-too-wide and 2x-too-tall are equally far off.
|
|
225
|
+
const distance = Math.abs(Math.log(w / h) - Math.log(target));
|
|
226
|
+
if (distance < bestDistance) {
|
|
227
|
+
bestDistance = distance;
|
|
228
|
+
best = size;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return best;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Translate an aspect ratio request into a prompt prefix + concrete `size` for
|
|
235
|
+
* models that have no native aspect ratio parameter.
|
|
236
|
+
*/
|
|
237
|
+
export function aspectRatioFallback(prompt, aspectRatio, sizes) {
|
|
238
|
+
if (!aspectRatio)
|
|
239
|
+
return { prompt };
|
|
240
|
+
const ratio = ratioValue(aspectRatio);
|
|
241
|
+
if (ratio === undefined)
|
|
242
|
+
return { prompt };
|
|
243
|
+
return {
|
|
244
|
+
prompt: `Image aspect ratio: ${aspectRatio}, ${orientationLabel(ratio)} orientation. ${prompt}`,
|
|
245
|
+
size: closestSize(aspectRatio, sizes),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
199
248
|
/** Convert any thrown value into an Error instance. */
|
|
200
249
|
function toError(err) {
|
|
201
250
|
if (err instanceof Error)
|
|
@@ -336,12 +385,19 @@ export async function generateVideoUncached(opts) {
|
|
|
336
385
|
}
|
|
337
386
|
// ─── internal generation paths ───────────────────────────────────────────────
|
|
338
387
|
async function generateWithImageModel(opts) {
|
|
339
|
-
const imagePrompt = opts.inputImages.length > 0
|
|
340
|
-
? { text: opts.prompt, images: opts.inputImages, ...(opts.maskImage ? { mask: opts.maskImage } : {}) }
|
|
341
|
-
: opts.prompt;
|
|
342
388
|
const config = getModelConfig(opts.model);
|
|
343
389
|
if (config instanceof Error)
|
|
344
390
|
return config;
|
|
391
|
+
// Models with an empty `aspectRatios` list have no native aspect ratio
|
|
392
|
+
// parameter, so the request is expressed through `size` + the prompt text.
|
|
393
|
+
const features = config.strategy === 'video' ? undefined : config.features;
|
|
394
|
+
const supportsNativeAspectRatio = (features?.aspectRatios?.length ?? 0) > 0;
|
|
395
|
+
const fallback = supportsNativeAspectRatio
|
|
396
|
+
? { prompt: opts.prompt, size: undefined }
|
|
397
|
+
: aspectRatioFallback(opts.prompt, opts.aspectRatio, features?.sizes ?? []);
|
|
398
|
+
const imagePrompt = opts.inputImages.length > 0
|
|
399
|
+
? { text: fallback.prompt, images: opts.inputImages, ...(opts.maskImage ? { mask: opts.maskImage } : {}) }
|
|
400
|
+
: fallback.prompt;
|
|
345
401
|
const imageModel = await createImageModel(opts.model);
|
|
346
402
|
if (imageModel instanceof Error)
|
|
347
403
|
return imageModel;
|
|
@@ -359,7 +415,8 @@ async function generateWithImageModel(opts) {
|
|
|
359
415
|
model: imageModel,
|
|
360
416
|
prompt: imagePrompt,
|
|
361
417
|
n: opts.count,
|
|
362
|
-
...(opts.aspectRatio ? { aspectRatio: opts.aspectRatio } : {}),
|
|
418
|
+
...(opts.aspectRatio && supportsNativeAspectRatio ? { aspectRatio: opts.aspectRatio } : {}),
|
|
419
|
+
...(fallback.size ? { size: fallback.size } : {}),
|
|
363
420
|
...(opts.seed !== undefined ? { seed: opts.seed } : {}),
|
|
364
421
|
providerOptions,
|
|
365
422
|
});
|
|
@@ -453,10 +510,7 @@ async function generateWithResponsesApi(opts) {
|
|
|
453
510
|
if (opts.maskImage) {
|
|
454
511
|
return new ValidationError('ChatGPT image generation does not support mask yet.');
|
|
455
512
|
}
|
|
456
|
-
const size = opts.
|
|
457
|
-
if (opts.aspectRatio && !size) {
|
|
458
|
-
return new ValidationError('ChatGPT image generation only supports aspect ratios 1:1, 3:2, or 2:3.');
|
|
459
|
-
}
|
|
513
|
+
const { prompt, size } = aspectRatioFallback(opts.prompt, opts.aspectRatio, OPENAI_SIZES);
|
|
460
514
|
const storedAuth = getChatGptAuth();
|
|
461
515
|
if (!storedAuth?.accountId) {
|
|
462
516
|
return new ValidationError('Missing ChatGPT account metadata. Please run `egaki login --provider chatgpt` again.');
|
|
@@ -465,49 +519,62 @@ async function generateWithResponsesApi(opts) {
|
|
|
465
519
|
if (auth instanceof Error)
|
|
466
520
|
return auth;
|
|
467
521
|
const content = [
|
|
468
|
-
{ type: 'input_text', text:
|
|
522
|
+
{ type: 'input_text', text: prompt },
|
|
469
523
|
...opts.inputImages.map((image) => ({
|
|
470
524
|
type: 'input_image',
|
|
471
525
|
image_url: imageBytesToDataUrl(image),
|
|
472
526
|
})),
|
|
473
527
|
];
|
|
528
|
+
const responsesModel = await resolveChatGptResponsesModel({ auth });
|
|
529
|
+
if (responsesModel instanceof Error)
|
|
530
|
+
return responsesModel;
|
|
531
|
+
const requestBodyFor = (model) => JSON.stringify({
|
|
532
|
+
model,
|
|
533
|
+
instructions: 'You are Codex.',
|
|
534
|
+
input: [
|
|
535
|
+
{
|
|
536
|
+
type: 'message',
|
|
537
|
+
role: 'user',
|
|
538
|
+
content,
|
|
539
|
+
},
|
|
540
|
+
],
|
|
541
|
+
tools: [
|
|
542
|
+
{
|
|
543
|
+
type: 'image_generation',
|
|
544
|
+
model: opts.model,
|
|
545
|
+
quality: 'auto',
|
|
546
|
+
output_format: 'png',
|
|
547
|
+
output_compression: 100,
|
|
548
|
+
moderation: 'auto',
|
|
549
|
+
size: size ?? 'auto',
|
|
550
|
+
},
|
|
551
|
+
],
|
|
552
|
+
tool_choice: 'auto',
|
|
553
|
+
parallel_tool_calls: true,
|
|
554
|
+
stream: true,
|
|
555
|
+
store: false,
|
|
556
|
+
include: [],
|
|
557
|
+
});
|
|
558
|
+
const post = ({ access, accountId, body, }) => {
|
|
559
|
+
const headers = new Headers({
|
|
560
|
+
Authorization: `Bearer ${access}`,
|
|
561
|
+
'Content-Type': 'application/json',
|
|
562
|
+
});
|
|
563
|
+
if (accountId)
|
|
564
|
+
headers.set('ChatGPT-Account-ID', accountId);
|
|
565
|
+
return fetch('https://chatgpt.com/backend-api/codex/responses', {
|
|
566
|
+
method: 'POST',
|
|
567
|
+
headers,
|
|
568
|
+
body,
|
|
569
|
+
});
|
|
570
|
+
};
|
|
571
|
+
let activeModel = responsesModel;
|
|
474
572
|
let response;
|
|
475
573
|
try {
|
|
476
|
-
response = await
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
...(auth.accountId && { 'ChatGPT-Account-ID': auth.accountId }),
|
|
481
|
-
'Content-Type': 'application/json',
|
|
482
|
-
},
|
|
483
|
-
body: JSON.stringify({
|
|
484
|
-
model: 'gpt-5.4',
|
|
485
|
-
instructions: 'You are Codex.',
|
|
486
|
-
input: [
|
|
487
|
-
{
|
|
488
|
-
type: 'message',
|
|
489
|
-
role: 'user',
|
|
490
|
-
content,
|
|
491
|
-
},
|
|
492
|
-
],
|
|
493
|
-
tools: [
|
|
494
|
-
{
|
|
495
|
-
type: 'image_generation',
|
|
496
|
-
model: opts.model,
|
|
497
|
-
size: 'auto',
|
|
498
|
-
quality: 'auto',
|
|
499
|
-
output_format: 'png',
|
|
500
|
-
output_compression: 100,
|
|
501
|
-
moderation: 'auto',
|
|
502
|
-
...(size ? { size } : {}),
|
|
503
|
-
},
|
|
504
|
-
],
|
|
505
|
-
tool_choice: 'auto',
|
|
506
|
-
parallel_tool_calls: true,
|
|
507
|
-
stream: true,
|
|
508
|
-
store: false,
|
|
509
|
-
include: [],
|
|
510
|
-
}),
|
|
574
|
+
response = await post({
|
|
575
|
+
access: auth.access,
|
|
576
|
+
accountId: auth.accountId,
|
|
577
|
+
body: requestBodyFor(activeModel),
|
|
511
578
|
});
|
|
512
579
|
}
|
|
513
580
|
catch (err) {
|
|
@@ -515,7 +582,52 @@ async function generateWithResponsesApi(opts) {
|
|
|
515
582
|
}
|
|
516
583
|
if (!response.ok || !response.body) {
|
|
517
584
|
const body = await response.text().catch(() => '');
|
|
518
|
-
|
|
585
|
+
if (!(response.status === 400 && body.includes('not supported'))) {
|
|
586
|
+
if (!isRevokedChatGptAccessToken(response.status, body)) {
|
|
587
|
+
return new Error(`ChatGPT image generation failed: ${response.status}${body ? ` ${body}` : ''}`);
|
|
588
|
+
}
|
|
589
|
+
const refreshed = await refreshChatGptToken(auth);
|
|
590
|
+
if (refreshed instanceof Error)
|
|
591
|
+
return refreshed;
|
|
592
|
+
saveChatGptAuth(refreshed);
|
|
593
|
+
try {
|
|
594
|
+
response = await post({
|
|
595
|
+
access: refreshed.access,
|
|
596
|
+
accountId: refreshed.accountId,
|
|
597
|
+
body: requestBodyFor(activeModel),
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
catch (err) {
|
|
601
|
+
return toError(err);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
else {
|
|
605
|
+
const nextModel = await resolveChatGptResponsesModel({
|
|
606
|
+
auth,
|
|
607
|
+
exclude: [activeModel],
|
|
608
|
+
});
|
|
609
|
+
if (nextModel instanceof Error) {
|
|
610
|
+
return new Error(`ChatGPT image generation failed: ${response.status} ${body}`);
|
|
611
|
+
}
|
|
612
|
+
activeModel = nextModel;
|
|
613
|
+
try {
|
|
614
|
+
response = await post({
|
|
615
|
+
access: auth.access,
|
|
616
|
+
accountId: auth.accountId,
|
|
617
|
+
body: requestBodyFor(activeModel),
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
catch (err) {
|
|
621
|
+
return toError(err);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
if (!response.ok || !response.body) {
|
|
625
|
+
const retryBody = await response.text().catch(() => '');
|
|
626
|
+
if (isRevokedChatGptAccessToken(response.status, retryBody)) {
|
|
627
|
+
return new Error(chatGptReLoginMessage({ status: response.status, body: retryBody }));
|
|
628
|
+
}
|
|
629
|
+
return new Error(`ChatGPT image generation failed: ${response.status}${retryBody ? ` ${retryBody}` : ''}`);
|
|
630
|
+
}
|
|
519
631
|
}
|
|
520
632
|
let imageBase64;
|
|
521
633
|
let revisedPrompt = null;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Tests the error-as-value pattern and validation logic without
|
|
3
3
|
// making actual API calls (no credentials needed).
|
|
4
4
|
import { describe, it, expect } from 'vitest';
|
|
5
|
-
import { generateImage, generateVideo, calculateCost, buildImageProviderOptions, buildVideoProviderOptions, ValidationError, } from './generate.js';
|
|
5
|
+
import { generateImage, generateVideo, calculateCost, buildImageProviderOptions, buildVideoProviderOptions, aspectRatioFallback, ValidationError, } from './generate.js';
|
|
6
6
|
describe('generateImage', () => {
|
|
7
7
|
it('returns ValidationError for unknown model', async () => {
|
|
8
8
|
const result = await generateImage({
|
|
@@ -110,6 +110,55 @@ describe('buildImageProviderOptions', () => {
|
|
|
110
110
|
expect(opts).toEqual({});
|
|
111
111
|
});
|
|
112
112
|
});
|
|
113
|
+
describe('aspectRatioFallback', () => {
|
|
114
|
+
const openaiSizes = ['1024x1024', '1536x1024', '1024x1536'];
|
|
115
|
+
it('leaves the prompt untouched when no aspect ratio is requested', () => {
|
|
116
|
+
expect(aspectRatioFallback('a sunset', undefined, openaiSizes)).toMatchInlineSnapshot(`
|
|
117
|
+
{
|
|
118
|
+
"prompt": "a sunset",
|
|
119
|
+
}
|
|
120
|
+
`);
|
|
121
|
+
});
|
|
122
|
+
it('prepends the ratio and picks the closest landscape size', () => {
|
|
123
|
+
expect(aspectRatioFallback('a sunset', '16:9', openaiSizes)).toMatchInlineSnapshot(`
|
|
124
|
+
{
|
|
125
|
+
"prompt": "Image aspect ratio: 16:9, landscape orientation. a sunset",
|
|
126
|
+
"size": "1536x1024",
|
|
127
|
+
}
|
|
128
|
+
`);
|
|
129
|
+
});
|
|
130
|
+
it('picks the closest portrait size', () => {
|
|
131
|
+
expect(aspectRatioFallback('a sunset', '9:16', openaiSizes)).toMatchInlineSnapshot(`
|
|
132
|
+
{
|
|
133
|
+
"prompt": "Image aspect ratio: 9:16, portrait orientation. a sunset",
|
|
134
|
+
"size": "1024x1536",
|
|
135
|
+
}
|
|
136
|
+
`);
|
|
137
|
+
});
|
|
138
|
+
it('picks the square size for 1:1', () => {
|
|
139
|
+
expect(aspectRatioFallback('a sunset', '1:1', openaiSizes)).toMatchInlineSnapshot(`
|
|
140
|
+
{
|
|
141
|
+
"prompt": "Image aspect ratio: 1:1, square orientation. a sunset",
|
|
142
|
+
"size": "1024x1024",
|
|
143
|
+
}
|
|
144
|
+
`);
|
|
145
|
+
});
|
|
146
|
+
it('still prepends the ratio when the model has no size list', () => {
|
|
147
|
+
expect(aspectRatioFallback('a sunset', '21:9', [])).toMatchInlineSnapshot(`
|
|
148
|
+
{
|
|
149
|
+
"prompt": "Image aspect ratio: 21:9, landscape orientation. a sunset",
|
|
150
|
+
"size": undefined,
|
|
151
|
+
}
|
|
152
|
+
`);
|
|
153
|
+
});
|
|
154
|
+
it('ignores malformed ratios', () => {
|
|
155
|
+
expect(aspectRatioFallback('a sunset', '0:0', openaiSizes)).toMatchInlineSnapshot(`
|
|
156
|
+
{
|
|
157
|
+
"prompt": "a sunset",
|
|
158
|
+
}
|
|
159
|
+
`);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
113
162
|
describe('buildVideoProviderOptions', () => {
|
|
114
163
|
it('builds xai edit-video options', () => {
|
|
115
164
|
const opts = buildVideoProviderOptions('xai', {
|
package/dist/cli/login.d.ts
CHANGED
|
@@ -1,9 +1,44 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
type LoginAction = {
|
|
2
|
+
type: 'show';
|
|
3
|
+
} | {
|
|
4
|
+
type: 'remove';
|
|
5
|
+
provider: string;
|
|
6
|
+
} | {
|
|
7
|
+
type: 'oauth-daemon';
|
|
8
|
+
provider: string;
|
|
9
|
+
} | {
|
|
10
|
+
type: 'oauth-client';
|
|
11
|
+
provider: string;
|
|
12
|
+
} | {
|
|
13
|
+
type: 'api-key';
|
|
14
|
+
provider: string;
|
|
15
|
+
key: string | undefined;
|
|
16
|
+
} | {
|
|
17
|
+
type: 'interactive';
|
|
18
|
+
};
|
|
19
|
+
export declare function resolveLoginAction({ options, daemonProvider, isAgent, }: {
|
|
20
|
+
options: {
|
|
21
|
+
provider?: string;
|
|
22
|
+
key?: string;
|
|
23
|
+
show?: boolean;
|
|
24
|
+
remove?: string;
|
|
25
|
+
};
|
|
26
|
+
daemonProvider: string | undefined;
|
|
27
|
+
isAgent: boolean;
|
|
28
|
+
}): LoginAction | Error;
|
|
29
|
+
export declare function loginInteractive({ loginOAuth, }: {
|
|
30
|
+
loginOAuth: (provider: string) => Promise<void>;
|
|
31
|
+
}): Promise<void>;
|
|
32
|
+
export declare function loginNonInteractive({ provider, key, background, }: {
|
|
3
33
|
provider: string;
|
|
4
34
|
key: string;
|
|
35
|
+
background?: boolean;
|
|
5
36
|
}): Promise<void>;
|
|
6
|
-
export declare function
|
|
37
|
+
export declare function describeChatGptExpiry(expires: number, now: number): string;
|
|
38
|
+
export declare function showLoginStatus({ loginRunning }?: {
|
|
39
|
+
loginRunning?: boolean | undefined;
|
|
40
|
+
}): void;
|
|
7
41
|
export declare function removeLogin(provider: string): void;
|
|
8
42
|
export declare function readKeyFromStdin(): Promise<string>;
|
|
43
|
+
export {};
|
|
9
44
|
//# sourceMappingURL=login.d.ts.map
|
package/dist/cli/login.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/cli/login.ts"],"names":[],"mappings":"AA4BA,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/cli/login.ts"],"names":[],"mappings":"AA4BA,KAAK,WAAW,GACZ;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GAC9D;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,CAAA;AAM3B,wBAAgB,kBAAkB,CAAC,EACjC,OAAO,EACP,cAAc,EACd,OAAO,GACR,EAAE;IACD,OAAO,EAAE;QACP,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,GAAG,CAAC,EAAE,MAAM,CAAA;QACZ,IAAI,CAAC,EAAE,OAAO,CAAA;QACd,MAAM,CAAC,EAAE,MAAM,CAAA;KAChB,CAAA;IACD,cAAc,EAAE,MAAM,GAAG,SAAS,CAAA;IAClC,OAAO,EAAE,OAAO,CAAA;CACjB,GAAG,WAAW,GAAG,KAAK,CAiBtB;AAED,wBAAsB,gBAAgB,CAAC,EACrC,UAAU,GACX,EAAE;IACD,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAChD,GAAG,OAAO,CAAC,IAAI,CAAC,CA8FhB;AAED,wBAAsB,mBAAmB,CAAC,EACxC,QAAQ,EACR,GAAG,EACH,UAAkB,GACnB,EAAE;IACD,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiChB;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAG1E;AAED,wBAAgB,eAAe,CAAC,EAAE,YAAoB,EAAE;;CAAK,GAAG,IAAI,CA4DnE;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAalD;AAGD,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC,CAMxD"}
|
package/dist/cli/login.js
CHANGED
|
@@ -1,13 +1,37 @@
|
|
|
1
1
|
// Login command — interactive and non-interactive API key management.
|
|
2
2
|
// Interactive mode uses clack prompts for provider selection and key input.
|
|
3
3
|
// Non-interactive mode supports --provider + --key flags and stdin piping.
|
|
4
|
-
//
|
|
4
|
+
// OAuth providers run through the login command's goke daemon.
|
|
5
5
|
import { intro, outro, select, password, isCancel, cancel, log, note, } from '@clack/prompts';
|
|
6
|
-
import pc from '
|
|
6
|
+
import { colors as pc } from 'goke';
|
|
7
7
|
import { PROVIDERS, saveProviderKey, removeProviderKey, getKeyStatus, getChatGptAuth, saveChatGptAuth, getXaiAuth, saveXaiAuth, } from './credentials.js';
|
|
8
8
|
import { chatGptOAuthLogin, extractPlanType } from './chatgpt-auth.js';
|
|
9
9
|
import { xaiOAuthLogin } from './xai-auth.js';
|
|
10
|
-
|
|
10
|
+
function isOAuthProvider(provider) {
|
|
11
|
+
return provider === 'chatgpt' || provider === 'xai-oauth';
|
|
12
|
+
}
|
|
13
|
+
export function resolveLoginAction({ options, daemonProvider, isAgent, }) {
|
|
14
|
+
if (options.show)
|
|
15
|
+
return { type: 'show' };
|
|
16
|
+
if (options.remove)
|
|
17
|
+
return { type: 'remove', provider: options.remove };
|
|
18
|
+
if (daemonProvider && !isOAuthProvider(daemonProvider)) {
|
|
19
|
+
return new Error(`Invalid background login provider: ${daemonProvider}`);
|
|
20
|
+
}
|
|
21
|
+
if (daemonProvider)
|
|
22
|
+
return { type: 'oauth-daemon', provider: daemonProvider };
|
|
23
|
+
if (options.provider && isOAuthProvider(options.provider)) {
|
|
24
|
+
return { type: 'oauth-client', provider: options.provider };
|
|
25
|
+
}
|
|
26
|
+
if (options.provider) {
|
|
27
|
+
return { type: 'api-key', provider: options.provider, key: options.key };
|
|
28
|
+
}
|
|
29
|
+
if (isAgent) {
|
|
30
|
+
return new Error('Missing --provider. Run egaki login --provider <name>.');
|
|
31
|
+
}
|
|
32
|
+
return { type: 'interactive' };
|
|
33
|
+
}
|
|
34
|
+
export async function loginInteractive({ loginOAuth, }) {
|
|
11
35
|
intro(pc.bold('egaki login'));
|
|
12
36
|
const providerOptions = Object.entries(PROVIDERS).map(([key, info]) => {
|
|
13
37
|
const status = getKeyStatus(key);
|
|
@@ -58,16 +82,12 @@ export async function loginInteractive() {
|
|
|
58
82
|
}
|
|
59
83
|
// ChatGPT uses browser OAuth instead of key paste
|
|
60
84
|
if (provider === 'chatgpt') {
|
|
61
|
-
|
|
62
|
-
saveChatGptAuth(auth);
|
|
63
|
-
outro('Done — ChatGPT OAuth saved');
|
|
85
|
+
await loginOAuth(provider);
|
|
64
86
|
return;
|
|
65
87
|
}
|
|
66
88
|
// xAI Grok Build uses browser OAuth
|
|
67
89
|
if (provider === 'xai-oauth') {
|
|
68
|
-
|
|
69
|
-
saveXaiAuth(auth);
|
|
70
|
-
outro('Done — xAI OAuth saved');
|
|
90
|
+
await loginOAuth(provider);
|
|
71
91
|
return;
|
|
72
92
|
}
|
|
73
93
|
log.info(`${pc.dim('Env var:')} ${info.envVar} ${pc.dim('(also used by the CLI when set)')}`);
|
|
@@ -89,7 +109,7 @@ export async function loginInteractive() {
|
|
|
89
109
|
note(`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"`)}`, 'Tip');
|
|
90
110
|
outro('Done');
|
|
91
111
|
}
|
|
92
|
-
export async function loginNonInteractive({ provider, key, }) {
|
|
112
|
+
export async function loginNonInteractive({ provider, key, background = false, }) {
|
|
93
113
|
const info = PROVIDERS[provider];
|
|
94
114
|
if (!info) {
|
|
95
115
|
const available = Object.keys(PROVIDERS).join(', ');
|
|
@@ -98,14 +118,14 @@ export async function loginNonInteractive({ provider, key, }) {
|
|
|
98
118
|
}
|
|
99
119
|
// ChatGPT uses browser OAuth — key flag is not needed
|
|
100
120
|
if (provider === 'chatgpt') {
|
|
101
|
-
const auth = await chatGptOAuthLogin();
|
|
121
|
+
const auth = await chatGptOAuthLogin({ openInBackground: background });
|
|
102
122
|
saveChatGptAuth(auth);
|
|
103
123
|
console.log(pc.green('ChatGPT OAuth saved'));
|
|
104
124
|
return;
|
|
105
125
|
}
|
|
106
126
|
// xAI Grok Build uses browser OAuth — key flag is not needed
|
|
107
127
|
if (provider === 'xai-oauth') {
|
|
108
|
-
const auth = await xaiOAuthLogin();
|
|
128
|
+
const auth = await xaiOAuthLogin({ openInBackground: background });
|
|
109
129
|
saveXaiAuth(auth);
|
|
110
130
|
console.log(pc.green('xAI OAuth saved'));
|
|
111
131
|
return;
|
|
@@ -117,7 +137,14 @@ export async function loginNonInteractive({ provider, key, }) {
|
|
|
117
137
|
saveProviderKey(provider, key.trim());
|
|
118
138
|
console.log(pc.green(`${info.label} key saved`));
|
|
119
139
|
}
|
|
120
|
-
export function
|
|
140
|
+
export function describeChatGptExpiry(expires, now) {
|
|
141
|
+
if (expires < now)
|
|
142
|
+
return 'expired locally, the next request will try to refresh';
|
|
143
|
+
return 'not expired locally';
|
|
144
|
+
}
|
|
145
|
+
export function showLoginStatus({ loginRunning = false } = {}) {
|
|
146
|
+
if (loginRunning)
|
|
147
|
+
console.log(pc.cyan('OAuth login is running in the background.\n'));
|
|
121
148
|
console.log(pc.bold('Configured providers:\n'));
|
|
122
149
|
for (const [key, info] of Object.entries(PROVIDERS)) {
|
|
123
150
|
const status = getKeyStatus(key);
|
|
@@ -126,10 +153,10 @@ export function showLoginStatus() {
|
|
|
126
153
|
if (auth) {
|
|
127
154
|
const plan = extractPlanType(auth);
|
|
128
155
|
const email = auth.email ?? auth.accountId ?? 'unknown';
|
|
129
|
-
const
|
|
130
|
-
const expiryLabel =
|
|
131
|
-
? pc.yellow(
|
|
132
|
-
: pc.dim(`(
|
|
156
|
+
const expiryText = describeChatGptExpiry(auth.expires, Date.now());
|
|
157
|
+
const expiryLabel = auth.expires < Date.now()
|
|
158
|
+
? pc.yellow(`(${expiryText})`)
|
|
159
|
+
: pc.dim(`(${expiryText})`);
|
|
133
160
|
console.log(`${pc.green('*')} ${info.label} ${pc.green('(signed in)')}`);
|
|
134
161
|
console.log(pc.dim(` account: ${email}${plan ? `, plan: ${plan}` : ''}`));
|
|
135
162
|
console.log(` ${expiryLabel}`);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.test.d.ts","sourceRoot":"","sources":["../../src/cli/login.test.ts"],"names":[],"mappings":""}
|