@interactive-inc/claude-funnel 0.60.1 → 0.64.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 +2 -2
- package/dist/bin.js +428 -761
- package/dist/{channels-2g_BU1N0.d.ts → channels-CRGb6B5_.d.ts} +17 -16
- package/dist/claude.d.ts +5 -7
- package/dist/claude.js +143 -36
- package/dist/{connector-descriptor-6SXJoszo.d.ts → connector-descriptor-BFIhyTfa.d.ts} +49 -10
- package/dist/connector-diagnostics-recorder-COtNEmUp.js +42 -0
- package/dist/connectors/discord.d.ts +31 -37
- package/dist/connectors/discord.js +3 -3
- package/dist/connectors/gh.d.ts +37 -33
- package/dist/connectors/gh.js +3 -3
- package/dist/connectors/schedule.d.ts +9 -57
- package/dist/connectors/schedule.js +3 -3
- package/dist/connectors/slack.d.ts +106 -132
- package/dist/connectors/slack.js +4 -3
- package/dist/diagnostics.d.ts +1 -1
- package/dist/diagnostics.js +1 -1
- package/dist/discord-connector-DIFkYBbi.js +250 -0
- package/dist/discord-connector-schema-D-bOVAKt.d.ts +22 -0
- package/dist/docs.js +1 -1
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +1 -1
- package/dist/{file-process-guard-C_PLxfUX.d.ts → file-process-guard-tVcgckH6.d.ts} +6 -6
- package/dist/{file-system-o51IsM0W.d.ts → file-system-VhwwXZbm.d.ts} +8 -0
- package/dist/flume-source-listener-BNyAII7N.d.ts +133 -0
- package/dist/{funnel-diagnostics-CSiJmPlZ.js → funnel-diagnostics-Cvk6Sk4x.js} +193 -43
- package/dist/{funnel-diagnostics-DpXOsCty.d.ts → funnel-diagnostics-b9ar0Ing.d.ts} +67 -5
- package/dist/{funnel-docs-BxXZ9Ksx.js → funnel-docs-C-ge0MuB.js} +42 -6
- package/dist/{funnel-doctor-CZf_0Luq.d.ts → funnel-doctor-CnRQi4kM.d.ts} +2 -2
- package/dist/{funnel-doctor-DiJCjHsg.js → funnel-doctor-XrI2GBH8.js} +1 -1
- package/dist/funnel-error-0t1MK1R6.js +75 -0
- package/dist/{funnel-recovery-DnLrdWO9.d.ts → funnel-recovery-CMhY8Jfk.d.ts} +1 -1
- package/dist/gateway/daemon.js +167 -527
- package/dist/gateway.d.ts +3 -3
- package/dist/gateway.js +3 -3
- package/dist/gh-connector-BUGCOEWS.js +187 -0
- package/dist/{gh-connector-schema-Rzwc1c1N.js → gh-connector-schema-CAqIhzGr.js} +7 -0
- package/dist/gh-connector-schema-DWQaB6gX.d.ts +16 -0
- package/dist/{index-CgY8NdMz.d.ts → index-Ds6sHhA-.d.ts} +37 -19
- package/dist/index.d.ts +182 -22
- package/dist/index.js +363 -173
- package/dist/{local-config-json-schema-JyLqOQNX.js → local-config-json-schema-DexV8vX3.js} +24 -4
- package/dist/local-config.d.ts +39 -2
- package/dist/local-config.js +53 -2
- package/dist/logger.js +1 -1
- package/dist/loopback-fetch-CVNuN3YZ.js +40 -0
- package/dist/{local-config-sync-Dh1Croqe.d.ts → memory-token-prompter-BoV8Hf-n.d.ts} +30 -3
- package/dist/node-file-system-BOXIHW_Q.js +174 -0
- package/dist/{profiles-DSzTeKQw.js → profiles-ZHLONml4.js} +49 -49
- package/dist/{profiles-Cy5wXQ0L.d.ts → profiles-cVZQkM69.d.ts} +3 -3
- package/dist/profiles.d.ts +1 -1
- package/dist/profiles.js +1 -1
- package/dist/recovery.d.ts +1 -1
- package/dist/recovery.js +1 -1
- package/dist/resolve-connector-token-DxDG9mhf.js +22 -0
- package/dist/{schedule-connector-L4uzg5M8.js → schedule-connector-9k3gOIgl.js} +54 -55
- package/dist/schedule-connector-schema-Z0RXLgPI.d.ts +49 -0
- package/dist/settings-reader-BNxjsxCB.d.ts +27 -0
- package/dist/{settings-store-CUKSeTXC.js → settings-store-C2QdOH-t.js} +23 -4
- package/dist/slack-connector-CxpWagbT.js +388 -0
- package/dist/slack-event-processor-BhCf5Wiy.d.ts +95 -0
- package/dist/slack-event-processor-xFDG3US0.js +176 -0
- package/dist/slot-fields-D-pvMgTK.js +249 -0
- package/dist/{memory-diagnostic-log-CI60kNfB.js → sqlite-diagnostic-log-DOTPW-tG.js} +373 -249
- package/dist/{yaml-render-93pX7EF7.js → yaml-render--J1_3BSA.js} +25 -21
- package/package.json +2 -4
- package/dist/discord-connector-BL36yvbL.js +0 -250
- package/dist/gateway-base-url-Dy4Ykuoh.js +0 -14
- package/dist/gh-connector-DpiixfQZ.js +0 -226
- package/dist/http-client-oICicjuO.d.ts +0 -18
- package/dist/memory-token-prompter-B4sjyaAq.d.ts +0 -57
- package/dist/memory-token-prompter-CZde7e6y.js +0 -61
- package/dist/node-file-system-Blr8pAir.js +0 -48
- package/dist/settings-reader-BIFB_j2f.d.ts +0 -18
- package/dist/slack-connector-DQIFPdBF.js +0 -484
- package/dist/slot-fields-CMoRpwuy.js +0 -45
- /package/dist/{connector-adapter-DU9Rvyec.js → connector-adapter-Dvs8N7ew.js} +0 -0
- /package/dist/{connector-listener-DR3aKOuK.js → connector-listener-mPGZYa8e.js} +0 -0
- /package/dist/{diagnostic-sql-reader-C9zR-Csp.js → diagnostic-sql-reader-oXZnWFf_.js} +0 -0
- /package/dist/{discord-connector-schema-B_N6IXLz.js → discord-connector-schema-B4YpWpR3.js} +0 -0
- /package/dist/{error-message-of-Byi4y0Uf.js → error-message-of-ColuYmAk.js} +0 -0
- /package/dist/{funnel-log-sqlite-sink-kqJbx2H7.js → funnel-log-sqlite-sink-DLYkY0pZ.js} +0 -0
- /package/dist/{funnel-recovery-BFdPjL6Z.js → funnel-recovery-DKnEutUS.js} +0 -0
- /package/dist/{node-http-client-lowp60Oa.js → node-http-client-u00atiKx.js} +0 -0
- /package/dist/{schedule-connector-schema-CfyuMCMh.js → schedule-connector-schema-DKEPZnVv.js} +0 -0
- /package/dist/{settings-reader-CtQ-Ix8_.js → settings-reader-9FcX3qS1.js} +0 -0
- /package/dist/{settings-schema-D1xcOqRu.d.ts → settings-schema-BL_c2Udm.d.ts} +0 -0
- /package/dist/{slack-connector-schema-C1zEf4TG.js → slack-connector-schema-Dem8to4P.js} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as discordConnectorSchema } from "./discord-connector-schema-
|
|
2
|
-
import { t as ghConnectorSchema } from "./gh-connector-schema-
|
|
3
|
-
import { t as slackConnectorSchema } from "./slack-connector-schema-
|
|
1
|
+
import { t as discordConnectorSchema } from "./discord-connector-schema-B4YpWpR3.js";
|
|
2
|
+
import { t as ghConnectorSchema } from "./gh-connector-schema-CAqIhzGr.js";
|
|
3
|
+
import { t as slackConnectorSchema } from "./slack-connector-schema-Dem8to4P.js";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { stderr, stdin } from "node:process";
|
|
@@ -451,6 +451,26 @@ var NodeFunnelTokenPrompter = class extends FunnelTokenPrompter {
|
|
|
451
451
|
}
|
|
452
452
|
};
|
|
453
453
|
//#endregion
|
|
454
|
+
//#region lib/engine/token-prompter/memory-token-prompter.ts
|
|
455
|
+
/**
|
|
456
|
+
* Pre-seeded answers keyed by prompt label. Tests configure the map up front;
|
|
457
|
+
* unmapped labels throw so the test surfaces unexpected prompts loudly.
|
|
458
|
+
*/
|
|
459
|
+
var MemoryFunnelTokenPrompter = class extends FunnelTokenPrompter {
|
|
460
|
+
answers;
|
|
461
|
+
asked = [];
|
|
462
|
+
constructor(props = {}) {
|
|
463
|
+
super();
|
|
464
|
+
this.answers = new Map(Object.entries(props.answers ?? {}));
|
|
465
|
+
}
|
|
466
|
+
async promptSecret(label) {
|
|
467
|
+
this.asked.push(label);
|
|
468
|
+
const answer = this.answers.get(label);
|
|
469
|
+
if (answer === void 0) throw new Error(`no answer seeded for prompt "${label}"`);
|
|
470
|
+
return answer;
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
//#endregion
|
|
454
474
|
//#region lib/services/local-config/local-config-json-schema.ts
|
|
455
475
|
/**
|
|
456
476
|
* Generates the JSON Schema (draft 2020-12) for `funnel.json`. Useful for
|
|
@@ -466,4 +486,4 @@ const funnelJsonSchema = () => {
|
|
|
466
486
|
};
|
|
467
487
|
};
|
|
468
488
|
//#endregion
|
|
469
|
-
export {
|
|
489
|
+
export { FunnelTokenPrompter as a, channelSpecSchema as c, profileSpecSchema as d, FunnelLocalConfigSync as i, connectorSpecSchema as l, MemoryFunnelTokenPrompter as n, FunnelLocalConfig as o, NodeFunnelTokenPrompter as r, LOCAL_CONFIG_FILENAME as s, funnelJsonSchema as t, localConfigSchema as u };
|
package/dist/local-config.d.ts
CHANGED
|
@@ -1,3 +1,40 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { i as
|
|
1
|
+
import { n as FunnelFileSystem } from "./file-system-VhwwXZbm.js";
|
|
2
|
+
import { a as LocalConfigSyncResult, c as ChannelSpec, d as LocalConfig, f as ProfileSpec, g as profileSpecSchema, h as localConfigSchema, i as FunnelLocalConfigSync, l as ConnectorSpec, m as connectorSpecSchema, n as NodeFunnelTokenPrompter, o as FunnelTokenPrompter, p as channelSpecSchema, r as ConnectorSyncOutcome, s as FunnelLocalConfig, t as MemoryFunnelTokenPrompter, u as LOCAL_CONFIG_FILENAME } from "./memory-token-prompter-BoV8Hf-n.js";
|
|
3
|
+
|
|
4
|
+
//#region lib/services/local-config/local-config-json-schema.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Generates the JSON Schema (draft 2020-12) for `funnel.json`. Useful for
|
|
7
|
+
* `$schema` references in committed `funnel.json` files so editors can give
|
|
8
|
+
* autocomplete and validation for channels[] (transport) and profiles[]
|
|
9
|
+
* (launch recipe) without anyone hand-maintaining a separate schema.
|
|
10
|
+
*/
|
|
11
|
+
declare const funnelJsonSchema: () => Record<string, unknown>;
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region lib/services/local-config/local-config-writer.d.ts
|
|
14
|
+
type Deps = {
|
|
15
|
+
fs: FunnelFileSystem;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* The one path that mutates the repo-committed funnel.json, and it only ever
|
|
19
|
+
* inserts `id`. On first launch a repo has no `id`; funnel generates one and
|
|
20
|
+
* writes it back here so future launches resolve the same `~/.funnel/projects/<id>/`.
|
|
21
|
+
* Idempotent — a no-op once `id` is present. Kept separate from the read-only
|
|
22
|
+
* FunnelLocalConfig so reads stay side-effect free.
|
|
23
|
+
*/
|
|
24
|
+
declare class FunnelLocalConfigWriter {
|
|
25
|
+
private readonly fs;
|
|
26
|
+
constructor(deps: Deps);
|
|
27
|
+
/**
|
|
28
|
+
* Returns the id that ends up persisted in funnel.json. If the file already
|
|
29
|
+
* has an id, the candidate is ignored and the persisted one wins. Otherwise
|
|
30
|
+
* the candidate is written and returned. Returns null when there is no
|
|
31
|
+
* funnel.json (the caller stays on the global ~/.funnel).
|
|
32
|
+
*
|
|
33
|
+
* The read+merge+write runs under an exclusive lock so two concurrent
|
|
34
|
+
* 'fnl claude' launches on the same repo cannot each persist a different
|
|
35
|
+
* generated id and split state across two ~/.funnel/projects/<id>/ dirs.
|
|
36
|
+
*/
|
|
37
|
+
ensureId(cwd: string, candidate: string): string | null;
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
3
40
|
export { ChannelSpec, ConnectorSpec, ConnectorSyncOutcome, FunnelLocalConfig, FunnelLocalConfigSync, FunnelLocalConfigWriter, FunnelTokenPrompter, LOCAL_CONFIG_FILENAME, LocalConfig, LocalConfigSyncResult, MemoryFunnelTokenPrompter, NodeFunnelTokenPrompter, ProfileSpec, channelSpecSchema, connectorSpecSchema, funnelJsonSchema, localConfigSchema, profileSpecSchema };
|
package/dist/local-config.js
CHANGED
|
@@ -1,3 +1,54 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import {
|
|
1
|
+
import { a as FunnelTokenPrompter, c as channelSpecSchema, d as profileSpecSchema, i as FunnelLocalConfigSync, l as connectorSpecSchema, n as MemoryFunnelTokenPrompter, o as FunnelLocalConfig, r as NodeFunnelTokenPrompter, s as LOCAL_CONFIG_FILENAME, t as funnelJsonSchema, u as localConfigSchema } from "./local-config-json-schema-DexV8vX3.js";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
//#region lib/services/local-config/local-config-writer.ts
|
|
4
|
+
const isRecord = (value) => {
|
|
5
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6
|
+
};
|
|
7
|
+
const withIdFirst = (config, id) => {
|
|
8
|
+
const ordered = {};
|
|
9
|
+
if (config.$schema !== void 0) ordered.$schema = config.$schema;
|
|
10
|
+
ordered.id = id;
|
|
11
|
+
for (const key of Object.keys(config)) {
|
|
12
|
+
if (key === "$schema" || key === "id") continue;
|
|
13
|
+
ordered[key] = config[key];
|
|
14
|
+
}
|
|
15
|
+
return ordered;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* The one path that mutates the repo-committed funnel.json, and it only ever
|
|
19
|
+
* inserts `id`. On first launch a repo has no `id`; funnel generates one and
|
|
20
|
+
* writes it back here so future launches resolve the same `~/.funnel/projects/<id>/`.
|
|
21
|
+
* Idempotent — a no-op once `id` is present. Kept separate from the read-only
|
|
22
|
+
* FunnelLocalConfig so reads stay side-effect free.
|
|
23
|
+
*/
|
|
24
|
+
var FunnelLocalConfigWriter = class {
|
|
25
|
+
fs;
|
|
26
|
+
constructor(deps) {
|
|
27
|
+
this.fs = deps.fs;
|
|
28
|
+
Object.freeze(this);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Returns the id that ends up persisted in funnel.json. If the file already
|
|
32
|
+
* has an id, the candidate is ignored and the persisted one wins. Otherwise
|
|
33
|
+
* the candidate is written and returned. Returns null when there is no
|
|
34
|
+
* funnel.json (the caller stays on the global ~/.funnel).
|
|
35
|
+
*
|
|
36
|
+
* The read+merge+write runs under an exclusive lock so two concurrent
|
|
37
|
+
* 'fnl claude' launches on the same repo cannot each persist a different
|
|
38
|
+
* generated id and split state across two ~/.funnel/projects/<id>/ dirs.
|
|
39
|
+
*/
|
|
40
|
+
ensureId(cwd, candidate) {
|
|
41
|
+
const path = join(cwd, LOCAL_CONFIG_FILENAME);
|
|
42
|
+
if (!this.fs.existsSync(path)) return null;
|
|
43
|
+
return this.fs.withFileLock(`${path}.lock`, () => {
|
|
44
|
+
const parsed = JSON.parse(this.fs.readFileSync(path));
|
|
45
|
+
if (!isRecord(parsed)) return null;
|
|
46
|
+
if (typeof parsed.id === "string" && parsed.id !== "") return parsed.id;
|
|
47
|
+
const ordered = withIdFirst(parsed, candidate);
|
|
48
|
+
this.fs.writeFileSync(path, `${JSON.stringify(ordered, null, 2)}\n`);
|
|
49
|
+
return candidate;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
//#endregion
|
|
3
54
|
export { FunnelLocalConfig, FunnelLocalConfigSync, FunnelLocalConfigWriter, FunnelTokenPrompter, LOCAL_CONFIG_FILENAME, MemoryFunnelTokenPrompter, NodeFunnelTokenPrompter, channelSpecSchema, connectorSpecSchema, funnelJsonSchema, localConfigSchema, profileSpecSchema };
|
package/dist/logger.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as FunnelLogSqliteSink } from "./funnel-log-sqlite-sink-
|
|
1
|
+
import { t as FunnelLogSqliteSink } from "./funnel-log-sqlite-sink-DLYkY0pZ.js";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import { appendFileSync, existsSync, mkdirSync, renameSync, statSync, unlinkSync } from "node:fs";
|
|
4
4
|
//#region lib/logger/funnel-log.ts
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//#region lib/engine/http/gateway-base-url.ts
|
|
2
|
+
/**
|
|
3
|
+
* The HTTP base URL of a gateway daemon on the loopback interface. The daemon
|
|
4
|
+
* always binds 127.0.0.1 for its management API (only the WS `/ws` endpoint is
|
|
5
|
+
* ever exposed off-box), so every in-process HTTP client — publisher, listeners
|
|
6
|
+
* client, MCP channel server — talks to it here. Centralizing the construction
|
|
7
|
+
* keeps the host/port shape in one place instead of re-spelling
|
|
8
|
+
* `http://127.0.0.1:${port}` at each call site.
|
|
9
|
+
*/
|
|
10
|
+
function gatewayLoopbackUrl(port) {
|
|
11
|
+
return `http://127.0.0.1:${port}`;
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region lib/engine/http/loopback-fetch.ts
|
|
15
|
+
/**
|
|
16
|
+
* Default ceiling on every loopback request to the gateway daemon. Five
|
|
17
|
+
* seconds is well above the daemon's normal /status latency (microseconds)
|
|
18
|
+
* but short enough that a wedged daemon does not hang the CLI / MCP / SDK
|
|
19
|
+
* caller for any meaningful time.
|
|
20
|
+
*/
|
|
21
|
+
const DEFAULT_LOOPBACK_TIMEOUT_MS = 5e3;
|
|
22
|
+
/**
|
|
23
|
+
* Wraps `fetch` with an automatic abort signal so a wedged gateway daemon
|
|
24
|
+
* cannot hang the caller forever. Composes with a host-supplied
|
|
25
|
+
* `init.signal`: if either the timeout or the host signal aborts, the
|
|
26
|
+
* request is cancelled and `fetch` rejects with an AbortError.
|
|
27
|
+
*
|
|
28
|
+
* Returns the raw `Response` so callers can branch on `res.ok` / parse body
|
|
29
|
+
* however they like — this is the lowest-level wrapper, not a JSON helper.
|
|
30
|
+
*/
|
|
31
|
+
const loopbackFetch = async (url, init = {}, timeoutMs = DEFAULT_LOOPBACK_TIMEOUT_MS) => {
|
|
32
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
33
|
+
const signal = init.signal ? AbortSignal.any([init.signal, timeoutSignal]) : timeoutSignal;
|
|
34
|
+
return fetch(url, {
|
|
35
|
+
...init,
|
|
36
|
+
signal
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
//#endregion
|
|
40
|
+
export { gatewayLoopbackUrl as n, loopbackFetch as t };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as FunnelFileSystem } from "./file-system-
|
|
2
|
-
import { r as FunnelChannels } from "./channels-
|
|
1
|
+
import { n as FunnelFileSystem } from "./file-system-VhwwXZbm.js";
|
|
2
|
+
import { r as FunnelChannels } from "./channels-CRGb6B5_.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
|
|
5
5
|
//#region lib/services/local-config/local-config-schema.d.ts
|
|
@@ -166,4 +166,31 @@ declare class FunnelLocalConfigSync {
|
|
|
166
166
|
private resolveSlot;
|
|
167
167
|
}
|
|
168
168
|
//#endregion
|
|
169
|
-
|
|
169
|
+
//#region lib/engine/token-prompter/node-token-prompter.d.ts
|
|
170
|
+
/**
|
|
171
|
+
* Reads a secret from stdin in raw mode. Echoes a `*` per byte so the user
|
|
172
|
+
* can see progress without exposing the token. Refuses to prompt when stdin
|
|
173
|
+
* is not a TTY — callers should surface the resulting error with a hint
|
|
174
|
+
* pointing at the corresponding env var or CLI command.
|
|
175
|
+
*/
|
|
176
|
+
declare class NodeFunnelTokenPrompter extends FunnelTokenPrompter {
|
|
177
|
+
promptSecret(label: string): Promise<string>;
|
|
178
|
+
private readSecret;
|
|
179
|
+
}
|
|
180
|
+
//#endregion
|
|
181
|
+
//#region lib/engine/token-prompter/memory-token-prompter.d.ts
|
|
182
|
+
type Props = {
|
|
183
|
+
answers?: Record<string, string>;
|
|
184
|
+
};
|
|
185
|
+
/**
|
|
186
|
+
* Pre-seeded answers keyed by prompt label. Tests configure the map up front;
|
|
187
|
+
* unmapped labels throw so the test surfaces unexpected prompts loudly.
|
|
188
|
+
*/
|
|
189
|
+
declare class MemoryFunnelTokenPrompter extends FunnelTokenPrompter {
|
|
190
|
+
private readonly answers;
|
|
191
|
+
readonly asked: string[];
|
|
192
|
+
constructor(props?: Props);
|
|
193
|
+
promptSecret(label: string): Promise<string>;
|
|
194
|
+
}
|
|
195
|
+
//#endregion
|
|
196
|
+
export { LocalConfigSyncResult as a, ChannelSpec as c, LocalConfig as d, ProfileSpec as f, profileSpecSchema as g, localConfigSchema as h, FunnelLocalConfigSync as i, ConnectorSpec as l, connectorSpecSchema as m, NodeFunnelTokenPrompter as n, FunnelTokenPrompter as o, channelSpecSchema as p, ConnectorSyncOutcome as r, FunnelLocalConfig as s, MemoryFunnelTokenPrompter as t, LOCAL_CONFIG_FILENAME as u };
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { t as FunnelFileSystem } from "./file-system-Wvzc2ePY.js";
|
|
2
|
+
import { basename, dirname } from "node:path";
|
|
3
|
+
import { appendFileSync, chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
|
+
//#region lib/engine/fs/node-file-system.ts
|
|
5
|
+
const SECRET_MODE = 384;
|
|
6
|
+
/**
|
|
7
|
+
* Random suffix for the temp file used by the atomic write path. Cannot use
|
|
8
|
+
* crypto.randomUUID directly because Bun spec restricts where it works; a
|
|
9
|
+
* pid+counter pair is enough for in-process uniqueness, which is all we need
|
|
10
|
+
* (the temp file lives at most a few ms before rename).
|
|
11
|
+
*/
|
|
12
|
+
let tempCounter = 0;
|
|
13
|
+
const nextTempSuffix = () => {
|
|
14
|
+
tempCounter = tempCounter + 1 | 0;
|
|
15
|
+
return `${process.pid}-${tempCounter}-${Math.floor(Math.random() * 1e9)}`;
|
|
16
|
+
};
|
|
17
|
+
var NodeFunnelFileSystem = class extends FunnelFileSystem {
|
|
18
|
+
constructor() {
|
|
19
|
+
super();
|
|
20
|
+
Object.freeze(this);
|
|
21
|
+
}
|
|
22
|
+
existsSync(path) {
|
|
23
|
+
return existsSync(path);
|
|
24
|
+
}
|
|
25
|
+
readFileSync(path) {
|
|
26
|
+
return readFileSync(path, "utf-8");
|
|
27
|
+
}
|
|
28
|
+
writeFileSync(path, data) {
|
|
29
|
+
atomicWrite(path, data, null);
|
|
30
|
+
}
|
|
31
|
+
writeSecretFileSync(path, data) {
|
|
32
|
+
atomicWrite(path, data, SECRET_MODE);
|
|
33
|
+
}
|
|
34
|
+
appendFileSync(path, data) {
|
|
35
|
+
appendFileSync(path, data);
|
|
36
|
+
}
|
|
37
|
+
unlink(path) {
|
|
38
|
+
try {
|
|
39
|
+
unlinkSync(path);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (isErrnoCode(error, "ENOENT")) return;
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
mkdirSync(path, options) {
|
|
46
|
+
mkdirSync(path, { recursive: options?.recursive ?? false });
|
|
47
|
+
}
|
|
48
|
+
readdirSync(path) {
|
|
49
|
+
return readdirSync(path);
|
|
50
|
+
}
|
|
51
|
+
statSync(path) {
|
|
52
|
+
const stat = statSync(path);
|
|
53
|
+
return {
|
|
54
|
+
mtimeMs: stat.mtimeMs,
|
|
55
|
+
mode: stat.mode & 511
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
withFileLock(lockPath, fn) {
|
|
59
|
+
const fd = acquireLock(lockPath);
|
|
60
|
+
try {
|
|
61
|
+
return fn();
|
|
62
|
+
} finally {
|
|
63
|
+
try {
|
|
64
|
+
closeSync(fd);
|
|
65
|
+
} catch {}
|
|
66
|
+
try {
|
|
67
|
+
unlinkSync(lockPath);
|
|
68
|
+
} catch {}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
const LOCK_RETRY_BASE_MS = 10;
|
|
73
|
+
const LOCK_RETRY_MAX_MS = 100;
|
|
74
|
+
const LOCK_TIMEOUT_MS = 5e3;
|
|
75
|
+
const LOCK_STALE_AFTER_MS = 3e4;
|
|
76
|
+
/**
|
|
77
|
+
* Acquire an exclusive lock by atomically creating `lockPath` (`O_EXCL`).
|
|
78
|
+
* Retries with bounded backoff up to LOCK_TIMEOUT_MS. If the existing lock
|
|
79
|
+
* file is older than LOCK_STALE_AFTER_MS or owned by a dead pid, break it
|
|
80
|
+
* and try again. The pid is written to the lock file so the staleness check
|
|
81
|
+
* can be precise (mtime alone is fooled by clock jumps).
|
|
82
|
+
*/
|
|
83
|
+
const acquireLock = (lockPath) => {
|
|
84
|
+
const deadline = performance.now() + LOCK_TIMEOUT_MS;
|
|
85
|
+
let attempt = 0;
|
|
86
|
+
while (true) try {
|
|
87
|
+
const fd = openSync(lockPath, "wx", 384);
|
|
88
|
+
writeFileSync(fd, String(process.pid));
|
|
89
|
+
return fd;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (!isErrnoCode(error, "EEXIST")) throw error;
|
|
92
|
+
if (performance.now() >= deadline) throw new Error(`failed to acquire file lock ${lockPath} within ${LOCK_TIMEOUT_MS}ms`);
|
|
93
|
+
breakIfStale(lockPath);
|
|
94
|
+
sleepSyncMs(Math.min(LOCK_RETRY_MAX_MS, LOCK_RETRY_BASE_MS * 2 ** Math.min(attempt, 4)));
|
|
95
|
+
attempt = attempt + 1;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
const breakIfStale = (lockPath) => {
|
|
99
|
+
let pid;
|
|
100
|
+
let mtimeMs;
|
|
101
|
+
try {
|
|
102
|
+
mtimeMs = statSync(lockPath).mtimeMs;
|
|
103
|
+
pid = Number(readFileSync(lockPath, "utf-8").trim());
|
|
104
|
+
} catch {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (Date.now() - mtimeMs > LOCK_STALE_AFTER_MS) {
|
|
108
|
+
try {
|
|
109
|
+
unlinkSync(lockPath);
|
|
110
|
+
} catch {}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (pid > 0 && !isPidAlive(pid)) try {
|
|
114
|
+
unlinkSync(lockPath);
|
|
115
|
+
} catch {}
|
|
116
|
+
};
|
|
117
|
+
const isPidAlive = (pid) => {
|
|
118
|
+
try {
|
|
119
|
+
process.kill(pid, 0);
|
|
120
|
+
return true;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (isErrnoCode(error, "EPERM")) return true;
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Sleep synchronously for `ms` by spinning on Atomics.wait against a private
|
|
128
|
+
* SharedArrayBuffer. Required because the lock acquisition path is itself
|
|
129
|
+
* synchronous (every settings-mutating call site is sync) and cannot await.
|
|
130
|
+
* The spin is bounded by LOCK_RETRY_MAX_MS so total wall time stays low.
|
|
131
|
+
*/
|
|
132
|
+
const sleepSyncMs = (ms) => {
|
|
133
|
+
const sab = new SharedArrayBuffer(4);
|
|
134
|
+
const view = new Int32Array(sab);
|
|
135
|
+
Atomics.wait(view, 0, 0, ms);
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* Narrow `unknown` to a Node errno-typed error and check whether its `code`
|
|
139
|
+
* matches the expected value. Avoids `as NodeJS.ErrnoException` casts at
|
|
140
|
+
* each call site while still letting callers distinguish ENOENT / EACCES /
|
|
141
|
+
* etc. without falling back to message-string matching.
|
|
142
|
+
*/
|
|
143
|
+
const isErrnoCode = (error, code) => {
|
|
144
|
+
if (!(error instanceof Error)) return false;
|
|
145
|
+
if (!("code" in error)) return false;
|
|
146
|
+
return error.code === code;
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* Atomic write via temp + rename. `rename(2)` is atomic on POSIX when source
|
|
150
|
+
* and target share a filesystem, which is guaranteed because the temp file
|
|
151
|
+
* lives in the same directory as the target. A failed write unlinks the
|
|
152
|
+
* temp file so we do not leak `.foo.json.<pid>.tmp` leftovers.
|
|
153
|
+
*
|
|
154
|
+
* `mode` controls the perm bits on both temp and final file. Pass `null` for
|
|
155
|
+
* the OS default (umask-derived), or `0o600` for secret-bearing files.
|
|
156
|
+
*/
|
|
157
|
+
const atomicWrite = (path, data, mode) => {
|
|
158
|
+
const tempPath = `${dirname(path)}/.${basename(path)}.${nextTempSuffix()}.tmp`;
|
|
159
|
+
const writeOptions = mode === null ? void 0 : { mode };
|
|
160
|
+
try {
|
|
161
|
+
writeFileSync(tempPath, data, writeOptions);
|
|
162
|
+
if (mode !== null) try {
|
|
163
|
+
chmodSync(tempPath, mode);
|
|
164
|
+
} catch {}
|
|
165
|
+
renameSync(tempPath, path);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
try {
|
|
168
|
+
unlinkSync(tempPath);
|
|
169
|
+
} catch {}
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
//#endregion
|
|
174
|
+
export { NodeFunnelFileSystem as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as NodeFunnelFileSystem } from "./node-file-system-
|
|
1
|
+
import { t as NodeFunnelFileSystem } from "./node-file-system-BOXIHW_Q.js";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
//#region lib/engine/profiles/profiles.ts
|
|
@@ -43,44 +43,44 @@ var FunnelProfiles = class {
|
|
|
43
43
|
return this.list()[0] ?? null;
|
|
44
44
|
}
|
|
45
45
|
add(input) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
46
|
+
this.store.update((settings) => {
|
|
47
|
+
if (settings.profiles.some((p) => p.name === input.name)) throw new Error(`profile "${input.name}" already exists`);
|
|
48
|
+
if (!settings.channels.some((c) => c.id === input.channelId)) throw new Error(`channel id "${input.channelId}" not found`);
|
|
49
|
+
settings.profiles.push({
|
|
50
|
+
id: this.idGenerator.generate(),
|
|
51
|
+
name: input.name,
|
|
52
|
+
path: input.path,
|
|
53
|
+
channelId: input.channelId,
|
|
54
|
+
options: input.options ?? [],
|
|
55
|
+
env: input.env ?? {},
|
|
56
|
+
resume: input.resume ?? true
|
|
57
|
+
});
|
|
57
58
|
});
|
|
58
|
-
this.store.write(settings);
|
|
59
59
|
}
|
|
60
60
|
remove(name) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
this.store.update((settings) => {
|
|
62
|
+
const index = settings.profiles.findIndex((p) => p.name === name);
|
|
63
|
+
if (index < 0) throw new Error(`profile "${name}" not found`);
|
|
64
|
+
settings.profiles.splice(index, 1);
|
|
65
|
+
});
|
|
66
66
|
}
|
|
67
67
|
rename(oldName, newName) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
this.store.update((settings) => {
|
|
69
|
+
const profile = settings.profiles.find((p) => p.name === oldName);
|
|
70
|
+
if (!profile) throw new Error(`profile "${oldName}" not found`);
|
|
71
|
+
if (settings.profiles.some((p) => p.name === newName)) throw new Error(`profile "${newName}" already exists`);
|
|
72
|
+
profile.name = newName;
|
|
73
|
+
});
|
|
74
74
|
}
|
|
75
75
|
asDefault(name) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
76
|
+
this.store.update((settings) => {
|
|
77
|
+
const index = settings.profiles.findIndex((p) => p.name === name);
|
|
78
|
+
if (index < 0) throw new Error(`profile "${name}" not found`);
|
|
79
|
+
if (index === 0) return;
|
|
80
|
+
const [profile] = settings.profiles.splice(index, 1);
|
|
81
|
+
if (!profile) return;
|
|
82
|
+
settings.profiles.unshift(profile);
|
|
83
|
+
});
|
|
84
84
|
}
|
|
85
85
|
hasChannelRef(channelId) {
|
|
86
86
|
return this.store.read().profiles.some((p) => p.channelId === channelId);
|
|
@@ -91,11 +91,11 @@ var FunnelProfiles = class {
|
|
|
91
91
|
}
|
|
92
92
|
/** Records the claude session id this profile launched, overwriting any prior one. */
|
|
93
93
|
setSessionId(id, sessionId) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
this.store.update((settings) => {
|
|
95
|
+
const profile = settings.profiles.find((p) => p.id === id);
|
|
96
|
+
if (!profile) throw new Error(`profile id "${id}" not found`);
|
|
97
|
+
profile.sessionId = sessionId;
|
|
98
|
+
});
|
|
99
99
|
}
|
|
100
100
|
/**
|
|
101
101
|
* Mirrors claude's session storage path
|
|
@@ -111,18 +111,18 @@ var FunnelProfiles = class {
|
|
|
111
111
|
return this.fs.readFileSync(path).trim().length > 0;
|
|
112
112
|
}
|
|
113
113
|
update(name, fields) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
114
|
+
this.store.update((settings) => {
|
|
115
|
+
const profile = settings.profiles.find((p) => p.name === name);
|
|
116
|
+
if (!profile) throw new Error(`profile "${name}" not found`);
|
|
117
|
+
if (fields.channelId !== void 0) {
|
|
118
|
+
if (!settings.channels.some((c) => c.id === fields.channelId)) throw new Error(`channel id "${fields.channelId}" not found`);
|
|
119
|
+
profile.channelId = fields.channelId;
|
|
120
|
+
}
|
|
121
|
+
if (fields.path !== void 0) profile.path = fields.path;
|
|
122
|
+
if (fields.options !== void 0) profile.options = fields.options;
|
|
123
|
+
if (fields.env !== void 0) profile.env = fields.env;
|
|
124
|
+
if (fields.resume !== void 0) profile.resume = fields.resume;
|
|
125
|
+
});
|
|
126
126
|
}
|
|
127
127
|
};
|
|
128
128
|
//#endregion
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { r as ProfileConfig } from "./settings-schema-
|
|
2
|
-
import { n as FunnelIdGenerator, t as FunnelSettingsReader } from "./settings-reader-
|
|
3
|
-
import { n as FunnelFileSystem } from "./file-system-
|
|
1
|
+
import { r as ProfileConfig } from "./settings-schema-BL_c2Udm.js";
|
|
2
|
+
import { n as FunnelIdGenerator, t as FunnelSettingsReader } from "./settings-reader-BNxjsxCB.js";
|
|
3
|
+
import { n as FunnelFileSystem } from "./file-system-VhwwXZbm.js";
|
|
4
4
|
|
|
5
5
|
//#region lib/engine/profiles/profiles.d.ts
|
|
6
6
|
type Deps = {
|
package/dist/profiles.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as FunnelProfiles } from "./profiles-
|
|
1
|
+
import { t as FunnelProfiles } from "./profiles-cVZQkM69.js";
|
|
2
2
|
export { FunnelProfiles };
|
package/dist/profiles.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as FunnelProfiles } from "./profiles-
|
|
1
|
+
import { t as FunnelProfiles } from "./profiles-ZHLONml4.js";
|
|
2
2
|
export { FunnelProfiles };
|
package/dist/recovery.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as RecoveryListenerControl, i as RecoveryGatewayControl, n as RecoveryAction, o as RecoveryResult, r as RecoveryChannelSource, t as FunnelRecovery } from "./funnel-recovery-
|
|
1
|
+
import { a as RecoveryListenerControl, i as RecoveryGatewayControl, n as RecoveryAction, o as RecoveryResult, r as RecoveryChannelSource, t as FunnelRecovery } from "./funnel-recovery-CMhY8Jfk.js";
|
|
2
2
|
export { FunnelRecovery, RecoveryAction, RecoveryChannelSource, RecoveryGatewayControl, RecoveryListenerControl, RecoveryResult };
|
package/dist/recovery.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as FunnelRecovery } from "./funnel-recovery-
|
|
1
|
+
import { t as FunnelRecovery } from "./funnel-recovery-DKnEutUS.js";
|
|
2
2
|
export { FunnelRecovery };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region lib/engine/connectors/resolve-connector-token.ts
|
|
2
|
+
/**
|
|
3
|
+
* Resolves a connector token from either a literal value or the name of an env
|
|
4
|
+
* var. A connector config carries one or the other per slot (see
|
|
5
|
+
* slack-connector-schema): literals are inlined into settings.json, references
|
|
6
|
+
* keep the secret in `process.env` and out of settings.json.
|
|
7
|
+
*
|
|
8
|
+
* Errors loudly when neither yields a value — a misconfigured connector should
|
|
9
|
+
* fail at listener start, not connect with an empty token and silently never
|
|
10
|
+
* receive events.
|
|
11
|
+
*/
|
|
12
|
+
const resolveConnectorToken = (props) => {
|
|
13
|
+
if (props.literal !== void 0 && props.literal !== "") return props.literal;
|
|
14
|
+
if (props.envVar !== void 0 && props.envVar !== "") {
|
|
15
|
+
const fromEnv = props.env[props.envVar];
|
|
16
|
+
if (fromEnv !== void 0 && fromEnv !== "") return fromEnv;
|
|
17
|
+
throw new Error(`${props.label} references env var "${props.envVar}" but it is not set in the environment`);
|
|
18
|
+
}
|
|
19
|
+
throw new Error(`${props.label} has neither a literal token nor an env var reference`);
|
|
20
|
+
};
|
|
21
|
+
//#endregion
|
|
22
|
+
export { resolveConnectorToken as t };
|