diff --git a/README.md b/README.md index c01030d..c0d9972 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,8 @@ insta status # 登录态 + 已 link 的 project/branch | `insta secrets list [--branch]` | 仅列出 secret 名 | | `insta deploy --image [--branch --group --port]` | 部署镜像 | | `insta manifest [--json]` | agent 可读的环境清单 | +| `insta metrics [group] [--branch --from --to --step --json]` | 资源指标(compute=Fly;db 受限) | +| `insta logs [group] [--branch --limit --region --instance --json]` | 运行时日志(compute=Fly;db 受限) | | `insta events [--branch --limit --json]` | 审计 + agent 事件时间线 | | `insta approvals list [--status] [--json]` | 治理审批列表 | | `insta approvals approve [--always]` / `deny ` | 批准 / 拒绝(admin) | @@ -65,5 +67,5 @@ cd ../platform && DATABASE_URL='postgres://postgres:insta@localhost:55432/insta_ INSTA_API_URL=http://localhost:8899 insta login --email you@x.com --password ... ``` -> v1 覆盖平台已实现的能力。`metrics` / `logs`(observability)与 `usage`(billing)待平台 #4/#5 -> 完成后补充;OAuth 浏览器登录、`compute add-group/scale/set-domain`、镜像构建后续加入。 +> `metrics` / `logs` 已支持(compute 实时;db 受 provider 限制)。`usage`(billing)待平台 #5 完成后补充; +> OAuth 浏览器登录、`compute add-group/scale/set-domain`、镜像构建后续加入。 diff --git a/src/commands/metrics.ts b/src/commands/metrics.ts new file mode 100644 index 0000000..70e252a --- /dev/null +++ b/src/commands/metrics.ts @@ -0,0 +1,34 @@ +import { ApiClient, requireProject } from '../api.js' +import { info, printJson } from '../util.js' + +function qs(params: Record): string { + const u = new URLSearchParams() + for (const [k, v] of Object.entries(params)) if (v !== undefined && v !== '') u.set(k, String(v)) + const s = u.toString() + return s ? `?${s}` : '' +} + +// insta metrics [group] +export async function metrics(component: string, group: string | undefined, opts: { branch?: string; from?: string; to?: string; step?: string; json?: boolean }): Promise { + const api = await ApiClient.load() + const p = await requireProject() + const res = await api.request('GET', `/projects/${p.projectId}/metrics${qs({ component, group, branch: opts.branch ?? p.branch, from: opts.from, to: opts.to, step: opts.step })}`) + if (opts.json) return printJson(res) + if (res.note) info(`note: ${res.note}`) + if (!res.series?.length) return info('(no series)') + for (const s of res.series) { + const last = s.points?.[s.points.length - 1] + info(`${s.name}${s.unit ? ` (${s.unit})` : ''}: ${last ? last[1] : 'n/a'} [${s.points?.length ?? 0} points]`) + } +} + +// insta logs [group] +export async function logs(component: string, group: string | undefined, opts: { branch?: string; limit?: string; region?: string; instance?: string; json?: boolean }): Promise { + const api = await ApiClient.load() + const p = await requireProject() + const res = await api.request('GET', `/projects/${p.projectId}/logs${qs({ component, group, branch: opts.branch ?? p.branch, limit: opts.limit, region: opts.region, instance: opts.instance })}`) + if (opts.json) return printJson(res) + if (res.note) info(`note: ${res.note}`) + if (!res.lines?.length) return info('(no logs)') + for (const l of res.lines) info(`${l.ts} ${(l.level ?? '').padEnd(5)} ${l.message}`) +} diff --git a/src/index.ts b/src/index.ts index 80a09e3..4947813 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,6 +11,7 @@ import { deploy } from './commands/deploy.js' import { manifest } from './commands/manifest.js' import * as govern from './commands/govern.js' import * as observe from './commands/observe.js' +import * as obs from './commands/metrics.js' function onError(e: unknown): never { if (e instanceof ApiError) die(`${e.message} (HTTP ${e.status})`) @@ -66,6 +67,14 @@ program.command('deploy').description('Deploy a container image to a branch comp // ---- manifest ---- program.command('manifest').description('Print an agent-legible view of the project environments').option('--json').action(guard((o) => manifest(o))) +// ---- observability ---- +program.command('metrics [group]').description('Resource metrics (component: db|compute)') + .option('--branch ').option('--from ').option('--to ').option('--step ').option('--json') + .action(guard((component, group, o) => obs.metrics(component, group, o))) +program.command('logs [group]').description('Runtime logs (component: db|compute)') + .option('--branch ').option('--limit ').option('--region ').option('--instance ').option('--json') + .action(guard((component, group, o) => obs.logs(component, group, o))) + // ---- events (audit timeline) ---- program.command('events').description('Show the audit + agent-event timeline').option('--branch ').option('--limit ').option('--json').action(guard((o) => govern.events(o)))