@echofiles/echo-pdf 0.4.3 → 0.6.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.
- package/LICENSE +201 -0
- package/README.md +85 -562
- package/bin/echo-pdf.js +130 -525
- package/dist/file-utils.d.ts +0 -3
- package/dist/file-utils.js +0 -18
- package/dist/local/document.d.ts +10 -0
- package/dist/local/document.js +133 -0
- package/dist/local/index.d.ts +3 -135
- package/dist/local/index.js +2 -555
- package/dist/local/semantic.d.ts +2 -0
- package/dist/local/semantic.js +231 -0
- package/dist/local/shared.d.ts +50 -0
- package/dist/local/shared.js +173 -0
- package/dist/local/types.d.ts +183 -0
- package/dist/local/types.js +2 -0
- package/dist/node/pdfium-local.js +30 -6
- package/dist/pdf-config.js +2 -65
- package/dist/pdf-types.d.ts +1 -58
- package/dist/types.d.ts +1 -87
- package/echo-pdf.config.json +1 -21
- package/package.json +25 -22
- package/bin/lib/http.js +0 -97
- package/bin/lib/mcp-stdio.js +0 -99
- package/dist/auth.d.ts +0 -18
- package/dist/auth.js +0 -36
- package/dist/core/index.d.ts +0 -50
- package/dist/core/index.js +0 -7
- package/dist/file-ops.d.ts +0 -11
- package/dist/file-ops.js +0 -36
- package/dist/file-store-do.d.ts +0 -36
- package/dist/file-store-do.js +0 -298
- package/dist/http-error.d.ts +0 -9
- package/dist/http-error.js +0 -14
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/mcp-server.d.ts +0 -3
- package/dist/mcp-server.js +0 -124
- package/dist/node/semantic-local.d.ts +0 -16
- package/dist/node/semantic-local.js +0 -113
- package/dist/pdf-agent.d.ts +0 -18
- package/dist/pdf-agent.js +0 -217
- package/dist/pdf-storage.d.ts +0 -8
- package/dist/pdf-storage.js +0 -86
- package/dist/pdfium-engine.d.ts +0 -9
- package/dist/pdfium-engine.js +0 -180
- package/dist/r2-file-store.d.ts +0 -20
- package/dist/r2-file-store.js +0 -176
- package/dist/response-schema.d.ts +0 -15
- package/dist/response-schema.js +0 -159
- package/dist/tool-registry.d.ts +0 -16
- package/dist/tool-registry.js +0 -175
- package/dist/worker.d.ts +0 -7
- package/dist/worker.js +0 -386
- package/scripts/export-fixtures.sh +0 -204
- package/wrangler.toml +0 -19
package/bin/echo-pdf.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { spawn } from "node:child_process"
|
|
3
2
|
import fs from "node:fs"
|
|
4
3
|
import os from "node:os"
|
|
5
4
|
import path from "node:path"
|
|
6
5
|
import { fileURLToPath } from "node:url"
|
|
7
|
-
import { downloadFile, postJson, prepareArgsWithLocalUploads, uploadFile, withUploadedLocalFile } from "./lib/http.js"
|
|
8
|
-
import { runMcpStdio } from "./lib/mcp-stdio.js"
|
|
9
6
|
|
|
10
7
|
const CONFIG_DIR = path.join(os.homedir(), ".config", "echo-pdf-cli")
|
|
11
8
|
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json")
|
|
@@ -14,16 +11,9 @@ const PROJECT_CONFIG_FILE = path.resolve(__dirname, "../echo-pdf.config.json")
|
|
|
14
11
|
const PROJECT_CONFIG = JSON.parse(fs.readFileSync(PROJECT_CONFIG_FILE, "utf-8"))
|
|
15
12
|
const PROVIDER_ENTRIES = Object.entries(PROJECT_CONFIG.providers || {})
|
|
16
13
|
const PROVIDER_ALIASES = PROVIDER_ENTRIES.map(([alias]) => alias)
|
|
17
|
-
const PROVIDER_ALIAS_BY_TYPE = new Map(
|
|
18
|
-
|
|
19
|
-
)
|
|
20
|
-
const PROVIDER_SET_NAMES = Array.from(
|
|
21
|
-
new Set(PROVIDER_ENTRIES.flatMap(([alias, provider]) => [alias, provider.type]))
|
|
22
|
-
)
|
|
14
|
+
const PROVIDER_ALIAS_BY_TYPE = new Map(PROVIDER_ENTRIES.map(([alias, provider]) => [provider.type, alias]))
|
|
15
|
+
const PROVIDER_SET_NAMES = Array.from(new Set(PROVIDER_ENTRIES.flatMap(([alias, provider]) => [alias, provider.type])))
|
|
23
16
|
const PROJECT_DEFAULT_MODEL = String(PROJECT_CONFIG.agent?.defaultModel || "").trim()
|
|
24
|
-
const DEFAULT_WORKER_NAME = process.env.ECHO_PDF_WORKER_NAME || PROJECT_CONFIG.service?.name || "echo-pdf"
|
|
25
|
-
const DEFAULT_SERVICE_URL = process.env.ECHO_PDF_SERVICE_URL || `https://${DEFAULT_WORKER_NAME}.echofilesai.workers.dev`
|
|
26
|
-
const DEFAULT_MCP_HEADER = process.env.ECHO_PDF_MCP_HEADER?.trim() || PROJECT_CONFIG.mcp?.authHeader || "x-mcp-key"
|
|
27
17
|
|
|
28
18
|
const emptyProviders = () =>
|
|
29
19
|
Object.fromEntries(PROVIDER_ALIASES.map((providerAlias) => [providerAlias, { apiKey: "" }]))
|
|
@@ -50,7 +40,6 @@ function resolveDefaultProviderAlias() {
|
|
|
50
40
|
const DEFAULT_PROVIDER_ALIAS = resolveDefaultProviderAlias()
|
|
51
41
|
|
|
52
42
|
const defaultConfig = () => ({
|
|
53
|
-
serviceUrl: DEFAULT_SERVICE_URL,
|
|
54
43
|
profile: "default",
|
|
55
44
|
profiles: {
|
|
56
45
|
default: {
|
|
@@ -68,56 +57,11 @@ const ensureConfig = () => {
|
|
|
68
57
|
}
|
|
69
58
|
}
|
|
70
59
|
|
|
71
|
-
const loadConfig = () => {
|
|
72
|
-
ensureConfig()
|
|
73
|
-
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8"))
|
|
74
|
-
if (!config.profiles || typeof config.profiles !== "object") {
|
|
75
|
-
config.profiles = {}
|
|
76
|
-
}
|
|
77
|
-
if (typeof config.profile !== "string" || !config.profile) {
|
|
78
|
-
config.profile = "default"
|
|
79
|
-
}
|
|
80
|
-
const profile = getProfile(config, config.profile)
|
|
81
|
-
if (typeof profile.defaultProvider !== "string" || !profile.defaultProvider) {
|
|
82
|
-
profile.defaultProvider = DEFAULT_PROVIDER_ALIAS
|
|
83
|
-
}
|
|
84
|
-
if (!profile.providers || typeof profile.providers !== "object") {
|
|
85
|
-
profile.providers = emptyProviders()
|
|
86
|
-
}
|
|
87
|
-
for (const providerAlias of PROVIDER_ALIASES) {
|
|
88
|
-
if (!profile.providers[providerAlias] || typeof profile.providers[providerAlias] !== "object") {
|
|
89
|
-
profile.providers[providerAlias] = { apiKey: "" }
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
if (!profile.models || typeof profile.models !== "object") {
|
|
93
|
-
profile.models = {}
|
|
94
|
-
}
|
|
95
|
-
saveConfig(config)
|
|
96
|
-
return config
|
|
97
|
-
}
|
|
98
|
-
|
|
99
60
|
const saveConfig = (config) => {
|
|
100
61
|
fs.mkdirSync(CONFIG_DIR, { recursive: true })
|
|
101
62
|
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2))
|
|
102
63
|
}
|
|
103
64
|
|
|
104
|
-
const parseFlags = (args) => {
|
|
105
|
-
const flags = {}
|
|
106
|
-
for (let i = 0; i < args.length; i += 1) {
|
|
107
|
-
const token = args[i]
|
|
108
|
-
if (!token?.startsWith("--")) continue
|
|
109
|
-
const key = token.slice(2)
|
|
110
|
-
const next = args[i + 1]
|
|
111
|
-
if (!next || next.startsWith("--")) {
|
|
112
|
-
flags[key] = true
|
|
113
|
-
} else {
|
|
114
|
-
flags[key] = next
|
|
115
|
-
i += 1
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return flags
|
|
119
|
-
}
|
|
120
|
-
|
|
121
65
|
const getProfile = (config, name) => {
|
|
122
66
|
const profileName = name || config.profile || "default"
|
|
123
67
|
if (!config.profiles[profileName]) {
|
|
@@ -141,6 +85,33 @@ const getProfile = (config, name) => {
|
|
|
141
85
|
return profile
|
|
142
86
|
}
|
|
143
87
|
|
|
88
|
+
const loadConfig = () => {
|
|
89
|
+
ensureConfig()
|
|
90
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8"))
|
|
91
|
+
if (!config.profiles || typeof config.profiles !== "object") config.profiles = {}
|
|
92
|
+
if (typeof config.profile !== "string" || !config.profile) config.profile = "default"
|
|
93
|
+
getProfile(config, config.profile)
|
|
94
|
+
saveConfig(config)
|
|
95
|
+
return config
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const parseFlags = (args) => {
|
|
99
|
+
const flags = {}
|
|
100
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
101
|
+
const token = args[i]
|
|
102
|
+
if (!token?.startsWith("--")) continue
|
|
103
|
+
const key = token.slice(2)
|
|
104
|
+
const next = args[i + 1]
|
|
105
|
+
if (!next || next.startsWith("--")) {
|
|
106
|
+
flags[key] = true
|
|
107
|
+
} else {
|
|
108
|
+
flags[key] = next
|
|
109
|
+
i += 1
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return flags
|
|
113
|
+
}
|
|
114
|
+
|
|
144
115
|
const getProfileName = (config, profileName) => profileName || config.profile || "default"
|
|
145
116
|
|
|
146
117
|
const resolveProviderAlias = (profile, explicitProvider) =>
|
|
@@ -154,6 +125,25 @@ const resolveDefaultModel = (profile, providerAlias) => {
|
|
|
154
125
|
return PROJECT_DEFAULT_MODEL
|
|
155
126
|
}
|
|
156
127
|
|
|
128
|
+
const readEnvApiKey = (providerAlias) => {
|
|
129
|
+
const providerConfig = PROJECT_CONFIG.providers?.[providerAlias]
|
|
130
|
+
const keyName = providerConfig?.apiKeyEnv
|
|
131
|
+
if (typeof keyName !== "string" || keyName.trim().length === 0) return ""
|
|
132
|
+
const read = (name) => {
|
|
133
|
+
const value = process.env[name]
|
|
134
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : ""
|
|
135
|
+
}
|
|
136
|
+
const direct = read(keyName)
|
|
137
|
+
if (direct) return direct
|
|
138
|
+
if (keyName.endsWith("_API_KEY")) {
|
|
139
|
+
return read(keyName.replace(/_API_KEY$/, "_KEY"))
|
|
140
|
+
}
|
|
141
|
+
if (keyName.endsWith("_KEY")) {
|
|
142
|
+
return read(keyName.replace(/_KEY$/, "_API_KEY"))
|
|
143
|
+
}
|
|
144
|
+
return ""
|
|
145
|
+
}
|
|
146
|
+
|
|
157
147
|
const buildProviderApiKeys = (config, profileName) => {
|
|
158
148
|
const profile = getProfile(config, profileName)
|
|
159
149
|
const providerApiKeys = {}
|
|
@@ -165,165 +155,41 @@ const buildProviderApiKeys = (config, profileName) => {
|
|
|
165
155
|
return providerApiKeys
|
|
166
156
|
}
|
|
167
157
|
|
|
168
|
-
const
|
|
169
|
-
process.stdout.write(`${JSON.stringify(data, null, 2)}\n`)
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const buildMcpHeaders = () => {
|
|
173
|
-
const token = process.env.ECHO_PDF_MCP_KEY?.trim()
|
|
174
|
-
if (!token) return {}
|
|
175
|
-
return { [DEFAULT_MCP_HEADER]: token }
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const buildModelsRequest = (provider, providerApiKeys) => ({ provider, providerApiKeys })
|
|
179
|
-
|
|
180
|
-
const buildToolCallRequest = (input) => ({
|
|
181
|
-
name: input.tool,
|
|
182
|
-
arguments: input.args,
|
|
183
|
-
provider: input.provider,
|
|
184
|
-
model: input.model,
|
|
185
|
-
providerApiKeys: input.providerApiKeys,
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
const buildMcpRequest = (id, method, params = {}) => ({
|
|
189
|
-
jsonrpc: "2.0",
|
|
190
|
-
id,
|
|
191
|
-
method,
|
|
192
|
-
params,
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
const runDevServer = (port, host) => {
|
|
196
|
-
const wranglerBin = path.resolve(__dirname, "../node_modules/.bin/wrangler")
|
|
197
|
-
const wranglerArgs = ["dev", "--port", String(port), "--ip", host]
|
|
198
|
-
const cmd = fs.existsSync(wranglerBin) ? wranglerBin : "npx"
|
|
199
|
-
const args = fs.existsSync(wranglerBin) ? wranglerArgs : ["-y", "wrangler", ...wranglerArgs]
|
|
200
|
-
const child = spawn(cmd, args, {
|
|
201
|
-
stdio: "inherit",
|
|
202
|
-
env: process.env,
|
|
203
|
-
cwd: process.cwd(),
|
|
204
|
-
})
|
|
205
|
-
child.on("exit", (code, signal) => {
|
|
206
|
-
if (signal) process.kill(process.pid, signal)
|
|
207
|
-
process.exit(code ?? 0)
|
|
208
|
-
})
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const printLocalServiceHints = (host, port) => {
|
|
212
|
-
const resolvedHost = host === "0.0.0.0" ? "127.0.0.1" : host
|
|
213
|
-
const baseUrl = `http://${resolvedHost}:${port}`
|
|
214
|
-
const mcpUrl = `${baseUrl}/mcp`
|
|
215
|
-
process.stdout.write(`\nLocal component endpoints:\n`)
|
|
216
|
-
process.stdout.write(` ECHO_PDF_BASE_URL=${baseUrl}\n`)
|
|
217
|
-
process.stdout.write(` ECHO_PDF_MCP_URL=${mcpUrl}\n`)
|
|
218
|
-
process.stdout.write(`\nExport snippet:\n`)
|
|
219
|
-
process.stdout.write(` export ECHO_PDF_BASE_URL=${baseUrl}\n`)
|
|
220
|
-
process.stdout.write(` export ECHO_PDF_MCP_URL=${mcpUrl}\n\n`)
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const runMcpStdioCommand = async (serviceUrlOverride) => {
|
|
158
|
+
const resolveLocalSemanticContext = (flags) => {
|
|
224
159
|
const config = loadConfig()
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
const parseConfigValue = (raw, type = "auto") => {
|
|
237
|
-
if (type === "string") return String(raw)
|
|
238
|
-
if (type === "number") {
|
|
239
|
-
const n = Number(raw)
|
|
240
|
-
if (!Number.isFinite(n)) throw new Error(`Invalid number: ${raw}`)
|
|
241
|
-
return n
|
|
242
|
-
}
|
|
243
|
-
if (type === "boolean") {
|
|
244
|
-
if (raw === "true") return true
|
|
245
|
-
if (raw === "false") return false
|
|
246
|
-
throw new Error(`Invalid boolean: ${raw}`)
|
|
247
|
-
}
|
|
248
|
-
if (type === "json") {
|
|
249
|
-
return JSON.parse(raw)
|
|
250
|
-
}
|
|
251
|
-
if (raw === "true") return true
|
|
252
|
-
if (raw === "false") return false
|
|
253
|
-
if (raw === "null") return null
|
|
254
|
-
if (/^-?\d+(\.\d+)?$/.test(raw)) return Number(raw)
|
|
255
|
-
if ((raw.startsWith("{") && raw.endsWith("}")) || (raw.startsWith("[") && raw.endsWith("]"))) {
|
|
256
|
-
try {
|
|
257
|
-
return JSON.parse(raw)
|
|
258
|
-
} catch {
|
|
259
|
-
return raw
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
return raw
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const hasPath = (obj, dottedPath) => {
|
|
266
|
-
const parts = dottedPath.split(".").filter(Boolean)
|
|
267
|
-
let cur = obj
|
|
268
|
-
for (const part of parts) {
|
|
269
|
-
if (!cur || typeof cur !== "object" || !(part in cur)) return false
|
|
270
|
-
cur = cur[part]
|
|
271
|
-
}
|
|
272
|
-
return true
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
const setPath = (obj, dottedPath, value) => {
|
|
276
|
-
const parts = dottedPath.split(".").filter(Boolean)
|
|
277
|
-
if (parts.length === 0) throw new Error("config key is required")
|
|
278
|
-
let cur = obj
|
|
279
|
-
for (let i = 0; i < parts.length - 1; i += 1) {
|
|
280
|
-
const part = parts[i]
|
|
281
|
-
if (!cur[part] || typeof cur[part] !== "object" || Array.isArray(cur[part])) {
|
|
282
|
-
cur[part] = {}
|
|
283
|
-
}
|
|
284
|
-
cur = cur[part]
|
|
160
|
+
const profileName = getProfileName(config, flags.profile)
|
|
161
|
+
const profile = getProfile(config, profileName)
|
|
162
|
+
const provider = resolveProviderAlias(profile, flags.provider)
|
|
163
|
+
const model = typeof flags.model === "string" ? flags.model.trim() : resolveDefaultModel(profile, provider)
|
|
164
|
+
if (!model) {
|
|
165
|
+
throw new Error(
|
|
166
|
+
[
|
|
167
|
+
`semantic requires a configured model for provider "${provider}".`,
|
|
168
|
+
`Pass \`--model <model-id>\`, or run \`echo-pdf model set --provider ${provider} --model <model-id>${profileName ? ` --profile ${profileName}` : ""}\`.`,
|
|
169
|
+
].join(" ")
|
|
170
|
+
)
|
|
285
171
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
172
|
+
const providerApiKeys = buildProviderApiKeys(config, profileName)
|
|
173
|
+
const configuredApiKey = typeof providerApiKeys[provider] === "string" ? providerApiKeys[provider].trim() : ""
|
|
174
|
+
if (!configuredApiKey && !readEnvApiKey(provider)) {
|
|
175
|
+
const apiKeyEnv = PROJECT_CONFIG.providers?.[provider]?.apiKeyEnv || "PROVIDER_API_KEY"
|
|
176
|
+
throw new Error(
|
|
177
|
+
[
|
|
178
|
+
`semantic requires an API key for provider "${provider}".`,
|
|
179
|
+
`Run \`echo-pdf provider set --provider ${provider} --api-key <KEY>${profileName ? ` --profile ${profileName}` : ""}\``,
|
|
180
|
+
`or export \`${apiKeyEnv}\` before running the VL-first semantic path.`,
|
|
181
|
+
].join(" ")
|
|
182
|
+
)
|
|
298
183
|
}
|
|
299
|
-
return
|
|
184
|
+
return { provider, model, providerApiKeys }
|
|
300
185
|
}
|
|
301
186
|
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
const nextLine = `ECHO_PDF_CONFIG_JSON=${serialized}`
|
|
305
|
-
let lines = []
|
|
306
|
-
if (fs.existsSync(devVarsPath)) {
|
|
307
|
-
lines = fs.readFileSync(devVarsPath, "utf-8").split(/\r?\n/)
|
|
308
|
-
let replaced = false
|
|
309
|
-
lines = lines.map((line) => {
|
|
310
|
-
if (line.startsWith("ECHO_PDF_CONFIG_JSON=")) {
|
|
311
|
-
replaced = true
|
|
312
|
-
return nextLine
|
|
313
|
-
}
|
|
314
|
-
return line
|
|
315
|
-
})
|
|
316
|
-
if (!replaced) {
|
|
317
|
-
if (lines.length > 0 && lines[lines.length - 1].trim().length !== 0) lines.push("")
|
|
318
|
-
lines.push(nextLine)
|
|
319
|
-
}
|
|
320
|
-
} else {
|
|
321
|
-
lines = [nextLine]
|
|
322
|
-
}
|
|
323
|
-
fs.writeFileSync(devVarsPath, lines.join("\n"))
|
|
187
|
+
const print = (data) => {
|
|
188
|
+
process.stdout.write(`${JSON.stringify(data, null, 2)}\n`)
|
|
324
189
|
}
|
|
325
190
|
|
|
326
191
|
const LOCAL_DOCUMENT_DIST_ENTRY = new URL("../dist/local/index.js", import.meta.url)
|
|
192
|
+
const LOCAL_DOCUMENT_DIST_PATH = fileURLToPath(LOCAL_DOCUMENT_DIST_ENTRY)
|
|
327
193
|
const LOCAL_DOCUMENT_SOURCE_ENTRY = new URL("../src/local/index.ts", import.meta.url)
|
|
328
194
|
const IS_BUN_RUNTIME = typeof process.versions?.bun === "string"
|
|
329
195
|
const SHOULD_PREFER_SOURCE_DOCUMENT_API = process.env.ECHO_PDF_SOURCE_DEV === "1"
|
|
@@ -334,45 +200,54 @@ const loadLocalDocumentApi = async () => {
|
|
|
334
200
|
return import(LOCAL_DOCUMENT_SOURCE_ENTRY.href)
|
|
335
201
|
}
|
|
336
202
|
throw new Error(
|
|
337
|
-
"
|
|
338
|
-
"Use `npm run
|
|
203
|
+
"Internal source-checkout CLI dev mode requires Bun and src/local/index.ts. " +
|
|
204
|
+
"Use `npm run cli:dev -- <primitive> ...` only from a source checkout."
|
|
339
205
|
)
|
|
340
206
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
throw new Error(
|
|
347
|
-
"Local document commands require built artifacts in a source checkout. " +
|
|
348
|
-
"Run `npm run build` first, use `npm run document:dev -- <command> ...` in a source checkout, or install the published package."
|
|
349
|
-
)
|
|
350
|
-
}
|
|
351
|
-
throw error
|
|
207
|
+
if (!fs.existsSync(LOCAL_DOCUMENT_DIST_PATH)) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
"Local primitive commands require built artifacts in a source checkout. " +
|
|
210
|
+
"Run `npm run build` first, use the internal `npm run cli:dev -- <primitive> ...` path in this repo, or install the published package."
|
|
211
|
+
)
|
|
352
212
|
}
|
|
213
|
+
return import(LOCAL_DOCUMENT_DIST_ENTRY.href)
|
|
353
214
|
}
|
|
354
215
|
|
|
355
|
-
const LOCAL_PRIMITIVE_COMMANDS = ["document", "structure", "semantic", "page", "render"
|
|
356
|
-
const
|
|
216
|
+
const LOCAL_PRIMITIVE_COMMANDS = ["document", "structure", "semantic", "page", "render"]
|
|
217
|
+
const REMOVED_DOCUMENT_ALIAS_TO_PRIMITIVE = {
|
|
218
|
+
index: "document",
|
|
219
|
+
get: "document",
|
|
220
|
+
structure: "structure",
|
|
221
|
+
semantic: "semantic",
|
|
222
|
+
page: "page",
|
|
223
|
+
render: "render",
|
|
224
|
+
}
|
|
357
225
|
|
|
358
|
-
const
|
|
226
|
+
const isRemovedDocumentAlias = (value) =>
|
|
227
|
+
typeof value === "string" && Object.hasOwn(REMOVED_DOCUMENT_ALIAS_TO_PRIMITIVE, value)
|
|
228
|
+
|
|
229
|
+
const removedDocumentAliasMessage = (alias) => {
|
|
230
|
+
const primitive = REMOVED_DOCUMENT_ALIAS_TO_PRIMITIVE[alias]
|
|
231
|
+
return `Legacy \`document ${alias}\` was removed. Use \`echo-pdf ${primitive} <file.pdf>\` instead.`
|
|
232
|
+
}
|
|
359
233
|
|
|
360
234
|
const readDocumentPrimitiveArgs = (command, subcommand, rest) => {
|
|
361
|
-
if (command === "document"
|
|
362
|
-
|
|
235
|
+
if (command === "document") {
|
|
236
|
+
if (isRemovedDocumentAlias(subcommand) && typeof rest[0] === "string" && !rest[0].startsWith("--")) {
|
|
237
|
+
throw new Error(removedDocumentAliasMessage(subcommand))
|
|
238
|
+
}
|
|
363
239
|
return {
|
|
364
|
-
primitive,
|
|
365
|
-
pdfPath:
|
|
240
|
+
primitive: "document",
|
|
241
|
+
pdfPath: subcommand,
|
|
366
242
|
}
|
|
367
243
|
}
|
|
368
244
|
return {
|
|
369
245
|
primitive: command,
|
|
370
|
-
pdfPath:
|
|
246
|
+
pdfPath: rest[0],
|
|
371
247
|
}
|
|
372
248
|
}
|
|
373
249
|
|
|
374
250
|
const runLocalPrimitiveCommand = async (command, subcommand, rest, flags) => {
|
|
375
|
-
const local = await loadLocalDocumentApi()
|
|
376
251
|
const { primitive, pdfPath } = readDocumentPrimitiveArgs(command, subcommand, rest)
|
|
377
252
|
const workspaceDir = typeof flags.workspace === "string" ? flags.workspace : undefined
|
|
378
253
|
const forceRefresh = flags["force-refresh"] === true
|
|
@@ -383,26 +258,28 @@ const runLocalPrimitiveCommand = async (command, subcommand, rest, flags) => {
|
|
|
383
258
|
}
|
|
384
259
|
|
|
385
260
|
if (primitive === "document") {
|
|
386
|
-
const
|
|
387
|
-
print(
|
|
261
|
+
const local = await loadLocalDocumentApi()
|
|
262
|
+
print(await local.get_document({ pdfPath, workspaceDir, forceRefresh }))
|
|
388
263
|
return
|
|
389
264
|
}
|
|
390
265
|
|
|
391
266
|
if (primitive === "structure") {
|
|
392
|
-
const
|
|
393
|
-
print(
|
|
267
|
+
const local = await loadLocalDocumentApi()
|
|
268
|
+
print(await local.get_document_structure({ pdfPath, workspaceDir, forceRefresh }))
|
|
394
269
|
return
|
|
395
270
|
}
|
|
396
271
|
|
|
397
272
|
if (primitive === "semantic") {
|
|
398
|
-
const
|
|
273
|
+
const semanticContext = resolveLocalSemanticContext(flags)
|
|
274
|
+
const local = await loadLocalDocumentApi()
|
|
275
|
+
print(await local.get_semantic_document_structure({
|
|
399
276
|
pdfPath,
|
|
400
277
|
workspaceDir,
|
|
401
278
|
forceRefresh,
|
|
402
|
-
provider:
|
|
403
|
-
model:
|
|
404
|
-
|
|
405
|
-
|
|
279
|
+
provider: semanticContext.provider,
|
|
280
|
+
model: semanticContext.model,
|
|
281
|
+
providerApiKeys: semanticContext.providerApiKeys,
|
|
282
|
+
}))
|
|
406
283
|
return
|
|
407
284
|
}
|
|
408
285
|
|
|
@@ -412,29 +289,14 @@ const runLocalPrimitiveCommand = async (command, subcommand, rest, flags) => {
|
|
|
412
289
|
}
|
|
413
290
|
|
|
414
291
|
if (primitive === "page") {
|
|
415
|
-
const
|
|
416
|
-
print(
|
|
292
|
+
const local = await loadLocalDocumentApi()
|
|
293
|
+
print(await local.get_page_content({ pdfPath, workspaceDir, forceRefresh, pageNumber }))
|
|
417
294
|
return
|
|
418
295
|
}
|
|
419
296
|
|
|
420
297
|
if (primitive === "render") {
|
|
421
|
-
const
|
|
422
|
-
print(
|
|
423
|
-
return
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
if (primitive === "ocr") {
|
|
427
|
-
const data = await local.get_page_ocr({
|
|
428
|
-
pdfPath,
|
|
429
|
-
workspaceDir,
|
|
430
|
-
forceRefresh,
|
|
431
|
-
pageNumber,
|
|
432
|
-
renderScale,
|
|
433
|
-
provider: typeof flags.provider === "string" ? flags.provider : undefined,
|
|
434
|
-
model: typeof flags.model === "string" ? flags.model : undefined,
|
|
435
|
-
prompt: typeof flags.prompt === "string" ? flags.prompt : undefined,
|
|
436
|
-
})
|
|
437
|
-
print(data)
|
|
298
|
+
const local = await loadLocalDocumentApi()
|
|
299
|
+
print(await local.get_page_render({ pdfPath, workspaceDir, forceRefresh, pageNumber, renderScale }))
|
|
438
300
|
return
|
|
439
301
|
}
|
|
440
302
|
|
|
@@ -443,114 +305,19 @@ const runLocalPrimitiveCommand = async (command, subcommand, rest, flags) => {
|
|
|
443
305
|
|
|
444
306
|
const usage = () => {
|
|
445
307
|
process.stdout.write(`echo-pdf CLI\n\n`)
|
|
446
|
-
process.stdout.write(`
|
|
308
|
+
process.stdout.write(`Primary local primitive commands:\n`)
|
|
447
309
|
process.stdout.write(` document <file.pdf> [--workspace DIR] [--force-refresh]\n`)
|
|
448
310
|
process.stdout.write(` structure <file.pdf> [--workspace DIR] [--force-refresh]\n`)
|
|
449
|
-
process.stdout.write(` semantic <file.pdf> [--provider alias] [--model model] [--workspace DIR] [--force-refresh]\n`)
|
|
311
|
+
process.stdout.write(` semantic <file.pdf> [--provider alias] [--model model] [--profile name] [--workspace DIR] [--force-refresh]\n`)
|
|
450
312
|
process.stdout.write(` page <file.pdf> --page <N> [--workspace DIR] [--force-refresh]\n`)
|
|
451
313
|
process.stdout.write(` render <file.pdf> --page <N> [--scale N] [--workspace DIR] [--force-refresh]\n`)
|
|
452
|
-
process.stdout.write(
|
|
453
|
-
process.stdout.write(`\nCompatibility / existing service commands:\n`)
|
|
454
|
-
process.stdout.write(` init [--service-url URL]\n`)
|
|
455
|
-
process.stdout.write(` dev [--port 8788] [--host 127.0.0.1]\n`)
|
|
314
|
+
process.stdout.write(`\nLocal config commands:\n`)
|
|
456
315
|
process.stdout.write(` provider set --provider <${PROVIDER_SET_NAMES.join("|")}> --api-key <KEY> [--profile name]\n`)
|
|
457
316
|
process.stdout.write(` provider use --provider <${PROVIDER_ALIASES.join("|")}> [--profile name]\n`)
|
|
458
317
|
process.stdout.write(` provider list [--profile name]\n`)
|
|
459
|
-
process.stdout.write(` models [--provider alias] [--profile name]\n`)
|
|
460
|
-
process.stdout.write(` config set --key <dotted.path> --value <value> [--type auto|string|number|boolean|json] [--dev-vars .dev.vars]\n`)
|
|
461
318
|
process.stdout.write(` model set --model <model-id> [--provider alias] [--profile name]\n`)
|
|
462
319
|
process.stdout.write(` model get [--provider alias] [--profile name]\n`)
|
|
463
320
|
process.stdout.write(` model list [--profile name]\n`)
|
|
464
|
-
process.stdout.write(` tools\n`)
|
|
465
|
-
process.stdout.write(` call --tool <name> --args '<json>' [--provider alias] [--model model] [--profile name] [--auto-upload]\n`)
|
|
466
|
-
process.stdout.write(` document get <file.pdf> [--workspace DIR] [--force-refresh]\n`)
|
|
467
|
-
process.stdout.write(` document structure <file.pdf> [--workspace DIR] [--force-refresh]\n`)
|
|
468
|
-
process.stdout.write(` document semantic <file.pdf> [--provider alias] [--model model] [--workspace DIR] [--force-refresh]\n`)
|
|
469
|
-
process.stdout.write(` document page <file.pdf> --page <N> [--workspace DIR] [--force-refresh]\n`)
|
|
470
|
-
process.stdout.write(` document render <file.pdf> --page <N> [--scale N] [--workspace DIR] [--force-refresh]\n`)
|
|
471
|
-
process.stdout.write(` document ocr <file.pdf> --page <N> [--scale N] [--provider alias] [--model model] [--prompt text] [--workspace DIR] [--force-refresh]\n`)
|
|
472
|
-
process.stdout.write(` file upload <local.pdf>\n`)
|
|
473
|
-
process.stdout.write(` file get --file-id <id> --out <path>\n`)
|
|
474
|
-
process.stdout.write(` mcp initialize\n`)
|
|
475
|
-
process.stdout.write(` mcp tools\n`)
|
|
476
|
-
process.stdout.write(` mcp call --tool <name> --args '<json>'\n`)
|
|
477
|
-
process.stdout.write(` mcp-stdio [--service-url URL]\n`)
|
|
478
|
-
process.stdout.write(` mcp stdio\n`)
|
|
479
|
-
process.stdout.write(` setup add <claude-desktop|claude-code|cursor|cline|windsurf|gemini|json>\n`)
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
const setupSnippet = (tool, serviceUrl, mode = "http") => {
|
|
483
|
-
if (mode === "stdio") {
|
|
484
|
-
return {
|
|
485
|
-
mcpServers: {
|
|
486
|
-
"echo-pdf": {
|
|
487
|
-
command: "echo-pdf",
|
|
488
|
-
args: ["mcp-stdio"],
|
|
489
|
-
env: {
|
|
490
|
-
ECHO_PDF_SERVICE_URL: serviceUrl,
|
|
491
|
-
},
|
|
492
|
-
},
|
|
493
|
-
},
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
const transport = {
|
|
497
|
-
type: "streamable-http",
|
|
498
|
-
url: `${serviceUrl}/mcp`,
|
|
499
|
-
}
|
|
500
|
-
if (tool === "json") {
|
|
501
|
-
return {
|
|
502
|
-
mcpServers: {
|
|
503
|
-
"echo-pdf": transport,
|
|
504
|
-
},
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
if (tool === "claude-desktop") {
|
|
508
|
-
return {
|
|
509
|
-
file: "claude_desktop_config.json",
|
|
510
|
-
snippet: {
|
|
511
|
-
mcpServers: {
|
|
512
|
-
"echo-pdf": transport,
|
|
513
|
-
},
|
|
514
|
-
},
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
if (tool === "cursor") {
|
|
518
|
-
return {
|
|
519
|
-
file: "~/.cursor/mcp.json",
|
|
520
|
-
snippet: {
|
|
521
|
-
mcpServers: {
|
|
522
|
-
"echo-pdf": transport,
|
|
523
|
-
},
|
|
524
|
-
},
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
if (tool === "cline") {
|
|
528
|
-
return {
|
|
529
|
-
file: "~/.cline/mcp_settings.json",
|
|
530
|
-
snippet: {
|
|
531
|
-
mcpServers: {
|
|
532
|
-
"echo-pdf": transport,
|
|
533
|
-
},
|
|
534
|
-
},
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
if (tool === "windsurf") {
|
|
538
|
-
return {
|
|
539
|
-
file: "~/.codeium/windsurf/mcp_config.json",
|
|
540
|
-
snippet: {
|
|
541
|
-
mcpServers: {
|
|
542
|
-
"echo-pdf": transport,
|
|
543
|
-
},
|
|
544
|
-
},
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
if (tool === "claude-code" || tool === "gemini") {
|
|
548
|
-
return {
|
|
549
|
-
note: "If your tool does not support streamable-http directly, use an HTTP-to-stdio MCP bridge (for example mcp-remote) and point it to /mcp.",
|
|
550
|
-
url: `${serviceUrl}/mcp`,
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
throw new Error(`Unsupported tool: ${tool}`)
|
|
554
321
|
}
|
|
555
322
|
|
|
556
323
|
const main = async () => {
|
|
@@ -563,34 +330,16 @@ const main = async () => {
|
|
|
563
330
|
const [command, ...raw] = argv
|
|
564
331
|
let subcommand = ""
|
|
565
332
|
let rest = raw
|
|
566
|
-
if (["provider", "
|
|
333
|
+
if (["provider", "model", "document"].includes(command)) {
|
|
567
334
|
subcommand = raw[0] || ""
|
|
568
335
|
rest = raw.slice(1)
|
|
569
336
|
}
|
|
570
337
|
const flags = parseFlags(rest)
|
|
571
338
|
|
|
572
|
-
if (command === "
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
saveConfig(config)
|
|
577
|
-
}
|
|
578
|
-
print({ ok: true, configFile: CONFIG_FILE, serviceUrl: config.serviceUrl })
|
|
579
|
-
return
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
if (command === "dev") {
|
|
583
|
-
const port = typeof flags.port === "string" ? Number(flags.port) : 8788
|
|
584
|
-
const host = typeof flags.host === "string" ? flags.host : "127.0.0.1"
|
|
585
|
-
if (!Number.isFinite(port) || port <= 0) throw new Error("dev --port must be positive number")
|
|
586
|
-
printLocalServiceHints(host, Math.floor(port))
|
|
587
|
-
runDevServer(Math.floor(port), host)
|
|
588
|
-
return
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
if (command === "mcp-stdio") {
|
|
592
|
-
await runMcpStdioCommand(typeof flags["service-url"] === "string" ? flags["service-url"] : undefined)
|
|
593
|
-
return
|
|
339
|
+
if (command === "ocr") {
|
|
340
|
+
throw new Error(
|
|
341
|
+
"`echo-pdf ocr` was removed from the first-class CLI surface. OCR is migration-only and no longer a supported primary command."
|
|
342
|
+
)
|
|
594
343
|
}
|
|
595
344
|
|
|
596
345
|
if (command === "provider" && subcommand === "set") {
|
|
@@ -633,45 +382,6 @@ const main = async () => {
|
|
|
633
382
|
return
|
|
634
383
|
}
|
|
635
384
|
|
|
636
|
-
if (command === "models") {
|
|
637
|
-
const config = loadConfig()
|
|
638
|
-
const profileName = getProfileName(config, flags.profile)
|
|
639
|
-
const profile = getProfile(config, profileName)
|
|
640
|
-
const provider = flags.provider ? resolveProviderAliasInput(flags.provider) : resolveProviderAlias(profile, flags.provider)
|
|
641
|
-
const providerApiKeys = buildProviderApiKeys(config, profileName)
|
|
642
|
-
const data = await postJson(`${config.serviceUrl}/providers/models`, buildModelsRequest(provider, providerApiKeys))
|
|
643
|
-
print(data)
|
|
644
|
-
return
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
if (command === "config" && subcommand === "set") {
|
|
648
|
-
const key = flags.key
|
|
649
|
-
const rawValue = flags.value
|
|
650
|
-
if (typeof key !== "string" || key.trim().length === 0) {
|
|
651
|
-
throw new Error("config set requires --key")
|
|
652
|
-
}
|
|
653
|
-
if (typeof rawValue !== "string") {
|
|
654
|
-
throw new Error("config set requires --value")
|
|
655
|
-
}
|
|
656
|
-
const type = typeof flags.type === "string" ? flags.type : "auto"
|
|
657
|
-
if (!["auto", "string", "number", "boolean", "json"].includes(type)) {
|
|
658
|
-
throw new Error("config set --type must be one of auto|string|number|boolean|json")
|
|
659
|
-
}
|
|
660
|
-
const devVarsPath = typeof flags["dev-vars"] === "string"
|
|
661
|
-
? path.resolve(process.cwd(), flags["dev-vars"])
|
|
662
|
-
: path.resolve(process.cwd(), ".dev.vars")
|
|
663
|
-
|
|
664
|
-
const baseConfig = readDevVarsConfigJson(devVarsPath) || JSON.parse(JSON.stringify(PROJECT_CONFIG))
|
|
665
|
-
if (!hasPath(PROJECT_CONFIG, key)) {
|
|
666
|
-
throw new Error(`Unknown config key: ${key}`)
|
|
667
|
-
}
|
|
668
|
-
const value = parseConfigValue(rawValue, type)
|
|
669
|
-
setPath(baseConfig, key, value)
|
|
670
|
-
writeDevVarsConfigJson(devVarsPath, baseConfig)
|
|
671
|
-
print({ ok: true, key, value, devVarsPath })
|
|
672
|
-
return
|
|
673
|
-
}
|
|
674
|
-
|
|
675
385
|
if (command === "model" && subcommand === "set") {
|
|
676
386
|
const model = flags.model
|
|
677
387
|
if (typeof model !== "string" || model.length === 0) {
|
|
@@ -710,116 +420,11 @@ const main = async () => {
|
|
|
710
420
|
return
|
|
711
421
|
}
|
|
712
422
|
|
|
713
|
-
if (command
|
|
714
|
-
const config = loadConfig()
|
|
715
|
-
const response = await fetch(`${config.serviceUrl}/tools/catalog`)
|
|
716
|
-
const data = await response.json()
|
|
717
|
-
if (!response.ok) throw new Error(JSON.stringify(data))
|
|
718
|
-
print(data)
|
|
719
|
-
return
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
if (LOCAL_PRIMITIVE_COMMANDS.includes(command) || (command === "document" && isLegacyDocumentSubcommand(subcommand))) {
|
|
423
|
+
if (LOCAL_PRIMITIVE_COMMANDS.includes(command)) {
|
|
723
424
|
await runLocalPrimitiveCommand(command, subcommand, rest, flags)
|
|
724
425
|
return
|
|
725
426
|
}
|
|
726
427
|
|
|
727
|
-
if (command === "call") {
|
|
728
|
-
const config = loadConfig()
|
|
729
|
-
const profileName = getProfileName(config, flags.profile)
|
|
730
|
-
const profile = getProfile(config, profileName)
|
|
731
|
-
const tool = flags.tool
|
|
732
|
-
if (typeof tool !== "string") throw new Error("call requires --tool")
|
|
733
|
-
const args = typeof flags.args === "string" ? JSON.parse(flags.args) : {}
|
|
734
|
-
const autoUpload = flags["auto-upload"] === true
|
|
735
|
-
const prepared = await prepareArgsWithLocalUploads(config.serviceUrl, tool, args, {
|
|
736
|
-
autoUpload,
|
|
737
|
-
})
|
|
738
|
-
if (prepared.uploads.length > 0) {
|
|
739
|
-
process.stderr.write(`[echo-pdf] auto-uploaded local files:\n`)
|
|
740
|
-
for (const item of prepared.uploads) {
|
|
741
|
-
process.stderr.write(` - ${item.localPath} -> ${item.fileId} (${item.tool})\n`)
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
const preparedArgs = prepared.args
|
|
745
|
-
const provider = resolveProviderAlias(profile, flags.provider)
|
|
746
|
-
const model = typeof flags.model === "string" ? flags.model : resolveDefaultModel(profile, provider)
|
|
747
|
-
const providerApiKeys = buildProviderApiKeys(config, profileName)
|
|
748
|
-
const payload = buildToolCallRequest({ tool, args: preparedArgs, provider, model, providerApiKeys })
|
|
749
|
-
const data = await postJson(`${config.serviceUrl}/tools/call`, payload)
|
|
750
|
-
print(data)
|
|
751
|
-
return
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
if (command === "file") {
|
|
755
|
-
const action = rest[0] || ""
|
|
756
|
-
const config = loadConfig()
|
|
757
|
-
if (action === "upload") {
|
|
758
|
-
const filePath = rest[1]
|
|
759
|
-
if (!filePath) throw new Error("file upload requires a path")
|
|
760
|
-
const data = await uploadFile(config.serviceUrl, filePath)
|
|
761
|
-
print({
|
|
762
|
-
fileId: data?.file?.id || "",
|
|
763
|
-
filename: data?.file?.filename || path.basename(filePath),
|
|
764
|
-
sizeBytes: data?.file?.sizeBytes || 0,
|
|
765
|
-
file: data?.file || null,
|
|
766
|
-
})
|
|
767
|
-
return
|
|
768
|
-
}
|
|
769
|
-
if (action === "get") {
|
|
770
|
-
const fileId = typeof flags["file-id"] === "string" ? flags["file-id"] : ""
|
|
771
|
-
const out = typeof flags.out === "string" ? flags.out : ""
|
|
772
|
-
if (!fileId || !out) throw new Error("file get requires --file-id and --out")
|
|
773
|
-
const savedTo = await downloadFile(config.serviceUrl, fileId, out)
|
|
774
|
-
print({ ok: true, fileId, savedTo })
|
|
775
|
-
return
|
|
776
|
-
}
|
|
777
|
-
throw new Error("file command supports: upload|get")
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
if (command === "mcp" && subcommand === "initialize") {
|
|
781
|
-
const config = loadConfig()
|
|
782
|
-
const data = await postJson(`${config.serviceUrl}/mcp`, buildMcpRequest(1, "initialize"), buildMcpHeaders())
|
|
783
|
-
print(data)
|
|
784
|
-
return
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
if (command === "mcp" && subcommand === "tools") {
|
|
788
|
-
const config = loadConfig()
|
|
789
|
-
const data = await postJson(`${config.serviceUrl}/mcp`, buildMcpRequest(2, "tools/list"), buildMcpHeaders())
|
|
790
|
-
print(data)
|
|
791
|
-
return
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
if (command === "mcp" && subcommand === "call") {
|
|
795
|
-
const config = loadConfig()
|
|
796
|
-
const tool = flags.tool
|
|
797
|
-
if (typeof tool !== "string") throw new Error("mcp call requires --tool")
|
|
798
|
-
const args = typeof flags.args === "string" ? JSON.parse(flags.args) : {}
|
|
799
|
-
const data = await postJson(
|
|
800
|
-
`${config.serviceUrl}/mcp`,
|
|
801
|
-
buildMcpRequest(3, "tools/call", { name: tool, arguments: args }),
|
|
802
|
-
buildMcpHeaders()
|
|
803
|
-
)
|
|
804
|
-
print(data)
|
|
805
|
-
return
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
if (command === "mcp" && subcommand === "stdio") {
|
|
809
|
-
await runMcpStdioCommand(typeof flags["service-url"] === "string" ? flags["service-url"] : undefined)
|
|
810
|
-
return
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
if (command === "setup" && subcommand === "add") {
|
|
814
|
-
const tool = rest[0]
|
|
815
|
-
if (!tool) throw new Error("setup add requires tool name")
|
|
816
|
-
const config = loadConfig()
|
|
817
|
-
const mode = typeof flags.mode === "string" ? flags.mode : "http"
|
|
818
|
-
if (!["http", "stdio"].includes(mode)) throw new Error("setup add --mode must be http|stdio")
|
|
819
|
-
print(setupSnippet(tool, config.serviceUrl, mode))
|
|
820
|
-
return
|
|
821
|
-
}
|
|
822
|
-
|
|
823
428
|
usage()
|
|
824
429
|
process.exitCode = 1
|
|
825
430
|
}
|