@armadra/agent 0.2.1 → 0.4.0

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 (232) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/README.md +142 -49
  3. package/dist/agent/retry.d.ts +1 -1
  4. package/dist/agent/retry.js +2 -1
  5. package/dist/agent/session-cache.js +6 -3
  6. package/dist/agent/session-classifier.d.ts +18 -0
  7. package/dist/agent/session-classifier.js +103 -0
  8. package/dist/agent/session-core.d.ts +13 -0
  9. package/dist/agent/session-state.js +2 -1
  10. package/dist/agent/session-sync.js +9 -2
  11. package/dist/agent/session-tools.js +24 -3
  12. package/dist/agent/session.d.ts +3 -0
  13. package/dist/agent/session.js +20 -10
  14. package/dist/agent/tool-runner.js +15 -2
  15. package/dist/agent/types.d.ts +9 -1
  16. package/dist/ai/apis/anthropic-messages.js +3 -2
  17. package/dist/ai/apis/google-generative-ai.js +3 -2
  18. package/dist/ai/apis/openai-completions.js +3 -2
  19. package/dist/ai/apis/openai-responses.js +5 -3
  20. package/dist/ai/cache/fingerprint.d.ts +1 -1
  21. package/dist/ai/cache/fingerprint.js +1 -1
  22. package/dist/ai/cache/reporting.js +2 -1
  23. package/dist/ai/http.d.ts +28 -6
  24. package/dist/ai/http.js +41 -8
  25. package/dist/ai/providers/channels.d.ts +51 -0
  26. package/dist/ai/providers/channels.js +96 -0
  27. package/dist/ai/providers/enrich.d.ts +34 -0
  28. package/dist/ai/providers/enrich.js +86 -0
  29. package/dist/ai/providers/models-dev-cache.d.ts +53 -0
  30. package/dist/ai/providers/models-dev-cache.js +147 -0
  31. package/dist/ai/providers/models-dev.d.ts +99 -0
  32. package/dist/ai/providers/models-dev.js +315 -0
  33. package/dist/ai/providers/registry.d.ts +42 -4
  34. package/dist/ai/providers/registry.js +195 -34
  35. package/dist/ai/providers/suggest.d.ts +18 -0
  36. package/dist/ai/providers/suggest.js +72 -0
  37. package/dist/ai/sse.d.ts +6 -2
  38. package/dist/ai/sse.js +22 -2
  39. package/dist/ai/types.d.ts +30 -3
  40. package/dist/bundle/ama.cjs +17893 -10698
  41. package/dist/cli/args.d.ts +23 -5
  42. package/dist/cli/args.js +113 -13
  43. package/dist/cli/bootstrap.js +28 -3
  44. package/dist/cli/codemode-notice.d.ts +20 -0
  45. package/dist/cli/codemode-notice.js +55 -0
  46. package/dist/cli/compose-providers.js +3 -0
  47. package/dist/cli/compose-session.d.ts +8 -0
  48. package/dist/cli/compose-session.js +32 -1
  49. package/dist/cli/compose-store.d.ts +1 -1
  50. package/dist/cli/compose-store.js +3 -1
  51. package/dist/cli/compose.d.ts +17 -5
  52. package/dist/cli/compose.js +53 -19
  53. package/dist/cli/default-model.d.ts +38 -1
  54. package/dist/cli/default-model.js +96 -9
  55. package/dist/cli/deps.d.ts +38 -0
  56. package/dist/cli/exit-codes.d.ts +2 -0
  57. package/dist/cli/exit-codes.js +3 -0
  58. package/dist/cli/fake-visibility.d.ts +13 -0
  59. package/dist/cli/fake-visibility.js +26 -0
  60. package/dist/cli/from-prompt.d.ts +18 -0
  61. package/dist/cli/from-prompt.js +49 -0
  62. package/dist/cli/main.d.ts +10 -2
  63. package/dist/cli/main.js +88 -2
  64. package/dist/cli/proxy.d.ts +51 -0
  65. package/dist/cli/proxy.js +135 -0
  66. package/dist/cli/startup-screen.d.ts +29 -0
  67. package/dist/cli/startup-screen.js +47 -0
  68. package/dist/cli/startup-steps.d.ts +1 -1
  69. package/dist/cli/startup-steps.js +20 -13
  70. package/dist/cli/subcommands/config.d.ts +29 -4
  71. package/dist/cli/subcommands/config.js +188 -25
  72. package/dist/cli/subcommands/context.js +5 -1
  73. package/dist/cli/subcommands/doctor.js +12 -1
  74. package/dist/cli/subcommands/init.d.ts +6 -0
  75. package/dist/cli/subcommands/init.js +23 -0
  76. package/dist/cli/subcommands/model-meta.d.ts +16 -0
  77. package/dist/cli/subcommands/model-meta.js +54 -0
  78. package/dist/cli/subcommands/models-cache-probe.js +1 -1
  79. package/dist/cli/subcommands/models-discover.d.ts +18 -6
  80. package/dist/cli/subcommands/models-discover.js +87 -52
  81. package/dist/cli/subcommands/models.js +26 -18
  82. package/dist/cli/subcommands/probe-runner.d.ts +96 -0
  83. package/dist/cli/subcommands/probe-runner.js +264 -0
  84. package/dist/cli/subcommands/providers-list.d.ts +8 -0
  85. package/dist/cli/subcommands/providers-list.js +97 -0
  86. package/dist/cli/subcommands/providers-plan.d.ts +79 -0
  87. package/dist/cli/subcommands/providers-plan.js +215 -0
  88. package/dist/cli/subcommands/providers-probe.d.ts +34 -0
  89. package/dist/cli/subcommands/providers-probe.js +87 -0
  90. package/dist/cli/subcommands/providers.d.ts +28 -0
  91. package/dist/cli/subcommands/providers.js +436 -0
  92. package/dist/cli/subcommands/sessions-export.d.ts +10 -0
  93. package/dist/cli/subcommands/sessions-export.js +59 -0
  94. package/dist/cli/subcommands/sessions-search.d.ts +13 -0
  95. package/dist/cli/subcommands/sessions-search.js +103 -0
  96. package/dist/cli/subcommands/sessions.d.ts +3 -2
  97. package/dist/cli/subcommands/sessions.js +22 -1
  98. package/dist/cli/subcommands/stats.d.ts +17 -0
  99. package/dist/cli/subcommands/stats.js +198 -0
  100. package/dist/cli/system-prompt-arg.d.ts +11 -0
  101. package/dist/cli/system-prompt-arg.js +34 -0
  102. package/dist/codemode/modes.d.ts +4 -11
  103. package/dist/codemode/modes.js +5 -27
  104. package/dist/codemode/tool.d.ts +18 -14
  105. package/dist/codemode/tool.js +71 -28
  106. package/dist/config/init.d.ts +34 -0
  107. package/dist/config/init.js +99 -0
  108. package/dist/config/json-schema.d.ts +15 -0
  109. package/dist/config/json-schema.js +215 -0
  110. package/dist/config/key-docs.d.ts +22 -0
  111. package/dist/config/key-docs.js +105 -0
  112. package/dist/config/merge.d.ts +9 -8
  113. package/dist/config/merge.js +22 -7
  114. package/dist/config/schema.d.ts +1 -1
  115. package/dist/config/schema.js +97 -15
  116. package/dist/config/types.d.ts +57 -11
  117. package/dist/config/types.js +19 -1
  118. package/dist/modes/commands-core.js +6 -5
  119. package/dist/modes/image-input.d.ts +27 -0
  120. package/dist/modes/image-input.js +78 -0
  121. package/dist/modes/interactive/approval-dialog.d.ts +21 -5
  122. package/dist/modes/interactive/approval-dialog.js +106 -27
  123. package/dist/modes/interactive/commands.d.ts +11 -2
  124. package/dist/modes/interactive/commands.js +58 -19
  125. package/dist/modes/interactive/interactive-mode.d.ts +2 -1
  126. package/dist/modes/interactive/interactive-mode.js +70 -82
  127. package/dist/modes/interactive/key-dispatch.d.ts +3 -1
  128. package/dist/modes/interactive/key-dispatch.js +5 -6
  129. package/dist/modes/interactive/line/line-mode.d.ts +1 -0
  130. package/dist/modes/interactive/line/line-mode.js +9 -5
  131. package/dist/modes/interactive/line/line-render.d.ts +4 -0
  132. package/dist/modes/interactive/line/line-render.js +28 -6
  133. package/dist/modes/interactive/message-view.d.ts +48 -9
  134. package/dist/modes/interactive/message-view.js +238 -44
  135. package/dist/modes/interactive/panels.d.ts +18 -0
  136. package/dist/modes/interactive/panels.js +143 -0
  137. package/dist/modes/interactive/pickers.d.ts +23 -2
  138. package/dist/modes/interactive/pickers.js +48 -15
  139. package/dist/modes/interactive/run-indicator.d.ts +51 -0
  140. package/dist/modes/interactive/run-indicator.js +189 -0
  141. package/dist/modes/interactive/startup-header.d.ts +40 -0
  142. package/dist/modes/interactive/startup-header.js +169 -0
  143. package/dist/modes/interactive/startup-ui.d.ts +7 -2
  144. package/dist/modes/interactive/startup-ui.js +41 -11
  145. package/dist/modes/interactive/status-bar.d.ts +18 -15
  146. package/dist/modes/interactive/status-bar.js +98 -56
  147. package/dist/modes/interactive/tool-summary.d.ts +46 -0
  148. package/dist/modes/interactive/tool-summary.js +218 -0
  149. package/dist/modes/interactive/tool-view.d.ts +48 -15
  150. package/dist/modes/interactive/tool-view.js +203 -145
  151. package/dist/modes/print/print-mode.d.ts +33 -4
  152. package/dist/modes/print/print-mode.js +128 -8
  153. package/dist/modes/rpc/commands.js +12 -2
  154. package/dist/permissions/auto-safe.d.ts +60 -0
  155. package/dist/permissions/auto-safe.js +529 -0
  156. package/dist/permissions/classifier.d.ts +64 -0
  157. package/dist/permissions/classifier.js +184 -0
  158. package/dist/permissions/dangerous.d.ts +5 -0
  159. package/dist/permissions/dangerous.js +1 -1
  160. package/dist/permissions/modes.d.ts +30 -0
  161. package/dist/permissions/modes.js +78 -0
  162. package/dist/permissions/pipeline.d.ts +31 -4
  163. package/dist/permissions/pipeline.js +196 -6
  164. package/dist/permissions/protected.d.ts +19 -0
  165. package/dist/permissions/protected.js +74 -0
  166. package/dist/permissions/rules.js +3 -0
  167. package/dist/permissions/types.d.ts +50 -3
  168. package/dist/permissions/types.js +3 -0
  169. package/dist/rpc.d.ts +2 -0
  170. package/dist/sdk.d.ts +9 -3
  171. package/dist/sdk.js +10 -2
  172. package/dist/session/export.d.ts +32 -0
  173. package/dist/session/export.js +187 -0
  174. package/dist/session/projection.js +5 -1
  175. package/dist/session/redact.d.ts +15 -0
  176. package/dist/session/redact.js +55 -0
  177. package/dist/session/reuse.d.ts +33 -0
  178. package/dist/session/reuse.js +86 -0
  179. package/dist/session/scan.d.ts +34 -0
  180. package/dist/session/scan.js +140 -0
  181. package/dist/session/search.d.ts +52 -0
  182. package/dist/session/search.js +211 -0
  183. package/dist/session/stats-aggregate.d.ts +63 -0
  184. package/dist/session/stats-aggregate.js +163 -0
  185. package/dist/session/stats-index.d.ts +26 -0
  186. package/dist/session/stats-index.js +91 -0
  187. package/dist/session/stats-scan.d.ts +54 -0
  188. package/dist/session/stats-scan.js +236 -0
  189. package/dist/session/types.d.ts +2 -0
  190. package/dist/tools/image-file.d.ts +31 -0
  191. package/dist/tools/image-file.js +114 -0
  192. package/dist/tools/presets.d.ts +35 -8
  193. package/dist/tools/presets.js +56 -17
  194. package/dist/tools/read.d.ts +4 -8
  195. package/dist/tools/read.js +15 -64
  196. package/dist/tui/component.d.ts +8 -2
  197. package/dist/tui/component.js +3 -1
  198. package/dist/tui/components/box.d.ts +6 -1
  199. package/dist/tui/components/box.js +16 -6
  200. package/dist/tui/components/card.d.ts +23 -0
  201. package/dist/tui/components/card.js +37 -0
  202. package/dist/tui/components/editor-history.d.ts +6 -0
  203. package/dist/tui/components/editor-history.js +45 -0
  204. package/dist/tui/components/editor-paste.d.ts +1 -1
  205. package/dist/tui/components/editor-paste.js +4 -4
  206. package/dist/tui/components/editor.d.ts +11 -5
  207. package/dist/tui/components/editor.js +52 -58
  208. package/dist/tui/components/key-value.d.ts +3 -0
  209. package/dist/tui/components/key-value.js +16 -6
  210. package/dist/tui/components/loader.d.ts +37 -7
  211. package/dist/tui/components/loader.js +84 -21
  212. package/dist/tui/components/markdown.d.ts +5 -1
  213. package/dist/tui/components/markdown.js +45 -21
  214. package/dist/tui/components/meter.d.ts +3 -3
  215. package/dist/tui/components/meter.js +13 -11
  216. package/dist/tui/components/select-list.d.ts +23 -1
  217. package/dist/tui/components/select-list.js +76 -13
  218. package/dist/tui/glyphs.d.ts +68 -0
  219. package/dist/tui/glyphs.js +114 -0
  220. package/dist/tui/theme.d.ts +30 -6
  221. package/dist/tui/theme.js +103 -17
  222. package/dist/tui.d.ts +4 -2
  223. package/dist/tui.js +3 -1
  224. package/docs/codemode.md +23 -9
  225. package/docs/hooks.md +10 -10
  226. package/docs/permissions.md +148 -0
  227. package/docs/providers.md +241 -3
  228. package/docs/rpc.md +38 -38
  229. package/docs/session-format.md +3 -2
  230. package/docs/sessions.md +134 -0
  231. package/docs/tui.md +140 -61
  232. package/package.json +3 -1
@@ -0,0 +1,436 @@
1
+ /**
2
+ * `ama providers add|list|channels|remove|refresh`(docs/providers.md「一键接入」「渠道」)。
3
+ *
4
+ * - add:只给 baseUrl 与 key,列出中转的模型(`GET /models`)、用 models.dev 补元数据、可选逐渠道
5
+ * 探测,把渠道与模型追加进用户级 config.json(先备份);key 从 stdin 读(不回显)存 auth.json,
6
+ * 或 `--key-env VAR` 写 `"apiKey": "$VAR"`。对已存在的供应商只追加,不改已有条目。
7
+ * - refresh:重拉 `/models` 与 models.dev,只追加新模型;已下架的只提示。
8
+ * - list / channels:供应商 → 渠道 → 模型数与 key 来源(从不显示 key)。remove:删配置与 auth.json 条目。
9
+ *
10
+ * 计费:`--probe` 每模型每渠道一次最小请求(并发执行,见到首个流事件即判可用并断开,见
11
+ * probe-runner.ts);执行前打印预估,TTY 问 y/N,非 TTY 必须 `--yes`。
12
+ */
13
+ import { existsSync } from "node:fs";
14
+ import { createInterface } from "node:readline/promises";
15
+ import { describeRefresh, refreshModelsDev } from "../../ai/providers/models-dev-cache.js";
16
+ import { modelsDevFields } from "../../ai/providers/models-dev.js";
17
+ import { PROVIDER_ID_PATTERN, setAuthKey } from "../../config/auth-file.js";
18
+ import { loadConfigFile } from "../../config/load.js";
19
+ import { CONFIG_FILE_VERSION } from "../../config/types.js";
20
+ import { writeConfigFile } from "../../config/write.js";
21
+ import { parseSubArgs, UsageError } from "../args.js";
22
+ import { pickByPrice } from "../default-model.js";
23
+ import { ExitCode } from "../exit-codes.js";
24
+ import { extractKey } from "./auth.js";
25
+ import { listChannels, listProviders, removeProvider } from "./providers-list.js";
26
+ import { buildRegistry, loadUserLevel } from "./context.js";
27
+ import { DISCOVER_TIMEOUT_MS } from "./models-discover.js";
28
+ import { describeProbePlan, parseProbeTuning } from "./probe-runner.js";
29
+ import { probeChannels } from "./providers-probe.js";
30
+ import { DEFAULT_PREFER, capRequests, defaultChannels, hintedChannels, listingTarget, mergeProvider, orderChannels, parseChannelSpec, parseModelList, probeSelection, renderTable, } from "./providers-plan.js";
31
+ export const DEFAULT_MAX_REQUESTS = 60;
32
+ export const DEFAULT_LIMIT = 30;
33
+ export const PROVIDERS_USAGE = `用法:ama providers add <id> --base-url <url> [--channel <名字>=<协议>@<地址> …]
34
+ [--api openai-completions|openai-responses|anthropic-messages|auto]
35
+ [--key-env <VAR>] [--probe] [--limit N] [--probe-models a,b,…]
36
+ [--max-requests N] [--concurrency N] [--probe-timeout ms]
37
+ [--prefer chat,responses,messages] [--include-no-tools] [--yes]
38
+ ama providers list
39
+ ama providers channels <id>
40
+ ama providers remove <id>
41
+ ama providers refresh <id> [--probe] [--limit N] [--probe-models a,b,…] [--max-requests N]
42
+ [--concurrency N] [--probe-timeout ms] [--yes]
43
+ `;
44
+ const VALUE_OPTIONS = [
45
+ "base-url",
46
+ "api",
47
+ "key-env",
48
+ "limit",
49
+ "probe-models",
50
+ "max-requests",
51
+ "concurrency",
52
+ "probe-timeout",
53
+ "prefer",
54
+ "name",
55
+ "profile",
56
+ "auth-file",
57
+ ];
58
+ const FLAG_OPTIONS = ["probe", "include-no-tools", "yes"];
59
+ function intValue(ctx, name, fallback) {
60
+ const raw = ctx.values.get(name);
61
+ if (raw === undefined)
62
+ return fallback;
63
+ const n = Number(raw);
64
+ if (!Number.isInteger(n) || n < 1)
65
+ throw new UsageError(`--${name} 需要正整数:${raw}`);
66
+ return n;
67
+ }
68
+ function list(raw) {
69
+ return raw
70
+ ?.split(",")
71
+ .map((s) => s.trim())
72
+ .filter((s) => s !== "");
73
+ }
74
+ export function userConfig(level) {
75
+ return structuredClone(loadConfigFile("config", level.userConfigPath)?.value ?? { version: CONFIG_FILE_VERSION });
76
+ }
77
+ async function confirm(io, question) {
78
+ io.stderr(question);
79
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
80
+ try {
81
+ return /^y(es)?$/i.test((await rl.question("继续?[y/N] ")).trim());
82
+ }
83
+ finally {
84
+ rl.close();
85
+ }
86
+ }
87
+ /** --yes / TTY 确认;非 TTY 且没有 --yes 返回 false 并提示(调用方退出 2)。 */
88
+ async function approved(ctx, what) {
89
+ if (ctx.flags.has("yes"))
90
+ return true;
91
+ if (!ctx.io.stdinIsTTY) {
92
+ ctx.io.stderr(`ama: ${what},非交互环境需加 --yes\n`);
93
+ return "usage";
94
+ }
95
+ return confirm(ctx.io, `${what}\n`);
96
+ }
97
+ /**
98
+ * `GET /models`。OpenAI 系带 `Authorization: Bearer`;Anthropic 形状的渠道先试 Bearer(中转多半只认它),
99
+ * 401 / 403 再试 `x-api-key` + `anthropic-version`(官方形状)。
100
+ */
101
+ async function fetchModelList(target, apiKey) {
102
+ const get = (headers) => fetch(target.url, {
103
+ headers: { accept: "application/json", ...headers },
104
+ signal: AbortSignal.timeout(DISCOVER_TIMEOUT_MS),
105
+ });
106
+ let response = await get(apiKey !== undefined ? { authorization: `Bearer ${apiKey}` } : {});
107
+ if ((response.status === 401 || response.status === 403) &&
108
+ apiKey !== undefined &&
109
+ target.channel.api === "anthropic-messages")
110
+ response = await get({ "x-api-key": apiKey, "anthropic-version": "2023-06-01" });
111
+ if (!response.ok)
112
+ throw new Error(`HTTP ${response.status} ${target.url}`);
113
+ return parseModelList(await response.json());
114
+ }
115
+ function channelsOfConfig(config) {
116
+ if (config.channels !== undefined)
117
+ return Object.entries(config.channels).map(([name, c]) => ({
118
+ name,
119
+ api: c.api,
120
+ baseUrl: c.baseUrl,
121
+ }));
122
+ if (config.baseUrl === undefined)
123
+ return [];
124
+ const api = config.api ?? "openai-completions";
125
+ return [{ name: "default", api, baseUrl: config.baseUrl }];
126
+ }
127
+ /** 逐模型定渠道(含探测);返回各模型的计划与是否因鉴权 / 限流提前停止。 */
128
+ async function plan(ctx, input) {
129
+ const { io } = ctx;
130
+ const prefer = list(ctx.values.get("prefer")) ?? [...DEFAULT_PREFER];
131
+ const byId = new Map(input.listed.map((m) => [m.id, m]));
132
+ const fresh = input.listed.filter((m) => !input.existingIds.has(m.id));
133
+ const hint = (id) => {
134
+ const listed = byId.get(id);
135
+ return listed !== undefined ? hintedChannels(listed, input.candidates) : undefined;
136
+ };
137
+ const tryChannels = (id) => orderChannels(hint(id) ?? input.candidates.map((c) => c.name), prefer);
138
+ const probeable = (id) => tryChannels(id).length > 0;
139
+ const results = new Map();
140
+ let stopped;
141
+ if (input.probe) {
142
+ const only = list(ctx.values.get("probe-models"));
143
+ const selected = probeSelection(fresh.map((m) => m.id).filter(probeable), intValue(ctx, "limit", DEFAULT_LIMIT), only);
144
+ const max = intValue(ctx, "max-requests", DEFAULT_MAX_REQUESTS);
145
+ const capped = capRequests(selected, tryChannels, max);
146
+ const tuning = parseProbeTuning(ctx.values);
147
+ io.stdout(`\n探测:${capped.ids.length} 个模型、${capped.requests} 次最小请求(每模型每渠道 1 次,上限 ${max})` +
148
+ `${capped.dropped > 0 ? `;另有 ${capped.dropped} 个超出上限未探测` : ""}\n` +
149
+ `${describeProbePlan(capped.requests, tuning.concurrency, tuning.timeoutMs)}\n`);
150
+ const ok = await approved(ctx, `将发 ${capped.requests} 次计费请求并写入 ${ctx.level.userConfigPath}`);
151
+ if (ok === "usage")
152
+ return { plans: [], cancelled: ExitCode.Usage };
153
+ if (!ok)
154
+ return { plans: [], cancelled: ExitCode.Ok };
155
+ const probed = await probeChannels({
156
+ io,
157
+ providerId: input.id,
158
+ ids: capped.ids,
159
+ channelsFor: tryChannels,
160
+ candidates: input.candidates,
161
+ registry: input.registry,
162
+ apiKey: input.apiKey,
163
+ ...tuning,
164
+ });
165
+ for (const [id, result] of probed.results)
166
+ results.set(id, result);
167
+ stopped = probed.stopped;
168
+ }
169
+ const includeNoTools = ctx.flags.has("include-no-tools");
170
+ const plans = input.listed.map((listed) => {
171
+ const match = input.index?.match(listed.id);
172
+ const fields = match !== undefined ? modelsDevFields(match.model) : {};
173
+ const base = { id: listed.id, fields, match };
174
+ if (input.existingIds.has(listed.id))
175
+ return { ...base, channels: [], status: "existing" };
176
+ const probed = results.get(listed.id);
177
+ if (probed !== undefined) {
178
+ if (probed.ok.length === 0)
179
+ return {
180
+ ...base,
181
+ channels: [],
182
+ status: "failed",
183
+ ...(probed.error ? { error: probed.error } : {}),
184
+ };
185
+ return { ...base, channels: orderChannels(probed.ok, prefer), status: "ok" };
186
+ }
187
+ const hinted = hint(listed.id);
188
+ if (hinted !== undefined && hinted.length === 0)
189
+ return { ...base, channels: [], status: "no-channel" };
190
+ const channels = hinted !== undefined
191
+ ? orderChannels(hinted, prefer)
192
+ : orderChannels(input.candidates.map((c) => c.name), prefer).slice(0, 1);
193
+ if (fields.toolCall === false && !includeNoTools)
194
+ return { ...base, channels, status: "no-tools" };
195
+ return { ...base, channels, status: "unprobed" };
196
+ });
197
+ for (const p of plans)
198
+ if (p.status === "ok" && p.fields.toolCall === false && !includeNoTools)
199
+ p.status = "no-tools";
200
+ return { plans, ...(stopped !== undefined ? { stopped } : {}) };
201
+ }
202
+ async function obtainKey(ctx, id, registry) {
203
+ const keyEnv = ctx.values.get("key-env");
204
+ if (keyEnv !== undefined) {
205
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(keyEnv))
206
+ throw new UsageError(`--key-env 不是合法的变量名:${keyEnv}`);
207
+ const value = ctx.io.env[keyEnv]?.trim();
208
+ if (!value)
209
+ ctx.io.stderr(`ama: 警告:环境变量 ${keyEnv} 未设置,列模型与探测将不带 key\n`);
210
+ return { apiKey: value || undefined, configValue: `$${keyEnv}` };
211
+ }
212
+ if (registry.get(id) !== undefined) {
213
+ const existing = await registry.resolveApiKey(id);
214
+ if (existing.apiKey !== undefined)
215
+ return { apiKey: existing.apiKey };
216
+ }
217
+ if (ctx.io.stdinIsTTY)
218
+ ctx.io.stderr(`输入 ${id} 的 API key(不回显),回车结束:`);
219
+ const key = extractKey(await ctx.io.readStdin());
220
+ if (ctx.io.stdinIsTTY)
221
+ ctx.io.stderr("\n");
222
+ if (key === "")
223
+ throw new UsageError("没有从 stdin 读到 key(或用 --key-env <VAR>)");
224
+ return { apiKey: key, store: key };
225
+ }
226
+ async function add(ctx, id, refresh) {
227
+ const { io, level } = ctx;
228
+ if (!PROVIDER_ID_PATTERN.test(id) || id.includes("@"))
229
+ throw new UsageError(`供应商 id 不合法:${id}`);
230
+ const config = userConfig(level);
231
+ const existing = config.providers?.[id];
232
+ if (refresh && existing === undefined) {
233
+ io.stderr(`ama: ${level.userConfigPath} 里没有供应商 ${id}(先 ama providers add)\n`);
234
+ return ExitCode.Usage;
235
+ }
236
+ const registry = await buildRegistry(level, io, ctx.deps);
237
+ let candidates;
238
+ if (refresh)
239
+ candidates = channelsOfConfig(existing);
240
+ else if (ctx.channelSpecs.length > 0)
241
+ candidates = ctx.channelSpecs.map(parseChannelSpec);
242
+ else {
243
+ const baseUrl = ctx.values.get("base-url");
244
+ if (baseUrl === undefined)
245
+ throw new UsageError("ama providers add 需要 --base-url <url> 或 --channel");
246
+ if (!/^https?:\/\//.test(baseUrl))
247
+ throw new UsageError(`--base-url 应为 http(s) URL:${baseUrl}`);
248
+ candidates = defaultChannels(baseUrl, ctx.values.get("api"));
249
+ }
250
+ const target = listingTarget(candidates);
251
+ if (target === undefined)
252
+ throw new UsageError(`${id} 没有可用的渠道`);
253
+ const key = await obtainKey(ctx, id, registry);
254
+ let listed;
255
+ try {
256
+ listed = await fetchModelList(target, key.apiKey);
257
+ }
258
+ catch (error) {
259
+ io.stderr(`ama: ${id} 模型列表获取失败:${error.message}\n`);
260
+ return ExitCode.RuntimeError;
261
+ }
262
+ io.stdout(`${id}:发现 ${listed.length} 个模型(${target.url})\n`);
263
+ const md = await refreshModelsDev({ dataDir: level.dataDir, env: io.env, force: refresh });
264
+ io.stdout(`${describeRefresh(md)}\n`);
265
+ if (md.warning !== undefined)
266
+ io.stderr(`ama: 警告:${md.warning}\n`);
267
+ io.stdout(`候选渠道:${candidates.map((c) => `${c.name}(${c.api} ${c.baseUrl})`).join(" · ")}\n`);
268
+ const existingIds = new Set((existing?.models ?? []).map((m) => m.id));
269
+ const result = await plan(ctx, {
270
+ id,
271
+ candidates,
272
+ listed,
273
+ index: md.index,
274
+ existingIds,
275
+ apiKey: key.apiKey,
276
+ registry,
277
+ probe: ctx.flags.has("probe"),
278
+ });
279
+ if (result.cancelled !== undefined)
280
+ return result.cancelled;
281
+ io.stdout(`\n${renderTable(result.plans)}`);
282
+ if (refresh) {
283
+ const gone = [...existingIds].filter((m) => !listed.some((l) => l.id === m));
284
+ if (gone.length > 0)
285
+ io.stdout(`上游已不再列出(未删除):${gone.join(", ")}\n`);
286
+ }
287
+ const writable = result.plans.filter((p) => p.status === "ok" || p.status === "unprobed");
288
+ const legacy = existing !== undefined && existing.channels === undefined;
289
+ const merged = legacy && refresh
290
+ ? legacyMerge(existing, writable.map((p) => p.id))
291
+ : mergeProvider(legacy ? asChannels(existing) : existing, {
292
+ ...(ctx.values.get("name") !== undefined
293
+ ? { name: ctx.values.get("name") }
294
+ : {}),
295
+ ...(key.configValue !== undefined ? { apiKey: key.configValue } : {}),
296
+ channels: candidates,
297
+ models: writable,
298
+ prefer: list(ctx.values.get("prefer")) ?? [...DEFAULT_PREFER],
299
+ });
300
+ const keyNote = key.store !== undefined ? `;key → ${level.authFile}(0600)` : "";
301
+ // 还没有缺省模型时按价格规则挑一个写进 defaultModel(default-model.ts 的 pickByPrice);
302
+ // 探测过时只在探测通过的模型里挑
303
+ const verified = writable.filter((p) => p.status === "ok");
304
+ const defaultPick = level.merged.config.defaultModel === undefined && config.defaultModel === undefined
305
+ ? pickByPrice((verified.length > 0 ? verified : writable).map((p) => ({
306
+ id: p.id,
307
+ contextWindow: p.fields.contextWindow,
308
+ inputCost: p.fields.cost?.input,
309
+ toolCall: p.fields.toolCall,
310
+ })))
311
+ : undefined;
312
+ const defaultNote = defaultPick !== undefined ? `;defaultModel → ${id}/${defaultPick.id}` : "";
313
+ const summary = `\n将写入 ${level.userConfigPath}:${id} 新增渠道 ${merged.addedChannels.join(", ") || "无"},` +
314
+ `新增模型 ${merged.addedModels.length} 个${keyNote}${defaultNote}`;
315
+ io.stdout(`${summary}\n`);
316
+ if (defaultPick !== undefined)
317
+ io.stdout(` 选 ${defaultPick.id}:${defaultPick.reason}\n`);
318
+ else if (level.merged.config.defaultModel === undefined && writable.length > 0)
319
+ io.stdout(" 未设置 defaultModel:没有同时支持工具调用、上下文 ≥ 64k 且有价格的模型;用 ama config edit 设置\n");
320
+ if (merged.addedModels.length === 0 &&
321
+ merged.addedChannels.length === 0 &&
322
+ key.store === undefined) {
323
+ io.stdout("没有要写入的内容\n");
324
+ return result.stopped !== undefined ? ExitCode.RuntimeError : ExitCode.Ok;
325
+ }
326
+ if (!ctx.flags.has("probe")) {
327
+ const ok = await approved(ctx, "确认写入");
328
+ if (ok === "usage")
329
+ return ExitCode.Usage;
330
+ if (!ok) {
331
+ io.stderr("ama: 已取消\n");
332
+ return ExitCode.Ok;
333
+ }
334
+ }
335
+ const providers = (config.providers ??= {});
336
+ providers[id] = merged.config;
337
+ if (defaultPick !== undefined)
338
+ config.defaultModel = `${id}/${defaultPick.id}`;
339
+ const backup = existsSync(level.userConfigPath);
340
+ writeConfigFile(level.userConfigPath, config, { backup: true });
341
+ if (key.store !== undefined)
342
+ setAuthKey(level.authFile, id, key.store);
343
+ io.stdout(`已写入 ${level.userConfigPath}${backup ? "(原文件备份为 config.json.bak)" : ""}\n`);
344
+ const sample = defaultPick?.id ??
345
+ result.plans.find((p) => p.status === "ok" && merged.addedModels.includes(p.id))?.id ??
346
+ merged.addedModels[0];
347
+ if (sample !== undefined)
348
+ io.stdout(`试试:ama -p "hi" --model ${id}/${sample}\n`);
349
+ if (result.stopped !== undefined) {
350
+ io.stderr(`ama: 探测提前停止(${result.stopped})\n`);
351
+ return ExitCode.RuntimeError;
352
+ }
353
+ return ExitCode.Ok;
354
+ }
355
+ /** 旧写法(无 channels)的供应商:先把供应商级 api + baseUrl 变成 `default` 渠道。 */
356
+ function asChannels(config) {
357
+ const { api, baseUrl, ...rest } = structuredClone(config);
358
+ if (baseUrl === undefined)
359
+ return rest;
360
+ return {
361
+ ...rest,
362
+ channels: { default: { api: api ?? "openai-completions", baseUrl } },
363
+ defaultChannel: "default",
364
+ };
365
+ }
366
+ /** 旧写法供应商的 refresh:只追加 `{ id }`。 */
367
+ function legacyMerge(config, ids) {
368
+ const next = structuredClone(config);
369
+ const models = (next.models ??= []);
370
+ const known = new Set(models.map((m) => m.id));
371
+ const addedModels = ids.filter((id) => !known.has(id));
372
+ for (const id of addedModels)
373
+ models.push({ id });
374
+ return { config: next, addedChannels: [], addedModels };
375
+ }
376
+ /** `--channel` 可重复:先从 argv 里取出来,其余交给 parseSubArgs。 */
377
+ function takeChannels(argv) {
378
+ const rest = [];
379
+ const specs = [];
380
+ for (let i = 0; i < argv.length; i++) {
381
+ const token = argv[i] ?? "";
382
+ if (token === "--channel") {
383
+ const value = argv[++i];
384
+ if (value === undefined || value === "")
385
+ throw new UsageError("--channel 需要一个值");
386
+ specs.push(value);
387
+ }
388
+ else if (token.startsWith("--channel="))
389
+ specs.push(token.slice("--channel=".length));
390
+ else
391
+ rest.push(token);
392
+ }
393
+ return { rest, specs };
394
+ }
395
+ export async function runProviders(argv, io, deps) {
396
+ const { rest, specs } = takeChannels(argv);
397
+ const { positionals, values, flags } = parseSubArgs(rest, VALUE_OPTIONS, FLAG_OPTIONS);
398
+ const action = positionals[0];
399
+ if (flags.has("help") || action === undefined) {
400
+ io.stdout(PROVIDERS_USAGE);
401
+ return flags.has("help") ? ExitCode.Ok : ExitCode.Usage;
402
+ }
403
+ if (deps === undefined) {
404
+ io.stderr("ama: providers 尚未装配(供应商注册表由集成批次注入)\n");
405
+ return ExitCode.RuntimeError;
406
+ }
407
+ const level = loadUserLevel(io, {
408
+ profile: values.get("profile"),
409
+ authFile: values.get("auth-file"),
410
+ });
411
+ for (const warning of level.warnings)
412
+ io.stderr(`ama: 警告:${warning}\n`);
413
+ const ctx = { io, deps, level, values, flags, channelSpecs: specs };
414
+ const target = positionals[1];
415
+ const need = () => {
416
+ if (target === undefined)
417
+ throw new UsageError(`ama providers ${action} 需要 <id>`);
418
+ if (positionals.length > 2)
419
+ throw new UsageError(`多余的参数:${positionals.slice(2).join(" ")}`);
420
+ return target;
421
+ };
422
+ switch (action) {
423
+ case "add":
424
+ return add(ctx, need(), false);
425
+ case "refresh":
426
+ return add(ctx, need(), true);
427
+ case "list":
428
+ return listProviders(ctx);
429
+ case "channels":
430
+ return listChannels(ctx, need());
431
+ case "remove":
432
+ return removeProvider(ctx, need());
433
+ default:
434
+ throw new UsageError(`未知的 providers 子命令:${action}`);
435
+ }
436
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * `ama sessions export <id>`:导出会话(只读,不加锁;正在运行的会话也能导)。[W4-D]
3
+ *
4
+ * - `--format md|json|jsonl`(缺省 md);`--branch leaf|all`(缺省 leaf = 当前分支);
5
+ * - `--output <文件>`:写文件(0600,已存在则覆盖),否则写 stdout;
6
+ * - 导出内容先脱敏(session/redact.ts),格式说明见 docs/sessions.md「导出」。
7
+ */
8
+ import type { CliIo } from "../deps.js";
9
+ export declare const SESSIONS_EXPORT_USAGE = "\u7528\u6CD5\uFF1Aama sessions export <id> [--format md|json|jsonl] [--output <\u6587\u4EF6>] [--branch leaf|all]\n [--session-dir <\u76EE\u5F55>]\n";
10
+ export declare function runSessionsExport(argv: readonly string[], io: CliIo): Promise<number>;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * `ama sessions export <id>`:导出会话(只读,不加锁;正在运行的会话也能导)。[W4-D]
3
+ *
4
+ * - `--format md|json|jsonl`(缺省 md);`--branch leaf|all`(缺省 leaf = 当前分支);
5
+ * - `--output <文件>`:写文件(0600,已存在则覆盖),否则写 stdout;
6
+ * - 导出内容先脱敏(session/redact.ts),格式说明见 docs/sessions.md「导出」。
7
+ */
8
+ import { writeFileSync } from "node:fs";
9
+ import { join, resolve } from "node:path";
10
+ import { expandHome, resolveDataDir } from "../../config/paths.js";
11
+ import { renderExport } from "../../session/export.js";
12
+ import { findSessionFileReadOnly, readSessionReadOnly } from "../../session/scan.js";
13
+ import { parseSubArgs, UsageError } from "../args.js";
14
+ import { ExitCode } from "../exit-codes.js";
15
+ export const SESSIONS_EXPORT_USAGE = `用法:ama sessions export <id> [--format md|json|jsonl] [--output <文件>] [--branch leaf|all]
16
+ [--session-dir <目录>]
17
+ `;
18
+ const FORMATS = ["md", "json", "jsonl"];
19
+ const BRANCHES = ["leaf", "all"];
20
+ export async function runSessionsExport(argv, io) {
21
+ const { positionals, values, flags } = parseSubArgs(argv, [
22
+ "format",
23
+ "output",
24
+ "branch",
25
+ "session-dir",
26
+ ]);
27
+ if (flags.has("help")) {
28
+ io.stdout(SESSIONS_EXPORT_USAGE);
29
+ return ExitCode.Ok;
30
+ }
31
+ const [id, extra] = positionals;
32
+ if (id === undefined)
33
+ throw new UsageError("ama sessions export 需要 <id>");
34
+ if (extra !== undefined)
35
+ throw new UsageError(`多余的参数:${extra}`);
36
+ const format = (values.get("format") ?? "md");
37
+ if (!FORMATS.includes(format)) {
38
+ throw new UsageError(`--format 的取值应为 ${FORMATS.join(" | ")}(收到 ${format})`);
39
+ }
40
+ const branch = (values.get("branch") ?? "leaf");
41
+ if (!BRANCHES.includes(branch)) {
42
+ throw new UsageError(`--branch 的取值应为 ${BRANCHES.join(" | ")}(收到 ${branch})`);
43
+ }
44
+ const dirFlag = values.get("session-dir");
45
+ const root = dirFlag !== undefined
46
+ ? resolve(io.cwd, expandHome(dirFlag, { env: io.env }))
47
+ : join(resolveDataDir({ env: io.env }), "sessions");
48
+ const session = readSessionReadOnly(findSessionFileReadOnly(root, id, io.cwd));
49
+ const text = renderExport(session, format, branch);
50
+ const output = values.get("output");
51
+ if (output === undefined) {
52
+ io.stdout(text);
53
+ return ExitCode.Ok;
54
+ }
55
+ const target = resolve(io.cwd, expandHome(output, { env: io.env }));
56
+ writeFileSync(target, text, { mode: 0o600 });
57
+ io.stderr(`已导出到 ${target}\n`);
58
+ return ExitCode.Ok;
59
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `ama sessions search <关键词|/正则/>`:跨会话全文检索(只读)。[W4-D]
3
+ *
4
+ * - 范围:缺省当前目录的会话,`--all` 全部;`--role user|assistant|tool`(可逗号分隔多个);
5
+ * `--since 7d|today|YYYY-MM-DD`;`--limit N`(缺省 20);`--json` 每条命中一行 JSON。
6
+ * - 输出一行一条:会话 id 前 8 位、条目时间、项目、角色与编号(user 是 `#n`,可直接给 `--from <id>#n`;
7
+ * 其它角色是条目序号 `@k`)、命中片段。stdout 是 TTY 且没设 NO_COLOR 时用 ANSI 高亮,否则纯文本。
8
+ */
9
+ import { type SearchHit } from "../../session/search.js";
10
+ import type { CliIo } from "../deps.js";
11
+ export declare const SESSIONS_SEARCH_USAGE = "\u7528\u6CD5\uFF1Aama sessions search <\u5173\u952E\u8BCD|/\u6B63\u5219/\u6807\u5FD7> [--all] [--role user|assistant|tool]\n [--since 7d|today|YYYY-MM-DD] [--limit N] [--json] [--session-dir <\u76EE\u5F55>]\n";
12
+ export declare function renderHit(hit: SearchHit, color: boolean): string;
13
+ export declare function runSessionsSearch(argv: readonly string[], io: CliIo): Promise<number>;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * `ama sessions search <关键词|/正则/>`:跨会话全文检索(只读)。[W4-D]
3
+ *
4
+ * - 范围:缺省当前目录的会话,`--all` 全部;`--role user|assistant|tool`(可逗号分隔多个);
5
+ * `--since 7d|today|YYYY-MM-DD`;`--limit N`(缺省 20);`--json` 每条命中一行 JSON。
6
+ * - 输出一行一条:会话 id 前 8 位、条目时间、项目、角色与编号(user 是 `#n`,可直接给 `--from <id>#n`;
7
+ * 其它角色是条目序号 `@k`)、命中片段。stdout 是 TTY 且没设 NO_COLOR 时用 ANSI 高亮,否则纯文本。
8
+ */
9
+ import { join, resolve } from "node:path";
10
+ import { expandHome, resolveDataDir } from "../../config/paths.js";
11
+ import { sessionFilesInScope } from "../../session/scan.js";
12
+ import { compileQuery, searchSessions, } from "../../session/search.js";
13
+ import { parseSubArgs, UsageError } from "../args.js";
14
+ import { ExitCode } from "../exit-codes.js";
15
+ import { parseDaySpec } from "./stats.js";
16
+ export const SESSIONS_SEARCH_USAGE = `用法:ama sessions search <关键词|/正则/标志> [--all] [--role user|assistant|tool]
17
+ [--since 7d|today|YYYY-MM-DD] [--limit N] [--json] [--session-dir <目录>]
18
+ `;
19
+ const ROLES = ["user", "assistant", "tool"];
20
+ function highlight(text, ranges, color) {
21
+ if (!color || ranges.length === 0)
22
+ return text;
23
+ let out = "";
24
+ let at = 0;
25
+ for (const [a, b] of ranges) {
26
+ if (a < at)
27
+ continue;
28
+ out += text.slice(at, a) + `\x1b[1;33m${text.slice(a, b)}\x1b[0m`;
29
+ at = b;
30
+ }
31
+ return out + text.slice(at);
32
+ }
33
+ function shortTime(iso) {
34
+ return iso
35
+ .replace("T", " ")
36
+ .replace(/\.\d+Z$|Z$/, "")
37
+ .slice(0, 16);
38
+ }
39
+ export function renderHit(hit, color) {
40
+ const ref = hit.userN !== undefined ? `#${hit.userN}` : `@${hit.entryIndex}`;
41
+ const dim = (s) => (color ? `\x1b[2m${s}\x1b[0m` : s);
42
+ return `${hit.sessionId.slice(0, 8)}${ref.padEnd(4)} ${dim(shortTime(hit.timestamp))} ${dim(hit.cwd)} ${hit.role.padEnd(9)} ${highlight(hit.snippet, hit.ranges, color)}\n`;
43
+ }
44
+ export async function runSessionsSearch(argv, io) {
45
+ const { positionals, values, flags } = parseSubArgs(argv, ["role", "since", "limit", "session-dir"], ["all", "json"]);
46
+ if (flags.has("help")) {
47
+ io.stdout(SESSIONS_SEARCH_USAGE);
48
+ return ExitCode.Ok;
49
+ }
50
+ const pattern = positionals.join(" ");
51
+ if (pattern.trim() === "")
52
+ throw new UsageError("ama sessions search 需要关键词或 /正则/");
53
+ let query;
54
+ try {
55
+ query = compileQuery(pattern);
56
+ }
57
+ catch (error) {
58
+ throw new UsageError(`正则无效:${error.message}`);
59
+ }
60
+ let roles;
61
+ const roleRaw = values.get("role");
62
+ if (roleRaw !== undefined) {
63
+ roles = new Set();
64
+ for (const role of roleRaw.split(",").map((r) => r.trim())) {
65
+ if (!ROLES.includes(role)) {
66
+ throw new UsageError(`--role 的取值应为 ${ROLES.join(" | ")}(收到 ${role})`);
67
+ }
68
+ roles.add(role);
69
+ }
70
+ }
71
+ const limit = Number(values.get("limit") ?? "20");
72
+ if (!Number.isInteger(limit) || limit < 1)
73
+ throw new UsageError("--limit 应为正整数");
74
+ const sinceRaw = values.get("since");
75
+ const sinceIso = sinceRaw === undefined
76
+ ? undefined
77
+ : new Date(`${parseDaySpec("since", sinceRaw)}T00:00:00`).toISOString();
78
+ const dirFlag = values.get("session-dir");
79
+ const root = dirFlag !== undefined
80
+ ? resolve(io.cwd, expandHome(dirFlag, { env: io.env }))
81
+ : join(resolveDataDir({ env: io.env }), "sessions");
82
+ const files = sessionFilesInScope(root, flags.has("all") ? undefined : io.cwd);
83
+ const { hits, truncated } = searchSessions(files, query, {
84
+ limit,
85
+ ...(roles !== undefined ? { roles } : {}),
86
+ ...(sinceIso !== undefined ? { sinceIso } : {}),
87
+ });
88
+ if (flags.has("json")) {
89
+ for (const hit of hits)
90
+ io.stdout(`${JSON.stringify(hit)}\n`);
91
+ return ExitCode.Ok;
92
+ }
93
+ if (hits.length === 0) {
94
+ io.stdout(flags.has("all") ? "没有命中\n" : "没有命中(只搜了当前目录的会话,--all 搜全部)\n");
95
+ return ExitCode.Ok;
96
+ }
97
+ const color = io.stdoutIsTTY && (io.env["NO_COLOR"] ?? "") === "";
98
+ for (const hit of hits)
99
+ io.stdout(renderHit(hit, color));
100
+ if (truncated)
101
+ io.stdout(`(已到 --limit ${limit},可能还有更多)\n`);
102
+ return ExitCode.Ok;
103
+ }
@@ -3,9 +3,10 @@
3
3
  *
4
4
  * 会话存储是 B2 的实现,经 RuntimeDeps.sessions 注入;这里只做参数、目录解析与输出格式。
5
5
  * - list [--all]:缺省只列当前目录的会话;`--all` 列全部。
6
- * - show <id>:头信息 + 条目类型统计 + 首条提示。
6
+ * - show <id>:头信息 + 条目类型统计 + 首条提示 + 用户消息编号(`--from <id>#<编号>` 复用)。
7
+ * - search / export:见 sessions-search.ts、sessions-export.ts(只读扫描,不经会话存储)。
7
8
  * - prune [--older-than <天>] [--dry-run]:缺省 30 天,移到 trash(不删除)。
8
9
  */
9
10
  import type { CliIo, RuntimeDeps } from "../deps.js";
10
- export declare const SESSIONS_USAGE = "\u7528\u6CD5\uFF1Aama sessions list [--all] [--session-dir <\u76EE\u5F55>]\n ama sessions show <id> [--session-dir <\u76EE\u5F55>]\n ama sessions prune [--older-than <\u5929>] [--dry-run] [--all] [--session-dir <\u76EE\u5F55>]\n";
11
+ export declare const SESSIONS_USAGE = "\u7528\u6CD5\uFF1Aama sessions list [--all] [--session-dir <\u76EE\u5F55>]\n ama sessions show <id> [--session-dir <\u76EE\u5F55>]\n ama sessions prune [--older-than <\u5929>] [--dry-run] [--all] [--session-dir <\u76EE\u5F55>]\n ama sessions search <\u5173\u952E\u8BCD|/\u6B63\u5219/> [--all] [--role user|assistant|tool] [--since 7d] [--limit N]\n ama sessions export <id> [--format md|json|jsonl] [--output <\u6587\u4EF6>] [--branch leaf|all]\n";
11
12
  export declare function runSessions(argv: readonly string[], io: CliIo, deps: Pick<RuntimeDeps, "sessions"> | undefined): Promise<number>;