diff --git a/src/commands/auth/login.ts b/src/commands/auth/login.ts index a047390..b979b6f 100644 --- a/src/commands/auth/login.ts +++ b/src/commands/auth/login.ts @@ -6,7 +6,7 @@ import * as readline from 'node:readline' export default class AuthLogin extends Command { static description = 'Authenticate with the DevHelm API' - static examples = ['<%= config.bin %> auth login', '<%= config.bin %> auth login --token sk_live_...'] + static examples = ['<%= config.bin %> auth login', '<%= config.bin %> auth login --token dh_live_...'] static flags = { ...globalFlags, token: Flags.string({description: 'API token (skips interactive prompt)'}), @@ -23,14 +23,35 @@ export default class AuthLogin extends Command { const apiUrl = flags['api-url'] || resolveApiUrl() this.log('Validating token...') const client = createApiClient({baseUrl: apiUrl, token}) + + // Try /api/v1/auth/me first (API key — returns rich identity info). + // Falls back to /api/v1/dashboard/overview for non-API-key tokens (dev tokens, JWTs). try { // eslint-disable-next-line @typescript-eslint/no-explicit-any - const me = await checkedFetch(client.GET('/platform/me' as any, {} as any)) + const resp = await checkedFetch(client.GET('/api/v1/auth/me' as any, {} as any)) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const me = (resp as any)?.data ?? resp + + saveContext({name: flags.name, apiUrl, token}, true) + this.log('') + this.log(` Authenticated successfully.`) + this.log(` Organization: ${me.organization?.name ?? 'unknown'} (ID: ${me.organization?.id ?? '?'})`) + this.log(` Key: ${me.key?.name ?? 'unknown'}`) + this.log(` Plan: ${me.plan?.tier ?? 'unknown'}`) + this.log('') + this.log(` Context '${flags.name}' saved to ~/.devhelm/contexts.json`) + return + } catch { + // /auth/me failed — might be a non-API-key token; try basic validation + } + + try { // eslint-disable-next-line @typescript-eslint/no-explicit-any - const email = (me as any)?.data?.email ?? (me as any)?.email + await checkedFetch(client.GET('/api/v1/dashboard/overview' as any, {} as any)) saveContext({name: flags.name, apiUrl, token}, true) - this.log(`\nAuthenticated as ${email}`) - this.log(`Context '${flags.name}' saved to ~/.devhelm/contexts.json`) + this.log('') + this.log(` Authenticated successfully.`) + this.log(` Context '${flags.name}' saved to ~/.devhelm/contexts.json`) } catch { this.error('Invalid token. Authentication failed.', {exit: 2}) } diff --git a/src/commands/auth/me.ts b/src/commands/auth/me.ts new file mode 100644 index 0000000..120e30d --- /dev/null +++ b/src/commands/auth/me.ts @@ -0,0 +1,60 @@ +import {Command} from '@oclif/core' +import {globalFlags, buildClient} from '../../lib/base-command.js' +import {checkedFetch} from '../../lib/api-client.js' +import {formatOutput, OutputFormat} from '../../lib/output.js' + +export default class AuthMe extends Command { + static description = 'Show current API key identity, organization, plan, and rate limits' + static examples = ['<%= config.bin %> auth me', '<%= config.bin %> auth me --output json'] + static flags = {...globalFlags} + + async run() { + const {flags} = await this.parse(AuthMe) + const client = buildClient(flags) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const resp = await checkedFetch(client.GET('/api/v1/auth/me' as any, {} as any)) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const me = (resp as any)?.data ?? resp + + const format = flags.output as OutputFormat + if (format === 'json' || format === 'yaml') { + this.log(formatOutput(me, format)) + return + } + + const k = me.key ?? {} + const o = me.organization ?? {} + const p = me.plan ?? {} + const r = me.rateLimits ?? {} + + this.log('') + this.log(' API Key') + this.log(` Name: ${k.name ?? '–'} ID: ${k.id ?? '–'}`) + this.log(` Created: ${k.createdAt ?? '–'} Expires: ${k.expiresAt ?? 'never'}`) + this.log(` Last used: ${k.lastUsedAt ?? 'never'}`) + this.log('') + this.log(' Organization') + this.log(` Name: ${o.name ?? '–'} ID: ${o.id ?? '–'}`) + this.log('') + this.log(' Plan') + this.log(` Tier: ${p.tier ?? '–'} Status: ${p.subscriptionStatus ?? '–'} Trial: ${p.trialActive ? `active (expires ${p.trialExpiresAt})` : 'no'}`) + this.log('') + this.log(' Rate Limits') + this.log(` Limit: ${r.requestsPerMinute ?? '–'} req/min Remaining: ${r.remaining ?? '–'} Window: ${r.windowMs ? `${r.windowMs / 1000}s` : '–'}`) + + const usage = p.usage as Record | undefined + const entitlements = p.entitlements as Record | undefined + if (usage && entitlements) { + this.log('') + this.log(' Usage') + for (const [key, used] of Object.entries(usage)) { + const limit = entitlements[key]?.value + const limitStr = limit != null && limit < Number.MAX_SAFE_INTEGER ? String(limit) : '∞' + const label = key.replace(/\./g, ' ').replace(/_/g, ' ') + this.log(` ${label}: ${used} / ${limitStr}`) + } + } + + this.log('') + } +}