diff --git a/package.json b/package.json index f78eb5c..c412ed1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@insforge/cli", - "version": "0.1.36", + "version": "0.1.37", "description": "InsForge CLI - Command line tool for InsForge platform", "type": "module", "bin": { diff --git a/src/commands/deployments/env-vars.ts b/src/commands/deployments/env-vars.ts new file mode 100644 index 0000000..0265554 --- /dev/null +++ b/src/commands/deployments/env-vars.ts @@ -0,0 +1,111 @@ +import type { Command } from 'commander'; +import { ossFetch } from '../../lib/api/oss.js'; +import { requireAuth } from '../../lib/credentials.js'; +import { handleError, getRootOpts, ProjectNotLinkedError, CLIError } from '../../lib/errors.js'; +import { getProjectConfig } from '../../lib/config.js'; +import { outputJson, outputTable, outputSuccess } from '../../lib/output.js'; +import { reportCliUsage } from '../../lib/skills.js'; + +interface EnvVar { + id: string; + key: string; + type: string; + updatedAt: number; +} + +export function registerDeploymentsEnvVarsCommand(deploymentsCmd: Command): void { + const envCmd = deploymentsCmd.command('env').description('Manage deployment environment variables'); + + // list + envCmd + .command('list') + .description('List all deployment environment variables') + .action(async (_opts, cmd) => { + const { json } = getRootOpts(cmd); + try { + await requireAuth(); + if (!getProjectConfig()) throw new ProjectNotLinkedError(); + + const res = await ossFetch('/api/deployments/env-vars'); + const data = (await res.json()) as { envVars: EnvVar[] }; + const envVars = data.envVars ?? []; + + if (json) { + outputJson(data); + } else { + if (!envVars.length) { + console.log('No environment variables found.'); + return; + } + outputTable( + ['ID', 'Key', 'Type', 'Updated At'], + envVars.map((v) => [ + v.id, + v.key, + v.type, + new Date(v.updatedAt).toLocaleString(), + ]), + ); + } + await reportCliUsage('cli.deployments.env.list', true); + } catch (err) { + await reportCliUsage('cli.deployments.env.list', false); + handleError(err, json); + } + }); + + // create / update + envCmd + .command('set ') + .description('Create or update a deployment environment variable') + .action(async (key: string, value: string, _opts, cmd) => { + const { json } = getRootOpts(cmd); + try { + await requireAuth(); + if (!getProjectConfig()) throw new ProjectNotLinkedError(); + + const res = await ossFetch('/api/deployments/env-vars', { + method: 'POST', + body: JSON.stringify({ envVars: [{ key, value }] }), + }); + const data = (await res.json()) as { success: boolean; message: string; count: number }; + + if (json) { + outputJson(data); + } else { + outputSuccess(data.message); + } + await reportCliUsage('cli.deployments.env.set', true); + } catch (err) { + await reportCliUsage('cli.deployments.env.set', false); + handleError(err, json); + } + }); + + // delete + envCmd + .command('delete ') + .description('Delete a deployment environment variable by ID') + .action(async (id: string, _opts, cmd) => { + const { json } = getRootOpts(cmd); + try { + await requireAuth(); + if (!getProjectConfig()) throw new ProjectNotLinkedError(); + + const res = await ossFetch(`/api/deployments/env-vars/${encodeURIComponent(id)}`, { + method: 'DELETE', + }); + const data = (await res.json()) as { success: boolean; message: string }; + + if (json) { + outputJson(data); + } else { + outputSuccess(data.message); + } + await reportCliUsage('cli.deployments.env.delete', true); + } catch (err) { + await reportCliUsage('cli.deployments.env.delete', false); + handleError(err, json); + } + }); +} diff --git a/src/commands/diagnose/logs.ts b/src/commands/diagnose/logs.ts index 1c625b8..f8bb2fb 100644 --- a/src/commands/diagnose/logs.ts +++ b/src/commands/diagnose/logs.ts @@ -6,10 +6,15 @@ import { getProjectConfig } from '../../lib/config.js'; import { outputJson, outputTable } from '../../lib/output.js'; import { reportCliUsage } from '../../lib/skills.js'; -const LOG_SOURCES = ['insforge.logs', 'postgREST.logs', 'postgres.logs', 'function.logs'] as const; +const LOG_SOURCES = ['insforge.logs', 'postgREST.logs', 'postgres.logs', 'function.logs', 'function-deploy.logs'] as const; const ERROR_PATTERN = /\b(error|fatal|panic)\b/i; +/** Maps source names to their API paths. Most use /api/logs/{source}, but some have custom paths. */ +const SOURCE_PATH: Record = { + 'function-deploy.logs': '/api/logs/functions/build-logs', +}; + interface LogEntry { timestamp: string; message: string; @@ -32,8 +37,14 @@ function parseLogEntry(entry: unknown): { ts: string; msg: string } { return { ts, msg }; } +function getLogPath(source: string, limit: number): string { + const custom = SOURCE_PATH[source]; + if (custom) return `${custom}?limit=${limit}`; + return `/api/logs/${encodeURIComponent(source)}?limit=${limit}`; +} + async function fetchSourceLogs(source: string, limit: number): Promise { - const res = await ossFetch(`/api/logs/${encodeURIComponent(source)}?limit=${limit}`); + const res = await ossFetch(getLogPath(source, limit)); const data = await res.json(); const logs = Array.isArray(data) ? data : ((data as Record).logs as unknown[]) ?? []; diff --git a/src/commands/logs.ts b/src/commands/logs.ts index dfa82a6..84b6ca1 100644 --- a/src/commands/logs.ts +++ b/src/commands/logs.ts @@ -4,13 +4,24 @@ import { requireAuth } from '../lib/credentials.js'; import { handleError, getRootOpts, CLIError } from '../lib/errors.js'; import { outputJson } from '../lib/output.js'; -const VALID_SOURCES = ['insforge.logs', 'postgREST.logs', 'postgres.logs', 'function.logs'] as const; +const VALID_SOURCES = ['insforge.logs', 'postgREST.logs', 'postgres.logs', 'function.logs', 'function-deploy.logs'] as const; const SOURCE_LOOKUP = new Map(VALID_SOURCES.map((s) => [s.toLowerCase(), s])); +/** Maps source names to their API paths. Most use /api/logs/{source}, but some have custom paths. */ +const SOURCE_PATH: Record = { + 'function-deploy.logs': '/api/logs/functions/build-logs', +}; + +function getLogPath(source: string, limit: number): string { + const custom = SOURCE_PATH[source]; + if (custom) return `${custom}?limit=${limit}`; + return `/api/logs/${encodeURIComponent(source)}?limit=${limit}`; +} + export function registerLogsCommand(program: Command): void { program .command('logs ') - .description('Fetch backend container logs (insforge.logs | postgREST.logs | postgres.logs | function.logs)') + .description('Fetch backend container logs (insforge.logs | postgREST.logs | postgres.logs | function.logs | function-deploy.logs)') .option('--limit ', 'Number of log entries to return', '20') .action(async (source: string, opts, cmd) => { const { json } = getRootOpts(cmd); @@ -23,7 +34,7 @@ export function registerLogsCommand(program: Command): void { } const limit = parseInt(opts.limit, 10) || 20; - const res = await ossFetch(`/api/logs/${encodeURIComponent(resolved)}?limit=${limit}`); + const res = await ossFetch(getLogPath(resolved, limit)); const data = await res.json(); if (json) { diff --git a/src/index.ts b/src/index.ts index 5748ce4..24dd749 100644 --- a/src/index.ts +++ b/src/index.ts @@ -41,6 +41,7 @@ import { registerDeploymentsDeployCommand } from './commands/deployments/deploy. import { registerDeploymentsListCommand } from './commands/deployments/list.js'; import { registerDeploymentsStatusCommand } from './commands/deployments/status.js'; import { registerDeploymentsCancelCommand } from './commands/deployments/cancel.js'; +import { registerDeploymentsEnvVarsCommand } from './commands/deployments/env-vars.js'; import { registerDocsCommand } from './commands/docs.js'; import { registerSecretsListCommand } from './commands/secrets/list.js'; @@ -145,6 +146,7 @@ registerDeploymentsDeployCommand(deploymentsCmd); registerDeploymentsListCommand(deploymentsCmd); registerDeploymentsStatusCommand(deploymentsCmd); registerDeploymentsCancelCommand(deploymentsCmd); +registerDeploymentsEnvVarsCommand(deploymentsCmd); // registerDeploymentsMetadataCommand(deploymentsCmd); // slug command doesn't work yet. // registerDeploymentsSlugCommand(deploymentsCmd); diff --git a/src/lib/skills.ts b/src/lib/skills.ts index 3072492..e4496f7 100644 --- a/src/lib/skills.ts +++ b/src/lib/skills.ts @@ -38,13 +38,13 @@ function updateGitignore(): void { export async function installSkills(json: boolean): Promise { try { if (!json) clack.log.info('Installing InsForge agent skills...'); - await execAsync('npx skills add insforge/agent-skills -y -s insforge -s insforge-cli -a antigravity -a augment -a claude-code -a cline -a codex -a cursor -a gemini-cli -a github-copilot -a kilo -a qoder -a qwen-code -a roo -a trae -a windsurf', { + await execAsync('npx skills add insforge/agent-skills -y -a antigravity -a augment -a claude-code -a cline -a codex -a cursor -a gemini-cli -a github-copilot -a kilo -a qoder -a qwen-code -a roo -a trae -a windsurf', { cwd: process.cwd(), timeout: 60_000, }); if (!json) clack.log.success('InsForge agent skills installed.'); } catch { - if (!json) clack.log.warn('Failed to install agent skills. You can run manually: npx skills add insforge/agent-skills -s insforge -s insforge-cli'); + if (!json) clack.log.warn('Failed to install agent skills. You can run manually: npx skills add insforge/agent-skills'); } // Install find-skills from vercel-labs for skill discovery