@x-otto/cli 0.0.1-alpha.41 → 0.0.1-alpha.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +13 -6
  2. package/dist/build-id-ClyMIKLy.js +3 -0
  3. package/dist/build-id-ClyMIKLy.js.map +1 -0
  4. package/dist/{command-dispatcher-BtW9xSr5.js → command-dispatcher-DhcLUPR5.js} +13 -13
  5. package/dist/command-dispatcher-DhcLUPR5.js.map +1 -0
  6. package/dist/config-migration-DatflIxs.js.map +1 -1
  7. package/dist/create-host-app-DG58-vRA.js +2 -0
  8. package/dist/{daemon-base-YOWgYYN7.js → daemon-base-Bc9p51Um.js} +1 -1
  9. package/dist/{daemon-base-YOWgYYN7.js.map → daemon-base-Bc9p51Um.js.map} +1 -1
  10. package/dist/{doctor-CuNvDmyA.js → doctor-lT2QhG2q.js} +2 -2
  11. package/dist/{doctor-CuNvDmyA.js.map → doctor-lT2QhG2q.js.map} +1 -1
  12. package/dist/extension-CTyrcdT1.js +2 -0
  13. package/dist/{extension-1-lxuerZ.js → extension-DVEml5IN.js} +35 -34
  14. package/dist/{extension-1-lxuerZ.js.map → extension-DVEml5IN.js.map} +1 -1
  15. package/dist/{feedback-C0oxUebv.js → feedback-BQ_oz5nh.js} +2 -2
  16. package/dist/{feedback-C0oxUebv.js.map → feedback-BQ_oz5nh.js.map} +1 -1
  17. package/dist/feedback-C2Hdxp1y.js +2 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +7 -7
  20. package/dist/index.js.map +1 -1
  21. package/dist/install-DlVJRHRT.js.map +1 -1
  22. package/dist/{mcp-DbTYT7hB.js → mcp-9P_AmnDk.js} +1 -1
  23. package/dist/{mcp-DbTYT7hB.js.map → mcp-9P_AmnDk.js.map} +1 -1
  24. package/dist/mcp-server-BK4L-YBN.js +5 -0
  25. package/dist/mcp-server-BK4L-YBN.js.map +1 -0
  26. package/dist/{migrate-boDXhEyV.js → migrate-DDNkik_0.js} +1 -1
  27. package/dist/{migrate-boDXhEyV.js.map → migrate-DDNkik_0.js.map} +1 -1
  28. package/dist/{observe-CZ96Vre9.js → observe-CzIv09hO.js} +1 -1
  29. package/dist/{observe-CZ96Vre9.js.map → observe-CzIv09hO.js.map} +1 -1
  30. package/dist/parse-cli-3fWEIMWc.js +3 -0
  31. package/dist/parse-cli-3fWEIMWc.js.map +1 -0
  32. package/dist/{persistence-CpwMgSMe.js → persistence-DOaizHMA.js} +2 -2
  33. package/dist/{persistence-CpwMgSMe.js.map → persistence-DOaizHMA.js.map} +1 -1
  34. package/dist/{plugin-assembly-check-C2TephMb.js → plugin-assembly-check-Ov17ra7G.js} +1 -1
  35. package/dist/{plugin-assembly-check-C2TephMb.js.map → plugin-assembly-check-Ov17ra7G.js.map} +1 -1
  36. package/dist/proxy-evg7ZvoD.js.map +1 -1
  37. package/dist/{ps-DKrluEuy.js → ps-BovE9XMQ.js} +1 -1
  38. package/dist/{ps-DKrluEuy.js.map → ps-BovE9XMQ.js.map} +1 -1
  39. package/dist/{run-attach-CvGCWF_i.js → run-attach-z7YECztf.js} +1 -1
  40. package/dist/{run-attach-CvGCWF_i.js.map → run-attach-z7YECztf.js.map} +1 -1
  41. package/dist/{serve-DbBDYdoC.js → serve-DaxogAZ5.js} +2 -2
  42. package/dist/{serve-DbBDYdoC.js.map → serve-DaxogAZ5.js.map} +1 -1
  43. package/dist/{version-0tC0345U.js → version-Eh8zZKgk.js} +2 -2
  44. package/dist/{version-0tC0345U.js.map → version-Eh8zZKgk.js.map} +1 -1
  45. package/package.json +12 -12
  46. package/dist/build-id-DuVd7k7E.js +0 -3
  47. package/dist/build-id-DuVd7k7E.js.map +0 -1
  48. package/dist/command-dispatcher-BtW9xSr5.js.map +0 -1
  49. package/dist/create-host-app-DXII2WTM.js +0 -2
  50. package/dist/extension-DWGIIBc6.js +0 -2
  51. package/dist/feedback-eJ87AQPr.js +0 -2
  52. package/dist/parse-cli-DO9wTwtm.js +0 -3
  53. package/dist/parse-cli-DO9wTwtm.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"config-migration-DatflIxs.js","names":[],"sources":["../src/commands/config-migration.ts"],"sourcesContent":["/**\n * 配置迁移工具:settings → config 升级 + 备份 + dry-run。\n */\nimport { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { createInterface } from 'node:readline/promises'\nimport { dirname, join, resolve } from 'node:path'\nimport {\n OTTO_HOME,\n OTTO_CONFIG_FILENAME,\n OTTO_PROJECT_CONFIG_RELPATH,\n currentHostname,\n} from '@x-otto/env'\nimport {\n SettingsManager,\n buildExportPayload,\n parseExportFile,\n diffConfig,\n classifyPluginChanges,\n hasSecuritySensitiveChanges,\n applyMergeStrategy,\n ExportFileVersionError,\n ExportFileParseError,\n} from '@x-otto/setting'\nimport { installPluginFromPackage, recordPluginInstalled, HIGH_RISK_CAPABILITIES } from '@x-otto/coding'\nimport type { Setting, SettingKey } from '@x-otto/setting'\n\nexport interface ConfigMigrationIO {\n stdout: (s: string) => void\n stderr: (s: string) => void\n}\n\ninterface ParsedArgs {\n sub: string | undefined\n positional: string[]\n flags: Set<string>\n values: Map<string, string>\n}\n\nfunction parseArgs(args: string[]): ParsedArgs {\n const [sub, ...rest] = args\n const positional: string[] = []\n const flags = new Set<string>()\n const values = new Map<string, string>()\n for (let i = 0; i < rest.length; i++) {\n const a = rest[i]!\n if (a === '-o' || a === '--output') {\n const next = rest[++i]\n if (next) values.set('output', next)\n continue\n }\n if (a === '--scope') {\n const next = rest[++i]\n if (next) values.set('scope', next)\n continue\n }\n if (a.startsWith('--')) {\n flags.add(a)\n continue\n }\n positional.push(a)\n }\n return { sub, positional, flags, values }\n}\n\n// ── 路径解析 ──\n\nfunction globalConfigPath(): string {\n return join(OTTO_HOME, OTTO_CONFIG_FILENAME)\n}\n\nfunction projectConfigPath(cwd: string): string {\n return join(cwd, OTTO_PROJECT_CONFIG_RELPATH)\n}\n\nfunction backupPath(configPath: string, timestamp: string): string {\n return `${configPath}.bak-${timestamp}`\n}\n\nfunction timestampNow(): string {\n return new Date().toISOString().replace(/[:.]/g, '-')\n}\n\n// ── otto config export ──\n\nasync function runExport(parsed: ParsedArgs, io: ConfigMigrationIO): Promise<number> {\n const cwd = process.cwd()\n const useProject = parsed.flags.has('--project')\n const scope: 'global' | 'project' = useProject ? 'project' : 'global'\n const configPath = useProject ? projectConfigPath(cwd) : globalConfigPath()\n\n if (useProject && !existsSync(configPath)) {\n io.stderr(`项目级配置不存在:${configPath}\\n`)\n return 1\n }\n\n const settings = useProject\n ? new SettingsManager({ workspaceDir: cwd })\n : new SettingsManager({ projectConfigPath: globalConfigPath() })\n await settings.load()\n\n const payload = buildExportPayload(settings.config, scope, currentHostname())\n const outputPath = parsed.values.get('output')\n ? resolve(parsed.values.get('output')!)\n : join(process.cwd(), `otto-config-export-${timestampNow()}.json`)\n\n mkdirSync(dirname(outputPath), { recursive: true })\n writeFileSync(outputPath, JSON.stringify(payload, null, 2), 'utf-8')\n\n io.stdout(`配置已导出: ${outputPath}\\n`)\n return 0\n}\n\n// ── otto config import ──\n\n/** 两行/字段格式展示单个差异(RFC-219 设计定稿:抗长文本折行,不用表格对齐假设)。 */\nfunction formatFieldDiff(key: SettingKey, from: unknown, to: unknown): string {\n const fromStr = formatValue(from)\n const toStr = formatValue(to)\n return ` ${key}\\n ${fromStr} → ${toStr}`\n}\n\nfunction formatValue(value: unknown): string {\n if (value === undefined) return '(未设置)'\n if (Array.isArray(value)) return `[${value.length} 项]`\n if (typeof value === 'object' && value !== null) return JSON.stringify(value)\n return String(value)\n}\n\ninterface ImportPlan {\n diffs: ReturnType<typeof diffConfig>\n pluginDiff: ReturnType<typeof classifyPluginChanges>\n targetScope: 'global' | 'project'\n}\n\nfunction buildImportReport(plan: ImportPlan): string {\n const lines: string[] = []\n const changedNonPlugin = plan.diffs\n if (changedNonPlugin.length > 0) {\n lines.push(`将更新 ${changedNonPlugin.length} 个字段:`)\n for (const d of changedNonPlugin) {\n lines.push(formatFieldDiff(d.key, d.from, d.to))\n }\n }\n if (plan.pluginDiff.toInstall.length > 0) {\n lines.push(`\\n将安装 ${plan.pluginDiff.toInstall.length} 个插件:`)\n lines.push(` ${plan.pluginDiff.toInstall.map((p) => p.id).join(', ')}`)\n }\n if (plan.pluginDiff.localOnly.length > 0) {\n lines.push(`\\n${plan.pluginDiff.localOnly.length} 个本地插件不受影响(导入不卸载):`)\n lines.push(` ${plan.pluginDiff.localOnly.map((p) => p.id).join(', ')}`)\n }\n return lines.join('\\n')\n}\n\n/** 判定插件是否声明了真正的 `HIGH_RISK_CAPABILITIES`(对抗审核 P2 修复:不是\"任意非空 capabilities\")。 */\nfunction isHighRiskPlugin(capabilities: readonly string[] | undefined): boolean {\n return (capabilities ?? []).some((c) => (HIGH_RISK_CAPABILITIES as readonly string[]).includes(c))\n}\n\nasync function confirmSecuritySensitive(\n io: ConfigMigrationIO,\n plan: ImportPlan,\n yes: boolean,\n): Promise<boolean> {\n const sensitiveDiffs = plan.diffs.filter((d) => d.risk === 'sensitive')\n const sensitivePlugins = plan.pluginDiff.toInstall.filter((p) => isHighRiskPlugin(p.capabilities))\n\n const lines = ['安全相关变更:']\n for (const d of sensitiveDiffs) {\n lines.push(formatFieldDiff(d.key, d.from, d.to))\n }\n for (const p of sensitivePlugins) {\n lines.push(` + ${p.id} 声明高危能力: ${p.capabilities?.join(', ')}`)\n }\n io.stdout(`${lines.join('\\n')}\\n`)\n\n if (yes) {\n io.stdout('(--yes 已跳过交互确认,全部接受)\\n')\n return true\n }\n\n // 对抗审核 P1 修复:CI/管道场景无 TTY 时,readline.question() 会挂起等待 EOF 或产出\n // 不可预期结果——按 RFC-219 milestone M3 Grill 确认约定,无 TTY 时自动接受(`--yes`\n // 本就是\"信任本次导入\"的显式声明,无 TTY 环境视为等价于隐式给出该信号)。\n if (!process.stdin.isTTY) {\n io.stdout('(非交互终端,安全相关变更自动接受。可用 --yes 显式声明该意图)\\n')\n return true\n }\n\n const rl = createInterface({ input: process.stdin, output: process.stdout })\n try {\n const answer = (await rl.question('接受以上安全相关变更?[y/N] ')).trim().toLowerCase()\n return answer === 'y' || answer === 'yes'\n } finally {\n rl.close()\n }\n}\n\nasync function ensureBackup(configPath: string, io: ConfigMigrationIO): Promise<string> {\n const timestamp = timestampNow()\n const target = backupPath(configPath, timestamp)\n if (existsSync(configPath)) {\n const content = readFileSync(configPath, 'utf-8')\n mkdirSync(dirname(target), { recursive: true })\n writeFileSync(target, content, 'utf-8')\n } else {\n // 目标配置文件此前不存在(全新机器场景)——备份为空文件占位,restore 时按空文件处理\n // (等价于\"撤销即删除该配置文件\",见 runRestore 对空备份文件的处理)。\n mkdirSync(dirname(target), { recursive: true })\n writeFileSync(target, '', 'utf-8')\n }\n io.stdout(`备份: ${target}\\n`)\n return target\n}\n\nasync function runImport(parsed: ParsedArgs, io: ConfigMigrationIO): Promise<number> {\n const filePath = parsed.positional[0]\n if (!filePath) {\n io.stderr('otto config import <file> [--dry-run] [--scope global|project] [--no-plugins] [--plugins-only] [--yes]\\n')\n return 1\n }\n\n let raw: string\n try {\n raw = readFileSync(resolve(filePath), 'utf-8')\n } catch (err) {\n io.stderr(`无法读取导入文件:${err instanceof Error ? err.message : String(err)}\\n`)\n return 1\n }\n\n let exportFile: ReturnType<typeof parseExportFile>\n try {\n exportFile = parseExportFile(raw)\n } catch (err) {\n if (err instanceof ExportFileVersionError || err instanceof ExportFileParseError) {\n io.stderr(`${err.message}\\n`)\n return 1\n }\n throw err\n }\n\n // RFC-219 重要事项规则 4:scope 判定用确定性规则——默认遵循文件声明的 scope,\n // --scope 可显式覆盖,不做\"检测到歧义弹窗询问\"。\n const scopeOverride = parsed.values.get('scope')\n const targetScope: 'global' | 'project' =\n scopeOverride === 'global' || scopeOverride === 'project' ? scopeOverride : exportFile.scope\n\n const cwd = process.cwd()\n const configPath = targetScope === 'project' ? projectConfigPath(cwd) : globalConfigPath()\n const settings =\n targetScope === 'project'\n ? new SettingsManager({ workspaceDir: cwd })\n : new SettingsManager({ projectConfigPath: globalConfigPath() })\n await settings.load()\n\n const noPlugins = parsed.flags.has('--no-plugins')\n const pluginsOnly = parsed.flags.has('--plugins-only')\n const dryRun = parsed.flags.has('--dry-run')\n const yes = parsed.flags.has('--yes')\n\n // RFC-219 重要事项规则 5:installed_plugins 恒定路由 user 级配置,不受 targetScope 影响。\n const incomingFields = pluginsOnly ? {} : exportFile.fields\n const diffs = diffConfig(settings.config, incomingFields)\n const currentPlugins = settings.get('installed_plugins') ?? []\n const incomingPlugins = noPlugins ? [] : (exportFile.fields.installed_plugins ?? [])\n const pluginDiff = classifyPluginChanges(currentPlugins, incomingPlugins)\n\n const plan: ImportPlan = { diffs, pluginDiff, targetScope }\n\n // RFC-219 重要事项规则 3:零差异快路径。\n if (diffs.length === 0 && pluginDiff.toInstall.length === 0) {\n io.stdout('配置已是最新(与导入文件零差异),无需变更\\n')\n return 0\n }\n\n io.stdout(`${buildImportReport(plan)}\\n`)\n\n if (dryRun) {\n io.stdout('\\n(--dry-run 预览完毕,未执行任何写入)\\n')\n return 0\n }\n\n // RFC-219 D4:唯一合法确认——安全敏感变更批量门。\n let acceptAll = true\n if (hasSecuritySensitiveChanges(diffs, pluginDiff)) {\n const accepted = await confirmSecuritySensitive(io, plan, yes)\n if (!accepted) {\n acceptAll = false\n io.stdout('已跳过安全相关变更,继续导入其余安全字段\\n')\n }\n }\n\n // RFC-219 重要事项规则 6:强制自动备份(不可通过 flag 跳过)。\n await ensureBackup(configPath, io)\n\n // 应用配置字段变更。\n const toApply = applyMergeStrategy(diffs, acceptAll ? 'all' : 'safe-only')\n if (Object.keys(toApply).length > 0) {\n await settings.persist(toApply as Partial<Setting>, targetScope)\n }\n\n // installed_plugins 恒定路由 user 级(重要事项规则 5),与 targetScope 无关。\n // 对抗审核 P0 修复(安全门绕过):`acceptAll=false`(用户在安全门拒绝)时,声明\n // HIGH_RISK_CAPABILITIES 的插件必须被跳过——此前实现只对 applyMergeStrategy 应用了\n // 'safe-only',插件安装循环无条件跑全部 toInstall,等价于安全门形同虚设。\n const pluginsToInstall = acceptAll\n ? pluginDiff.toInstall\n : pluginDiff.toInstall.filter((p) => !isHighRiskPlugin(p.capabilities))\n const skippedHighRiskPlugins = acceptAll\n ? []\n : pluginDiff.toInstall.filter((p) => isHighRiskPlugin(p.capabilities))\n\n if (pluginsToInstall.length > 0) {\n const userSettings =\n targetScope === 'global' ? settings : new SettingsManager({ projectConfigPath: globalConfigPath() })\n if (targetScope !== 'global') await userSettings.load()\n\n const installed: string[] = []\n const failed: Array<{ id: string; error: string }> = []\n for (const entry of pluginsToInstall) {\n try {\n const res =\n entry.source === 'npm'\n ? await installPluginFromPackage({ pkg: entry.sourceSpec || entry.id })\n : undefined\n if (!res) {\n failed.push({ id: entry.id, error: `不支持从导入文件自动安装来源 \"${entry.source}\"` })\n continue\n }\n await recordPluginInstalled(userSettings, {\n id: res.id,\n source: 'npm',\n sourceSpec: entry.sourceSpec || entry.id,\n version: res.version,\n scope: 'global',\n capabilities: entry.capabilities,\n })\n installed.push(res.id)\n } catch (err) {\n failed.push({ id: entry.id, error: err instanceof Error ? err.message : String(err) })\n }\n }\n\n if (installed.length > 0) io.stdout(`✓ 已安装: ${installed.join(', ')}\\n`)\n for (const f of failed) {\n io.stdout(`✗ 安装失败: ${f.id} — ${f.error} → otto extension install ${f.id}\\n`)\n }\n }\n if (skippedHighRiskPlugins.length > 0) {\n io.stdout(\n `- 跳过(用户拒绝安全相关变更): ${skippedHighRiskPlugins.map((p) => p.id).join(', ')} ` +\n `→ 可稍后手动安装: otto extension install <name>\\n`,\n )\n }\n\n io.stdout(`\\n撤销: otto config restore\\n`)\n return 0\n}\n\n// ── otto config restore ──\n\nfunction listBackups(configPath: string): string[] {\n const dir = dirname(configPath)\n const prefix = `${OTTO_CONFIG_FILENAME}.bak-`\n if (!existsSync(dir)) return []\n return readdirSync(dir)\n .filter((f) => f.startsWith(prefix))\n .sort()\n .reverse()\n .map((f) => join(dir, f))\n}\n\nasync function runRestore(parsed: ParsedArgs, io: ConfigMigrationIO): Promise<number> {\n const useProject = parsed.flags.has('--project')\n const configPath = useProject ? projectConfigPath(process.cwd()) : globalConfigPath()\n const backups = listBackups(configPath)\n\n if (backups.length === 0) {\n io.stdout('未找到任何备份\\n')\n return 0\n }\n\n if (parsed.flags.has('--list')) {\n io.stdout(`可用备份(最新在前):\\n${backups.map((b) => ` ${b}`).join('\\n')}\\n`)\n return 0\n }\n\n const latest = backups[0]!\n const content = readFileSync(latest, 'utf-8')\n if (content === '') {\n // 空备份 = 撤销前配置文件本不存在(全新机器场景),撤销即删除。\n const { unlinkSync } = await import('node:fs')\n if (existsSync(configPath)) unlinkSync(configPath)\n io.stdout(`已恢复: ${configPath} 不存在(撤销前该文件本不存在)\\n`)\n return 0\n }\n mkdirSync(dirname(configPath), { recursive: true })\n writeFileSync(configPath, content, 'utf-8')\n io.stdout(`已恢复: ${configPath} ← ${latest}\\n`)\n return 0\n}\n\n// ── 入口 ──\n\nexport async function runConfigMigrationCommand(args: string[], io: ConfigMigrationIO): Promise<number> {\n const parsed = parseArgs(args)\n switch (parsed.sub) {\n case 'export':\n return runExport(parsed, io)\n case 'import':\n return runImport(parsed, io)\n case 'restore':\n return runRestore(parsed, io)\n default:\n io.stdout(\n 'otto config <命令>\\n' +\n ' export [--project] [-o <path>] 导出配置(默认用户级)\\n' +\n ' import <file> [--dry-run] [--scope global|project]\\n' +\n ' [--no-plugins] [--plugins-only] [--yes] 导入配置\\n' +\n ' restore [--list] [--project] 撤销最近一次导入\\n',\n )\n return parsed.sub ? 1 : 0\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"config-migration-DatflIxs.js","names":[],"sources":["../src/commands/config-migration.ts"],"sourcesContent":["/**\n * 配置迁移工具:settings → config 升级 + 备份 + dry-run。\n */\nimport { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { createInterface } from 'node:readline/promises'\nimport { dirname, join, resolve } from 'node:path'\nimport {\n OTTO_HOME,\n OTTO_CONFIG_FILENAME,\n OTTO_PROJECT_CONFIG_RELPATH,\n currentHostname,\n} from '@x-otto/env'\nimport {\n SettingsManager,\n buildExportPayload,\n parseExportFile,\n diffConfig,\n classifyPluginChanges,\n hasSecuritySensitiveChanges,\n applyMergeStrategy,\n ExportFileVersionError,\n ExportFileParseError,\n} from '@x-otto/setting'\nimport { installPluginFromPackage, recordPluginInstalled, HIGH_RISK_CAPABILITIES } from '@x-otto/coding'\nimport type { Setting, SettingKey } from '@x-otto/setting'\n\nexport interface ConfigMigrationIO {\n stdout: (s: string) => void\n stderr: (s: string) => void\n}\n\ninterface ParsedArgs {\n sub: string | undefined\n positional: string[]\n flags: Set<string>\n values: Map<string, string>\n}\n\nfunction parseArgs(args: string[]): ParsedArgs {\n const [sub, ...rest] = args\n const positional: string[] = []\n const flags = new Set<string>()\n const values = new Map<string, string>()\n for (let i = 0; i < rest.length; i++) {\n const a = rest[i]!\n if (a === '-o' || a === '--output') {\n const next = rest[++i]\n if (next) values.set('output', next)\n continue\n }\n if (a === '--scope') {\n const next = rest[++i]\n if (next) values.set('scope', next)\n continue\n }\n if (a.startsWith('--')) {\n flags.add(a)\n continue\n }\n positional.push(a)\n }\n return { sub, positional, flags, values }\n}\n\n// ── 路径解析 ──\n\nfunction globalConfigPath(): string {\n return join(OTTO_HOME, OTTO_CONFIG_FILENAME)\n}\n\nfunction projectConfigPath(cwd: string): string {\n return join(cwd, OTTO_PROJECT_CONFIG_RELPATH)\n}\n\nfunction backupPath(configPath: string, timestamp: string): string {\n return `${configPath}.bak-${timestamp}`\n}\n\nfunction timestampNow(): string {\n return new Date().toISOString().replace(/[:.]/g, '-')\n}\n\n// ── otto config export ──\n\nasync function runExport(parsed: ParsedArgs, io: ConfigMigrationIO): Promise<number> {\n const cwd = process.cwd()\n const useProject = parsed.flags.has('--project')\n const scope: 'global' | 'project' = useProject ? 'project' : 'global'\n const configPath = useProject ? projectConfigPath(cwd) : globalConfigPath()\n\n if (useProject && !existsSync(configPath)) {\n io.stderr(`项目级配置不存在:${configPath}\\n`)\n return 1\n }\n\n const settings = useProject\n ? new SettingsManager({ workspaceDir: cwd })\n : new SettingsManager({ projectConfigPath: globalConfigPath() })\n await settings.load()\n\n const payload = buildExportPayload(settings.config, scope, currentHostname())\n const outputPath = parsed.values.get('output')\n ? resolve(parsed.values.get('output')!)\n : join(process.cwd(), `otto-config-export-${timestampNow()}.json`)\n\n mkdirSync(dirname(outputPath), { recursive: true })\n writeFileSync(outputPath, JSON.stringify(payload, null, 2), 'utf-8')\n\n io.stdout(`配置已导出: ${outputPath}\\n`)\n return 0\n}\n\n// ── otto config import ──\n\n/** 两行/字段格式展示单个差异(RFC-219 设计定稿:抗长文本折行,不用表格对齐假设)。 */\nfunction formatFieldDiff(key: SettingKey, from: unknown, to: unknown): string {\n const fromStr = formatValue(from)\n const toStr = formatValue(to)\n return ` ${key}\\n ${fromStr} → ${toStr}`\n}\n\nfunction formatValue(value: unknown): string {\n if (value === undefined) return '(未设置)'\n if (Array.isArray(value)) return `[${value.length} 项]`\n if (typeof value === 'object' && value !== null) return JSON.stringify(value)\n return String(value)\n}\n\ninterface ImportPlan {\n diffs: ReturnType<typeof diffConfig>\n pluginDiff: ReturnType<typeof classifyPluginChanges>\n targetScope: 'global' | 'project'\n}\n\nfunction buildImportReport(plan: ImportPlan): string {\n const lines: string[] = []\n const changedNonPlugin = plan.diffs\n if (changedNonPlugin.length > 0) {\n lines.push(`将更新 ${changedNonPlugin.length} 个字段:`)\n for (const d of changedNonPlugin) {\n lines.push(formatFieldDiff(d.key, d.from, d.to))\n }\n }\n if (plan.pluginDiff.toInstall.length > 0) {\n lines.push(`\\n将安装 ${plan.pluginDiff.toInstall.length} 个插件:`)\n lines.push(` ${plan.pluginDiff.toInstall.map((p) => p.id).join(', ')}`)\n }\n if (plan.pluginDiff.localOnly.length > 0) {\n lines.push(`\\n${plan.pluginDiff.localOnly.length} 个本地插件不受影响(导入不卸载):`)\n lines.push(` ${plan.pluginDiff.localOnly.map((p) => p.id).join(', ')}`)\n }\n return lines.join('\\n')\n}\n\n/** 判定插件是否声明了真正的 `HIGH_RISK_CAPABILITIES`(对抗审核 P2 修复:不是\"任意非空 capabilities\")。 */\nfunction isHighRiskPlugin(capabilities: readonly string[] | undefined): boolean {\n return (capabilities ?? []).some((c) => (HIGH_RISK_CAPABILITIES as readonly string[]).includes(c))\n}\n\nasync function confirmSecuritySensitive(\n io: ConfigMigrationIO,\n plan: ImportPlan,\n yes: boolean,\n): Promise<boolean> {\n const sensitiveDiffs = plan.diffs.filter((d) => d.risk === 'sensitive')\n const sensitivePlugins = plan.pluginDiff.toInstall.filter((p) => isHighRiskPlugin(p.capabilities))\n\n const lines = ['安全相关变更:']\n for (const d of sensitiveDiffs) {\n lines.push(formatFieldDiff(d.key, d.from, d.to))\n }\n for (const p of sensitivePlugins) {\n lines.push(` + ${p.id} 声明高危能力: ${p.capabilities?.join(', ')}`)\n }\n io.stdout(`${lines.join('\\n')}\\n`)\n\n if (yes) {\n io.stdout('(--yes 已跳过交互确认,全部接受)\\n')\n return true\n }\n\n // 对抗审核 P1 修复:CI/管道场景无 TTY 时,readline.question() 会挂起等待 EOF 或产出\n // 不可预期结果——按 RFC-219 milestone M3 Grill 确认约定,无 TTY 时自动接受(`--yes`\n // 本就是\"信任本次导入\"的显式声明,无 TTY 环境视为等价于隐式给出该信号)。\n if (!process.stdin.isTTY) {\n io.stdout('(非交互终端,安全相关变更自动接受。可用 --yes 显式声明该意图)\\n')\n return true\n }\n\n const rl = createInterface({ input: process.stdin, output: process.stdout })\n try {\n const answer = (await rl.question('接受以上安全相关变更?[y/N] ')).trim().toLowerCase()\n return answer === 'y' || answer === 'yes'\n } finally {\n rl.close()\n }\n}\n\nasync function ensureBackup(configPath: string, io: ConfigMigrationIO): Promise<string> {\n const timestamp = timestampNow()\n const target = backupPath(configPath, timestamp)\n if (existsSync(configPath)) {\n const content = readFileSync(configPath, 'utf-8')\n mkdirSync(dirname(target), { recursive: true })\n writeFileSync(target, content, 'utf-8')\n } else {\n // 目标配置文件此前不存在(全新机器场景)——备份为空文件占位,restore 时按空文件处理\n // (等价于\"撤销即删除该配置文件\",见 runRestore 对空备份文件的处理)。\n mkdirSync(dirname(target), { recursive: true })\n writeFileSync(target, '', 'utf-8')\n }\n io.stdout(`备份: ${target}\\n`)\n return target\n}\n\nasync function runImport(parsed: ParsedArgs, io: ConfigMigrationIO): Promise<number> {\n const filePath = parsed.positional[0]\n if (!filePath) {\n io.stderr('otto config import <file> [--dry-run] [--scope global|project] [--no-plugins] [--plugins-only] [--yes]\\n')\n return 1\n }\n\n let raw: string\n try {\n raw = readFileSync(resolve(filePath), 'utf-8')\n } catch (err) {\n io.stderr(`无法读取导入文件:${err instanceof Error ? err.message : String(err)}\\n`)\n return 1\n }\n\n let exportFile: ReturnType<typeof parseExportFile>\n try {\n exportFile = parseExportFile(raw)\n } catch (err) {\n if (err instanceof ExportFileVersionError || err instanceof ExportFileParseError) {\n io.stderr(`${err.message}\\n`)\n return 1\n }\n throw err\n }\n\n // RFC-219 重要事项规则 4:scope 判定用确定性规则——默认遵循文件声明的 scope,\n // --scope 可显式覆盖,不做\"检测到歧义弹窗询问\"。\n const scopeOverride = parsed.values.get('scope')\n const targetScope: 'global' | 'project' =\n scopeOverride === 'global' || scopeOverride === 'project' ? scopeOverride : exportFile.scope\n\n const cwd = process.cwd()\n const configPath = targetScope === 'project' ? projectConfigPath(cwd) : globalConfigPath()\n const settings =\n targetScope === 'project'\n ? new SettingsManager({ workspaceDir: cwd })\n : new SettingsManager({ projectConfigPath: globalConfigPath() })\n await settings.load()\n\n const noPlugins = parsed.flags.has('--no-plugins')\n const pluginsOnly = parsed.flags.has('--plugins-only')\n const dryRun = parsed.flags.has('--dry-run')\n const yes = parsed.flags.has('--yes')\n\n // RFC-219 重要事项规则 5:installed_plugins 恒定路由 user 级配置,不受 targetScope 影响。\n const incomingFields = pluginsOnly ? {} : exportFile.fields\n const diffs = diffConfig(settings.config, incomingFields)\n const currentPlugins = settings.get('installed_plugins') ?? []\n const incomingPlugins = noPlugins ? [] : (exportFile.fields.installed_plugins ?? [])\n const pluginDiff = classifyPluginChanges(currentPlugins, incomingPlugins)\n\n const plan: ImportPlan = { diffs, pluginDiff, targetScope }\n\n // RFC-219 重要事项规则 3:零差异快路径。\n if (diffs.length === 0 && pluginDiff.toInstall.length === 0) {\n io.stdout('配置已是最新(与导入文件零差异),无需变更\\n')\n return 0\n }\n\n io.stdout(`${buildImportReport(plan)}\\n`)\n\n if (dryRun) {\n io.stdout('\\n(--dry-run 预览完毕,未执行任何写入)\\n')\n return 0\n }\n\n // RFC-219 D4:唯一合法确认——安全敏感变更批量门。\n let acceptAll = true\n if (hasSecuritySensitiveChanges(diffs, pluginDiff)) {\n const accepted = await confirmSecuritySensitive(io, plan, yes)\n if (!accepted) {\n acceptAll = false\n io.stdout('已跳过安全相关变更,继续导入其余安全字段\\n')\n }\n }\n\n // RFC-219 重要事项规则 6:强制自动备份(不可通过 flag 跳过)。\n await ensureBackup(configPath, io)\n\n const toApply = applyMergeStrategy(diffs, acceptAll ? 'all' : 'safe-only')\n if (Object.keys(toApply).length > 0) {\n await settings.persist(toApply as Partial<Setting>, targetScope)\n }\n\n // installed_plugins 恒定路由 user 级(重要事项规则 5),与 targetScope 无关。\n // 对抗审核 P0 修复(安全门绕过):`acceptAll=false`(用户在安全门拒绝)时,声明\n // HIGH_RISK_CAPABILITIES 的插件必须被跳过——此前实现只对 applyMergeStrategy 应用了\n // 'safe-only',插件安装循环无条件跑全部 toInstall,等价于安全门形同虚设。\n const pluginsToInstall = acceptAll\n ? pluginDiff.toInstall\n : pluginDiff.toInstall.filter((p) => !isHighRiskPlugin(p.capabilities))\n const skippedHighRiskPlugins = acceptAll\n ? []\n : pluginDiff.toInstall.filter((p) => isHighRiskPlugin(p.capabilities))\n\n if (pluginsToInstall.length > 0) {\n const userSettings =\n targetScope === 'global' ? settings : new SettingsManager({ projectConfigPath: globalConfigPath() })\n if (targetScope !== 'global') await userSettings.load()\n\n const installed: string[] = []\n const failed: Array<{ id: string; error: string }> = []\n for (const entry of pluginsToInstall) {\n try {\n const res =\n entry.source === 'npm'\n ? await installPluginFromPackage({ pkg: entry.sourceSpec || entry.id })\n : undefined\n if (!res) {\n failed.push({ id: entry.id, error: `不支持从导入文件自动安装来源 \"${entry.source}\"` })\n continue\n }\n await recordPluginInstalled(userSettings, {\n id: res.id,\n source: 'npm',\n sourceSpec: entry.sourceSpec || entry.id,\n version: res.version,\n scope: 'global',\n capabilities: entry.capabilities,\n })\n installed.push(res.id)\n } catch (err) {\n failed.push({ id: entry.id, error: err instanceof Error ? err.message : String(err) })\n }\n }\n\n if (installed.length > 0) io.stdout(`✓ 已安装: ${installed.join(', ')}\\n`)\n for (const f of failed) {\n io.stdout(`✗ 安装失败: ${f.id} — ${f.error} → otto extension install ${f.id}\\n`)\n }\n }\n if (skippedHighRiskPlugins.length > 0) {\n io.stdout(\n `- 跳过(用户拒绝安全相关变更): ${skippedHighRiskPlugins.map((p) => p.id).join(', ')} ` +\n `→ 可稍后手动安装: otto extension install <name>\\n`,\n )\n }\n\n io.stdout(`\\n撤销: otto config restore\\n`)\n return 0\n}\n\n// ── otto config restore ──\n\nfunction listBackups(configPath: string): string[] {\n const dir = dirname(configPath)\n const prefix = `${OTTO_CONFIG_FILENAME}.bak-`\n if (!existsSync(dir)) return []\n return readdirSync(dir)\n .filter((f) => f.startsWith(prefix))\n .sort()\n .reverse()\n .map((f) => join(dir, f))\n}\n\nasync function runRestore(parsed: ParsedArgs, io: ConfigMigrationIO): Promise<number> {\n const useProject = parsed.flags.has('--project')\n const configPath = useProject ? projectConfigPath(process.cwd()) : globalConfigPath()\n const backups = listBackups(configPath)\n\n if (backups.length === 0) {\n io.stdout('未找到任何备份\\n')\n return 0\n }\n\n if (parsed.flags.has('--list')) {\n io.stdout(`可用备份(最新在前):\\n${backups.map((b) => ` ${b}`).join('\\n')}\\n`)\n return 0\n }\n\n const latest = backups[0]!\n const content = readFileSync(latest, 'utf-8')\n if (content === '') {\n // 空备份 = 撤销前配置文件本不存在(全新机器场景),撤销即删除。\n const { unlinkSync } = await import('node:fs')\n if (existsSync(configPath)) unlinkSync(configPath)\n io.stdout(`已恢复: ${configPath} 不存在(撤销前该文件本不存在)\\n`)\n return 0\n }\n mkdirSync(dirname(configPath), { recursive: true })\n writeFileSync(configPath, content, 'utf-8')\n io.stdout(`已恢复: ${configPath} ← ${latest}\\n`)\n return 0\n}\n\n// ── 入口 ──\n\nexport async function runConfigMigrationCommand(args: string[], io: ConfigMigrationIO): Promise<number> {\n const parsed = parseArgs(args)\n switch (parsed.sub) {\n case 'export':\n return runExport(parsed, io)\n case 'import':\n return runImport(parsed, io)\n case 'restore':\n return runRestore(parsed, io)\n default:\n io.stdout(\n 'otto config <命令>\\n' +\n ' export [--project] [-o <path>] 导出配置(默认用户级)\\n' +\n ' import <file> [--dry-run] [--scope global|project]\\n' +\n ' [--no-plugins] [--plugins-only] [--yes] 导入配置\\n' +\n ' restore [--list] [--project] 撤销最近一次导入\\n',\n )\n return parsed.sub ? 1 : 0\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{n as e,t}from"./create-host-app-BXQ8bU-B.js";export{t as createHostApp,e as hostSelfDescribingOptions};
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
2
  function e(e){return new Promise(t=>{if(e){if(e.aborted){t();return}e.addEventListener(`abort`,()=>t(),{once:!0});return}let n=()=>{process.off(`SIGINT`,n),process.off(`SIGTERM`,n),t()};process.on(`SIGINT`,n),process.on(`SIGTERM`,n)})}export{e as t};
3
- //# sourceMappingURL=daemon-base-YOWgYYN7.js.map
3
+ //# sourceMappingURL=daemon-base-Bc9p51Um.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"daemon-base-YOWgYYN7.js","names":[],"sources":["../src/commands/daemon-base.ts"],"sourcesContent":["/**\n * daemon-base.ts — 常驻进程启动器共享基座。\n *\n * `serve`/`persistenced`/`remote-persistence-server` 三个子命令各自启动一个\n * 常驻服务进程,生命周期形状相同(启动 → 打印监听地址 → 等待信号 → 优雅关闭),\n * 唯一差异是启动的具体服务。`waitForShutdown` 此前在三个文件里逐字重复(终局架构\n * review 发现),此处收敛为单一真源。\n */\n\n/**\n * 等待进程收到关闭信号(外部 AbortSignal,或退化到监听 SIGINT/SIGTERM)。\n *\n * `signal` 由测试注入(模拟主动关闭);生产路径缺省走 SIGINT/SIGTERM,\n * 监听器在触发后自行摘除,不留悬挂 listener。\n */\nexport function waitForShutdown(signal?: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n if (signal) {\n if (signal.aborted) {\n resolve()\n return\n }\n signal.addEventListener('abort', () => resolve(), { once: true })\n return\n }\n const onSignal = () => {\n process.off('SIGINT', onSignal)\n process.off('SIGTERM', onSignal)\n resolve()\n }\n process.on('SIGINT', onSignal)\n process.on('SIGTERM', onSignal)\n })\n}\n"],"mappings":";AAeA,SAAgB,EAAgB,EAAqC,CACnE,OAAO,IAAI,QAAS,GAAY,CAC9B,GAAI,EAAQ,CACV,GAAI,EAAO,QAAS,CAClB,GAAS,CACT,OAEF,EAAO,iBAAiB,YAAe,GAAS,CAAE,CAAE,KAAM,GAAM,CAAC,CACjE,OAEF,IAAM,MAAiB,CACrB,QAAQ,IAAI,SAAU,EAAS,CAC/B,QAAQ,IAAI,UAAW,EAAS,CAChC,GAAS,EAEX,QAAQ,GAAG,SAAU,EAAS,CAC9B,QAAQ,GAAG,UAAW,EAAS,EAC/B"}
1
+ {"version":3,"file":"daemon-base-Bc9p51Um.js","names":[],"sources":["../src/commands/daemon-base.ts"],"sourcesContent":["/**\n * daemon-base.ts — 常驻进程启动器共享基座。\n *\n * `serve`/`persistenced`/`remote-persistence-server` 三个子命令各自启动一个\n * 常驻服务进程,生命周期形状相同(启动 → 打印监听地址 → 等待信号 → 优雅关闭),\n * 唯一差异是启动的具体服务。`waitForShutdown` 此前在三个文件里逐字重复(终局架构\n * review 发现),此处收敛为单一真源。\n */\n\n/**\n * 等待进程收到关闭信号(外部 AbortSignal,或退化到监听 SIGINT/SIGTERM)。\n *\n * `signal` 由测试注入(模拟主动关闭);生产路径缺省走 SIGINT/SIGTERM,\n * 监听器在触发后自行摘除,不留悬挂 listener。\n */\nexport function waitForShutdown(signal?: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n if (signal) {\n if (signal.aborted) {\n resolve()\n return\n }\n signal.addEventListener('abort', () => resolve(), { once: true })\n return\n }\n const onSignal = () => {\n process.off('SIGINT', onSignal)\n process.off('SIGTERM', onSignal)\n resolve()\n }\n process.on('SIGINT', onSignal)\n process.on('SIGTERM', onSignal)\n })\n}\n"],"mappings":";AAeA,SAAgB,EAAgB,EAAqC,CACnE,OAAO,IAAI,QAAS,GAAY,CAC9B,GAAI,EAAQ,CACV,GAAI,EAAO,QAAS,CAClB,GAAS,CACT,OAEF,EAAO,iBAAiB,YAAe,GAAS,CAAE,CAAE,KAAM,GAAM,CAAC,CACjE,OAEF,IAAM,MAAiB,CACrB,QAAQ,IAAI,SAAU,EAAS,CAC/B,QAAQ,IAAI,UAAW,EAAS,CAChC,GAAS,EAEX,QAAQ,GAAG,SAAU,EAAS,CAC9B,QAAQ,GAAG,UAAW,EAAS,EAC/B"}
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import{arch as e,cpus as t,freemem as n,homedir as r,platform as i,release as a,totalmem as o}from"node:os";import{join as s}from"node:path";import{accessSync as c,existsSync as l,statSync as u}from"node:fs";import{SESSION_MAX_HISTORY_MESSAGES as d,SESSION_MAX_PER_SESSION_BYTES as f,SESSION_MIN_PER_SESSION_BYTES as p,SESSION_RESUME_WINDOW_MESSAGES as m,SESSION_TOTAL_RESIDENCY_BUDGET as h}from"@x-otto/session-contract";const g=s(r(),`.otto`);async function _(e,t){let n=t.stdout,r=t.stderr,i=e.find(e=>!e.startsWith(`-`));if(i===`ps`||i===`attach`||i===`heap`){let n=e.filter(e=>e!==i);switch(i){case`ps`:{let{runDoctorPs:e}=await import(`./doctor-ps-OL3FGQYu.js`);return e(n,t)}case`attach`:{let{runDoctorAttach:e}=await import(`./doctor-attach-_zeIJewx.js`);return e(n,t)}case`heap`:{let{runDoctorHeap:e}=await import(`./doctor-heap-Rr2nNvcw.js`);return e(n,t)}}}if(i!==void 0)return r(`otto doctor: unknown subcommand "${i}" (available: ps, attach, heap)\n`),1;if(e.includes(`--help`)||e.includes(`-h`))return k(n),0;let a=e.includes(`--json`)?`json`:e.includes(`--summary`)?`summary`:`detailed`;try{let e=await v();return a===`json`?n(JSON.stringify(e,null,2)+`
3
- `):a===`summary`?D(n,e):O(n,e),0}catch(e){return r(`otto doctor: ${e instanceof Error?e.message:String(e)}\n`),1}}async function v(){let e=await y(),t=await b(),n=await C(),r=await w(),{plugins:i,providerPluginDiscovered:a}=await x();return{environment:e,configuration:t,state:n,connectivity:r,plugins:i,ftue:await S(a)}}async function y(){return{runtime:`Node.js ${process.version}`,installPath:process.execPath,shell:process.env.SHELL??`(unknown)`,terminal:process.env.TERM??`(unknown)`,cpu:`${t().length}×${e()}`,memory:{total:o(),free:n()},platform:i(),release:a()}}async function b(){let e=process.env.ANTHROPIC_API_KEY?`api-key (ANTHROPIC_API_KEY)`:process.env.OTTO_API_KEY?`api-key (OTTO_API_KEY)`:process.env.AWS_ACCESS_KEY_ID?`aws-bedrock (env keys)`:`not configured`;return{workspaceDir:process.cwd(),storageDir:g,hasRipgrep:await T(`rg`),hasGit:await T(`git`),authMode:e,residencyBudget:{totalBytes:h,minPerSessionBytes:p,maxPerSessionBytes:f,historyMessages:d,resumeWindowMessages:m}}}async function x(){try{let{discoverPluginsWithBundled:e}=await import(`./discover-with-bundled-B5d4q___.js`),{hostSelfDescribingOptions:t}=await import(`./create-host-app-DXII2WTM.js`),{checkPluginAssembly:n,formatAssemblyGap:i}=await import(`./plugin-assembly-check-C2TephMb.js`),a=e({cwd:process.cwd(),homedir:r()}),o=t().pluginBundledDirs??[],s=a.some(e=>e.manifest.capabilities?.includes(`provider`)),c=a.filter(e=>e.scope===`bundled`).length>0&&o.length===0,l=n({discovered:a.map(e=>({id:e.id,scope:e.scope})),active:c?a.filter(e=>e.scope!==`bundled`).map(e=>({id:e.id,scope:e.scope})):a.map(e=>({id:e.id,scope:e.scope}))});return{plugins:{discovered:a.length,gaps:i(l),missing:l.missingFromRuntime},providerPluginDiscovered:s}}catch{return{plugins:void 0,providerPluginDiscovered:!1}}}async function S(e){try{let{createDefaultAuthStore:t}=await import(`@x-otto/ai`),{deriveDoctorFtueGuidance:n}=await import(`./ftue-doctor-recovery-DeAf8jVo.js`),{SettingsManager:r}=await import(`@x-otto/setting`),{OTTO_CONFIG_FILENAME:i}=await import(`@x-otto/env`),a=t({watchForExternalChanges:!1});await a.ensureInitialized();let o=a.listProviders().length>0,c=new r({projectConfigPath:s(g,i)});return await c.load(),n({providerPluginDiscovered:e,hasAnyCredential:o,firstPromptSubmitted:typeof c.get(`first_prompt_submitted_at`)==`number`})}catch{return}}async function C(){let e=s(g,`storage.db`);if(!l(e))return{dbPath:e,dbExists:!1};try{let{SqlitePersistence:t}=await import(`@x-otto/persistence`),n=new t({dbPath:e,namespace:`doctor`,wal:!1});try{let t=n.getInfo();return{dbPath:e,dbExists:!0,dbSize:E(e),...t}}finally{n.close()}}catch{return{dbPath:e,dbExists:!0,integrity:`error (unable to open)`}}}async function w(){let e=`api.anthropic.com`,t=!1,n,r=Date.now();try{let i=new AbortController,a=setTimeout(()=>i.abort(),5e3);await fetch(`https://${e}`,{method:`HEAD`,signal:i.signal}),clearTimeout(a),t=!0,n=Date.now()-r}catch{t=!1}return{providerHost:e,providerReachable:t,providerLatency:n,storageHostConfigured:!1}}async function T(e){try{return c(s(`/usr/local/bin`,e),1),!0}catch{try{return c(s(`/usr/bin`,e),1),!0}catch{return!1}}}function E(e){try{return u(e).size}catch{return 0}}function D(e,t){let{environment:n,configuration:r,state:i,connectivity:a}=t,o=i?.dbExists&&i.integrity===`ok`?`✅`:`❌`,s=a.providerReachable?`✅`:`❌`,c=t.plugins&&t.plugins.gaps.length>0?` ⚠ 插件装配缺口 ${t.plugins.missing.length} 项(otto doctor 查看详情)`:``,l=t.ftue&&t.ftue.status!==`looks-configured`?` ⚠ ${t.ftue.summary} → ${t.ftue.recoveryAction}`:``;e([`otto doctor — ${n.runtime} | ${r.storageDir} | DB:${o} ${s}`,i?` ${i.snapshots??`?`} snapshots, ${i.changeLogEntries??`?`} change_log`:``,c,l].filter(Boolean).join(`
3
+ `):a===`summary`?D(n,e):O(n,e),0}catch(e){return r(`otto doctor: ${e instanceof Error?e.message:String(e)}\n`),1}}async function v(){let e=await y(),t=await b(),n=await C(),r=await w(),{plugins:i,providerPluginDiscovered:a}=await x();return{environment:e,configuration:t,state:n,connectivity:r,plugins:i,ftue:await S(a)}}async function y(){return{runtime:`Node.js ${process.version}`,installPath:process.execPath,shell:process.env.SHELL??`(unknown)`,terminal:process.env.TERM??`(unknown)`,cpu:`${t().length}×${e()}`,memory:{total:o(),free:n()},platform:i(),release:a()}}async function b(){let e=process.env.ANTHROPIC_API_KEY?`api-key (ANTHROPIC_API_KEY)`:process.env.OTTO_API_KEY?`api-key (OTTO_API_KEY)`:process.env.AWS_ACCESS_KEY_ID?`aws-bedrock (env keys)`:`not configured`;return{workspaceDir:process.cwd(),storageDir:g,hasRipgrep:await T(`rg`),hasGit:await T(`git`),authMode:e,residencyBudget:{totalBytes:h,minPerSessionBytes:p,maxPerSessionBytes:f,historyMessages:d,resumeWindowMessages:m}}}async function x(){try{let{discoverPluginsWithBundled:e}=await import(`./discover-with-bundled-B5d4q___.js`),{hostSelfDescribingOptions:t}=await import(`./create-host-app-DG58-vRA.js`),{checkPluginAssembly:n,formatAssemblyGap:i}=await import(`./plugin-assembly-check-Ov17ra7G.js`),a=e({cwd:process.cwd(),homedir:r()}),o=t().pluginBundledDirs??[],s=a.some(e=>e.manifest.capabilities?.includes(`provider`)),c=a.filter(e=>e.scope===`bundled`).length>0&&o.length===0,l=n({discovered:a.map(e=>({id:e.id,scope:e.scope})),active:c?a.filter(e=>e.scope!==`bundled`).map(e=>({id:e.id,scope:e.scope})):a.map(e=>({id:e.id,scope:e.scope}))});return{plugins:{discovered:a.length,gaps:i(l),missing:l.missingFromRuntime},providerPluginDiscovered:s}}catch{return{plugins:void 0,providerPluginDiscovered:!1}}}async function S(e){try{let{createDefaultAuthStore:t}=await import(`@x-otto/ai`),{deriveDoctorFtueGuidance:n}=await import(`./ftue-doctor-recovery-DeAf8jVo.js`),{SettingsManager:r}=await import(`@x-otto/setting`),{OTTO_CONFIG_FILENAME:i}=await import(`@x-otto/env`),a=t({watchForExternalChanges:!1});await a.ensureInitialized();let o=a.listProviders().length>0,c=new r({projectConfigPath:s(g,i)});return await c.load(),n({providerPluginDiscovered:e,hasAnyCredential:o,firstPromptSubmitted:typeof c.get(`first_prompt_submitted_at`)==`number`})}catch{return}}async function C(){let e=s(g,`storage.db`);if(!l(e))return{dbPath:e,dbExists:!1};try{let{SqlitePersistence:t}=await import(`@x-otto/persistence`),n=new t({dbPath:e,namespace:`doctor`,wal:!1});try{let t=n.getInfo();return{dbPath:e,dbExists:!0,dbSize:E(e),...t}}finally{n.close()}}catch{return{dbPath:e,dbExists:!0,integrity:`error (unable to open)`}}}async function w(){let e=`api.anthropic.com`,t=!1,n,r=Date.now();try{let i=new AbortController,a=setTimeout(()=>i.abort(),5e3);await fetch(`https://${e}`,{method:`HEAD`,signal:i.signal}),clearTimeout(a),t=!0,n=Date.now()-r}catch{t=!1}return{providerHost:e,providerReachable:t,providerLatency:n,storageHostConfigured:!1}}async function T(e){try{return c(s(`/usr/local/bin`,e),1),!0}catch{try{return c(s(`/usr/bin`,e),1),!0}catch{return!1}}}function E(e){try{return u(e).size}catch{return 0}}function D(e,t){let{environment:n,configuration:r,state:i,connectivity:a}=t,o=i?.dbExists&&i.integrity===`ok`?`✅`:`❌`,s=a.providerReachable?`✅`:`❌`,c=t.plugins&&t.plugins.gaps.length>0?` ⚠ 插件装配缺口 ${t.plugins.missing.length} 项(otto doctor 查看详情)`:``,l=t.ftue&&t.ftue.status!==`looks-configured`?` ⚠ ${t.ftue.summary} → ${t.ftue.recoveryAction}`:``;e([`otto doctor — ${n.runtime} | ${r.storageDir} | DB:${o} ${s}`,i?` ${i.snapshots??`?`} snapshots, ${i.changeLogEntries??`?`} change_log`:``,c,l].filter(Boolean).join(`
4
4
  `)+`
5
5
  `)}function O(e,t){let{environment:n,configuration:r,state:i,connectivity:a}=t;if(e(`
6
6
  Environment
@@ -33,4 +33,4 @@ First-time setup
33
33
  otto doctor --summary
34
34
  otto doctor --json
35
35
  `)}function A(e){if(e===0)return`0 B`;let t=[`B`,`KB`,`MB`,`GB`,`TB`],n=Math.floor(Math.log(e)/Math.log(1024));return`${(e/1024**n).toFixed(1)} ${t[n]}`}export{_ as runDoctorCommand};
36
- //# sourceMappingURL=doctor-CuNvDmyA.js.map
36
+ //# sourceMappingURL=doctor-lT2QhG2q.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"doctor-CuNvDmyA.js","names":[],"sources":["../src/commands/doctor.ts"],"sourcesContent":["import { homedir, release, platform, arch, cpus, totalmem, freemem } from 'node:os'\nimport { existsSync, statSync, accessSync } from 'node:fs'\nimport { join } from 'node:path'\nimport {\n SESSION_TOTAL_RESIDENCY_BUDGET,\n SESSION_MIN_PER_SESSION_BYTES,\n SESSION_MAX_PER_SESSION_BYTES,\n SESSION_MAX_HISTORY_MESSAGES,\n SESSION_RESUME_WINDOW_MESSAGES,\n} from '@x-otto/session-contract'\nexport interface DoctorDeps {\n stdout: (s: string) => void\n stderr: (s: string) => void\n}\n\nexport interface DoctorReport {\n environment: EnvironmentSection\n configuration: ConfigurationSection\n state?: StateSection\n connectivity: ConnectivitySection\n /** RFC-364 M2:插件装配一致性——发现得到却未装载的缺口。 */\n plugins?: PluginsSection\n /** RFC-326 M6-01:FTUE 环境自检(廉价事实推导,非权威 readiness 判定,见模块头注释)。 */\n ftue?: import('./ftue/ftue-doctor-recovery').DoctorFtueGuidance\n}\n\ninterface PluginsSection {\n discovered: number\n /** 装配缺口的可执行诊断行(空数组 = 无缺口)。 */\n gaps: string[]\n /** 缺口涉及的插件 id(供 --json 消费方精确处理)。 */\n missing: Array<{ id: string; scope?: string }>\n}\n\ninterface EnvironmentSection {\n runtime: string\n installPath: string\n shell: string\n terminal: string\n cpu: string\n memory: { total: number; free: number }\n platform: string\n release: string\n}\n\ninterface ConfigurationSection {\n workspaceDir: string\n storageDir: string\n hasRipgrep: boolean\n hasGit: boolean\n authMode: string\n /** RFC-323:驻留预算配置(只读展示,使基线估算偏差可被校准) */\n residencyBudget?: {\n totalBytes: number\n minPerSessionBytes: number\n maxPerSessionBytes: number\n historyMessages: number\n resumeWindowMessages: number\n }\n}\n\ninterface StateSection {\n dbPath: string\n dbExists: boolean\n dbSize?: number\n walEnabled?: boolean\n snapshots?: number\n changeLogEntries?: number\n sessions?: number\n integrity?: string\n}\n\ninterface ConnectivitySection {\n providerHost: string\n providerReachable: boolean\n providerLatency?: number\n storageHostConfigured: boolean\n}\n\nconst OTTO_DIR = join(homedir(), '.otto')\n\nexport async function runDoctorCommand(\n args: string[],\n deps: DoctorDeps,\n): Promise<number> {\n const stdout = deps.stdout\n const stderr = deps.stderr\n\n // RFC-200 D1.0:子命令路由——首个位置参数(非 -- 开头)分发到诊断子命令;\n // 无子命令时走既有健康检查(行为零变化)。\n const sub = args.find((a) => !a.startsWith('-'))\n if (sub === 'ps' || sub === 'attach' || sub === 'heap') {\n const rest = args.filter((a) => a !== sub)\n switch (sub) {\n case 'ps': {\n const { runDoctorPs } = await import('./handlers/doctor-ps')\n return runDoctorPs(rest, deps)\n }\n case 'attach': {\n const { runDoctorAttach } = await import('./handlers/doctor-attach')\n return runDoctorAttach(rest, deps)\n }\n case 'heap': {\n const { runDoctorHeap } = await import('./handlers/doctor-heap')\n return runDoctorHeap(rest, deps)\n }\n }\n }\n if (sub !== undefined) {\n stderr(`otto doctor: unknown subcommand \"${sub}\" (available: ps, attach, heap)\\n`)\n return 1\n }\n\n if (args.includes('--help') || args.includes('-h')) {\n printHelp(stdout)\n return 0\n }\n\n const outputFormat = args.includes('--json') ? 'json' : args.includes('--summary') ? 'summary' : 'detailed'\n\n try {\n const report = await collectReport()\n\n if (outputFormat === 'json') {\n stdout(JSON.stringify(report, null, 2) + '\\n')\n } else if (outputFormat === 'summary') {\n printSummary(stdout, report)\n } else {\n printDetailed(stdout, report)\n }\n\n return 0\n } catch (error) {\n stderr(`otto doctor: ${error instanceof Error ? error.message : String(error)}\\n`)\n return 1\n }\n}\n\n/** 收集结构化诊断报告(TUI /doctor 折叠档复用;不打印)。 */\nexport async function collectReport(): Promise<DoctorReport> {\n const env = await collectEnvironment()\n const config = await collectConfiguration()\n const state = await collectState()\n const connectivity = await collectConnectivity()\n const { plugins, providerPluginDiscovered } = await collectPluginsAndProviderFlag()\n const ftue = await collectFtueGuidance(providerPluginDiscovered)\n\n return { environment: env, configuration: config, state, connectivity, plugins, ftue }\n}\n\nasync function collectEnvironment(): Promise<EnvironmentSection> {\n return {\n runtime: `Node.js ${process.version}`,\n installPath: process.execPath,\n shell: process.env.SHELL ?? '(unknown)',\n terminal: process.env.TERM ?? '(unknown)',\n cpu: `${cpus().length}×${arch()}`,\n memory: { total: totalmem(), free: freemem() },\n platform: platform(),\n release: release(),\n }\n}\n\nasync function collectConfiguration(): Promise<ConfigurationSection> {\n const authMode = process.env.ANTHROPIC_API_KEY\n ? 'api-key (ANTHROPIC_API_KEY)'\n : process.env.OTTO_API_KEY\n ? 'api-key (OTTO_API_KEY)'\n : process.env.AWS_ACCESS_KEY_ID\n ? 'aws-bedrock (env keys)'\n : 'not configured'\n\n return {\n workspaceDir: process.cwd(),\n storageDir: OTTO_DIR,\n hasRipgrep: await checkBinary('rg'),\n hasGit: await checkBinary('git'),\n authMode,\n // RFC-323 M3:暴露驻留预算配置,使\"基线 1500MB 估算偏差\"(R-2)可被真机数据校准。\n // 纯静态读常量,不接运行时状态——运行时有效分配值取决于活跃会话数 N,在 ps/doctor-ps 展示。\n residencyBudget: {\n totalBytes: SESSION_TOTAL_RESIDENCY_BUDGET,\n minPerSessionBytes: SESSION_MIN_PER_SESSION_BYTES,\n maxPerSessionBytes: SESSION_MAX_PER_SESSION_BYTES,\n historyMessages: SESSION_MAX_HISTORY_MESSAGES,\n resumeWindowMessages: SESSION_RESUME_WINDOW_MESSAGES,\n },\n }\n}\n\n/**\n * RFC-364 M2:插件装配一致性自检。\n *\n * 判据是「cli 侧完整发现(含 bundled 层)」与「App 实际装载」的差集。但 doctor 是\n * 轻量诊断命令(RFC-150 D3 明确要求不承担重依赖),构造真实 App 代价过高——\n * 故此处只做**发现侧**的自检:确认 bundled 目录可解析且其中的扩展能被发现。\n *\n * 真正的「发现 vs 装载」全量比对由运行时侧承担(App 启动路径),本处提供的是\n * 用户可主动运行的诊断入口:当用户报告\"插件不见了\"时,`otto doctor` 能立刻回答\n * \"otto 这一侧到底看见了哪些\"。\n */\n/**\n * RFC-326 M6-01:与 `providerPluginDiscovered` 标志合并计算——`discoverPluginsWithBundled`\n * 是本函数唯一的重量级调用,`collectFtueGuidance` 需要同一份发现结果的 `provider`\n * capability 过滤,若各自重新扫描一次会重复触发文件系统遍历。`providerPluginDiscovered`\n * 在扫描失败时缺省 `false`(诊断失败不阻断其余部分,与既有 fail-soft 语义一致;\n * FTUE 分支据此会诚实报告\"未发现 provider 插件\"而非静默跳过整个 ftue 字段)。\n */\nasync function collectPluginsAndProviderFlag(): Promise<{\n plugins: PluginsSection | undefined\n providerPluginDiscovered: boolean\n}> {\n try {\n const { discoverPluginsWithBundled } = await import('../discover-with-bundled')\n const { hostSelfDescribingOptions } = await import('../create-host-app')\n const { checkPluginAssembly, formatAssemblyGap } = await import('../plugin-assembly-check')\n\n const discovered = discoverPluginsWithBundled({ cwd: process.cwd(), homedir: homedir() })\n const bundledDirs = hostSelfDescribingOptions().pluginBundledDirs ?? []\n const providerPluginDiscovered = discovered.some((p) => p.manifest.capabilities?.includes('provider'))\n\n // 宿主自描述参数解析不出目录,但发现层却报告了 bundled 作用域插件(或反之)——\n // 这正是 M1 修的那类装配漂移,在此处显式暴露。\n const bundledSeen = discovered.filter((p) => p.scope === 'bundled')\n const assemblyBroken = bundledSeen.length > 0 && bundledDirs.length === 0\n\n const result = checkPluginAssembly({\n discovered: discovered.map((p) => ({ id: p.id, scope: p.scope })),\n // 发现侧自检:把「宿主参数解析失败」建模为\"全部 bundled 未装载\"\n active: assemblyBroken\n ? discovered.filter((p) => p.scope !== 'bundled').map((p) => ({ id: p.id, scope: p.scope }))\n : discovered.map((p) => ({ id: p.id, scope: p.scope })),\n })\n\n return {\n plugins: {\n discovered: discovered.length,\n gaps: formatAssemblyGap(result),\n missing: result.missingFromRuntime,\n },\n providerPluginDiscovered,\n }\n } catch {\n // 诊断失败不阻断 doctor 其余部分(与既有 collectState 的 fail-soft 一致)。\n return { plugins: undefined, providerPluginDiscovered: false }\n }\n}\n\n/**\n * RFC-326 M6-01:FTUE 环境自检——三个廉价事实推导诊断状态(见\n * `ftue-doctor-recovery.ts` 头注释的判据边界)。`AuthStore` 构造零 App 依赖\n * (默认读全局 `AUTH_PATH`),`listProviders()` 只读本地文件/env,不发网络请求。\n */\nasync function collectFtueGuidance(\n providerPluginDiscovered: boolean,\n): Promise<import('./ftue/ftue-doctor-recovery').DoctorFtueGuidance | undefined> {\n try {\n const { createDefaultAuthStore } = await import('@x-otto/ai')\n const { deriveDoctorFtueGuidance } = await import('./ftue/ftue-doctor-recovery')\n const { SettingsManager } = await import('@x-otto/setting')\n const { OTTO_CONFIG_FILENAME } = await import('@x-otto/env')\n\n // watchForExternalChanges:false —— doctor 是一次性命令,跑完即退出,不需要\n // fs.watch(同 auth-command.ts 的既有约定,见其头注释)。\n const authStore = createDefaultAuthStore({ watchForExternalChanges: false })\n // listProviders() 读内存缓存(this.cache),不像 getAuthSource/hasCredential 那样\n // 自带 ensureInitialized() 前置——首次调用前必须显式触发一次磁盘加载,否则读到空态。\n await authStore.ensureInitialized()\n const hasAnyCredential = authStore.listProviders().length > 0\n\n const settings = new SettingsManager({ projectConfigPath: join(OTTO_DIR, OTTO_CONFIG_FILENAME) })\n await settings.load()\n const firstPromptSubmitted = typeof settings.get('first_prompt_submitted_at') === 'number'\n\n return deriveDoctorFtueGuidance({ providerPluginDiscovered, hasAnyCredential, firstPromptSubmitted })\n } catch {\n // 诊断失败不阻断 doctor 其余部分。\n return undefined\n }\n}\n\nasync function collectState(): Promise<StateSection | undefined> {\n const dbPath = join(OTTO_DIR, 'storage.db')\n if (!existsSync(dbPath)) {\n return { dbPath, dbExists: false }\n }\n\n try {\n const { SqlitePersistence } = await import('@x-otto/persistence')\n const store = new SqlitePersistence({ dbPath, namespace: 'doctor', wal: false })\n try {\n const info = store.getInfo() as {\n walEnabled: boolean\n snapshots: number\n changeLogEntries: number\n sessions: number\n integrity: string\n }\n\n return {\n dbPath,\n dbExists: true,\n dbSize: getFileSizeSync(dbPath),\n ...info,\n }\n } finally {\n store.close()\n }\n } catch {\n return { dbPath, dbExists: true, integrity: 'error (unable to open)' }\n }\n}\n\nasync function collectConnectivity(): Promise<ConnectivitySection> {\n let providerHost = 'api.anthropic.com'\n let providerReachable = false\n let providerLatency: number | undefined\n\n const start = Date.now()\n try {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), 5000)\n await fetch(`https://${providerHost}`, {\n method: 'HEAD',\n signal: controller.signal,\n })\n clearTimeout(timeout)\n providerReachable = true\n providerLatency = Date.now() - start\n } catch {\n providerReachable = false\n }\n\n return {\n providerHost,\n providerReachable,\n providerLatency,\n storageHostConfigured: false,\n }\n}\n\nasync function checkBinary(name: string): Promise<boolean> {\n try {\n accessSync(join('/usr/local/bin', name), 1)\n return true\n } catch {\n try {\n accessSync(join('/usr/bin', name), 1)\n return true\n } catch {\n return false\n }\n }\n}\n\nfunction getFileSizeSync(path: string): number {\n try {\n return statSync(path).size\n } catch {\n return 0\n }\n}\n\nfunction printSummary(stdout: (s: string) => void, report: DoctorReport): void {\n const { environment: env, configuration: config, state, connectivity: conn } = report\n const dbOk = state?.dbExists && state.integrity === 'ok' ? '✅' : '❌'\n const netOk = conn.providerReachable ? '✅' : '❌'\n const pluginGap = report.plugins && report.plugins.gaps.length > 0\n ? ` ⚠ 插件装配缺口 ${report.plugins.missing.length} 项(otto doctor 查看详情)`\n : ''\n // RFC-326 M6-01:只在未配置(非 looks-configured)时提示——已配置用户不需要看\n // 这行噪音,未配置用户能立刻看到下一步命令,不必翻到 detailed 输出。\n const ftueHint = report.ftue && report.ftue.status !== 'looks-configured'\n ? ` ⚠ ${report.ftue.summary} → ${report.ftue.recoveryAction}`\n : ''\n stdout([\n `otto doctor — ${env.runtime} | ${config.storageDir} | DB:${dbOk} ${netOk}`,\n state ? ` ${state.snapshots ?? '?'} snapshots, ${state.changeLogEntries ?? '?'} change_log` : '',\n pluginGap,\n ftueHint,\n ].filter(Boolean).join('\\n') + '\\n')\n}\n\nfunction printDetailed(stdout: (s: string) => void, report: DoctorReport): void {\n const { environment: env, configuration: config, state, connectivity: conn } = report\n\n stdout('\\nEnvironment\\n')\n stdout(` Runtime: ${env.runtime}\\n`)\n stdout(` Install: ${env.installPath}\\n`)\n stdout(` Shell: ${env.shell}\\n`)\n stdout(` Terminal: ${env.terminal} (${env.release})\\n`)\n stdout(` CPU: ${env.cpu}\\n`)\n stdout(` Memory: ${formatBytes(env.memory.free)} / ${formatBytes(env.memory.total)} free\\n`)\n if (config.residencyBudget) {\n const rb = config.residencyBudget\n stdout(` Residency: budget=${formatBytes(rb.totalBytes)} per-session=${formatBytes(rb.minPerSessionBytes)}~${formatBytes(rb.maxPerSessionBytes)} history=${rb.historyMessages} resume=${rb.resumeWindowMessages}\\n`)\n }\n\n stdout('\\nConfiguration\\n')\n stdout(` Workspace: ${config.workspaceDir}\\n`)\n stdout(` Storage: ${config.storageDir} ${existsSync(config.storageDir) ? '✅' : '❌'}\\n`)\n stdout(` ripgrep: ${config.hasRipgrep ? '✅' : '❌'}\\n`)\n stdout(` git: ${config.hasGit ? '✅' : '❌'}\\n`)\n stdout(` Auth: ${config.authMode}\\n`)\n\n // RFC-364 M2:插件装配一致性——有缺口才打印(无缺口时不制造噪音)。\n if (report.plugins) {\n stdout('\\nPlugins\\n')\n stdout(` Discovered: ${report.plugins.discovered}\\n`)\n if (report.plugins.gaps.length > 0) {\n stdout(' Assembly: ❌ 检测到装配缺口\\n')\n for (const line of report.plugins.gaps) stdout(` ${line}\\n`)\n } else {\n stdout(' Assembly: ✅\\n')\n }\n }\n\n if (state) {\n stdout('\\nState\\n')\n stdout(` SQLite DB: ${state.dbPath}\\n`)\n if (state.dbExists) {\n stdout(` size: ${formatBytes(state.dbSize ?? 0)}\\n`)\n stdout(` integrity: ${state.integrity === 'ok' ? 'ok ✅' : state.integrity ?? 'N/A'}\\n`)\n if (state.snapshots !== undefined) stdout(` snapshots: ${state.snapshots}\\n`)\n if (state.changeLogEntries !== undefined) stdout(` change_log: ${state.changeLogEntries} entries\\n`)\n if (state.sessions !== undefined) stdout(` sessions: ${state.sessions}\\n`)\n if (state.walEnabled !== undefined) stdout(` WAL: ${state.walEnabled ? 'enabled' : 'disabled'}\\n`)\n } else {\n stdout(` (no storage.db found — start otto to create one)\\n`)\n }\n }\n\n stdout('\\nConnectivity\\n')\n stdout(` ${conn.providerHost}: ${conn.providerReachable ? `reachable (${conn.providerLatency}ms) ✅` : 'unreachable ❌'}\\n`)\n stdout(` storage host: ${conn.storageHostConfigured ? 'configured' : 'not configured (local only)'}\\n`)\n\n // RFC-326 M6-01:FTUE 环境自检——廉价事实推导,非权威 readiness 判定\n // (见 ftue-doctor-recovery.ts 头注释;权威判定只在 TUI 运行时的 FtueController)。\n if (report.ftue) {\n const icon = report.ftue.status === 'looks-configured' ? '✅' : '⚠'\n stdout('\\nFirst-time setup\\n')\n stdout(` ${icon} ${report.ftue.summary}\\n`)\n stdout(` → ${report.ftue.recoveryAction}\\n`)\n }\n\n stdout('\\n')\n}\n\nfunction printHelp(stdout: (s: string) => void): void {\n stdout(`otto doctor — 环境健康诊断\n\n用法:\n otto doctor [--summary|--json]\n\n选项:\n --summary 紧凑输出(一行摘要)\n --json 结构化 JSON(redacted)\n --help, -h 帮助信息\n\n示例:\n otto doctor\n otto doctor --summary\n otto doctor --json\n`)\n}\n\nfunction formatBytes(bytes: number): string {\n if (bytes === 0) return '0 B'\n const units = ['B', 'KB', 'MB', 'GB', 'TB']\n const i = Math.floor(Math.log(bytes) / Math.log(1024))\n return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${units[i]}`\n}\n"],"mappings":";saA+EA,MAAM,EAAW,EAAK,GAAS,CAAE,QAAQ,CAEzC,eAAsB,EACpB,EACA,EACiB,CACjB,IAAM,EAAS,EAAK,OACd,EAAS,EAAK,OAId,EAAM,EAAK,KAAM,GAAM,CAAC,EAAE,WAAW,IAAI,CAAC,CAChD,GAAI,IAAQ,MAAQ,IAAQ,UAAY,IAAQ,OAAQ,CACtD,IAAM,EAAO,EAAK,OAAQ,GAAM,IAAM,EAAI,CAC1C,OAAQ,EAAR,CACE,IAAK,KAAM,CACT,GAAM,CAAE,eAAgB,MAAM,OAAO,2BACrC,OAAO,EAAY,EAAM,EAAK,CAEhC,IAAK,SAAU,CACb,GAAM,CAAE,mBAAoB,MAAM,OAAO,+BACzC,OAAO,EAAgB,EAAM,EAAK,CAEpC,IAAK,OAAQ,CACX,GAAM,CAAE,iBAAkB,MAAM,OAAO,6BACvC,OAAO,EAAc,EAAM,EAAK,GAItC,GAAI,IAAQ,IAAA,GAEV,OADA,EAAO,oCAAoC,EAAI,mCAAmC,CAC3E,EAGT,GAAI,EAAK,SAAS,SAAS,EAAI,EAAK,SAAS,KAAK,CAEhD,OADA,EAAU,EAAO,CACV,EAGT,IAAM,EAAe,EAAK,SAAS,SAAS,CAAG,OAAS,EAAK,SAAS,YAAY,CAAG,UAAY,WAEjG,GAAI,CACF,IAAM,EAAS,MAAM,GAAe,CAUpC,OARI,IAAiB,OACnB,EAAO,KAAK,UAAU,EAAQ,KAAM,EAAE,CAAG;EAAK,CACrC,IAAiB,UAC1B,EAAa,EAAQ,EAAO,CAE5B,EAAc,EAAQ,EAAO,CAGxB,QACA,EAAO,CAEd,OADA,EAAO,gBAAgB,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,CAAC,IAAI,CAC3E,GAKX,eAAsB,GAAuC,CAC3D,IAAM,EAAM,MAAM,GAAoB,CAChC,EAAS,MAAM,GAAsB,CACrC,EAAQ,MAAM,GAAc,CAC5B,EAAe,MAAM,GAAqB,CAC1C,CAAE,UAAS,4BAA6B,MAAM,GAA+B,CAGnF,MAAO,CAAE,YAAa,EAAK,cAAe,EAAQ,QAAO,eAAc,UAAS,KAFnE,MAAM,EAAoB,EAAyB,CAEsB,CAGxF,eAAe,GAAkD,CAC/D,MAAO,CACL,QAAS,WAAW,QAAQ,UAC5B,YAAa,QAAQ,SACrB,MAAO,QAAQ,IAAI,OAAS,YAC5B,SAAU,QAAQ,IAAI,MAAQ,YAC9B,IAAK,GAAG,GAAM,CAAC,OAAO,GAAG,GAAM,GAC/B,OAAQ,CAAE,MAAO,GAAU,CAAE,KAAM,GAAS,CAAE,CAC9C,SAAU,GAAU,CACpB,QAAS,GAAS,CACnB,CAGH,eAAe,GAAsD,CACnE,IAAM,EAAW,QAAQ,IAAI,kBACzB,8BACA,QAAQ,IAAI,aACV,yBACA,QAAQ,IAAI,kBACV,yBACA,iBAER,MAAO,CACL,aAAc,QAAQ,KAAK,CAC3B,WAAY,EACZ,WAAY,MAAM,EAAY,KAAK,CACnC,OAAQ,MAAM,EAAY,MAAM,CAChC,WAGA,gBAAiB,CACf,WAAY,EACZ,mBAAoB,EACpB,mBAAoB,EACpB,gBAAiB,EACjB,qBAAsB,EACvB,CACF,CAqBH,eAAe,GAGZ,CACD,GAAI,CACF,GAAM,CAAE,8BAA+B,MAAM,OAAO,uCAC9C,CAAE,6BAA8B,MAAM,OAAO,iCAC7C,CAAE,sBAAqB,qBAAsB,MAAM,OAAO,uCAE1D,EAAa,EAA2B,CAAE,IAAK,QAAQ,KAAK,CAAE,QAAS,GAAS,CAAE,CAAC,CACnF,EAAc,GAA2B,CAAC,mBAAqB,EAAE,CACjE,EAA2B,EAAW,KAAM,GAAM,EAAE,SAAS,cAAc,SAAS,WAAW,CAAC,CAKhG,EADc,EAAW,OAAQ,GAAM,EAAE,QAAU,UAAU,CAChC,OAAS,GAAK,EAAY,SAAW,EAElE,EAAS,EAAoB,CACjC,WAAY,EAAW,IAAK,IAAO,CAAE,GAAI,EAAE,GAAI,MAAO,EAAE,MAAO,EAAE,CAEjE,OAAQ,EACJ,EAAW,OAAQ,GAAM,EAAE,QAAU,UAAU,CAAC,IAAK,IAAO,CAAE,GAAI,EAAE,GAAI,MAAO,EAAE,MAAO,EAAE,CAC1F,EAAW,IAAK,IAAO,CAAE,GAAI,EAAE,GAAI,MAAO,EAAE,MAAO,EAAE,CAC1D,CAAC,CAEF,MAAO,CACL,QAAS,CACP,WAAY,EAAW,OACvB,KAAM,EAAkB,EAAO,CAC/B,QAAS,EAAO,mBACjB,CACD,2BACD,MACK,CAEN,MAAO,CAAE,QAAS,IAAA,GAAW,yBAA0B,GAAO,EASlE,eAAe,EACb,EAC+E,CAC/E,GAAI,CACF,GAAM,CAAE,0BAA2B,MAAM,OAAO,cAC1C,CAAE,4BAA6B,MAAM,OAAO,sCAC5C,CAAE,mBAAoB,MAAM,OAAO,mBACnC,CAAE,wBAAyB,MAAM,OAAO,eAIxC,EAAY,EAAuB,CAAE,wBAAyB,GAAO,CAAC,CAG5E,MAAM,EAAU,mBAAmB,CACnC,IAAM,EAAmB,EAAU,eAAe,CAAC,OAAS,EAEtD,EAAW,IAAI,EAAgB,CAAE,kBAAmB,EAAK,EAAU,EAAqB,CAAE,CAAC,CAIjG,OAHA,MAAM,EAAS,MAAM,CAGd,EAAyB,CAAE,2BAA0B,mBAAkB,qBAFjD,OAAO,EAAS,IAAI,4BAA4B,EAAK,SAEkB,CAAC,MAC/F,CAEN,QAIJ,eAAe,GAAkD,CAC/D,IAAM,EAAS,EAAK,EAAU,aAAa,CAC3C,GAAI,CAAC,EAAW,EAAO,CACrB,MAAO,CAAE,SAAQ,SAAU,GAAO,CAGpC,GAAI,CACF,GAAM,CAAE,qBAAsB,MAAM,OAAO,uBACrC,EAAQ,IAAI,EAAkB,CAAE,SAAQ,UAAW,SAAU,IAAK,GAAO,CAAC,CAChF,GAAI,CACF,IAAM,EAAO,EAAM,SAAS,CAQ5B,MAAO,CACL,SACA,SAAU,GACV,OAAQ,EAAgB,EAAO,CAC/B,GAAG,EACJ,QACO,CACR,EAAM,OAAO,OAET,CACN,MAAO,CAAE,SAAQ,SAAU,GAAM,UAAW,yBAA0B,EAI1E,eAAe,GAAoD,CACjE,IAAI,EAAe,oBACf,EAAoB,GACpB,EAEE,EAAQ,KAAK,KAAK,CACxB,GAAI,CACF,IAAM,EAAa,IAAI,gBACjB,EAAU,eAAiB,EAAW,OAAO,CAAE,IAAK,CAC1D,MAAM,MAAM,WAAW,IAAgB,CACrC,OAAQ,OACR,OAAQ,EAAW,OACpB,CAAC,CACF,aAAa,EAAQ,CACrB,EAAoB,GACpB,EAAkB,KAAK,KAAK,CAAG,OACzB,CACN,EAAoB,GAGtB,MAAO,CACL,eACA,oBACA,kBACA,sBAAuB,GACxB,CAGH,eAAe,EAAY,EAAgC,CACzD,GAAI,CAEF,OADA,EAAW,EAAK,iBAAkB,EAAK,CAAE,EAAE,CACpC,QACD,CACN,GAAI,CAEF,OADA,EAAW,EAAK,WAAY,EAAK,CAAE,EAAE,CAC9B,QACD,CACN,MAAO,KAKb,SAAS,EAAgB,EAAsB,CAC7C,GAAI,CACF,OAAO,EAAS,EAAK,CAAC,UAChB,CACN,MAAO,IAIX,SAAS,EAAa,EAA6B,EAA4B,CAC7E,GAAM,CAAE,YAAa,EAAK,cAAe,EAAQ,QAAO,aAAc,GAAS,EACzE,EAAO,GAAO,UAAY,EAAM,YAAc,KAAO,IAAM,IAC3D,EAAQ,EAAK,kBAAoB,IAAM,IACvC,EAAY,EAAO,SAAW,EAAO,QAAQ,KAAK,OAAS,EAC7D,cAAc,EAAO,QAAQ,QAAQ,OAAO,sBAC5C,GAGE,EAAW,EAAO,MAAQ,EAAO,KAAK,SAAW,mBACnD,OAAO,EAAO,KAAK,QAAQ,KAAK,EAAO,KAAK,iBAC5C,GACJ,EAAO,CACL,iBAAiB,EAAI,QAAQ,KAAK,EAAO,WAAW,QAAQ,EAAK,GAAG,IACpE,EAAQ,KAAK,EAAM,WAAa,IAAI,cAAc,EAAM,kBAAoB,IAAI,aAAe,GAC/F,EACA,EACD,CAAC,OAAO,QAAQ,CAAC,KAAK;EAAK,CAAG;EAAK,CAGtC,SAAS,EAAc,EAA6B,EAA4B,CAC9E,GAAM,CAAE,YAAa,EAAK,cAAe,EAAQ,QAAO,aAAc,GAAS,EAS/E,GAPA,EAAO;;EAAkB,CACzB,EAAO,kBAAkB,EAAI,QAAQ,IAAI,CACzC,EAAO,kBAAkB,EAAI,YAAY,IAAI,CAC7C,EAAO,kBAAkB,EAAI,MAAM,IAAI,CACvC,EAAO,kBAAkB,EAAI,SAAS,IAAI,EAAI,QAAQ,KAAK,CAC3D,EAAO,kBAAkB,EAAI,IAAI,IAAI,CACrC,EAAO,kBAAkB,EAAY,EAAI,OAAO,KAAK,CAAC,KAAK,EAAY,EAAI,OAAO,MAAM,CAAC,SAAS,CAC9F,EAAO,gBAAiB,CAC1B,IAAM,EAAK,EAAO,gBAClB,EAAO,yBAAyB,EAAY,EAAG,WAAW,CAAC,gBAAgB,EAAY,EAAG,mBAAmB,CAAC,GAAG,EAAY,EAAG,mBAAmB,CAAC,YAAY,EAAG,gBAAgB,WAAW,EAAG,qBAAqB,IAAI,CAW5N,GARA,EAAO;;EAAoB,CAC3B,EAAO,kBAAkB,EAAO,aAAa,IAAI,CACjD,EAAO,kBAAkB,EAAO,WAAW,GAAG,EAAW,EAAO,WAAW,CAAG,IAAM,IAAI,IAAI,CAC5F,EAAO,kBAAkB,EAAO,WAAa,IAAM,IAAI,IAAI,CAC3D,EAAO,kBAAkB,EAAO,OAAS,IAAM,IAAI,IAAI,CACvD,EAAO,kBAAkB,EAAO,SAAS,IAAI,CAGzC,EAAO,QAGT,GAFA,EAAO;;EAAc,CACrB,EAAO,kBAAkB,EAAO,QAAQ,WAAW,IAAI,CACnD,EAAO,QAAQ,KAAK,OAAS,EAAG,CAClC,EAAO;EAA6B,CACpC,IAAK,IAAM,KAAQ,EAAO,QAAQ,KAAM,EAAO,OAAO,EAAK,IAAI,MAE/D,EAAO;EAAqB,CAyBhC,GArBI,IACF,EAAO;;EAAY,CACnB,EAAO,kBAAkB,EAAM,OAAO,IAAI,CACtC,EAAM,UACR,EAAO,kBAAkB,EAAY,EAAM,QAAU,EAAE,CAAC,IAAI,CAC5D,EAAO,kBAAkB,EAAM,YAAc,KAAO,OAAS,EAAM,WAAa,MAAM,IAAI,CACtF,EAAM,YAAc,IAAA,IAAW,EAAO,kBAAkB,EAAM,UAAU,IAAI,CAC5E,EAAM,mBAAqB,IAAA,IAAW,EAAO,mBAAmB,EAAM,iBAAiB,YAAY,CACnG,EAAM,WAAa,IAAA,IAAW,EAAO,mBAAmB,EAAM,SAAS,IAAI,CAC3E,EAAM,aAAe,IAAA,IAAW,EAAO,kBAAkB,EAAM,WAAa,UAAY,WAAW,IAAI,EAE3G,EAAO;EAAyD,EAIpE,EAAO;;EAAmB,CAC1B,EAAO,KAAK,EAAK,aAAa,IAAI,EAAK,kBAAoB,cAAc,EAAK,gBAAgB,OAAS,gBAAgB,IAAI,CAC3H,EAAO,mBAAmB,EAAK,sBAAwB,aAAe,8BAA8B,IAAI,CAIpG,EAAO,KAAM,CACf,IAAM,EAAO,EAAO,KAAK,SAAW,mBAAqB,IAAM,IAC/D,EAAO;;EAAuB,CAC9B,EAAO,KAAK,EAAK,GAAG,EAAO,KAAK,QAAQ,IAAI,CAC5C,EAAO,OAAO,EAAO,KAAK,eAAe,IAAI,CAG/C,EAAO;EAAK,CAGd,SAAS,EAAU,EAAmC,CACpD,EAAO;;;;;;;;;;;;;;EAcP,CAGF,SAAS,EAAY,EAAuB,CAC1C,GAAI,IAAU,EAAG,MAAO,MACxB,IAAM,EAAQ,CAAC,IAAK,KAAM,KAAM,KAAM,KAAK,CACrC,EAAI,KAAK,MAAM,KAAK,IAAI,EAAM,CAAG,KAAK,IAAI,KAAK,CAAC,CACtD,MAAO,IAAI,EAAiB,MAAM,GAAI,QAAQ,EAAE,CAAC,GAAG,EAAM"}
1
+ {"version":3,"file":"doctor-lT2QhG2q.js","names":[],"sources":["../src/commands/doctor.ts"],"sourcesContent":["import { homedir, release, platform, arch, cpus, totalmem, freemem } from 'node:os'\nimport { existsSync, statSync, accessSync } from 'node:fs'\nimport { join } from 'node:path'\nimport {\n SESSION_TOTAL_RESIDENCY_BUDGET,\n SESSION_MIN_PER_SESSION_BYTES,\n SESSION_MAX_PER_SESSION_BYTES,\n SESSION_MAX_HISTORY_MESSAGES,\n SESSION_RESUME_WINDOW_MESSAGES,\n} from '@x-otto/session-contract'\nexport interface DoctorDeps {\n stdout: (s: string) => void\n stderr: (s: string) => void\n}\n\nexport interface DoctorReport {\n environment: EnvironmentSection\n configuration: ConfigurationSection\n state?: StateSection\n connectivity: ConnectivitySection\n /** RFC-364 M2:插件装配一致性——发现得到却未装载的缺口。 */\n plugins?: PluginsSection\n /** RFC-326 M6-01:FTUE 环境自检(廉价事实推导,非权威 readiness 判定,见模块头注释)。 */\n ftue?: import('./ftue/ftue-doctor-recovery').DoctorFtueGuidance\n}\n\ninterface PluginsSection {\n discovered: number\n /** 装配缺口的可执行诊断行(空数组 = 无缺口)。 */\n gaps: string[]\n /** 缺口涉及的插件 id(供 --json 消费方精确处理)。 */\n missing: Array<{ id: string; scope?: string }>\n}\n\ninterface EnvironmentSection {\n runtime: string\n installPath: string\n shell: string\n terminal: string\n cpu: string\n memory: { total: number; free: number }\n platform: string\n release: string\n}\n\ninterface ConfigurationSection {\n workspaceDir: string\n storageDir: string\n hasRipgrep: boolean\n hasGit: boolean\n authMode: string\n /** RFC-323:驻留预算配置(只读展示,使基线估算偏差可被校准) */\n residencyBudget?: {\n totalBytes: number\n minPerSessionBytes: number\n maxPerSessionBytes: number\n historyMessages: number\n resumeWindowMessages: number\n }\n}\n\ninterface StateSection {\n dbPath: string\n dbExists: boolean\n dbSize?: number\n walEnabled?: boolean\n snapshots?: number\n changeLogEntries?: number\n sessions?: number\n integrity?: string\n}\n\ninterface ConnectivitySection {\n providerHost: string\n providerReachable: boolean\n providerLatency?: number\n storageHostConfigured: boolean\n}\n\nconst OTTO_DIR = join(homedir(), '.otto')\n\nexport async function runDoctorCommand(\n args: string[],\n deps: DoctorDeps,\n): Promise<number> {\n const stdout = deps.stdout\n const stderr = deps.stderr\n\n // RFC-200 D1.0:子命令路由——首个位置参数(非 -- 开头)分发到诊断子命令;\n // 无子命令时走既有健康检查(行为零变化)。\n const sub = args.find((a) => !a.startsWith('-'))\n if (sub === 'ps' || sub === 'attach' || sub === 'heap') {\n const rest = args.filter((a) => a !== sub)\n switch (sub) {\n case 'ps': {\n const { runDoctorPs } = await import('./handlers/doctor-ps')\n return runDoctorPs(rest, deps)\n }\n case 'attach': {\n const { runDoctorAttach } = await import('./handlers/doctor-attach')\n return runDoctorAttach(rest, deps)\n }\n case 'heap': {\n const { runDoctorHeap } = await import('./handlers/doctor-heap')\n return runDoctorHeap(rest, deps)\n }\n }\n }\n if (sub !== undefined) {\n stderr(`otto doctor: unknown subcommand \"${sub}\" (available: ps, attach, heap)\\n`)\n return 1\n }\n\n if (args.includes('--help') || args.includes('-h')) {\n printHelp(stdout)\n return 0\n }\n\n const outputFormat = args.includes('--json') ? 'json' : args.includes('--summary') ? 'summary' : 'detailed'\n\n try {\n const report = await collectReport()\n\n if (outputFormat === 'json') {\n stdout(JSON.stringify(report, null, 2) + '\\n')\n } else if (outputFormat === 'summary') {\n printSummary(stdout, report)\n } else {\n printDetailed(stdout, report)\n }\n\n return 0\n } catch (error) {\n stderr(`otto doctor: ${error instanceof Error ? error.message : String(error)}\\n`)\n return 1\n }\n}\n\n/** 收集结构化诊断报告(TUI /doctor 折叠档复用;不打印)。 */\nexport async function collectReport(): Promise<DoctorReport> {\n const env = await collectEnvironment()\n const config = await collectConfiguration()\n const state = await collectState()\n const connectivity = await collectConnectivity()\n const { plugins, providerPluginDiscovered } = await collectPluginsAndProviderFlag()\n const ftue = await collectFtueGuidance(providerPluginDiscovered)\n\n return { environment: env, configuration: config, state, connectivity, plugins, ftue }\n}\n\nasync function collectEnvironment(): Promise<EnvironmentSection> {\n return {\n runtime: `Node.js ${process.version}`,\n installPath: process.execPath,\n shell: process.env.SHELL ?? '(unknown)',\n terminal: process.env.TERM ?? '(unknown)',\n cpu: `${cpus().length}×${arch()}`,\n memory: { total: totalmem(), free: freemem() },\n platform: platform(),\n release: release(),\n }\n}\n\nasync function collectConfiguration(): Promise<ConfigurationSection> {\n const authMode = process.env.ANTHROPIC_API_KEY\n ? 'api-key (ANTHROPIC_API_KEY)'\n : process.env.OTTO_API_KEY\n ? 'api-key (OTTO_API_KEY)'\n : process.env.AWS_ACCESS_KEY_ID\n ? 'aws-bedrock (env keys)'\n : 'not configured'\n\n return {\n workspaceDir: process.cwd(),\n storageDir: OTTO_DIR,\n hasRipgrep: await checkBinary('rg'),\n hasGit: await checkBinary('git'),\n authMode,\n // RFC-323 M3:暴露驻留预算配置,使\"基线 1500MB 估算偏差\"(R-2)可被真机数据校准。\n // 纯静态读常量,不接运行时状态——运行时有效分配值取决于活跃会话数 N,在 ps/doctor-ps 展示。\n residencyBudget: {\n totalBytes: SESSION_TOTAL_RESIDENCY_BUDGET,\n minPerSessionBytes: SESSION_MIN_PER_SESSION_BYTES,\n maxPerSessionBytes: SESSION_MAX_PER_SESSION_BYTES,\n historyMessages: SESSION_MAX_HISTORY_MESSAGES,\n resumeWindowMessages: SESSION_RESUME_WINDOW_MESSAGES,\n },\n }\n}\n\n/**\n * RFC-364 M2:插件装配一致性自检。\n *\n * 判据是「cli 侧完整发现(含 bundled 层)」与「App 实际装载」的差集。但 doctor 是\n * 轻量诊断命令(RFC-150 D3 明确要求不承担重依赖),构造真实 App 代价过高——\n * 故此处只做**发现侧**的自检:确认 bundled 目录可解析且其中的扩展能被发现。\n *\n * 真正的「发现 vs 装载」全量比对由运行时侧承担(App 启动路径),本处提供的是\n * 用户可主动运行的诊断入口:当用户报告\"插件不见了\"时,`otto doctor` 能立刻回答\n * \"otto 这一侧到底看见了哪些\"。\n */\n/**\n * RFC-326 M6-01:与 `providerPluginDiscovered` 标志合并计算——`discoverPluginsWithBundled`\n * 是本函数唯一的重量级调用,`collectFtueGuidance` 需要同一份发现结果的 `provider`\n * capability 过滤,若各自重新扫描一次会重复触发文件系统遍历。`providerPluginDiscovered`\n * 在扫描失败时缺省 `false`(诊断失败不阻断其余部分,与既有 fail-soft 语义一致;\n * FTUE 分支据此会诚实报告\"未发现 provider 插件\"而非静默跳过整个 ftue 字段)。\n */\nasync function collectPluginsAndProviderFlag(): Promise<{\n plugins: PluginsSection | undefined\n providerPluginDiscovered: boolean\n}> {\n try {\n const { discoverPluginsWithBundled } = await import('../discover-with-bundled')\n const { hostSelfDescribingOptions } = await import('../create-host-app')\n const { checkPluginAssembly, formatAssemblyGap } = await import('../plugin-assembly-check')\n\n const discovered = discoverPluginsWithBundled({ cwd: process.cwd(), homedir: homedir() })\n const bundledDirs = hostSelfDescribingOptions().pluginBundledDirs ?? []\n const providerPluginDiscovered = discovered.some((p) => p.manifest.capabilities?.includes('provider'))\n\n // 宿主自描述参数解析不出目录,但发现层却报告了 bundled 作用域插件(或反之)——\n // 这正是 M1 修的那类装配漂移,在此处显式暴露。\n const bundledSeen = discovered.filter((p) => p.scope === 'bundled')\n const assemblyBroken = bundledSeen.length > 0 && bundledDirs.length === 0\n\n const result = checkPluginAssembly({\n discovered: discovered.map((p) => ({ id: p.id, scope: p.scope })),\n // 发现侧自检:把「宿主参数解析失败」建模为\"全部 bundled 未装载\"\n active: assemblyBroken\n ? discovered.filter((p) => p.scope !== 'bundled').map((p) => ({ id: p.id, scope: p.scope }))\n : discovered.map((p) => ({ id: p.id, scope: p.scope })),\n })\n\n return {\n plugins: {\n discovered: discovered.length,\n gaps: formatAssemblyGap(result),\n missing: result.missingFromRuntime,\n },\n providerPluginDiscovered,\n }\n } catch {\n // 诊断失败不阻断 doctor 其余部分(与既有 collectState 的 fail-soft 一致)。\n return { plugins: undefined, providerPluginDiscovered: false }\n }\n}\n\n/**\n * RFC-326 M6-01:FTUE 环境自检——三个廉价事实推导诊断状态(见\n * `ftue-doctor-recovery.ts` 头注释的判据边界)。`AuthStore` 构造零 App 依赖\n * (默认读全局 `AUTH_PATH`),`listProviders()` 只读本地文件/env,不发网络请求。\n */\nasync function collectFtueGuidance(\n providerPluginDiscovered: boolean,\n): Promise<import('./ftue/ftue-doctor-recovery').DoctorFtueGuidance | undefined> {\n try {\n const { createDefaultAuthStore } = await import('@x-otto/ai')\n const { deriveDoctorFtueGuidance } = await import('./ftue/ftue-doctor-recovery')\n const { SettingsManager } = await import('@x-otto/setting')\n const { OTTO_CONFIG_FILENAME } = await import('@x-otto/env')\n\n // watchForExternalChanges:false —— doctor 是一次性命令,跑完即退出,不需要\n // fs.watch(同 auth-command.ts 的既有约定,见其头注释)。\n const authStore = createDefaultAuthStore({ watchForExternalChanges: false })\n // listProviders() 读内存缓存(this.cache),不像 getAuthSource/hasCredential 那样\n // 自带 ensureInitialized() 前置——首次调用前必须显式触发一次磁盘加载,否则读到空态。\n await authStore.ensureInitialized()\n const hasAnyCredential = authStore.listProviders().length > 0\n\n const settings = new SettingsManager({ projectConfigPath: join(OTTO_DIR, OTTO_CONFIG_FILENAME) })\n await settings.load()\n const firstPromptSubmitted = typeof settings.get('first_prompt_submitted_at') === 'number'\n\n return deriveDoctorFtueGuidance({ providerPluginDiscovered, hasAnyCredential, firstPromptSubmitted })\n } catch {\n // 诊断失败不阻断 doctor 其余部分。\n return undefined\n }\n}\n\nasync function collectState(): Promise<StateSection | undefined> {\n const dbPath = join(OTTO_DIR, 'storage.db')\n if (!existsSync(dbPath)) {\n return { dbPath, dbExists: false }\n }\n\n try {\n const { SqlitePersistence } = await import('@x-otto/persistence')\n const store = new SqlitePersistence({ dbPath, namespace: 'doctor', wal: false })\n try {\n const info = store.getInfo() as {\n walEnabled: boolean\n snapshots: number\n changeLogEntries: number\n sessions: number\n integrity: string\n }\n\n return {\n dbPath,\n dbExists: true,\n dbSize: getFileSizeSync(dbPath),\n ...info,\n }\n } finally {\n store.close()\n }\n } catch {\n return { dbPath, dbExists: true, integrity: 'error (unable to open)' }\n }\n}\n\nasync function collectConnectivity(): Promise<ConnectivitySection> {\n let providerHost = 'api.anthropic.com'\n let providerReachable = false\n let providerLatency: number | undefined\n\n const start = Date.now()\n try {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), 5000)\n await fetch(`https://${providerHost}`, {\n method: 'HEAD',\n signal: controller.signal,\n })\n clearTimeout(timeout)\n providerReachable = true\n providerLatency = Date.now() - start\n } catch {\n providerReachable = false\n }\n\n return {\n providerHost,\n providerReachable,\n providerLatency,\n storageHostConfigured: false,\n }\n}\n\nasync function checkBinary(name: string): Promise<boolean> {\n try {\n accessSync(join('/usr/local/bin', name), 1)\n return true\n } catch {\n try {\n accessSync(join('/usr/bin', name), 1)\n return true\n } catch {\n return false\n }\n }\n}\n\nfunction getFileSizeSync(path: string): number {\n try {\n return statSync(path).size\n } catch {\n return 0\n }\n}\n\nfunction printSummary(stdout: (s: string) => void, report: DoctorReport): void {\n const { environment: env, configuration: config, state, connectivity: conn } = report\n const dbOk = state?.dbExists && state.integrity === 'ok' ? '✅' : '❌'\n const netOk = conn.providerReachable ? '✅' : '❌'\n const pluginGap = report.plugins && report.plugins.gaps.length > 0\n ? ` ⚠ 插件装配缺口 ${report.plugins.missing.length} 项(otto doctor 查看详情)`\n : ''\n // RFC-326 M6-01:只在未配置(非 looks-configured)时提示——已配置用户不需要看\n // 这行噪音,未配置用户能立刻看到下一步命令,不必翻到 detailed 输出。\n const ftueHint = report.ftue && report.ftue.status !== 'looks-configured'\n ? ` ⚠ ${report.ftue.summary} → ${report.ftue.recoveryAction}`\n : ''\n stdout([\n `otto doctor — ${env.runtime} | ${config.storageDir} | DB:${dbOk} ${netOk}`,\n state ? ` ${state.snapshots ?? '?'} snapshots, ${state.changeLogEntries ?? '?'} change_log` : '',\n pluginGap,\n ftueHint,\n ].filter(Boolean).join('\\n') + '\\n')\n}\n\nfunction printDetailed(stdout: (s: string) => void, report: DoctorReport): void {\n const { environment: env, configuration: config, state, connectivity: conn } = report\n\n stdout('\\nEnvironment\\n')\n stdout(` Runtime: ${env.runtime}\\n`)\n stdout(` Install: ${env.installPath}\\n`)\n stdout(` Shell: ${env.shell}\\n`)\n stdout(` Terminal: ${env.terminal} (${env.release})\\n`)\n stdout(` CPU: ${env.cpu}\\n`)\n stdout(` Memory: ${formatBytes(env.memory.free)} / ${formatBytes(env.memory.total)} free\\n`)\n if (config.residencyBudget) {\n const rb = config.residencyBudget\n stdout(` Residency: budget=${formatBytes(rb.totalBytes)} per-session=${formatBytes(rb.minPerSessionBytes)}~${formatBytes(rb.maxPerSessionBytes)} history=${rb.historyMessages} resume=${rb.resumeWindowMessages}\\n`)\n }\n\n stdout('\\nConfiguration\\n')\n stdout(` Workspace: ${config.workspaceDir}\\n`)\n stdout(` Storage: ${config.storageDir} ${existsSync(config.storageDir) ? '✅' : '❌'}\\n`)\n stdout(` ripgrep: ${config.hasRipgrep ? '✅' : '❌'}\\n`)\n stdout(` git: ${config.hasGit ? '✅' : '❌'}\\n`)\n stdout(` Auth: ${config.authMode}\\n`)\n\n // RFC-364 M2:插件装配一致性——有缺口才打印(无缺口时不制造噪音)。\n if (report.plugins) {\n stdout('\\nPlugins\\n')\n stdout(` Discovered: ${report.plugins.discovered}\\n`)\n if (report.plugins.gaps.length > 0) {\n stdout(' Assembly: ❌ 检测到装配缺口\\n')\n for (const line of report.plugins.gaps) stdout(` ${line}\\n`)\n } else {\n stdout(' Assembly: ✅\\n')\n }\n }\n\n if (state) {\n stdout('\\nState\\n')\n stdout(` SQLite DB: ${state.dbPath}\\n`)\n if (state.dbExists) {\n stdout(` size: ${formatBytes(state.dbSize ?? 0)}\\n`)\n stdout(` integrity: ${state.integrity === 'ok' ? 'ok ✅' : state.integrity ?? 'N/A'}\\n`)\n if (state.snapshots !== undefined) stdout(` snapshots: ${state.snapshots}\\n`)\n if (state.changeLogEntries !== undefined) stdout(` change_log: ${state.changeLogEntries} entries\\n`)\n if (state.sessions !== undefined) stdout(` sessions: ${state.sessions}\\n`)\n if (state.walEnabled !== undefined) stdout(` WAL: ${state.walEnabled ? 'enabled' : 'disabled'}\\n`)\n } else {\n stdout(` (no storage.db found — start otto to create one)\\n`)\n }\n }\n\n stdout('\\nConnectivity\\n')\n stdout(` ${conn.providerHost}: ${conn.providerReachable ? `reachable (${conn.providerLatency}ms) ✅` : 'unreachable ❌'}\\n`)\n stdout(` storage host: ${conn.storageHostConfigured ? 'configured' : 'not configured (local only)'}\\n`)\n\n // RFC-326 M6-01:FTUE 环境自检——廉价事实推导,非权威 readiness 判定\n // (见 ftue-doctor-recovery.ts 头注释;权威判定只在 TUI 运行时的 FtueController)。\n if (report.ftue) {\n const icon = report.ftue.status === 'looks-configured' ? '✅' : '⚠'\n stdout('\\nFirst-time setup\\n')\n stdout(` ${icon} ${report.ftue.summary}\\n`)\n stdout(` → ${report.ftue.recoveryAction}\\n`)\n }\n\n stdout('\\n')\n}\n\nfunction printHelp(stdout: (s: string) => void): void {\n stdout(`otto doctor — 环境健康诊断\n\n用法:\n otto doctor [--summary|--json]\n\n选项:\n --summary 紧凑输出(一行摘要)\n --json 结构化 JSON(redacted)\n --help, -h 帮助信息\n\n示例:\n otto doctor\n otto doctor --summary\n otto doctor --json\n`)\n}\n\nfunction formatBytes(bytes: number): string {\n if (bytes === 0) return '0 B'\n const units = ['B', 'KB', 'MB', 'GB', 'TB']\n const i = Math.floor(Math.log(bytes) / Math.log(1024))\n return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${units[i]}`\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{n as e,t}from"./extension-DVEml5IN.js";export{e as EXTENSION_ALIASES,t as runExtensionCommand};
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{t as e}from"./discover-with-bundled-CF8qFP41.js";import{homedir as t}from"node:os";import{HIGH_RISK_CAPABILITIES as n,InstallManager as r,RegistryClient as i,builtinOfficialSource as a,checkUpgrades as o,compilePluginModule as s,compilePluginPanels as c,defaultPostInstallExecutor as l,evaluatePluginTrust as u,findWorkspaceRoot as d,grantPluginCapabilities as f,installPluginFromDir as p,installPluginFromPackage as m,purgePluginState as h,readHighRiskCapabilities as g,recordPluginInstalled as _,resolvePackageDir as v,scanInstalledVersions as ee,trustPlugin as y,untrustPlugin as te}from"@x-otto/coding";import{OTTO_CONFIG_FILENAME as b,OTTO_HOME as x,OTTO_USER_CONFIG_DIR as S}from"@x-otto/env";import{join as C,resolve as w,sep as ne}from"node:path";import{existsSync as T,lstatSync as E,mkdirSync as D,readFileSync as O,readlinkSync as re,symlinkSync as ie,watch as ae,writeFileSync as oe}from"node:fs";import{createInterface as se}from"node:readline/promises";import ce from"react";import{ConfirmDialog as le,runStandaloneTui as ue}from"@x-otto/tui/standalone";import{SettingsManager as k}from"@x-otto/setting";const A=/^[a-z0-9]+(?:-[a-z0-9]+)*$/;function de(e,t={}){let n={commands:!0,panels:[{id:`status`,title:`${e} status`,entry:`panels-dist/status.mjs`}]},r=[];t.withProvider&&(n.providers=[{id:`example`,baseUrl:`https://api.example.com/v1`,wireApi:`openai-completions`,envKey:`EXAMPLE_API_KEY`,models:[{id:`example/gpt-tiny`,contextWindow:8192,maxOutput:512}]}],r.push(`provider`)),t.withRenderer&&(n.renderers=[{id:`screenshot`,matcher:{toolName:`browser_screenshot`},entry:`renderers-dist/screenshot.js`}],r.push(`tui.renderer`)),t.withI18n!==!1&&(n.i18nDir=`locale`);let i={id:e,name:e,version:`0.1.0`,description:`An otto extension: ${e}`,contributes:n,scripts:{postinstall:`otto extension build`}};return r.length&&(i.capabilities=r),`${JSON.stringify(i,null,2)}\n`}function fe(e){return`${JSON.stringify({"hello.description":`Say hello from the ${e} plugin`},null,2)}\n`}function pe(e){return`${JSON.stringify({"hello.description":`从 ${e} 插件说 hello`},null,2)}\n`}function me(e){return`/**
2
+ import{t as e}from"./discover-with-bundled-CF8qFP41.js";import{homedir as t}from"node:os";import{HIGH_RISK_CAPABILITIES as n,InstallManager as r,RegistryClient as i,builtinOfficialSource as a,checkUpgrades as o,compilePluginModule as s,compilePluginPanels as c,defaultPostInstallExecutor as l,evaluatePluginTrust as u,findWorkspaceRoot as d,grantPluginCapabilities as f,grantPluginCapabilitiesWithVersion as p,grantedPluginCapabilitiesForVersion as m,installPluginFromDir as h,installPluginFromPackage as g,purgePluginState as _,readHighRiskCapabilities as v,recordPluginInstalled as ee,resolvePackageDir as y,scanInstalledVersions as te,trustPlugin as b,untrustPlugin as ne}from"@x-otto/coding";import{OTTO_CONFIG_FILENAME as x,OTTO_HOME as S,OTTO_USER_CONFIG_DIR as re}from"@x-otto/env";import{join as C,resolve as w,sep as T}from"node:path";import{existsSync as E,lstatSync as ie,mkdirSync as D,readFileSync as O,readlinkSync as ae,symlinkSync as oe,watch as se,writeFileSync as ce}from"node:fs";import{createInterface as le}from"node:readline/promises";import ue from"react";import{ConfirmDialog as de,runStandaloneTui as fe}from"@x-otto/tui/standalone";import{SettingsManager as k}from"@x-otto/setting";const A=/^[a-z0-9]+(?:-[a-z0-9]+)*$/;function pe(e,t={}){let n={commands:!0,panels:[{id:`status`,title:`${e} status`,entry:`panels-dist/status.mjs`}]},r=[];t.withProvider&&(n.providers=[{id:`example`,baseUrl:`https://api.example.com/v1`,wireApi:`openai-completions`,envKey:`EXAMPLE_API_KEY`,models:[{id:`example/gpt-tiny`,contextWindow:8192,maxOutput:512}]}],r.push(`provider`)),t.withRenderer&&(n.renderers=[{id:`screenshot`,matcher:{toolName:`browser_screenshot`},entry:`renderers-dist/screenshot.js`}],r.push(`tui.renderer`)),t.withI18n!==!1&&(n.i18nDir=`locale`);let i={id:e,name:e,version:`0.1.0`,description:`An otto extension: ${e}`,contributes:n,scripts:{postinstall:`otto extension build`}};return r.length&&(i.capabilities=r),`${JSON.stringify(i,null,2)}\n`}function me(e){return`${JSON.stringify({"hello.description":`Say hello from the ${e} plugin`},null,2)}\n`}function he(e){return`${JSON.stringify({"hello.description":`从 ${e} 插件说 hello`},null,2)}\n`}function j(e){return`/**
3
3
  * ${e} — definePlugin 工具示例(RFC-105 D4)。
4
4
  * 需 manifest 声明 capabilities: ["tools"] 并经信任门后才加载执行。
5
5
  * 运行 otto extension build → 编译 → /extension reload 生效。
@@ -27,7 +27,7 @@ export default definePlugin((ctx) => ({
27
27
  },
28
28
  ],
29
29
  }))
30
- `}function j(e){return`/**
30
+ `}function M(e){return`/**
31
31
  * ${e} screenshot renderer — 自定义 browser_screenshot 工具结果的渲染方式。
32
32
  * 签名:(message: unknown, columns: number) => string[]
33
33
  * 返回 ANSI 着色字符串数组,与 renderMessageLines 同构(不引入 React)。
@@ -41,14 +41,14 @@ module.exports = function screenshotRenderer(message: unknown, columns: number):
41
41
  ' use /extension → ${e} → open panel → preview',
42
42
  ]
43
43
  }
44
- `}function M(e){return`---
44
+ `}function N(e){return`---
45
45
  description: Say hello from the ${e} plugin
46
46
  argument-hint: [your name]
47
47
  ---
48
48
  Hello from the **${e}** plugin! 👋
49
49
 
50
50
  You passed: $ARGUMENTS
51
- `}function N(e){return`import React from 'react'
51
+ `}function P(e){return`import React from 'react'
52
52
  import { Box, Text } from 'ink'
53
53
 
54
54
  /**
@@ -65,7 +65,7 @@ export default function StatusPanel(): React.ReactElement {
65
65
  </Box>
66
66
  )
67
67
  }
68
- `}function P(e){return`# ${e}
68
+ `}function F(e){return`# ${e}
69
69
 
70
70
  一个 otto 插件骨架(RFC-082 Tier 0:纯文件,零编译即可用)。
71
71
 
@@ -95,31 +95,31 @@ otto extension dev --link <workspace> # 链接进工作区 + 监视改动(
95
95
 
96
96
  改命令/agent/skill/manifest 后,在 otto 内 \`/extension reload\` 即生效。
97
97
  (面板 \`panels/*.tsx\` 的热编译待 esbuild 依赖接线,当前需手动编译为 \`panels-dist/*.js\`。)
98
- `}function F(e,t,n={}){if(!A.test(e))throw Error(`插件名须 kebab-case(小写字母/数字/连字符,首尾非连字符):${e}`);let r=C(t,e);if(T(r))throw Error(`目录已存在,拒绝覆盖:${r}`);D(C(r,`commands`),{recursive:!0}),D(C(r,`panels`),{recursive:!0});let i=[],a=(e,t)=>{D(C(r,e,`..`),{recursive:!0}),oe(C(r,e),t),i.push(e)};return a(`otto-plugin.json`,de(e,n)),a(`commands/hello.md`,M(e)),a(`panels/status.tsx`,N(e)),a(`.gitignore`,`panels-dist/
98
+ `}function I(e,t,n={}){if(!A.test(e))throw Error(`插件名须 kebab-case(小写字母/数字/连字符,首尾非连字符):${e}`);let r=C(t,e);if(E(r))throw Error(`目录已存在,拒绝覆盖:${r}`);D(C(r,`commands`),{recursive:!0}),D(C(r,`panels`),{recursive:!0});let i=[],a=(e,t)=>{D(C(r,e,`..`),{recursive:!0}),ce(C(r,e),t),i.push(e)};return a(`otto-plugin.json`,pe(e,n)),a(`commands/hello.md`,N(e)),a(`panels/status.tsx`,P(e)),a(`.gitignore`,`panels-dist/
99
99
  renderers-dist/
100
100
  plugin-dist/
101
101
  dist/
102
102
  bin/
103
- `),n.withTool&&a(`plugin.ts`,me(e)),n.withRenderer&&(D(C(r,`renderers`),{recursive:!0}),a(`renderers/screenshot.ts`,j(e))),n.withI18n!==!1&&(D(C(r,`locale`),{recursive:!0}),a(`locale/en.json`,fe(e)),a(`locale/zh.json`,pe(e))),a(`README.md`,P(e)),{dir:r,files:i}}function I(e,t){let n=e.find(e=>!e.startsWith(`-`));if(!n)return t.stderr(`用法: otto extension create <name> [--with-tool] [--with-provider] [--with-renderer] [--no-i18n]
104
- `),1;let r={withTool:e.includes(`--with-tool`),withProvider:e.includes(`--with-provider`),withRenderer:e.includes(`--with-renderer`),withI18n:!e.includes(`--no-i18n`)};try{let{files:e}=F(n,process.cwd(),r);t.stdout(`✓ 已创建插件 ${n}/\n`);for(let r of e)t.stdout(` ${n}/${r}\n`);let i=[`cp -r ${n} <workspace>/.otto/plugins/ # 装入工作区`,`# 在 otto 内 /extension reload,然后运行 /hello world`,`# 项目级插件含可执行面需 /trust plugin ${n}`];return r.withTool&&i.push(`# plugin.ts 编译/加载需信任门后 —— otto extension build → /extension reload`),r.withProvider&&i.push(`# 声明式 provider 无需编译,manifest 已含示例 —— /extension reload 后即生效`),r.withRenderer&&i.push(`# renderer 源码在 renderers/,需编译:otto extension build → /extension reload`),r.withI18n&&i.push(`# locale/*.json 无需编译,manifest 已声明 i18nDir —— /extension reload 后即生效`),t.stdout(`
103
+ `),n.withTool&&a(`plugin.ts`,j(e)),n.withRenderer&&(D(C(r,`renderers`),{recursive:!0}),a(`renderers/screenshot.ts`,M(e))),n.withI18n!==!1&&(D(C(r,`locale`),{recursive:!0}),a(`locale/en.json`,me(e)),a(`locale/zh.json`,he(e))),a(`README.md`,F(e)),{dir:r,files:i}}function L(e,t){let n=e.find(e=>!e.startsWith(`-`));if(!n)return t.stderr(`用法: otto extension create <name> [--with-tool] [--with-provider] [--with-renderer] [--no-i18n]
104
+ `),1;let r={withTool:e.includes(`--with-tool`),withProvider:e.includes(`--with-provider`),withRenderer:e.includes(`--with-renderer`),withI18n:!e.includes(`--no-i18n`)};try{let{files:e}=I(n,process.cwd(),r);t.stdout(`✓ 已创建插件 ${n}/\n`);for(let r of e)t.stdout(` ${n}/${r}\n`);let i=[`cp -r ${n} <workspace>/.otto/plugins/ # 装入工作区`,`# 在 otto 内 /extension reload,然后运行 /hello world`,`# 项目级插件含可执行面需 /trust plugin ${n}`];return r.withTool&&i.push(`# plugin.ts 编译/加载需信任门后 —— otto extension build → /extension reload`),r.withProvider&&i.push(`# 声明式 provider 无需编译,manifest 已含示例 —— /extension reload 后即生效`),r.withRenderer&&i.push(`# renderer 源码在 renderers/,需编译:otto extension build → /extension reload`),r.withI18n&&i.push(`# locale/*.json 无需编译,manifest 已声明 i18nDir —— /extension reload 后即生效`),t.stdout(`
105
105
  下一步:
106
106
  `+i.join(`
107
107
  `)+`
108
- `),0}catch(e){return t.stderr(`otto extension create 失败: ${e.message}\n`),1}}function L(e,t){let{compiled:n,errors:r,skipped:i}=c(e);if(i)return t.stdout(` (无 panels/*.tsx,跳过面板编译)
109
- `),0;for(let e of n)t.stdout(` ✓ 编译 ${e}\n`);for(let e of r)t.stderr(` ✗ ${e.entry}: ${e.message}\n`);return r.length}async function R(e,t){try{return await s(e)&&t.stdout(` ✓ 编译 ${C(`plugin-dist`,`plugin.cjs`)}\n`),0}catch(e){return t.stderr(` ✗ plugin.ts: ${e.message}\n`),1}}function z(e,t){let n=w(B(e,`--dir`)??process.cwd());return T(C(n,`otto-plugin.json`))?(t.stdout(`▶ otto extension build:${n}\n`),L(n,t)>0?1:0):(t.stderr(`otto extension build 失败: 未找到 otto-plugin.json:${n}\n`),1)}function B(e,t){let n=e.indexOf(t);return n>=0&&n+1<e.length?e[n+1]:void 0}function V(e){let t=C(e,`otto-plugin.json`);if(!T(t))throw Error(`未找到 otto-plugin.json:${e}(请在插件目录内运行,或用 --dir <path>)`);let n;try{n=JSON.parse(O(t,`utf-8`))}catch{throw Error(`otto-plugin.json 非法 JSON:${t}`)}let r=n?.id;if(typeof r!=`string`||!A.test(r))throw Error(`otto-plugin.json 缺少合法 id(kebab-case):${t}`);return r}function H(e,t,n){let r=C(n,`.otto`,`plugins`);D(r,{recursive:!0});let i=C(r,t);if(T(i)||U(i)){if(U(i))try{if(w(re(i))===w(e))return i}catch{}throw Error(`目标已存在且非本插件 dev 链接,拒绝覆盖:${i}`)}return ie(w(e),i),i}function U(e){try{return E(e).isSymbolicLink()}catch{return!1}}function W(e,t){return`[${t}] ${e} 改动 → 在 otto 内 /extension reload 生效\n`}function he(e){return[e.getHours(),e.getMinutes(),e.getSeconds()].map(e=>String(e).padStart(2,`0`)).join(`:`)}async function ge(e,t){let n=w(B(e,`--dir`)??process.cwd()),r;try{r=V(n)}catch(e){return t.stderr(`otto extension dev 失败: ${e.message}\n`),Promise.resolve(1)}t.stdout(`▶ otto extension dev:${r}\n 监视 ${n}\n`);let i=B(e,`--link`);if(i)try{let e=H(n,r,w(i));t.stdout(` 已链接 → ${e}\n`)}catch(e){t.stderr(` 链接失败:${e.message}\n`)}let a=T(C(n,`panels`));a&&(t.stdout(` 编译面板…
110
- `),L(n,t));let o=T(C(n,`plugin.ts`))||T(C(n,`plugin.tsx`));return o&&(t.stdout(` 编译 plugin.ts…
111
- `),await R(n,t)),t.stdout(` 改动后在 otto 内 /extension reload 生效。Ctrl+C 退出。
112
- `),new Promise(e=>{let r;try{r=ae(n,{recursive:!0},(e,r)=>{if(!r)return;let i=String(r);i.includes(`panels-dist`)||i.includes(`plugin-dist`)||i.startsWith(`.`)||(t.stdout(W(i,he(new Date))),a&&i.endsWith(`.tsx`)&&i!==`plugin.tsx`&&L(n,t),o&&(i===`plugin.ts`||i===`plugin.tsx`)&&R(n,t))})}catch(n){t.stderr(` 监视启动失败(平台可能不支持递归监视):${n.message}\n`),e(1);return}let i=()=>{r?.close(),process.off(`SIGINT`,i),t.stdout(`
108
+ `),0}catch(e){return t.stderr(`otto extension create 失败: ${e.message}\n`),1}}function R(e,t){let{compiled:n,errors:r,skipped:i}=c(e);if(i)return t.stdout(` (无 panels/*.tsx,跳过面板编译)
109
+ `),0;for(let e of n)t.stdout(` ✓ 编译 ${e}\n`);for(let e of r)t.stderr(` ✗ ${e.entry}: ${e.message}\n`);return r.length}async function z(e,t){try{return await s(e)&&t.stdout(` ✓ 编译 ${C(`plugin-dist`,`plugin.cjs`)}\n`),0}catch(e){return t.stderr(` ✗ plugin.ts: ${e.message}\n`),1}}function ge(e,t){let n=w(B(e,`--dir`)??process.cwd());return E(C(n,`otto-plugin.json`))?(t.stdout(`▶ otto extension build:${n}\n`),R(n,t)>0?1:0):(t.stderr(`otto extension build 失败: 未找到 otto-plugin.json:${n}\n`),1)}function B(e,t){let n=e.indexOf(t);return n>=0&&n+1<e.length?e[n+1]:void 0}function V(e){let t=C(e,`otto-plugin.json`);if(!E(t))throw Error(`未找到 otto-plugin.json:${e}(请在插件目录内运行,或用 --dir <path>)`);let n;try{n=JSON.parse(O(t,`utf-8`))}catch{throw Error(`otto-plugin.json 非法 JSON:${t}`)}let r=n?.id;if(typeof r!=`string`||!A.test(r))throw Error(`otto-plugin.json 缺少合法 id(kebab-case):${t}`);return r}function H(e,t,n){let r=C(n,`.otto`,`plugins`);D(r,{recursive:!0});let i=C(r,t);if(E(i)||U(i)){if(U(i))try{if(w(ae(i))===w(e))return i}catch{}throw Error(`目标已存在且非本插件 dev 链接,拒绝覆盖:${i}`)}return oe(w(e),i),i}function U(e){try{return ie(e).isSymbolicLink()}catch{return!1}}function W(e,t){return`[${t}] ${e} 改动 → 在 otto 内 /extension reload 生效\n`}function _e(e){return[e.getHours(),e.getMinutes(),e.getSeconds()].map(e=>String(e).padStart(2,`0`)).join(`:`)}async function ve(e,t){let n=w(B(e,`--dir`)??process.cwd()),r;try{r=V(n)}catch(e){return t.stderr(`otto extension dev 失败: ${e.message}\n`),Promise.resolve(1)}t.stdout(`▶ otto extension dev:${r}\n 监视 ${n}\n`);let i=B(e,`--link`);if(i)try{let e=H(n,r,w(i));t.stdout(` 已链接 → ${e}\n`)}catch(e){t.stderr(` 链接失败:${e.message}\n`)}let a=E(C(n,`panels`));a&&(t.stdout(` 编译面板…
110
+ `),R(n,t));let o=E(C(n,`plugin.ts`))||E(C(n,`plugin.tsx`));return o&&(t.stdout(` 编译 plugin.ts…
111
+ `),await z(n,t)),t.stdout(` 改动后在 otto 内 /extension reload 生效。Ctrl+C 退出。
112
+ `),new Promise(e=>{let r;try{r=se(n,{recursive:!0},(e,r)=>{if(!r)return;let i=String(r);i.includes(`panels-dist`)||i.includes(`plugin-dist`)||i.startsWith(`.`)||(t.stdout(W(i,_e(new Date))),a&&i.endsWith(`.tsx`)&&i!==`plugin.tsx`&&R(n,t),o&&(i===`plugin.ts`||i===`plugin.tsx`)&&z(n,t))})}catch(n){t.stderr(` 监视启动失败(平台可能不支持递归监视):${n.message}\n`),e(1);return}let i=()=>{r?.close(),process.off(`SIGINT`,i),t.stdout(`
113
113
  停止 dev。
114
- `),e(0)};process.on(`SIGINT`,i)})}const G={"plugin-browser-use":{pkg:`@x-otto/plugin-browser-use-mcp`,capabilities:[`tui.renderer`,`panel.backend`]},"plugin-mobile-use":{pkg:`@x-otto/plugin-mobile-use-mcp`,capabilities:[`tui.renderer`,`panel.backend`]},"plugin-feishu-use":{pkg:`@x-otto/plugin-feishu-use`,capabilities:[]},"plugin-miniprogram-use":{pkg:`@x-otto/plugin-miniprogram-use-mcp`,capabilities:[`tui.renderer`,`panel.backend`]},"plugin-schedule":{pkg:`@x-otto/plugin-schedule-panel`,capabilities:[`tui.renderer`]},"anthropic-provider":{pkg:`@x-otto/plugin-anthropic`,capabilities:[`provider`]},"deepseek-provider":{pkg:`@x-otto/plugin-deepseek`,capabilities:[`provider`]},"oxygen-provider":{pkg:`@x-otto/plugin-oxygen`,capabilities:[`provider`]},"openai-codex-provider":{pkg:`@x-otto/plugin-openai-codex`,capabilities:[`provider`]},"github-copilot-provider":{pkg:`@x-otto/plugin-github-copilot`,capabilities:[`provider`]},"cursor-provider":{pkg:`@x-otto/plugin-cursor`,capabilities:[`provider`]},"wire-protocols":{pkg:`@x-otto/plugin-otto-wire-protocols`,capabilities:[`wire-protocol`]}},K={provider:`provider —— 注册 LLM provider,将处理你的凭据与请求流量`,tools:`tools —— 注册可被 agent 调用的工具`,"tui.renderer":`tui.renderer —— 在 TUI 进程内运行自定义渲染代码`,network:`network —— 发起任意网络请求`,"wire-protocol":`wire-protocol —— 注册 wire 协议实现,可截获全部模型请求与响应流量(含凭据)`,"a2ui.component":`a2ui.component —— 在 TUI 进程内运行自定义渲染组件代码`,"panel.backend":`panel.backend —— 面板可驱动有副作用的后端业务操作`,"input.resolver":`input.resolver —— 在提交时展开 @ 引用,把内容注入 prompt`,"a2ui.renderer":`a2ui.renderer —— 运行自定义代码,跨 TUI 与 web-ui 渲染消息`};async function q(e,t,n,r){if(n.length===0)return!0;if(!r&&process.stdout.isTTY&&process.stdin.isTTY)return _e(t,n);let i=[`插件 '${t}' 申请以下高危能力:`,...n.map(e=>` • ${K[e]??e}`)];if(e.stdout(`${i.join(`
114
+ `),e(0)};process.on(`SIGINT`,i)})}const G={"plugin-browser-use":{pkg:`@x-otto/plugin-browser-use-mcp`,capabilities:[`tui.renderer`,`panel.backend`]},"plugin-mobile-use":{pkg:`@x-otto/plugin-mobile-use-mcp`,capabilities:[`tui.renderer`,`panel.backend`]},"plugin-feishu-use":{pkg:`@x-otto/plugin-feishu-use`,capabilities:[]},"plugin-miniprogram-use":{pkg:`@x-otto/plugin-miniprogram-use-mcp`,capabilities:[`tui.renderer`,`panel.backend`]},"plugin-schedule":{pkg:`@x-otto/plugin-schedule-panel`,capabilities:[`tui.renderer`]},"anthropic-provider":{pkg:`@x-otto/plugin-anthropic`,capabilities:[`provider`]},"deepseek-provider":{pkg:`@x-otto/plugin-deepseek`,capabilities:[`provider`]},"oxygen-provider":{pkg:`@x-otto/plugin-oxygen`,capabilities:[`provider`]},"openai-codex-provider":{pkg:`@x-otto/plugin-openai-codex`,capabilities:[`provider`]},"github-copilot-provider":{pkg:`@x-otto/plugin-github-copilot`,capabilities:[`provider`]},"cursor-provider":{pkg:`@x-otto/plugin-cursor`,capabilities:[`provider`]},"wire-protocols":{pkg:`@x-otto/plugin-otto-wire-protocols`,capabilities:[`wire-protocol`]}},K={provider:`provider —— 注册 LLM provider,将处理你的凭据与请求流量`,tools:`tools —— 注册可被 agent 调用的工具`,"tui.renderer":`tui.renderer —— 在 TUI 进程内运行自定义渲染代码`,network:`network —— 发起任意网络请求`,"wire-protocol":`wire-protocol —— 注册 wire 协议实现,可截获全部模型请求与响应流量(含凭据)`,"a2ui.component":`a2ui.component —— 在 TUI 进程内运行自定义渲染组件代码`,"panel.backend":`panel.backend —— 面板可驱动有副作用的后端业务操作`,"input.resolver":`input.resolver —— 在提交时展开 @ 引用,把内容注入 prompt`,"a2ui.renderer":`a2ui.renderer —— 运行自定义代码,跨 TUI 与 web-ui 渲染消息`};async function q(e,t,n,r){if(n.length===0)return!0;if(!r&&process.stdout.isTTY&&process.stdin.isTTY)return ye(t,n);let i=[`插件 '${t}' 申请以下高危能力:`,...n.map(e=>` • ${K[e]??e}`)];if(e.stdout(`${i.join(`
115
115
  `)}\n`),r)return e.stdout(`(--yes 已跳过交互确认,继续安装)
116
- `),!0;let a=se({input:process.stdin,output:process.stdout});try{let e=(await a.question(`继续安装并授予以上能力?[y/N] `)).trim().toLowerCase();return e===`y`||e===`yes`}finally{a.close()}}function _e(e,t){let n={current:null},r=[`插件 '${e}' 申请以下高危能力:`,...t.map(e=>` • ${K[e]??e}`),``,`继续安装即视为同意授予以上能力(等价于 /trust plugin 的确认动作)。`];return ue({routes:ce.createElement(le,{title:`高危能力确认`,message:r,confirmLabel:`继续安装`,cancelLabel:`取消`,confirmColor:`amber`,onConfirm:()=>n.current?.(!0),onCancel:()=>n.current?.(!1)})},e=>{n.current=e.resolve})}function J(e,t){t.length!==0&&(y(e.dir),f(e.dir,t))}async function Y(e){let t=new k({projectConfigPath:C(x,b)});await t.load(),await _(t,e)}async function ve(e){try{let{registries:t}=await new i({configDir:S,ottoHome:x,officialSource:a(process.env.OTTO_OFFICIAL_REGISTRY_URL??`https://x-ottojs.github.io/otto-registry/registry.json`)}).loadAll();for(let n of t){let t=n.entries.find(t=>t.id===e);if(t&&t.type===`plugin`&&!t.subPath)return t.source}return null}catch{return null}}async function ye(e,n){let i=B(e,`--link`),a=i?void 0:e.find(e=>!e.startsWith(`-`)),o=e.includes(`--project`),s=e.includes(`--repo`),c=e.includes(`--yes`)||e.includes(`-y`);if(i){if(o||s)return n.stderr("--link 仅支持用户级安装(`~/.otto/plugins/`),不接受 --project/--repo。\n"),1;let e=w(i),t=g(e);if(!await q(n,t.length>0?V(e):``,t,c))return n.stdout(`已取消安装。
117
- `),1;try{let r=await p(e);J(r,t),await Y({id:r.id,source:`dir`,sourceSpec:e,version:r.version,scope:`global`,capabilities:t}),n.stdout(`${r.created?`已安装`:`已是最新`}插件 '${r.id}' → ${r.dir}\n 来源: 本地 (${e})(软链)\n 用户级插件默认受信任。otto 内 /extension reload 生效。\n`+(t.length>0?` 已授予高危能力:${t.join(`, `)}(等价于 /trust plugin ${r.id} 的确认动作,\n 可随时用 /extension → ${r.id} → 撤销信任 关闭)。\n`:``));for(let e of r.compileWarnings)n.stderr(` ✗ ${e}\n`);return 0}catch(e){return n.stderr(`插件安装失败:${e.message}\n`),1}}if(!a)return n.stderr(`otto extension install <name|package> [--project] [--repo] 或 --link <dir>
118
- `),1;let u=G[a]?.pkg??await ve(a)??a;try{let e=o?C(process.cwd(),`.otto`):s?(()=>{let e=d(process.cwd());return e?C(e,`.otto`):(n.stderr(`--repo 要求当前目录在 pnpm workspace 内(向上未找到 pnpm-workspace.yaml)。
119
- `),``)})():C(t(),`.otto`);if(s&&!e)return 1;let i=[];if(!o&&!s){let e=v(u,process.cwd());e&&(i=g(e))}if(!await q(n,a,i,c))return n.stdout(`已取消安装。
120
- `),1;let p=!o&&!s?v(u,process.cwd()):void 0;if(!o&&!s&&!p){let e=new r;try{let t=await e.install(u,{global:!0,as:`plugin`,postInstallExecutor:l});if(!t.success)return n.stderr(`插件安装失败:${t.error??`未知错误`}\n`),1;let r=t.targetPath?g(t.targetPath):[];if(r.length>0&&t.targetPath)try{y(t.targetPath),f(t.targetPath,r)}catch{}return await Y({id:t.name??u,source:`npm`,sourceSpec:u,version:t.manifest?.version,scope:`global`,capabilities:r}),n.stdout(`已安装插件 '${t.name??u}' → ${t.targetPath}\n 来源: npm registry(${u})\n`+(r.length>0?` 已授予高危能力:${r.join(`, `)}(等价于 /trust plugin ${t.name} 的确认动作)。\n`:``)+` 用户级插件默认受信任。otto 内 /extension reload 生效。
121
- `),0}catch(e){return n.stderr(`插件安装失败:${e.message}\n`),1}}let h=await m({pkg:u,ottoDir:e,cwd:process.cwd()});!o&&!s&&J(h,i),await Y({id:h.id,source:`npm`,sourceSpec:u,version:h.version,scope:o?`project`:s?`repository`:`global`,capabilities:i}),n.stdout(`${h.created?`已安装`:`已是最新`}插件 '${h.id}' → ${h.dir}\n 来源包: ${u}(软链 → ${h.packageDir})\n`+(i.length>0?` 已授予高危能力:${i.join(`, `)}(等价于 /trust plugin ${h.id} 的确认动作,\n 可随时用 /extension → ${h.id} → 撤销信任 关闭)。\n`:``)+(o?` 项目级插件需先信任方激活:在 otto 内运行 /trust plugin ${h.id}(或 /extension)信任该插件。\n`:s?` 仓库级插件需先信任方激活:在 otto 内运行 /extension trust plugin ${h.id}(或打开 /extension 面板)信任该插件。\n`:a===`plugin-feishu-use`?` 用户级插件默认受信任(feishu-use 不提供 MCP 工具,是独立长连接进程,见下)。
122
- `:` 用户级插件默认受信任,工具 mcp__${h.id}__* 即刻可用(otto 内 /extension reload 生效)。\n`)+(a===`plugin-browser-use`?` 默认 auto:有 relay 用真 Chrome,否则本地 headless(自动探测浏览器,零配置)。
116
+ `),!0;let a=le({input:process.stdin,output:process.stdout});try{let e=(await a.question(`继续安装并授予以上能力?[y/N] `)).trim().toLowerCase();return e===`y`||e===`yes`}finally{a.close()}}function ye(e,t){let n={current:null},r=[`插件 '${e}' 申请以下高危能力:`,...t.map(e=>` • ${K[e]??e}`),``,`继续安装即视为同意授予以上能力(等价于 /trust plugin 的确认动作)。`];return fe({routes:ue.createElement(de,{title:`高危能力确认`,message:r,confirmLabel:`继续安装`,cancelLabel:`取消`,confirmColor:`amber`,onConfirm:()=>n.current?.(!0),onCancel:()=>n.current?.(!1)})},e=>{n.current=e.resolve})}function J(e,t){t.length!==0&&(b(e.dir),f(e.dir,t))}async function Y(e){let t=new k({projectConfigPath:C(S,x)});await t.load(),await ee(t,e)}async function be(e){try{let{registries:t}=await new i({configDir:re,ottoHome:S,officialSource:a(process.env.OTTO_OFFICIAL_REGISTRY_URL??`https://x-ottojs.github.io/otto-registry/registry.json`)}).loadAll();for(let n of t){let t=n.entries.find(t=>t.id===e);if(t&&t.type===`plugin`&&!t.subPath)return t.source}return null}catch{return null}}async function xe(e,n){let i=B(e,`--link`),a=i?void 0:e.find(e=>!e.startsWith(`-`)),o=e.includes(`--project`),s=e.includes(`--repo`),c=e.includes(`--yes`)||e.includes(`-y`);if(i){if(o||s)return n.stderr("--link 仅支持用户级安装(`~/.otto/plugins/`),不接受 --project/--repo。\n"),1;let e=w(i),t=v(e);if(!await q(n,t.length>0?V(e):``,t,c))return n.stdout(`已取消安装。
117
+ `),1;try{let r=await h(e);J(r,t),await Y({id:r.id,source:`dir`,sourceSpec:e,version:r.version,scope:`global`,capabilities:t}),n.stdout(`${r.created?`已安装`:`已是最新`}插件 '${r.id}' → ${r.dir}\n 来源: 本地 (${e})(软链)\n 用户级插件默认受信任。otto 内 /extension reload 生效。\n`+(t.length>0?` 已授予高危能力:${t.join(`, `)}(等价于 /trust plugin ${r.id} 的确认动作,\n 可随时用 /extension → ${r.id} → 撤销信任 关闭)。\n`:``));for(let e of r.compileWarnings)n.stderr(` ✗ ${e}\n`);return 0}catch(e){return n.stderr(`插件安装失败:${e.message}\n`),1}}if(!a)return n.stderr(`otto extension install <name|package> [--project] [--repo] 或 --link <dir>
118
+ `),1;let u=G[a]?.pkg??await be(a)??a;try{let e=o?C(process.cwd(),`.otto`):s?(()=>{let e=d(process.cwd());return e?C(e,`.otto`):(n.stderr(`--repo 要求当前目录在 pnpm workspace 内(向上未找到 pnpm-workspace.yaml)。
119
+ `),``)})():C(t(),`.otto`);if(s&&!e)return 1;let i=[];if(!o&&!s){let e=y(u,process.cwd());e&&(i=v(e))}if(!await q(n,a,i,c))return n.stdout(`已取消安装。
120
+ `),1;let p=!o&&!s?y(u,process.cwd()):void 0;if(!o&&!s&&!p){let e=new r;try{let t=await e.install(u,{global:!0,as:`plugin`,postInstallExecutor:l});if(!t.success)return n.stderr(`插件安装失败:${t.error??`未知错误`}\n`),1;let r=t.targetPath?v(t.targetPath):[];if(r.length>0&&t.targetPath)try{b(t.targetPath),f(t.targetPath,r)}catch{}return await Y({id:t.name??u,source:`npm`,sourceSpec:u,version:t.manifest?.version,scope:`global`,capabilities:r}),n.stdout(`已安装插件 '${t.name??u}' → ${t.targetPath}\n 来源: npm registry(${u})\n`+(r.length>0?` 已授予高危能力:${r.join(`, `)}(等价于 /trust plugin ${t.name} 的确认动作)。\n`:``)+` 用户级插件默认受信任。otto 内 /extension reload 生效。
121
+ `),0}catch(e){return n.stderr(`插件安装失败:${e.message}\n`),1}}let m=await g({pkg:u,ottoDir:e,cwd:process.cwd()});!o&&!s&&J(m,i),await Y({id:m.id,source:`npm`,sourceSpec:u,version:m.version,scope:o?`project`:s?`repository`:`global`,capabilities:i}),n.stdout(`${m.created?`已安装`:`已是最新`}插件 '${m.id}' → ${m.dir}\n 来源包: ${u}(软链 → ${m.packageDir})\n`+(i.length>0?` 已授予高危能力:${i.join(`, `)}(等价于 /trust plugin ${m.id} 的确认动作,\n 可随时用 /extension → ${m.id} → 撤销信任 关闭)。\n`:``)+(o?` 项目级插件需先信任方激活:在 otto 内运行 /trust plugin ${m.id}(或 /extension)信任该插件。\n`:s?` 仓库级插件需先信任方激活:在 otto 内运行 /extension trust plugin ${m.id}(或打开 /extension 面板)信任该插件。\n`:a===`plugin-feishu-use`?` 用户级插件默认受信任(feishu-use 不提供 MCP 工具,是独立长连接进程,见下)。
122
+ `:` 用户级插件默认受信任,工具 mcp__${m.id}__* 即刻可用(otto 内 /extension reload 生效)。\n`)+(a===`plugin-browser-use`?` 默认 auto:有 relay 用真 Chrome,否则本地 headless(自动探测浏览器,零配置)。
123
123
  relay native host 首次 /extension reload 由插件 setup 钩子自动注入(前提 cargo build --release -p otto-relay);
124
124
  之后 chrome://extensions 加载 packages/extension/dist 即可接管真实 Chrome。
125
125
  `:``)+(a===`plugin-mobile-use`?` 默认 Android:零配置自动探测首个已连接设备/模拟器(\`adb devices\` 可见即用)。
@@ -130,8 +130,8 @@ bin/
130
130
  真机连接默认拒绝(安全 fail-closed):需显式设 {"real_device_confirmation":"allow"}
131
131
  才允许连接真实 Android/iOS 设备(模拟器/Simulator 不受此限制,零配置可用)。
132
132
  `:``)+(a===`plugin-feishu-use`?` feishu-use 是一个 7×24 独立长连接进程,不由 otto 插件系统管理其常驻生命周期:
133
- 1) otto feishu config init # 交互式配置向导(${o?`写入 ${h.dir}/../../feishu-use.json`:s?`写入 <workspaceRoot>/.otto/feishu-use.json`:`默认写入项目级 <.otto>/feishu-use.json;--global 写 ~/.otto/`})\n 2) otto feishu config get # 核对配置(appSecret 自动脱敏)
134
- 3) node ${h.dir}/bin/bridge-cli.mjs # 前台启动(或用 launchd/systemd/pm2 常驻)\n 也可用 \`otto feishu config set <key> <value>\` 随时修改单个字段。
133
+ 1) otto feishu config init # 交互式配置向导(${o?`写入 ${m.dir}/../../feishu-use.json`:s?`写入 <workspaceRoot>/.otto/feishu-use.json`:`默认写入项目级 <.otto>/feishu-use.json;--global 写 ~/.otto/`})\n 2) otto feishu config get # 核对配置(appSecret 自动脱敏)
134
+ 3) node ${m.dir}/bin/bridge-cli.mjs # 前台启动(或用 launchd/systemd/pm2 常驻)\n 也可用 \`otto feishu config set <key> <value>\` 随时修改单个字段。
135
135
  配置按作用域自动选择(project > repo > user,first-found wins)。
136
136
  详见 extensions/plugin-feishu-use/README.md(含"用来迭代 otto 框架"专项章节)。
137
137
  `:``)+(a===`plugin-miniprogram-use`?` miniprogram-use 需要两步配置:
@@ -142,16 +142,17 @@ bin/
142
142
  可选:在 ~/.otto/miniprogram-use.json 配置 admin_endpoints(需真实抓包获得)后,
143
143
  才会注册后台管理只读工具(get_members/get_permission_status/
144
144
  get_experience_qrcode)。详见 docs/rfc/RFC-121-miniprogram-use.md。
145
- `:``));for(let e of h.compileWarnings)n.stderr(` ✗ ${e}\n`);return 0}catch(e){return n.stderr(`插件安装失败:${e.message}\n`),1}}async function be(e,n){let i=e.find(e=>!e.startsWith(`-`)),a=ee({workspaceDir:process.cwd(),workspaceRoot:d(process.cwd()),scope:`all`}),s=i?a.filter(e=>e.name===i):a;if(s.length===0)return n.stdout(i?`未找到已安装且带版本号的扩展 "${i}"。\n`:`未发现带版本号的已安装扩展(skill/plugin)。
145
+ `:``));for(let e of m.compileWarnings)n.stderr(` ✗ ${e}\n`);return 0}catch(e){return n.stderr(`插件安装失败:${e.message}\n`),1}}async function Se(e,n){let i=e.find(e=>!e.startsWith(`-`)),a=te({workspaceDir:process.cwd(),workspaceRoot:d(process.cwd()),scope:`all`}),s=i?a.filter(e=>e.name===i):a;if(s.length===0)return n.stdout(i?`未找到已安装且带版本号的扩展 "${i}"。\n`:`未发现带版本号的已安装扩展(skill/plugin)。
146
146
  `),i?1:0;let c=await o(s),u=c.filter(e=>e.newer);if(u.length===0){n.stdout(`全部扩展已是最新版本。
147
- `);for(let e of c)e.error&&n.stdout(` [${e.type}] ${e.name} v${e.installed} — ${e.error}\n`);return 0}n.stdout(`发现 ${u.length} 个可更新扩展,开始重装:\n`);let f=new r,p=0,m=0;for(let e of u){let r=s.find(t=>t.name===e.name&&t.type===e.type),i=r?xe(r):void 0;if(!i){n.stderr(` ✗ ${e.name}:无法确定重装来源(.otto-install.json 缺失或来源类型不支持自动重装)\n`),m++;continue}let a=r.path.startsWith(C(t(),`.otto`)+ne);try{let t=await f.install(i,{workspaceDir:process.cwd(),global:a,force:!0,as:e.type,postInstallExecutor:l});if(t.success)n.stdout(` ✓ ${e.name} v${e.installed} → v${e.latest??`?`}\n`),p++;else{let r=t.failureKind===`conflict`?`(已存在且未覆盖)`:``;n.stderr(` ✗ ${e.name}:${t.error??`重装失败`}${r}\n`),m++}}catch(t){n.stderr(` ✗ ${e.name}:${t.message}\n`),m++}}return n.stdout(`\n完成:${p} 个已更新${m>0?`,${m} 个失败`:``}。\n`),m>0?1:0}function xe(e){let t=C(e.path,`.otto-install.json`);if(T(t))try{let e=JSON.parse(O(t,`utf-8`));return e.sourceType===`npm`&&e.sourceUrl?.startsWith(`npm:`)?e.sourceUrl.slice(4):e.sourceType===`git`&&e.sourceUrl?e.sourceUrl:void 0}catch{return}}function X(){return new k({projectConfigPath:C(x,b)})}function Z(n){return e({cwd:process.cwd(),homedir:n??t()}).map(e=>({id:e.id,name:e.manifest.name??e.id,scope:e.scope}))}async function Q(e,t){let n=e.find(e=>!e.startsWith(`-`));if(!n)return t.stderr(`otto extension enable <id>
148
- `),1;let r=X();await r.load();let i=new Set(r.get(`disabled_plugins`)??[]);return i.has(n)?(i.delete(n),await r.persist({disabled_plugins:[...i]}),t.stdout(`已启用插件 '${n}'。运行中的 otto 会话内执行 /extension reload 或重启生效。\n`),0):(t.stdout(`插件 '${n}' 当前已是启用状态。\n`),0)}async function Se(e,t){let n=e.find(e=>!e.startsWith(`-`));if(!n)return t.stderr(`otto extension disable <id>
149
- `),1;if(Z().find(e=>e.id===n)?.scope===`builtin`)return t.stderr(`插件 "${n}" 是 otto 内置插件,恒定信任+启用,无法禁用。\n`),1;let r=X();await r.load();let i=new Set(r.get(`disabled_plugins`)??[]);return i.has(n)?(t.stdout(`插件 '${n}' 当前已是禁用状态。\n`),0):(i.add(n),await r.persist({disabled_plugins:[...i]}),t.stdout(`已禁用插件 '${n}'。运行中的 otto 会话内执行 /extension reload 或重启生效(CLI 路径不杀运行中进程,reload 时统一清理)。\n`),0)}async function Ce(e,t){let n=X();await n.load();let r=new Set(n.get(`disabled_plugins`)??[]),i=Z();if(i.length===0)return t.stdout(`(未发现任何插件)
150
- `),0;for(let e of i){let n=e.scope===`builtin`||!r.has(e.id)?`enabled `:`disabled`,i=e.scope===`builtin`?` (builtin, readonly)`:` (${e.scope})`;t.stdout(` ${n} ${e.id}${i} ${e.name}\n`)}return 0}async function we(e,t){let n=e.find(e=>!e.startsWith(`-`)),r=e.includes(`--yes`)||e.includes(`-y`);if(!n)return t.stderr(`otto extension purge <id> [--yes]
151
- `),1;let i=C(x,`plugin-state`,n);if(!T(i))return t.stdout(`插件 '${n}' 无持久化状态(${i} 不存在)。\n`),0;if(!r){if(!process.stdout.isTTY)return t.stderr(`插件 '${n}' 有持久化状态,删除不可逆——非交互终端请显式传 --yes。\n`),1;let{createInterface:e}=await import(`node:readline/promises`),r=e({input:process.stdin,output:process.stdout});try{let e=(await r.question(`确认删除插件 '${n}' 的全部持久化状态?此操作不可逆 [y/N] `)).trim().toLowerCase();if(e!==`y`&&e!==`yes`)return t.stdout(`已取消。
152
- `),0}finally{r.close()}}let a=h(n);return t.stdout(a.removed?`已清除插件 '${n}' 的持久化状态。\n`:`清除插件 '${n}' 状态失败(目录可能仍存在)。\n`),a.removed?0:1}function $(n){return e({cwd:process.cwd(),homedir:t()}).find(e=>e.id===n)}async function Te(e,t){let r=e.find(e=>!e.startsWith(`-`));if(!r)return t.stderr(`otto extension trust: 需要插件 id(用法:otto extension trust <id> [--grant <caps>])
153
- `),1;let i=$(r);if(!i)return t.stderr(`otto extension trust: 未找到插件 "${r}"(先 otto extension list 查看已安装插件)\n`),1;let a=(i.manifest?.capabilities??[]).filter(e=>n.includes(e)),o=e.find(e=>e.startsWith(`--grant=`)),s=e.indexOf(`--grant`),c=o?o.slice(8):s>=0?e[s+1]:void 0,l=e.includes(`--grant-declared`),d=[];if(l)d=[...a];else if(c){d=c.split(`,`).map(e=>e.trim()).filter(Boolean);let e=d.filter(e=>!n.includes(e));if(e.length>0)return t.stderr(`otto extension trust: 未知能力 ${e.join(`, `)}\n 可用高危能力:${n.join(`, `)}\n`),1;let i=d.filter(e=>!a.includes(e));if(i.length>0)return t.stderr(`otto extension trust: 插件 "${r}" 未声明能力 ${i.join(`, `)}——授予无效。\n 该插件声明的高危能力:${a.length?a.join(`, `):`(无)`}\n`),1}y(i.dir),d.length>0&&f(i.dir,d);let p=u(i);return t.stdout(`✓ 已信任 ${r}(${i.scope})\n`),d.length>0&&t.stdout(` 已授予能力:${d.join(`, `)}\n`),p.pendingCapabilities.length>0&&t.stdout(` ⚠ 仍待授权:${p.pendingCapabilities.join(`, `)}\n 这些能力未授予前对应贡献点不会注册(fail-closed)。\n 授予:otto extension trust ${r} --grant ${p.pendingCapabilities.join(`,`)}\n`),0}async function Ee(e,t){let n=e.find(e=>!e.startsWith(`-`));if(!n)return t.stderr(`otto extension untrust: 需要插件 id
154
- `),1;let r=$(n);return r?(te(r.dir),t.stdout(`✓ 已撤销信任 ${n}\n`),0):(t.stderr(`otto extension untrust: 未找到插件 "${n}"\n`),1)}function De(e,t){let[n,...r]=e;switch(n){case`create`:return I(r,t);case`build`:return z(r,t);case`dev`:return ge(r,t);case`install`:return ye(r,t);case`update`:return be(r,t);case`enable`:return Q(r,t);case`disable`:return Se(r,t);case`list`:return Ce(r,t);case`purge`:return we(r,t);case`trust`:return Te(r,t);case`untrust`:return Ee(r,t);default:return t.stdout(`otto extension <命令>
147
+ `);for(let e of c)e.error&&n.stdout(` [${e.type}] ${e.name} v${e.installed} — ${e.error}\n`);return 0}n.stdout(`发现 ${u.length} 个可更新扩展,开始重装:\n`);let f=new r,p=0,m=0;for(let e of u){let r=s.find(t=>t.name===e.name&&t.type===e.type),i=r?Ce(r):void 0;if(!i){n.stderr(` ✗ ${e.name}:无法确定重装来源(.otto-install.json 缺失或来源类型不支持自动重装)\n`),m++;continue}let a=r.path.startsWith(C(t(),`.otto`)+T);try{let t=await f.install(i,{workspaceDir:process.cwd(),global:a,force:!0,as:e.type,postInstallExecutor:l});if(t.success)n.stdout(` ✓ ${e.name} v${e.installed} → v${e.latest??`?`}\n`),p++;else{let r=t.failureKind===`conflict`?`(已存在且未覆盖)`:``;n.stderr(` ✗ ${e.name}:${t.error??`重装失败`}${r}\n`),m++}}catch(t){n.stderr(` ✗ ${e.name}:${t.message}\n`),m++}}return n.stdout(`\n完成:${p} 个已更新${m>0?`,${m} 个失败`:``}。\n`),m>0?1:0}function Ce(e){let t=C(e.path,`.otto-install.json`);if(E(t))try{let e=JSON.parse(O(t,`utf-8`));return e.sourceType===`npm`&&e.sourceUrl?.startsWith(`npm:`)?e.sourceUrl.slice(4):e.sourceType===`git`&&e.sourceUrl?e.sourceUrl:void 0}catch{return}}function X(){return new k({projectConfigPath:C(S,x)})}function Z(n){return e({cwd:process.cwd(),homedir:n??t()}).map(e=>({id:e.id,name:e.manifest.name??e.id,scope:e.scope}))}async function we(e,t){let n=e.find(e=>!e.startsWith(`-`));if(!n)return t.stderr(`otto extension enable <id>
148
+ `),1;let r=X();await r.load();let i=new Set(r.get(`disabled_plugins`)??[]);return i.has(n)?(i.delete(n),await r.persist({disabled_plugins:[...i]}),t.stdout(`已启用插件 '${n}'。运行中的 otto 会话内执行 /extension reload 或重启生效。\n`),0):(t.stdout(`插件 '${n}' 当前已是启用状态。\n`),0)}async function Q(e,t){let n=e.find(e=>!e.startsWith(`-`));if(!n)return t.stderr(`otto extension disable <id>
149
+ `),1;if(Z().find(e=>e.id===n)?.scope===`builtin`)return t.stderr(`插件 "${n}" 是 otto 内置插件,恒定信任+启用,无法禁用。\n`),1;let r=X();await r.load();let i=new Set(r.get(`disabled_plugins`)??[]);return i.has(n)?(t.stdout(`插件 '${n}' 当前已是禁用状态。\n`),0):(i.add(n),await r.persist({disabled_plugins:[...i]}),t.stdout(`已禁用插件 '${n}'。运行中的 otto 会话内执行 /extension reload 或重启生效(CLI 路径不杀运行中进程,reload 时统一清理)。\n`),0)}async function Te(e,t){let n=X();await n.load();let r=new Set(n.get(`disabled_plugins`)??[]),i=Z();if(i.length===0)return t.stdout(`(未发现任何插件)
150
+ `),0;for(let e of i){let n=e.scope===`builtin`||!r.has(e.id)?`enabled `:`disabled`,i=e.scope===`builtin`?` (builtin, readonly)`:` (${e.scope})`;t.stdout(` ${n} ${e.id}${i} ${e.name}\n`)}return 0}async function Ee(e,t){let n=e.find(e=>!e.startsWith(`-`)),r=e.includes(`--yes`)||e.includes(`-y`);if(!n)return t.stderr(`otto extension purge <id> [--yes]
151
+ `),1;let i=C(S,`plugin-state`,n);if(!E(i))return t.stdout(`插件 '${n}' 无持久化状态(${i} 不存在)。\n`),0;if(!r){if(!process.stdout.isTTY)return t.stderr(`插件 '${n}' 有持久化状态,删除不可逆——非交互终端请显式传 --yes。\n`),1;let{createInterface:e}=await import(`node:readline/promises`),r=e({input:process.stdin,output:process.stdout});try{let e=(await r.question(`确认删除插件 '${n}' 的全部持久化状态?此操作不可逆 [y/N] `)).trim().toLowerCase();if(e!==`y`&&e!==`yes`)return t.stdout(`已取消。
152
+ `),0}finally{r.close()}}let a=_(n);return t.stdout(a.removed?`已清除插件 '${n}' 的持久化状态。\n`:`清除插件 '${n}' 状态失败(目录可能仍存在)。\n`),a.removed?0:1}function $(n){return e({cwd:process.cwd(),homedir:t()}).find(e=>e.id===n)}async function De(e,t){let r=e.find(e=>!e.startsWith(`-`));if(!r)return t.stderr(`otto extension trust: 需要插件 id(用法:otto extension trust <id> [--grant <caps>])
153
+ `),1;let i=$(r);if(!i)return t.stderr(`otto extension trust: 未找到插件 "${r}"(先 otto extension list 查看已安装插件)\n`),1;let a=(i.manifest?.capabilities??[]).filter(e=>n.includes(e)),o=e.find(e=>e.startsWith(`--grant=`)),s=e.indexOf(`--grant`),c=o?o.slice(8):s>=0?e[s+1]:void 0,l=e.includes(`--grant-declared`),d=[];if(l)d=[...a];else if(c){d=c.split(`,`).map(e=>e.trim()).filter(Boolean);let e=d.filter(e=>!n.includes(e));if(e.length>0)return t.stderr(`otto extension trust: 未知能力 ${e.join(`, `)}\n 可用高危能力:${n.join(`, `)}\n`),1;let i=d.filter(e=>!a.includes(e));if(i.length>0)return t.stderr(`otto extension trust: 插件 "${r}" 未声明能力 ${i.join(`, `)}——授予无效。\n 该插件声明的高危能力:${a.length?a.join(`, `):`(无)`}\n`),1}b(i.dir);let h=i.manifest.version;d.length>0&&(h?p(i.dir,d,h):(t.stderr(`otto extension trust: 插件 "${i.id}" manifest 无版本字段——授权不绑定版本,后续插件更新后需重新信任(R6' 版本感知无法生效)。
154
+ `),f(i.dir,d)));let g=u(i);return t.stdout(`✓ 已信任 ${r}(${i.scope})\n`),d.length>0&&(t.stdout(` 已授予能力:${d.join(`, `)}\n`),h&&(m(i.dir,h).length===0?t.stderr(` 版本感知授权未生效(读回为空)——插件 "${i.id}" 的高危能力在版本不匹配时将被 fail-closed 拒绝,请确认 manifest 版本与授权版本一致。\n`):t.stdout(` ✓ 版本感知已生效(授权绑定 manifest v${h},更新后需重新信任)\n`))),g.pendingCapabilities.length>0&&t.stdout(` 仍待授权:${g.pendingCapabilities.join(`, `)}\n 这些能力未授予前对应贡献点不会注册(fail-closed)。\n 授予:otto extension trust ${r} --grant ${g.pendingCapabilities.join(`,`)}\n`),0}async function Oe(e,t){let n=e.find(e=>!e.startsWith(`-`));if(!n)return t.stderr(`otto extension untrust: 需要插件 id
155
+ `),1;let r=$(n);return r?(ne(r.dir),t.stdout(`✓ 已撤销信任 ${n}\n`),0):(t.stderr(`otto extension untrust: 未找到插件 "${n}"\n`),1)}function ke(e,t){let[n,...r]=e;switch(n){case`create`:return L(r,t);case`build`:return ge(r,t);case`dev`:return ve(r,t);case`install`:return xe(r,t);case`update`:return Se(r,t);case`enable`:return we(r,t);case`disable`:return Q(r,t);case`list`:return Te(r,t);case`purge`:return Ee(r,t);case`trust`:return De(r,t);case`untrust`:return Oe(r,t);default:return t.stdout(`otto extension <命令>
155
156
  create <name> 生成插件骨架(命令 + 面板 + manifest)
156
157
  install <name|pkg> [--project] [--repo] [--yes] 从 npm 安装插件(别名 browser-use/mobile-use/
157
158
  feishu-use/miniprogram-use/anthropic-provider/
@@ -174,5 +175,5 @@ bin/
174
175
  /trust plugin 同一份落盘。未授予的能力对应贡献点
175
176
  不会注册(fail-closed)
176
177
  untrust <id> 撤销信任
177
- `),n?1:0}}export{H as a,W as i,G as n,V as o,R as r,F as s,De as t};
178
- //# sourceMappingURL=extension-1-lxuerZ.js.map
178
+ `),n?1:0}}export{H as a,W as i,G as n,V as o,z as r,I as s,ke as t};
179
+ //# sourceMappingURL=extension-DVEml5IN.js.map