fix: restore CLI and quota provider startup (#1857)

Export ngrok tunnel capabilities for CLI startup
Import Clack spinner/progress helpers used in interactive CLI paths
Restore Google quota provider registry exports
This commit is contained in:
Bohdan Triapitsyn
2026-06-27 09:19:24 +03:00
parent 1cec2f3b1a
commit 38c9ff77c9
7 changed files with 85 additions and 10 deletions
+2
View File
@@ -15,6 +15,8 @@ import {
select, select,
text, text,
password, password,
spinner,
progress,
cancel, cancel,
isCancel, isCancel,
} from '@clack/prompts'; } from '@clack/prompts';
+36
View File
@@ -0,0 +1,36 @@
import { describe, expect, it } from 'vitest';
async function withInteractiveTty(fn) {
const stdoutDescriptor = Object.getOwnPropertyDescriptor(process.stdout, 'isTTY');
const stdinDescriptor = Object.getOwnPropertyDescriptor(process.stdin, 'isTTY');
Object.defineProperty(process.stdout, 'isTTY', { configurable: true, value: true });
Object.defineProperty(process.stdin, 'isTTY', { configurable: true, value: true });
try {
return await fn();
} finally {
if (stdoutDescriptor) {
Object.defineProperty(process.stdout, 'isTTY', stdoutDescriptor);
} else {
delete process.stdout.isTTY;
}
if (stdinDescriptor) {
Object.defineProperty(process.stdin, 'isTTY', stdinDescriptor);
} else {
delete process.stdin.isTTY;
}
}
}
describe('cli output', () => {
it('creates interactive clack spinner and progress helpers', async () => {
await withInteractiveTty(async () => {
const output = await import('./cli-output.js?interactive-test');
expect(output.createSpinner({})).toBeTruthy();
await expect(output.createProgress({}, { max: 2 })).resolves.toBeTruthy();
});
});
});
+12
View File
@@ -10,6 +10,11 @@ import { pathToFileURL } from 'url';
import { isModuleCliExecution, normalizeCliEntryPath } from './cli-entry.js'; import { isModuleCliExecution, normalizeCliEntryPath } from './cli-entry.js';
import { requestJson } from './lib/cli-http.js'; import { requestJson } from './lib/cli-http.js';
import { inspectTunnelAttachability } from './lib/cli-lifecycle.js'; import { inspectTunnelAttachability } from './lib/cli-lifecycle.js';
import { DEFAULT_TUNNEL_PROVIDER_CAPABILITIES } from './lib/cli-tunnel-capabilities.js';
import {
TUNNEL_PROVIDER_CLOUDFLARE,
TUNNEL_PROVIDER_NGROK,
} from '../server/lib/tunnels/types.js';
import { import {
assertAuthenticatedNetworkExposure, assertAuthenticatedNetworkExposure,
commands, commands,
@@ -173,6 +178,13 @@ function spawnOpenChamberLikeHungServer(port) {
} }
describe('cli args', () => { describe('cli args', () => {
it('loads fallback tunnel provider capabilities for CLI startup', () => {
expect(DEFAULT_TUNNEL_PROVIDER_CAPABILITIES.map((provider) => provider.provider)).toEqual([
TUNNEL_PROVIDER_CLOUDFLARE,
TUNNEL_PROVIDER_NGROK,
]);
});
it('accepts legacy daemon flags as no-ops', () => { it('accepts legacy daemon flags as no-ops', () => {
expect(parseArgs(['serve', '--daemon']).removedFlagErrors).toEqual([]); expect(parseArgs(['serve', '--daemon']).removedFlagErrors).toEqual([]);
expect(parseArgs(['serve', '-d']).removedFlagErrors).toEqual([]); expect(parseArgs(['serve', '-d']).removedFlagErrors).toEqual([]);
@@ -11,12 +11,20 @@ import {
fetchGoogleModels fetchGoogleModels
} from './api.js'; } from './api.js';
export { resolveGoogleAuthSources } from './auth.js';
export const providerId = 'google';
export const providerName = 'Google';
export const aliases = ['google', 'google.oauth'];
export const isConfigured = () => resolveGoogleAuthSources().length > 0;
export const fetchGoogleQuota = async () => { export const fetchGoogleQuota = async () => {
const authSources = resolveGoogleAuthSources(); const authSources = resolveGoogleAuthSources();
if (!authSources.length) { if (!authSources.length) {
return buildResult({ return buildResult({
providerId: 'google', providerId,
providerName: 'Google', providerName,
ok: false, ok: false,
configured: false, configured: false,
error: 'Not configured' error: 'Not configured'
@@ -76,8 +84,8 @@ export const fetchGoogleQuota = async () => {
if (!Object.keys(models).length) { if (!Object.keys(models).length) {
return buildResult({ return buildResult({
providerId: 'google', providerId,
providerName: 'Google', providerName,
ok: false, ok: false,
configured: true, configured: true,
error: sourceErrors[0] ?? 'Failed to fetch models' error: sourceErrors[0] ?? 'Failed to fetch models'
@@ -85,8 +93,8 @@ export const fetchGoogleQuota = async () => {
} }
return buildResult({ return buildResult({
providerId: 'google', providerId,
providerName: 'Google', providerName,
ok: true, ok: true,
configured: true, configured: true,
usage: { usage: {
@@ -43,9 +43,9 @@ const registry = {
fetchQuota: cursor.fetchQuota fetchQuota: cursor.fetchQuota
}, },
google: { google: {
providerId: 'google', providerId: google.providerId,
providerName: 'Google', providerName: google.providerName,
isConfigured: () => google.resolveGoogleAuthSources().length > 0, isConfigured: google.isConfigured,
fetchQuota: google.fetchGoogleQuota fetchQuota: google.fetchGoogleQuota
}, },
'zai-coding-plan': { 'zai-coding-plan': {
@@ -0,0 +1,17 @@
import { describe, expect, it } from 'vitest';
import * as google from './google/index.js';
import { listConfiguredQuotaProviders } from './index.js';
describe('quota provider registry', () => {
it('exposes google provider configuration helpers through the provider module', () => {
expect(google.providerId).toBe('google');
expect(google.providerName).toBe('Google');
expect(typeof google.isConfigured).toBe('function');
expect(typeof google.resolveGoogleAuthSources).toBe('function');
});
it('can list configured providers without missing provider exports', () => {
expect(() => listConfiguredQuotaProviders()).not.toThrow();
});
});
@@ -13,7 +13,7 @@ import {
} from '../types.js'; } from '../types.js';
import { getTunnelDependencyInstallInfo } from '../install-help.js'; import { getTunnelDependencyInstallInfo } from '../install-help.js';
const ngrokTunnelProviderCapabilities = { export const ngrokTunnelProviderCapabilities = {
provider: TUNNEL_PROVIDER_NGROK, provider: TUNNEL_PROVIDER_NGROK,
defaults: { defaults: {
mode: TUNNEL_MODE_QUICK, mode: TUNNEL_MODE_QUICK,