跳到正文

Inspect、call、dev 与 build

显式声明可调用操作,理解命令效果,并用结构化 CLI 诊断定位失败。

本页描述已发布的 @lenso/cli@0.19.0 与 Core/Engine 0.2.0,源码审计基线固定为 549b987。排查不同版本时,应先查看实际安装入口的 lenso help --json。

按命令实际执行内容选择

命令主要效果
help --json列出命令、参数、错误码与退出码,不导入应用配置。
inspect [plugin-id [method]] --json导入 canonical 可信 lenso.config.ts,描述声明,不执行 setup。
check --json执行可信 Engine setup/discovery,验证装配,不运行应用 setup。
generate --json执行 Engine,写入归框架所有的 .lenso 文件。
build [--entry file] --json先生成,再打包到 dist。
call plugin-id method验证输入,启动完整应用,调用一个显式操作,再关闭。
dev [--entry file]监听源码并监督自己拥有的 Engine/运行时进程,不支持 --json。

所有命令接受 --root directory。--entry 仅适用于 build/dev。Inspect 不是沙箱:即使不运行插件 setup 和配置 source read,import 和可信 schema converter 的顶层代码仍会执行。

在应用旁显式声明操作

lenso.config.ts 默认导出 defineApp({ plugins }),独立的具名 operations 导出是 CLI 调用白名单。具名 mcpOperations 独立选择 MCP 操作;省略时 MCP 回退到 operations,显式空列表则关闭这个回退。具名 operationBinding 属于 CLI 入口。这些导出都不能放在默认 defineApp 声明中,返回服务的方法也不会自动暴露。

Notes 使用返回 { plugin, operations, manage } 的 companion factory,其中的真实声明为:

const operations = [  defineOperation({    plugin,    method: "list",    context: true,    input: notesListInput,    effect: "read",    destructive: false,    retry: "safe",    cancellation: "none",    outputDescription: "The authenticated user's private notes.",    source,    description: "List the authenticated user's private notes.",  }),];

真实文件从 @lenso/engine/operations 导入 defineOperation,@lenso/cli 也重新导出它。工厂只创建一次 plugin,并使用共享契约。应用配置 安装 notesOperations.plugin,再从声明中选择 CLI/MCP 子集。selectManageOperations 返回原 Operation,Manage 不增加另一套 handler,也不授予权限。每个声明必须引用同一个已安装对象。

defineOperation 按 Standard Schema v1 输出检查服务方法输入类型。运行时要求返回服务上存在 own callable method,并以原服务为 this 调用。单输入方法继续受支持。需要请求证据或 Auth 产生的 actor 时,方法可以声明 context: true,context 类型从真实方法的第二参数推导。可信入口把该参数与业务 JSON 分开提供。

Notes 通过 NotesOperationContext 传入可信入口的证据,每次调用都经 Auth 验证,并在 Notes 服务中保留 owner 检查。业务 JSON 中的 actor、subjectId 或权限标记都不是证据。操作声明负责暴露,权限由服务规则决定。

启动后绑定可信上下文

在现有 Notes 配置中,选定的 operations 旁加入有类型的具名 binding:

import type { OperationBinding } from "@lenso/cli";
export const operationBinding: OperationBinding<typeof operations[number]> =  () => ({ context: { evidence: process.env.NOTES_SESSION ?? null } });

OperationBinding<O> 接收 (operation, validatedInput, running)。CLI 先验证一次原始业务输入(包括 schema transform),再启动完整应用,最后等待 binding。OperationContext<O> 取得真实方法的 context 类型,OperationBoundOptions<O> 要求 context: true 声明提供 context。类型和 context 的存在不证明身份:Notes 通过 authentication.for(...).required(...) 验证证据,再执行共享服务策略。

OperationInvocationOptions 包含 context、maxOutputBytes、confirm、approve,没有顶层 signal 选项。Notes 的真实 context 可携带 signal,方法明确把它传给 Auth;binding 和 cancellation 元数据都不会自动取消所有业务效果。

inspect 描述 contextRequired、confirmation、approval,不调用 binding,也不推断 context schema。MCP 只使用可信启动选项 StdioOptions.binding,不继承 CLI 的 operationBinding。Manage 适配器使用自己的逐次 binding,并借用已启动应用。

分别执行确认和审批要求

Operation 可以声明 confirmation: "required"、approval: "required",或同时声明。共享调用路径在 context binding 后、服务调用前,要求对应的可信 confirm/approve callback 返回精确的 true。入口必须实现真实流程,验证这次调用的用户确认或配置的审批所有者;常量 callback 或业务 JSON 标记不能建立证据。

缺少 context 返回 missing-context-binding,缺少或未通过的 gate 返回 confirmation-required / approval-required。这些检查可能在完整应用 setup 后失败,所以 CLI 仍会等待资源清理。白名单、Auth 权限、确认、审批是独立要求;destructive: true 本身不会触发这些 gate。

先 inspect,再选择一种输入来源调用

在版本匹配、准备好的框架 checkout 中,可以检查真实 Notes 操作,而不连接其数据库或 Auth:

bun run cli inspect notes-operations list --root examples/notes --json

完成显式迁移与 Notes 认证配置后,使用已授权的入口环境调用:

printf '%s\n' '{}' |  bun run cli call notes-operations list --root examples/notes --stdin --json

这些命令使用仓库现有 launcher;外部应用使用自己安装的 lenso binary。Notes call 需要示例选中的数据库/storage 与有效 NOTES_SESSION。Inspect 不证明这些运行时前提已经满足。

输入只能选择一种来源:不敏感的 inline JSON、--stdin 或 --input-file file,全部省略时使用 {}。非法 JSON/输入和未知操作在启动前失败。合法调用会先解析所有已安装配置实例,再启动全部插件,即使选择的是只读操作。

每次调用拥有一个新应用,不共享正在运行的 HTTP 服务或上次 call 的内存状态。调用与清理失败会同时保留。输出或清理失败可能发生在业务写入已提交之后;重复不确定的修改前,先查询授权状态。

元数据是语义说明

effect 为 read、write 或 unknown。destructive、retry、cancellation 为用户和适配器描述语义,不会授权、实现幂等、自动重试、传递 AbortSignal 或回滚效果。省略的 retry/cancellation 为 unknown,省略的 destructive/output 元数据为 null。

outputDescription 是说明文本,不是输出验证器。返回有限、无环的普通 JSON,使用明确的 null 或结果对象,避免 undefined。Date、函数、stream、带 symbol 键的对象等不受支持值会产生 serialization-failed。共享调用路径先检查原结果,再脱敏并检查安全 JSON。默认输出预算为 1 MiB,可信 binding 可设置 maxOutputBytes,MCP 使用自身配置的宿主限制。超限在 CLI 停止应用前产生 output-too-large,不会撤销业务效果。

输入 inspect 在可用时使用验证器的 Standard JSON Schema converter,否则报告 schemaAvailability: "runtime-validation-only" 与 inputSchema: null,运行时验证仍然生效。Defaults/examples 被省略,但字段名与约束仍可被发现,所以 schema 和元数据不得嵌入 secret。

同时看 phase、code 和有序 cause

有限命令使用 --json 时,stdout 只有一个 envelope:{ schemaVersion: 1, ok: true, data } 或 { schemaVersion: 1, ok: false, error },日志走 stderr。

退出码含义首先检查
0成功查看该命令返回的数据。
2参数/输入Flag、JSON 来源、共享输入 schema。
3发现/装配配置 import、精确实例、操作声明。
1运行时/构建/输出配置预检、setup、invoke、cleanup、打包或 JSON 序列化。

可信配置 import 失败与运行时配置预检不同。配置预检失败使用 phase config 和顶层 config-invalid,有序的安全 cause 给出实例、字段路径与来源。Setup/invoke/cleanup 可能分别失败;遇到 invocation-and-cleanup-failed 应保留两组 cause。

未知应用错误文本、原始输入和 stack 会被省略,避免泄露 secret。Console 方法被转到 stderr 并进行脱敏,但直接写 stdout 或藏在普通字段名下的 secret 无法可靠隔离。Stdout 应只保留协议,应用也应主动避免记录敏感值。

实际就绪后报告 dev 状态

在现有 server 入口中,应用启动和监听成功后,通过 @lenso/engine/dev-ready 调用 reportDevReady({ urls: [actualUrl], capabilities })。纯服务入口可以省略 URLs。这个 helper 在非 supervised dev 中无操作,不需要手写 IPC。

使用 lenso dev --root .,或通过 --entry 选择现有入口。失败的启动继续监听改动。build 写 bundle,不会发布 npm、配置数据库或部署 Workers。自定义生成/target 与文件归属见 Engine。

程序化 API inspect(root, pluginId?, method?)、call(root, pluginId, method, input)、invoke(appDefinition, pluginId, method, input, binding?) 从 @lenso/cli 导出。程序化 invoke 默认使用 appDefinition.operationBinding,第五参数可以选择 binding;call 从 canonical 配置加载具名 binding,不接收 binding 参数。构建阶段使用 Engine。完整行为见 CLI 契约 与参数解析实现。