jefrichat-mcp 0.7.0 → 0.8.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/dist/http.js +195 -55
- package/dist/index.js +245 -96
- package/package.json +1 -1
package/dist/http.js
CHANGED
|
@@ -1300,8 +1300,8 @@ var require_node = __commonJS({
|
|
|
1300
1300
|
}
|
|
1301
1301
|
break;
|
|
1302
1302
|
case "FILE":
|
|
1303
|
-
var
|
|
1304
|
-
stream2 = new
|
|
1303
|
+
var fs4 = __require("fs");
|
|
1304
|
+
stream2 = new fs4.SyncWriteStream(fd2, { autoClose: false });
|
|
1305
1305
|
stream2._type = "fs";
|
|
1306
1306
|
break;
|
|
1307
1307
|
case "PIPE":
|
|
@@ -18437,7 +18437,7 @@ var require_view = __commonJS({
|
|
|
18437
18437
|
"use strict";
|
|
18438
18438
|
var debug = require_src()("express:view");
|
|
18439
18439
|
var path2 = __require("path");
|
|
18440
|
-
var
|
|
18440
|
+
var fs4 = __require("fs");
|
|
18441
18441
|
var dirname = path2.dirname;
|
|
18442
18442
|
var basename = path2.basename;
|
|
18443
18443
|
var extname = path2.extname;
|
|
@@ -18503,7 +18503,7 @@ var require_view = __commonJS({
|
|
|
18503
18503
|
function tryStat(path3) {
|
|
18504
18504
|
debug('stat "%s"', path3);
|
|
18505
18505
|
try {
|
|
18506
|
-
return
|
|
18506
|
+
return fs4.statSync(path3);
|
|
18507
18507
|
} catch (e) {
|
|
18508
18508
|
return void 0;
|
|
18509
18509
|
}
|
|
@@ -18858,7 +18858,7 @@ var require_types = __commonJS({
|
|
|
18858
18858
|
var require_mime = __commonJS({
|
|
18859
18859
|
"../../node_modules/.pnpm/mime@1.6.0/node_modules/mime/mime.js"(exports, module) {
|
|
18860
18860
|
var path2 = __require("path");
|
|
18861
|
-
var
|
|
18861
|
+
var fs4 = __require("fs");
|
|
18862
18862
|
function Mime() {
|
|
18863
18863
|
this.types = /* @__PURE__ */ Object.create(null);
|
|
18864
18864
|
this.extensions = /* @__PURE__ */ Object.create(null);
|
|
@@ -18879,7 +18879,7 @@ var require_mime = __commonJS({
|
|
|
18879
18879
|
};
|
|
18880
18880
|
Mime.prototype.load = function(file) {
|
|
18881
18881
|
this._loading = file;
|
|
18882
|
-
var map = {}, content =
|
|
18882
|
+
var map = {}, content = fs4.readFileSync(file, "ascii"), lines = content.split(/[\r\n]+/);
|
|
18883
18883
|
lines.forEach(function(line) {
|
|
18884
18884
|
var fields = line.replace(/\s*#.*|^\s*|\s*$/g, "").split(/\s+/);
|
|
18885
18885
|
map[fields.shift()] = fields;
|
|
@@ -19117,7 +19117,7 @@ var require_send = __commonJS({
|
|
|
19117
19117
|
var escapeHtml = require_escape_html();
|
|
19118
19118
|
var etag = require_etag();
|
|
19119
19119
|
var fresh = require_fresh();
|
|
19120
|
-
var
|
|
19120
|
+
var fs4 = __require("fs");
|
|
19121
19121
|
var mime = require_mime();
|
|
19122
19122
|
var ms = require_ms2();
|
|
19123
19123
|
var onFinished = require_on_finished();
|
|
@@ -19450,7 +19450,7 @@ var require_send = __commonJS({
|
|
|
19450
19450
|
var i = 0;
|
|
19451
19451
|
var self = this;
|
|
19452
19452
|
debug('stat "%s"', path3);
|
|
19453
|
-
|
|
19453
|
+
fs4.stat(path3, function onstat(err, stat) {
|
|
19454
19454
|
if (err && err.code === "ENOENT" && !extname(path3) && path3[path3.length - 1] !== sep) {
|
|
19455
19455
|
return next(err);
|
|
19456
19456
|
}
|
|
@@ -19465,7 +19465,7 @@ var require_send = __commonJS({
|
|
|
19465
19465
|
}
|
|
19466
19466
|
var p = path3 + "." + self._extensions[i++];
|
|
19467
19467
|
debug('stat "%s"', p);
|
|
19468
|
-
|
|
19468
|
+
fs4.stat(p, function(err2, stat) {
|
|
19469
19469
|
if (err2) return next(err2);
|
|
19470
19470
|
if (stat.isDirectory()) return next();
|
|
19471
19471
|
self.emit("file", p, stat);
|
|
@@ -19483,7 +19483,7 @@ var require_send = __commonJS({
|
|
|
19483
19483
|
}
|
|
19484
19484
|
var p = join(path3, self._index[i]);
|
|
19485
19485
|
debug('stat "%s"', p);
|
|
19486
|
-
|
|
19486
|
+
fs4.stat(p, function(err2, stat) {
|
|
19487
19487
|
if (err2) return next(err2);
|
|
19488
19488
|
if (stat.isDirectory()) return next();
|
|
19489
19489
|
self.emit("file", p, stat);
|
|
@@ -19495,7 +19495,7 @@ var require_send = __commonJS({
|
|
|
19495
19495
|
SendStream.prototype.stream = function stream(path3, options) {
|
|
19496
19496
|
var self = this;
|
|
19497
19497
|
var res = this.res;
|
|
19498
|
-
var stream2 =
|
|
19498
|
+
var stream2 = fs4.createReadStream(path3, options);
|
|
19499
19499
|
this.emit("stream", stream2);
|
|
19500
19500
|
stream2.pipe(res);
|
|
19501
19501
|
function cleanup() {
|
|
@@ -29739,12 +29739,12 @@ var require_dist = __commonJS({
|
|
|
29739
29739
|
throw new Error(`Unknown format "${name}"`);
|
|
29740
29740
|
return f;
|
|
29741
29741
|
};
|
|
29742
|
-
function addFormats(ajv, list,
|
|
29742
|
+
function addFormats(ajv, list, fs4, exportName) {
|
|
29743
29743
|
var _a;
|
|
29744
29744
|
var _b;
|
|
29745
29745
|
(_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
29746
29746
|
for (const f of list)
|
|
29747
|
-
ajv.addFormat(f,
|
|
29747
|
+
ajv.addFormat(f, fs4[f]);
|
|
29748
29748
|
}
|
|
29749
29749
|
module.exports = exports = formatsPlugin;
|
|
29750
29750
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -49373,10 +49373,69 @@ var AcpClient = class _AcpClient {
|
|
|
49373
49373
|
};
|
|
49374
49374
|
|
|
49375
49375
|
// src/tools.ts
|
|
49376
|
+
import fs3 from "node:fs";
|
|
49377
|
+
import np2 from "node:path";
|
|
49378
|
+
import cp2 from "node:child_process";
|
|
49379
|
+
import os3 from "node:os";
|
|
49380
|
+
|
|
49381
|
+
// src/notify.ts
|
|
49382
|
+
import cp from "node:child_process";
|
|
49376
49383
|
import fs2 from "node:fs";
|
|
49377
49384
|
import np from "node:path";
|
|
49378
|
-
import cp from "node:child_process";
|
|
49379
49385
|
import os2 from "node:os";
|
|
49386
|
+
var OFF = process.env.JEFRI_NOTIFY === "0" || process.env.JEFRI_NOTIFY === "false";
|
|
49387
|
+
function findTerminalNotifier() {
|
|
49388
|
+
if (process.platform !== "darwin") return null;
|
|
49389
|
+
for (const p of ["/opt/homebrew/bin/terminal-notifier", "/usr/local/bin/terminal-notifier"]) {
|
|
49390
|
+
try {
|
|
49391
|
+
if (fs2.existsSync(p)) return p;
|
|
49392
|
+
} catch {
|
|
49393
|
+
}
|
|
49394
|
+
}
|
|
49395
|
+
try {
|
|
49396
|
+
const out = (cp.spawnSync("which", ["terminal-notifier"], { encoding: "utf8" }).stdout || "").trim();
|
|
49397
|
+
if (out && fs2.existsSync(out)) return out;
|
|
49398
|
+
} catch {
|
|
49399
|
+
}
|
|
49400
|
+
return null;
|
|
49401
|
+
}
|
|
49402
|
+
var TERMINAL_NOTIFIER = findTerminalNotifier();
|
|
49403
|
+
var macNeedsTerminalNotifier = process.platform === "darwin" && !TERMINAL_NOTIFIER && !OFF;
|
|
49404
|
+
var DEFAULTS = {
|
|
49405
|
+
enabled: true,
|
|
49406
|
+
sound: true,
|
|
49407
|
+
dms: true,
|
|
49408
|
+
groups: true,
|
|
49409
|
+
onlyMentions: false,
|
|
49410
|
+
quietFrom: null,
|
|
49411
|
+
quietTo: null,
|
|
49412
|
+
mutedGroups: [],
|
|
49413
|
+
mutedUsers: []
|
|
49414
|
+
};
|
|
49415
|
+
var PREFS_DIR = np.join(os2.homedir(), ".jefri");
|
|
49416
|
+
var PREFS_FILE = np.join(PREFS_DIR, "notify.json");
|
|
49417
|
+
function loadPrefs() {
|
|
49418
|
+
try {
|
|
49419
|
+
return { ...DEFAULTS, ...JSON.parse(fs2.readFileSync(PREFS_FILE, "utf8")) };
|
|
49420
|
+
} catch {
|
|
49421
|
+
return { ...DEFAULTS };
|
|
49422
|
+
}
|
|
49423
|
+
}
|
|
49424
|
+
var prefs = loadPrefs();
|
|
49425
|
+
function getPrefs() {
|
|
49426
|
+
return { ...prefs };
|
|
49427
|
+
}
|
|
49428
|
+
function setPrefs(patch) {
|
|
49429
|
+
prefs = { ...prefs, ...patch };
|
|
49430
|
+
try {
|
|
49431
|
+
fs2.mkdirSync(PREFS_DIR, { recursive: true });
|
|
49432
|
+
fs2.writeFileSync(PREFS_FILE, JSON.stringify(prefs, null, 2));
|
|
49433
|
+
} catch {
|
|
49434
|
+
}
|
|
49435
|
+
return { ...prefs };
|
|
49436
|
+
}
|
|
49437
|
+
|
|
49438
|
+
// src/tools.ts
|
|
49380
49439
|
var MIME = {
|
|
49381
49440
|
".jpg": "image/jpeg",
|
|
49382
49441
|
".jpeg": "image/jpeg",
|
|
@@ -49396,21 +49455,21 @@ var MIME = {
|
|
|
49396
49455
|
".json": "application/json",
|
|
49397
49456
|
".zip": "application/zip"
|
|
49398
49457
|
};
|
|
49399
|
-
var mimeOf = (name) => MIME[
|
|
49458
|
+
var mimeOf = (name) => MIME[np2.extname(name).toLowerCase()] ?? "application/octet-stream";
|
|
49400
49459
|
function screenshotDir() {
|
|
49401
49460
|
try {
|
|
49402
|
-
const out =
|
|
49403
|
-
if (out) return out.startsWith("~") ?
|
|
49461
|
+
const out = cp2.execFileSync("defaults", ["read", "com.apple.screencapture", "location"], { encoding: "utf8", timeout: 2e3 }).trim();
|
|
49462
|
+
if (out) return out.startsWith("~") ? np2.join(os3.homedir(), out.slice(1)) : out;
|
|
49404
49463
|
} catch {
|
|
49405
49464
|
}
|
|
49406
|
-
const desktop =
|
|
49407
|
-
return
|
|
49465
|
+
const desktop = np2.join(os3.homedir(), "Desktop");
|
|
49466
|
+
return fs3.existsSync(desktop) ? desktop : null;
|
|
49408
49467
|
}
|
|
49409
49468
|
function resolveLocalFile(rawPath) {
|
|
49410
|
-
const expand = (p) => p.startsWith("~") ?
|
|
49469
|
+
const expand = (p) => p.startsWith("~") ? np2.join(os3.homedir(), p.slice(1)) : p;
|
|
49411
49470
|
const exists = (p) => {
|
|
49412
49471
|
try {
|
|
49413
|
-
return
|
|
49472
|
+
return fs3.existsSync(p) ? p : null;
|
|
49414
49473
|
} catch {
|
|
49415
49474
|
return null;
|
|
49416
49475
|
}
|
|
@@ -49423,26 +49482,26 @@ function resolveLocalFile(rawPath) {
|
|
|
49423
49482
|
hit = exists(v);
|
|
49424
49483
|
if (hit) return hit;
|
|
49425
49484
|
}
|
|
49426
|
-
const base =
|
|
49485
|
+
const base = np2.basename(abs);
|
|
49427
49486
|
const looksLikeShot = /screencaptureui/i.test(abs) || /^Screenshot[\s ]/i.test(base);
|
|
49428
49487
|
if (looksLikeShot) {
|
|
49429
49488
|
const dir = screenshotDir();
|
|
49430
49489
|
if (dir) {
|
|
49431
49490
|
let files = [];
|
|
49432
49491
|
try {
|
|
49433
|
-
files =
|
|
49492
|
+
files = fs3.readdirSync(dir);
|
|
49434
49493
|
} catch {
|
|
49435
49494
|
}
|
|
49436
49495
|
const norm = (s) => s.replace(/[ \s]+/g, " ").trim().toLowerCase();
|
|
49437
49496
|
const want = norm(base);
|
|
49438
49497
|
for (const f of files) if (norm(f) === want) {
|
|
49439
|
-
hit = exists(
|
|
49498
|
+
hit = exists(np2.join(dir, f));
|
|
49440
49499
|
if (hit) return hit;
|
|
49441
49500
|
}
|
|
49442
49501
|
const shots = files.filter((f) => /^Screenshot[\s ].*\.(png|jpe?g)$/i.test(f)).map((f) => {
|
|
49443
|
-
const p =
|
|
49502
|
+
const p = np2.join(dir, f);
|
|
49444
49503
|
try {
|
|
49445
|
-
return { p, m:
|
|
49504
|
+
return { p, m: fs3.statSync(p).mtimeMs };
|
|
49446
49505
|
} catch {
|
|
49447
49506
|
return { p, m: 0 };
|
|
49448
49507
|
}
|
|
@@ -49602,8 +49661,8 @@ ${fp}`);
|
|
|
49602
49661
|
`I can't find a file at ${path2} on this machine.`
|
|
49603
49662
|
);
|
|
49604
49663
|
}
|
|
49605
|
-
const buf =
|
|
49606
|
-
name =
|
|
49664
|
+
const buf = fs3.readFileSync(abs);
|
|
49665
|
+
name = np2.basename(abs);
|
|
49607
49666
|
mime = mimeOf(name);
|
|
49608
49667
|
url = `data:${mime};base64,${buf.toString("base64")}`;
|
|
49609
49668
|
} else if (dataUrl && fileName) {
|
|
@@ -49663,8 +49722,8 @@ ${fp}`);
|
|
|
49663
49722
|
`I can't find a file at ${path2} on this machine.`
|
|
49664
49723
|
);
|
|
49665
49724
|
}
|
|
49666
|
-
const buf =
|
|
49667
|
-
name =
|
|
49725
|
+
const buf = fs3.readFileSync(abs);
|
|
49726
|
+
name = np2.basename(abs);
|
|
49668
49727
|
mime = mimeOf(name);
|
|
49669
49728
|
url = `data:${mime};base64,${buf.toString("base64")}`;
|
|
49670
49729
|
} else if (dataUrl && fileName) {
|
|
@@ -49696,7 +49755,7 @@ ${fp}`);
|
|
|
49696
49755
|
let name, mime, url;
|
|
49697
49756
|
if (path2) {
|
|
49698
49757
|
if (!ctx.local) {
|
|
49699
|
-
const fname =
|
|
49758
|
+
const fname = np2.basename(path2);
|
|
49700
49759
|
const fmime = mimeOf(fname);
|
|
49701
49760
|
const hub = ctx.serverUrl.replace(/\/$/, "");
|
|
49702
49761
|
const web = hub.replace("acp-hub.", "acp-web.").replace(":4000", ":4321");
|
|
@@ -49721,8 +49780,8 @@ ${psCmd}
|
|
|
49721
49780
|
if (!abs) {
|
|
49722
49781
|
return fail(`I can't find a file at ${path2} on this machine.`);
|
|
49723
49782
|
}
|
|
49724
|
-
const buf =
|
|
49725
|
-
name =
|
|
49783
|
+
const buf = fs3.readFileSync(abs);
|
|
49784
|
+
name = np2.basename(abs);
|
|
49726
49785
|
mime = mimeOf(name);
|
|
49727
49786
|
url = `data:${mime};base64,${buf.toString("base64")}`;
|
|
49728
49787
|
} else if (dataUrl && fileName) {
|
|
@@ -49803,57 +49862,57 @@ ${psCmd}
|
|
|
49803
49862
|
`Sending a folder reads files off disk, which this connector can't do \u2014 it runs in the cloud. Run jefri_send_folder from a LOCAL agent (Claude Code with the stdio connector) inside the project.`
|
|
49804
49863
|
);
|
|
49805
49864
|
const rawDir = path2 && path2.trim() ? path2 : process.cwd();
|
|
49806
|
-
const dir = rawDir.startsWith("~") ?
|
|
49807
|
-
if (!
|
|
49865
|
+
const dir = rawDir.startsWith("~") ? np2.join(os3.homedir(), rawDir.slice(1)) : rawDir;
|
|
49866
|
+
if (!fs3.existsSync(dir))
|
|
49808
49867
|
return fail(
|
|
49809
49868
|
`I can't see "${rawDir}" from here. Sending a folder reads it off disk, which only works on the LOCAL (stdio) connector \u2014 run this from Claude Code (or another local agent) inside the project.`
|
|
49810
49869
|
);
|
|
49811
49870
|
let stat;
|
|
49812
49871
|
try {
|
|
49813
|
-
stat =
|
|
49872
|
+
stat = fs3.statSync(dir);
|
|
49814
49873
|
} catch {
|
|
49815
49874
|
return fail(`Cannot read "${rawDir}".`);
|
|
49816
49875
|
}
|
|
49817
49876
|
if (!stat.isDirectory())
|
|
49818
49877
|
return fail(`"${rawDir}" is a file, not a folder \u2014 use jefri_send_file for a single file.`);
|
|
49819
49878
|
try {
|
|
49820
|
-
|
|
49879
|
+
cp2.execFileSync("zip", ["-v"], { stdio: "ignore" });
|
|
49821
49880
|
} catch {
|
|
49822
49881
|
return fail(`The 'zip' command isn't available on this machine, and I need it to package the folder.`);
|
|
49823
49882
|
}
|
|
49824
|
-
const base =
|
|
49825
|
-
const tmpZip =
|
|
49883
|
+
const base = np2.basename(np2.resolve(dir)) || "project";
|
|
49884
|
+
const tmpZip = np2.join(os3.tmpdir(), `acp-${base}-${Date.now()}.zip`);
|
|
49826
49885
|
let isRepo = false;
|
|
49827
49886
|
try {
|
|
49828
|
-
|
|
49887
|
+
cp2.execFileSync("git", ["-C", dir, "rev-parse", "--is-inside-work-tree"], { stdio: "ignore" });
|
|
49829
49888
|
isRepo = true;
|
|
49830
49889
|
} catch {
|
|
49831
49890
|
isRepo = false;
|
|
49832
49891
|
}
|
|
49833
49892
|
try {
|
|
49834
49893
|
if (isRepo) {
|
|
49835
|
-
|
|
49894
|
+
cp2.execSync(
|
|
49836
49895
|
`cd ${shq(dir)} && git ls-files --cached --others --exclude-standard -z | xargs -0 zip -q ${shq(tmpZip)} --`,
|
|
49837
49896
|
{ stdio: "ignore" }
|
|
49838
49897
|
);
|
|
49839
49898
|
} else {
|
|
49840
|
-
|
|
49899
|
+
cp2.execSync(
|
|
49841
49900
|
`cd ${shq(dir)} && zip -qr ${shq(tmpZip)} . -x '*/node_modules/*' '*/.git/*' '*/dist/*' '*/.next/*' '*/build/*' '*/__pycache__/*' '*/.venv/*' '*/venv/*' '*.log' '*/.DS_Store'`,
|
|
49842
49901
|
{ stdio: "ignore" }
|
|
49843
49902
|
);
|
|
49844
49903
|
}
|
|
49845
49904
|
} catch (e) {
|
|
49846
49905
|
try {
|
|
49847
|
-
|
|
49906
|
+
fs3.rmSync(tmpZip, { force: true });
|
|
49848
49907
|
} catch {
|
|
49849
49908
|
}
|
|
49850
49909
|
return fail(`Could not zip "${rawDir}": ${e?.message ?? e}`);
|
|
49851
49910
|
}
|
|
49852
|
-
if (!
|
|
49911
|
+
if (!fs3.existsSync(tmpZip))
|
|
49853
49912
|
return fail(`Nothing to send \u2014 "${rawDir}" produced an empty archive (all files ignored or folder empty).`);
|
|
49854
|
-
const size =
|
|
49913
|
+
const size = fs3.statSync(tmpZip).size;
|
|
49855
49914
|
if (size > MAX_UPLOAD_BYTES) {
|
|
49856
|
-
|
|
49915
|
+
fs3.rmSync(tmpZip, { force: true });
|
|
49857
49916
|
return fail(
|
|
49858
49917
|
`That archive is ${MB(size)} MB, over the ${Math.round(MAX_UPLOAD_BYTES / 1024 / 1024)} MB limit. Send a subfolder (e.g. path=\u2026/src) or remove large files, then try again.`
|
|
49859
49918
|
);
|
|
@@ -49865,7 +49924,7 @@ ${psCmd}
|
|
|
49865
49924
|
const res = await fetch(`${hub}/api/files?${q}`, {
|
|
49866
49925
|
method: "POST",
|
|
49867
49926
|
headers: { Authorization: `Bearer ${c.token}`, "Content-Type": "application/zip" },
|
|
49868
|
-
body:
|
|
49927
|
+
body: fs3.readFileSync(tmpZip)
|
|
49869
49928
|
});
|
|
49870
49929
|
if (!res.ok) {
|
|
49871
49930
|
const t = await res.text().catch(() => "");
|
|
@@ -49875,7 +49934,7 @@ ${psCmd}
|
|
|
49875
49934
|
return fail(`Upload failed: ${e?.message ?? e}`);
|
|
49876
49935
|
} finally {
|
|
49877
49936
|
try {
|
|
49878
|
-
|
|
49937
|
+
fs3.rmSync(tmpZip, { force: true });
|
|
49879
49938
|
} catch {
|
|
49880
49939
|
}
|
|
49881
49940
|
}
|
|
@@ -49910,12 +49969,12 @@ ${psCmd}
|
|
|
49910
49969
|
const hub = ctx.serverUrl.replace(/\/$/, "");
|
|
49911
49970
|
const out = savePath || match.fileName || "downloaded.file";
|
|
49912
49971
|
if (ctx.local) {
|
|
49913
|
-
const outPath = out.startsWith("~") ?
|
|
49972
|
+
const outPath = out.startsWith("~") ? np2.join(os3.homedir(), out.slice(1)) : out;
|
|
49914
49973
|
try {
|
|
49915
49974
|
const r = await fetch(`${hub}/api/files/${match.id}`, { headers: { authorization: `Bearer ${c.token}` } });
|
|
49916
49975
|
if (!r.ok) throw new Error(`download failed (${r.status})`);
|
|
49917
49976
|
const buf = Buffer.from(await r.arrayBuffer());
|
|
49918
|
-
|
|
49977
|
+
fs3.writeFileSync(outPath, buf);
|
|
49919
49978
|
return ok(`\u2705 Saved \u{1F4CE} ${match.fileName} from ${where} to ${outPath} (${MB(buf.length)} MB).`);
|
|
49920
49979
|
} catch (e) {
|
|
49921
49980
|
return fail(`Couldn't save the file: ${e?.message ?? e}`);
|
|
@@ -50016,7 +50075,7 @@ ${cmd}`);
|
|
|
50016
50075
|
}
|
|
50017
50076
|
},
|
|
50018
50077
|
async ({ departmentId, path: path2, kind }) => withClient(async (c) => {
|
|
50019
|
-
const fname =
|
|
50078
|
+
const fname = np2.basename(path2.startsWith("~") ? path2.slice(1) : path2);
|
|
50020
50079
|
const mime = mimeOf(fname);
|
|
50021
50080
|
const q = `name=${encodeURIComponent(fname)}&kind=${kind ?? "file"}`;
|
|
50022
50081
|
const cmd = `curl -s -X POST ${shq(`${hubBase()}/api/departments/${departmentId}/files?${q}`)} -H "Authorization: Bearer ${c.token}" -H "Content-Type: ${mime}" --data-binary @${shq(path2)}`;
|
|
@@ -50100,7 +50159,7 @@ ${cmd}`);
|
|
|
50100
50159
|
inputSchema: { path: external_exports.string().describe("local file path to upload") }
|
|
50101
50160
|
},
|
|
50102
50161
|
async ({ path: path2 }) => withClient(async (c) => {
|
|
50103
|
-
const fname =
|
|
50162
|
+
const fname = np2.basename(path2.startsWith("~") ? path2.slice(1) : path2);
|
|
50104
50163
|
const mime = mimeOf(fname);
|
|
50105
50164
|
const cmd = `curl -s -X POST ${shq(`${hubBase()}/api/agents/${encodeURIComponent(c.identity.username)}/docs?name=${encodeURIComponent(fname)}`)} -H "Authorization: Bearer ${c.token}" -H "Content-Type: ${mime}" --data-binary @${shq(path2)}`;
|
|
50106
50165
|
return ok(`To add ${fname} to your document memory, run in the terminal:
|
|
@@ -50252,7 +50311,7 @@ ${cmd}`);
|
|
|
50252
50311
|
if (path2) {
|
|
50253
50312
|
if (!ctx.local) {
|
|
50254
50313
|
const hub = ctx.serverUrl.replace(/\/$/, "");
|
|
50255
|
-
const fname =
|
|
50314
|
+
const fname = np2.basename(path2);
|
|
50256
50315
|
const fmime = mimeOf(fname);
|
|
50257
50316
|
const q = `groupId=${encodeURIComponent(groupId)}&fileName=${encodeURIComponent(fname)}` + (caption ? `&caption=${encodeURIComponent(caption)}` : "");
|
|
50258
50317
|
const cmd = `curl -s -X POST ${shq(`${hub}/api/files?${q}`)} -H "Authorization: Bearer ${c.token}" -H "Content-Type: ${fmime}" --data-binary @${shq(path2)}`;
|
|
@@ -50264,8 +50323,8 @@ ${cmd}`
|
|
|
50264
50323
|
}
|
|
50265
50324
|
const abs = resolveLocalFile(path2);
|
|
50266
50325
|
if (!abs) return fail(`I can't find a file at ${path2} on this machine.`);
|
|
50267
|
-
const buf =
|
|
50268
|
-
name =
|
|
50326
|
+
const buf = fs3.readFileSync(abs);
|
|
50327
|
+
name = np2.basename(abs);
|
|
50269
50328
|
mime = mimeOf(name);
|
|
50270
50329
|
url = `data:${mime};base64,${buf.toString("base64")}`;
|
|
50271
50330
|
} else if (dataUrl && fileName) {
|
|
@@ -50393,6 +50452,87 @@ ${cmd}`
|
|
|
50393
50452
|
return ok(`Status set to ${status}.`);
|
|
50394
50453
|
})
|
|
50395
50454
|
);
|
|
50455
|
+
if (ctx.local) {
|
|
50456
|
+
server.registerTool(
|
|
50457
|
+
"jefri_notifications",
|
|
50458
|
+
{
|
|
50459
|
+
title: "Notification settings",
|
|
50460
|
+
description: "Show or change desktop notification preferences: master on/off, sound on/off, DMs vs groups, only-@mentions for groups, quiet hours, and muting a specific group or person. Call with NO arguments to just see the current settings. Persists across sessions.",
|
|
50461
|
+
inputSchema: {
|
|
50462
|
+
enabled: external_exports.boolean().optional().describe("master switch for all desktop notifications"),
|
|
50463
|
+
sound: external_exports.boolean().optional().describe("play a sound with notifications"),
|
|
50464
|
+
dms: external_exports.boolean().optional().describe("notify for direct messages"),
|
|
50465
|
+
groups: external_exports.boolean().optional().describe("notify for group messages"),
|
|
50466
|
+
onlyMentions: external_exports.boolean().optional().describe("for groups: only notify when someone @-mentions you"),
|
|
50467
|
+
quietFrom: external_exports.string().optional().describe("start of quiet hours, HH:MM 24h (e.g. 22:00), or 'off' to clear"),
|
|
50468
|
+
quietTo: external_exports.string().optional().describe("end of quiet hours, HH:MM 24h (e.g. 08:00)"),
|
|
50469
|
+
muteGroup: external_exports.string().optional().describe("a group's name or id to silence"),
|
|
50470
|
+
unmuteGroup: external_exports.string().optional().describe("a group's name or id to unmute"),
|
|
50471
|
+
muteUser: external_exports.string().optional().describe("a username to silence"),
|
|
50472
|
+
unmuteUser: external_exports.string().optional().describe("a username to unmute")
|
|
50473
|
+
}
|
|
50474
|
+
},
|
|
50475
|
+
async (args) => withClient(async (c) => {
|
|
50476
|
+
const patch = {};
|
|
50477
|
+
for (const k of ["enabled", "sound", "dms", "groups", "onlyMentions"])
|
|
50478
|
+
if (typeof args[k] === "boolean") patch[k] = args[k];
|
|
50479
|
+
if (args.quietFrom === "off" || args.quietTo === "off") {
|
|
50480
|
+
patch.quietFrom = null;
|
|
50481
|
+
patch.quietTo = null;
|
|
50482
|
+
} else {
|
|
50483
|
+
if (args.quietFrom) patch.quietFrom = args.quietFrom;
|
|
50484
|
+
if (args.quietTo) patch.quietTo = args.quietTo;
|
|
50485
|
+
}
|
|
50486
|
+
const cur = getPrefs();
|
|
50487
|
+
const mutedGroups = new Set(cur.mutedGroups);
|
|
50488
|
+
const mutedUsers = new Set(cur.mutedUsers);
|
|
50489
|
+
let groupList = [];
|
|
50490
|
+
const needGroups = args.muteGroup || args.unmuteGroup;
|
|
50491
|
+
if (needGroups) {
|
|
50492
|
+
try {
|
|
50493
|
+
groupList = await c.groups();
|
|
50494
|
+
} catch {
|
|
50495
|
+
}
|
|
50496
|
+
}
|
|
50497
|
+
const resolveGroup = (nameOrId) => {
|
|
50498
|
+
const byId = groupList.find((g) => g.id === nameOrId);
|
|
50499
|
+
if (byId) return byId.id;
|
|
50500
|
+
const byName = groupList.find((g) => g.name.toLowerCase() === nameOrId.toLowerCase());
|
|
50501
|
+
return byName ? byName.id : null;
|
|
50502
|
+
};
|
|
50503
|
+
if (args.muteGroup) {
|
|
50504
|
+
const id = resolveGroup(args.muteGroup);
|
|
50505
|
+
if (!id) return fail(`No group named "${args.muteGroup}".`);
|
|
50506
|
+
mutedGroups.add(id);
|
|
50507
|
+
}
|
|
50508
|
+
if (args.unmuteGroup) {
|
|
50509
|
+
const id = resolveGroup(args.unmuteGroup);
|
|
50510
|
+
if (id) mutedGroups.delete(id);
|
|
50511
|
+
}
|
|
50512
|
+
if (args.muteUser) mutedUsers.add(args.muteUser.replace(/^@/, ""));
|
|
50513
|
+
if (args.unmuteUser) mutedUsers.delete(args.unmuteUser.replace(/^@/, ""));
|
|
50514
|
+
patch.mutedGroups = [...mutedGroups];
|
|
50515
|
+
patch.mutedUsers = [...mutedUsers];
|
|
50516
|
+
const p = setPrefs(patch);
|
|
50517
|
+
const names = {};
|
|
50518
|
+
if (p.mutedGroups.length) {
|
|
50519
|
+
try {
|
|
50520
|
+
for (const g of await c.groups()) names[g.id] = g.name;
|
|
50521
|
+
} catch {
|
|
50522
|
+
}
|
|
50523
|
+
}
|
|
50524
|
+
const summary = [
|
|
50525
|
+
`\u{1F514} Notifications: ${p.enabled ? "ON" : "OFF"}`,
|
|
50526
|
+
` Sound: ${p.sound ? "on" : "off"}`,
|
|
50527
|
+
` DMs: ${p.dms ? "on" : "off"} \xB7 Groups: ${p.groups ? "on" : "off"}${p.onlyMentions ? " (only @mentions)" : ""}`,
|
|
50528
|
+
` Quiet hours: ${p.quietFrom && p.quietTo ? `${p.quietFrom}\u2013${p.quietTo}` : "off"}`,
|
|
50529
|
+
` Muted groups: ${p.mutedGroups.length ? p.mutedGroups.map((id) => names[id] ?? id).join(", ") : "none"}`,
|
|
50530
|
+
` Muted people: ${p.mutedUsers.length ? p.mutedUsers.map((u) => "@" + u).join(", ") : "none"}`
|
|
50531
|
+
].join("\n");
|
|
50532
|
+
return ok(summary);
|
|
50533
|
+
})
|
|
50534
|
+
);
|
|
50535
|
+
}
|
|
50396
50536
|
}
|
|
50397
50537
|
function attachInbox(c, inbox, self, onIncoming) {
|
|
50398
50538
|
const capture = (e) => {
|
package/dist/index.js
CHANGED
|
@@ -24789,7 +24789,7 @@ var StdioServerTransport = class {
|
|
|
24789
24789
|
|
|
24790
24790
|
// src/index.ts
|
|
24791
24791
|
import fs4 from "node:fs";
|
|
24792
|
-
import
|
|
24792
|
+
import os4 from "node:os";
|
|
24793
24793
|
import path2 from "node:path";
|
|
24794
24794
|
|
|
24795
24795
|
// ../../node_modules/.pnpm/ws@8.21.0/node_modules/ws/wrapper.mjs
|
|
@@ -25281,10 +25281,120 @@ var AcpClient = class _AcpClient {
|
|
|
25281
25281
|
};
|
|
25282
25282
|
|
|
25283
25283
|
// src/tools.ts
|
|
25284
|
+
import fs3 from "node:fs";
|
|
25285
|
+
import np2 from "node:path";
|
|
25286
|
+
import cp2 from "node:child_process";
|
|
25287
|
+
import os3 from "node:os";
|
|
25288
|
+
|
|
25289
|
+
// src/notify.ts
|
|
25290
|
+
import cp from "node:child_process";
|
|
25284
25291
|
import fs2 from "node:fs";
|
|
25285
25292
|
import np from "node:path";
|
|
25286
|
-
import cp from "node:child_process";
|
|
25287
25293
|
import os2 from "node:os";
|
|
25294
|
+
var OFF = process.env.JEFRI_NOTIFY === "0" || process.env.JEFRI_NOTIFY === "false";
|
|
25295
|
+
function findTerminalNotifier() {
|
|
25296
|
+
if (process.platform !== "darwin") return null;
|
|
25297
|
+
for (const p of ["/opt/homebrew/bin/terminal-notifier", "/usr/local/bin/terminal-notifier"]) {
|
|
25298
|
+
try {
|
|
25299
|
+
if (fs2.existsSync(p)) return p;
|
|
25300
|
+
} catch {
|
|
25301
|
+
}
|
|
25302
|
+
}
|
|
25303
|
+
try {
|
|
25304
|
+
const out = (cp.spawnSync("which", ["terminal-notifier"], { encoding: "utf8" }).stdout || "").trim();
|
|
25305
|
+
if (out && fs2.existsSync(out)) return out;
|
|
25306
|
+
} catch {
|
|
25307
|
+
}
|
|
25308
|
+
return null;
|
|
25309
|
+
}
|
|
25310
|
+
var TERMINAL_NOTIFIER = findTerminalNotifier();
|
|
25311
|
+
var macNeedsTerminalNotifier = process.platform === "darwin" && !TERMINAL_NOTIFIER && !OFF;
|
|
25312
|
+
var DEFAULTS = {
|
|
25313
|
+
enabled: true,
|
|
25314
|
+
sound: true,
|
|
25315
|
+
dms: true,
|
|
25316
|
+
groups: true,
|
|
25317
|
+
onlyMentions: false,
|
|
25318
|
+
quietFrom: null,
|
|
25319
|
+
quietTo: null,
|
|
25320
|
+
mutedGroups: [],
|
|
25321
|
+
mutedUsers: []
|
|
25322
|
+
};
|
|
25323
|
+
var PREFS_DIR = np.join(os2.homedir(), ".jefri");
|
|
25324
|
+
var PREFS_FILE = np.join(PREFS_DIR, "notify.json");
|
|
25325
|
+
function loadPrefs() {
|
|
25326
|
+
try {
|
|
25327
|
+
return { ...DEFAULTS, ...JSON.parse(fs2.readFileSync(PREFS_FILE, "utf8")) };
|
|
25328
|
+
} catch {
|
|
25329
|
+
return { ...DEFAULTS };
|
|
25330
|
+
}
|
|
25331
|
+
}
|
|
25332
|
+
var prefs = loadPrefs();
|
|
25333
|
+
function getPrefs() {
|
|
25334
|
+
return { ...prefs };
|
|
25335
|
+
}
|
|
25336
|
+
function setPrefs(patch) {
|
|
25337
|
+
prefs = { ...prefs, ...patch };
|
|
25338
|
+
try {
|
|
25339
|
+
fs2.mkdirSync(PREFS_DIR, { recursive: true });
|
|
25340
|
+
fs2.writeFileSync(PREFS_FILE, JSON.stringify(prefs, null, 2));
|
|
25341
|
+
} catch {
|
|
25342
|
+
}
|
|
25343
|
+
return { ...prefs };
|
|
25344
|
+
}
|
|
25345
|
+
function inQuietHours() {
|
|
25346
|
+
if (!prefs.quietFrom || !prefs.quietTo) return false;
|
|
25347
|
+
const parse3 = (s) => {
|
|
25348
|
+
const [h, m] = s.split(":").map(Number);
|
|
25349
|
+
return (h || 0) * 60 + (m || 0);
|
|
25350
|
+
};
|
|
25351
|
+
const now = /* @__PURE__ */ new Date();
|
|
25352
|
+
const cur = now.getHours() * 60 + now.getMinutes();
|
|
25353
|
+
const from = parse3(prefs.quietFrom), to = parse3(prefs.quietTo);
|
|
25354
|
+
if (from === to) return false;
|
|
25355
|
+
return from < to ? cur >= from && cur < to : cur >= from || cur < to;
|
|
25356
|
+
}
|
|
25357
|
+
var clean = (s) => (s || "").replace(/[\r\n\t]+/g, " ").replace(/[\x00-\x1F]/g, "").slice(0, 140).trim();
|
|
25358
|
+
function notifyIncoming(m) {
|
|
25359
|
+
if (OFF || !prefs.enabled) return;
|
|
25360
|
+
if (m.isGroup ? !prefs.groups : !prefs.dms) return;
|
|
25361
|
+
if (m.isGroup && prefs.onlyMentions && !m.isMention) return;
|
|
25362
|
+
if (m.isGroup && m.groupId && prefs.mutedGroups.includes(m.groupId)) return;
|
|
25363
|
+
if (prefs.mutedUsers.includes(m.sender)) return;
|
|
25364
|
+
if (inQuietHours()) return;
|
|
25365
|
+
const sound = prefs.sound;
|
|
25366
|
+
const title = "Jefri Chat";
|
|
25367
|
+
const body = clean(m.body);
|
|
25368
|
+
if (sound) {
|
|
25369
|
+
try {
|
|
25370
|
+
process.stderr.write("\x07");
|
|
25371
|
+
} catch {
|
|
25372
|
+
}
|
|
25373
|
+
}
|
|
25374
|
+
try {
|
|
25375
|
+
if (process.platform === "darwin") {
|
|
25376
|
+
if (TERMINAL_NOTIFIER) {
|
|
25377
|
+
const args = ["-title", title, "-message", body];
|
|
25378
|
+
if (sound) args.push("-sound", "Ping");
|
|
25379
|
+
cp.spawn(TERMINAL_NOTIFIER, args, { stdio: "ignore", detached: true }).unref();
|
|
25380
|
+
} else {
|
|
25381
|
+
const esc2 = (x) => x.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
25382
|
+
const snd = sound ? ` sound name "Ping"` : "";
|
|
25383
|
+
const script = `display notification "${esc2(body)}" with title "${esc2(title)}"${snd}`;
|
|
25384
|
+
cp.spawn("osascript", ["-e", script], { stdio: "ignore", detached: true }).unref();
|
|
25385
|
+
}
|
|
25386
|
+
} else if (process.platform === "win32") {
|
|
25387
|
+
const esc2 = (x) => x.replace(/'/g, "''");
|
|
25388
|
+
const ps = `Add-Type -AssemblyName System.Windows.Forms;$n=New-Object System.Windows.Forms.NotifyIcon;$n.Icon=[System.Drawing.SystemIcons]::Information;$n.BalloonTipTitle='${esc2(title)}';$n.BalloonTipText='${esc2(body)}';$n.Visible=$true;$n.ShowBalloonTip(6000);Start-Sleep -Milliseconds 6500;$n.Dispose()`;
|
|
25389
|
+
cp.spawn("powershell", ["-NoProfile", "-WindowStyle", "Hidden", "-Command", ps], { stdio: "ignore", detached: true }).unref();
|
|
25390
|
+
} else {
|
|
25391
|
+
cp.spawn("notify-send", ["-a", "Jefri Chat", title, body], { stdio: "ignore", detached: true }).unref();
|
|
25392
|
+
}
|
|
25393
|
+
} catch {
|
|
25394
|
+
}
|
|
25395
|
+
}
|
|
25396
|
+
|
|
25397
|
+
// src/tools.ts
|
|
25288
25398
|
var MIME = {
|
|
25289
25399
|
".jpg": "image/jpeg",
|
|
25290
25400
|
".jpeg": "image/jpeg",
|
|
@@ -25304,21 +25414,21 @@ var MIME = {
|
|
|
25304
25414
|
".json": "application/json",
|
|
25305
25415
|
".zip": "application/zip"
|
|
25306
25416
|
};
|
|
25307
|
-
var mimeOf = (name) => MIME[
|
|
25417
|
+
var mimeOf = (name) => MIME[np2.extname(name).toLowerCase()] ?? "application/octet-stream";
|
|
25308
25418
|
function screenshotDir() {
|
|
25309
25419
|
try {
|
|
25310
|
-
const out =
|
|
25311
|
-
if (out) return out.startsWith("~") ?
|
|
25420
|
+
const out = cp2.execFileSync("defaults", ["read", "com.apple.screencapture", "location"], { encoding: "utf8", timeout: 2e3 }).trim();
|
|
25421
|
+
if (out) return out.startsWith("~") ? np2.join(os3.homedir(), out.slice(1)) : out;
|
|
25312
25422
|
} catch {
|
|
25313
25423
|
}
|
|
25314
|
-
const desktop =
|
|
25315
|
-
return
|
|
25424
|
+
const desktop = np2.join(os3.homedir(), "Desktop");
|
|
25425
|
+
return fs3.existsSync(desktop) ? desktop : null;
|
|
25316
25426
|
}
|
|
25317
25427
|
function resolveLocalFile(rawPath) {
|
|
25318
|
-
const expand = (p) => p.startsWith("~") ?
|
|
25428
|
+
const expand = (p) => p.startsWith("~") ? np2.join(os3.homedir(), p.slice(1)) : p;
|
|
25319
25429
|
const exists = (p) => {
|
|
25320
25430
|
try {
|
|
25321
|
-
return
|
|
25431
|
+
return fs3.existsSync(p) ? p : null;
|
|
25322
25432
|
} catch {
|
|
25323
25433
|
return null;
|
|
25324
25434
|
}
|
|
@@ -25331,26 +25441,26 @@ function resolveLocalFile(rawPath) {
|
|
|
25331
25441
|
hit = exists(v);
|
|
25332
25442
|
if (hit) return hit;
|
|
25333
25443
|
}
|
|
25334
|
-
const base =
|
|
25444
|
+
const base = np2.basename(abs);
|
|
25335
25445
|
const looksLikeShot = /screencaptureui/i.test(abs) || /^Screenshot[\s ]/i.test(base);
|
|
25336
25446
|
if (looksLikeShot) {
|
|
25337
25447
|
const dir = screenshotDir();
|
|
25338
25448
|
if (dir) {
|
|
25339
25449
|
let files = [];
|
|
25340
25450
|
try {
|
|
25341
|
-
files =
|
|
25451
|
+
files = fs3.readdirSync(dir);
|
|
25342
25452
|
} catch {
|
|
25343
25453
|
}
|
|
25344
25454
|
const norm = (s) => s.replace(/[ \s]+/g, " ").trim().toLowerCase();
|
|
25345
25455
|
const want = norm(base);
|
|
25346
25456
|
for (const f of files) if (norm(f) === want) {
|
|
25347
|
-
hit = exists(
|
|
25457
|
+
hit = exists(np2.join(dir, f));
|
|
25348
25458
|
if (hit) return hit;
|
|
25349
25459
|
}
|
|
25350
25460
|
const shots = files.filter((f) => /^Screenshot[\s ].*\.(png|jpe?g)$/i.test(f)).map((f) => {
|
|
25351
|
-
const p =
|
|
25461
|
+
const p = np2.join(dir, f);
|
|
25352
25462
|
try {
|
|
25353
|
-
return { p, m:
|
|
25463
|
+
return { p, m: fs3.statSync(p).mtimeMs };
|
|
25354
25464
|
} catch {
|
|
25355
25465
|
return { p, m: 0 };
|
|
25356
25466
|
}
|
|
@@ -25510,8 +25620,8 @@ ${fp}`);
|
|
|
25510
25620
|
`I can't find a file at ${path3} on this machine.`
|
|
25511
25621
|
);
|
|
25512
25622
|
}
|
|
25513
|
-
const buf =
|
|
25514
|
-
name =
|
|
25623
|
+
const buf = fs3.readFileSync(abs);
|
|
25624
|
+
name = np2.basename(abs);
|
|
25515
25625
|
mime = mimeOf(name);
|
|
25516
25626
|
url = `data:${mime};base64,${buf.toString("base64")}`;
|
|
25517
25627
|
} else if (dataUrl && fileName) {
|
|
@@ -25571,8 +25681,8 @@ ${fp}`);
|
|
|
25571
25681
|
`I can't find a file at ${path3} on this machine.`
|
|
25572
25682
|
);
|
|
25573
25683
|
}
|
|
25574
|
-
const buf =
|
|
25575
|
-
name =
|
|
25684
|
+
const buf = fs3.readFileSync(abs);
|
|
25685
|
+
name = np2.basename(abs);
|
|
25576
25686
|
mime = mimeOf(name);
|
|
25577
25687
|
url = `data:${mime};base64,${buf.toString("base64")}`;
|
|
25578
25688
|
} else if (dataUrl && fileName) {
|
|
@@ -25604,7 +25714,7 @@ ${fp}`);
|
|
|
25604
25714
|
let name, mime, url;
|
|
25605
25715
|
if (path3) {
|
|
25606
25716
|
if (!ctx.local) {
|
|
25607
|
-
const fname =
|
|
25717
|
+
const fname = np2.basename(path3);
|
|
25608
25718
|
const fmime = mimeOf(fname);
|
|
25609
25719
|
const hub = ctx.serverUrl.replace(/\/$/, "");
|
|
25610
25720
|
const web = hub.replace("acp-hub.", "acp-web.").replace(":4000", ":4321");
|
|
@@ -25629,8 +25739,8 @@ ${psCmd}
|
|
|
25629
25739
|
if (!abs) {
|
|
25630
25740
|
return fail(`I can't find a file at ${path3} on this machine.`);
|
|
25631
25741
|
}
|
|
25632
|
-
const buf =
|
|
25633
|
-
name =
|
|
25742
|
+
const buf = fs3.readFileSync(abs);
|
|
25743
|
+
name = np2.basename(abs);
|
|
25634
25744
|
mime = mimeOf(name);
|
|
25635
25745
|
url = `data:${mime};base64,${buf.toString("base64")}`;
|
|
25636
25746
|
} else if (dataUrl && fileName) {
|
|
@@ -25711,57 +25821,57 @@ ${psCmd}
|
|
|
25711
25821
|
`Sending a folder reads files off disk, which this connector can't do \u2014 it runs in the cloud. Run jefri_send_folder from a LOCAL agent (Claude Code with the stdio connector) inside the project.`
|
|
25712
25822
|
);
|
|
25713
25823
|
const rawDir = path3 && path3.trim() ? path3 : process.cwd();
|
|
25714
|
-
const dir = rawDir.startsWith("~") ?
|
|
25715
|
-
if (!
|
|
25824
|
+
const dir = rawDir.startsWith("~") ? np2.join(os3.homedir(), rawDir.slice(1)) : rawDir;
|
|
25825
|
+
if (!fs3.existsSync(dir))
|
|
25716
25826
|
return fail(
|
|
25717
25827
|
`I can't see "${rawDir}" from here. Sending a folder reads it off disk, which only works on the LOCAL (stdio) connector \u2014 run this from Claude Code (or another local agent) inside the project.`
|
|
25718
25828
|
);
|
|
25719
25829
|
let stat;
|
|
25720
25830
|
try {
|
|
25721
|
-
stat =
|
|
25831
|
+
stat = fs3.statSync(dir);
|
|
25722
25832
|
} catch {
|
|
25723
25833
|
return fail(`Cannot read "${rawDir}".`);
|
|
25724
25834
|
}
|
|
25725
25835
|
if (!stat.isDirectory())
|
|
25726
25836
|
return fail(`"${rawDir}" is a file, not a folder \u2014 use jefri_send_file for a single file.`);
|
|
25727
25837
|
try {
|
|
25728
|
-
|
|
25838
|
+
cp2.execFileSync("zip", ["-v"], { stdio: "ignore" });
|
|
25729
25839
|
} catch {
|
|
25730
25840
|
return fail(`The 'zip' command isn't available on this machine, and I need it to package the folder.`);
|
|
25731
25841
|
}
|
|
25732
|
-
const base =
|
|
25733
|
-
const tmpZip =
|
|
25842
|
+
const base = np2.basename(np2.resolve(dir)) || "project";
|
|
25843
|
+
const tmpZip = np2.join(os3.tmpdir(), `acp-${base}-${Date.now()}.zip`);
|
|
25734
25844
|
let isRepo = false;
|
|
25735
25845
|
try {
|
|
25736
|
-
|
|
25846
|
+
cp2.execFileSync("git", ["-C", dir, "rev-parse", "--is-inside-work-tree"], { stdio: "ignore" });
|
|
25737
25847
|
isRepo = true;
|
|
25738
25848
|
} catch {
|
|
25739
25849
|
isRepo = false;
|
|
25740
25850
|
}
|
|
25741
25851
|
try {
|
|
25742
25852
|
if (isRepo) {
|
|
25743
|
-
|
|
25853
|
+
cp2.execSync(
|
|
25744
25854
|
`cd ${shq(dir)} && git ls-files --cached --others --exclude-standard -z | xargs -0 zip -q ${shq(tmpZip)} --`,
|
|
25745
25855
|
{ stdio: "ignore" }
|
|
25746
25856
|
);
|
|
25747
25857
|
} else {
|
|
25748
|
-
|
|
25858
|
+
cp2.execSync(
|
|
25749
25859
|
`cd ${shq(dir)} && zip -qr ${shq(tmpZip)} . -x '*/node_modules/*' '*/.git/*' '*/dist/*' '*/.next/*' '*/build/*' '*/__pycache__/*' '*/.venv/*' '*/venv/*' '*.log' '*/.DS_Store'`,
|
|
25750
25860
|
{ stdio: "ignore" }
|
|
25751
25861
|
);
|
|
25752
25862
|
}
|
|
25753
25863
|
} catch (e) {
|
|
25754
25864
|
try {
|
|
25755
|
-
|
|
25865
|
+
fs3.rmSync(tmpZip, { force: true });
|
|
25756
25866
|
} catch {
|
|
25757
25867
|
}
|
|
25758
25868
|
return fail(`Could not zip "${rawDir}": ${e?.message ?? e}`);
|
|
25759
25869
|
}
|
|
25760
|
-
if (!
|
|
25870
|
+
if (!fs3.existsSync(tmpZip))
|
|
25761
25871
|
return fail(`Nothing to send \u2014 "${rawDir}" produced an empty archive (all files ignored or folder empty).`);
|
|
25762
|
-
const size =
|
|
25872
|
+
const size = fs3.statSync(tmpZip).size;
|
|
25763
25873
|
if (size > MAX_UPLOAD_BYTES) {
|
|
25764
|
-
|
|
25874
|
+
fs3.rmSync(tmpZip, { force: true });
|
|
25765
25875
|
return fail(
|
|
25766
25876
|
`That archive is ${MB(size)} MB, over the ${Math.round(MAX_UPLOAD_BYTES / 1024 / 1024)} MB limit. Send a subfolder (e.g. path=\u2026/src) or remove large files, then try again.`
|
|
25767
25877
|
);
|
|
@@ -25773,7 +25883,7 @@ ${psCmd}
|
|
|
25773
25883
|
const res = await fetch(`${hub}/api/files?${q}`, {
|
|
25774
25884
|
method: "POST",
|
|
25775
25885
|
headers: { Authorization: `Bearer ${c.token}`, "Content-Type": "application/zip" },
|
|
25776
|
-
body:
|
|
25886
|
+
body: fs3.readFileSync(tmpZip)
|
|
25777
25887
|
});
|
|
25778
25888
|
if (!res.ok) {
|
|
25779
25889
|
const t = await res.text().catch(() => "");
|
|
@@ -25783,7 +25893,7 @@ ${psCmd}
|
|
|
25783
25893
|
return fail(`Upload failed: ${e?.message ?? e}`);
|
|
25784
25894
|
} finally {
|
|
25785
25895
|
try {
|
|
25786
|
-
|
|
25896
|
+
fs3.rmSync(tmpZip, { force: true });
|
|
25787
25897
|
} catch {
|
|
25788
25898
|
}
|
|
25789
25899
|
}
|
|
@@ -25818,12 +25928,12 @@ ${psCmd}
|
|
|
25818
25928
|
const hub = ctx.serverUrl.replace(/\/$/, "");
|
|
25819
25929
|
const out = savePath || match.fileName || "downloaded.file";
|
|
25820
25930
|
if (ctx.local) {
|
|
25821
|
-
const outPath = out.startsWith("~") ?
|
|
25931
|
+
const outPath = out.startsWith("~") ? np2.join(os3.homedir(), out.slice(1)) : out;
|
|
25822
25932
|
try {
|
|
25823
25933
|
const r = await fetch(`${hub}/api/files/${match.id}`, { headers: { authorization: `Bearer ${c.token}` } });
|
|
25824
25934
|
if (!r.ok) throw new Error(`download failed (${r.status})`);
|
|
25825
25935
|
const buf = Buffer.from(await r.arrayBuffer());
|
|
25826
|
-
|
|
25936
|
+
fs3.writeFileSync(outPath, buf);
|
|
25827
25937
|
return ok(`\u2705 Saved \u{1F4CE} ${match.fileName} from ${where} to ${outPath} (${MB(buf.length)} MB).`);
|
|
25828
25938
|
} catch (e) {
|
|
25829
25939
|
return fail(`Couldn't save the file: ${e?.message ?? e}`);
|
|
@@ -25924,7 +26034,7 @@ ${cmd}`);
|
|
|
25924
26034
|
}
|
|
25925
26035
|
},
|
|
25926
26036
|
async ({ departmentId, path: path3, kind }) => withClient(async (c) => {
|
|
25927
|
-
const fname =
|
|
26037
|
+
const fname = np2.basename(path3.startsWith("~") ? path3.slice(1) : path3);
|
|
25928
26038
|
const mime = mimeOf(fname);
|
|
25929
26039
|
const q = `name=${encodeURIComponent(fname)}&kind=${kind ?? "file"}`;
|
|
25930
26040
|
const cmd = `curl -s -X POST ${shq(`${hubBase()}/api/departments/${departmentId}/files?${q}`)} -H "Authorization: Bearer ${c.token}" -H "Content-Type: ${mime}" --data-binary @${shq(path3)}`;
|
|
@@ -26008,7 +26118,7 @@ ${cmd}`);
|
|
|
26008
26118
|
inputSchema: { path: external_exports.string().describe("local file path to upload") }
|
|
26009
26119
|
},
|
|
26010
26120
|
async ({ path: path3 }) => withClient(async (c) => {
|
|
26011
|
-
const fname =
|
|
26121
|
+
const fname = np2.basename(path3.startsWith("~") ? path3.slice(1) : path3);
|
|
26012
26122
|
const mime = mimeOf(fname);
|
|
26013
26123
|
const cmd = `curl -s -X POST ${shq(`${hubBase()}/api/agents/${encodeURIComponent(c.identity.username)}/docs?name=${encodeURIComponent(fname)}`)} -H "Authorization: Bearer ${c.token}" -H "Content-Type: ${mime}" --data-binary @${shq(path3)}`;
|
|
26014
26124
|
return ok(`To add ${fname} to your document memory, run in the terminal:
|
|
@@ -26160,7 +26270,7 @@ ${cmd}`);
|
|
|
26160
26270
|
if (path3) {
|
|
26161
26271
|
if (!ctx.local) {
|
|
26162
26272
|
const hub = ctx.serverUrl.replace(/\/$/, "");
|
|
26163
|
-
const fname =
|
|
26273
|
+
const fname = np2.basename(path3);
|
|
26164
26274
|
const fmime = mimeOf(fname);
|
|
26165
26275
|
const q = `groupId=${encodeURIComponent(groupId)}&fileName=${encodeURIComponent(fname)}` + (caption ? `&caption=${encodeURIComponent(caption)}` : "");
|
|
26166
26276
|
const cmd = `curl -s -X POST ${shq(`${hub}/api/files?${q}`)} -H "Authorization: Bearer ${c.token}" -H "Content-Type: ${fmime}" --data-binary @${shq(path3)}`;
|
|
@@ -26172,8 +26282,8 @@ ${cmd}`
|
|
|
26172
26282
|
}
|
|
26173
26283
|
const abs = resolveLocalFile(path3);
|
|
26174
26284
|
if (!abs) return fail(`I can't find a file at ${path3} on this machine.`);
|
|
26175
|
-
const buf =
|
|
26176
|
-
name =
|
|
26285
|
+
const buf = fs3.readFileSync(abs);
|
|
26286
|
+
name = np2.basename(abs);
|
|
26177
26287
|
mime = mimeOf(name);
|
|
26178
26288
|
url = `data:${mime};base64,${buf.toString("base64")}`;
|
|
26179
26289
|
} else if (dataUrl && fileName) {
|
|
@@ -26301,6 +26411,87 @@ ${cmd}`
|
|
|
26301
26411
|
return ok(`Status set to ${status}.`);
|
|
26302
26412
|
})
|
|
26303
26413
|
);
|
|
26414
|
+
if (ctx.local) {
|
|
26415
|
+
server2.registerTool(
|
|
26416
|
+
"jefri_notifications",
|
|
26417
|
+
{
|
|
26418
|
+
title: "Notification settings",
|
|
26419
|
+
description: "Show or change desktop notification preferences: master on/off, sound on/off, DMs vs groups, only-@mentions for groups, quiet hours, and muting a specific group or person. Call with NO arguments to just see the current settings. Persists across sessions.",
|
|
26420
|
+
inputSchema: {
|
|
26421
|
+
enabled: external_exports.boolean().optional().describe("master switch for all desktop notifications"),
|
|
26422
|
+
sound: external_exports.boolean().optional().describe("play a sound with notifications"),
|
|
26423
|
+
dms: external_exports.boolean().optional().describe("notify for direct messages"),
|
|
26424
|
+
groups: external_exports.boolean().optional().describe("notify for group messages"),
|
|
26425
|
+
onlyMentions: external_exports.boolean().optional().describe("for groups: only notify when someone @-mentions you"),
|
|
26426
|
+
quietFrom: external_exports.string().optional().describe("start of quiet hours, HH:MM 24h (e.g. 22:00), or 'off' to clear"),
|
|
26427
|
+
quietTo: external_exports.string().optional().describe("end of quiet hours, HH:MM 24h (e.g. 08:00)"),
|
|
26428
|
+
muteGroup: external_exports.string().optional().describe("a group's name or id to silence"),
|
|
26429
|
+
unmuteGroup: external_exports.string().optional().describe("a group's name or id to unmute"),
|
|
26430
|
+
muteUser: external_exports.string().optional().describe("a username to silence"),
|
|
26431
|
+
unmuteUser: external_exports.string().optional().describe("a username to unmute")
|
|
26432
|
+
}
|
|
26433
|
+
},
|
|
26434
|
+
async (args) => withClient(async (c) => {
|
|
26435
|
+
const patch = {};
|
|
26436
|
+
for (const k of ["enabled", "sound", "dms", "groups", "onlyMentions"])
|
|
26437
|
+
if (typeof args[k] === "boolean") patch[k] = args[k];
|
|
26438
|
+
if (args.quietFrom === "off" || args.quietTo === "off") {
|
|
26439
|
+
patch.quietFrom = null;
|
|
26440
|
+
patch.quietTo = null;
|
|
26441
|
+
} else {
|
|
26442
|
+
if (args.quietFrom) patch.quietFrom = args.quietFrom;
|
|
26443
|
+
if (args.quietTo) patch.quietTo = args.quietTo;
|
|
26444
|
+
}
|
|
26445
|
+
const cur = getPrefs();
|
|
26446
|
+
const mutedGroups = new Set(cur.mutedGroups);
|
|
26447
|
+
const mutedUsers = new Set(cur.mutedUsers);
|
|
26448
|
+
let groupList = [];
|
|
26449
|
+
const needGroups = args.muteGroup || args.unmuteGroup;
|
|
26450
|
+
if (needGroups) {
|
|
26451
|
+
try {
|
|
26452
|
+
groupList = await c.groups();
|
|
26453
|
+
} catch {
|
|
26454
|
+
}
|
|
26455
|
+
}
|
|
26456
|
+
const resolveGroup = (nameOrId) => {
|
|
26457
|
+
const byId = groupList.find((g) => g.id === nameOrId);
|
|
26458
|
+
if (byId) return byId.id;
|
|
26459
|
+
const byName = groupList.find((g) => g.name.toLowerCase() === nameOrId.toLowerCase());
|
|
26460
|
+
return byName ? byName.id : null;
|
|
26461
|
+
};
|
|
26462
|
+
if (args.muteGroup) {
|
|
26463
|
+
const id = resolveGroup(args.muteGroup);
|
|
26464
|
+
if (!id) return fail(`No group named "${args.muteGroup}".`);
|
|
26465
|
+
mutedGroups.add(id);
|
|
26466
|
+
}
|
|
26467
|
+
if (args.unmuteGroup) {
|
|
26468
|
+
const id = resolveGroup(args.unmuteGroup);
|
|
26469
|
+
if (id) mutedGroups.delete(id);
|
|
26470
|
+
}
|
|
26471
|
+
if (args.muteUser) mutedUsers.add(args.muteUser.replace(/^@/, ""));
|
|
26472
|
+
if (args.unmuteUser) mutedUsers.delete(args.unmuteUser.replace(/^@/, ""));
|
|
26473
|
+
patch.mutedGroups = [...mutedGroups];
|
|
26474
|
+
patch.mutedUsers = [...mutedUsers];
|
|
26475
|
+
const p = setPrefs(patch);
|
|
26476
|
+
const names = {};
|
|
26477
|
+
if (p.mutedGroups.length) {
|
|
26478
|
+
try {
|
|
26479
|
+
for (const g of await c.groups()) names[g.id] = g.name;
|
|
26480
|
+
} catch {
|
|
26481
|
+
}
|
|
26482
|
+
}
|
|
26483
|
+
const summary = [
|
|
26484
|
+
`\u{1F514} Notifications: ${p.enabled ? "ON" : "OFF"}`,
|
|
26485
|
+
` Sound: ${p.sound ? "on" : "off"}`,
|
|
26486
|
+
` DMs: ${p.dms ? "on" : "off"} \xB7 Groups: ${p.groups ? "on" : "off"}${p.onlyMentions ? " (only @mentions)" : ""}`,
|
|
26487
|
+
` Quiet hours: ${p.quietFrom && p.quietTo ? `${p.quietFrom}\u2013${p.quietTo}` : "off"}`,
|
|
26488
|
+
` Muted groups: ${p.mutedGroups.length ? p.mutedGroups.map((id) => names[id] ?? id).join(", ") : "none"}`,
|
|
26489
|
+
` Muted people: ${p.mutedUsers.length ? p.mutedUsers.map((u) => "@" + u).join(", ") : "none"}`
|
|
26490
|
+
].join("\n");
|
|
26491
|
+
return ok(summary);
|
|
26492
|
+
})
|
|
26493
|
+
);
|
|
26494
|
+
}
|
|
26304
26495
|
}
|
|
26305
26496
|
function attachInbox(c, inbox2, self, onIncoming) {
|
|
26306
26497
|
const capture = (e) => {
|
|
@@ -26317,56 +26508,6 @@ function attachInbox(c, inbox2, self, onIncoming) {
|
|
|
26317
26508
|
c.on("file_received", capture);
|
|
26318
26509
|
}
|
|
26319
26510
|
|
|
26320
|
-
// src/notify.ts
|
|
26321
|
-
import cp2 from "node:child_process";
|
|
26322
|
-
import fs3 from "node:fs";
|
|
26323
|
-
var OFF = process.env.JEFRI_NOTIFY === "0" || process.env.JEFRI_NOTIFY === "false";
|
|
26324
|
-
function findTerminalNotifier() {
|
|
26325
|
-
if (process.platform !== "darwin") return null;
|
|
26326
|
-
for (const p of ["/opt/homebrew/bin/terminal-notifier", "/usr/local/bin/terminal-notifier"]) {
|
|
26327
|
-
try {
|
|
26328
|
-
if (fs3.existsSync(p)) return p;
|
|
26329
|
-
} catch {
|
|
26330
|
-
}
|
|
26331
|
-
}
|
|
26332
|
-
try {
|
|
26333
|
-
const out = (cp2.spawnSync("which", ["terminal-notifier"], { encoding: "utf8" }).stdout || "").trim();
|
|
26334
|
-
if (out && fs3.existsSync(out)) return out;
|
|
26335
|
-
} catch {
|
|
26336
|
-
}
|
|
26337
|
-
return null;
|
|
26338
|
-
}
|
|
26339
|
-
var TERMINAL_NOTIFIER = findTerminalNotifier();
|
|
26340
|
-
var macNeedsTerminalNotifier = process.platform === "darwin" && !TERMINAL_NOTIFIER && !OFF;
|
|
26341
|
-
var clean = (s) => (s || "").replace(/[\r\n\t]+/g, " ").replace(/[\x00-\x1F]/g, "").slice(0, 140).trim();
|
|
26342
|
-
function notifyIncoming(title, body) {
|
|
26343
|
-
if (OFF) return;
|
|
26344
|
-
const t = clean(title) || "Jefri Chat";
|
|
26345
|
-
const b = clean(body);
|
|
26346
|
-
try {
|
|
26347
|
-
process.stderr.write("\x07");
|
|
26348
|
-
} catch {
|
|
26349
|
-
}
|
|
26350
|
-
try {
|
|
26351
|
-
if (process.platform === "darwin") {
|
|
26352
|
-
if (TERMINAL_NOTIFIER) {
|
|
26353
|
-
cp2.spawn(TERMINAL_NOTIFIER, ["-title", t, "-message", b, "-sound", "Ping"], { stdio: "ignore", detached: true }).unref();
|
|
26354
|
-
} else {
|
|
26355
|
-
const esc2 = (x) => x.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
26356
|
-
const script = `display notification "${esc2(b)}" with title "${esc2(t)}" sound name "Ping"`;
|
|
26357
|
-
cp2.spawn("osascript", ["-e", script], { stdio: "ignore", detached: true }).unref();
|
|
26358
|
-
}
|
|
26359
|
-
} else if (process.platform === "win32") {
|
|
26360
|
-
const esc2 = (x) => x.replace(/'/g, "''");
|
|
26361
|
-
const ps = `Add-Type -AssemblyName System.Windows.Forms;$n=New-Object System.Windows.Forms.NotifyIcon;$n.Icon=[System.Drawing.SystemIcons]::Information;$n.BalloonTipTitle='${esc2(t)}';$n.BalloonTipText='${esc2(b)}';$n.Visible=$true;$n.ShowBalloonTip(6000);Start-Sleep -Milliseconds 6500;$n.Dispose()`;
|
|
26362
|
-
cp2.spawn("powershell", ["-NoProfile", "-WindowStyle", "Hidden", "-Command", ps], { stdio: "ignore", detached: true }).unref();
|
|
26363
|
-
} else {
|
|
26364
|
-
cp2.spawn("notify-send", ["-a", "Jefri Chat", t, b], { stdio: "ignore", detached: true }).unref();
|
|
26365
|
-
}
|
|
26366
|
-
} catch {
|
|
26367
|
-
}
|
|
26368
|
-
}
|
|
26369
|
-
|
|
26370
26511
|
// src/index.ts
|
|
26371
26512
|
var SERVER = process.env.ACP_SERVER ?? "http://localhost:4000";
|
|
26372
26513
|
var TOKEN = process.env.ACP_TOKEN;
|
|
@@ -26375,7 +26516,7 @@ var NAME = process.env.ACP_NAME ?? USERNAME;
|
|
|
26375
26516
|
var TAGS = (process.env.ACP_TAGS ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
26376
26517
|
var CAPS = (process.env.ACP_CAPABILITIES ?? "coding").split(",").map((s) => s.trim()).filter(Boolean);
|
|
26377
26518
|
var log = (...a) => console.error("[jefrichat-mcp]", ...a);
|
|
26378
|
-
var TOKEN_CACHE = path2.join(
|
|
26519
|
+
var TOKEN_CACHE = path2.join(os4.homedir(), ".acp", "tokens.json");
|
|
26379
26520
|
var cacheKey = `${SERVER}::${USERNAME}`;
|
|
26380
26521
|
function readTokenCache() {
|
|
26381
26522
|
try {
|
|
@@ -26432,8 +26573,16 @@ function ensureClient() {
|
|
|
26432
26573
|
log("tip: for reliable desktop notifications on macOS, run: brew install terminal-notifier");
|
|
26433
26574
|
attachInbox(c, inbox, self, (m) => {
|
|
26434
26575
|
const preview = m.kind === "file" ? `\u{1F4CE} ${m.fileName ?? "file"}` : m.content ?? "";
|
|
26435
|
-
const
|
|
26436
|
-
|
|
26576
|
+
const isGroup = !!m.groupId;
|
|
26577
|
+
const gname = isGroup ? groupNames.get(m.groupId) ?? "group" : "";
|
|
26578
|
+
const isMention = isGroup && typeof m.content === "string" && new RegExp(`@${self}\\b`, "i").test(m.content);
|
|
26579
|
+
notifyIncoming({
|
|
26580
|
+
body: `${m.senderUsername}${isGroup ? ` \xB7 ${gname}` : ""}: ${preview}`,
|
|
26581
|
+
isGroup,
|
|
26582
|
+
groupId: m.groupId,
|
|
26583
|
+
sender: m.senderUsername,
|
|
26584
|
+
isMention
|
|
26585
|
+
});
|
|
26437
26586
|
});
|
|
26438
26587
|
log(`connected to ${SERVER} as ${self}`);
|
|
26439
26588
|
return c;
|
package/package.json
CHANGED