jefrichat-mcp 0.8.0 → 0.12.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 +174 -55
- package/dist/index.js +578 -152
- 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,44 @@ 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 DEFAULT_TEMPLATE = '{persona}You are @{me}, an autonomous agent on Jefri Chat. A message came in{where}. It may address several agents by @name (e.g. "@alice: do X @bob: do Y") \u2014 do ONLY the part addressed to you (@{me}); ignore parts meant for other agents. If nothing is addressed to you, reply with nothing to do. You can do ANYTHING (research, writing, analysis, code, running tools) \u2014 not just code. If a CLAUDE.md or relevant files are in this folder, read them first to get oriented. If the request is unclear, reply with ONE short clarifying question and stop (only your owner will answer). Otherwise do your part and reply concisely with the result.\n\n{context}Latest message from @{sender}: {message}';
|
|
49475
|
+
var DEFAULTS2 = {
|
|
49476
|
+
enabled: false,
|
|
49477
|
+
brain: "claude",
|
|
49478
|
+
workdir: process.cwd(),
|
|
49479
|
+
replyMode: "mentions",
|
|
49480
|
+
persona: "",
|
|
49481
|
+
replyToBots: false,
|
|
49482
|
+
ownerOnly: false,
|
|
49483
|
+
contextMessages: 12,
|
|
49484
|
+
promptTemplate: DEFAULT_TEMPLATE
|
|
49485
|
+
};
|
|
49486
|
+
var DIR = np2.join(os3.homedir(), ".jefri");
|
|
49487
|
+
var FILE = np2.join(DIR, "autonomous.json");
|
|
49488
|
+
var LOG_FILE = np2.join(DIR, "autonomous.log");
|
|
49489
|
+
function loadCfg() {
|
|
49490
|
+
try {
|
|
49491
|
+
return { ...DEFAULTS2, ...JSON.parse(fs3.readFileSync(FILE, "utf8")) };
|
|
49492
|
+
} catch {
|
|
49493
|
+
return { ...DEFAULTS2 };
|
|
49494
|
+
}
|
|
49495
|
+
}
|
|
49496
|
+
var cfg = loadCfg();
|
|
49497
|
+
function setAuto(patch) {
|
|
49498
|
+
cfg = { ...cfg, ...patch };
|
|
49499
|
+
try {
|
|
49500
|
+
fs3.mkdirSync(DIR, { recursive: true });
|
|
49501
|
+
fs3.writeFileSync(FILE, JSON.stringify(cfg, null, 2));
|
|
49502
|
+
} catch {
|
|
49503
|
+
}
|
|
49504
|
+
return { ...cfg };
|
|
49505
|
+
}
|
|
49506
|
+
var TIMEOUT_MS = 5 * 60 * 1e3;
|
|
49507
|
+
|
|
49438
49508
|
// src/tools.ts
|
|
49439
49509
|
var MIME = {
|
|
49440
49510
|
".jpg": "image/jpeg",
|
|
@@ -49455,21 +49525,21 @@ var MIME = {
|
|
|
49455
49525
|
".json": "application/json",
|
|
49456
49526
|
".zip": "application/zip"
|
|
49457
49527
|
};
|
|
49458
|
-
var mimeOf = (name) => MIME[
|
|
49528
|
+
var mimeOf = (name) => MIME[np3.extname(name).toLowerCase()] ?? "application/octet-stream";
|
|
49459
49529
|
function screenshotDir() {
|
|
49460
49530
|
try {
|
|
49461
49531
|
const out = cp2.execFileSync("defaults", ["read", "com.apple.screencapture", "location"], { encoding: "utf8", timeout: 2e3 }).trim();
|
|
49462
|
-
if (out) return out.startsWith("~") ?
|
|
49532
|
+
if (out) return out.startsWith("~") ? np3.join(os4.homedir(), out.slice(1)) : out;
|
|
49463
49533
|
} catch {
|
|
49464
49534
|
}
|
|
49465
|
-
const desktop =
|
|
49466
|
-
return
|
|
49535
|
+
const desktop = np3.join(os4.homedir(), "Desktop");
|
|
49536
|
+
return fs4.existsSync(desktop) ? desktop : null;
|
|
49467
49537
|
}
|
|
49468
49538
|
function resolveLocalFile(rawPath) {
|
|
49469
|
-
const expand = (p) => p.startsWith("~") ?
|
|
49539
|
+
const expand = (p) => p.startsWith("~") ? np3.join(os4.homedir(), p.slice(1)) : p;
|
|
49470
49540
|
const exists = (p) => {
|
|
49471
49541
|
try {
|
|
49472
|
-
return
|
|
49542
|
+
return fs4.existsSync(p) ? p : null;
|
|
49473
49543
|
} catch {
|
|
49474
49544
|
return null;
|
|
49475
49545
|
}
|
|
@@ -49482,26 +49552,26 @@ function resolveLocalFile(rawPath) {
|
|
|
49482
49552
|
hit = exists(v);
|
|
49483
49553
|
if (hit) return hit;
|
|
49484
49554
|
}
|
|
49485
|
-
const base =
|
|
49555
|
+
const base = np3.basename(abs);
|
|
49486
49556
|
const looksLikeShot = /screencaptureui/i.test(abs) || /^Screenshot[\s ]/i.test(base);
|
|
49487
49557
|
if (looksLikeShot) {
|
|
49488
49558
|
const dir = screenshotDir();
|
|
49489
49559
|
if (dir) {
|
|
49490
49560
|
let files = [];
|
|
49491
49561
|
try {
|
|
49492
|
-
files =
|
|
49562
|
+
files = fs4.readdirSync(dir);
|
|
49493
49563
|
} catch {
|
|
49494
49564
|
}
|
|
49495
49565
|
const norm = (s) => s.replace(/[ \s]+/g, " ").trim().toLowerCase();
|
|
49496
49566
|
const want = norm(base);
|
|
49497
49567
|
for (const f of files) if (norm(f) === want) {
|
|
49498
|
-
hit = exists(
|
|
49568
|
+
hit = exists(np3.join(dir, f));
|
|
49499
49569
|
if (hit) return hit;
|
|
49500
49570
|
}
|
|
49501
49571
|
const shots = files.filter((f) => /^Screenshot[\s ].*\.(png|jpe?g)$/i.test(f)).map((f) => {
|
|
49502
|
-
const p =
|
|
49572
|
+
const p = np3.join(dir, f);
|
|
49503
49573
|
try {
|
|
49504
|
-
return { p, m:
|
|
49574
|
+
return { p, m: fs4.statSync(p).mtimeMs };
|
|
49505
49575
|
} catch {
|
|
49506
49576
|
return { p, m: 0 };
|
|
49507
49577
|
}
|
|
@@ -49661,8 +49731,8 @@ ${fp}`);
|
|
|
49661
49731
|
`I can't find a file at ${path2} on this machine.`
|
|
49662
49732
|
);
|
|
49663
49733
|
}
|
|
49664
|
-
const buf =
|
|
49665
|
-
name =
|
|
49734
|
+
const buf = fs4.readFileSync(abs);
|
|
49735
|
+
name = np3.basename(abs);
|
|
49666
49736
|
mime = mimeOf(name);
|
|
49667
49737
|
url = `data:${mime};base64,${buf.toString("base64")}`;
|
|
49668
49738
|
} else if (dataUrl && fileName) {
|
|
@@ -49722,8 +49792,8 @@ ${fp}`);
|
|
|
49722
49792
|
`I can't find a file at ${path2} on this machine.`
|
|
49723
49793
|
);
|
|
49724
49794
|
}
|
|
49725
|
-
const buf =
|
|
49726
|
-
name =
|
|
49795
|
+
const buf = fs4.readFileSync(abs);
|
|
49796
|
+
name = np3.basename(abs);
|
|
49727
49797
|
mime = mimeOf(name);
|
|
49728
49798
|
url = `data:${mime};base64,${buf.toString("base64")}`;
|
|
49729
49799
|
} else if (dataUrl && fileName) {
|
|
@@ -49755,7 +49825,7 @@ ${fp}`);
|
|
|
49755
49825
|
let name, mime, url;
|
|
49756
49826
|
if (path2) {
|
|
49757
49827
|
if (!ctx.local) {
|
|
49758
|
-
const fname =
|
|
49828
|
+
const fname = np3.basename(path2);
|
|
49759
49829
|
const fmime = mimeOf(fname);
|
|
49760
49830
|
const hub = ctx.serverUrl.replace(/\/$/, "");
|
|
49761
49831
|
const web = hub.replace("acp-hub.", "acp-web.").replace(":4000", ":4321");
|
|
@@ -49780,8 +49850,8 @@ ${psCmd}
|
|
|
49780
49850
|
if (!abs) {
|
|
49781
49851
|
return fail(`I can't find a file at ${path2} on this machine.`);
|
|
49782
49852
|
}
|
|
49783
|
-
const buf =
|
|
49784
|
-
name =
|
|
49853
|
+
const buf = fs4.readFileSync(abs);
|
|
49854
|
+
name = np3.basename(abs);
|
|
49785
49855
|
mime = mimeOf(name);
|
|
49786
49856
|
url = `data:${mime};base64,${buf.toString("base64")}`;
|
|
49787
49857
|
} else if (dataUrl && fileName) {
|
|
@@ -49862,14 +49932,14 @@ ${psCmd}
|
|
|
49862
49932
|
`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
49933
|
);
|
|
49864
49934
|
const rawDir = path2 && path2.trim() ? path2 : process.cwd();
|
|
49865
|
-
const dir = rawDir.startsWith("~") ?
|
|
49866
|
-
if (!
|
|
49935
|
+
const dir = rawDir.startsWith("~") ? np3.join(os4.homedir(), rawDir.slice(1)) : rawDir;
|
|
49936
|
+
if (!fs4.existsSync(dir))
|
|
49867
49937
|
return fail(
|
|
49868
49938
|
`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
49939
|
);
|
|
49870
49940
|
let stat;
|
|
49871
49941
|
try {
|
|
49872
|
-
stat =
|
|
49942
|
+
stat = fs4.statSync(dir);
|
|
49873
49943
|
} catch {
|
|
49874
49944
|
return fail(`Cannot read "${rawDir}".`);
|
|
49875
49945
|
}
|
|
@@ -49880,8 +49950,8 @@ ${psCmd}
|
|
|
49880
49950
|
} catch {
|
|
49881
49951
|
return fail(`The 'zip' command isn't available on this machine, and I need it to package the folder.`);
|
|
49882
49952
|
}
|
|
49883
|
-
const base =
|
|
49884
|
-
const tmpZip =
|
|
49953
|
+
const base = np3.basename(np3.resolve(dir)) || "project";
|
|
49954
|
+
const tmpZip = np3.join(os4.tmpdir(), `acp-${base}-${Date.now()}.zip`);
|
|
49885
49955
|
let isRepo = false;
|
|
49886
49956
|
try {
|
|
49887
49957
|
cp2.execFileSync("git", ["-C", dir, "rev-parse", "--is-inside-work-tree"], { stdio: "ignore" });
|
|
@@ -49903,16 +49973,16 @@ ${psCmd}
|
|
|
49903
49973
|
}
|
|
49904
49974
|
} catch (e) {
|
|
49905
49975
|
try {
|
|
49906
|
-
|
|
49976
|
+
fs4.rmSync(tmpZip, { force: true });
|
|
49907
49977
|
} catch {
|
|
49908
49978
|
}
|
|
49909
49979
|
return fail(`Could not zip "${rawDir}": ${e?.message ?? e}`);
|
|
49910
49980
|
}
|
|
49911
|
-
if (!
|
|
49981
|
+
if (!fs4.existsSync(tmpZip))
|
|
49912
49982
|
return fail(`Nothing to send \u2014 "${rawDir}" produced an empty archive (all files ignored or folder empty).`);
|
|
49913
|
-
const size =
|
|
49983
|
+
const size = fs4.statSync(tmpZip).size;
|
|
49914
49984
|
if (size > MAX_UPLOAD_BYTES) {
|
|
49915
|
-
|
|
49985
|
+
fs4.rmSync(tmpZip, { force: true });
|
|
49916
49986
|
return fail(
|
|
49917
49987
|
`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
49988
|
);
|
|
@@ -49924,7 +49994,7 @@ ${psCmd}
|
|
|
49924
49994
|
const res = await fetch(`${hub}/api/files?${q}`, {
|
|
49925
49995
|
method: "POST",
|
|
49926
49996
|
headers: { Authorization: `Bearer ${c.token}`, "Content-Type": "application/zip" },
|
|
49927
|
-
body:
|
|
49997
|
+
body: fs4.readFileSync(tmpZip)
|
|
49928
49998
|
});
|
|
49929
49999
|
if (!res.ok) {
|
|
49930
50000
|
const t = await res.text().catch(() => "");
|
|
@@ -49934,7 +50004,7 @@ ${psCmd}
|
|
|
49934
50004
|
return fail(`Upload failed: ${e?.message ?? e}`);
|
|
49935
50005
|
} finally {
|
|
49936
50006
|
try {
|
|
49937
|
-
|
|
50007
|
+
fs4.rmSync(tmpZip, { force: true });
|
|
49938
50008
|
} catch {
|
|
49939
50009
|
}
|
|
49940
50010
|
}
|
|
@@ -49969,12 +50039,12 @@ ${psCmd}
|
|
|
49969
50039
|
const hub = ctx.serverUrl.replace(/\/$/, "");
|
|
49970
50040
|
const out = savePath || match.fileName || "downloaded.file";
|
|
49971
50041
|
if (ctx.local) {
|
|
49972
|
-
const outPath = out.startsWith("~") ?
|
|
50042
|
+
const outPath = out.startsWith("~") ? np3.join(os4.homedir(), out.slice(1)) : out;
|
|
49973
50043
|
try {
|
|
49974
50044
|
const r = await fetch(`${hub}/api/files/${match.id}`, { headers: { authorization: `Bearer ${c.token}` } });
|
|
49975
50045
|
if (!r.ok) throw new Error(`download failed (${r.status})`);
|
|
49976
50046
|
const buf = Buffer.from(await r.arrayBuffer());
|
|
49977
|
-
|
|
50047
|
+
fs4.writeFileSync(outPath, buf);
|
|
49978
50048
|
return ok(`\u2705 Saved \u{1F4CE} ${match.fileName} from ${where} to ${outPath} (${MB(buf.length)} MB).`);
|
|
49979
50049
|
} catch (e) {
|
|
49980
50050
|
return fail(`Couldn't save the file: ${e?.message ?? e}`);
|
|
@@ -50075,7 +50145,7 @@ ${cmd}`);
|
|
|
50075
50145
|
}
|
|
50076
50146
|
},
|
|
50077
50147
|
async ({ departmentId, path: path2, kind }) => withClient(async (c) => {
|
|
50078
|
-
const fname =
|
|
50148
|
+
const fname = np3.basename(path2.startsWith("~") ? path2.slice(1) : path2);
|
|
50079
50149
|
const mime = mimeOf(fname);
|
|
50080
50150
|
const q = `name=${encodeURIComponent(fname)}&kind=${kind ?? "file"}`;
|
|
50081
50151
|
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 +50229,7 @@ ${cmd}`);
|
|
|
50159
50229
|
inputSchema: { path: external_exports.string().describe("local file path to upload") }
|
|
50160
50230
|
},
|
|
50161
50231
|
async ({ path: path2 }) => withClient(async (c) => {
|
|
50162
|
-
const fname =
|
|
50232
|
+
const fname = np3.basename(path2.startsWith("~") ? path2.slice(1) : path2);
|
|
50163
50233
|
const mime = mimeOf(fname);
|
|
50164
50234
|
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
50235
|
return ok(`To add ${fname} to your document memory, run in the terminal:
|
|
@@ -50311,7 +50381,7 @@ ${cmd}`);
|
|
|
50311
50381
|
if (path2) {
|
|
50312
50382
|
if (!ctx.local) {
|
|
50313
50383
|
const hub = ctx.serverUrl.replace(/\/$/, "");
|
|
50314
|
-
const fname =
|
|
50384
|
+
const fname = np3.basename(path2);
|
|
50315
50385
|
const fmime = mimeOf(fname);
|
|
50316
50386
|
const q = `groupId=${encodeURIComponent(groupId)}&fileName=${encodeURIComponent(fname)}` + (caption ? `&caption=${encodeURIComponent(caption)}` : "");
|
|
50317
50387
|
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 +50393,8 @@ ${cmd}`
|
|
|
50323
50393
|
}
|
|
50324
50394
|
const abs = resolveLocalFile(path2);
|
|
50325
50395
|
if (!abs) return fail(`I can't find a file at ${path2} on this machine.`);
|
|
50326
|
-
const buf =
|
|
50327
|
-
name =
|
|
50396
|
+
const buf = fs4.readFileSync(abs);
|
|
50397
|
+
name = np3.basename(abs);
|
|
50328
50398
|
mime = mimeOf(name);
|
|
50329
50399
|
url = `data:${mime};base64,${buf.toString("base64")}`;
|
|
50330
50400
|
} else if (dataUrl && fileName) {
|
|
@@ -50532,6 +50602,55 @@ ${cmd}`
|
|
|
50532
50602
|
return ok(summary);
|
|
50533
50603
|
})
|
|
50534
50604
|
);
|
|
50605
|
+
server.registerTool(
|
|
50606
|
+
"jefri_autonomous",
|
|
50607
|
+
{
|
|
50608
|
+
title: "Autonomous mode",
|
|
50609
|
+
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.",
|
|
50610
|
+
inputSchema: {
|
|
50611
|
+
enabled: external_exports.boolean().optional().describe("turn autonomous replies on/off"),
|
|
50612
|
+
brain: external_exports.string().optional().describe("'claude', 'codex', or a full custom command (e.g. for OpenClaw)"),
|
|
50613
|
+
workdir: external_exports.string().optional().describe("folder the brain works in (scope it!)"),
|
|
50614
|
+
replyMode: external_exports.enum(["mentions", "dms", "all"]).optional().describe("groups: mentions=only when @-mentioned, all=every message, dms=DMs only"),
|
|
50615
|
+
persona: external_exports.string().optional().describe("role/instructions for the agent (injected as {persona})"),
|
|
50616
|
+
promptTemplate: external_exports.string().optional().describe("advanced: fully customize how the message is framed to the brain. Placeholders: {persona} {me} {sender} {where} {context} {message}. Pass 'default' to reset."),
|
|
50617
|
+
ownerOnly: external_exports.boolean().optional().describe("only act on messages from your OWNER \u2014 ignore other people (great for a private agent team). Default off."),
|
|
50618
|
+
contextMessages: external_exports.number().optional().describe("how many recent messages of the conversation to give the brain for memory (0 = stateless, default 12)"),
|
|
50619
|
+
replyToBots: external_exports.boolean().optional().describe("also auto-reply to other agents (default off \u2014 prevents bot loops)")
|
|
50620
|
+
}
|
|
50621
|
+
},
|
|
50622
|
+
async (args) => withClient(async () => {
|
|
50623
|
+
const patch = {};
|
|
50624
|
+
if (typeof args.enabled === "boolean") patch.enabled = args.enabled;
|
|
50625
|
+
if (typeof args.replyToBots === "boolean") patch.replyToBots = args.replyToBots;
|
|
50626
|
+
if (typeof args.ownerOnly === "boolean") patch.ownerOnly = args.ownerOnly;
|
|
50627
|
+
if (typeof args.contextMessages === "number") patch.contextMessages = Math.max(0, Math.min(40, Math.floor(args.contextMessages)));
|
|
50628
|
+
if (typeof args.brain === "string" && args.brain.trim()) patch.brain = args.brain.trim();
|
|
50629
|
+
if (typeof args.workdir === "string" && args.workdir.trim()) patch.workdir = args.workdir.trim();
|
|
50630
|
+
if (typeof args.persona === "string") patch.persona = args.persona;
|
|
50631
|
+
if (typeof args.promptTemplate === "string" && args.promptTemplate.trim())
|
|
50632
|
+
patch.promptTemplate = args.promptTemplate.trim().toLowerCase() === "default" ? DEFAULT_TEMPLATE : args.promptTemplate;
|
|
50633
|
+
if (args.replyMode) patch.replyMode = args.replyMode;
|
|
50634
|
+
const c = setAuto(patch);
|
|
50635
|
+
const modeLabel = c.replyMode === "all" ? "every message" : c.replyMode === "dms" ? "DMs only" : "DMs + group @mentions";
|
|
50636
|
+
const lines = [
|
|
50637
|
+
`\u{1F916} Autonomous mode: ${c.enabled ? "ON" : "OFF"}`,
|
|
50638
|
+
` Brain: ${c.brain}`,
|
|
50639
|
+
` Work folder: ${c.workdir}`,
|
|
50640
|
+
` Replies to: ${modeLabel}`,
|
|
50641
|
+
` Owner-only: ${c.ownerOnly ? "yes (only you direct me)" : "no (anyone who @mentions me)"}`,
|
|
50642
|
+
` Persona: ${c.persona ? c.persona.slice(0, 80) : "(none)"}`,
|
|
50643
|
+
` Conversation memory: ${c.contextMessages > 0 ? `last ${c.contextMessages} messages` : "off"}`,
|
|
50644
|
+
` Prompt: ${c.promptTemplate === DEFAULT_TEMPLATE ? "default (@name routing + ask-if-unclear)" : "custom"}`,
|
|
50645
|
+
` Reply to other bots: ${c.replyToBots ? "yes" : "no"}`,
|
|
50646
|
+
` Activity log: ~/.jefri/autonomous.log (watch it: tail -f ~/.jefri/autonomous.log)`
|
|
50647
|
+
];
|
|
50648
|
+
if (c.enabled)
|
|
50649
|
+
lines.push(`
|
|
50650
|
+
\u26A0\uFE0F Incoming messages now drive "${c.brain}" (with tool access) in ${c.workdir}. Keep this on only with people you trust.`);
|
|
50651
|
+
return ok(lines.join("\n"));
|
|
50652
|
+
})
|
|
50653
|
+
);
|
|
50535
50654
|
}
|
|
50536
50655
|
}
|
|
50537
50656
|
function attachInbox(c, inbox, self, onIncoming) {
|