@interactive-inc/claude-funnel 0.27.0 → 0.29.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/README.md +18 -20
- package/dist/bin.js +875 -880
- package/dist/{connector-diagnostic-log-Clb2sCcz.d.ts → connector-diagnostic-log-OPpPi9V9.d.ts} +2 -0
- package/dist/connectors/discord.d.ts +1 -1
- package/dist/connectors/gh.d.ts +1 -1
- package/dist/connectors/schedule.d.ts +1 -1
- package/dist/connectors/slack.d.ts +1 -1
- package/dist/gateway/daemon.js +225 -260
- package/dist/index.d.ts +45 -73
- package/dist/index.js +130 -107
- package/dist/{schedule-listener-C2-KqHQc.d.ts → schedule-listener-3M6WkH1Y.d.ts} +1 -1
- package/dist/{slack-listener-BMknoyVr.d.ts → slack-listener-CLTiOEJw.d.ts} +1 -1
- package/funnel.schema.json +3 -30
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -887,8 +887,9 @@ var MemoryFunnelIdGenerator = class extends FunnelIdGenerator {
|
|
|
887
887
|
* `fnl claude` reads this when no global --profile preset is used. It picks one
|
|
888
888
|
* of the declared channels (`--channel <name>` selects by name; otherwise the
|
|
889
889
|
* first entry wins) and materializes its transport (connectors / delivery) into
|
|
890
|
-
* `~/.funnel/settings.json` on launch
|
|
891
|
-
*
|
|
890
|
+
* the repo's scoped settings (`~/.funnel/projects/<id>/settings.json`) on launch.
|
|
891
|
+
* Connectors carry no tokens here — a token absent from settings is prompted for
|
|
892
|
+
* at launch (TTY) and saved there, never in the repo.
|
|
892
893
|
*
|
|
893
894
|
* The launch recipe (`options` / `env` / `resume`) lives on `profiles[]`, not on
|
|
894
895
|
* the channel: a channel only describes where events come from. `fnl claude`
|
|
@@ -896,25 +897,15 @@ var MemoryFunnelIdGenerator = class extends FunnelIdGenerator {
|
|
|
896
897
|
* straight to the launcher and is not persisted into the global profile list.
|
|
897
898
|
* These profiles are selected by their `channel` binding, not by name.
|
|
898
899
|
*/
|
|
899
|
-
const slackEnvSchema = z.object({
|
|
900
|
-
botToken: z.string().optional(),
|
|
901
|
-
appToken: z.string().optional()
|
|
902
|
-
}).optional();
|
|
903
900
|
const slackConnectorSpecSchema = z.object({
|
|
904
901
|
type: z.literal("slack"),
|
|
905
902
|
name: z.string(),
|
|
906
|
-
botToken: z.string().optional(),
|
|
907
|
-
appToken: z.string().optional(),
|
|
908
903
|
/** Shrink raw Slack events before fanout. Defaults to true. */
|
|
909
|
-
minify: z.boolean().optional()
|
|
910
|
-
env: slackEnvSchema
|
|
904
|
+
minify: z.boolean().optional()
|
|
911
905
|
});
|
|
912
|
-
const discordEnvSchema = z.object({ botToken: z.string().optional() }).optional();
|
|
913
906
|
const discordConnectorSpecSchema = z.object({
|
|
914
907
|
type: z.literal("discord"),
|
|
915
|
-
name: z.string()
|
|
916
|
-
botToken: z.string().optional(),
|
|
917
|
-
env: discordEnvSchema
|
|
908
|
+
name: z.string()
|
|
918
909
|
});
|
|
919
910
|
const ghConnectorSpecSchema = z.object({
|
|
920
911
|
type: z.literal("gh"),
|
|
@@ -952,54 +943,18 @@ const profileSpecSchema = z.object({
|
|
|
952
943
|
});
|
|
953
944
|
const localConfigSchema = z.object({
|
|
954
945
|
$schema: z.string().optional(),
|
|
946
|
+
/**
|
|
947
|
+
* Stable per-repo identifier. funnel writes this on first launch when absent;
|
|
948
|
+
* all funnel state for this repo lives under `~/.funnel/projects/<id>/`, so the
|
|
949
|
+
* repo itself never holds settings or tokens. Committed alongside funnel.json.
|
|
950
|
+
*/
|
|
951
|
+
id: z.string().optional(),
|
|
955
952
|
/** Declared channels (transport only). First entry is the default; --channel <name> selects by name. */
|
|
956
953
|
channels: z.array(channelSpecSchema).min(1),
|
|
957
954
|
/** Launch presets bound to a channel. First entry bound to the chosen channel is the default. */
|
|
958
955
|
profiles: z.array(profileSpecSchema).optional()
|
|
959
956
|
});
|
|
960
957
|
const LOCAL_CONFIG_FILENAME = "funnel.json";
|
|
961
|
-
const LOCAL_ENV_FILENAME = ".env.local";
|
|
962
|
-
//#endregion
|
|
963
|
-
//#region lib/engine/local-config/dotenv-reader.ts
|
|
964
|
-
const VARIABLE_LINE = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/;
|
|
965
|
-
const unquote = (value) => {
|
|
966
|
-
if (value.length < 2) return value;
|
|
967
|
-
const first = value[0];
|
|
968
|
-
const last = value[value.length - 1];
|
|
969
|
-
if (first === "\"" && last === "\"") return value.slice(1, -1);
|
|
970
|
-
if (first === "'" && last === "'") return value.slice(1, -1);
|
|
971
|
-
return value;
|
|
972
|
-
};
|
|
973
|
-
/**
|
|
974
|
-
* Minimal `.env.local` parser. Supports `KEY=value` lines, blank lines, and
|
|
975
|
-
* `#` comments. Strips matching surrounding single or double quotes. No
|
|
976
|
-
* interpolation, no `export` prefix — anything fancier should live in a real
|
|
977
|
-
* env file loaded by the shell.
|
|
978
|
-
*/
|
|
979
|
-
var FunnelDotenvReader = class {
|
|
980
|
-
fs;
|
|
981
|
-
constructor(deps) {
|
|
982
|
-
this.fs = deps.fs;
|
|
983
|
-
Object.freeze(this);
|
|
984
|
-
}
|
|
985
|
-
read(cwd) {
|
|
986
|
-
const path = join(cwd, LOCAL_ENV_FILENAME);
|
|
987
|
-
if (!this.fs.existsSync(path)) return {};
|
|
988
|
-
const raw = this.fs.readFileSync(path);
|
|
989
|
-
const out = {};
|
|
990
|
-
for (const line of raw.split("\n")) {
|
|
991
|
-
const trimmed = line.trim();
|
|
992
|
-
if (trimmed === "" || trimmed.startsWith("#")) continue;
|
|
993
|
-
const match = trimmed.match(VARIABLE_LINE);
|
|
994
|
-
if (!match) continue;
|
|
995
|
-
const key = match[1];
|
|
996
|
-
const value = match[2];
|
|
997
|
-
if (!key) continue;
|
|
998
|
-
out[key] = unquote(value ?? "");
|
|
999
|
-
}
|
|
1000
|
-
return out;
|
|
1001
|
-
}
|
|
1002
|
-
};
|
|
1003
958
|
//#endregion
|
|
1004
959
|
//#region lib/engine/local-config/local-config.ts
|
|
1005
960
|
/**
|
|
@@ -1075,27 +1030,22 @@ var FunnelTokenPrompter = class {};
|
|
|
1075
1030
|
*/
|
|
1076
1031
|
var FunnelLocalConfigSync = class {
|
|
1077
1032
|
channels;
|
|
1078
|
-
dotenv;
|
|
1079
1033
|
prompter;
|
|
1080
|
-
env;
|
|
1081
1034
|
constructor(deps) {
|
|
1082
1035
|
this.channels = deps.channels;
|
|
1083
|
-
this.dotenv = deps.dotenv;
|
|
1084
1036
|
this.prompter = deps.prompter;
|
|
1085
|
-
this.env = deps.env ?? process.env;
|
|
1086
1037
|
Object.freeze(this);
|
|
1087
1038
|
}
|
|
1088
|
-
async ensure(channel
|
|
1039
|
+
async ensure(channel) {
|
|
1089
1040
|
if (!this.channels.get(channel.name)) this.channels.add({ name: channel.name });
|
|
1090
1041
|
if (channel.connectors === void 0) return {
|
|
1091
1042
|
touched: [],
|
|
1092
1043
|
removed: []
|
|
1093
1044
|
};
|
|
1094
|
-
const dotenv = this.dotenv.read(cwd);
|
|
1095
1045
|
const touched = [];
|
|
1096
1046
|
const touchedIds = /* @__PURE__ */ new Set();
|
|
1097
1047
|
for (const spec of channel.connectors) {
|
|
1098
|
-
const outcome = await this.ensureConnector(channel.name, spec
|
|
1048
|
+
const outcome = await this.ensureConnector(channel.name, spec);
|
|
1099
1049
|
touched.push({
|
|
1100
1050
|
name: outcome.name,
|
|
1101
1051
|
changed: outcome.changed
|
|
@@ -1107,26 +1057,20 @@ var FunnelLocalConfigSync = class {
|
|
|
1107
1057
|
removed: this.removeExtras(channel.name, touchedIds)
|
|
1108
1058
|
};
|
|
1109
1059
|
}
|
|
1110
|
-
async ensureConnector(channelName, spec
|
|
1111
|
-
if (spec.type === "slack") return await this.ensureSlack(channelName, spec
|
|
1112
|
-
if (spec.type === "discord") return await this.ensureDiscord(channelName, spec
|
|
1060
|
+
async ensureConnector(channelName, spec) {
|
|
1061
|
+
if (spec.type === "slack") return await this.ensureSlack(channelName, spec);
|
|
1062
|
+
if (spec.type === "discord") return await this.ensureDiscord(channelName, spec);
|
|
1113
1063
|
if (spec.type === "gh") return this.ensureGh(channelName, spec);
|
|
1114
1064
|
return this.ensureSchedule(channelName, spec);
|
|
1115
1065
|
}
|
|
1116
|
-
async ensureSlack(channelName, spec
|
|
1066
|
+
async ensureSlack(channelName, spec) {
|
|
1117
1067
|
const byName = this.findExistingSlack(channelName, spec.name);
|
|
1118
1068
|
const bot = await this.resolveSlot({
|
|
1119
|
-
literal: spec.botToken,
|
|
1120
|
-
envVar: spec.env?.botToken,
|
|
1121
|
-
dotenv,
|
|
1122
1069
|
label: `${spec.name}.botToken`,
|
|
1123
1070
|
existingLiteral: byName?.botToken,
|
|
1124
1071
|
existingEnv: byName?.botTokenEnv
|
|
1125
1072
|
});
|
|
1126
1073
|
const app = await this.resolveSlot({
|
|
1127
|
-
literal: spec.appToken,
|
|
1128
|
-
envVar: spec.env?.appToken,
|
|
1129
|
-
dotenv,
|
|
1130
1074
|
label: `${spec.name}.appToken`,
|
|
1131
1075
|
existingLiteral: byName?.appToken,
|
|
1132
1076
|
existingEnv: byName?.appTokenEnv
|
|
@@ -1163,12 +1107,9 @@ var FunnelLocalConfigSync = class {
|
|
|
1163
1107
|
changed: true
|
|
1164
1108
|
};
|
|
1165
1109
|
}
|
|
1166
|
-
async ensureDiscord(channelName, spec
|
|
1110
|
+
async ensureDiscord(channelName, spec) {
|
|
1167
1111
|
const byName = this.findExistingDiscord(channelName, spec.name);
|
|
1168
1112
|
const bot = await this.resolveSlot({
|
|
1169
|
-
literal: spec.botToken,
|
|
1170
|
-
envVar: spec.env?.botToken,
|
|
1171
|
-
dotenv,
|
|
1172
1113
|
label: `${spec.name}.botToken`,
|
|
1173
1114
|
existingLiteral: byName?.botToken,
|
|
1174
1115
|
existingEnv: byName?.botTokenEnv
|
|
@@ -1267,30 +1208,14 @@ var FunnelLocalConfigSync = class {
|
|
|
1267
1208
|
return stale.map((c) => c.name);
|
|
1268
1209
|
}
|
|
1269
1210
|
/**
|
|
1270
|
-
* Decides how a single token slot is stored in settings.json
|
|
1271
|
-
*
|
|
1272
|
-
*
|
|
1273
|
-
*
|
|
1274
|
-
*
|
|
1275
|
-
*
|
|
1276
|
-
* - literal → `{ token: "<secret>" }`.
|
|
1277
|
-
* - neither, but a prior value exists → carry it over verbatim (whichever
|
|
1278
|
-
* form it already was), so a tokenless re-sync is a no-op.
|
|
1279
|
-
* - nothing at all → prompt for a literal (TTY only; throws otherwise).
|
|
1211
|
+
* Decides how a single token slot is stored in settings.json. funnel.json
|
|
1212
|
+
* never carries tokens, so the only sources are a value already in
|
|
1213
|
+
* settings.json (carried over verbatim, whichever form it was — literal or an
|
|
1214
|
+
* `env`-var reference set via the CLI) or, on first sync, a TTY prompt for a
|
|
1215
|
+
* literal (throws when stdin is not a TTY). Either way the secret lands in the
|
|
1216
|
+
* repo-scoped settings, never in the repo itself.
|
|
1280
1217
|
*/
|
|
1281
1218
|
async resolveSlot(input) {
|
|
1282
|
-
if (input.literal !== void 0 && input.envVar !== void 0) throw new Error(`${input.label} is set both as a literal and as env.${input.label.split(".").pop()}; pick one`);
|
|
1283
|
-
if (input.envVar !== void 0 && input.envVar !== "") {
|
|
1284
|
-
if (!this.env[input.envVar] && !input.dotenv[input.envVar]) throw new Error(`${input.label} references env var "${input.envVar}" but it is not set in process env or .env.local`);
|
|
1285
|
-
return {
|
|
1286
|
-
token: void 0,
|
|
1287
|
-
tokenEnv: input.envVar
|
|
1288
|
-
};
|
|
1289
|
-
}
|
|
1290
|
-
if (input.literal !== void 0 && input.literal !== "") return {
|
|
1291
|
-
token: input.literal,
|
|
1292
|
-
tokenEnv: void 0
|
|
1293
|
-
};
|
|
1294
1219
|
if (input.existingEnv !== void 0) return {
|
|
1295
1220
|
token: void 0,
|
|
1296
1221
|
tokenEnv: input.existingEnv
|
|
@@ -1306,6 +1231,44 @@ var FunnelLocalConfigSync = class {
|
|
|
1306
1231
|
}
|
|
1307
1232
|
};
|
|
1308
1233
|
//#endregion
|
|
1234
|
+
//#region lib/engine/local-config/local-config-writer.ts
|
|
1235
|
+
const isRecord = (value) => {
|
|
1236
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1237
|
+
};
|
|
1238
|
+
const withIdFirst = (config, id) => {
|
|
1239
|
+
const ordered = {};
|
|
1240
|
+
if (config.$schema !== void 0) ordered.$schema = config.$schema;
|
|
1241
|
+
ordered.id = id;
|
|
1242
|
+
for (const key of Object.keys(config)) {
|
|
1243
|
+
if (key === "$schema" || key === "id") continue;
|
|
1244
|
+
ordered[key] = config[key];
|
|
1245
|
+
}
|
|
1246
|
+
return ordered;
|
|
1247
|
+
};
|
|
1248
|
+
/**
|
|
1249
|
+
* The one path that mutates the repo-committed funnel.json, and it only ever
|
|
1250
|
+
* inserts `id`. On first launch a repo has no `id`; funnel generates one and
|
|
1251
|
+
* writes it back here so future launches resolve the same `~/.funnel/projects/<id>/`.
|
|
1252
|
+
* Idempotent — a no-op once `id` is present. Kept separate from the read-only
|
|
1253
|
+
* FunnelLocalConfig so reads stay side-effect free.
|
|
1254
|
+
*/
|
|
1255
|
+
var FunnelLocalConfigWriter = class {
|
|
1256
|
+
fs;
|
|
1257
|
+
constructor(deps) {
|
|
1258
|
+
this.fs = deps.fs;
|
|
1259
|
+
Object.freeze(this);
|
|
1260
|
+
}
|
|
1261
|
+
ensureId(cwd, id) {
|
|
1262
|
+
const path = join(cwd, LOCAL_CONFIG_FILENAME);
|
|
1263
|
+
if (!this.fs.existsSync(path)) return;
|
|
1264
|
+
const parsed = JSON.parse(this.fs.readFileSync(path));
|
|
1265
|
+
if (!isRecord(parsed)) return;
|
|
1266
|
+
if (typeof parsed.id === "string" && parsed.id !== "") return;
|
|
1267
|
+
const ordered = withIdFirst(parsed, id);
|
|
1268
|
+
this.fs.writeFileSync(path, `${JSON.stringify(ordered, null, 2)}\n`);
|
|
1269
|
+
}
|
|
1270
|
+
};
|
|
1271
|
+
//#endregion
|
|
1309
1272
|
//#region lib/engine/logger/memory-logger.ts
|
|
1310
1273
|
var MemoryFunnelLogger = class extends FunnelLogger {
|
|
1311
1274
|
file = null;
|
|
@@ -2263,6 +2226,8 @@ var FunnelEventLog = class {};
|
|
|
2263
2226
|
//#region lib/logger/leuco-logger-sqlite-sink.ts
|
|
2264
2227
|
/** Conservative whitelist for column names interpolated into SQL. */
|
|
2265
2228
|
const COLUMN_NAME_RE = /^[a-z_][a-z0-9_]*$/;
|
|
2229
|
+
/** How many inserts between on-disk size checks (see insertsSinceByteCheck). */
|
|
2230
|
+
const BYTE_CHECK_INTERVAL = 500;
|
|
2266
2231
|
const RESERVED_COLUMNS = new Set([
|
|
2267
2232
|
"seq",
|
|
2268
2233
|
"ts",
|
|
@@ -2316,6 +2281,8 @@ var LeucoLoggerSqliteSink = class {
|
|
|
2316
2281
|
db;
|
|
2317
2282
|
maxRows;
|
|
2318
2283
|
maxAgeMs;
|
|
2284
|
+
maxBytes;
|
|
2285
|
+
targetBytes;
|
|
2319
2286
|
now;
|
|
2320
2287
|
indexes;
|
|
2321
2288
|
extractIndexes;
|
|
@@ -2325,12 +2292,16 @@ var LeucoLoggerSqliteSink = class {
|
|
|
2325
2292
|
countStmt;
|
|
2326
2293
|
trimRowsStmt;
|
|
2327
2294
|
trimAgeStmt;
|
|
2295
|
+
trimOldestStmt;
|
|
2296
|
+
insertsSinceByteCheck = 0;
|
|
2328
2297
|
constructor(props) {
|
|
2329
2298
|
this.db = new Database(props.path);
|
|
2330
2299
|
this.db.run("PRAGMA journal_mode = WAL");
|
|
2331
2300
|
this.migrate();
|
|
2332
2301
|
this.maxRows = props.maxRows ?? null;
|
|
2333
2302
|
this.maxAgeMs = props.maxAgeMs ?? null;
|
|
2303
|
+
this.maxBytes = props.maxBytes ?? null;
|
|
2304
|
+
this.targetBytes = props.targetBytes ?? (props.maxBytes !== void 0 ? Math.floor(props.maxBytes / 4) : null);
|
|
2334
2305
|
this.now = props.now ?? (() => Date.now());
|
|
2335
2306
|
this.indexes = props.indexes ?? [];
|
|
2336
2307
|
if (this.indexes.length > 0) {
|
|
@@ -2353,6 +2324,7 @@ var LeucoLoggerSqliteSink = class {
|
|
|
2353
2324
|
this.countStmt = this.db.prepare("SELECT COUNT(*) AS n FROM leuco_log");
|
|
2354
2325
|
this.trimRowsStmt = this.db.prepare("DELETE FROM leuco_log WHERE seq <= (SELECT seq FROM leuco_log ORDER BY seq DESC LIMIT 1 OFFSET ?)");
|
|
2355
2326
|
this.trimAgeStmt = this.db.prepare("DELETE FROM leuco_log WHERE ts < ?");
|
|
2327
|
+
this.trimOldestStmt = this.db.prepare("DELETE FROM leuco_log WHERE seq IN (SELECT seq FROM leuco_log ORDER BY seq ASC LIMIT ?)");
|
|
2356
2328
|
}
|
|
2357
2329
|
insert(input) {
|
|
2358
2330
|
try {
|
|
@@ -2463,6 +2435,40 @@ var LeucoLoggerSqliteSink = class {
|
|
|
2463
2435
|
if (row && row.n > this.maxRows) this.trimRowsStmt.run(this.maxRows);
|
|
2464
2436
|
}
|
|
2465
2437
|
if (this.maxAgeMs !== null) this.trimAgeStmt.run(this.now() - this.maxAgeMs);
|
|
2438
|
+
this.maybeTrimBytes();
|
|
2439
|
+
}
|
|
2440
|
+
/**
|
|
2441
|
+
* Throttled byte-size enforcement. Only every BYTE_CHECK_INTERVAL inserts do
|
|
2442
|
+
* we measure the file; on overflow we estimate how many of the oldest rows to
|
|
2443
|
+
* drop to land near targetBytes (by the byte/row ratio), delete them in one
|
|
2444
|
+
* statement, then VACUUM once to return the freed pages to the filesystem (a
|
|
2445
|
+
* plain DELETE only frees pages inside the file). One DELETE + one VACUUM per
|
|
2446
|
+
* overflow keeps the expensive rewrite rare — the file must refill the whole
|
|
2447
|
+
* maxBytes→targetBytes delta before the next overflow can trigger.
|
|
2448
|
+
*/
|
|
2449
|
+
maybeTrimBytes() {
|
|
2450
|
+
if (this.maxBytes === null || this.targetBytes === null) return;
|
|
2451
|
+
this.insertsSinceByteCheck += 1;
|
|
2452
|
+
if (this.insertsSinceByteCheck < BYTE_CHECK_INTERVAL) return;
|
|
2453
|
+
this.insertsSinceByteCheck = 0;
|
|
2454
|
+
const bytes = this.byteSize();
|
|
2455
|
+
if (bytes <= this.maxBytes) return;
|
|
2456
|
+
const rows = this.countStmt.get()?.n ?? 0;
|
|
2457
|
+
if (rows === 0) return;
|
|
2458
|
+
const bytesToFree = bytes - this.targetBytes;
|
|
2459
|
+
const bytesPerRow = bytes / rows;
|
|
2460
|
+
const rowsToDrop = Math.min(rows, Math.ceil(bytesToFree / bytesPerRow));
|
|
2461
|
+
this.trimOldestStmt.run(rowsToDrop);
|
|
2462
|
+
this.db.run("VACUUM");
|
|
2463
|
+
}
|
|
2464
|
+
byteSize() {
|
|
2465
|
+
return (this.db.prepare("PRAGMA page_count").get()?.n ?? 0) * (this.db.prepare("PRAGMA page_size").get()?.n ?? 0);
|
|
2466
|
+
}
|
|
2467
|
+
/** Drop every row and reclaim the file space. Used by `<log>.clear()`. */
|
|
2468
|
+
clear() {
|
|
2469
|
+
this.db.run("DELETE FROM leuco_log");
|
|
2470
|
+
this.db.run("VACUUM");
|
|
2471
|
+
this.insertsSinceByteCheck = 0;
|
|
2466
2472
|
}
|
|
2467
2473
|
syncIndexColumns() {
|
|
2468
2474
|
const existing = new Set(this.db.prepare("PRAGMA table_info(leuco_log)").all().map((r) => r.name));
|
|
@@ -2539,7 +2545,9 @@ var SqliteFunnelEventLog = class extends FunnelEventLog {
|
|
|
2539
2545
|
}),
|
|
2540
2546
|
now: this.now,
|
|
2541
2547
|
...props.maxRows !== void 0 ? { maxRows: props.maxRows } : {},
|
|
2542
|
-
...props.maxAgeMs !== void 0 ? { maxAgeMs: props.maxAgeMs } : {}
|
|
2548
|
+
...props.maxAgeMs !== void 0 ? { maxAgeMs: props.maxAgeMs } : {},
|
|
2549
|
+
...props.maxBytes !== void 0 ? { maxBytes: props.maxBytes } : {},
|
|
2550
|
+
...props.targetBytes !== void 0 ? { targetBytes: props.targetBytes } : {}
|
|
2543
2551
|
});
|
|
2544
2552
|
}
|
|
2545
2553
|
/**
|
|
@@ -2600,6 +2608,9 @@ var SqliteFunnelEventLog = class extends FunnelEventLog {
|
|
|
2600
2608
|
findMaxOffset() {
|
|
2601
2609
|
return this.sink.getMaxSeq();
|
|
2602
2610
|
}
|
|
2611
|
+
clear() {
|
|
2612
|
+
this.sink.clear();
|
|
2613
|
+
}
|
|
2603
2614
|
close() {
|
|
2604
2615
|
this.sink.close();
|
|
2605
2616
|
}
|
|
@@ -3622,10 +3633,10 @@ var Funnel = class Funnel {
|
|
|
3622
3633
|
if (!this.memos.localConfig) this.memos.localConfig = new FunnelLocalConfig({ fs: this.fs });
|
|
3623
3634
|
return this.memos.localConfig;
|
|
3624
3635
|
}
|
|
3625
|
-
/**
|
|
3626
|
-
get
|
|
3627
|
-
if (!this.memos.
|
|
3628
|
-
return this.memos.
|
|
3636
|
+
/** Writes the stable `id` into funnel.json on first launch so state can be scoped to `~/.funnel/projects/<id>/`. */
|
|
3637
|
+
get localConfigWriter() {
|
|
3638
|
+
if (!this.memos.localConfigWriter) this.memos.localConfigWriter = new FunnelLocalConfigWriter({ fs: this.fs });
|
|
3639
|
+
return this.memos.localConfigWriter;
|
|
3629
3640
|
}
|
|
3630
3641
|
/** Secret prompter. Defaults to a TTY-only stdin reader; tests inject MemoryFunnelTokenPrompter. */
|
|
3631
3642
|
get tokenPrompter() {
|
|
@@ -3636,7 +3647,6 @@ var Funnel = class Funnel {
|
|
|
3636
3647
|
get localConfigSync() {
|
|
3637
3648
|
if (!this.memos.localConfigSync) this.memos.localConfigSync = new FunnelLocalConfigSync({
|
|
3638
3649
|
channels: this.channels,
|
|
3639
|
-
dotenv: this.dotenv,
|
|
3640
3650
|
prompter: this.tokenPrompter
|
|
3641
3651
|
});
|
|
3642
3652
|
return this.memos.localConfigSync;
|
|
@@ -4032,6 +4042,9 @@ var MemoryFunnelEventLog = class extends FunnelEventLog {
|
|
|
4032
4042
|
for (const event of this.events) if (event.offset > max) max = event.offset;
|
|
4033
4043
|
return max;
|
|
4034
4044
|
}
|
|
4045
|
+
clear() {
|
|
4046
|
+
this.events.length = 0;
|
|
4047
|
+
}
|
|
4035
4048
|
close() {}
|
|
4036
4049
|
};
|
|
4037
4050
|
//#endregion
|
|
@@ -4319,6 +4332,11 @@ var SqliteConnectorDiagnosticLog = class extends ConnectorDiagnosticLog {
|
|
|
4319
4332
|
detail: record.event.detail
|
|
4320
4333
|
}));
|
|
4321
4334
|
}
|
|
4335
|
+
clear() {
|
|
4336
|
+
this.raw.clear();
|
|
4337
|
+
this.processed.clear();
|
|
4338
|
+
this.connection.clear();
|
|
4339
|
+
}
|
|
4322
4340
|
close() {
|
|
4323
4341
|
this.raw.close();
|
|
4324
4342
|
this.processed.close();
|
|
@@ -4435,6 +4453,11 @@ var MemoryConnectorDiagnosticLog = class extends ConnectorDiagnosticLog {
|
|
|
4435
4453
|
return true;
|
|
4436
4454
|
}), query.limit);
|
|
4437
4455
|
}
|
|
4456
|
+
clear() {
|
|
4457
|
+
this.raws.length = 0;
|
|
4458
|
+
this.processeds.length = 0;
|
|
4459
|
+
this.connections.length = 0;
|
|
4460
|
+
}
|
|
4438
4461
|
close() {}
|
|
4439
4462
|
};
|
|
4440
4463
|
const matches$1 = (event, query) => {
|
|
@@ -5079,7 +5102,7 @@ const claudeHandler = factory.createHandlers(zValidator$1("query", z.object({
|
|
|
5079
5102
|
if (local) {
|
|
5080
5103
|
const picked = query.channel !== void 0 ? local.channels.find((c) => c.name === query.channel) : local.channels[0];
|
|
5081
5104
|
if (!picked) throw new HTTPException(404, { message: query.channel ? `channel "${query.channel}" is not declared in funnel.json` : `funnel.json declares no channels` });
|
|
5082
|
-
const synced = await funnel.localConfigSync.ensure(picked
|
|
5105
|
+
const synced = await funnel.localConfigSync.ensure(picked);
|
|
5083
5106
|
for (const outcome of synced.touched) if (outcome.changed) await funnel.listeners.restart(picked.name, outcome.name);
|
|
5084
5107
|
else await funnel.listeners.start(picked.name, outcome.name);
|
|
5085
5108
|
for (const name of synced.removed) await funnel.listeners.stop(picked.name, name);
|
|
@@ -7919,4 +7942,4 @@ async function launchTui(funnel) {
|
|
|
7919
7942
|
});
|
|
7920
7943
|
}
|
|
7921
7944
|
//#endregion
|
|
7922
|
-
export { CONNECTOR_CONNECTION_STATUSES, ConnectorDiagnosticLog, ConnectorDiagnosticSqlReader, DEFAULT_GATEWAY_TOKEN_PATH, FUNNEL_DIR, FUNNEL_MCP_COMMAND, FUNNEL_MCP_NAME, Funnel, FunnelBroadcaster, FunnelChannelPublisher, FunnelChannels, FunnelClaude, FunnelClock, FunnelConnectorFactory, FunnelConnectorListener,
|
|
7945
|
+
export { CONNECTOR_CONNECTION_STATUSES, ConnectorDiagnosticLog, ConnectorDiagnosticSqlReader, DEFAULT_GATEWAY_TOKEN_PATH, FUNNEL_DIR, FUNNEL_MCP_COMMAND, FUNNEL_MCP_NAME, Funnel, FunnelBroadcaster, FunnelChannelPublisher, FunnelChannels, FunnelClaude, FunnelClock, FunnelConnectorFactory, FunnelConnectorListener, FunnelEventLog, FunnelFileSystem, FunnelGateway, FunnelGatewayServer, FunnelGatewayToken, FunnelIdGenerator, FunnelListenerSupervisor, FunnelListenersClient, FunnelLocalConfig, FunnelLocalConfigSync, FunnelLocalConfigWriter, FunnelLogger, FunnelMcp, FunnelProcessRunner, FunnelProfiles, FunnelSettingsReader, FunnelSettingsStore, FunnelSlackEventProcessor, FunnelTokenPrompter, LOCAL_CONFIG_FILENAME, MemoryConnectorDiagnosticLog, MemoryFunnelClock, MemoryFunnelEventLog, MemoryFunnelFileSystem, MemoryFunnelIdGenerator, MemoryFunnelLogger, MemoryFunnelProcessRunner, MemoryFunnelTokenPrompter, MockFunnelSettingsReader, NodeFunnelClock, NodeFunnelFileSystem, NodeFunnelIdGenerator, NodeFunnelLogger, NodeFunnelProcessRunner, NodeFunnelTokenPrompter, NoopFunnelLogger, SETTINGS_PATH, SETTINGS_VERSION, SqliteConnectorDiagnosticLog, SqliteFunnelEventLog, channelConfigSchema, channelDeliveryModeSchema, channelSpecSchema, app as cliApp, connectorConfigSchema, connectorConnectionEventSchema, connectorProcessedEventSchema, connectorRawEventSchema, connectorSpecSchema, createCliApp, createSettings, discordConnectorSchema, factory, funnelEventSchema, funnelJsonSchema, ghConnectorSchema, launchTui, localConfigSchema, profileConfigSchema, profileSpecSchema, publishRequestSchema, publishResponseSchema, queryToCliArgs, resolveFunnelDir, scheduleCatchupPolicySchema, scheduleConnectorSchema, scheduleEntrySchema, settingsSchema, slackConnectorSchema, startChannelServer, toRequest };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as NotifyFn, b as FunnelLogger, o as ConnectorDiagnosticLog, x as FunnelConnectorListener } from "./connector-diagnostic-log-
|
|
1
|
+
import { S as NotifyFn, b as FunnelLogger, o as ConnectorDiagnosticLog, x as FunnelConnectorListener } from "./connector-diagnostic-log-OPpPi9V9.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
4
|
//#region lib/connectors/schedule-connector-schema.d.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as NotifyFn, b as FunnelLogger, o as ConnectorDiagnosticLog, x as FunnelConnectorListener } from "./connector-diagnostic-log-
|
|
1
|
+
import { S as NotifyFn, b as FunnelLogger, o as ConnectorDiagnosticLog, x as FunnelConnectorListener } from "./connector-diagnostic-log-OPpPi9V9.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { App } from "@slack/bolt";
|
|
4
4
|
|
package/funnel.schema.json
CHANGED
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
"$schema": {
|
|
6
6
|
"type": "string"
|
|
7
7
|
},
|
|
8
|
+
"id": {
|
|
9
|
+
"type": "string"
|
|
10
|
+
},
|
|
8
11
|
"channels": {
|
|
9
12
|
"minItems": 1,
|
|
10
13
|
"type": "array",
|
|
@@ -28,26 +31,8 @@
|
|
|
28
31
|
"name": {
|
|
29
32
|
"type": "string"
|
|
30
33
|
},
|
|
31
|
-
"botToken": {
|
|
32
|
-
"type": "string"
|
|
33
|
-
},
|
|
34
|
-
"appToken": {
|
|
35
|
-
"type": "string"
|
|
36
|
-
},
|
|
37
34
|
"minify": {
|
|
38
35
|
"type": "boolean"
|
|
39
|
-
},
|
|
40
|
-
"env": {
|
|
41
|
-
"type": "object",
|
|
42
|
-
"properties": {
|
|
43
|
-
"botToken": {
|
|
44
|
-
"type": "string"
|
|
45
|
-
},
|
|
46
|
-
"appToken": {
|
|
47
|
-
"type": "string"
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
"additionalProperties": false
|
|
51
36
|
}
|
|
52
37
|
},
|
|
53
38
|
"required": [
|
|
@@ -65,18 +50,6 @@
|
|
|
65
50
|
},
|
|
66
51
|
"name": {
|
|
67
52
|
"type": "string"
|
|
68
|
-
},
|
|
69
|
-
"botToken": {
|
|
70
|
-
"type": "string"
|
|
71
|
-
},
|
|
72
|
-
"env": {
|
|
73
|
-
"type": "object",
|
|
74
|
-
"properties": {
|
|
75
|
-
"botToken": {
|
|
76
|
-
"type": "string"
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
"additionalProperties": false
|
|
80
53
|
}
|
|
81
54
|
},
|
|
82
55
|
"required": [
|
package/package.json
CHANGED