echopai 2.2.0 → 2.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 (47) hide show
  1. package/README.md +63 -348
  2. package/dist/bin.js +8302 -149
  3. package/package.json +11 -13
  4. package/dist/_generated/commands.js +0 -282
  5. package/dist/_generated/help.js +0 -195
  6. package/dist/_generated/operations.js +0 -1529
  7. package/dist/runtime/auth.js +0 -95
  8. package/dist/runtime/envelope.js +0 -52
  9. package/dist/runtime/errors.js +0 -186
  10. package/dist/runtime/filters.js +0 -153
  11. package/dist/runtime/format.js +0 -143
  12. package/dist/runtime/http.js +0 -65
  13. package/dist/runtime/idempotency.js +0 -18
  14. package/dist/runtime/invoker.js +0 -387
  15. package/dist/runtime/io.js +0 -16
  16. package/dist/runtime/paginator.js +0 -146
  17. package/dist/runtime/trace.js +0 -99
  18. package/dist/runtime/tty.js +0 -51
  19. package/dist/runtime/verb_cmd.js +0 -70
  20. package/dist/runtime/verb_runner.js +0 -152
  21. package/dist/runtime/whoami_cache.js +0 -109
  22. package/dist/tools/api.js +0 -81
  23. package/dist/tools/completion.js +0 -116
  24. package/dist/tools/config.js +0 -123
  25. package/dist/tools/doctor.js +0 -183
  26. package/dist/tools/login.js +0 -99
  27. package/dist/tools/mcp.js +0 -141
  28. package/dist/tools/raw.js +0 -96
  29. package/dist/tools/schema.js +0 -58
  30. package/dist/tools/trace.js +0 -54
  31. package/dist/tools/welcome.js +0 -190
  32. package/dist/tools/whoami.js +0 -132
  33. package/dist/verbs/_spec.js +0 -15
  34. package/dist/verbs/bars_batch.js +0 -66
  35. package/dist/verbs/chart.js +0 -110
  36. package/dist/verbs/digest.js +0 -344
  37. package/dist/verbs/financials.js +0 -212
  38. package/dist/verbs/hot.js +0 -29
  39. package/dist/verbs/index.js +0 -57
  40. package/dist/verbs/lookup.js +0 -72
  41. package/dist/verbs/news.js +0 -67
  42. package/dist/verbs/quote.js +0 -53
  43. package/dist/verbs/scan.js +0 -42
  44. package/dist/verbs/search.js +0 -105
  45. package/dist/verbs/sentiment.js +0 -46
  46. package/dist/verbs/views.js +0 -83
  47. package/dist/version.js +0 -5
@@ -1,123 +0,0 @@
1
- /**
2
- * `echopai config [...]`
3
- *
4
- * echopai config show 显示完整 config
5
- * echopai config set default_profile <name> 切默认 profile
6
- *
7
- * echopai config profile add <name> --key <eps> [--base-url <url>]
8
- * echopai config profile rm <name>
9
- * echopai config profile list
10
- * echopai config profile switch <name> ≡ set default_profile
11
- *
12
- * 配置文件 ~/.config/echopai/config.toml(XDG)。文件 mode 0600。
13
- */
14
- import { Command } from "commander";
15
- import { configPath, readConfigFile, writeConfigFile, } from "../runtime/auth.js";
16
- export function buildConfigCommand() {
17
- const cmd = new Command("config").description("Manage CLI config (~/.config/echopai/config.toml)");
18
- cmd
19
- .command("show")
20
- .description("Print current config (key 字段会脱敏成 eps_live_***_***)")
21
- .action(() => {
22
- const cfg = readConfigFile();
23
- const masked = JSON.parse(JSON.stringify(cfg));
24
- if (masked.profiles) {
25
- for (const p of Object.values(masked.profiles)) {
26
- if (p && typeof p === "object" && typeof p.key === "string") {
27
- p.key = redactKey(p.key);
28
- }
29
- }
30
- }
31
- process.stdout.write(JSON.stringify({ data: masked, meta: { path: configPath() } }, null, 2) + "\n");
32
- process.exit(0);
33
- });
34
- cmd
35
- .command("set <key> <value>")
36
- .description("Set a top-level config key (currently only 'default_profile' is recognized)")
37
- .action((key, value) => {
38
- const cfg = readConfigFile();
39
- if (key === "default_profile") {
40
- cfg.default_profile = value;
41
- writeConfigFile(cfg);
42
- process.stdout.write(JSON.stringify({ data: { ok: true }, meta: { path: configPath() } }) + "\n");
43
- process.exit(0);
44
- }
45
- writeError("invalid_args", `Unknown top-level config key '${key}'`, "Use one of: default_profile", 1);
46
- });
47
- const profile = cmd.command("profile").description("Manage credential profiles");
48
- profile
49
- .command("add <name>")
50
- .description("Add or overwrite a profile")
51
- .requiredOption("--key <eps_live_X_Y>", "Bearer credential")
52
- .option("--base-url <url>", "API base URL", "https://api.echopai.com")
53
- .action((name, opts) => {
54
- const cfg = readConfigFile();
55
- cfg.profiles = cfg.profiles ?? {};
56
- cfg.profiles[name] = { key: opts.key, base_url: opts.baseUrl };
57
- if (!cfg.default_profile)
58
- cfg.default_profile = name;
59
- writeConfigFile(cfg);
60
- process.stdout.write(JSON.stringify({ data: { ok: true, name, default: cfg.default_profile === name }, meta: { path: configPath() } }) + "\n");
61
- process.exit(0);
62
- });
63
- profile
64
- .command("rm <name>")
65
- .description("Remove a profile")
66
- .action((name) => {
67
- const cfg = readConfigFile();
68
- if (!cfg.profiles?.[name]) {
69
- writeError("profile_not_found", `Profile '${name}' not in config.`, undefined, 1);
70
- }
71
- delete cfg.profiles[name];
72
- if (cfg.default_profile === name)
73
- cfg.default_profile = undefined;
74
- writeConfigFile(cfg);
75
- process.stdout.write(JSON.stringify({ data: { ok: true, removed: name }, meta: { path: configPath() } }) + "\n");
76
- process.exit(0);
77
- });
78
- profile
79
- .command("list")
80
- .description("List all profiles (keys redacted)")
81
- .action(() => {
82
- const cfg = readConfigFile();
83
- const items = Object.entries(cfg.profiles ?? {}).map(([name, p]) => ({
84
- name,
85
- is_default: cfg.default_profile === name,
86
- base_url: p?.base_url ?? "https://api.echopai.com",
87
- key_redacted: p?.key ? redactKey(p.key) : null,
88
- }));
89
- process.stdout.write(JSON.stringify({ data: items, meta: { path: configPath() } }) + "\n");
90
- process.exit(0);
91
- });
92
- profile
93
- .command("switch <name>")
94
- .description("Set default_profile")
95
- .action((name) => {
96
- const cfg = readConfigFile();
97
- if (!cfg.profiles?.[name]) {
98
- writeError("profile_not_found", `Profile '${name}' not in config.`, "Run `echopai config profile list` to see available.", 1);
99
- }
100
- cfg.default_profile = name;
101
- writeConfigFile(cfg);
102
- process.stdout.write(JSON.stringify({ data: { ok: true, default_profile: name } }) + "\n");
103
- process.exit(0);
104
- });
105
- return cmd;
106
- }
107
- export function redactKey(key) {
108
- // eps_live_<lookup>_<secret> → eps_live_<lookup>_*** (last 4 chars)
109
- if (!key.startsWith("eps_live_"))
110
- return "eps_***";
111
- const parts = key.split("_");
112
- if (parts.length < 4)
113
- return "eps_live_***";
114
- const last = parts.slice(3).join("_");
115
- return `eps_live_${parts[2]}_***${last.slice(-4)}`;
116
- }
117
- function writeError(code, message, recovery_hint, exitCode) {
118
- const env = { error: { code, message } };
119
- if (recovery_hint)
120
- env.error.recovery_hint = recovery_hint;
121
- process.stderr.write(JSON.stringify(env) + "\n");
122
- process.exit(exitCode);
123
- }
@@ -1,183 +0,0 @@
1
- /**
2
- * `echopai doctor`
3
- *
4
- * 跑一组健康检查,每条 ok | warn | fail。所有 ok → exit 0;存在 fail → exit 1。
5
- *
6
- * 当前覆盖(Phase 2.4 起步集):
7
- * credential 本地 token / profile 能解析
8
- * whoami /v1/auth/whoami 可达且 200
9
- * operations token 至少能调到一条 operation
10
- * api_version CLI 期望 api_version 与 server 报告兼容
11
- *
12
- * 未来扩展:clock_skew (Date header) / ws_news (1-frame 探测) / scope_coverage
13
- * (verb tier 满足度)。
14
- */
15
- import { Command, Option } from "commander";
16
- import { listOperations } from "../_generated/operations.js";
17
- import { resolveCredentials, AuthMissingError } from "../runtime/auth.js";
18
- import { CallApiError } from "../runtime/errors.js";
19
- import { isTtyHuman, cyan, dim, green, red, yellow } from "../runtime/tty.js";
20
- import { clearWhoamiCache, getWhoami } from "../runtime/whoami_cache.js";
21
- import { deriveOperationAvailability } from "./whoami.js";
22
- import { CLI_VERSION } from "../version.js";
23
- const EXPECTED_API_VERSION = "2026-05"; // bump when CLI hard-depends on server change
24
- export async function runDoctor(ctx) {
25
- const checks = [];
26
- const resolveFn = ctx.resolveCredentialsImpl ?? resolveCredentials;
27
- const whoamiFn = ctx.getWhoamiImpl ?? getWhoami;
28
- // 1. credential resolution
29
- let creds = null;
30
- try {
31
- creds = resolveFn({});
32
- checks.push({ name: "credential", status: "ok" });
33
- }
34
- catch (e) {
35
- if (e instanceof AuthMissingError) {
36
- checks.push({
37
- name: "credential",
38
- status: "fail",
39
- message: e.message,
40
- recovery_hint: e.recovery_hint ?? "Run `echopai login` or set `ECHOPAI_KEY`.",
41
- });
42
- }
43
- else {
44
- checks.push({
45
- name: "credential",
46
- status: "fail",
47
- message: e instanceof Error ? e.message : String(e),
48
- });
49
- }
50
- }
51
- if (!creds) {
52
- // Subsequent checks all require a credential.
53
- for (const name of ["whoami", "operations", "api_version"]) {
54
- checks.push({
55
- name,
56
- status: "fail",
57
- message: "skipped: credential unavailable",
58
- });
59
- }
60
- return { ok: false, cli_version: ctx.cliVersion, checks };
61
- }
62
- // 2. whoami reachable
63
- let whoami;
64
- try {
65
- whoami = await whoamiFn({ baseUrl: creds.baseUrl, bearer: creds.key, cliVersion: ctx.cliVersion }, { force: true });
66
- checks.push({
67
- name: "whoami",
68
- status: "ok",
69
- info: {
70
- kind: whoami.kind,
71
- scope_count: whoami.scopes.length,
72
- ...(whoami.app_slug ? { app_slug: whoami.app_slug } : {}),
73
- },
74
- });
75
- }
76
- catch (e) {
77
- const code = e instanceof CallApiError ? e.code : "internal_error";
78
- const msg = e instanceof Error ? e.message : String(e);
79
- const hint = e instanceof CallApiError ? e.recovery_hint : undefined;
80
- checks.push({
81
- name: "whoami",
82
- status: "fail",
83
- message: `${code}: ${msg}`,
84
- ...(hint ? { recovery_hint: hint } : {}),
85
- });
86
- // Without whoami, downstream checks are meaningless.
87
- for (const name of ["operations", "api_version"]) {
88
- checks.push({
89
- name,
90
- status: "fail",
91
- message: "skipped: whoami failed",
92
- });
93
- }
94
- return { ok: false, cli_version: ctx.cliVersion, checks };
95
- }
96
- // 3. operations: at least one usable op
97
- const { available, unavailable } = deriveOperationAvailability(new Set(whoami.scopes), listOperations());
98
- if (available.length === 0) {
99
- checks.push({
100
- name: "operations",
101
- status: "fail",
102
- message: `Token scopes ${JSON.stringify(whoami.scopes)} grant access to 0 operations.`,
103
- recovery_hint: "Request additional scopes (e.g. news:read / quote:l1) from your app admin.",
104
- info: { available: 0, unavailable: unavailable.length },
105
- });
106
- }
107
- else {
108
- checks.push({
109
- name: "operations",
110
- status: "ok",
111
- info: { available: available.length, unavailable: unavailable.length },
112
- });
113
- }
114
- // 4. api_version compatibility
115
- const serverVersion = whoami.api_version;
116
- if (!serverVersion) {
117
- checks.push({
118
- name: "api_version",
119
- status: "warn",
120
- message: "Server did not report api_version (older deployment).",
121
- });
122
- }
123
- else if (serverVersion !== EXPECTED_API_VERSION) {
124
- checks.push({
125
- name: "api_version",
126
- status: "warn",
127
- message: `CLI expects ${EXPECTED_API_VERSION}, server reports ${serverVersion}.`,
128
- recovery_hint: "Usually safe (additive changes). Upgrade CLI if you hit unexpected errors.",
129
- info: { cli_expected: EXPECTED_API_VERSION, server: serverVersion },
130
- });
131
- }
132
- else {
133
- checks.push({
134
- name: "api_version",
135
- status: "ok",
136
- info: { version: serverVersion },
137
- });
138
- }
139
- const ok = checks.every((c) => c.status !== "fail");
140
- return { ok, cli_version: ctx.cliVersion, checks };
141
- }
142
- export function renderDoctorReport(report) {
143
- if (!isTtyHuman)
144
- return JSON.stringify({ data: report }) + "\n";
145
- const lines = [];
146
- lines.push(`${cyan("echopai doctor")} ${dim("cli " + report.cli_version)}`);
147
- lines.push("");
148
- for (const c of report.checks) {
149
- const icon = c.status === "ok" ? green("✓") : c.status === "warn" ? yellow("!") : red("✗");
150
- const head = ` ${icon} ${c.name}`;
151
- if (c.message) {
152
- lines.push(`${head} ${dim("—")} ${c.message}`);
153
- }
154
- else if (c.info) {
155
- lines.push(`${head} ${dim(JSON.stringify(c.info))}`);
156
- }
157
- else {
158
- lines.push(head);
159
- }
160
- if (c.recovery_hint)
161
- lines.push(` ${cyan("hint:")} ${c.recovery_hint}`);
162
- }
163
- lines.push("");
164
- lines.push(report.ok ? green("All checks passed.") : red("One or more checks failed."));
165
- return lines.join("\n") + "\n";
166
- }
167
- export function buildDoctorCommand() {
168
- const cmd = new Command("doctor").description("Diagnose CLI environment, credential, server reachability, and capability");
169
- cmd.addOption(new Option("--no-cache", "Bypass whoami cache for fresh check"));
170
- cmd.action(async (opts) => {
171
- if (opts.cache === false)
172
- clearWhoamiCache();
173
- const report = await runDoctor({ cliVersion: CLI_VERSION });
174
- if (isTtyHuman) {
175
- process.stdout.write(renderDoctorReport(report));
176
- }
177
- else {
178
- process.stdout.write(JSON.stringify({ data: report }) + "\n");
179
- }
180
- process.exit(report.ok ? 0 : 1);
181
- });
182
- return cmd;
183
- }
@@ -1,99 +0,0 @@
1
- /**
2
- * `echopai login --key <eps_live_X_Y> [--profile <name>] [--base-url <url>]`
3
- * `echopai logout [--profile <name>]`
4
- * `echopai status [--profile <name>]`
5
- *
6
- * login 把 key 写入 config profile(默认 'default')。不在 login 时 round-trip
7
- * 验证 key —— 第一次实际调用会自然报 auth_invalid。
8
- *
9
- * status 显示当前 profile / base_url / 脱敏 key prefix。
10
- */
11
- import { Command } from "commander";
12
- import { configPath, readConfigFile, resolveCredentials, writeConfigFile, AuthMissingError, } from "../runtime/auth.js";
13
- import { redactKey } from "./config.js";
14
- export function buildLoginCommand() {
15
- return new Command("login")
16
- .description("Save Bearer credential to config profile")
17
- .requiredOption("--key <eps_live_X_Y>", "Bearer credential issued by EchoPai admin console")
18
- .option("--profile <name>", "Profile to save under", "default")
19
- .option("--base-url <url>", "API base URL", "https://api.echopai.com")
20
- .action((opts) => {
21
- const cfg = readConfigFile();
22
- cfg.profiles = cfg.profiles ?? {};
23
- cfg.profiles[opts.profile] = { key: opts.key, base_url: opts.baseUrl };
24
- if (!cfg.default_profile)
25
- cfg.default_profile = opts.profile;
26
- writeConfigFile(cfg);
27
- process.stdout.write(JSON.stringify({
28
- data: {
29
- ok: true,
30
- profile: opts.profile,
31
- base_url: opts.baseUrl,
32
- key_redacted: redactKey(opts.key),
33
- default: cfg.default_profile === opts.profile,
34
- },
35
- meta: { path: configPath() },
36
- }) + "\n");
37
- process.exit(0);
38
- });
39
- }
40
- export function buildLogoutCommand() {
41
- return new Command("logout")
42
- .description("Remove Bearer credential from config profile")
43
- .option("--profile <name>", "Profile to clear (default = default_profile)")
44
- .action((opts) => {
45
- const cfg = readConfigFile();
46
- const target = opts.profile || cfg.default_profile;
47
- if (!target || !cfg.profiles?.[target]) {
48
- process.stderr.write(JSON.stringify({
49
- error: {
50
- code: "profile_not_found",
51
- message: `No profile '${target ?? "(none)"}' configured.`,
52
- },
53
- }) + "\n");
54
- process.exit(1);
55
- }
56
- delete cfg.profiles[target];
57
- if (cfg.default_profile === target)
58
- cfg.default_profile = undefined;
59
- writeConfigFile(cfg);
60
- process.stdout.write(JSON.stringify({ data: { ok: true, removed_profile: target }, meta: { path: configPath() } }) + "\n");
61
- process.exit(0);
62
- });
63
- }
64
- export function buildStatusCommand() {
65
- return new Command("status")
66
- .description("Show current credential / profile / base URL (key redacted)")
67
- .option("--profile <name>", "Profile to inspect")
68
- .action((opts) => {
69
- try {
70
- const resolveOpts = {};
71
- if (opts.profile)
72
- resolveOpts.profile = opts.profile;
73
- const c = resolveCredentials(resolveOpts);
74
- process.stdout.write(JSON.stringify({
75
- data: {
76
- source: process.env.ECHOPAI_KEY && !opts.profile
77
- ? "env"
78
- : c.profile
79
- ? "config"
80
- : "env",
81
- profile: c.profile,
82
- base_url: c.baseUrl,
83
- key_redacted: redactKey(c.key),
84
- },
85
- meta: { config_path: configPath() },
86
- }) + "\n");
87
- process.exit(0);
88
- }
89
- catch (e) {
90
- if (e instanceof AuthMissingError) {
91
- process.stderr.write(JSON.stringify({
92
- error: { code: "auth_missing", message: e.message, recovery_hint: e.recovery_hint },
93
- }) + "\n");
94
- process.exit(1);
95
- }
96
- throw e;
97
- }
98
- });
99
- }
package/dist/tools/mcp.js DELETED
@@ -1,141 +0,0 @@
1
- /**
2
- * `echopai mcp serve` — stdio MCP server.
3
- *
4
- * 启动流程:
5
- * 1. 解析凭据 (ECHOPAI_KEY / ECHOPAI_PROFILE / --profile)
6
- * 2. getWhoami(channel="mcp") 拿 token scopes
7
- * 3. 用 OPERATIONS.scopesAny 把 ALL_VERB_SPECS 过滤到 token 可调子集
8
- * 4. registerTool 每个可用 verb (Zod inputSchema 直接给 SDK)
9
- * 5. connect stdio transport, 进 message loop
10
- *
11
- * Tool handler:
12
- * - 收到 tools/call → 调 spec.handler(args, ctx) (channel="mcp" 注入 header)
13
- * - 成功 → 返 MCP content block (JSON envelope 序列化为 text)
14
- * - 失败 → CallApiError 映射为 MCP isError=true tool response
15
- *
16
- * 错误日志走 stderr (MCP stdio 协议: stdout 仅 JSON-RPC, stderr 可任意文本)。
17
- */
18
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
19
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
20
- import { Command, Option } from "commander";
21
- import { OPERATIONS } from "../_generated/operations.js";
22
- import { resolveCredentials, AuthMissingError } from "../runtime/auth.js";
23
- import { CallApiError } from "../runtime/errors.js";
24
- import { getWhoami } from "../runtime/whoami_cache.js";
25
- import { ALL_VERB_SPECS } from "../verbs/index.js";
26
- import { CLI_VERSION } from "../version.js";
27
- /** Verb is available iff at least one backing op is callable with token scopes. */
28
- export function verbAvailable(spec, tokenScopes) {
29
- if (spec.backingOps.length === 0)
30
- return true;
31
- for (const opKey of spec.backingOps) {
32
- const op = OPERATIONS[opKey];
33
- if (!op)
34
- continue;
35
- if (op.scopesAny.length === 0)
36
- return true;
37
- if (op.scopesAny.some((s) => tokenScopes.has(s)))
38
- return true;
39
- }
40
- return false;
41
- }
42
- export function filterAvailableVerbs(specs, tokenScopes) {
43
- return specs.filter((s) => verbAvailable(s, tokenScopes));
44
- }
45
- export function envelopeToToolResponse(envelope) {
46
- return {
47
- content: [{ type: "text", text: JSON.stringify(envelope) }],
48
- };
49
- }
50
- export function errorToToolResponse(e) {
51
- if (e instanceof CallApiError) {
52
- const env = {
53
- error: {
54
- code: e.code,
55
- message: e.message,
56
- retryable: e.retryable,
57
- ...(e.recovery_hint ? { recovery_hint: e.recovery_hint } : {}),
58
- ...(e.requestId ? { request_id: e.requestId } : {}),
59
- },
60
- };
61
- return { content: [{ type: "text", text: JSON.stringify(env) }], isError: true };
62
- }
63
- const env = {
64
- error: {
65
- code: "internal_error",
66
- message: e instanceof Error ? e.message : String(e),
67
- retryable: false,
68
- },
69
- };
70
- return { content: [{ type: "text", text: JSON.stringify(env) }], isError: true };
71
- }
72
- export function buildMcpCommand() {
73
- const mcp = new Command("mcp").description("Model Context Protocol bridge (expose curated verbs to MCP hosts)");
74
- mcp
75
- .command("serve")
76
- .description("Run an MCP stdio server on this process. For Claude Desktop / Cursor / Claude Code.")
77
- .addOption(new Option("--profile <name>", "Use a specific profile"))
78
- .action(async (opts) => {
79
- let creds;
80
- try {
81
- creds = resolveCredentials(opts.profile ? { profile: opts.profile } : {});
82
- }
83
- catch (e) {
84
- if (e instanceof AuthMissingError) {
85
- process.stderr.write(`[mcp] credential resolution failed: ${e.message}\n` +
86
- `[mcp] hint: ${e.recovery_hint ?? "Run `echopai login` or set ECHOPAI_KEY."}\n`);
87
- process.exit(1);
88
- }
89
- throw e;
90
- }
91
- let whoami;
92
- try {
93
- whoami = await getWhoami({
94
- baseUrl: creds.baseUrl,
95
- bearer: creds.key,
96
- cliVersion: CLI_VERSION,
97
- channel: "mcp",
98
- });
99
- }
100
- catch (e) {
101
- process.stderr.write(`[mcp] whoami failed: ${e instanceof Error ? e.message : String(e)}\n`);
102
- process.exit(1);
103
- }
104
- const tokenScopes = new Set(whoami.scopes);
105
- const availableVerbs = filterAvailableVerbs(ALL_VERB_SPECS, tokenScopes);
106
- process.stderr.write(`[mcp] kind=${whoami.kind} scopes=[${whoami.scopes.join(",")}]\n` +
107
- `[mcp] exposing ${availableVerbs.length}/${ALL_VERB_SPECS.length} curated verbs as MCP tools\n`);
108
- const server = new McpServer({
109
- name: "echopai",
110
- version: CLI_VERSION,
111
- title: "EchoPai",
112
- }, {
113
- capabilities: { tools: {} },
114
- });
115
- const handlerCtx = {
116
- baseUrl: creds.baseUrl,
117
- bearer: creds.key,
118
- cliVersion: CLI_VERSION,
119
- channel: "mcp",
120
- };
121
- for (const spec of availableVerbs) {
122
- server.registerTool(spec.name, {
123
- description: spec.description,
124
- inputSchema: spec.inputSchema,
125
- }, async (args) => {
126
- try {
127
- const envelope = await spec.handler(args, handlerCtx);
128
- return envelopeToToolResponse(envelope);
129
- }
130
- catch (e) {
131
- return errorToToolResponse(e);
132
- }
133
- });
134
- }
135
- const transport = new StdioServerTransport();
136
- await server.connect(transport);
137
- process.stderr.write("[mcp] connected on stdio; waiting for messages\n");
138
- // The server runs until stdin closes; SDK handles shutdown.
139
- });
140
- return mcp;
141
- }
package/dist/tools/raw.js DELETED
@@ -1,96 +0,0 @@
1
- /**
2
- * `echopai raw call <method> <path> [--data '{...}'] [--query k=v ...]`
3
- *
4
- * Raw HTTP passthrough — 给未 codegen 的端点 / debug / 应急用。
5
- * 跳过 Ajv pre-flight 校验、跳过 envelope 解析;行为 = curl + Bearer 注入 + base URL。
6
- *
7
- * 自动注入 X-Client / X-Client-Channel / X-Request-Id (与 spec-driven 调用同源),
8
- * 这样 server 端 audit 能识别这是 CLI 流量。
9
- *
10
- * `echopai api call ...` 保留为 deprecated alias 一个 release (tools/api.ts);
11
- * 下一个 major 移除。
12
- */
13
- import { Command } from "commander";
14
- import { fetch as undiciFetch } from "undici";
15
- import { resolveCredentials, AuthMissingError } from "../runtime/auth.js";
16
- import { buildHttpHeaders } from "../runtime/http.js";
17
- import { CLI_VERSION } from "../version.js";
18
- export function buildRawCallCommand() {
19
- const call = new Command("call")
20
- .description("Raw HTTP passthrough (arbitrary <method> <path>; debug / non-codegen endpoints)")
21
- .argument("<method>", "HTTP method (GET|POST|PUT|PATCH|DELETE|HEAD)")
22
- .argument("<path>", "URL path (relative; base URL injected from credential)")
23
- .option("-d, --data <json>", "POST/PUT/PATCH body (JSON string)")
24
- .option("-q, --query <kv...>", "Query string entries: k=v")
25
- .option("--header <kv...>", "Extra headers: K=V");
26
- call.action(async (method, urlPath, opts) => {
27
- const m = method.toUpperCase();
28
- if (!["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"].includes(m)) {
29
- return die("invalid_args", `Unknown HTTP method: ${method}`, 1);
30
- }
31
- let creds;
32
- try {
33
- creds = resolveCredentials({});
34
- }
35
- catch (e) {
36
- if (e instanceof AuthMissingError) {
37
- return die("auth_missing", e.message, 1, e.recovery_hint);
38
- }
39
- throw e;
40
- }
41
- let url = creds.baseUrl + (urlPath.startsWith("/") ? urlPath : "/" + urlPath);
42
- if (opts.query?.length) {
43
- const qs = opts.query
44
- .map((kv) => {
45
- const idx = kv.indexOf("=");
46
- if (idx === -1)
47
- return null;
48
- return `${encodeURIComponent(kv.slice(0, idx))}=${encodeURIComponent(kv.slice(idx + 1))}`;
49
- })
50
- .filter((s) => s !== null)
51
- .join("&");
52
- if (qs)
53
- url += (url.includes("?") ? "&" : "?") + qs;
54
- }
55
- // Use centralized header builder (Phase 1.1) — automatically injects
56
- // X-Client / X-Client-Channel / X-Request-Id. Raw passthrough should
57
- // still be tagged as CLI traffic in server audit.
58
- const { headers } = buildHttpHeaders({
59
- bearer: creds.key,
60
- cliVersion: CLI_VERSION,
61
- });
62
- if (opts.header) {
63
- for (const kv of opts.header) {
64
- const idx = kv.indexOf("=");
65
- if (idx === -1)
66
- continue;
67
- headers.set(kv.slice(0, idx), kv.slice(idx + 1));
68
- }
69
- }
70
- const init = { method: m, headers };
71
- if (opts.data) {
72
- if (!headers.has("content-type")) {
73
- headers.set("content-type", "application/json");
74
- }
75
- init.body = opts.data;
76
- }
77
- const res = await undiciFetch(url, init);
78
- const body = await res.text();
79
- process.stdout.write(body + (body.endsWith("\n") ? "" : "\n"));
80
- process.exit(res.status >= 400 && res.status < 500
81
- ? 1
82
- : res.status >= 500
83
- ? 2
84
- : 0);
85
- });
86
- return call;
87
- }
88
- function die(code, message, exitCode, recovery_hint) {
89
- const env = {
90
- error: { code, message, retryable: false },
91
- };
92
- if (recovery_hint)
93
- env.error.recovery_hint = recovery_hint;
94
- process.stderr.write(JSON.stringify(env) + "\n");
95
- process.exit(exitCode);
96
- }