@echofiles/echo-pdf 0.5.0 → 0.7.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 +100 -563
- package/bin/echo-pdf.js +147 -536
- 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 +2 -59
- package/dist/provider-client.js +1 -1
- package/dist/provider-keys.js +4 -1
- package/dist/types.d.ts +2 -88
- package/echo-pdf.config.json +10 -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,42 +1,34 @@
|
|
|
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")
|
|
12
9
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
13
10
|
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
|
-
const PROVIDER_ENTRIES = Object.entries(PROJECT_CONFIG.providers || {})
|
|
16
|
-
const PROVIDER_ALIASES = PROVIDER_ENTRIES.map(([alias]) => alias)
|
|
17
|
-
const PROVIDER_ALIAS_BY_TYPE = new Map(
|
|
18
|
-
PROVIDER_ENTRIES.map(([alias, provider]) => [provider.type, alias])
|
|
19
|
-
)
|
|
20
|
-
const PROVIDER_SET_NAMES = Array.from(
|
|
21
|
-
new Set(PROVIDER_ENTRIES.flatMap(([alias, provider]) => [alias, provider.type]))
|
|
22
|
-
)
|
|
23
12
|
const PROJECT_DEFAULT_MODEL = String(PROJECT_CONFIG.agent?.defaultModel || "").trim()
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
const
|
|
13
|
+
|
|
14
|
+
const getProviderEntries = () => Object.entries(PROJECT_CONFIG.providers || {})
|
|
15
|
+
const getProviderAliases = () => getProviderEntries().map(([alias]) => alias)
|
|
16
|
+
const getProviderAliasByType = () => new Map(getProviderEntries().map(([alias, provider]) => [provider.type, alias]))
|
|
17
|
+
const getProviderSetNames = () => Array.from(new Set(getProviderEntries().flatMap(([alias, provider]) => [alias, provider.type])))
|
|
27
18
|
|
|
28
19
|
const emptyProviders = () =>
|
|
29
|
-
Object.fromEntries(
|
|
20
|
+
Object.fromEntries(getProviderAliases().map((providerAlias) => [providerAlias, { apiKey: "" }]))
|
|
30
21
|
|
|
31
22
|
const resolveProviderAliasInput = (input) => {
|
|
32
23
|
if (typeof input !== "string" || input.trim().length === 0) {
|
|
33
24
|
throw new Error("provider is required")
|
|
34
25
|
}
|
|
35
26
|
const raw = input.trim()
|
|
36
|
-
|
|
37
|
-
|
|
27
|
+
const providerAliases = getProviderAliases()
|
|
28
|
+
if (providerAliases.includes(raw)) return raw
|
|
29
|
+
const fromType = getProviderAliasByType().get(raw)
|
|
38
30
|
if (fromType) return fromType
|
|
39
|
-
throw new Error(`provider must be one of: ${
|
|
31
|
+
throw new Error(`provider must be one of: ${getProviderSetNames().join(", ")}`)
|
|
40
32
|
}
|
|
41
33
|
|
|
42
34
|
function resolveDefaultProviderAlias() {
|
|
@@ -44,13 +36,12 @@ function resolveDefaultProviderAlias() {
|
|
|
44
36
|
if (typeof configured === "string" && configured.trim().length > 0) {
|
|
45
37
|
return resolveProviderAliasInput(configured.trim())
|
|
46
38
|
}
|
|
47
|
-
return
|
|
39
|
+
return getProviderAliases()[0] || "openai"
|
|
48
40
|
}
|
|
49
41
|
|
|
50
42
|
const DEFAULT_PROVIDER_ALIAS = resolveDefaultProviderAlias()
|
|
51
43
|
|
|
52
44
|
const defaultConfig = () => ({
|
|
53
|
-
serviceUrl: DEFAULT_SERVICE_URL,
|
|
54
45
|
profile: "default",
|
|
55
46
|
profiles: {
|
|
56
47
|
default: {
|
|
@@ -68,56 +59,11 @@ const ensureConfig = () => {
|
|
|
68
59
|
}
|
|
69
60
|
}
|
|
70
61
|
|
|
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
62
|
const saveConfig = (config) => {
|
|
100
63
|
fs.mkdirSync(CONFIG_DIR, { recursive: true })
|
|
101
64
|
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2))
|
|
102
65
|
}
|
|
103
66
|
|
|
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
67
|
const getProfile = (config, name) => {
|
|
122
68
|
const profileName = name || config.profile || "default"
|
|
123
69
|
if (!config.profiles[profileName]) {
|
|
@@ -129,7 +75,7 @@ const getProfile = (config, name) => {
|
|
|
129
75
|
}
|
|
130
76
|
const profile = config.profiles[profileName]
|
|
131
77
|
if (!profile.providers || typeof profile.providers !== "object") profile.providers = {}
|
|
132
|
-
for (const providerAlias of
|
|
78
|
+
for (const providerAlias of getProviderAliases()) {
|
|
133
79
|
if (!profile.providers[providerAlias] || typeof profile.providers[providerAlias] !== "object") {
|
|
134
80
|
profile.providers[providerAlias] = { apiKey: "" }
|
|
135
81
|
}
|
|
@@ -141,6 +87,33 @@ const getProfile = (config, name) => {
|
|
|
141
87
|
return profile
|
|
142
88
|
}
|
|
143
89
|
|
|
90
|
+
const loadConfig = () => {
|
|
91
|
+
ensureConfig()
|
|
92
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8"))
|
|
93
|
+
if (!config.profiles || typeof config.profiles !== "object") config.profiles = {}
|
|
94
|
+
if (typeof config.profile !== "string" || !config.profile) config.profile = "default"
|
|
95
|
+
getProfile(config, config.profile)
|
|
96
|
+
saveConfig(config)
|
|
97
|
+
return config
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const parseFlags = (args) => {
|
|
101
|
+
const flags = {}
|
|
102
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
103
|
+
const token = args[i]
|
|
104
|
+
if (!token?.startsWith("--")) continue
|
|
105
|
+
const key = token.slice(2)
|
|
106
|
+
const next = args[i + 1]
|
|
107
|
+
if (typeof next !== "string" || next.startsWith("--")) {
|
|
108
|
+
flags[key] = true
|
|
109
|
+
} else {
|
|
110
|
+
flags[key] = next
|
|
111
|
+
i += 1
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return flags
|
|
115
|
+
}
|
|
116
|
+
|
|
144
117
|
const getProfileName = (config, profileName) => profileName || config.profile || "default"
|
|
145
118
|
|
|
146
119
|
const resolveProviderAlias = (profile, explicitProvider) =>
|
|
@@ -154,10 +127,29 @@ const resolveDefaultModel = (profile, providerAlias) => {
|
|
|
154
127
|
return PROJECT_DEFAULT_MODEL
|
|
155
128
|
}
|
|
156
129
|
|
|
130
|
+
const readEnvApiKey = (providerAlias) => {
|
|
131
|
+
const providerConfig = PROJECT_CONFIG.providers?.[providerAlias]
|
|
132
|
+
const keyName = providerConfig?.apiKeyEnv
|
|
133
|
+
if (typeof keyName !== "string" || keyName.trim().length === 0) return ""
|
|
134
|
+
const read = (name) => {
|
|
135
|
+
const value = process.env[name]
|
|
136
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : ""
|
|
137
|
+
}
|
|
138
|
+
const direct = read(keyName)
|
|
139
|
+
if (direct) return direct
|
|
140
|
+
if (keyName.endsWith("_API_KEY")) {
|
|
141
|
+
return read(keyName.replace(/_API_KEY$/, "_KEY"))
|
|
142
|
+
}
|
|
143
|
+
if (keyName.endsWith("_KEY")) {
|
|
144
|
+
return read(keyName.replace(/_KEY$/, "_API_KEY"))
|
|
145
|
+
}
|
|
146
|
+
return ""
|
|
147
|
+
}
|
|
148
|
+
|
|
157
149
|
const buildProviderApiKeys = (config, profileName) => {
|
|
158
150
|
const profile = getProfile(config, profileName)
|
|
159
151
|
const providerApiKeys = {}
|
|
160
|
-
for (const [providerAlias, providerConfig] of
|
|
152
|
+
for (const [providerAlias, providerConfig] of getProviderEntries()) {
|
|
161
153
|
const apiKey = profile.providers?.[providerAlias]?.apiKey || profile.providers?.[providerConfig.type]?.apiKey || ""
|
|
162
154
|
providerApiKeys[providerAlias] = apiKey
|
|
163
155
|
providerApiKeys[providerConfig.type] = apiKey
|
|
@@ -165,165 +157,41 @@ const buildProviderApiKeys = (config, profileName) => {
|
|
|
165
157
|
return providerApiKeys
|
|
166
158
|
}
|
|
167
159
|
|
|
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) => {
|
|
160
|
+
const resolveLocalSemanticContext = (flags) => {
|
|
224
161
|
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]
|
|
162
|
+
const profileName = getProfileName(config, flags.profile)
|
|
163
|
+
const profile = getProfile(config, profileName)
|
|
164
|
+
const provider = resolveProviderAlias(profile, flags.provider)
|
|
165
|
+
const model = typeof flags.model === "string" ? flags.model.trim() : resolveDefaultModel(profile, provider)
|
|
166
|
+
if (!model) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
[
|
|
169
|
+
`semantic requires a configured model for provider "${provider}".`,
|
|
170
|
+
`Pass \`--model <model-id>\`, or run \`echo-pdf model set --provider ${provider} --model <model-id>${profileName ? ` --profile ${profileName}` : ""}\`.`,
|
|
171
|
+
].join(" ")
|
|
172
|
+
)
|
|
285
173
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
174
|
+
const providerApiKeys = buildProviderApiKeys(config, profileName)
|
|
175
|
+
const configuredApiKey = typeof providerApiKeys[provider] === "string" ? providerApiKeys[provider].trim() : ""
|
|
176
|
+
const apiKeyEnv = PROJECT_CONFIG.providers?.[provider]?.apiKeyEnv || ""
|
|
177
|
+
if (apiKeyEnv && !configuredApiKey && !readEnvApiKey(provider)) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
[
|
|
180
|
+
`semantic requires an API key for provider "${provider}".`,
|
|
181
|
+
`Run \`echo-pdf provider set --provider ${provider} --api-key <KEY>${profileName ? ` --profile ${profileName}` : ""}\``,
|
|
182
|
+
`or export \`${apiKeyEnv}\` before running the VL-first semantic path.`,
|
|
183
|
+
].join(" ")
|
|
184
|
+
)
|
|
298
185
|
}
|
|
299
|
-
return
|
|
186
|
+
return { provider, model, providerApiKeys }
|
|
300
187
|
}
|
|
301
188
|
|
|
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"))
|
|
189
|
+
const print = (data) => {
|
|
190
|
+
process.stdout.write(`${JSON.stringify(data, null, 2)}\n`)
|
|
324
191
|
}
|
|
325
192
|
|
|
326
193
|
const LOCAL_DOCUMENT_DIST_ENTRY = new URL("../dist/local/index.js", import.meta.url)
|
|
194
|
+
const LOCAL_DOCUMENT_DIST_PATH = fileURLToPath(LOCAL_DOCUMENT_DIST_ENTRY)
|
|
327
195
|
const LOCAL_DOCUMENT_SOURCE_ENTRY = new URL("../src/local/index.ts", import.meta.url)
|
|
328
196
|
const IS_BUN_RUNTIME = typeof process.versions?.bun === "string"
|
|
329
197
|
const SHOULD_PREFER_SOURCE_DOCUMENT_API = process.env.ECHO_PDF_SOURCE_DEV === "1"
|
|
@@ -334,45 +202,54 @@ const loadLocalDocumentApi = async () => {
|
|
|
334
202
|
return import(LOCAL_DOCUMENT_SOURCE_ENTRY.href)
|
|
335
203
|
}
|
|
336
204
|
throw new Error(
|
|
337
|
-
"
|
|
338
|
-
"Use `npm run
|
|
205
|
+
"Internal source-checkout CLI dev mode requires Bun and src/local/index.ts. " +
|
|
206
|
+
"Use `npm run cli:dev -- <primitive> ...` only from a source checkout."
|
|
339
207
|
)
|
|
340
208
|
}
|
|
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
|
|
209
|
+
if (!fs.existsSync(LOCAL_DOCUMENT_DIST_PATH)) {
|
|
210
|
+
throw new Error(
|
|
211
|
+
"Local primitive commands require built artifacts in a source checkout. " +
|
|
212
|
+
"Run `npm run build` first, use the internal `npm run cli:dev -- <primitive> ...` path in this repo, or install the published package."
|
|
213
|
+
)
|
|
352
214
|
}
|
|
215
|
+
return import(LOCAL_DOCUMENT_DIST_ENTRY.href)
|
|
353
216
|
}
|
|
354
217
|
|
|
355
|
-
const LOCAL_PRIMITIVE_COMMANDS = ["document", "structure", "semantic", "page", "render"
|
|
356
|
-
const
|
|
218
|
+
const LOCAL_PRIMITIVE_COMMANDS = ["document", "structure", "semantic", "page", "render"]
|
|
219
|
+
const REMOVED_DOCUMENT_ALIAS_TO_PRIMITIVE = {
|
|
220
|
+
index: "document",
|
|
221
|
+
get: "document",
|
|
222
|
+
structure: "structure",
|
|
223
|
+
semantic: "semantic",
|
|
224
|
+
page: "page",
|
|
225
|
+
render: "render",
|
|
226
|
+
}
|
|
357
227
|
|
|
358
|
-
const
|
|
228
|
+
const isRemovedDocumentAlias = (value) =>
|
|
229
|
+
typeof value === "string" && Object.hasOwn(REMOVED_DOCUMENT_ALIAS_TO_PRIMITIVE, value)
|
|
230
|
+
|
|
231
|
+
const removedDocumentAliasMessage = (alias) => {
|
|
232
|
+
const primitive = REMOVED_DOCUMENT_ALIAS_TO_PRIMITIVE[alias]
|
|
233
|
+
return `Legacy \`document ${alias}\` was removed. Use \`echo-pdf ${primitive} <file.pdf>\` instead.`
|
|
234
|
+
}
|
|
359
235
|
|
|
360
236
|
const readDocumentPrimitiveArgs = (command, subcommand, rest) => {
|
|
361
|
-
if (command === "document"
|
|
362
|
-
|
|
237
|
+
if (command === "document") {
|
|
238
|
+
if (isRemovedDocumentAlias(subcommand) && typeof rest[0] === "string" && !rest[0].startsWith("--")) {
|
|
239
|
+
throw new Error(removedDocumentAliasMessage(subcommand))
|
|
240
|
+
}
|
|
363
241
|
return {
|
|
364
|
-
primitive,
|
|
365
|
-
pdfPath:
|
|
242
|
+
primitive: "document",
|
|
243
|
+
pdfPath: subcommand,
|
|
366
244
|
}
|
|
367
245
|
}
|
|
368
246
|
return {
|
|
369
247
|
primitive: command,
|
|
370
|
-
pdfPath:
|
|
248
|
+
pdfPath: rest[0],
|
|
371
249
|
}
|
|
372
250
|
}
|
|
373
251
|
|
|
374
252
|
const runLocalPrimitiveCommand = async (command, subcommand, rest, flags) => {
|
|
375
|
-
const local = await loadLocalDocumentApi()
|
|
376
253
|
const { primitive, pdfPath } = readDocumentPrimitiveArgs(command, subcommand, rest)
|
|
377
254
|
const workspaceDir = typeof flags.workspace === "string" ? flags.workspace : undefined
|
|
378
255
|
const forceRefresh = flags["force-refresh"] === true
|
|
@@ -383,26 +260,28 @@ const runLocalPrimitiveCommand = async (command, subcommand, rest, flags) => {
|
|
|
383
260
|
}
|
|
384
261
|
|
|
385
262
|
if (primitive === "document") {
|
|
386
|
-
const
|
|
387
|
-
print(
|
|
263
|
+
const local = await loadLocalDocumentApi()
|
|
264
|
+
print(await local.get_document({ pdfPath, workspaceDir, forceRefresh }))
|
|
388
265
|
return
|
|
389
266
|
}
|
|
390
267
|
|
|
391
268
|
if (primitive === "structure") {
|
|
392
|
-
const
|
|
393
|
-
print(
|
|
269
|
+
const local = await loadLocalDocumentApi()
|
|
270
|
+
print(await local.get_document_structure({ pdfPath, workspaceDir, forceRefresh }))
|
|
394
271
|
return
|
|
395
272
|
}
|
|
396
273
|
|
|
397
274
|
if (primitive === "semantic") {
|
|
398
|
-
const
|
|
275
|
+
const semanticContext = resolveLocalSemanticContext(flags)
|
|
276
|
+
const local = await loadLocalDocumentApi()
|
|
277
|
+
print(await local.get_semantic_document_structure({
|
|
399
278
|
pdfPath,
|
|
400
279
|
workspaceDir,
|
|
401
280
|
forceRefresh,
|
|
402
|
-
provider:
|
|
403
|
-
model:
|
|
404
|
-
|
|
405
|
-
|
|
281
|
+
provider: semanticContext.provider,
|
|
282
|
+
model: semanticContext.model,
|
|
283
|
+
providerApiKeys: semanticContext.providerApiKeys,
|
|
284
|
+
}))
|
|
406
285
|
return
|
|
407
286
|
}
|
|
408
287
|
|
|
@@ -412,29 +291,14 @@ const runLocalPrimitiveCommand = async (command, subcommand, rest, flags) => {
|
|
|
412
291
|
}
|
|
413
292
|
|
|
414
293
|
if (primitive === "page") {
|
|
415
|
-
const
|
|
416
|
-
print(
|
|
294
|
+
const local = await loadLocalDocumentApi()
|
|
295
|
+
print(await local.get_page_content({ pdfPath, workspaceDir, forceRefresh, pageNumber }))
|
|
417
296
|
return
|
|
418
297
|
}
|
|
419
298
|
|
|
420
299
|
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)
|
|
300
|
+
const local = await loadLocalDocumentApi()
|
|
301
|
+
print(await local.get_page_render({ pdfPath, workspaceDir, forceRefresh, pageNumber, renderScale }))
|
|
438
302
|
return
|
|
439
303
|
}
|
|
440
304
|
|
|
@@ -443,114 +307,23 @@ const runLocalPrimitiveCommand = async (command, subcommand, rest, flags) => {
|
|
|
443
307
|
|
|
444
308
|
const usage = () => {
|
|
445
309
|
process.stdout.write(`echo-pdf CLI\n\n`)
|
|
446
|
-
process.stdout.write(`
|
|
310
|
+
process.stdout.write(`Primary local primitive commands:\n`)
|
|
447
311
|
process.stdout.write(` document <file.pdf> [--workspace DIR] [--force-refresh]\n`)
|
|
448
312
|
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`)
|
|
313
|
+
process.stdout.write(` semantic <file.pdf> [--provider alias] [--model model] [--profile name] [--workspace DIR] [--force-refresh]\n`)
|
|
450
314
|
process.stdout.write(` page <file.pdf> --page <N> [--workspace DIR] [--force-refresh]\n`)
|
|
451
315
|
process.stdout.write(` render <file.pdf> --page <N> [--scale N] [--workspace DIR] [--force-refresh]\n`)
|
|
452
|
-
process.stdout.write(
|
|
453
|
-
process.stdout.write(
|
|
454
|
-
process.stdout.write(`
|
|
455
|
-
process.stdout.write(` dev [--port 8788] [--host 127.0.0.1]\n`)
|
|
456
|
-
process.stdout.write(` provider set --provider <${PROVIDER_SET_NAMES.join("|")}> --api-key <KEY> [--profile name]\n`)
|
|
457
|
-
process.stdout.write(` provider use --provider <${PROVIDER_ALIASES.join("|")}> [--profile name]\n`)
|
|
316
|
+
process.stdout.write(`\nLocal config commands:\n`)
|
|
317
|
+
process.stdout.write(` provider set --provider <${getProviderSetNames().join("|")}> --api-key <KEY> [--profile name]\n`)
|
|
318
|
+
process.stdout.write(` provider use --provider <${getProviderAliases().join("|")}> [--profile name]\n`)
|
|
458
319
|
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
320
|
process.stdout.write(` model set --model <model-id> [--provider alias] [--profile name]\n`)
|
|
462
321
|
process.stdout.write(` model get [--provider alias] [--profile name]\n`)
|
|
463
322
|
process.stdout.write(` model list [--profile name]\n`)
|
|
464
|
-
process.stdout.write(
|
|
465
|
-
process.stdout.write(`
|
|
466
|
-
process.stdout.write(`
|
|
467
|
-
process.stdout.write(`
|
|
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}`)
|
|
323
|
+
process.stdout.write(`\nLocal LLM example (no auth):\n`)
|
|
324
|
+
process.stdout.write(` echo-pdf provider set --provider ollama --api-key \"\"\n`)
|
|
325
|
+
process.stdout.write(` echo-pdf model set --provider ollama --model llava:13b\n`)
|
|
326
|
+
process.stdout.write(` echo-pdf semantic ./sample.pdf --provider ollama\n`)
|
|
554
327
|
}
|
|
555
328
|
|
|
556
329
|
const main = async () => {
|
|
@@ -563,34 +336,16 @@ const main = async () => {
|
|
|
563
336
|
const [command, ...raw] = argv
|
|
564
337
|
let subcommand = ""
|
|
565
338
|
let rest = raw
|
|
566
|
-
if (["provider", "
|
|
339
|
+
if (["provider", "model", "document"].includes(command)) {
|
|
567
340
|
subcommand = raw[0] || ""
|
|
568
341
|
rest = raw.slice(1)
|
|
569
342
|
}
|
|
570
343
|
const flags = parseFlags(rest)
|
|
571
344
|
|
|
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
|
|
345
|
+
if (command === "ocr") {
|
|
346
|
+
throw new Error(
|
|
347
|
+
"`echo-pdf ocr` was removed from the first-class CLI surface. OCR is migration-only and no longer a supported primary command."
|
|
348
|
+
)
|
|
594
349
|
}
|
|
595
350
|
|
|
596
351
|
if (command === "provider" && subcommand === "set") {
|
|
@@ -633,45 +388,6 @@ const main = async () => {
|
|
|
633
388
|
return
|
|
634
389
|
}
|
|
635
390
|
|
|
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
391
|
if (command === "model" && subcommand === "set") {
|
|
676
392
|
const model = flags.model
|
|
677
393
|
if (typeof model !== "string" || model.length === 0) {
|
|
@@ -710,116 +426,11 @@ const main = async () => {
|
|
|
710
426
|
return
|
|
711
427
|
}
|
|
712
428
|
|
|
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))) {
|
|
429
|
+
if (LOCAL_PRIMITIVE_COMMANDS.includes(command)) {
|
|
723
430
|
await runLocalPrimitiveCommand(command, subcommand, rest, flags)
|
|
724
431
|
return
|
|
725
432
|
}
|
|
726
433
|
|
|
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
434
|
usage()
|
|
824
435
|
process.exitCode = 1
|
|
825
436
|
}
|