feat(tui): wire /pair command execution with PairingManager
This commit is contained in:
@@ -62,6 +62,10 @@ export function registerTuiCommand(program: Command): void {
|
||||
// local_providers, retry config, and per-tier fallback logic.
|
||||
const modelRouter = createModelRouter(config);
|
||||
|
||||
const { initPairingManager } = await import('../daemon/services.js');
|
||||
const pairingStore = config.pairing.enabled ? sessionStore.getPairingStore() : undefined;
|
||||
const pairingManager = initPairingManager(config, pairingStore);
|
||||
|
||||
const systemPrompt = loadSystemPrompt();
|
||||
|
||||
const hookEngine = new HookEngine(config.hooks);
|
||||
@@ -145,6 +149,7 @@ export function registerTuiCommand(program: Command): void {
|
||||
modelRouter,
|
||||
systemPrompt,
|
||||
agent,
|
||||
pairingManager,
|
||||
localProviders: config.models.local_providers,
|
||||
currentLocalProvider: config.models.local?.provider,
|
||||
onTransfer: (target) => {
|
||||
|
||||
@@ -9,6 +9,7 @@ import type { ModelConfig } from '../../config/schema.js';
|
||||
import { OllamaClient, LlamaCppClient } from '../../models/index.js';
|
||||
import { createClientFromConfig } from '../../daemon/index.js';
|
||||
import { loginGitHub } from '../../auth/index.js';
|
||||
import type { PairingManager } from '../../channels/pairing.js';
|
||||
|
||||
export { parseCommand, type Command };
|
||||
|
||||
@@ -40,6 +41,7 @@ export interface MinimalTuiConfig {
|
||||
onTransfer?: (target: string) => void;
|
||||
localProviders?: Record<string, ModelConfig>;
|
||||
currentLocalProvider?: string;
|
||||
pairingManager?: PairingManager;
|
||||
}
|
||||
|
||||
export class MinimalTui {
|
||||
@@ -196,6 +198,10 @@ export class MinimalTui {
|
||||
await this.handleLoginCommand(command.provider);
|
||||
break;
|
||||
|
||||
case 'pair':
|
||||
this.handlePairCommand(command.action, command.args);
|
||||
break;
|
||||
|
||||
case 'transfer':
|
||||
this.config.onTransfer?.(command.target);
|
||||
break;
|
||||
@@ -321,6 +327,74 @@ export class MinimalTui {
|
||||
}
|
||||
}
|
||||
|
||||
private handlePairCommand(action?: 'generate' | 'list' | 'revoke', args?: string): void {
|
||||
const pm = this.config.pairingManager;
|
||||
if (!pm) {
|
||||
console.log(`${colors.gray}Pairing not enabled. Set pairing.enabled: true in config.${colors.reset}\n`);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case 'generate': {
|
||||
const code = pm.generateCode(args);
|
||||
const pending = pm.listPendingCodes().find(p => p.code === code);
|
||||
const expiresIn = pending ? Math.round((pending.expiresAt - Date.now()) / 1000) : '?';
|
||||
console.log(`${colors.bold}Pairing code: ${code}${colors.reset}`);
|
||||
console.log(`${colors.gray}Expires in ${expiresIn}s${args ? ` (label: ${args})` : ''}${colors.reset}\n`);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'revoke': {
|
||||
if (!args) {
|
||||
console.log(`${colors.gray}Usage: /pair revoke <channel> <senderId>${colors.reset}\n`);
|
||||
return;
|
||||
}
|
||||
const parts = args.split(/\s+/);
|
||||
if (parts.length < 2) {
|
||||
console.log(`${colors.gray}Usage: /pair revoke <channel> <senderId>${colors.reset}\n`);
|
||||
return;
|
||||
}
|
||||
const [channel, senderId] = parts;
|
||||
const revoked = pm.revokeApproval(channel, senderId);
|
||||
if (revoked) {
|
||||
console.log(`${colors.bold}Revoked approval for ${channel}:${senderId}${colors.reset}\n`);
|
||||
} else {
|
||||
console.log(`${colors.gray}No approval found for ${channel}:${senderId}${colors.reset}\n`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'list':
|
||||
default: {
|
||||
const pending = pm.listPendingCodes();
|
||||
const approved = pm.listApproved();
|
||||
|
||||
if (pending.length === 0 && approved.length === 0) {
|
||||
console.log(`${colors.gray}No pending codes or approved senders.${colors.reset}\n`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pending.length > 0) {
|
||||
console.log(`${colors.bold}Pending codes:${colors.reset}`);
|
||||
for (const p of pending) {
|
||||
const expiresIn = Math.round((p.expiresAt - Date.now()) / 1000);
|
||||
console.log(` ${p.code} expires in ${expiresIn}s${p.label ? ` (${p.label})` : ''}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (approved.length > 0) {
|
||||
console.log(`${colors.bold}Approved senders:${colors.reset}`);
|
||||
for (const a of approved) {
|
||||
const date = new Date(a.approvedAt).toISOString().slice(0, 16).replace('T', ' ');
|
||||
console.log(` ${a.channel}:${a.senderId} since ${date} (code: ${a.codeUsed})`);
|
||||
}
|
||||
}
|
||||
console.log('');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getAvailableBackends(): string[] {
|
||||
const backends: string[] = [];
|
||||
if (this.config.currentLocalProvider) {
|
||||
|
||||
Reference in New Issue
Block a user