feat: add OpenAI OAuth, strict model overrides, and Gmail pull mode
This commit is contained in:
@@ -9,9 +9,10 @@ import type { ModelConfig, ModelProvider } from '../../config/schema.js';
|
||||
import { MODEL_PROVIDERS } from '../../config/schema.js';
|
||||
import { OllamaClient, LlamaCppClient } from '../../models/index.js';
|
||||
import { createClientFromConfig } from '../../daemon/index.js';
|
||||
import { loginGitHub } from '../../auth/index.js';
|
||||
import { loginGitHub, loginOpenAI } from '../../auth/index.js';
|
||||
import type { PairingManager } from '../../channels/pairing.js';
|
||||
import { getColoredBanner } from './banner.js';
|
||||
import type { HookEngine } from '../../hooks/index.js';
|
||||
|
||||
export { parseCommand, type Command };
|
||||
|
||||
@@ -42,8 +43,10 @@ export interface MinimalTuiConfig {
|
||||
onFullscreen?: () => void;
|
||||
onTransfer?: (target: string) => void;
|
||||
localProviders?: Record<string, ModelConfig>;
|
||||
modelProviderConfigs?: Partial<Record<ModelProvider, ModelConfig>>;
|
||||
currentLocalProvider?: string;
|
||||
pairingManager?: PairingManager;
|
||||
hookEngine?: HookEngine;
|
||||
}
|
||||
|
||||
export class MinimalTui {
|
||||
@@ -99,6 +102,10 @@ export class MinimalTui {
|
||||
async start(): Promise<void> {
|
||||
this.running = true;
|
||||
|
||||
if (this.config.agent && this.config.modelRouter) {
|
||||
this.config.agent.setModelTier(this.config.modelRouter.getTier());
|
||||
}
|
||||
|
||||
this.rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
@@ -108,6 +115,26 @@ export class MinimalTui {
|
||||
},
|
||||
});
|
||||
|
||||
// In minimal TUI we can prompt inline for tool confirmations.
|
||||
// This avoids deadlocks when hooks are configured to require confirmation
|
||||
// (e.g. shell.exec) and the tool loop is awaiting a decision.
|
||||
if (this.config.hookEngine) {
|
||||
this.config.hookEngine.setInteractiveConfirmer(async (pending) => {
|
||||
const tool = pending.tool;
|
||||
const args = pending.args;
|
||||
const argsStr = Object.keys(args).length > 0 ? ` ${JSON.stringify(args)}` : '';
|
||||
console.log(`\n${colors.bold}Confirmation required${colors.reset}`);
|
||||
console.log(`${colors.gray}${tool}${colors.reset}${argsStr}`);
|
||||
|
||||
const answer = (await this.prompt(`${colors.orange}${colors.bold}Approve?${colors.reset} ${colors.gray}(y/N)${colors.reset} `))
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
const approved = answer === 'y' || answer === 'yes';
|
||||
console.log(approved ? `${colors.gray}Approved.${colors.reset}\n` : `${colors.gray}Denied.${colors.reset}\n`);
|
||||
return approved ? { approved: true } : { approved: false, reason: 'Denied by user' };
|
||||
});
|
||||
}
|
||||
|
||||
// Listen for line changes to show hints
|
||||
process.stdin.on('keypress', () => {
|
||||
// Small delay to let readline update the line
|
||||
@@ -239,9 +266,22 @@ export class MinimalTui {
|
||||
}
|
||||
|
||||
try {
|
||||
const client = createClientFromConfig({ provider: provider as ModelProvider, model });
|
||||
const providerType = provider as ModelProvider;
|
||||
const template = this.config.modelProviderConfigs?.[providerType];
|
||||
const client = createClientFromConfig({
|
||||
...(template ?? {}),
|
||||
provider: providerType,
|
||||
model,
|
||||
});
|
||||
router.setClient(tier, client, providerModel);
|
||||
console.log(`${colors.gray}Set ${tier} to:${colors.reset} ${providerModel}\n`);
|
||||
router.setTierStrict(tier, true);
|
||||
|
||||
if (this.config.agent && tier === router.getTier()) {
|
||||
this.config.agent.setModelTier(tier);
|
||||
}
|
||||
|
||||
console.log(`${colors.gray}Set ${tier} to:${colors.reset} ${providerModel}`);
|
||||
console.log(`${colors.gray}Fallbacks for ${tier} disabled (strict tier mode).${colors.reset}\n`);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
console.log(`${colors.gray}Failed to create client:${colors.reset} ${message}\n`);
|
||||
@@ -383,27 +423,49 @@ export class MinimalTui {
|
||||
|
||||
private async handleLoginCommand(provider?: string): Promise<void> {
|
||||
const target = provider ?? 'github';
|
||||
if (target !== 'github') {
|
||||
console.log(`${colors.gray}Unknown login provider:${colors.reset} ${target}. Only 'github' is supported.\n`);
|
||||
if (target === 'github') {
|
||||
console.log(`${colors.gray}Starting GitHub OAuth device flow...${colors.reset}`);
|
||||
|
||||
try {
|
||||
await loginGitHub((userCode, verificationUri) => {
|
||||
console.log('');
|
||||
console.log(`${colors.gray}Please visit:${colors.reset} ${verificationUri}`);
|
||||
console.log(`${colors.gray}and enter code:${colors.reset} ${userCode}`);
|
||||
console.log('');
|
||||
console.log(`${colors.gray}Waiting for authorization...${colors.reset}`);
|
||||
});
|
||||
|
||||
console.log(`${colors.gray}GitHub authentication successful! Token stored.${colors.reset}\n`);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
console.log(`${colors.gray}GitHub login failed:${colors.reset} ${message}\n`);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`${colors.gray}Starting GitHub OAuth device flow...${colors.reset}`);
|
||||
if (target === 'openai') {
|
||||
console.log(`${colors.gray}Starting OpenAI OAuth device flow...${colors.reset}`);
|
||||
|
||||
try {
|
||||
await loginGitHub((userCode, verificationUri) => {
|
||||
console.log('');
|
||||
console.log(`${colors.gray}Please visit:${colors.reset} ${verificationUri}`);
|
||||
console.log(`${colors.gray}and enter code:${colors.reset} ${userCode}`);
|
||||
console.log('');
|
||||
console.log(`${colors.gray}Waiting for authorization...${colors.reset}`);
|
||||
});
|
||||
try {
|
||||
await loginOpenAI((userCode, verificationUri) => {
|
||||
console.log('');
|
||||
console.log(`${colors.gray}Please visit:${colors.reset} ${verificationUri}`);
|
||||
console.log(`${colors.gray}and enter code:${colors.reset} ${userCode}`);
|
||||
console.log('');
|
||||
console.log(`${colors.gray}Waiting for authorization...${colors.reset}`);
|
||||
});
|
||||
|
||||
console.log(`${colors.gray}GitHub authentication successful! Token stored.${colors.reset}\n`);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
console.log(`${colors.gray}GitHub login failed:${colors.reset} ${message}\n`);
|
||||
console.log(`${colors.gray}OpenAI authentication successful! Token stored.${colors.reset}\n`);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
console.log(`${colors.gray}OpenAI login failed:${colors.reset} ${message}\n`);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`${colors.gray}Unknown login provider:${colors.reset} ${target}. Supported: github, openai\n`);
|
||||
}
|
||||
|
||||
private handlePairCommand(action?: 'generate' | 'list' | 'revoke', args?: string): void {
|
||||
|
||||
Reference in New Issue
Block a user