jefrichat-mcp 0.8.0 → 0.9.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 +158 -55
- package/dist/index.js +272 -57
- 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 fs5 = __require("fs");
|
|
1304
|
+
stream2 = new fs5.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 fs5 = __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 fs5.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 fs5 = __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 = fs5.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 fs5 = __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
|
+
fs5.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
|
+
fs5.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
|
+
fs5.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 = fs5.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, fs5, 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, fs5[f]);
|
|
29748
29748
|
}
|
|
29749
29749
|
module.exports = exports = formatsPlugin;
|
|
29750
29750
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -48938,6 +48938,8 @@ var AcpClient = class _AcpClient {
|
|
|
48938
48938
|
closing = false;
|
|
48939
48939
|
authFailed = false;
|
|
48940
48940
|
// a bad token must NOT trigger a reconnect storm
|
|
48941
|
+
isAlive = true;
|
|
48942
|
+
// heartbeat: did we get a pong since the last ping?
|
|
48941
48943
|
constructor(server) {
|
|
48942
48944
|
this.server = server.replace(/\/$/, "");
|
|
48943
48945
|
}
|
|
@@ -49038,15 +49040,45 @@ var AcpClient = class _AcpClient {
|
|
|
49038
49040
|
});
|
|
49039
49041
|
});
|
|
49040
49042
|
}
|
|
49041
|
-
//
|
|
49043
|
+
// Heartbeat: keep the connection warm through idle proxies AND detect a
|
|
49044
|
+
// half-open (silently dead) socket. A dropped Wi-Fi / VPN / sleep can leave
|
|
49045
|
+
// the socket readyState=OPEN forever while no data flows, so `close` never
|
|
49046
|
+
// fires and we'd never reconnect. So each tick we send a WS-level ping (the
|
|
49047
|
+
// server's ws auto-pongs) and require a pong to have arrived since the last
|
|
49048
|
+
// tick — if not, the link is dead, so terminate() it, which fires `close` and
|
|
49049
|
+
// triggers the existing reconnect.
|
|
49042
49050
|
startPing() {
|
|
49043
49051
|
this.stopPing();
|
|
49052
|
+
this.isAlive = true;
|
|
49053
|
+
try {
|
|
49054
|
+
this.ws.on("pong", () => {
|
|
49055
|
+
this.isAlive = true;
|
|
49056
|
+
});
|
|
49057
|
+
} catch {
|
|
49058
|
+
}
|
|
49044
49059
|
this.pingTimer = setInterval(() => {
|
|
49060
|
+
if (this.ws.readyState !== this.ws.OPEN) return;
|
|
49061
|
+
if (!this.isAlive) {
|
|
49062
|
+
try {
|
|
49063
|
+
this.ws.terminate();
|
|
49064
|
+
} catch {
|
|
49065
|
+
try {
|
|
49066
|
+
this.ws.close();
|
|
49067
|
+
} catch {
|
|
49068
|
+
}
|
|
49069
|
+
}
|
|
49070
|
+
return;
|
|
49071
|
+
}
|
|
49072
|
+
this.isAlive = false;
|
|
49073
|
+
try {
|
|
49074
|
+
this.ws.ping();
|
|
49075
|
+
} catch {
|
|
49076
|
+
}
|
|
49045
49077
|
try {
|
|
49046
|
-
|
|
49078
|
+
this.ws.send(JSON.stringify({ type: "ping" }));
|
|
49047
49079
|
} catch {
|
|
49048
49080
|
}
|
|
49049
|
-
},
|
|
49081
|
+
}, 15e3);
|
|
49050
49082
|
}
|
|
49051
49083
|
stopPing() {
|
|
49052
49084
|
if (this.pingTimer) clearInterval(this.pingTimer);
|
|
@@ -49373,10 +49405,10 @@ var AcpClient = class _AcpClient {
|
|
|
49373
49405
|
};
|
|
49374
49406
|
|
|
49375
49407
|
// src/tools.ts
|
|
49376
|
-
import
|
|
49377
|
-
import
|
|
49408
|
+
import fs4 from "node:fs";
|
|
49409
|
+
import np3 from "node:path";
|
|
49378
49410
|
import cp2 from "node:child_process";
|
|
49379
|
-
import
|
|
49411
|
+
import os4 from "node:os";
|
|
49380
49412
|
|
|
49381
49413
|
// src/notify.ts
|
|
49382
49414
|
import cp from "node:child_process";
|
|
@@ -49435,6 +49467,39 @@ function setPrefs(patch) {
|
|
|
49435
49467
|
return { ...prefs };
|
|
49436
49468
|
}
|
|
49437
49469
|
|
|
49470
|
+
// src/autonomous.ts
|
|
49471
|
+
import fs3 from "node:fs";
|
|
49472
|
+
import np2 from "node:path";
|
|
49473
|
+
import os3 from "node:os";
|
|
49474
|
+
var DEFAULTS2 = {
|
|
49475
|
+
enabled: false,
|
|
49476
|
+
brain: "claude",
|
|
49477
|
+
workdir: process.cwd(),
|
|
49478
|
+
replyMode: "mentions",
|
|
49479
|
+
persona: "",
|
|
49480
|
+
replyToBots: false
|
|
49481
|
+
};
|
|
49482
|
+
var DIR = np2.join(os3.homedir(), ".jefri");
|
|
49483
|
+
var FILE = np2.join(DIR, "autonomous.json");
|
|
49484
|
+
function loadCfg() {
|
|
49485
|
+
try {
|
|
49486
|
+
return { ...DEFAULTS2, ...JSON.parse(fs3.readFileSync(FILE, "utf8")) };
|
|
49487
|
+
} catch {
|
|
49488
|
+
return { ...DEFAULTS2 };
|
|
49489
|
+
}
|
|
49490
|
+
}
|
|
49491
|
+
var cfg = loadCfg();
|
|
49492
|
+
function setAuto(patch) {
|
|
49493
|
+
cfg = { ...cfg, ...patch };
|
|
49494
|
+
try {
|
|
49495
|
+
fs3.mkdirSync(DIR, { recursive: true });
|
|
49496
|
+
fs3.writeFileSync(FILE, JSON.stringify(cfg, null, 2));
|
|
49497
|
+
} catch {
|
|
49498
|
+
}
|
|
49499
|
+
return { ...cfg };
|
|
49500
|
+
}
|
|
49501
|
+
var TIMEOUT_MS = 5 * 60 * 1e3;
|
|
49502
|
+
|
|
49438
49503
|
// src/tools.ts
|
|
49439
49504
|
var MIME = {
|
|
49440
49505
|
".jpg": "image/jpeg",
|
|
@@ -49455,21 +49520,21 @@ var MIME = {
|
|
|
49455
49520
|
".json": "application/json",
|
|
49456
49521
|
".zip": "application/zip"
|
|
49457
49522
|
};
|
|
49458
|
-
var mimeOf = (name) => MIME[
|
|
49523
|
+
var mimeOf = (name) => MIME[np3.extname(name).toLowerCase()] ?? "application/octet-stream";
|
|
49459
49524
|
function screenshotDir() {
|
|
49460
49525
|
try {
|
|
49461
49526
|
const out = cp2.execFileSync("defaults", ["read", "com.apple.screencapture", "location"], { encoding: "utf8", timeout: 2e3 }).trim();
|
|
49462
|
-
if (out) return out.startsWith("~") ?
|
|
49527
|
+
if (out) return out.startsWith("~") ? np3.join(os4.homedir(), out.slice(1)) : out;
|
|
49463
49528
|
} catch {
|
|
49464
49529
|
}
|
|
49465
|
-
const desktop =
|
|
49466
|
-
return
|
|
49530
|
+
const desktop = np3.join(os4.homedir(), "Desktop");
|
|
49531
|
+
return fs4.existsSync(desktop) ? desktop : null;
|
|
49467
49532
|
}
|
|
49468
49533
|
function resolveLocalFile(rawPath) {
|
|
49469
|
-
const expand = (p) => p.startsWith("~") ?
|
|
49534
|
+
const expand = (p) => p.startsWith("~") ? np3.join(os4.homedir(), p.slice(1)) : p;
|
|
49470
49535
|
const exists = (p) => {
|
|
49471
49536
|
try {
|
|
49472
|
-
return
|
|
49537
|
+
return fs4.existsSync(p) ? p : null;
|
|
49473
49538
|
} catch {
|
|
49474
49539
|
return null;
|
|
49475
49540
|
}
|
|
@@ -49482,26 +49547,26 @@ function resolveLocalFile(rawPath) {
|
|
|
49482
49547
|
hit = exists(v);
|
|
49483
49548
|
if (hit) return hit;
|
|
49484
49549
|
}
|
|
49485
|
-
const base =
|
|
49550
|
+
const base = np3.basename(abs);
|
|
49486
49551
|
const looksLikeShot = /screencaptureui/i.test(abs) || /^Screenshot[\s ]/i.test(base);
|
|
49487
49552
|
if (looksLikeShot) {
|
|
49488
49553
|
const dir = screenshotDir();
|
|
49489
49554
|
if (dir) {
|
|
49490
49555
|
let files = [];
|
|
49491
49556
|
try {
|
|
49492
|
-
files =
|
|
49557
|
+
files = fs4.readdirSync(dir);
|
|
49493
49558
|
} catch {
|
|
49494
49559
|
}
|
|
49495
49560
|
const norm = (s) => s.replace(/[ \s]+/g, " ").trim().toLowerCase();
|
|
49496
49561
|
const want = norm(base);
|
|
49497
49562
|
for (const f of files) if (norm(f) === want) {
|
|
49498
|
-
hit = exists(
|
|
49563
|
+
hit = exists(np3.join(dir, f));
|
|
49499
49564
|
if (hit) return hit;
|
|
49500
49565
|
}
|
|
49501
49566
|
const shots = files.filter((f) => /^Screenshot[\s ].*\.(png|jpe?g)$/i.test(f)).map((f) => {
|
|
49502
|
-
const p =
|
|
49567
|
+
const p = np3.join(dir, f);
|
|
49503
49568
|
try {
|
|
49504
|
-
return { p, m:
|
|
49569
|
+
return { p, m: fs4.statSync(p).mtimeMs };
|
|
49505
49570
|
} catch {
|
|
49506
49571
|
return { p, m: 0 };
|
|
49507
49572
|
}
|
|
@@ -49661,8 +49726,8 @@ ${fp}`);
|
|
|
49661
49726
|
`I can't find a file at ${path2} on this machine.`
|
|
49662
49727
|
);
|
|
49663
49728
|
}
|
|
49664
|
-
const buf =
|
|
49665
|
-
name =
|
|
49729
|
+
const buf = fs4.readFileSync(abs);
|
|
49730
|
+
name = np3.basename(abs);
|
|
49666
49731
|
mime = mimeOf(name);
|
|
49667
49732
|
url = `data:${mime};base64,${buf.toString("base64")}`;
|
|
49668
49733
|
} else if (dataUrl && fileName) {
|
|
@@ -49722,8 +49787,8 @@ ${fp}`);
|
|
|
49722
49787
|
`I can't find a file at ${path2} on this machine.`
|
|
49723
49788
|
);
|
|
49724
49789
|
}
|
|
49725
|
-
const buf =
|
|
49726
|
-
name =
|
|
49790
|
+
const buf = fs4.readFileSync(abs);
|
|
49791
|
+
name = np3.basename(abs);
|
|
49727
49792
|
mime = mimeOf(name);
|
|
49728
49793
|
url = `data:${mime};base64,${buf.toString("base64")}`;
|
|
49729
49794
|
} else if (dataUrl && fileName) {
|
|
@@ -49755,7 +49820,7 @@ ${fp}`);
|
|
|
49755
49820
|
let name, mime, url;
|
|
49756
49821
|
if (path2) {
|
|
49757
49822
|
if (!ctx.local) {
|
|
49758
|
-
const fname =
|
|
49823
|
+
const fname = np3.basename(path2);
|
|
49759
49824
|
const fmime = mimeOf(fname);
|
|
49760
49825
|
const hub = ctx.serverUrl.replace(/\/$/, "");
|
|
49761
49826
|
const web = hub.replace("acp-hub.", "acp-web.").replace(":4000", ":4321");
|
|
@@ -49780,8 +49845,8 @@ ${psCmd}
|
|
|
49780
49845
|
if (!abs) {
|
|
49781
49846
|
return fail(`I can't find a file at ${path2} on this machine.`);
|
|
49782
49847
|
}
|
|
49783
|
-
const buf =
|
|
49784
|
-
name =
|
|
49848
|
+
const buf = fs4.readFileSync(abs);
|
|
49849
|
+
name = np3.basename(abs);
|
|
49785
49850
|
mime = mimeOf(name);
|
|
49786
49851
|
url = `data:${mime};base64,${buf.toString("base64")}`;
|
|
49787
49852
|
} else if (dataUrl && fileName) {
|
|
@@ -49862,14 +49927,14 @@ ${psCmd}
|
|
|
49862
49927
|
`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.`
|
|
49863
49928
|
);
|
|
49864
49929
|
const rawDir = path2 && path2.trim() ? path2 : process.cwd();
|
|
49865
|
-
const dir = rawDir.startsWith("~") ?
|
|
49866
|
-
if (!
|
|
49930
|
+
const dir = rawDir.startsWith("~") ? np3.join(os4.homedir(), rawDir.slice(1)) : rawDir;
|
|
49931
|
+
if (!fs4.existsSync(dir))
|
|
49867
49932
|
return fail(
|
|
49868
49933
|
`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.`
|
|
49869
49934
|
);
|
|
49870
49935
|
let stat;
|
|
49871
49936
|
try {
|
|
49872
|
-
stat =
|
|
49937
|
+
stat = fs4.statSync(dir);
|
|
49873
49938
|
} catch {
|
|
49874
49939
|
return fail(`Cannot read "${rawDir}".`);
|
|
49875
49940
|
}
|
|
@@ -49880,8 +49945,8 @@ ${psCmd}
|
|
|
49880
49945
|
} catch {
|
|
49881
49946
|
return fail(`The 'zip' command isn't available on this machine, and I need it to package the folder.`);
|
|
49882
49947
|
}
|
|
49883
|
-
const base =
|
|
49884
|
-
const tmpZip =
|
|
49948
|
+
const base = np3.basename(np3.resolve(dir)) || "project";
|
|
49949
|
+
const tmpZip = np3.join(os4.tmpdir(), `acp-${base}-${Date.now()}.zip`);
|
|
49885
49950
|
let isRepo = false;
|
|
49886
49951
|
try {
|
|
49887
49952
|
cp2.execFileSync("git", ["-C", dir, "rev-parse", "--is-inside-work-tree"], { stdio: "ignore" });
|
|
@@ -49903,16 +49968,16 @@ ${psCmd}
|
|
|
49903
49968
|
}
|
|
49904
49969
|
} catch (e) {
|
|
49905
49970
|
try {
|
|
49906
|
-
|
|
49971
|
+
fs4.rmSync(tmpZip, { force: true });
|
|
49907
49972
|
} catch {
|
|
49908
49973
|
}
|
|
49909
49974
|
return fail(`Could not zip "${rawDir}": ${e?.message ?? e}`);
|
|
49910
49975
|
}
|
|
49911
|
-
if (!
|
|
49976
|
+
if (!fs4.existsSync(tmpZip))
|
|
49912
49977
|
return fail(`Nothing to send \u2014 "${rawDir}" produced an empty archive (all files ignored or folder empty).`);
|
|
49913
|
-
const size =
|
|
49978
|
+
const size = fs4.statSync(tmpZip).size;
|
|
49914
49979
|
if (size > MAX_UPLOAD_BYTES) {
|
|
49915
|
-
|
|
49980
|
+
fs4.rmSync(tmpZip, { force: true });
|
|
49916
49981
|
return fail(
|
|
49917
49982
|
`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.`
|
|
49918
49983
|
);
|
|
@@ -49924,7 +49989,7 @@ ${psCmd}
|
|
|
49924
49989
|
const res = await fetch(`${hub}/api/files?${q}`, {
|
|
49925
49990
|
method: "POST",
|
|
49926
49991
|
headers: { Authorization: `Bearer ${c.token}`, "Content-Type": "application/zip" },
|
|
49927
|
-
body:
|
|
49992
|
+
body: fs4.readFileSync(tmpZip)
|
|
49928
49993
|
});
|
|
49929
49994
|
if (!res.ok) {
|
|
49930
49995
|
const t = await res.text().catch(() => "");
|
|
@@ -49934,7 +49999,7 @@ ${psCmd}
|
|
|
49934
49999
|
return fail(`Upload failed: ${e?.message ?? e}`);
|
|
49935
50000
|
} finally {
|
|
49936
50001
|
try {
|
|
49937
|
-
|
|
50002
|
+
fs4.rmSync(tmpZip, { force: true });
|
|
49938
50003
|
} catch {
|
|
49939
50004
|
}
|
|
49940
50005
|
}
|
|
@@ -49969,12 +50034,12 @@ ${psCmd}
|
|
|
49969
50034
|
const hub = ctx.serverUrl.replace(/\/$/, "");
|
|
49970
50035
|
const out = savePath || match.fileName || "downloaded.file";
|
|
49971
50036
|
if (ctx.local) {
|
|
49972
|
-
const outPath = out.startsWith("~") ?
|
|
50037
|
+
const outPath = out.startsWith("~") ? np3.join(os4.homedir(), out.slice(1)) : out;
|
|
49973
50038
|
try {
|
|
49974
50039
|
const r = await fetch(`${hub}/api/files/${match.id}`, { headers: { authorization: `Bearer ${c.token}` } });
|
|
49975
50040
|
if (!r.ok) throw new Error(`download failed (${r.status})`);
|
|
49976
50041
|
const buf = Buffer.from(await r.arrayBuffer());
|
|
49977
|
-
|
|
50042
|
+
fs4.writeFileSync(outPath, buf);
|
|
49978
50043
|
return ok(`\u2705 Saved \u{1F4CE} ${match.fileName} from ${where} to ${outPath} (${MB(buf.length)} MB).`);
|
|
49979
50044
|
} catch (e) {
|
|
49980
50045
|
return fail(`Couldn't save the file: ${e?.message ?? e}`);
|
|
@@ -50075,7 +50140,7 @@ ${cmd}`);
|
|
|
50075
50140
|
}
|
|
50076
50141
|
},
|
|
50077
50142
|
async ({ departmentId, path: path2, kind }) => withClient(async (c) => {
|
|
50078
|
-
const fname =
|
|
50143
|
+
const fname = np3.basename(path2.startsWith("~") ? path2.slice(1) : path2);
|
|
50079
50144
|
const mime = mimeOf(fname);
|
|
50080
50145
|
const q = `name=${encodeURIComponent(fname)}&kind=${kind ?? "file"}`;
|
|
50081
50146
|
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)}`;
|
|
@@ -50159,7 +50224,7 @@ ${cmd}`);
|
|
|
50159
50224
|
inputSchema: { path: external_exports.string().describe("local file path to upload") }
|
|
50160
50225
|
},
|
|
50161
50226
|
async ({ path: path2 }) => withClient(async (c) => {
|
|
50162
|
-
const fname =
|
|
50227
|
+
const fname = np3.basename(path2.startsWith("~") ? path2.slice(1) : path2);
|
|
50163
50228
|
const mime = mimeOf(fname);
|
|
50164
50229
|
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)}`;
|
|
50165
50230
|
return ok(`To add ${fname} to your document memory, run in the terminal:
|
|
@@ -50311,7 +50376,7 @@ ${cmd}`);
|
|
|
50311
50376
|
if (path2) {
|
|
50312
50377
|
if (!ctx.local) {
|
|
50313
50378
|
const hub = ctx.serverUrl.replace(/\/$/, "");
|
|
50314
|
-
const fname =
|
|
50379
|
+
const fname = np3.basename(path2);
|
|
50315
50380
|
const fmime = mimeOf(fname);
|
|
50316
50381
|
const q = `groupId=${encodeURIComponent(groupId)}&fileName=${encodeURIComponent(fname)}` + (caption ? `&caption=${encodeURIComponent(caption)}` : "");
|
|
50317
50382
|
const cmd = `curl -s -X POST ${shq(`${hub}/api/files?${q}`)} -H "Authorization: Bearer ${c.token}" -H "Content-Type: ${fmime}" --data-binary @${shq(path2)}`;
|
|
@@ -50323,8 +50388,8 @@ ${cmd}`
|
|
|
50323
50388
|
}
|
|
50324
50389
|
const abs = resolveLocalFile(path2);
|
|
50325
50390
|
if (!abs) return fail(`I can't find a file at ${path2} on this machine.`);
|
|
50326
|
-
const buf =
|
|
50327
|
-
name =
|
|
50391
|
+
const buf = fs4.readFileSync(abs);
|
|
50392
|
+
name = np3.basename(abs);
|
|
50328
50393
|
mime = mimeOf(name);
|
|
50329
50394
|
url = `data:${mime};base64,${buf.toString("base64")}`;
|
|
50330
50395
|
} else if (dataUrl && fileName) {
|
|
@@ -50532,6 +50597,44 @@ ${cmd}`
|
|
|
50532
50597
|
return ok(summary);
|
|
50533
50598
|
})
|
|
50534
50599
|
);
|
|
50600
|
+
server.registerTool(
|
|
50601
|
+
"jefri_autonomous",
|
|
50602
|
+
{
|
|
50603
|
+
title: "Autonomous mode",
|
|
50604
|
+
description: "Turn autonomous replies on/off for THIS agent. When ON, each incoming message is handled by a 'brain' (claude / codex / a custom command) that does the work in a folder and replies on its own \u2014 so you can, e.g., leave it on and it builds what people ask and answers them. Only works for your own agent (this connector's token). Call with NO arguments to see current settings. \u26A0\uFE0F When on, remote messages drive a tool-enabled agent on your machine \u2014 only keep it on with people you trust, and scope the work folder.",
|
|
50605
|
+
inputSchema: {
|
|
50606
|
+
enabled: external_exports.boolean().optional().describe("turn autonomous replies on/off"),
|
|
50607
|
+
brain: external_exports.string().optional().describe("'claude', 'codex', or a full custom command (e.g. for OpenClaw)"),
|
|
50608
|
+
workdir: external_exports.string().optional().describe("folder the brain works in (scope it!)"),
|
|
50609
|
+
replyMode: external_exports.enum(["mentions", "dms", "all"]).optional().describe("groups: mentions=only when @-mentioned, all=every message, dms=DMs only"),
|
|
50610
|
+
persona: external_exports.string().optional().describe("role/instructions for the agent"),
|
|
50611
|
+
replyToBots: external_exports.boolean().optional().describe("also auto-reply to other agents (default off \u2014 prevents bot loops)")
|
|
50612
|
+
}
|
|
50613
|
+
},
|
|
50614
|
+
async (args) => withClient(async () => {
|
|
50615
|
+
const patch = {};
|
|
50616
|
+
if (typeof args.enabled === "boolean") patch.enabled = args.enabled;
|
|
50617
|
+
if (typeof args.replyToBots === "boolean") patch.replyToBots = args.replyToBots;
|
|
50618
|
+
if (typeof args.brain === "string" && args.brain.trim()) patch.brain = args.brain.trim();
|
|
50619
|
+
if (typeof args.workdir === "string" && args.workdir.trim()) patch.workdir = args.workdir.trim();
|
|
50620
|
+
if (typeof args.persona === "string") patch.persona = args.persona;
|
|
50621
|
+
if (args.replyMode) patch.replyMode = args.replyMode;
|
|
50622
|
+
const c = setAuto(patch);
|
|
50623
|
+
const modeLabel = c.replyMode === "all" ? "every message" : c.replyMode === "dms" ? "DMs only" : "DMs + group @mentions";
|
|
50624
|
+
const lines = [
|
|
50625
|
+
`\u{1F916} Autonomous mode: ${c.enabled ? "ON" : "OFF"}`,
|
|
50626
|
+
` Brain: ${c.brain}`,
|
|
50627
|
+
` Work folder: ${c.workdir}`,
|
|
50628
|
+
` Replies to: ${modeLabel}`,
|
|
50629
|
+
` Persona: ${c.persona ? c.persona.slice(0, 80) : "(none)"}`,
|
|
50630
|
+
` Reply to other bots: ${c.replyToBots ? "yes" : "no"}`
|
|
50631
|
+
];
|
|
50632
|
+
if (c.enabled)
|
|
50633
|
+
lines.push(`
|
|
50634
|
+
\u26A0\uFE0F Incoming messages now drive "${c.brain}" (with tool access) in ${c.workdir}. Keep this on only with people you trust.`);
|
|
50635
|
+
return ok(lines.join("\n"));
|
|
50636
|
+
})
|
|
50637
|
+
);
|
|
50535
50638
|
}
|
|
50536
50639
|
}
|
|
50537
50640
|
function attachInbox(c, inbox, self, onIncoming) {
|
package/dist/index.js
CHANGED
|
@@ -6883,12 +6883,12 @@ var require_dist = __commonJS({
|
|
|
6883
6883
|
throw new Error(`Unknown format "${name}"`);
|
|
6884
6884
|
return f;
|
|
6885
6885
|
};
|
|
6886
|
-
function addFormats(ajv, list,
|
|
6886
|
+
function addFormats(ajv, list, fs6, exportName) {
|
|
6887
6887
|
var _a;
|
|
6888
6888
|
var _b;
|
|
6889
6889
|
(_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
6890
6890
|
for (const f of list)
|
|
6891
|
-
ajv.addFormat(f,
|
|
6891
|
+
ajv.addFormat(f, fs6[f]);
|
|
6892
6892
|
}
|
|
6893
6893
|
module.exports = exports = formatsPlugin;
|
|
6894
6894
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -24788,8 +24788,8 @@ var StdioServerTransport = class {
|
|
|
24788
24788
|
};
|
|
24789
24789
|
|
|
24790
24790
|
// src/index.ts
|
|
24791
|
-
import
|
|
24792
|
-
import
|
|
24791
|
+
import fs5 from "node:fs";
|
|
24792
|
+
import os5 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
|
|
@@ -24846,6 +24846,8 @@ var AcpClient = class _AcpClient {
|
|
|
24846
24846
|
closing = false;
|
|
24847
24847
|
authFailed = false;
|
|
24848
24848
|
// a bad token must NOT trigger a reconnect storm
|
|
24849
|
+
isAlive = true;
|
|
24850
|
+
// heartbeat: did we get a pong since the last ping?
|
|
24849
24851
|
constructor(server2) {
|
|
24850
24852
|
this.server = server2.replace(/\/$/, "");
|
|
24851
24853
|
}
|
|
@@ -24946,15 +24948,45 @@ var AcpClient = class _AcpClient {
|
|
|
24946
24948
|
});
|
|
24947
24949
|
});
|
|
24948
24950
|
}
|
|
24949
|
-
//
|
|
24951
|
+
// Heartbeat: keep the connection warm through idle proxies AND detect a
|
|
24952
|
+
// half-open (silently dead) socket. A dropped Wi-Fi / VPN / sleep can leave
|
|
24953
|
+
// the socket readyState=OPEN forever while no data flows, so `close` never
|
|
24954
|
+
// fires and we'd never reconnect. So each tick we send a WS-level ping (the
|
|
24955
|
+
// server's ws auto-pongs) and require a pong to have arrived since the last
|
|
24956
|
+
// tick — if not, the link is dead, so terminate() it, which fires `close` and
|
|
24957
|
+
// triggers the existing reconnect.
|
|
24950
24958
|
startPing() {
|
|
24951
24959
|
this.stopPing();
|
|
24960
|
+
this.isAlive = true;
|
|
24961
|
+
try {
|
|
24962
|
+
this.ws.on("pong", () => {
|
|
24963
|
+
this.isAlive = true;
|
|
24964
|
+
});
|
|
24965
|
+
} catch {
|
|
24966
|
+
}
|
|
24952
24967
|
this.pingTimer = setInterval(() => {
|
|
24968
|
+
if (this.ws.readyState !== this.ws.OPEN) return;
|
|
24969
|
+
if (!this.isAlive) {
|
|
24970
|
+
try {
|
|
24971
|
+
this.ws.terminate();
|
|
24972
|
+
} catch {
|
|
24973
|
+
try {
|
|
24974
|
+
this.ws.close();
|
|
24975
|
+
} catch {
|
|
24976
|
+
}
|
|
24977
|
+
}
|
|
24978
|
+
return;
|
|
24979
|
+
}
|
|
24980
|
+
this.isAlive = false;
|
|
24953
24981
|
try {
|
|
24954
|
-
|
|
24982
|
+
this.ws.ping();
|
|
24955
24983
|
} catch {
|
|
24956
24984
|
}
|
|
24957
|
-
|
|
24985
|
+
try {
|
|
24986
|
+
this.ws.send(JSON.stringify({ type: "ping" }));
|
|
24987
|
+
} catch {
|
|
24988
|
+
}
|
|
24989
|
+
}, 15e3);
|
|
24958
24990
|
}
|
|
24959
24991
|
stopPing() {
|
|
24960
24992
|
if (this.pingTimer) clearInterval(this.pingTimer);
|
|
@@ -25281,10 +25313,10 @@ var AcpClient = class _AcpClient {
|
|
|
25281
25313
|
};
|
|
25282
25314
|
|
|
25283
25315
|
// src/tools.ts
|
|
25284
|
-
import
|
|
25285
|
-
import
|
|
25286
|
-
import
|
|
25287
|
-
import
|
|
25316
|
+
import fs4 from "node:fs";
|
|
25317
|
+
import np3 from "node:path";
|
|
25318
|
+
import cp3 from "node:child_process";
|
|
25319
|
+
import os4 from "node:os";
|
|
25288
25320
|
|
|
25289
25321
|
// src/notify.ts
|
|
25290
25322
|
import cp from "node:child_process";
|
|
@@ -25394,6 +25426,124 @@ function notifyIncoming(m) {
|
|
|
25394
25426
|
}
|
|
25395
25427
|
}
|
|
25396
25428
|
|
|
25429
|
+
// src/autonomous.ts
|
|
25430
|
+
import cp2 from "node:child_process";
|
|
25431
|
+
import fs3 from "node:fs";
|
|
25432
|
+
import np2 from "node:path";
|
|
25433
|
+
import os3 from "node:os";
|
|
25434
|
+
var DEFAULTS2 = {
|
|
25435
|
+
enabled: false,
|
|
25436
|
+
brain: "claude",
|
|
25437
|
+
workdir: process.cwd(),
|
|
25438
|
+
replyMode: "mentions",
|
|
25439
|
+
persona: "",
|
|
25440
|
+
replyToBots: false
|
|
25441
|
+
};
|
|
25442
|
+
var DIR = np2.join(os3.homedir(), ".jefri");
|
|
25443
|
+
var FILE = np2.join(DIR, "autonomous.json");
|
|
25444
|
+
function loadCfg() {
|
|
25445
|
+
try {
|
|
25446
|
+
return { ...DEFAULTS2, ...JSON.parse(fs3.readFileSync(FILE, "utf8")) };
|
|
25447
|
+
} catch {
|
|
25448
|
+
return { ...DEFAULTS2 };
|
|
25449
|
+
}
|
|
25450
|
+
}
|
|
25451
|
+
var cfg = loadCfg();
|
|
25452
|
+
function getAuto() {
|
|
25453
|
+
return { ...cfg };
|
|
25454
|
+
}
|
|
25455
|
+
function setAuto(patch) {
|
|
25456
|
+
cfg = { ...cfg, ...patch };
|
|
25457
|
+
try {
|
|
25458
|
+
fs3.mkdirSync(DIR, { recursive: true });
|
|
25459
|
+
fs3.writeFileSync(FILE, JSON.stringify(cfg, null, 2));
|
|
25460
|
+
} catch {
|
|
25461
|
+
}
|
|
25462
|
+
return { ...cfg };
|
|
25463
|
+
}
|
|
25464
|
+
var expand = (p) => p.startsWith("~") ? np2.join(os3.homedir(), p.slice(1)) : p;
|
|
25465
|
+
function brainArgv(brain, prompt) {
|
|
25466
|
+
if (brain === "claude") return ["claude", ["-p", "--permission-mode", "acceptEdits", prompt]];
|
|
25467
|
+
if (brain === "codex") return ["codex", ["exec", "--full-auto", prompt]];
|
|
25468
|
+
const parts = brain.trim().split(/\s+/);
|
|
25469
|
+
return [parts[0], [...parts.slice(1), prompt]];
|
|
25470
|
+
}
|
|
25471
|
+
var MAX_PROMPT = 6e3;
|
|
25472
|
+
var MAX_REPLY = 7900;
|
|
25473
|
+
var TIMEOUT_MS = 5 * 60 * 1e3;
|
|
25474
|
+
function shouldHandle(m) {
|
|
25475
|
+
if (!cfg.enabled) return false;
|
|
25476
|
+
if (m.senderIsBot && !cfg.replyToBots) return false;
|
|
25477
|
+
if (!m.isGroup) return true;
|
|
25478
|
+
if (cfg.replyMode === "all") return true;
|
|
25479
|
+
if (cfg.replyMode === "mentions") return m.isMention;
|
|
25480
|
+
return false;
|
|
25481
|
+
}
|
|
25482
|
+
var running = false;
|
|
25483
|
+
var queue = [];
|
|
25484
|
+
async function drain() {
|
|
25485
|
+
if (running) return;
|
|
25486
|
+
running = true;
|
|
25487
|
+
while (queue.length) {
|
|
25488
|
+
const job = queue.shift();
|
|
25489
|
+
try {
|
|
25490
|
+
await job();
|
|
25491
|
+
} catch {
|
|
25492
|
+
}
|
|
25493
|
+
}
|
|
25494
|
+
running = false;
|
|
25495
|
+
}
|
|
25496
|
+
function runBrain(cmd, args, cwd) {
|
|
25497
|
+
return new Promise((resolve, reject) => {
|
|
25498
|
+
let child;
|
|
25499
|
+
try {
|
|
25500
|
+
child = cp2.spawn(cmd, args, { cwd: expand(cwd), stdio: ["ignore", "pipe", "pipe"] });
|
|
25501
|
+
} catch (e) {
|
|
25502
|
+
return reject(e);
|
|
25503
|
+
}
|
|
25504
|
+
let out = "", err = "";
|
|
25505
|
+
const timer = setTimeout(() => {
|
|
25506
|
+
try {
|
|
25507
|
+
child.kill();
|
|
25508
|
+
} catch {
|
|
25509
|
+
}
|
|
25510
|
+
reject(new Error("timed out"));
|
|
25511
|
+
}, TIMEOUT_MS);
|
|
25512
|
+
child.stdout?.on("data", (d) => out += d.toString());
|
|
25513
|
+
child.stderr?.on("data", (d) => err += d.toString());
|
|
25514
|
+
child.on("error", (e) => {
|
|
25515
|
+
clearTimeout(timer);
|
|
25516
|
+
reject(e);
|
|
25517
|
+
});
|
|
25518
|
+
child.on("close", (code) => {
|
|
25519
|
+
clearTimeout(timer);
|
|
25520
|
+
if (code === 0 || out.trim()) resolve(out);
|
|
25521
|
+
else reject(new Error(err.trim().slice(0, 200) || `exit ${code}`));
|
|
25522
|
+
});
|
|
25523
|
+
});
|
|
25524
|
+
}
|
|
25525
|
+
function handleAutonomous(m, reply, log2) {
|
|
25526
|
+
if (!shouldHandle(m)) return;
|
|
25527
|
+
queue.push(async () => {
|
|
25528
|
+
const persona = cfg.persona ? cfg.persona.trim() + "\n\n" : "";
|
|
25529
|
+
const where = m.isGroup ? " in a group" : "";
|
|
25530
|
+
const prompt = `${persona}You are an autonomous agent on Jefri Chat. @${m.sender} sent you a message${where}. Do what it asks (you can use your tools/files) and then give a concise reply to send back.
|
|
25531
|
+
|
|
25532
|
+
Message from @${m.sender}: ${m.content}`.slice(0, MAX_PROMPT);
|
|
25533
|
+
const [cmd, args] = brainArgv(cfg.brain, prompt);
|
|
25534
|
+
log2(`autonomous: @${m.sender} \u2192 running "${cmd}"\u2026`);
|
|
25535
|
+
try {
|
|
25536
|
+
const out = await runBrain(cmd, args, cfg.workdir);
|
|
25537
|
+
reply((out.trim() || "(done)").slice(0, MAX_REPLY));
|
|
25538
|
+
log2(`autonomous: replied to @${m.sender}`);
|
|
25539
|
+
} catch (e) {
|
|
25540
|
+
reply(`(couldn't finish autonomously: ${e?.message ?? e})`.slice(0, MAX_REPLY));
|
|
25541
|
+
log2(`autonomous: failed for @${m.sender}: ${e?.message ?? e}`);
|
|
25542
|
+
}
|
|
25543
|
+
});
|
|
25544
|
+
void drain();
|
|
25545
|
+
}
|
|
25546
|
+
|
|
25397
25547
|
// src/tools.ts
|
|
25398
25548
|
var MIME = {
|
|
25399
25549
|
".jpg": "image/jpeg",
|
|
@@ -25414,26 +25564,26 @@ var MIME = {
|
|
|
25414
25564
|
".json": "application/json",
|
|
25415
25565
|
".zip": "application/zip"
|
|
25416
25566
|
};
|
|
25417
|
-
var mimeOf = (name) => MIME[
|
|
25567
|
+
var mimeOf = (name) => MIME[np3.extname(name).toLowerCase()] ?? "application/octet-stream";
|
|
25418
25568
|
function screenshotDir() {
|
|
25419
25569
|
try {
|
|
25420
|
-
const out =
|
|
25421
|
-
if (out) return out.startsWith("~") ?
|
|
25570
|
+
const out = cp3.execFileSync("defaults", ["read", "com.apple.screencapture", "location"], { encoding: "utf8", timeout: 2e3 }).trim();
|
|
25571
|
+
if (out) return out.startsWith("~") ? np3.join(os4.homedir(), out.slice(1)) : out;
|
|
25422
25572
|
} catch {
|
|
25423
25573
|
}
|
|
25424
|
-
const desktop =
|
|
25425
|
-
return
|
|
25574
|
+
const desktop = np3.join(os4.homedir(), "Desktop");
|
|
25575
|
+
return fs4.existsSync(desktop) ? desktop : null;
|
|
25426
25576
|
}
|
|
25427
25577
|
function resolveLocalFile(rawPath) {
|
|
25428
|
-
const
|
|
25578
|
+
const expand2 = (p) => p.startsWith("~") ? np3.join(os4.homedir(), p.slice(1)) : p;
|
|
25429
25579
|
const exists = (p) => {
|
|
25430
25580
|
try {
|
|
25431
|
-
return
|
|
25581
|
+
return fs4.existsSync(p) ? p : null;
|
|
25432
25582
|
} catch {
|
|
25433
25583
|
return null;
|
|
25434
25584
|
}
|
|
25435
25585
|
};
|
|
25436
|
-
const abs =
|
|
25586
|
+
const abs = expand2(rawPath);
|
|
25437
25587
|
let hit = exists(abs);
|
|
25438
25588
|
if (hit) return hit;
|
|
25439
25589
|
const UNI = /[ ]/g;
|
|
@@ -25441,26 +25591,26 @@ function resolveLocalFile(rawPath) {
|
|
|
25441
25591
|
hit = exists(v);
|
|
25442
25592
|
if (hit) return hit;
|
|
25443
25593
|
}
|
|
25444
|
-
const base =
|
|
25594
|
+
const base = np3.basename(abs);
|
|
25445
25595
|
const looksLikeShot = /screencaptureui/i.test(abs) || /^Screenshot[\s ]/i.test(base);
|
|
25446
25596
|
if (looksLikeShot) {
|
|
25447
25597
|
const dir = screenshotDir();
|
|
25448
25598
|
if (dir) {
|
|
25449
25599
|
let files = [];
|
|
25450
25600
|
try {
|
|
25451
|
-
files =
|
|
25601
|
+
files = fs4.readdirSync(dir);
|
|
25452
25602
|
} catch {
|
|
25453
25603
|
}
|
|
25454
25604
|
const norm = (s) => s.replace(/[ \s]+/g, " ").trim().toLowerCase();
|
|
25455
25605
|
const want = norm(base);
|
|
25456
25606
|
for (const f of files) if (norm(f) === want) {
|
|
25457
|
-
hit = exists(
|
|
25607
|
+
hit = exists(np3.join(dir, f));
|
|
25458
25608
|
if (hit) return hit;
|
|
25459
25609
|
}
|
|
25460
25610
|
const shots = files.filter((f) => /^Screenshot[\s ].*\.(png|jpe?g)$/i.test(f)).map((f) => {
|
|
25461
|
-
const p =
|
|
25611
|
+
const p = np3.join(dir, f);
|
|
25462
25612
|
try {
|
|
25463
|
-
return { p, m:
|
|
25613
|
+
return { p, m: fs4.statSync(p).mtimeMs };
|
|
25464
25614
|
} catch {
|
|
25465
25615
|
return { p, m: 0 };
|
|
25466
25616
|
}
|
|
@@ -25620,8 +25770,8 @@ ${fp}`);
|
|
|
25620
25770
|
`I can't find a file at ${path3} on this machine.`
|
|
25621
25771
|
);
|
|
25622
25772
|
}
|
|
25623
|
-
const buf =
|
|
25624
|
-
name =
|
|
25773
|
+
const buf = fs4.readFileSync(abs);
|
|
25774
|
+
name = np3.basename(abs);
|
|
25625
25775
|
mime = mimeOf(name);
|
|
25626
25776
|
url = `data:${mime};base64,${buf.toString("base64")}`;
|
|
25627
25777
|
} else if (dataUrl && fileName) {
|
|
@@ -25681,8 +25831,8 @@ ${fp}`);
|
|
|
25681
25831
|
`I can't find a file at ${path3} on this machine.`
|
|
25682
25832
|
);
|
|
25683
25833
|
}
|
|
25684
|
-
const buf =
|
|
25685
|
-
name =
|
|
25834
|
+
const buf = fs4.readFileSync(abs);
|
|
25835
|
+
name = np3.basename(abs);
|
|
25686
25836
|
mime = mimeOf(name);
|
|
25687
25837
|
url = `data:${mime};base64,${buf.toString("base64")}`;
|
|
25688
25838
|
} else if (dataUrl && fileName) {
|
|
@@ -25714,7 +25864,7 @@ ${fp}`);
|
|
|
25714
25864
|
let name, mime, url;
|
|
25715
25865
|
if (path3) {
|
|
25716
25866
|
if (!ctx.local) {
|
|
25717
|
-
const fname =
|
|
25867
|
+
const fname = np3.basename(path3);
|
|
25718
25868
|
const fmime = mimeOf(fname);
|
|
25719
25869
|
const hub = ctx.serverUrl.replace(/\/$/, "");
|
|
25720
25870
|
const web = hub.replace("acp-hub.", "acp-web.").replace(":4000", ":4321");
|
|
@@ -25739,8 +25889,8 @@ ${psCmd}
|
|
|
25739
25889
|
if (!abs) {
|
|
25740
25890
|
return fail(`I can't find a file at ${path3} on this machine.`);
|
|
25741
25891
|
}
|
|
25742
|
-
const buf =
|
|
25743
|
-
name =
|
|
25892
|
+
const buf = fs4.readFileSync(abs);
|
|
25893
|
+
name = np3.basename(abs);
|
|
25744
25894
|
mime = mimeOf(name);
|
|
25745
25895
|
url = `data:${mime};base64,${buf.toString("base64")}`;
|
|
25746
25896
|
} else if (dataUrl && fileName) {
|
|
@@ -25821,57 +25971,57 @@ ${psCmd}
|
|
|
25821
25971
|
`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.`
|
|
25822
25972
|
);
|
|
25823
25973
|
const rawDir = path3 && path3.trim() ? path3 : process.cwd();
|
|
25824
|
-
const dir = rawDir.startsWith("~") ?
|
|
25825
|
-
if (!
|
|
25974
|
+
const dir = rawDir.startsWith("~") ? np3.join(os4.homedir(), rawDir.slice(1)) : rawDir;
|
|
25975
|
+
if (!fs4.existsSync(dir))
|
|
25826
25976
|
return fail(
|
|
25827
25977
|
`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.`
|
|
25828
25978
|
);
|
|
25829
25979
|
let stat;
|
|
25830
25980
|
try {
|
|
25831
|
-
stat =
|
|
25981
|
+
stat = fs4.statSync(dir);
|
|
25832
25982
|
} catch {
|
|
25833
25983
|
return fail(`Cannot read "${rawDir}".`);
|
|
25834
25984
|
}
|
|
25835
25985
|
if (!stat.isDirectory())
|
|
25836
25986
|
return fail(`"${rawDir}" is a file, not a folder \u2014 use jefri_send_file for a single file.`);
|
|
25837
25987
|
try {
|
|
25838
|
-
|
|
25988
|
+
cp3.execFileSync("zip", ["-v"], { stdio: "ignore" });
|
|
25839
25989
|
} catch {
|
|
25840
25990
|
return fail(`The 'zip' command isn't available on this machine, and I need it to package the folder.`);
|
|
25841
25991
|
}
|
|
25842
|
-
const base =
|
|
25843
|
-
const tmpZip =
|
|
25992
|
+
const base = np3.basename(np3.resolve(dir)) || "project";
|
|
25993
|
+
const tmpZip = np3.join(os4.tmpdir(), `acp-${base}-${Date.now()}.zip`);
|
|
25844
25994
|
let isRepo = false;
|
|
25845
25995
|
try {
|
|
25846
|
-
|
|
25996
|
+
cp3.execFileSync("git", ["-C", dir, "rev-parse", "--is-inside-work-tree"], { stdio: "ignore" });
|
|
25847
25997
|
isRepo = true;
|
|
25848
25998
|
} catch {
|
|
25849
25999
|
isRepo = false;
|
|
25850
26000
|
}
|
|
25851
26001
|
try {
|
|
25852
26002
|
if (isRepo) {
|
|
25853
|
-
|
|
26003
|
+
cp3.execSync(
|
|
25854
26004
|
`cd ${shq(dir)} && git ls-files --cached --others --exclude-standard -z | xargs -0 zip -q ${shq(tmpZip)} --`,
|
|
25855
26005
|
{ stdio: "ignore" }
|
|
25856
26006
|
);
|
|
25857
26007
|
} else {
|
|
25858
|
-
|
|
26008
|
+
cp3.execSync(
|
|
25859
26009
|
`cd ${shq(dir)} && zip -qr ${shq(tmpZip)} . -x '*/node_modules/*' '*/.git/*' '*/dist/*' '*/.next/*' '*/build/*' '*/__pycache__/*' '*/.venv/*' '*/venv/*' '*.log' '*/.DS_Store'`,
|
|
25860
26010
|
{ stdio: "ignore" }
|
|
25861
26011
|
);
|
|
25862
26012
|
}
|
|
25863
26013
|
} catch (e) {
|
|
25864
26014
|
try {
|
|
25865
|
-
|
|
26015
|
+
fs4.rmSync(tmpZip, { force: true });
|
|
25866
26016
|
} catch {
|
|
25867
26017
|
}
|
|
25868
26018
|
return fail(`Could not zip "${rawDir}": ${e?.message ?? e}`);
|
|
25869
26019
|
}
|
|
25870
|
-
if (!
|
|
26020
|
+
if (!fs4.existsSync(tmpZip))
|
|
25871
26021
|
return fail(`Nothing to send \u2014 "${rawDir}" produced an empty archive (all files ignored or folder empty).`);
|
|
25872
|
-
const size =
|
|
26022
|
+
const size = fs4.statSync(tmpZip).size;
|
|
25873
26023
|
if (size > MAX_UPLOAD_BYTES) {
|
|
25874
|
-
|
|
26024
|
+
fs4.rmSync(tmpZip, { force: true });
|
|
25875
26025
|
return fail(
|
|
25876
26026
|
`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.`
|
|
25877
26027
|
);
|
|
@@ -25883,7 +26033,7 @@ ${psCmd}
|
|
|
25883
26033
|
const res = await fetch(`${hub}/api/files?${q}`, {
|
|
25884
26034
|
method: "POST",
|
|
25885
26035
|
headers: { Authorization: `Bearer ${c.token}`, "Content-Type": "application/zip" },
|
|
25886
|
-
body:
|
|
26036
|
+
body: fs4.readFileSync(tmpZip)
|
|
25887
26037
|
});
|
|
25888
26038
|
if (!res.ok) {
|
|
25889
26039
|
const t = await res.text().catch(() => "");
|
|
@@ -25893,7 +26043,7 @@ ${psCmd}
|
|
|
25893
26043
|
return fail(`Upload failed: ${e?.message ?? e}`);
|
|
25894
26044
|
} finally {
|
|
25895
26045
|
try {
|
|
25896
|
-
|
|
26046
|
+
fs4.rmSync(tmpZip, { force: true });
|
|
25897
26047
|
} catch {
|
|
25898
26048
|
}
|
|
25899
26049
|
}
|
|
@@ -25928,12 +26078,12 @@ ${psCmd}
|
|
|
25928
26078
|
const hub = ctx.serverUrl.replace(/\/$/, "");
|
|
25929
26079
|
const out = savePath || match.fileName || "downloaded.file";
|
|
25930
26080
|
if (ctx.local) {
|
|
25931
|
-
const outPath = out.startsWith("~") ?
|
|
26081
|
+
const outPath = out.startsWith("~") ? np3.join(os4.homedir(), out.slice(1)) : out;
|
|
25932
26082
|
try {
|
|
25933
26083
|
const r = await fetch(`${hub}/api/files/${match.id}`, { headers: { authorization: `Bearer ${c.token}` } });
|
|
25934
26084
|
if (!r.ok) throw new Error(`download failed (${r.status})`);
|
|
25935
26085
|
const buf = Buffer.from(await r.arrayBuffer());
|
|
25936
|
-
|
|
26086
|
+
fs4.writeFileSync(outPath, buf);
|
|
25937
26087
|
return ok(`\u2705 Saved \u{1F4CE} ${match.fileName} from ${where} to ${outPath} (${MB(buf.length)} MB).`);
|
|
25938
26088
|
} catch (e) {
|
|
25939
26089
|
return fail(`Couldn't save the file: ${e?.message ?? e}`);
|
|
@@ -26034,7 +26184,7 @@ ${cmd}`);
|
|
|
26034
26184
|
}
|
|
26035
26185
|
},
|
|
26036
26186
|
async ({ departmentId, path: path3, kind }) => withClient(async (c) => {
|
|
26037
|
-
const fname =
|
|
26187
|
+
const fname = np3.basename(path3.startsWith("~") ? path3.slice(1) : path3);
|
|
26038
26188
|
const mime = mimeOf(fname);
|
|
26039
26189
|
const q = `name=${encodeURIComponent(fname)}&kind=${kind ?? "file"}`;
|
|
26040
26190
|
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)}`;
|
|
@@ -26118,7 +26268,7 @@ ${cmd}`);
|
|
|
26118
26268
|
inputSchema: { path: external_exports.string().describe("local file path to upload") }
|
|
26119
26269
|
},
|
|
26120
26270
|
async ({ path: path3 }) => withClient(async (c) => {
|
|
26121
|
-
const fname =
|
|
26271
|
+
const fname = np3.basename(path3.startsWith("~") ? path3.slice(1) : path3);
|
|
26122
26272
|
const mime = mimeOf(fname);
|
|
26123
26273
|
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)}`;
|
|
26124
26274
|
return ok(`To add ${fname} to your document memory, run in the terminal:
|
|
@@ -26270,7 +26420,7 @@ ${cmd}`);
|
|
|
26270
26420
|
if (path3) {
|
|
26271
26421
|
if (!ctx.local) {
|
|
26272
26422
|
const hub = ctx.serverUrl.replace(/\/$/, "");
|
|
26273
|
-
const fname =
|
|
26423
|
+
const fname = np3.basename(path3);
|
|
26274
26424
|
const fmime = mimeOf(fname);
|
|
26275
26425
|
const q = `groupId=${encodeURIComponent(groupId)}&fileName=${encodeURIComponent(fname)}` + (caption ? `&caption=${encodeURIComponent(caption)}` : "");
|
|
26276
26426
|
const cmd = `curl -s -X POST ${shq(`${hub}/api/files?${q}`)} -H "Authorization: Bearer ${c.token}" -H "Content-Type: ${fmime}" --data-binary @${shq(path3)}`;
|
|
@@ -26282,8 +26432,8 @@ ${cmd}`
|
|
|
26282
26432
|
}
|
|
26283
26433
|
const abs = resolveLocalFile(path3);
|
|
26284
26434
|
if (!abs) return fail(`I can't find a file at ${path3} on this machine.`);
|
|
26285
|
-
const buf =
|
|
26286
|
-
name =
|
|
26435
|
+
const buf = fs4.readFileSync(abs);
|
|
26436
|
+
name = np3.basename(abs);
|
|
26287
26437
|
mime = mimeOf(name);
|
|
26288
26438
|
url = `data:${mime};base64,${buf.toString("base64")}`;
|
|
26289
26439
|
} else if (dataUrl && fileName) {
|
|
@@ -26491,6 +26641,44 @@ ${cmd}`
|
|
|
26491
26641
|
return ok(summary);
|
|
26492
26642
|
})
|
|
26493
26643
|
);
|
|
26644
|
+
server2.registerTool(
|
|
26645
|
+
"jefri_autonomous",
|
|
26646
|
+
{
|
|
26647
|
+
title: "Autonomous mode",
|
|
26648
|
+
description: "Turn autonomous replies on/off for THIS agent. When ON, each incoming message is handled by a 'brain' (claude / codex / a custom command) that does the work in a folder and replies on its own \u2014 so you can, e.g., leave it on and it builds what people ask and answers them. Only works for your own agent (this connector's token). Call with NO arguments to see current settings. \u26A0\uFE0F When on, remote messages drive a tool-enabled agent on your machine \u2014 only keep it on with people you trust, and scope the work folder.",
|
|
26649
|
+
inputSchema: {
|
|
26650
|
+
enabled: external_exports.boolean().optional().describe("turn autonomous replies on/off"),
|
|
26651
|
+
brain: external_exports.string().optional().describe("'claude', 'codex', or a full custom command (e.g. for OpenClaw)"),
|
|
26652
|
+
workdir: external_exports.string().optional().describe("folder the brain works in (scope it!)"),
|
|
26653
|
+
replyMode: external_exports.enum(["mentions", "dms", "all"]).optional().describe("groups: mentions=only when @-mentioned, all=every message, dms=DMs only"),
|
|
26654
|
+
persona: external_exports.string().optional().describe("role/instructions for the agent"),
|
|
26655
|
+
replyToBots: external_exports.boolean().optional().describe("also auto-reply to other agents (default off \u2014 prevents bot loops)")
|
|
26656
|
+
}
|
|
26657
|
+
},
|
|
26658
|
+
async (args) => withClient(async () => {
|
|
26659
|
+
const patch = {};
|
|
26660
|
+
if (typeof args.enabled === "boolean") patch.enabled = args.enabled;
|
|
26661
|
+
if (typeof args.replyToBots === "boolean") patch.replyToBots = args.replyToBots;
|
|
26662
|
+
if (typeof args.brain === "string" && args.brain.trim()) patch.brain = args.brain.trim();
|
|
26663
|
+
if (typeof args.workdir === "string" && args.workdir.trim()) patch.workdir = args.workdir.trim();
|
|
26664
|
+
if (typeof args.persona === "string") patch.persona = args.persona;
|
|
26665
|
+
if (args.replyMode) patch.replyMode = args.replyMode;
|
|
26666
|
+
const c = setAuto(patch);
|
|
26667
|
+
const modeLabel = c.replyMode === "all" ? "every message" : c.replyMode === "dms" ? "DMs only" : "DMs + group @mentions";
|
|
26668
|
+
const lines = [
|
|
26669
|
+
`\u{1F916} Autonomous mode: ${c.enabled ? "ON" : "OFF"}`,
|
|
26670
|
+
` Brain: ${c.brain}`,
|
|
26671
|
+
` Work folder: ${c.workdir}`,
|
|
26672
|
+
` Replies to: ${modeLabel}`,
|
|
26673
|
+
` Persona: ${c.persona ? c.persona.slice(0, 80) : "(none)"}`,
|
|
26674
|
+
` Reply to other bots: ${c.replyToBots ? "yes" : "no"}`
|
|
26675
|
+
];
|
|
26676
|
+
if (c.enabled)
|
|
26677
|
+
lines.push(`
|
|
26678
|
+
\u26A0\uFE0F Incoming messages now drive "${c.brain}" (with tool access) in ${c.workdir}. Keep this on only with people you trust.`);
|
|
26679
|
+
return ok(lines.join("\n"));
|
|
26680
|
+
})
|
|
26681
|
+
);
|
|
26494
26682
|
}
|
|
26495
26683
|
}
|
|
26496
26684
|
function attachInbox(c, inbox2, self, onIncoming) {
|
|
@@ -26516,11 +26704,11 @@ var NAME = process.env.ACP_NAME ?? USERNAME;
|
|
|
26516
26704
|
var TAGS = (process.env.ACP_TAGS ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
26517
26705
|
var CAPS = (process.env.ACP_CAPABILITIES ?? "coding").split(",").map((s) => s.trim()).filter(Boolean);
|
|
26518
26706
|
var log = (...a) => console.error("[jefrichat-mcp]", ...a);
|
|
26519
|
-
var TOKEN_CACHE = path2.join(
|
|
26707
|
+
var TOKEN_CACHE = path2.join(os5.homedir(), ".acp", "tokens.json");
|
|
26520
26708
|
var cacheKey = `${SERVER}::${USERNAME}`;
|
|
26521
26709
|
function readTokenCache() {
|
|
26522
26710
|
try {
|
|
26523
|
-
return JSON.parse(
|
|
26711
|
+
return JSON.parse(fs5.readFileSync(TOKEN_CACHE, "utf8"));
|
|
26524
26712
|
} catch {
|
|
26525
26713
|
return {};
|
|
26526
26714
|
}
|
|
@@ -26528,8 +26716,8 @@ function readTokenCache() {
|
|
|
26528
26716
|
function cacheToken(tok) {
|
|
26529
26717
|
const c = readTokenCache();
|
|
26530
26718
|
c[cacheKey] = tok;
|
|
26531
|
-
|
|
26532
|
-
|
|
26719
|
+
fs5.mkdirSync(path2.dirname(TOKEN_CACHE), { recursive: true });
|
|
26720
|
+
fs5.writeFileSync(TOKEN_CACHE, JSON.stringify(c, null, 2));
|
|
26533
26721
|
}
|
|
26534
26722
|
var clientPromise = null;
|
|
26535
26723
|
var inbox = [];
|
|
@@ -26569,8 +26757,18 @@ function ensureClient() {
|
|
|
26569
26757
|
});
|
|
26570
26758
|
void refreshGroups();
|
|
26571
26759
|
setInterval(refreshGroups, 6e4).unref?.();
|
|
26760
|
+
const agentUsers = /* @__PURE__ */ new Set();
|
|
26761
|
+
const refreshAgents = () => c.identities().then((ids) => {
|
|
26762
|
+
agentUsers.clear();
|
|
26763
|
+
for (const i of ids) if (i.type === "agent") agentUsers.add(i.username);
|
|
26764
|
+
}).catch(() => {
|
|
26765
|
+
});
|
|
26766
|
+
void refreshAgents();
|
|
26767
|
+
setInterval(refreshAgents, 6e4).unref?.();
|
|
26572
26768
|
if (macNeedsTerminalNotifier)
|
|
26573
26769
|
log("tip: for reliable desktop notifications on macOS, run: brew install terminal-notifier");
|
|
26770
|
+
if (getAuto().enabled)
|
|
26771
|
+
log(`autonomous mode is ON \u2014 incoming messages will be handled by "${getAuto().brain}" in ${getAuto().workdir}`);
|
|
26574
26772
|
attachInbox(c, inbox, self, (m) => {
|
|
26575
26773
|
const preview = m.kind === "file" ? `\u{1F4CE} ${m.fileName ?? "file"}` : m.content ?? "";
|
|
26576
26774
|
const isGroup = !!m.groupId;
|
|
@@ -26583,6 +26781,23 @@ function ensureClient() {
|
|
|
26583
26781
|
sender: m.senderUsername,
|
|
26584
26782
|
isMention
|
|
26585
26783
|
});
|
|
26784
|
+
if (m.kind === "text" && typeof m.content === "string") {
|
|
26785
|
+
handleAutonomous(
|
|
26786
|
+
{
|
|
26787
|
+
content: m.content,
|
|
26788
|
+
sender: m.senderUsername,
|
|
26789
|
+
isGroup,
|
|
26790
|
+
groupId: m.groupId,
|
|
26791
|
+
isMention,
|
|
26792
|
+
senderIsBot: agentUsers.has(m.senderUsername)
|
|
26793
|
+
},
|
|
26794
|
+
(text) => {
|
|
26795
|
+
if (isGroup && m.groupId) c.groupMessage(m.groupId, text);
|
|
26796
|
+
else c.message(m.senderUsername, text);
|
|
26797
|
+
},
|
|
26798
|
+
log
|
|
26799
|
+
);
|
|
26800
|
+
}
|
|
26586
26801
|
});
|
|
26587
26802
|
log(`connected to ${SERVER} as ${self}`);
|
|
26588
26803
|
return c;
|
package/package.json
CHANGED