@interactive-inc/claude-funnel 0.21.1 → 0.22.1
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/bin.js +436 -436
- package/dist/connectors/discord.d.ts +1 -1
- package/dist/connectors/discord.js +1 -1
- package/dist/connectors/gh.d.ts +2 -2
- package/dist/connectors/gh.js +1 -1
- package/dist/connectors/schedule.d.ts +1 -1
- package/dist/connectors/schedule.js +1 -1
- package/dist/connectors/slack.d.ts +1 -1
- package/dist/connectors/slack.js +1 -1
- package/dist/{discord-connector-schema-ygf5Df-2.js → discord-connector-schema-CR8RJ08_.js} +1 -1
- package/dist/gateway/daemon.js +180 -180
- package/dist/{gh-connector-schema-CD5HIkrd.js → gh-connector-schema-CAC24s0r.js} +148 -4
- package/dist/{gh-connector-schema-BNyTaASt.d.ts → gh-connector-schema-Cmi57jvL.d.ts} +16 -2
- package/dist/index.d.ts +41 -23
- package/dist/index.js +122 -106
- package/dist/{logger-CTlXs7z4.d.ts → logger-B3aXsVcX.d.ts} +1 -1
- package/dist/{node-logger-DQz_BGOD.js → node-logger-B97ZiGwj.js} +17 -4
- package/dist/{schedule-connector-schema-FxP7LPlx.js → schedule-connector-schema-BZpH6ZmR.js} +1 -1
- package/dist/{schedule-listener-BPodvbld.d.ts → schedule-listener-CBYF2bGZ.d.ts} +1 -1
- package/dist/{slack-connector-schema-BM9xshol.js → slack-connector-schema-B3jr-RTH.js} +1 -1
- package/dist/{slack-listener-CHj6uMY-.d.ts → slack-listener-tQH7cXU7.d.ts} +1 -1
- package/package.json +4 -1
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { i as FunnelDiscordAdapter, n as FunnelDiscordListener, t as discordConnectorSchema } from "./discord-connector-schema-
|
|
2
|
-
import { n as
|
|
3
|
-
import { a as FunnelProcessRunner, i as NodeFunnelProcessRunner, n as FunnelGhListener, r as FunnelGhAdapter, t as ghConnectorSchema } from "./gh-connector-schema-
|
|
4
|
-
import { a as ScheduleStateStore, i as FunnelScheduleListener, n as scheduleConnectorSchema, o as NodeFunnelFileSystem, r as scheduleEntrySchema, s as FunnelFileSystem, t as scheduleCatchupPolicySchema } from "./schedule-connector-schema-
|
|
5
|
-
import { i as FunnelSlackAdapter, n as FunnelSlackListener, r as FunnelSlackEventProcessor, t as slackConnectorSchema } from "./slack-connector-schema-
|
|
1
|
+
import { i as FunnelDiscordAdapter, n as FunnelDiscordListener, t as discordConnectorSchema } from "./discord-connector-schema-CR8RJ08_.js";
|
|
2
|
+
import { i as FunnelConnectorListener, n as funnelTmpDir, r as FunnelLogger, t as NodeFunnelLogger } from "./node-logger-B97ZiGwj.js";
|
|
3
|
+
import { a as FunnelProcessRunner, i as NodeFunnelProcessRunner, n as FunnelGhListener, r as FunnelGhAdapter, t as ghConnectorSchema } from "./gh-connector-schema-CAC24s0r.js";
|
|
4
|
+
import { a as ScheduleStateStore, i as FunnelScheduleListener, n as scheduleConnectorSchema, o as NodeFunnelFileSystem, r as scheduleEntrySchema, s as FunnelFileSystem, t as scheduleCatchupPolicySchema } from "./schedule-connector-schema-BZpH6ZmR.js";
|
|
5
|
+
import { i as FunnelSlackAdapter, n as FunnelSlackListener, r as FunnelSlackEventProcessor, t as slackConnectorSchema } from "./slack-connector-schema-B3jr-RTH.js";
|
|
6
6
|
import { dirname, join, resolve } from "node:path";
|
|
7
7
|
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
8
|
-
import { z } from "zod";
|
|
9
8
|
import { homedir } from "node:os";
|
|
9
|
+
import { z } from "zod";
|
|
10
10
|
import { stderr, stdin } from "node:process";
|
|
11
11
|
import { fileURLToPath } from "node:url";
|
|
12
12
|
import { timingSafeEqual } from "node:crypto";
|
|
@@ -597,8 +597,8 @@ var FunnelClaude = class {
|
|
|
597
597
|
this.writePidFile(options.profileName);
|
|
598
598
|
this.installCleanup(options.profileName);
|
|
599
599
|
}
|
|
600
|
-
const
|
|
601
|
-
const claudeArgs = this.buildArgs(channel.options, options.userArgs ?? [], cwd,
|
|
600
|
+
const session = channel.resume ? this.resolveSession(channel.id, cwd, options.userArgs ?? []) : null;
|
|
601
|
+
const claudeArgs = this.buildArgs(channel.options, options.userArgs ?? [], cwd, session);
|
|
602
602
|
const env = this.buildEnv(channel.id, channel.env);
|
|
603
603
|
this.logger.info(`claude launch`, {
|
|
604
604
|
channel: options.channel,
|
|
@@ -647,37 +647,37 @@ var FunnelClaude = class {
|
|
|
647
647
|
globalThis.process.once("exit", () => this.removePidFile(profileName));
|
|
648
648
|
}
|
|
649
649
|
isProcessAlive(pid) {
|
|
650
|
-
|
|
651
|
-
"ps",
|
|
652
|
-
"-p",
|
|
653
|
-
String(pid),
|
|
654
|
-
"-o",
|
|
655
|
-
"state="
|
|
656
|
-
]);
|
|
657
|
-
if (result.exitCode !== 0) return false;
|
|
658
|
-
const state = result.stdout.trim();
|
|
659
|
-
if (!state) return false;
|
|
660
|
-
return !state.startsWith("Z");
|
|
650
|
+
return this.process.isAlive(pid);
|
|
661
651
|
}
|
|
662
|
-
buildArgs(channelOptions, userArgs, cwd,
|
|
652
|
+
buildArgs(channelOptions, userArgs, cwd, session) {
|
|
663
653
|
const result = [...channelOptions, ...userArgs];
|
|
664
|
-
if (
|
|
654
|
+
if (session !== null) if (session.mode === "resume") result.push("--resume", session.id);
|
|
655
|
+
else result.push("--session-id", session.id);
|
|
665
656
|
const mcpName = this.mcp.findInstalledName(cwd);
|
|
666
657
|
if (mcpName && !result.includes("--dangerously-load-development-channels") && !result.includes("--channels")) result.push("--dangerously-load-development-channels", `server:${mcpName}`);
|
|
667
658
|
return result;
|
|
668
659
|
}
|
|
669
660
|
/**
|
|
670
|
-
* Decides whether funnel should
|
|
671
|
-
* the user already passed a
|
|
672
|
-
*
|
|
661
|
+
* Decides whether funnel should resume an existing claude session or start
|
|
662
|
+
* a freshly minted one. Backs off when the user already passed a
|
|
663
|
+
* session-shaping flag, since combining them would either confuse claude
|
|
664
|
+
* or override the explicit user intent.
|
|
673
665
|
*/
|
|
674
|
-
|
|
666
|
+
resolveSession(channelId, cwd, userArgs) {
|
|
675
667
|
for (const arg of userArgs) {
|
|
676
668
|
if (arg === "-c" || arg === "--continue") return null;
|
|
677
669
|
if (arg === "--resume" || arg.startsWith("--resume=")) return null;
|
|
678
670
|
if (arg === "--session-id" || arg.startsWith("--session-id=")) return null;
|
|
679
671
|
}
|
|
680
|
-
|
|
672
|
+
const existing = this.sessions.get(channelId, cwd);
|
|
673
|
+
if (existing !== null) return {
|
|
674
|
+
id: existing,
|
|
675
|
+
mode: "resume"
|
|
676
|
+
};
|
|
677
|
+
return {
|
|
678
|
+
id: this.sessions.create(channelId, cwd),
|
|
679
|
+
mode: "new"
|
|
680
|
+
};
|
|
681
681
|
}
|
|
682
682
|
buildEnv(channelId, channelEnv) {
|
|
683
683
|
const env = {};
|
|
@@ -1332,6 +1332,8 @@ var MemoryFunnelProcessRunner = class extends FunnelProcessRunner {
|
|
|
1332
1332
|
killed = [];
|
|
1333
1333
|
handler = () => empty;
|
|
1334
1334
|
syncHandler = () => empty;
|
|
1335
|
+
aliveStub = null;
|
|
1336
|
+
listStub = null;
|
|
1335
1337
|
on(handler) {
|
|
1336
1338
|
this.handler = handler;
|
|
1337
1339
|
return this;
|
|
@@ -1340,6 +1342,14 @@ var MemoryFunnelProcessRunner = class extends FunnelProcessRunner {
|
|
|
1340
1342
|
this.syncHandler = handler;
|
|
1341
1343
|
return this;
|
|
1342
1344
|
}
|
|
1345
|
+
onIsAlive(stub) {
|
|
1346
|
+
this.aliveStub = stub;
|
|
1347
|
+
return this;
|
|
1348
|
+
}
|
|
1349
|
+
onListProcessesContaining(stub) {
|
|
1350
|
+
this.listStub = stub;
|
|
1351
|
+
return this;
|
|
1352
|
+
}
|
|
1343
1353
|
async run(command, options = {}) {
|
|
1344
1354
|
this.calls.push({
|
|
1345
1355
|
kind: "run",
|
|
@@ -1391,6 +1401,24 @@ var MemoryFunnelProcessRunner = class extends FunnelProcessRunner {
|
|
|
1391
1401
|
signal
|
|
1392
1402
|
});
|
|
1393
1403
|
}
|
|
1404
|
+
isAlive(pid) {
|
|
1405
|
+
if (this.aliveStub) return this.aliveStub(pid);
|
|
1406
|
+
const result = this.syncHandler([
|
|
1407
|
+
"ps",
|
|
1408
|
+
"-p",
|
|
1409
|
+
String(pid),
|
|
1410
|
+
"-o",
|
|
1411
|
+
"state="
|
|
1412
|
+
]);
|
|
1413
|
+
if ((result.exitCode ?? 0) !== 0) return false;
|
|
1414
|
+
const state = (result.stdout ?? "").trim();
|
|
1415
|
+
if (!state) return false;
|
|
1416
|
+
return !state.startsWith("Z");
|
|
1417
|
+
}
|
|
1418
|
+
listProcessesContaining(marker) {
|
|
1419
|
+
if (this.listStub) return this.listStub(marker);
|
|
1420
|
+
return [];
|
|
1421
|
+
}
|
|
1394
1422
|
};
|
|
1395
1423
|
//#endregion
|
|
1396
1424
|
//#region lib/engine/profiles/profiles.ts
|
|
@@ -1474,10 +1502,15 @@ const sessionsMapSchema = z.record(z.string(), z.string());
|
|
|
1474
1502
|
/**
|
|
1475
1503
|
* Per-channel persistent Claude Code session IDs, keyed by the cwd the
|
|
1476
1504
|
* channel was launched from. The whole point is to give each (channel, cwd)
|
|
1477
|
-
* its own stable conversation: relaunching from the same path
|
|
1478
|
-
* previous claude session via `--
|
|
1479
|
-
*
|
|
1480
|
-
*
|
|
1505
|
+
* its own stable conversation: relaunching from the same path resumes the
|
|
1506
|
+
* previous claude session via `--resume <uuid>`, while a different cwd (or
|
|
1507
|
+
* a different channel) gets an independent one — so sessions never silently
|
|
1508
|
+
* bleed across workspaces the way claude's `-c` does.
|
|
1509
|
+
*
|
|
1510
|
+
* `get` and `create` are intentionally separate: claude's `--session-id`
|
|
1511
|
+
* only accepts a fresh UUID (it errors if the session jsonl already
|
|
1512
|
+
* exists), so callers must check `get` first and fall back to `create`
|
|
1513
|
+
* only when there is nothing to resume.
|
|
1481
1514
|
*
|
|
1482
1515
|
* Storage lives under `<dir>/channels/<channel-id>/sessions.json` (channel
|
|
1483
1516
|
* id, not name, so renames don't lose history). The file is a flat
|
|
@@ -1493,20 +1526,18 @@ var FunnelSessions = class {
|
|
|
1493
1526
|
this.dir = deps.dir;
|
|
1494
1527
|
Object.freeze(this);
|
|
1495
1528
|
}
|
|
1496
|
-
/** Returns the existing session id for (channelId, cwd) or
|
|
1497
|
-
|
|
1529
|
+
/** Returns the existing session id for (channelId, cwd) or null. */
|
|
1530
|
+
get(channelId, cwd) {
|
|
1531
|
+
return this.readMap(channelId)[cwd] ?? null;
|
|
1532
|
+
}
|
|
1533
|
+
/** Generates a new session id for (channelId, cwd) and persists it, overwriting any prior entry. */
|
|
1534
|
+
create(channelId, cwd) {
|
|
1498
1535
|
const map = this.readMap(channelId);
|
|
1499
|
-
const existing = map[cwd];
|
|
1500
|
-
if (existing) return existing;
|
|
1501
1536
|
const sessionId = this.idGenerator.generate();
|
|
1502
1537
|
map[cwd] = sessionId;
|
|
1503
1538
|
this.writeMap(channelId, map);
|
|
1504
1539
|
return sessionId;
|
|
1505
1540
|
}
|
|
1506
|
-
/** Returns the existing session id for (channelId, cwd) or null. */
|
|
1507
|
-
get(channelId, cwd) {
|
|
1508
|
-
return this.readMap(channelId)[cwd] ?? null;
|
|
1509
|
-
}
|
|
1510
1541
|
/** Drops the recorded session id for (channelId, cwd). No-op if absent. */
|
|
1511
1542
|
clear(channelId, cwd) {
|
|
1512
1543
|
const map = this.readMap(channelId);
|
|
@@ -1749,7 +1780,6 @@ const resolveDaemonScript = () => {
|
|
|
1749
1780
|
//#endregion
|
|
1750
1781
|
//#region lib/gateway/gateway.ts
|
|
1751
1782
|
const DEFAULT_PORT$1 = 9742;
|
|
1752
|
-
const DEFAULT_TMP_DIR$1 = "/tmp/funnel";
|
|
1753
1783
|
const STARTUP_TIMEOUT_MS = 5e3;
|
|
1754
1784
|
const SIGTERM_TIMEOUT_MS = 2e3;
|
|
1755
1785
|
const POLL_INTERVAL_MS$1 = 100;
|
|
@@ -1771,7 +1801,6 @@ var FunnelGateway = class {
|
|
|
1771
1801
|
clock;
|
|
1772
1802
|
dir;
|
|
1773
1803
|
pidFile;
|
|
1774
|
-
logDir;
|
|
1775
1804
|
gatewayLog;
|
|
1776
1805
|
tmpDir;
|
|
1777
1806
|
port;
|
|
@@ -1781,9 +1810,8 @@ var FunnelGateway = class {
|
|
|
1781
1810
|
this.fs = deps.fs ?? defaultFs$1;
|
|
1782
1811
|
this.clock = deps.clock ?? defaultClock;
|
|
1783
1812
|
this.dir = deps.dir ?? FUNNEL_DIR;
|
|
1784
|
-
this.tmpDir = deps.tmpDir ??
|
|
1813
|
+
this.tmpDir = deps.tmpDir ?? funnelTmpDir();
|
|
1785
1814
|
this.pidFile = join(this.dir, "gateway.pid");
|
|
1786
|
-
this.logDir = join(this.tmpDir, "events");
|
|
1787
1815
|
this.gatewayLog = join(this.tmpDir, "gateway.log");
|
|
1788
1816
|
this.port = deps.port ?? DEFAULT_PORT$1;
|
|
1789
1817
|
this.sleep = deps.sleep ?? defaultSleep$1;
|
|
@@ -1808,11 +1836,10 @@ var FunnelGateway = class {
|
|
|
1808
1836
|
this.fs.mkdirSync(this.tmpDir, { recursive: true });
|
|
1809
1837
|
const gatewayScript = resolveDaemonScript();
|
|
1810
1838
|
const command = this.buildStartCommand(gatewayScript, options);
|
|
1811
|
-
this.process.detach(
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
]);
|
|
1839
|
+
this.process.detach(command, {
|
|
1840
|
+
stdoutFile: this.gatewayLog,
|
|
1841
|
+
stderrFile: this.gatewayLog
|
|
1842
|
+
});
|
|
1816
1843
|
const deadline = this.clock.millis() + STARTUP_TIMEOUT_MS;
|
|
1817
1844
|
while (this.clock.millis() < deadline) {
|
|
1818
1845
|
if (this.isRunning()) return true;
|
|
@@ -1821,7 +1848,19 @@ var FunnelGateway = class {
|
|
|
1821
1848
|
return this.isRunning();
|
|
1822
1849
|
}
|
|
1823
1850
|
buildStartCommand(gatewayScript, options = {}) {
|
|
1824
|
-
|
|
1851
|
+
const tag = `funnel-gateway[${this.dir}]`;
|
|
1852
|
+
if (options.caffeinate !== false && globalThis.process.platform === "darwin") return [
|
|
1853
|
+
"caffeinate",
|
|
1854
|
+
"-is",
|
|
1855
|
+
"bun",
|
|
1856
|
+
gatewayScript,
|
|
1857
|
+
tag
|
|
1858
|
+
];
|
|
1859
|
+
return [
|
|
1860
|
+
"bun",
|
|
1861
|
+
gatewayScript,
|
|
1862
|
+
tag
|
|
1863
|
+
];
|
|
1825
1864
|
}
|
|
1826
1865
|
async stop() {
|
|
1827
1866
|
const pid = this.readPid();
|
|
@@ -1873,9 +1912,6 @@ var FunnelGateway = class {
|
|
|
1873
1912
|
started
|
|
1874
1913
|
};
|
|
1875
1914
|
}
|
|
1876
|
-
getLogDir() {
|
|
1877
|
-
return this.logDir;
|
|
1878
|
-
}
|
|
1879
1915
|
getGatewayLog() {
|
|
1880
1916
|
return this.gatewayLog;
|
|
1881
1917
|
}
|
|
@@ -1897,17 +1933,7 @@ var FunnelGateway = class {
|
|
|
1897
1933
|
this.fs.unlink(this.pidFile);
|
|
1898
1934
|
}
|
|
1899
1935
|
isProcessAlive(pid) {
|
|
1900
|
-
|
|
1901
|
-
"ps",
|
|
1902
|
-
"-p",
|
|
1903
|
-
String(pid),
|
|
1904
|
-
"-o",
|
|
1905
|
-
"state="
|
|
1906
|
-
]);
|
|
1907
|
-
if (result.exitCode !== 0) return false;
|
|
1908
|
-
const state = result.stdout.trim();
|
|
1909
|
-
if (!state) return false;
|
|
1910
|
-
return !state.startsWith("Z");
|
|
1936
|
+
return this.process.isAlive(pid);
|
|
1911
1937
|
}
|
|
1912
1938
|
};
|
|
1913
1939
|
//#endregion
|
|
@@ -2728,36 +2754,24 @@ const titleFor = (dir) => `funnel-gateway[${dir}]`;
|
|
|
2728
2754
|
* which is the only situation that causes a real conflict (duplicate Slack
|
|
2729
2755
|
* Socket Mode connections with the same tokens). Daemons rooted at a
|
|
2730
2756
|
* different `~/.funnel/` are left alone — they hold different tokens and
|
|
2731
|
-
* speak to different Slack apps. The daemon advertises its dir via
|
|
2732
|
-
* `
|
|
2757
|
+
* speak to different Slack apps. The daemon advertises its dir via the
|
|
2758
|
+
* `funnel-gateway[<dir>]` marker appended to argv (also assigned to
|
|
2759
|
+
* `process.title` on POSIX). `FunnelProcessRunner.listProcessesContaining`
|
|
2760
|
+
* absorbs the POSIX/Windows enumeration difference behind the marker match.
|
|
2733
2761
|
*/
|
|
2734
2762
|
const killCompetingSlackGateways = async (props) => {
|
|
2735
2763
|
const runner = props.process ?? defaultProcess;
|
|
2736
2764
|
const logger = props.logger ?? defaultLogger$1;
|
|
2737
|
-
const result = await runner.run([
|
|
2738
|
-
"ps",
|
|
2739
|
-
"-e",
|
|
2740
|
-
"-o",
|
|
2741
|
-
"pid=,args="
|
|
2742
|
-
]);
|
|
2743
|
-
if (result.exitCode !== 0) return [];
|
|
2744
2765
|
const expectedTitle = titleFor(props.dir);
|
|
2766
|
+
const snapshots = runner.listProcessesContaining(expectedTitle);
|
|
2745
2767
|
const killed = [];
|
|
2746
|
-
for (const
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
if (!match) continue;
|
|
2751
|
-
const pid = Number(match[1]);
|
|
2752
|
-
const args = match[2];
|
|
2753
|
-
if (!Number.isInteger(pid) || pid <= 0) continue;
|
|
2754
|
-
if (pid === props.selfPid) continue;
|
|
2755
|
-
if (!args.includes(expectedTitle)) continue;
|
|
2756
|
-
runner.kill(pid, "SIGTERM");
|
|
2757
|
-
killed.push(pid);
|
|
2768
|
+
for (const snapshot of snapshots) {
|
|
2769
|
+
if (snapshot.pid === props.selfPid) continue;
|
|
2770
|
+
runner.kill(snapshot.pid, "SIGTERM");
|
|
2771
|
+
killed.push(snapshot.pid);
|
|
2758
2772
|
logger.info("killed competing Slack gateway process", {
|
|
2759
|
-
pid,
|
|
2760
|
-
args:
|
|
2773
|
+
pid: snapshot.pid,
|
|
2774
|
+
args: snapshot.command.slice(0, 160)
|
|
2761
2775
|
});
|
|
2762
2776
|
}
|
|
2763
2777
|
return killed;
|
|
@@ -2923,8 +2937,7 @@ const gatewayRoutes = factory$1.createApp().get("/health", ...healthHandler).get
|
|
|
2923
2937
|
//#endregion
|
|
2924
2938
|
//#region lib/gateway/gateway-server.ts
|
|
2925
2939
|
const DEFAULT_PORT = 9742;
|
|
2926
|
-
const
|
|
2927
|
-
const DB_FILENAME = "events.db";
|
|
2940
|
+
const defaultDbPath = () => join(funnelTmpDir(), "events.db");
|
|
2928
2941
|
const defaultLogger = new NodeFunnelLogger();
|
|
2929
2942
|
/**
|
|
2930
2943
|
* In-process gateway: runs `Bun.serve` (HTTP + WebSocket /ws), boots connector
|
|
@@ -2940,7 +2953,7 @@ var FunnelGatewayServer = class {
|
|
|
2940
2953
|
channels;
|
|
2941
2954
|
settings;
|
|
2942
2955
|
port;
|
|
2943
|
-
|
|
2956
|
+
dbPath;
|
|
2944
2957
|
process;
|
|
2945
2958
|
logger;
|
|
2946
2959
|
selfPid;
|
|
@@ -2958,7 +2971,7 @@ var FunnelGatewayServer = class {
|
|
|
2958
2971
|
this.channels = deps.channels;
|
|
2959
2972
|
this.settings = deps.settings;
|
|
2960
2973
|
this.port = deps.port ?? DEFAULT_PORT;
|
|
2961
|
-
this.
|
|
2974
|
+
this.dbPath = deps.dbPath ?? defaultDbPath();
|
|
2962
2975
|
this.process = deps.process;
|
|
2963
2976
|
this.logger = deps.logger ?? defaultLogger;
|
|
2964
2977
|
this.selfPid = deps.selfPid ?? globalThis.process.pid;
|
|
@@ -2968,9 +2981,10 @@ var FunnelGatewayServer = class {
|
|
|
2968
2981
|
this.extraRoutes = deps.extraRoutes ?? null;
|
|
2969
2982
|
const clock = deps.clock;
|
|
2970
2983
|
this.nowMs = clock ? () => clock.millis() : () => Date.now();
|
|
2971
|
-
|
|
2984
|
+
const dbDir = dirname(this.dbPath);
|
|
2985
|
+
if (!existsSync(dbDir)) mkdirSync(dbDir, { recursive: true });
|
|
2972
2986
|
this.eventStore = new FunnelEventStore({
|
|
2973
|
-
path:
|
|
2987
|
+
path: this.dbPath,
|
|
2974
2988
|
now: this.nowMs
|
|
2975
2989
|
});
|
|
2976
2990
|
this.broadcaster = new FunnelBroadcaster({
|
|
@@ -3174,7 +3188,7 @@ var FunnelGatewayServer = class {
|
|
|
3174
3188
|
channel: entry.channelName,
|
|
3175
3189
|
connector: entry.name
|
|
3176
3190
|
});
|
|
3177
|
-
this.logger.info(`event store: ${
|
|
3191
|
+
this.logger.info(`event store: ${this.dbPath}`);
|
|
3178
3192
|
this.logger.info("funnel gateway running");
|
|
3179
3193
|
}
|
|
3180
3194
|
/**
|
|
@@ -3361,7 +3375,6 @@ var FunnelListenersClient = class {
|
|
|
3361
3375
|
};
|
|
3362
3376
|
//#endregion
|
|
3363
3377
|
//#region lib/funnel.ts
|
|
3364
|
-
const DEFAULT_TMP_DIR = "/tmp/funnel";
|
|
3365
3378
|
const SANDBOX_DIR = "/sandbox/.funnel";
|
|
3366
3379
|
const SANDBOX_TMP_DIR = "/sandbox/tmp";
|
|
3367
3380
|
/**
|
|
@@ -3411,7 +3424,7 @@ var Funnel = class Funnel {
|
|
|
3411
3424
|
const dir = this.props.dir ?? FUNNEL_DIR;
|
|
3412
3425
|
return {
|
|
3413
3426
|
dir,
|
|
3414
|
-
tmpDir: this.props.tmpDir ??
|
|
3427
|
+
tmpDir: this.props.tmpDir ?? funnelTmpDir(),
|
|
3415
3428
|
settings: join(dir, "settings.json")
|
|
3416
3429
|
};
|
|
3417
3430
|
}
|
|
@@ -3591,7 +3604,7 @@ var Funnel = class Funnel {
|
|
|
3591
3604
|
channels: this.channels,
|
|
3592
3605
|
settings: this.store,
|
|
3593
3606
|
port: options.port,
|
|
3594
|
-
|
|
3607
|
+
dbPath: options.dbPath,
|
|
3595
3608
|
process: this.process,
|
|
3596
3609
|
clock: this.clock,
|
|
3597
3610
|
logger: this.logger,
|
|
@@ -4445,13 +4458,13 @@ usage: funnel gateway logs [-n <N>]
|
|
|
4445
4458
|
options:
|
|
4446
4459
|
-n <N> number of trailing lines to show (default: 20)
|
|
4447
4460
|
|
|
4448
|
-
Tails
|
|
4461
|
+
Tails ${join(funnelTmpDir(), "funnel.log")} (the daemon's diagnostic stream — gateway
|
|
4449
4462
|
lifecycle, channel connect/disconnect, listener boot). Exit with SIGINT.
|
|
4450
4463
|
Output is formatted as YAML.
|
|
4451
4464
|
|
|
4452
4465
|
Domain events fanned out to WebSocket clients live in the SQLite event
|
|
4453
|
-
store (
|
|
4454
|
-
WS endpoint or query the store directly.
|
|
4466
|
+
store (${join(funnelTmpDir(), "events.db")}); they are not shown here. Subscribe via
|
|
4467
|
+
the WS endpoint or query the store directly.
|
|
4455
4468
|
|
|
4456
4469
|
examples:
|
|
4457
4470
|
funnel gateway logs
|
|
@@ -4525,7 +4538,7 @@ const gatewayRestartHandler = factory.createHandlers(zValidator$1("query", z.obj
|
|
|
4525
4538
|
usage: funnel gateway restart [--no-caffeine]
|
|
4526
4539
|
|
|
4527
4540
|
Stops the running process then starts it again in background.
|
|
4528
|
-
On macOS wraps with caffeinate -
|
|
4541
|
+
On macOS wraps with caffeinate -is by default. Use --no-caffeine to disable.
|
|
4529
4542
|
|
|
4530
4543
|
examples:
|
|
4531
4544
|
funnel gateway restart
|
|
@@ -4544,7 +4557,7 @@ const gatewayRunHandler = factory.createHandlers(zValidator$1("query", z.object(
|
|
|
4544
4557
|
usage: funnel gateway run [--no-caffeine]
|
|
4545
4558
|
|
|
4546
4559
|
For developers. The process is tied to the current terminal and exits on SIGINT / SIGTERM.
|
|
4547
|
-
On macOS wraps with caffeinate -
|
|
4560
|
+
On macOS wraps with caffeinate -is by default. Use --no-caffeine to disable.
|
|
4548
4561
|
|
|
4549
4562
|
For normal usage prefer funnel gateway start.
|
|
4550
4563
|
|
|
@@ -4556,28 +4569,31 @@ examples:
|
|
|
4556
4569
|
const gatewayScript = resolveDaemonScript();
|
|
4557
4570
|
const command = query["no-caffeine"] !== "true" && process.platform === "darwin" ? [
|
|
4558
4571
|
"caffeinate",
|
|
4559
|
-
"-
|
|
4572
|
+
"-is",
|
|
4560
4573
|
"bun",
|
|
4561
4574
|
gatewayScript
|
|
4562
4575
|
] : ["bun", gatewayScript];
|
|
4563
4576
|
const exitCode = await funnel.process.attach(command);
|
|
4564
4577
|
process.exit(exitCode);
|
|
4565
4578
|
});
|
|
4566
|
-
|
|
4579
|
+
//#endregion
|
|
4580
|
+
//#region lib/cli/routes/gateway.start.ts
|
|
4581
|
+
const startHelp = `funnel gateway start — start the gateway in background
|
|
4567
4582
|
|
|
4568
4583
|
usage: funnel gateway start [--no-caffeine]
|
|
4569
4584
|
|
|
4570
|
-
|
|
4571
|
-
On macOS wraps the process with caffeinate -
|
|
4585
|
+
Spawned as a detached background process so it keeps running after the terminal is closed.
|
|
4586
|
+
On macOS wraps the process with caffeinate -is by default to prevent idle and system sleep.
|
|
4572
4587
|
Use --no-caffeine to disable caffeinate.
|
|
4573
4588
|
|
|
4574
4589
|
port: 9742 (override via FUNNEL_PORT)
|
|
4575
4590
|
pid: ~/.funnel/gateway.pid
|
|
4576
|
-
log:
|
|
4591
|
+
log: ${join(funnelTmpDir(), "gateway.log")}
|
|
4577
4592
|
|
|
4578
4593
|
examples:
|
|
4579
4594
|
funnel gateway start
|
|
4580
|
-
funnel gateway start --no-caffeine
|
|
4595
|
+
funnel gateway start --no-caffeine`;
|
|
4596
|
+
const gatewayStartHandler = factory.createHandlers(zValidator$1("query", z.object({ "no-caffeine": z.string().optional() }), startHelp), async (c) => {
|
|
4581
4597
|
const query = c.req.valid("query");
|
|
4582
4598
|
const funnel = c.var.funnel;
|
|
4583
4599
|
if (funnel.gateway.isRunning()) {
|
|
@@ -20,7 +20,7 @@ declare abstract class FunnelConnectorListener {
|
|
|
20
20
|
//#region lib/engine/logger/logger.d.ts
|
|
21
21
|
/**
|
|
22
22
|
* Structured logger with three levels and an optional log-file path.
|
|
23
|
-
* Defaults to NodeFunnelLogger (appends to
|
|
23
|
+
* Defaults to NodeFunnelLogger (appends to `<os.tmpdir()>/funnel/funnel.log`);
|
|
24
24
|
* MemoryFunnelLogger captures entries in memory and NoopFunnelLogger silences output.
|
|
25
25
|
*/
|
|
26
26
|
declare abstract class FunnelLogger {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { dirname, join } from "node:path";
|
|
2
2
|
import { appendFileSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
3
4
|
//#region lib/connectors/connector-listener.ts
|
|
4
5
|
/**
|
|
5
6
|
* Long-lived event source for one connector.
|
|
@@ -21,19 +22,31 @@ var FunnelConnectorListener = class {
|
|
|
21
22
|
//#region lib/engine/logger/logger.ts
|
|
22
23
|
/**
|
|
23
24
|
* Structured logger with three levels and an optional log-file path.
|
|
24
|
-
* Defaults to NodeFunnelLogger (appends to
|
|
25
|
+
* Defaults to NodeFunnelLogger (appends to `<os.tmpdir()>/funnel/funnel.log`);
|
|
25
26
|
* MemoryFunnelLogger captures entries in memory and NoopFunnelLogger silences output.
|
|
26
27
|
*/
|
|
27
28
|
var FunnelLogger = class {};
|
|
28
29
|
//#endregion
|
|
30
|
+
//#region lib/engine/settings/tmp-dir.ts
|
|
31
|
+
/**
|
|
32
|
+
* Resolves the funnel temp/log root for the current OS. Defaults to
|
|
33
|
+
* `<os.tmpdir()>/funnel` so Windows lands under `%TEMP%\funnel` and POSIX
|
|
34
|
+
* lands under `/tmp/funnel`. Callers may override via `FUNNEL_TMP_DIR`.
|
|
35
|
+
*/
|
|
36
|
+
function funnelTmpDir() {
|
|
37
|
+
const override = process.env.FUNNEL_TMP_DIR;
|
|
38
|
+
if (override && override.length > 0) return override;
|
|
39
|
+
return join(tmpdir(), "funnel");
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
29
42
|
//#region lib/engine/logger/node-logger.ts
|
|
30
|
-
const
|
|
43
|
+
const defaultLogFile = () => join(funnelTmpDir(), "funnel.log");
|
|
31
44
|
var NodeFunnelLogger = class extends FunnelLogger {
|
|
32
45
|
file;
|
|
33
46
|
now;
|
|
34
47
|
constructor(props = {}) {
|
|
35
48
|
super();
|
|
36
|
-
this.file = props.file ??
|
|
49
|
+
this.file = props.file ?? defaultLogFile();
|
|
37
50
|
this.now = props.now ?? (() => /* @__PURE__ */ new Date());
|
|
38
51
|
Object.freeze(this);
|
|
39
52
|
}
|
|
@@ -58,4 +71,4 @@ var NodeFunnelLogger = class extends FunnelLogger {
|
|
|
58
71
|
}
|
|
59
72
|
};
|
|
60
73
|
//#endregion
|
|
61
|
-
export {
|
|
74
|
+
export { FunnelConnectorListener as i, funnelTmpDir as n, FunnelLogger as r, NodeFunnelLogger as t };
|
package/dist/{schedule-connector-schema-FxP7LPlx.js → schedule-connector-schema-BZpH6ZmR.js}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as FunnelConnectorListener, t as NodeFunnelLogger } from "./node-logger-B97ZiGwj.js";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { z } from "zod";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as FunnelConnectorListener, r as NotifyFn, t as FunnelLogger } from "./logger-
|
|
1
|
+
import { n as FunnelConnectorListener, r as NotifyFn, t as FunnelLogger } from "./logger-B3aXsVcX.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
4
|
//#region lib/connectors/schedule-connector-schema.d.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as FunnelConnectorAdapter } from "./connector-adapter-D5Utumgz.js";
|
|
2
|
-
import {
|
|
2
|
+
import { i as FunnelConnectorListener, t as NodeFunnelLogger } from "./node-logger-B97ZiGwj.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { WebClient } from "@slack/web-api";
|
|
5
5
|
import { App, LogLevel } from "@slack/bolt";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as FunnelConnectorListener, r as NotifyFn, t as FunnelLogger } from "./logger-
|
|
1
|
+
import { n as FunnelConnectorListener, r as NotifyFn, t as FunnelLogger } from "./logger-B3aXsVcX.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { App } from "@slack/bolt";
|
|
4
4
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@interactive-inc/claude-funnel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.1",
|
|
4
4
|
"description": "Hub CLI that routes external events (Slack / GitHub / Discord) to Claude Code agents through subscription channels over MCP.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -90,5 +90,8 @@
|
|
|
90
90
|
},
|
|
91
91
|
"engines": {
|
|
92
92
|
"bun": ">=1.3.0"
|
|
93
|
+
},
|
|
94
|
+
"scripts": {
|
|
95
|
+
"prepack": "make build"
|
|
93
96
|
}
|
|
94
97
|
}
|