jefrichat-mcp 0.48.13 → 0.48.14
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/dist/http.js +99 -17
- package/dist/index.js +99 -17
- package/package.json +1 -1
package/dist/http.js
CHANGED
|
@@ -62114,9 +62114,25 @@ function declKey(extra, parts) {
|
|
|
62114
62114
|
return "d-" + createHash2("sha256").update(JSON.stringify([ua, rid, ...parts])).digest("hex").slice(0, 40);
|
|
62115
62115
|
}
|
|
62116
62116
|
var crossBasename = (p) => String(p).split(/[\\/]/).pop() || "file";
|
|
62117
|
+
var DOWNLOAD_DIR_SH = "~/Downloads/jefri";
|
|
62118
|
+
var downloadDirAbs = () => process.env.JEFRI_DOWNLOAD_DIR || np3.join(os4.homedir(), "Downloads", "jefri");
|
|
62119
|
+
var ensureDownloadDir = () => {
|
|
62120
|
+
const dir = downloadDirAbs();
|
|
62121
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
62122
|
+
const st = fs4.lstatSync(dir);
|
|
62123
|
+
if (st.isSymbolicLink() || !st.isDirectory())
|
|
62124
|
+
throw new Error(`refusing to save: ${dir} is a symlink (or not a directory) \u2014 a planted link could redirect downloads anywhere. Remove it and retry.`);
|
|
62125
|
+
return dir;
|
|
62126
|
+
};
|
|
62127
|
+
var safeSaveName = (p) => {
|
|
62128
|
+
const base = String(p ?? "").split(/[\\/]/).pop() ?? "";
|
|
62129
|
+
const clean = base.replace(/[\x00-\x1f\x7f]/g, "").trim();
|
|
62130
|
+
return !clean || clean === "." || clean === ".." ? "" : clean;
|
|
62131
|
+
};
|
|
62117
62132
|
var shPathArg = (p) => p.startsWith("~/") ? `"$HOME"${shq(p.slice(1))}` : shq(p);
|
|
62118
62133
|
var psq = (s) => `'${String(s).replace(/'/g, "''")}'`;
|
|
62119
62134
|
var psPathArg = (p) => p.startsWith("~/") || p.startsWith("~\\") ? `($HOME + ${psq(p.slice(1))})` : psq(p);
|
|
62135
|
+
var isTextyFile = (mime, name) => /^text\/|json|xml|javascript|typescript|markdown|csv|ya?ml|x-sh|toml/i.test(mime) || /\.(md|txt|json|csv|ya?ml|log|ts|tsx|js|jsx|py|rb|go|rs|java|c|h|cpp|sh|sql|toml|env|cfg|ini)$/i.test(name);
|
|
62120
62136
|
var DATAURL_INLINE_MAX = 256 * 1024;
|
|
62121
62137
|
var dataUrlTooBig = (dataUrl) => dataUrl.length > DATAURL_INLINE_MAX * 1.4 ? `That dataUrl is ~${MB(dataUrl.length * 0.75)} MB decoded \u2014 far past the ${Math.round(DATAURL_INLINE_MAX / 1024)}KB inline limit. NEVER read a local file and base64 it through the model. Call this tool again with the file's PATH \u2014 you'll get a single-use upload command that sends it in seconds.` : null;
|
|
62122
62138
|
async function remoteUploadCommand(c, ctx, target, path3, caption, where) {
|
|
@@ -63004,13 +63020,13 @@ ${fp}`);
|
|
|
63004
63020
|
server2.registerTool(
|
|
63005
63021
|
"jefri_download_file",
|
|
63006
63022
|
{
|
|
63007
|
-
title: "
|
|
63008
|
-
description: "
|
|
63023
|
+
title: "Read or save a file someone sent you",
|
|
63024
|
+
description: "Get a file another agent/user sent you. For a DM give the sender's username `from`; for a GROUP file give `groupId` (from jefri_groups / jefri_inbox). Add `fileName` if there are several. TEXT files (code, markdown, JSON, CSV, logs \u2014 up to 256KB) come back with their FULL CONTENT inline, and PDFs come back as EXTRACTED TEXT \u2014 so you can read and discuss both directly, no terminal needed. Only images, video and other binaries return a save command (or view them in the web app). On the local connector, files are saved straight to disk. Use this when the user says 'read the file sara sent', 'download that file', 'what does the doc say', etc.",
|
|
63009
63025
|
inputSchema: {
|
|
63010
63026
|
from: external_exports.string().optional().describe("who sent you the file (their username) \u2014 for a DM"),
|
|
63011
63027
|
groupId: external_exports.string().optional().describe("the group's id \u2014 for a file sent in a group"),
|
|
63012
63028
|
fileName: external_exports.string().optional().describe("which file, if there are several"),
|
|
63013
|
-
savePath: external_exports.string().optional().describe("
|
|
63029
|
+
savePath: external_exports.string().optional().describe("the file NAME to save as (a bare name \u2014 every download lands in ~/Downloads/jefri/, never elsewhere); a taken name gets a -1/-2 suffix")
|
|
63014
63030
|
}
|
|
63015
63031
|
},
|
|
63016
63032
|
async ({ from, groupId, fileName, savePath }) => withClient(async (c) => {
|
|
@@ -63023,26 +63039,92 @@ ${fp}`);
|
|
|
63023
63039
|
const match = fileName ? [...files].reverse().find((m) => m.fileName === fileName || m.fileName?.includes(fileName)) : files[files.length - 1];
|
|
63024
63040
|
if (!match) return fail(`No file named "${fileName}" in ${where}.`);
|
|
63025
63041
|
const hub = ctx.serverUrl.replace(/\/$/, "");
|
|
63026
|
-
const
|
|
63042
|
+
const safeName2 = safeSaveName(match.fileName) || "downloaded.file";
|
|
63043
|
+
const out = safeSaveName(savePath) || safeName2;
|
|
63027
63044
|
if (ctx.local) {
|
|
63028
|
-
const outPath = out.startsWith("~") ? np3.join(os4.homedir(), out.slice(1)) : out;
|
|
63029
63045
|
try {
|
|
63046
|
+
const dlDir = ensureDownloadDir();
|
|
63047
|
+
const outPath = np3.join(dlDir, out);
|
|
63030
63048
|
const r = await fetch(`${hub}/api/files/${match.id}`, { headers: { authorization: `Bearer ${c.token}` } });
|
|
63031
63049
|
if (!r.ok) throw new Error(`download failed (${r.status})`);
|
|
63032
63050
|
const buf = Buffer.from(await r.arrayBuffer());
|
|
63033
|
-
|
|
63034
|
-
|
|
63051
|
+
let finalPath = outPath;
|
|
63052
|
+
const ext = np3.extname(finalPath);
|
|
63053
|
+
const stem = finalPath.slice(0, finalPath.length - ext.length);
|
|
63054
|
+
let written = false;
|
|
63055
|
+
for (let i = 0; i < 100 && !written; i++) {
|
|
63056
|
+
finalPath = i === 0 ? outPath : `${stem}-${i}${ext}`;
|
|
63057
|
+
try {
|
|
63058
|
+
fs4.writeFileSync(finalPath, buf, { flag: "wx" });
|
|
63059
|
+
written = true;
|
|
63060
|
+
} catch (e) {
|
|
63061
|
+
if (e?.code !== "EEXIST") throw e;
|
|
63062
|
+
}
|
|
63063
|
+
}
|
|
63064
|
+
if (!written) return fail(`Couldn't save: 100 files named like ${outPath} already exist here.`);
|
|
63065
|
+
return ok3(`\u2705 Saved \u{1F4CE} ${match.fileName} from ${where} to ${finalPath} (${MB(buf.length)} MB).`);
|
|
63035
63066
|
} catch (e) {
|
|
63036
63067
|
return fail(`Couldn't save the file: ${e?.message ?? e}`);
|
|
63037
63068
|
}
|
|
63038
63069
|
}
|
|
63039
|
-
const
|
|
63070
|
+
const fname2 = safeName2;
|
|
63071
|
+
const fmime2 = match.fileMime || mimeOf(fname2);
|
|
63072
|
+
const sizeKnownTooBig = typeof match.fileSize === "number" && match.fileSize > DATAURL_INLINE_MAX;
|
|
63073
|
+
if (isTextyFile(fmime2, fname2) && !sizeKnownTooBig) {
|
|
63074
|
+
try {
|
|
63075
|
+
const ac = new AbortController();
|
|
63076
|
+
const killer = setTimeout(() => ac.abort(), 15e3);
|
|
63077
|
+
try {
|
|
63078
|
+
const r = await fetch(`${hub}/api/files/${match.id}`, { headers: { authorization: `Bearer ${c.token}` }, signal: ac.signal });
|
|
63079
|
+
if (r.ok && r.body) {
|
|
63080
|
+
const declared = Number(r.headers.get("content-length") ?? 0);
|
|
63081
|
+
if (!declared || declared <= DATAURL_INLINE_MAX) {
|
|
63082
|
+
const bytes = await readCapped(r.body, DATAURL_INLINE_MAX, Date.now() + 15e3);
|
|
63083
|
+
return ok3(
|
|
63084
|
+
`\u{1F4CE} ${fname2} from ${where} (${(bytes.length / 1024).toFixed(1)} KB) \u2014 full content:
|
|
63085
|
+
|
|
63086
|
+
` + bytes.toString("utf8")
|
|
63087
|
+
);
|
|
63088
|
+
}
|
|
63089
|
+
}
|
|
63090
|
+
} finally {
|
|
63091
|
+
clearTimeout(killer);
|
|
63092
|
+
}
|
|
63093
|
+
} catch {
|
|
63094
|
+
}
|
|
63095
|
+
}
|
|
63096
|
+
if (/pdf$/i.test(fmime2) || /\.pdf$/i.test(fname2)) {
|
|
63097
|
+
const pdfTooBig = typeof match.fileSize === "number" && match.fileSize > 15 * 1024 * 1024;
|
|
63098
|
+
if (!pdfTooBig) {
|
|
63099
|
+
try {
|
|
63100
|
+
const ac = new AbortController();
|
|
63101
|
+
const killer = setTimeout(() => ac.abort(), 3e4);
|
|
63102
|
+
try {
|
|
63103
|
+
const r = await fetch(`${hub}/api/files/${match.id}/text`, { headers: { authorization: `Bearer ${c.token}` }, signal: ac.signal });
|
|
63104
|
+
if (r.ok) {
|
|
63105
|
+
const body = await r.json();
|
|
63106
|
+
if (body?.text)
|
|
63107
|
+
return ok3(
|
|
63108
|
+
`\u{1F4CE} ${fname2} from ${where} \u2014 extracted text (${body.chars ?? body.text.length} chars):
|
|
63109
|
+
|
|
63110
|
+
` + body.text
|
|
63111
|
+
);
|
|
63112
|
+
}
|
|
63113
|
+
} finally {
|
|
63114
|
+
clearTimeout(killer);
|
|
63115
|
+
}
|
|
63116
|
+
} catch {
|
|
63117
|
+
}
|
|
63118
|
+
}
|
|
63119
|
+
}
|
|
63120
|
+
const cmd = `curl -s --no-clobber --create-dirs -o ${shPathArg(`${DOWNLOAD_DIR_SH}/${out}`)} -H "Authorization: Bearer $JEFRI_TOKEN" ${shq(`${hub}/api/files/${match.id}`)}`;
|
|
63121
|
+
const web2 = hub.replace("acp-hub.", "acp-web.").replace(":4000", ":4321");
|
|
63040
63122
|
return ok3(
|
|
63041
|
-
|
|
63042
|
-
|
|
63043
|
-
${cmd}
|
|
63123
|
+
`\u{1F4CE} ${match.fileName} (${fmime2}) is a binary/large file \u2014 I can't show it inline.
|
|
63044
63124
|
|
|
63045
|
-
|
|
63125
|
+
\u2022 Easiest: open the chat in the web app, the file renders there: ${web2}
|
|
63126
|
+
\u2022 Terminal: ${cmd}
|
|
63127
|
+
(set your token first: export JEFRI_TOKEN=\u2026 from the app's Connect dialog \u2014 it isn't printed here on purpose.)`
|
|
63046
63128
|
);
|
|
63047
63129
|
})
|
|
63048
63130
|
);
|
|
@@ -63097,21 +63179,21 @@ It downloads the file to your machine (current folder unless you set a path). Fi
|
|
|
63097
63179
|
inputSchema: {
|
|
63098
63180
|
departmentId: external_exports.string(),
|
|
63099
63181
|
fileId: external_exports.string(),
|
|
63100
|
-
savePath: external_exports.string().optional().describe("
|
|
63182
|
+
savePath: external_exports.string().optional().describe("the file NAME to save as (a bare name \u2014 the command saves into ~/Downloads/jefri/)")
|
|
63101
63183
|
}
|
|
63102
63184
|
},
|
|
63103
63185
|
async ({ departmentId, fileId, savePath }) => withClient(async (c) => {
|
|
63104
|
-
let name = savePath || "department-file";
|
|
63186
|
+
let name = safeSaveName(savePath) || "department-file";
|
|
63105
63187
|
try {
|
|
63106
63188
|
const r = await fetch(`${hubBase()}/api/departments/${departmentId}/drive`, { headers: { authorization: `Bearer ${c.token}` } });
|
|
63107
63189
|
if (r.ok) {
|
|
63108
63190
|
const { files } = await r.json();
|
|
63109
63191
|
const f = files.find((x) => x.id === fileId);
|
|
63110
|
-
if (f && !savePath) name = f.name;
|
|
63192
|
+
if (f && !safeSaveName(savePath)) name = safeSaveName(f.name) || name;
|
|
63111
63193
|
}
|
|
63112
63194
|
} catch {
|
|
63113
63195
|
}
|
|
63114
|
-
const cmd = `curl -s -o ${
|
|
63196
|
+
const cmd = `curl -s --no-clobber --create-dirs -o ${shPathArg(`${DOWNLOAD_DIR_SH}/${name}`)} -H "Authorization: Bearer $JEFRI_TOKEN" ${shq(`${hubBase()}/api/departments/${departmentId}/files/${fileId}`)}`;
|
|
63115
63197
|
return ok3(`To save this department file, run in the terminal:
|
|
63116
63198
|
|
|
63117
63199
|
${cmd}
|
|
@@ -63201,7 +63283,7 @@ ${h.text}`).join("\n\n---\n\n")
|
|
|
63201
63283
|
if (d.text) return ok3(`Contents of "${d.name}":
|
|
63202
63284
|
|
|
63203
63285
|
${d.text}`);
|
|
63204
|
-
const cmd = `curl -s -o ${
|
|
63286
|
+
const cmd = `curl -s --no-clobber --create-dirs -o ${shPathArg(`${DOWNLOAD_DIR_SH}/${safeSaveName(d.name) || "document"}`)} -H "Authorization: Bearer $JEFRI_TOKEN" ${shq(`${hubBase()}/api/agents/${encodeURIComponent(c.identity.username)}/docs/${encodeURIComponent(docId)}`)}`;
|
|
63205
63287
|
return ok3(`"${d.name}" is a ${d.mime} file. To download it and work with it locally, run in the terminal:
|
|
63206
63288
|
|
|
63207
63289
|
${cmd}
|
package/dist/index.js
CHANGED
|
@@ -40574,9 +40574,25 @@ function declKey(extra, parts) {
|
|
|
40574
40574
|
return "d-" + createHash("sha256").update(JSON.stringify([ua, rid, ...parts])).digest("hex").slice(0, 40);
|
|
40575
40575
|
}
|
|
40576
40576
|
var crossBasename = (p) => String(p).split(/[\\/]/).pop() || "file";
|
|
40577
|
+
var DOWNLOAD_DIR_SH = "~/Downloads/jefri";
|
|
40578
|
+
var downloadDirAbs = () => process.env.JEFRI_DOWNLOAD_DIR || np4.join(os4.homedir(), "Downloads", "jefri");
|
|
40579
|
+
var ensureDownloadDir = () => {
|
|
40580
|
+
const dir = downloadDirAbs();
|
|
40581
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
40582
|
+
const st = fs5.lstatSync(dir);
|
|
40583
|
+
if (st.isSymbolicLink() || !st.isDirectory())
|
|
40584
|
+
throw new Error(`refusing to save: ${dir} is a symlink (or not a directory) \u2014 a planted link could redirect downloads anywhere. Remove it and retry.`);
|
|
40585
|
+
return dir;
|
|
40586
|
+
};
|
|
40587
|
+
var safeSaveName = (p) => {
|
|
40588
|
+
const base = String(p ?? "").split(/[\\/]/).pop() ?? "";
|
|
40589
|
+
const clean2 = base.replace(/[\x00-\x1f\x7f]/g, "").trim();
|
|
40590
|
+
return !clean2 || clean2 === "." || clean2 === ".." ? "" : clean2;
|
|
40591
|
+
};
|
|
40577
40592
|
var shPathArg = (p) => p.startsWith("~/") ? `"$HOME"${shq(p.slice(1))}` : shq(p);
|
|
40578
40593
|
var psq = (s) => `'${String(s).replace(/'/g, "''")}'`;
|
|
40579
40594
|
var psPathArg = (p) => p.startsWith("~/") || p.startsWith("~\\") ? `($HOME + ${psq(p.slice(1))})` : psq(p);
|
|
40595
|
+
var isTextyFile = (mime, name) => /^text\/|json|xml|javascript|typescript|markdown|csv|ya?ml|x-sh|toml/i.test(mime) || /\.(md|txt|json|csv|ya?ml|log|ts|tsx|js|jsx|py|rb|go|rs|java|c|h|cpp|sh|sql|toml|env|cfg|ini)$/i.test(name);
|
|
40580
40596
|
var DATAURL_INLINE_MAX = 256 * 1024;
|
|
40581
40597
|
var dataUrlTooBig = (dataUrl) => dataUrl.length > DATAURL_INLINE_MAX * 1.4 ? `That dataUrl is ~${MB(dataUrl.length * 0.75)} MB decoded \u2014 far past the ${Math.round(DATAURL_INLINE_MAX / 1024)}KB inline limit. NEVER read a local file and base64 it through the model. Call this tool again with the file's PATH \u2014 you'll get a single-use upload command that sends it in seconds.` : null;
|
|
40582
40598
|
async function remoteUploadCommand(c2, ctx, target, path4, caption, where) {
|
|
@@ -41467,13 +41483,13 @@ ${fp}`);
|
|
|
41467
41483
|
server2.registerTool(
|
|
41468
41484
|
"jefri_download_file",
|
|
41469
41485
|
{
|
|
41470
|
-
title: "
|
|
41471
|
-
description: "
|
|
41486
|
+
title: "Read or save a file someone sent you",
|
|
41487
|
+
description: "Get a file another agent/user sent you. For a DM give the sender's username `from`; for a GROUP file give `groupId` (from jefri_groups / jefri_inbox). Add `fileName` if there are several. TEXT files (code, markdown, JSON, CSV, logs \u2014 up to 256KB) come back with their FULL CONTENT inline, and PDFs come back as EXTRACTED TEXT \u2014 so you can read and discuss both directly, no terminal needed. Only images, video and other binaries return a save command (or view them in the web app). On the local connector, files are saved straight to disk. Use this when the user says 'read the file sara sent', 'download that file', 'what does the doc say', etc.",
|
|
41472
41488
|
inputSchema: {
|
|
41473
41489
|
from: external_exports.string().optional().describe("who sent you the file (their username) \u2014 for a DM"),
|
|
41474
41490
|
groupId: external_exports.string().optional().describe("the group's id \u2014 for a file sent in a group"),
|
|
41475
41491
|
fileName: external_exports.string().optional().describe("which file, if there are several"),
|
|
41476
|
-
savePath: external_exports.string().optional().describe("
|
|
41492
|
+
savePath: external_exports.string().optional().describe("the file NAME to save as (a bare name \u2014 every download lands in ~/Downloads/jefri/, never elsewhere); a taken name gets a -1/-2 suffix")
|
|
41477
41493
|
}
|
|
41478
41494
|
},
|
|
41479
41495
|
async ({ from, groupId, fileName, savePath }) => withClient(async (c2) => {
|
|
@@ -41486,26 +41502,92 @@ ${fp}`);
|
|
|
41486
41502
|
const match = fileName ? [...files].reverse().find((m) => m.fileName === fileName || m.fileName?.includes(fileName)) : files[files.length - 1];
|
|
41487
41503
|
if (!match) return fail(`No file named "${fileName}" in ${where}.`);
|
|
41488
41504
|
const hub = ctx.serverUrl.replace(/\/$/, "");
|
|
41489
|
-
const
|
|
41505
|
+
const safeName2 = safeSaveName(match.fileName) || "downloaded.file";
|
|
41506
|
+
const out = safeSaveName(savePath) || safeName2;
|
|
41490
41507
|
if (ctx.local) {
|
|
41491
|
-
const outPath = out.startsWith("~") ? np4.join(os4.homedir(), out.slice(1)) : out;
|
|
41492
41508
|
try {
|
|
41509
|
+
const dlDir = ensureDownloadDir();
|
|
41510
|
+
const outPath = np4.join(dlDir, out);
|
|
41493
41511
|
const r = await fetch(`${hub}/api/files/${match.id}`, { headers: { authorization: `Bearer ${c2.token}` } });
|
|
41494
41512
|
if (!r.ok) throw new Error(`download failed (${r.status})`);
|
|
41495
41513
|
const buf = Buffer.from(await r.arrayBuffer());
|
|
41496
|
-
|
|
41497
|
-
|
|
41514
|
+
let finalPath = outPath;
|
|
41515
|
+
const ext = np4.extname(finalPath);
|
|
41516
|
+
const stem = finalPath.slice(0, finalPath.length - ext.length);
|
|
41517
|
+
let written = false;
|
|
41518
|
+
for (let i = 0; i < 100 && !written; i++) {
|
|
41519
|
+
finalPath = i === 0 ? outPath : `${stem}-${i}${ext}`;
|
|
41520
|
+
try {
|
|
41521
|
+
fs5.writeFileSync(finalPath, buf, { flag: "wx" });
|
|
41522
|
+
written = true;
|
|
41523
|
+
} catch (e) {
|
|
41524
|
+
if (e?.code !== "EEXIST") throw e;
|
|
41525
|
+
}
|
|
41526
|
+
}
|
|
41527
|
+
if (!written) return fail(`Couldn't save: 100 files named like ${outPath} already exist here.`);
|
|
41528
|
+
return ok3(`\u2705 Saved \u{1F4CE} ${match.fileName} from ${where} to ${finalPath} (${MB(buf.length)} MB).`);
|
|
41498
41529
|
} catch (e) {
|
|
41499
41530
|
return fail(`Couldn't save the file: ${e?.message ?? e}`);
|
|
41500
41531
|
}
|
|
41501
41532
|
}
|
|
41502
|
-
const
|
|
41533
|
+
const fname2 = safeName2;
|
|
41534
|
+
const fmime2 = match.fileMime || mimeOf(fname2);
|
|
41535
|
+
const sizeKnownTooBig = typeof match.fileSize === "number" && match.fileSize > DATAURL_INLINE_MAX;
|
|
41536
|
+
if (isTextyFile(fmime2, fname2) && !sizeKnownTooBig) {
|
|
41537
|
+
try {
|
|
41538
|
+
const ac = new AbortController();
|
|
41539
|
+
const killer = setTimeout(() => ac.abort(), 15e3);
|
|
41540
|
+
try {
|
|
41541
|
+
const r = await fetch(`${hub}/api/files/${match.id}`, { headers: { authorization: `Bearer ${c2.token}` }, signal: ac.signal });
|
|
41542
|
+
if (r.ok && r.body) {
|
|
41543
|
+
const declared = Number(r.headers.get("content-length") ?? 0);
|
|
41544
|
+
if (!declared || declared <= DATAURL_INLINE_MAX) {
|
|
41545
|
+
const bytes = await readCapped(r.body, DATAURL_INLINE_MAX, Date.now() + 15e3);
|
|
41546
|
+
return ok3(
|
|
41547
|
+
`\u{1F4CE} ${fname2} from ${where} (${(bytes.length / 1024).toFixed(1)} KB) \u2014 full content:
|
|
41548
|
+
|
|
41549
|
+
` + bytes.toString("utf8")
|
|
41550
|
+
);
|
|
41551
|
+
}
|
|
41552
|
+
}
|
|
41553
|
+
} finally {
|
|
41554
|
+
clearTimeout(killer);
|
|
41555
|
+
}
|
|
41556
|
+
} catch {
|
|
41557
|
+
}
|
|
41558
|
+
}
|
|
41559
|
+
if (/pdf$/i.test(fmime2) || /\.pdf$/i.test(fname2)) {
|
|
41560
|
+
const pdfTooBig = typeof match.fileSize === "number" && match.fileSize > 15 * 1024 * 1024;
|
|
41561
|
+
if (!pdfTooBig) {
|
|
41562
|
+
try {
|
|
41563
|
+
const ac = new AbortController();
|
|
41564
|
+
const killer = setTimeout(() => ac.abort(), 3e4);
|
|
41565
|
+
try {
|
|
41566
|
+
const r = await fetch(`${hub}/api/files/${match.id}/text`, { headers: { authorization: `Bearer ${c2.token}` }, signal: ac.signal });
|
|
41567
|
+
if (r.ok) {
|
|
41568
|
+
const body = await r.json();
|
|
41569
|
+
if (body?.text)
|
|
41570
|
+
return ok3(
|
|
41571
|
+
`\u{1F4CE} ${fname2} from ${where} \u2014 extracted text (${body.chars ?? body.text.length} chars):
|
|
41572
|
+
|
|
41573
|
+
` + body.text
|
|
41574
|
+
);
|
|
41575
|
+
}
|
|
41576
|
+
} finally {
|
|
41577
|
+
clearTimeout(killer);
|
|
41578
|
+
}
|
|
41579
|
+
} catch {
|
|
41580
|
+
}
|
|
41581
|
+
}
|
|
41582
|
+
}
|
|
41583
|
+
const cmd = `curl -s --no-clobber --create-dirs -o ${shPathArg(`${DOWNLOAD_DIR_SH}/${out}`)} -H "Authorization: Bearer $JEFRI_TOKEN" ${shq(`${hub}/api/files/${match.id}`)}`;
|
|
41584
|
+
const web2 = hub.replace("acp-hub.", "acp-web.").replace(":4000", ":4321");
|
|
41503
41585
|
return ok3(
|
|
41504
|
-
|
|
41505
|
-
|
|
41506
|
-
${cmd}
|
|
41586
|
+
`\u{1F4CE} ${match.fileName} (${fmime2}) is a binary/large file \u2014 I can't show it inline.
|
|
41507
41587
|
|
|
41508
|
-
|
|
41588
|
+
\u2022 Easiest: open the chat in the web app, the file renders there: ${web2}
|
|
41589
|
+
\u2022 Terminal: ${cmd}
|
|
41590
|
+
(set your token first: export JEFRI_TOKEN=\u2026 from the app's Connect dialog \u2014 it isn't printed here on purpose.)`
|
|
41509
41591
|
);
|
|
41510
41592
|
})
|
|
41511
41593
|
);
|
|
@@ -41560,21 +41642,21 @@ It downloads the file to your machine (current folder unless you set a path). Fi
|
|
|
41560
41642
|
inputSchema: {
|
|
41561
41643
|
departmentId: external_exports.string(),
|
|
41562
41644
|
fileId: external_exports.string(),
|
|
41563
|
-
savePath: external_exports.string().optional().describe("
|
|
41645
|
+
savePath: external_exports.string().optional().describe("the file NAME to save as (a bare name \u2014 the command saves into ~/Downloads/jefri/)")
|
|
41564
41646
|
}
|
|
41565
41647
|
},
|
|
41566
41648
|
async ({ departmentId, fileId, savePath }) => withClient(async (c2) => {
|
|
41567
|
-
let name = savePath || "department-file";
|
|
41649
|
+
let name = safeSaveName(savePath) || "department-file";
|
|
41568
41650
|
try {
|
|
41569
41651
|
const r = await fetch(`${hubBase()}/api/departments/${departmentId}/drive`, { headers: { authorization: `Bearer ${c2.token}` } });
|
|
41570
41652
|
if (r.ok) {
|
|
41571
41653
|
const { files } = await r.json();
|
|
41572
41654
|
const f = files.find((x) => x.id === fileId);
|
|
41573
|
-
if (f && !savePath) name = f.name;
|
|
41655
|
+
if (f && !safeSaveName(savePath)) name = safeSaveName(f.name) || name;
|
|
41574
41656
|
}
|
|
41575
41657
|
} catch {
|
|
41576
41658
|
}
|
|
41577
|
-
const cmd = `curl -s -o ${
|
|
41659
|
+
const cmd = `curl -s --no-clobber --create-dirs -o ${shPathArg(`${DOWNLOAD_DIR_SH}/${name}`)} -H "Authorization: Bearer $JEFRI_TOKEN" ${shq(`${hubBase()}/api/departments/${departmentId}/files/${fileId}`)}`;
|
|
41578
41660
|
return ok3(`To save this department file, run in the terminal:
|
|
41579
41661
|
|
|
41580
41662
|
${cmd}
|
|
@@ -41664,7 +41746,7 @@ ${h.text}`).join("\n\n---\n\n")
|
|
|
41664
41746
|
if (d.text) return ok3(`Contents of "${d.name}":
|
|
41665
41747
|
|
|
41666
41748
|
${d.text}`);
|
|
41667
|
-
const cmd = `curl -s -o ${
|
|
41749
|
+
const cmd = `curl -s --no-clobber --create-dirs -o ${shPathArg(`${DOWNLOAD_DIR_SH}/${safeSaveName(d.name) || "document"}`)} -H "Authorization: Bearer $JEFRI_TOKEN" ${shq(`${hubBase()}/api/agents/${encodeURIComponent(c2.identity.username)}/docs/${encodeURIComponent(docId)}`)}`;
|
|
41668
41750
|
return ok3(`"${d.name}" is a ${d.mime} file. To download it and work with it locally, run in the terminal:
|
|
41669
41751
|
|
|
41670
41752
|
${cmd}
|
package/package.json
CHANGED