@interactive-inc/claude-funnel 0.36.0 → 0.38.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/index.d.ts CHANGED
@@ -4,11 +4,13 @@ import { S as NotifyFn, _ as connectorConnectionEventSchema, a as ConnectorConne
4
4
  import { a as FunnelProcessRunner, c as RunResult, i as DetachOptions, n as ghConnectorSchema, o as ProcessSnapshot, r as AttachOptions, s as RunOptions, t as GhConnectorConfig } from "./gh-connector-schema-CQmEWzdV.js";
5
5
  import { a as FunnelFileSystem, c as ScheduleEntry, d as scheduleEntrySchema, i as FileStat, l as scheduleCatchupPolicySchema, n as ScheduleOnFired, o as ScheduleCatchupPolicy, s as ScheduleConnectorConfig, u as scheduleConnectorSchema } from "./schedule-listener-3M6WkH1Y.js";
6
6
  import { a as SlackProcessed, c as SlackRawEvent, d as slackConnectorSchema, i as FunnelSlackEventProcessor, l as SlackSkipReason, n as SlackOnAppCreated, o as SlackProcessedEmit, r as SlackPreprocessEvent, s as SlackProcessedSkip, u as SlackConnectorConfig } from "./slack-listener-9UdAn_ui.js";
7
+ import { hc } from "hono/client";
7
8
  import { z } from "zod";
8
9
  import * as _$hono_factory0 from "hono/factory";
9
10
  import { Hono } from "hono";
10
11
  import { Server, ServerWebSocket } from "bun";
11
12
  import * as _$hono_utils_http_status0 from "hono/utils/http-status";
13
+ import * as _$hono_utils_types0 from "hono/utils/types";
12
14
  import * as _$hono_hono_base0 from "hono/hono-base";
13
15
 
14
16
  //#region lib/connectors/connector-config-schema.d.ts
@@ -386,696 +388,1014 @@ declare class FunnelChannels {
386
388
  private assertNoTokenCollision;
387
389
  }
388
390
  //#endregion
389
- //#region lib/engine/claude/gateway-controller.d.ts
390
- type GatewayController = {
391
- isRunning(): boolean;
392
- start(options?: {
393
- caffeinate?: boolean;
394
- }): Promise<boolean>;
395
- };
396
- //#endregion
397
- //#region lib/engine/mcp/mcp.d.ts
398
- declare const FUNNEL_MCP_COMMAND = "bun";
399
- declare const FUNNEL_MCP_ARGS: string[];
400
- declare const FUNNEL_MCP_NAME = "funnel";
401
- type Deps$13 = {
402
- fs?: FunnelFileSystem;
403
- };
391
+ //#region lib/engine/error/on-funnel-error.d.ts
404
392
  /**
405
- * Installs/uninstalls the funnel MCP entry into a target repository's
406
- * `.mcp.json`. Detects an existing entry by command match so renaming is
407
- * preserved across re-installs.
393
+ * Host integration hook called when Funnel catches an exception that would
394
+ * otherwise be silently swallowed (subscriber throw, listener start failure,
395
+ * MCP forward failure, etc.). Pass `Sentry.captureException` from the host to
396
+ * pipe these into your error reporter. Defaults to a no-op when omitted.
397
+ *
398
+ * `context` carries the component name and any extra metadata the caller had
399
+ * at the catch site (channel / connector / subscriber id when available).
408
400
  */
409
- declare class FunnelMcp {
410
- private readonly fs;
411
- constructor(deps?: Deps$13);
412
- install(repoPath: string): void;
413
- uninstall(repoPath: string): void;
414
- findInstalledName(cwd: string): string | null;
415
- private findServerName;
416
- private isFunnelEntry;
417
- private readConfig;
418
- private writeConfig;
419
- }
401
+ type OnFunnelError = (error: Error, context?: Record<string, unknown>) => void;
420
402
  //#endregion
421
- //#region lib/engine/profiles/profiles.d.ts
422
- type Deps$12 = {
423
- store: FunnelSettingsReader;
424
- idGenerator: FunnelIdGenerator;
403
+ //#region lib/gateway/broadcaster.d.ts
404
+ type ClientData = {
405
+ /** Stable channel id (uuid) that the WS client subscribed to. */channel: string; /** Human-facing channel name resolved at upgrade time, kept for log readability. */
406
+ channelName?: string | null; /** Connector names belonging to that channel; used by tap-all replay filtering. */
407
+ connectors: string[];
408
+ tapAll?: boolean; /** Routing mode resolved from channel config at upgrade time. Defaults to fanout. */
409
+ delivery?: "fanout" | "exclusive";
410
+ };
411
+ type BroadcastEvent = {
412
+ content: string;
413
+ meta?: Record<string, string>;
414
+ };
415
+ type ReplayableEvent = BroadcastEvent & {
416
+ offset: number;
425
417
  };
418
+ type BroadcastSubscriber = (event: ReplayableEvent) => void;
426
419
  /**
427
- * Named launch presets for `fnl claude`. Each profile bundles a working
428
- * directory, the channel id its Claude instance subscribes to, and the launch
429
- * recipe (`options` prepended to the claude argv, `env` layered under the
430
- * process, `resume` toggling session reuse). Implements ProfileChannelChecker
431
- * so FunnelChannels can refuse to remove a channel that is still referenced.
432
- *
433
- * Each profile has a stable `id` (uuid) minted at `add`. That id is the unit
434
- * everything internal keys on — the PID file, the resumable session id — so a
435
- * rename never strands either. `name` is purely the CLI/TUI handle; the CRUD
436
- * methods here take it because that is what the user types, but resolve to the
437
- * id before touching id-keyed state. The first array entry is the default
438
- * profile; `asDefault` reorders to put one first.
439
- *
440
- * `channelId` always stores the channel's stable id (uuid). CLI surfaces
441
- * resolve channel name → id before calling `add`/`update` here.
420
+ * Optional persistent replay source. Wired in by the gateway-server with a
421
+ * `FunnelEventLog` (SQLite-backed by default) so reconnects across daemon
422
+ * restarts can recover events older than the in-memory buffer via an indexed
423
+ * `seq > since` range scan.
442
424
  */
443
- declare class FunnelProfiles {
444
- private readonly store;
445
- private readonly idGenerator;
446
- constructor(deps: Deps$12);
447
- list(): ProfileConfig[];
448
- get(name: string): ProfileConfig | null;
449
- getById(id: string): ProfileConfig | null;
450
- getDefault(): ProfileConfig | null;
451
- add(input: {
452
- name: string;
453
- path: string;
454
- channelId: string;
455
- options?: string[];
456
- env?: Record<string, string>;
457
- resume?: boolean;
458
- }): void;
459
- remove(name: string): void;
460
- rename(oldName: string, newName: string): void;
461
- asDefault(name: string): void;
462
- hasChannelRef(channelId: string): boolean;
463
- /** Resumable claude session id last launched by this profile (by id), or null. */
464
- getSessionId(id: string): string | null;
465
- /** Records the claude session id this profile launched, overwriting any prior one. */
466
- setSessionId(id: string, sessionId: string): void;
467
- update(name: string, fields: Partial<Omit<ProfileConfig, "name">>): void;
468
- }
469
- //#endregion
470
- //#region lib/engine/claude/claude.d.ts
471
- type LaunchOptions = {
472
- channel: string;
473
- cwd?: string;
474
- userArgs?: string[];
475
- /** Stable id of the launching profile (uuid). Keys the singleton PID file and
476
- * the resumable session. Absent for a profile-less launch (raw `--channel`),
477
- * which never enforces singleton-ness and never resumes. */
478
- profileId?: string; /** Args prepended to the claude argv (typically a profile's recipe). Defaults to none. */
479
- options?: string[]; /** Env vars layered under the launched claude process. process.env wins on collision. */
480
- env?: Record<string, string>;
481
- /** Whether to inject a `--session-id`/`--resume` for this profile.
482
- * Defaults to false: resuming is opt-in and only meaningful for a profile,
483
- * since the persisted session is owned by the profile (by id). A launch
484
- * without a profile always starts a fresh session regardless of this flag. */
485
- resume?: boolean;
486
- /** Invoked synchronously after the child claude process has been spawned, with its PID.
487
- * Useful for hosts that need to register the spawned process before it exits
488
- * (e.g. multi-session registries that track per-claude liveness). */
489
- onSpawned?: (pid: number) => void;
490
- /** Whether to install the funnel MCP entry into `.mcp.json` (default: true).
491
- * Set to false when the host already provides its own MCP server entry and
492
- * does not need the funnel binary as an MCP endpoint. */
493
- installMcp?: boolean;
425
+ type ReplaySource = {
426
+ loadSince(since: number): ReplayableEvent[];
494
427
  };
495
- type Deps$11 = {
496
- channels: FunnelChannels;
497
- mcp: FunnelMcp;
498
- gateway: GatewayController;
499
- profiles: FunnelProfiles;
500
- process?: FunnelProcessRunner;
501
- fs?: FunnelFileSystem;
502
- idGenerator?: FunnelIdGenerator;
503
- logger?: FunnelLogger;
504
- dir?: string;
428
+ type Deps$13 = {
429
+ logger?: FunnelLogger; /** Host hook for surfacing subscriber-throw exceptions. Defaults to no-op. */
430
+ onError?: OnFunnelError;
431
+ maxBufferedBytes?: number;
432
+ now?: () => number; /** Number of recent events kept in the in-memory replay buffer. */
433
+ replayBufferSize?: number; /** Hard byte cap on replay buffer payloads. Older events are evicted FIFO until under this cap. */
434
+ replayBufferMaxBytes?: number; /** Persistent replay source consulted when the in-memory buffer cannot satisfy `since`. */
435
+ persistentReplay?: ReplaySource;
436
+ };
437
+ type BroadcasterMetrics = {
438
+ clients: number;
439
+ subscribers: number;
440
+ eventsBroadcast: number;
441
+ droppedSlowClients: number;
442
+ lastBroadcastAt: string | null; /** Latest emitted offset. Clients can `?since=<offset>` to ask for events strictly after this point. */
443
+ latestOffset: number; /** Oldest offset still held in the replay buffer. Older values cannot be replayed and trigger a full resync. */
444
+ oldestReplayableOffset: number | null;
505
445
  };
506
446
  /**
507
- * Launches Claude Code with funnel pre-wired: ensures the gateway is running,
508
- * installs the funnel MCP into the target repo's `.mcp.json` if missing,
509
- * injects `FUNNEL_CHANNEL_ID` into the child env, and writes a per-profile
510
- * PID file to enforce singleton launches.
447
+ * In-process pub/sub for connector events.
448
+ *
449
+ * Two outbound paths:
450
+ * - WS clients connected via the gateway's `/ws` endpoint, scoped per channel
451
+ * - In-process subscribers registered via `subscribe()` (programmable API)
452
+ *
453
+ * Backpressure: if a WS client's `bufferedAmount` exceeds `maxBufferedBytes`
454
+ * (default 1 MiB), the client is closed with code 1009 and dropped from the
455
+ * registry to keep one slow consumer from blocking the daemon.
456
+ *
457
+ * Replay: every emitted event gets a strictly increasing `offset`. The latest
458
+ * `replayBufferSize` events are kept in memory; reconnecting WS clients can
459
+ * pass `?since=<offset>` and the broadcaster resends matching events before
460
+ * resuming the live stream. The in-memory ring covers short reconnects;
461
+ * older history is served from the event log wired in as `persistentReplay`.
511
462
  */
512
- declare class FunnelClaude {
513
- private readonly channels;
514
- private readonly mcp;
515
- private readonly gateway;
516
- private readonly profiles;
517
- private readonly process;
518
- private readonly fs;
519
- private readonly idGenerator;
463
+ declare class FunnelBroadcaster {
464
+ private readonly clients;
465
+ private readonly subscribers;
520
466
  private readonly logger;
521
- private readonly pidDir;
522
- constructor(deps: Deps$11);
523
- launch(options: LaunchOptions): Promise<number>;
524
- isRunning(profileId: string): boolean;
525
- private pidPath;
526
- private readPid;
527
- private writePidFile;
528
- private removePidFile;
529
- private installCleanup;
530
- private isProcessAlive;
531
- private buildArgs;
467
+ private readonly onError;
468
+ private readonly maxBufferedBytes;
469
+ private readonly now;
470
+ private readonly replayBufferSize;
471
+ private readonly replayBufferMaxBytes;
472
+ private readonly replayBuffer;
473
+ private readonly persistentReplay;
474
+ private readonly exclusiveCursor;
475
+ private replayBufferBytes;
476
+ private eventsBroadcast;
477
+ private droppedSlowClients;
478
+ private lastBroadcastAt;
479
+ private latestOffset;
480
+ constructor(deps?: Deps$13);
481
+ getMetrics(): BroadcasterMetrics;
532
482
  /**
533
- * Decides whether funnel should resume an existing claude session or start
534
- * a freshly minted one. Backs off when the user already passed a
535
- * session-shaping flag, since combining them would either confuse claude
536
- * or override the explicit user intent.
483
+ * Returns events with offset > since, filtered by the connector subscription
484
+ * rules of `data`. Used at WS upgrade time when the client passes `?since=<offset>`.
537
485
  *
538
- * The session is owned by the profile (by id), not by cwd: two profiles
539
- * pointing at the same repo each keep their own conversation, and a launch
540
- * with no profile never resumes so an unrelated session in the same repo
541
- * can't bleed in. The channel never enters into it; sessions belong to the
542
- * launch layer (profiles), keeping the transport layer ignorant of them.
486
+ * Two-tier lookup:
487
+ * 1. The in-memory ring buffer (covers short reconnects, last `replayBufferSize` events).
488
+ * 2. If `since` predates the oldest in-memory entry and a persistent replay source
489
+ * is wired in (SQLite by default), the gap is filled from it. This covers reconnects
490
+ * across daemon restarts where the in-memory buffer was lost.
543
491
  *
544
- * A persisted id is only resumed when its session jsonl still exists on
545
- * disk. claude errors out on `--resume <id>` for a missing conversation, and
546
- * a persisted id can outlive its jsonl (claude pruned it, or the very first
547
- * launch was aborted after the id was written but before the jsonl
548
- * appeared). When the file is gone we mint a fresh session instead, which
549
- * overwrites the dangling entry — so the store self-heals.
492
+ * Result is sorted ascending by offset and de-duplicated against the in-memory buffer.
550
493
  */
551
- private resolveSession;
494
+ replaySince(since: number, data: ClientData): ReplayableEvent[];
495
+ private matchesClient;
552
496
  /**
553
- * Mirrors claude's session storage path
554
- * (`<config-dir>/projects/<cwd-with-slashes-as-dashes>/<id>.jsonl`) to check
555
- * whether a recorded session still exists AND is non-empty. Reads the same
556
- * `CLAUDE_CONFIG_DIR` the child will run under so the check matches reality; a
557
- * wrong guess can only ever produce a false negative (start fresh), never a
558
- * bad resume.
497
+ * Returns the list of WS clients that should receive `event`. Tap=all clients always
498
+ * receive (passive observation). For each per-channel group:
499
+ * - fanout every matching client receives
500
+ * - exclusive exactly one client receives, picked round-robin per channel
559
501
  */
560
- private sessionFileExists;
561
- private buildEnv;
562
- }
563
- //#endregion
564
- //#region lib/engine/error/on-funnel-error.d.ts
565
- /**
566
- * Host integration hook called when Funnel catches an exception that would
567
- * otherwise be silently swallowed (subscriber throw, listener start failure,
568
- * MCP forward failure, etc.). Pass `Sentry.captureException` from the host to
569
- * pipe these into your error reporter. Defaults to a no-op when omitted.
570
- *
571
- * `context` carries the component name and any extra metadata the caller had
572
- * at the catch site (channel / connector / subscriber id when available).
573
- */
574
- type OnFunnelError = (error: Error, context?: Record<string, unknown>) => void;
575
- //#endregion
576
- //#region lib/engine/local-config/local-config-schema.d.ts
577
- declare const connectorSpecSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
578
- type: z.ZodLiteral<"slack">;
579
- name: z.ZodString;
580
- minify: z.ZodOptional<z.ZodBoolean>;
581
- }, z.core.$strip>, z.ZodObject<{
582
- type: z.ZodLiteral<"discord">;
583
- name: z.ZodString;
584
- }, z.core.$strip>, z.ZodObject<{
585
- type: z.ZodLiteral<"gh">;
586
- name: z.ZodString;
587
- pollInterval: z.ZodOptional<z.ZodNumber>;
588
- }, z.core.$strip>, z.ZodObject<{
589
- type: z.ZodLiteral<"schedule">;
590
- name: z.ZodString;
591
- }, z.core.$strip>], "type">;
592
- type ConnectorSpec = z.infer<typeof connectorSpecSchema>;
593
- declare const channelSpecSchema: z.ZodObject<{
594
- name: z.ZodString;
595
- connectors: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
596
- type: z.ZodLiteral<"slack">;
597
- name: z.ZodString;
598
- minify: z.ZodOptional<z.ZodBoolean>;
599
- }, z.core.$strip>, z.ZodObject<{
600
- type: z.ZodLiteral<"discord">;
601
- name: z.ZodString;
602
- }, z.core.$strip>, z.ZodObject<{
603
- type: z.ZodLiteral<"gh">;
604
- name: z.ZodString;
605
- pollInterval: z.ZodOptional<z.ZodNumber>;
606
- }, z.core.$strip>, z.ZodObject<{
607
- type: z.ZodLiteral<"schedule">;
608
- name: z.ZodString;
609
- }, z.core.$strip>], "type">>>;
610
- }, z.core.$strip>;
611
- type ChannelSpec = z.infer<typeof channelSpecSchema>;
612
- declare const profileSpecSchema: z.ZodObject<{
613
- name: z.ZodString;
614
- channel: z.ZodString;
615
- options: z.ZodOptional<z.ZodArray<z.ZodString>>;
616
- env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
617
- resume: z.ZodOptional<z.ZodBoolean>;
618
- }, z.core.$strip>;
619
- type ProfileSpec = z.infer<typeof profileSpecSchema>;
620
- declare const localConfigSchema: z.ZodObject<{
621
- $schema: z.ZodOptional<z.ZodString>;
622
- id: z.ZodOptional<z.ZodString>;
623
- channels: z.ZodArray<z.ZodObject<{
624
- name: z.ZodString;
625
- connectors: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
626
- type: z.ZodLiteral<"slack">;
627
- name: z.ZodString;
628
- minify: z.ZodOptional<z.ZodBoolean>;
629
- }, z.core.$strip>, z.ZodObject<{
630
- type: z.ZodLiteral<"discord">;
631
- name: z.ZodString;
632
- }, z.core.$strip>, z.ZodObject<{
633
- type: z.ZodLiteral<"gh">;
634
- name: z.ZodString;
635
- pollInterval: z.ZodOptional<z.ZodNumber>;
636
- }, z.core.$strip>, z.ZodObject<{
637
- type: z.ZodLiteral<"schedule">;
638
- name: z.ZodString;
639
- }, z.core.$strip>], "type">>>;
640
- }, z.core.$strip>>;
641
- profiles: z.ZodOptional<z.ZodArray<z.ZodObject<{
642
- name: z.ZodString;
643
- channel: z.ZodString;
644
- options: z.ZodOptional<z.ZodArray<z.ZodString>>;
645
- env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
646
- resume: z.ZodOptional<z.ZodBoolean>;
647
- }, z.core.$strip>>>;
648
- }, z.core.$strip>;
649
- type LocalConfig = z.infer<typeof localConfigSchema>;
650
- declare const LOCAL_CONFIG_FILENAME = "funnel.json";
651
- //#endregion
652
- //#region lib/engine/local-config/local-config.d.ts
653
- type Deps$10 = {
654
- fs: FunnelFileSystem;
655
- };
656
- /**
657
- * Reads `funnel.json` from a directory. Returns `null` when the file is
658
- * absent so callers can fall through to other resolution paths (default
659
- * profile, help). Throws on present-but-invalid files so misconfiguration
660
- * surfaces loudly instead of silently launching the wrong channel.
661
- */
662
- declare class FunnelLocalConfig {
663
- private readonly fs;
664
- constructor(deps: Deps$10);
665
- read(cwd: string): LocalConfig | null;
666
- private assertProfilesValid;
667
- }
668
- //#endregion
669
- //#region lib/engine/token-prompter/token-prompter.d.ts
670
- /**
671
- * Asks the user for a secret value on stdin. Used as a last resort when a
672
- * funnel.json token field is absent and not present in `~/.funnel`. The Node
673
- * implementation refuses to prompt when stdin is not a TTY so non-interactive
674
- * launches (CI, agent spawning agent, daemons) fail fast instead of hanging.
675
- */
676
- declare abstract class FunnelTokenPrompter {
677
- abstract promptSecret(label: string): Promise<string>;
502
+ private pickRecipients;
503
+ addClient(ws: ServerWebSocket<unknown>, data: ClientData): void;
504
+ removeClient(ws: ServerWebSocket<unknown>): void;
505
+ getClientCount(): number;
506
+ listChannels(): {
507
+ channel: string;
508
+ connectors: string[];
509
+ }[];
510
+ subscribe(handler: BroadcastSubscriber): () => void;
511
+ broadcast(content: string, meta?: Record<string, string>): ReplayableEvent;
512
+ /** Forward-seed the offset counter (used at startup from the persisted event store). */
513
+ seedLatestOffset(offset: number): void;
678
514
  }
679
515
  //#endregion
680
- //#region lib/engine/local-config/local-config-sync.d.ts
681
- type Deps$9 = {
682
- channels: FunnelChannels;
683
- prompter: FunnelTokenPrompter;
516
+ //#region lib/gateway/listener-supervisor.d.ts
517
+ type ConnectorRegistry = {
518
+ listAllConnectors(): ChannelConnectorView[];
519
+ createListener(channelName: string, connectorName: string): {
520
+ config: ConnectorConfig;
521
+ channelId: string;
522
+ listener: FunnelConnectorListener;
523
+ } | null;
684
524
  };
685
- type ConnectorSyncOutcome = {
686
- name: string;
687
- changed: boolean;
525
+ type SupervisorNotify = (channelName: string, connectorName: string, content: string, meta?: Record<string, string>) => Promise<void>;
526
+ type Deps$12 = {
527
+ channels: ConnectorRegistry;
528
+ notify: SupervisorNotify;
529
+ logger?: FunnelLogger; /** Host hook for surfacing listener lifecycle exceptions. Defaults to no-op. */
530
+ onError?: OnFunnelError;
531
+ healthCheckIntervalMs?: number;
532
+ maxBackoffMs?: number;
533
+ sleep?: (ms: number) => Promise<void>;
534
+ now?: () => number;
688
535
  };
689
- type LocalConfigSyncResult = {
690
- touched: ConnectorSyncOutcome[];
691
- removed: string[];
536
+ type ListenerEntryStatus = {
537
+ channelName: string;
538
+ channelId: string;
539
+ name: string;
540
+ type: ConnectorConfig["type"];
541
+ alive: boolean;
542
+ events: number;
543
+ errors: number;
544
+ failureCount: number;
545
+ lastEventAt: string | null;
692
546
  };
693
547
  /**
694
- * Reconciles a single funnel.json channel spec with `~/.funnel/settings.json`.
695
- * The spec is the source of truth for the channel it declares:
696
- *
697
- * - missing channel → created
698
- * - declared connector matched by name → tokens reconciled
699
- * - declared connector matched by token in the same channel under a
700
- * different name → renamed in place (then tokens reconciled)
701
- * - declared connector with no match → added
702
- * - any connector left in the channel that the spec did not touch → removed
548
+ * Owns the running listener instances and their lifecycle.
703
549
  *
704
- * Removal only fires when the channel spec has a `connectors` field. An
705
- * absent field means "do not manage connectors from here" and leaves
706
- * everything in `~/.funnel` alone. Other channels in funnel.json (not
707
- * passed to this call) are untouched.
550
+ * Lives in the gateway process and is the only place that calls
551
+ * `listener.start()` / `listener.stop()`. Each entry is keyed by
552
+ * `${channelName}/${connectorName}` so the same connector name can exist in
553
+ * multiple channels without colliding.
708
554
  *
709
- * Returns the per-connector change set so callers (e.g. the claude launcher)
710
- * can drive listener hot-reload on the gateway after settings are written.
555
+ * Periodically polls each running listener's `isAlive()` and auto-restarts
556
+ * dead listeners with exponential backoff (1s, 2s, 4s, ... capped). Resets
557
+ * the backoff counter on successful restart.
711
558
  */
712
- declare class FunnelLocalConfigSync {
559
+ declare class FunnelListenerSupervisor {
713
560
  private readonly channels;
714
- private readonly prompter;
715
- constructor(deps: Deps$9);
716
- ensure(channel: ChannelSpec): Promise<LocalConfigSyncResult>;
717
- private ensureConnector;
718
- private ensureSlack;
719
- private ensureDiscord;
720
- private ensureGh;
721
- private ensureSchedule;
722
- private findExistingSlack;
723
- private findExistingDiscord;
724
- private removeExtras;
725
- /**
726
- * Decides how a single token slot is stored in settings.json. funnel.json
727
- * never carries tokens, so the only sources are a value already in
728
- * settings.json (carried over verbatim, whichever form it was — literal or an
729
- * `env`-var reference set via the CLI) or, on first sync, a TTY prompt for a
730
- * literal (throws when stdin is not a TTY). Either way the secret lands in the
731
- * repo-scoped settings, never in the repo itself.
732
- */
733
- private resolveSlot;
561
+ private readonly notify;
562
+ private readonly logger;
563
+ private readonly onError;
564
+ private readonly running;
565
+ private readonly failureCounts;
566
+ private readonly stats;
567
+ private readonly healthCheckIntervalMs;
568
+ private readonly maxBackoffMs;
569
+ private readonly sleep;
570
+ private readonly now;
571
+ private healthCheckTimer;
572
+ private healthCheckInFlight;
573
+ constructor(deps: Deps$12);
574
+ static keyOf(channelName: string, connectorName: string): string;
575
+ isRunning(channelName: string, connectorName: string): boolean;
576
+ list(): ListenerEntryStatus[];
577
+ start(channelName: string, connectorName: string): Promise<{
578
+ ok: boolean;
579
+ reason?: string;
580
+ }>;
581
+ stop(channelName: string, connectorName: string): Promise<{
582
+ ok: boolean;
583
+ reason?: string;
584
+ }>;
585
+ restart(channelName: string, connectorName: string): Promise<{
586
+ ok: boolean;
587
+ reason?: string;
588
+ }>;
589
+ startAll(): Promise<void>;
590
+ stopAll(): Promise<void>;
591
+ private ensureStats;
592
+ private recordEvent;
593
+ private recordError;
594
+ private startHealthCheck;
595
+ private stopHealthCheck;
596
+ private runHealthCheck;
597
+ private recoverDead;
734
598
  }
735
599
  //#endregion
736
- //#region lib/engine/local-config/local-config-writer.d.ts
737
- type Deps$8 = {
738
- fs: FunnelFileSystem;
600
+ //#region lib/gateway/routes/route-deps.d.ts
601
+ type GatewayEmitInput = {
602
+ channel: string;
603
+ connector?: string;
604
+ content: string;
605
+ meta?: Record<string, string>;
606
+ };
607
+ type GatewayRouteDeps = {
608
+ selfPid: number;
609
+ broadcaster: FunnelBroadcaster;
610
+ supervisor: FunnelListenerSupervisor;
611
+ channels: FunnelChannels;
612
+ uptimeMs: () => number;
613
+ emit: (input: GatewayEmitInput) => {
614
+ offset: number;
615
+ };
739
616
  };
740
- /**
741
- * The one path that mutates the repo-committed funnel.json, and it only ever
742
- * inserts `id`. On first launch a repo has no `id`; funnel generates one and
743
- * writes it back here so future launches resolve the same `~/.funnel/projects/<id>/`.
744
- * Idempotent — a no-op once `id` is present. Kept separate from the read-only
745
- * FunnelLocalConfig so reads stay side-effect free.
746
- */
747
- declare class FunnelLocalConfigWriter {
748
- private readonly fs;
749
- constructor(deps: Deps$8);
750
- ensureId(cwd: string, id: string): void;
751
- }
752
617
  //#endregion
753
- //#region lib/gateway/publish-schema.d.ts
618
+ //#region lib/gateway/factory.d.ts
619
+ type Env$1 = {
620
+ Variables: {
621
+ deps: GatewayRouteDeps;
622
+ };
623
+ };
624
+ //#endregion
625
+ //#region lib/gateway/routes/index.d.ts
754
626
  /**
755
- * Shared schema for `POST /channels/:channel/publish` used by both the
756
- * gateway route handler (input validation) and the CLI / programmable client
757
- * (request shape). The route resolves `channel` from the path; this body
758
- * covers everything else.
627
+ * Top-level Hono app for the gateway daemon. Mounts every HTTP endpoint flat
628
+ * (the WebSocket /ws upgrade is handled directly by `Bun.serve`). Deps come
629
+ * from the `deps` variable set by `FunnelGatewayServer`'s middleware same
630
+ * shape as CLI's `c.var.funnel`.
759
631
  */
760
- declare const publishRequestSchema: z.ZodObject<{
761
- content: z.ZodString;
762
- meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
763
- connector: z.ZodOptional<z.ZodString>;
764
- }, z.core.$strip>;
765
- type PublishRequest = z.infer<typeof publishRequestSchema>;
766
- declare const publishResponseSchema: z.ZodObject<{
767
- ok: z.ZodLiteral<true>;
768
- offset: z.ZodNumber;
769
- }, z.core.$strip>;
770
- type PublishResponse = z.infer<typeof publishResponseSchema>;
771
- type PublishResult = {
772
- state: "ok";
773
- offset: number;
774
- } | {
775
- state: "offline";
776
- } | {
777
- state: "error";
778
- reason: string;
632
+ type GatewayApp = ReturnType<typeof buildGatewayRoutes>;
633
+ declare function buildGatewayRoutes(): _$hono_hono_base0.HonoBase<Env$1, {
634
+ "/health": {
635
+ $get: {
636
+ input: {};
637
+ output: {
638
+ ok: true;
639
+ pid: number;
640
+ clients: number;
641
+ listeners: {
642
+ channelName: string;
643
+ channelId: string;
644
+ name: string;
645
+ type: ConnectorConfig["type"];
646
+ alive: boolean;
647
+ events: number;
648
+ errors: number;
649
+ failureCount: number;
650
+ lastEventAt: string | null;
651
+ }[];
652
+ };
653
+ outputFormat: "json";
654
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
655
+ };
656
+ };
657
+ } & {
658
+ "/status": {
659
+ $get: {
660
+ input: {};
661
+ output: {
662
+ ok: true;
663
+ pid: number;
664
+ uptimeMs: number;
665
+ clients: {
666
+ channel: string;
667
+ connectors: string[];
668
+ }[];
669
+ listeners: {
670
+ channelName: string;
671
+ channelId: string;
672
+ name: string;
673
+ type: ConnectorConfig["type"];
674
+ alive: boolean;
675
+ events: number;
676
+ errors: number;
677
+ failureCount: number;
678
+ lastEventAt: string | null;
679
+ }[];
680
+ broadcaster: {
681
+ clients: number;
682
+ subscribers: number;
683
+ eventsBroadcast: number;
684
+ droppedSlowClients: number;
685
+ lastBroadcastAt: string | null;
686
+ latestOffset: number;
687
+ oldestReplayableOffset: number | null;
688
+ };
689
+ };
690
+ outputFormat: "json";
691
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
692
+ };
693
+ };
694
+ } & {
695
+ "/debug": {
696
+ $get: {
697
+ input: {};
698
+ output: {
699
+ pid: number;
700
+ uptimeMs: number;
701
+ eventsBroadcast: number;
702
+ channels: {
703
+ id: string;
704
+ name: string;
705
+ connectors: string[];
706
+ listener: {
707
+ alive: boolean;
708
+ events: number;
709
+ errors: number;
710
+ lastEventAt: string | null;
711
+ } | null;
712
+ claudeClients: number;
713
+ recentEvents: {
714
+ seq: number | null;
715
+ ts: number | null;
716
+ type: string;
717
+ outcome: string;
718
+ payload: string | null;
719
+ payloadParsed: {
720
+ [x: string]: _$hono_utils_types0.JSONValue;
721
+ } | null;
722
+ preview: string | null;
723
+ }[];
724
+ connectionErrors: {
725
+ ts: number | null;
726
+ type: string;
727
+ status: string;
728
+ detail: string | null;
729
+ }[];
730
+ diagnosis: {
731
+ status: "ok" | "warn" | "error";
732
+ message: string;
733
+ nextActions: string[];
734
+ rootCause: string | null;
735
+ };
736
+ }[];
737
+ };
738
+ outputFormat: "json";
739
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
740
+ };
741
+ };
742
+ } & {
743
+ "/listeners": {
744
+ $get: {
745
+ input: {};
746
+ output: {
747
+ listeners: {
748
+ channelName: string;
749
+ channelId: string;
750
+ name: string;
751
+ type: ConnectorConfig["type"];
752
+ alive: boolean;
753
+ events: number;
754
+ errors: number;
755
+ failureCount: number;
756
+ lastEventAt: string | null;
757
+ }[];
758
+ };
759
+ outputFormat: "json";
760
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
761
+ };
762
+ };
763
+ } & {
764
+ "/listeners/:channel/:connector/start": {
765
+ $post: {
766
+ input: {
767
+ param: {
768
+ channel: string;
769
+ connector: string;
770
+ };
771
+ };
772
+ output: {
773
+ ok: boolean;
774
+ reason: string;
775
+ };
776
+ outputFormat: "json";
777
+ status: 400;
778
+ } | {
779
+ input: {
780
+ param: {
781
+ channel: string;
782
+ connector: string;
783
+ };
784
+ };
785
+ output: {
786
+ ok: boolean;
787
+ reason?: string | undefined;
788
+ };
789
+ outputFormat: "json";
790
+ status: 200 | 400;
791
+ };
792
+ };
793
+ } & {
794
+ "/listeners/:channel/:connector": {
795
+ $delete: {
796
+ input: {
797
+ param: {
798
+ channel: string;
799
+ connector: string;
800
+ };
801
+ };
802
+ output: {
803
+ ok: boolean;
804
+ reason: string;
805
+ };
806
+ outputFormat: "json";
807
+ status: 400;
808
+ } | {
809
+ input: {
810
+ param: {
811
+ channel: string;
812
+ connector: string;
813
+ };
814
+ };
815
+ output: {
816
+ ok: boolean;
817
+ reason?: string | undefined;
818
+ };
819
+ outputFormat: "json";
820
+ status: 200 | 400;
821
+ };
822
+ };
823
+ } & {
824
+ "/listeners/:channel/:connector/restart": {
825
+ $post: {
826
+ input: {
827
+ param: {
828
+ channel: string;
829
+ connector: string;
830
+ };
831
+ };
832
+ output: {
833
+ ok: boolean;
834
+ reason: string;
835
+ };
836
+ outputFormat: "json";
837
+ status: 400;
838
+ } | {
839
+ input: {
840
+ param: {
841
+ channel: string;
842
+ connector: string;
843
+ };
844
+ };
845
+ output: {
846
+ ok: boolean;
847
+ reason?: string | undefined;
848
+ };
849
+ outputFormat: "json";
850
+ status: 200 | 400;
851
+ };
852
+ };
853
+ } & {
854
+ "/channels/:channel/connectors/:connector/call": {
855
+ $post: {
856
+ input: {
857
+ param: {
858
+ channel: string;
859
+ connector: string;
860
+ };
861
+ };
862
+ output: {
863
+ ok: boolean;
864
+ reason: string;
865
+ };
866
+ outputFormat: "json";
867
+ status: 400;
868
+ } | {
869
+ input: {
870
+ param: {
871
+ channel: string;
872
+ connector: string;
873
+ };
874
+ };
875
+ output: {
876
+ ok: true;
877
+ result: _$hono_utils_types0.JSONValue;
878
+ };
879
+ outputFormat: "json";
880
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
881
+ };
882
+ };
883
+ } & {
884
+ "/channels/:channel/publish": {
885
+ $post: {
886
+ input: {
887
+ param: {
888
+ channel: string;
889
+ };
890
+ } & {
891
+ json: {
892
+ content: string;
893
+ meta?: Record<string, string> | undefined;
894
+ connector?: string | undefined;
895
+ };
896
+ };
897
+ output: {
898
+ ok: boolean;
899
+ reason: string;
900
+ };
901
+ outputFormat: "json";
902
+ status: 400;
903
+ } | {
904
+ input: {
905
+ param: {
906
+ channel: string;
907
+ };
908
+ } & {
909
+ json: {
910
+ content: string;
911
+ meta?: Record<string, string> | undefined;
912
+ connector?: string | undefined;
913
+ };
914
+ };
915
+ output: {
916
+ ok: boolean;
917
+ reason: string;
918
+ };
919
+ outputFormat: "json";
920
+ status: 400;
921
+ } | {
922
+ input: {
923
+ param: {
924
+ channel: string;
925
+ };
926
+ } & {
927
+ json: {
928
+ content: string;
929
+ meta?: Record<string, string> | undefined;
930
+ connector?: string | undefined;
931
+ };
932
+ };
933
+ output: {
934
+ ok: true;
935
+ offset: number;
936
+ };
937
+ outputFormat: "json";
938
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
939
+ };
940
+ };
941
+ }, "/", "/channels/:channel/publish">;
942
+ //#endregion
943
+ //#region lib/engine/claude/gateway-controller.d.ts
944
+ type GatewayController = {
945
+ isRunning(): boolean;
946
+ start(options?: {
947
+ caffeinate?: boolean;
948
+ }): Promise<boolean>;
779
949
  };
780
950
  //#endregion
781
- //#region lib/gateway/channel-publisher.d.ts
782
- type Deps$7 = {
783
- port: number;
784
- isDaemonRunning: () => boolean; /** Returns the daemon's gateway token, or null if unavailable. Sent as `Authorization: Bearer`. */
785
- getToken?: () => string | null;
951
+ //#region lib/engine/mcp/mcp.d.ts
952
+ declare const FUNNEL_MCP_COMMAND = "bun";
953
+ declare const FUNNEL_MCP_ARGS: string[];
954
+ declare const FUNNEL_MCP_NAME = "funnel";
955
+ type Deps$11 = {
956
+ fs?: FunnelFileSystem;
786
957
  };
787
958
  /**
788
- * HTTP client for `POST /channels/:channel/publish` on a running gateway
789
- * daemon. Returns `{ state: "offline" }` when the daemon isn't up so callers
790
- * can branch without exceptions, mirroring `FunnelListenersClient`.
959
+ * Installs/uninstalls the funnel MCP entry into a target repository's
960
+ * `.mcp.json`. Detects an existing entry by command match so renaming is
961
+ * preserved across re-installs.
791
962
  */
792
- declare class FunnelChannelPublisher {
793
- private readonly port;
794
- private readonly isDaemonRunning;
795
- private readonly getToken;
796
- constructor(deps: Deps$7);
797
- publish(channelName: string, request: PublishRequest): Promise<PublishResult>;
798
- private authHeaders;
963
+ declare class FunnelMcp {
964
+ private readonly fs;
965
+ constructor(deps?: Deps$11);
966
+ install(repoPath: string): void;
967
+ uninstall(repoPath: string): void;
968
+ findInstalledName(cwd: string): string | null;
969
+ private findServerName;
970
+ private isFunnelEntry;
971
+ private readConfig;
972
+ private writeConfig;
799
973
  }
800
974
  //#endregion
801
- //#region lib/gateway/broadcaster.d.ts
802
- type ClientData = {
803
- /** Stable channel id (uuid) that the WS client subscribed to. */channel: string; /** Human-facing channel name resolved at upgrade time, kept for log readability. */
804
- channelName?: string | null; /** Connector names belonging to that channel; used by tap-all replay filtering. */
805
- connectors: string[];
806
- tapAll?: boolean; /** Routing mode resolved from channel config at upgrade time. Defaults to fanout. */
807
- delivery?: "fanout" | "exclusive";
975
+ //#region lib/engine/profiles/profiles.d.ts
976
+ type Deps$10 = {
977
+ store: FunnelSettingsReader;
978
+ idGenerator: FunnelIdGenerator;
808
979
  };
809
- type BroadcastEvent = {
810
- content: string;
811
- meta?: Record<string, string>;
812
- };
813
- type ReplayableEvent = BroadcastEvent & {
814
- offset: number;
815
- };
816
- type BroadcastSubscriber = (event: ReplayableEvent) => void;
817
980
  /**
818
- * Optional persistent replay source. Wired in by the gateway-server with a
819
- * `FunnelEventLog` (SQLite-backed by default) so reconnects across daemon
820
- * restarts can recover events older than the in-memory buffer via an indexed
821
- * `seq > since` range scan.
981
+ * Named launch presets for `fnl claude`. Each profile bundles a working
982
+ * directory, the channel id its Claude instance subscribes to, and the launch
983
+ * recipe (`options` prepended to the claude argv, `env` layered under the
984
+ * process, `resume` toggling session reuse). Implements ProfileChannelChecker
985
+ * so FunnelChannels can refuse to remove a channel that is still referenced.
986
+ *
987
+ * Each profile has a stable `id` (uuid) minted at `add`. That id is the unit
988
+ * everything internal keys on — the PID file, the resumable session id — so a
989
+ * rename never strands either. `name` is purely the CLI/TUI handle; the CRUD
990
+ * methods here take it because that is what the user types, but resolve to the
991
+ * id before touching id-keyed state. The first array entry is the default
992
+ * profile; `asDefault` reorders to put one first.
993
+ *
994
+ * `channelId` always stores the channel's stable id (uuid). CLI surfaces
995
+ * resolve channel name → id before calling `add`/`update` here.
822
996
  */
823
- type ReplaySource = {
824
- loadSince(since: number): ReplayableEvent[];
825
- };
826
- type Deps$6 = {
827
- logger?: FunnelLogger; /** Host hook for surfacing subscriber-throw exceptions. Defaults to no-op. */
828
- onError?: OnFunnelError;
829
- maxBufferedBytes?: number;
830
- now?: () => number; /** Number of recent events kept in the in-memory replay buffer. */
831
- replayBufferSize?: number; /** Hard byte cap on replay buffer payloads. Older events are evicted FIFO until under this cap. */
832
- replayBufferMaxBytes?: number; /** Persistent replay source consulted when the in-memory buffer cannot satisfy `since`. */
833
- persistentReplay?: ReplaySource;
997
+ declare class FunnelProfiles {
998
+ private readonly store;
999
+ private readonly idGenerator;
1000
+ constructor(deps: Deps$10);
1001
+ list(): ProfileConfig[];
1002
+ get(name: string): ProfileConfig | null;
1003
+ getById(id: string): ProfileConfig | null;
1004
+ getDefault(): ProfileConfig | null;
1005
+ add(input: {
1006
+ name: string;
1007
+ path: string;
1008
+ channelId: string;
1009
+ options?: string[];
1010
+ env?: Record<string, string>;
1011
+ resume?: boolean;
1012
+ }): void;
1013
+ remove(name: string): void;
1014
+ rename(oldName: string, newName: string): void;
1015
+ asDefault(name: string): void;
1016
+ hasChannelRef(channelId: string): boolean;
1017
+ /** Resumable claude session id last launched by this profile (by id), or null. */
1018
+ getSessionId(id: string): string | null;
1019
+ /** Records the claude session id this profile launched, overwriting any prior one. */
1020
+ setSessionId(id: string, sessionId: string): void;
1021
+ update(name: string, fields: Partial<Omit<ProfileConfig, "name">>): void;
1022
+ }
1023
+ //#endregion
1024
+ //#region lib/engine/claude/claude.d.ts
1025
+ type LaunchOptions = {
1026
+ channel: string;
1027
+ cwd?: string;
1028
+ userArgs?: string[];
1029
+ /** Stable id of the launching profile (uuid). Keys the singleton PID file and
1030
+ * the resumable session. Absent for a profile-less launch (raw `--channel`),
1031
+ * which never enforces singleton-ness and never resumes. */
1032
+ profileId?: string; /** Args prepended to the claude argv (typically a profile's recipe). Defaults to none. */
1033
+ options?: string[]; /** Env vars layered under the launched claude process. process.env wins on collision. */
1034
+ env?: Record<string, string>;
1035
+ /** Whether to inject a `--session-id`/`--resume` for this profile.
1036
+ * Defaults to false: resuming is opt-in and only meaningful for a profile,
1037
+ * since the persisted session is owned by the profile (by id). A launch
1038
+ * without a profile always starts a fresh session regardless of this flag. */
1039
+ resume?: boolean;
1040
+ /** Invoked synchronously after the child claude process has been spawned, with its PID.
1041
+ * Useful for hosts that need to register the spawned process before it exits
1042
+ * (e.g. multi-session registries that track per-claude liveness). */
1043
+ onSpawned?: (pid: number) => void;
1044
+ /** Whether to install the funnel MCP entry into `.mcp.json` (default: true).
1045
+ * Set to false when the host already provides its own MCP server entry and
1046
+ * does not need the funnel binary as an MCP endpoint. */
1047
+ installMcp?: boolean;
834
1048
  };
835
- type BroadcasterMetrics = {
836
- clients: number;
837
- subscribers: number;
838
- eventsBroadcast: number;
839
- droppedSlowClients: number;
840
- lastBroadcastAt: string | null; /** Latest emitted offset. Clients can `?since=<offset>` to ask for events strictly after this point. */
841
- latestOffset: number; /** Oldest offset still held in the replay buffer. Older values cannot be replayed and trigger a full resync. */
842
- oldestReplayableOffset: number | null;
1049
+ type Deps$9 = {
1050
+ channels: FunnelChannels;
1051
+ mcp: FunnelMcp;
1052
+ gateway: GatewayController;
1053
+ profiles: FunnelProfiles;
1054
+ process?: FunnelProcessRunner;
1055
+ fs?: FunnelFileSystem;
1056
+ idGenerator?: FunnelIdGenerator;
1057
+ logger?: FunnelLogger;
1058
+ dir?: string;
843
1059
  };
844
1060
  /**
845
- * In-process pub/sub for connector events.
846
- *
847
- * Two outbound paths:
848
- * - WS clients connected via the gateway's `/ws` endpoint, scoped per channel
849
- * - In-process subscribers registered via `subscribe()` (programmable API)
850
- *
851
- * Backpressure: if a WS client's `bufferedAmount` exceeds `maxBufferedBytes`
852
- * (default 1 MiB), the client is closed with code 1009 and dropped from the
853
- * registry to keep one slow consumer from blocking the daemon.
854
- *
855
- * Replay: every emitted event gets a strictly increasing `offset`. The latest
856
- * `replayBufferSize` events are kept in memory; reconnecting WS clients can
857
- * pass `?since=<offset>` and the broadcaster resends matching events before
858
- * resuming the live stream. The in-memory ring covers short reconnects;
859
- * older history is served from the event log wired in as `persistentReplay`.
1061
+ * Launches Claude Code with funnel pre-wired: ensures the gateway is running,
1062
+ * installs the funnel MCP into the target repo's `.mcp.json` if missing,
1063
+ * injects `FUNNEL_CHANNEL_ID` into the child env, and writes a per-profile
1064
+ * PID file to enforce singleton launches.
860
1065
  */
861
- declare class FunnelBroadcaster {
862
- private readonly clients;
863
- private readonly subscribers;
1066
+ declare class FunnelClaude {
1067
+ private readonly channels;
1068
+ private readonly mcp;
1069
+ private readonly gateway;
1070
+ private readonly profiles;
1071
+ private readonly process;
1072
+ private readonly fs;
1073
+ private readonly idGenerator;
864
1074
  private readonly logger;
865
- private readonly onError;
866
- private readonly maxBufferedBytes;
867
- private readonly now;
868
- private readonly replayBufferSize;
869
- private readonly replayBufferMaxBytes;
870
- private readonly replayBuffer;
871
- private readonly persistentReplay;
872
- private readonly exclusiveCursor;
873
- private replayBufferBytes;
874
- private eventsBroadcast;
875
- private droppedSlowClients;
876
- private lastBroadcastAt;
877
- private latestOffset;
878
- constructor(deps?: Deps$6);
879
- getMetrics(): BroadcasterMetrics;
1075
+ private readonly pidDir;
1076
+ constructor(deps: Deps$9);
1077
+ launch(options: LaunchOptions): Promise<number>;
1078
+ isRunning(profileId: string): boolean;
1079
+ private pidPath;
1080
+ private readPid;
1081
+ private writePidFile;
1082
+ private removePidFile;
1083
+ private installCleanup;
1084
+ private isProcessAlive;
1085
+ private buildArgs;
880
1086
  /**
881
- * Returns events with offset > since, filtered by the connector subscription
882
- * rules of `data`. Used at WS upgrade time when the client passes `?since=<offset>`.
1087
+ * Decides whether funnel should resume an existing claude session or start
1088
+ * a freshly minted one. Backs off when the user already passed a
1089
+ * session-shaping flag, since combining them would either confuse claude
1090
+ * or override the explicit user intent.
883
1091
  *
884
- * Two-tier lookup:
885
- * 1. The in-memory ring buffer (covers short reconnects, last `replayBufferSize` events).
886
- * 2. If `since` predates the oldest in-memory entry and a persistent replay source
887
- * is wired in (SQLite by default), the gap is filled from it. This covers reconnects
888
- * across daemon restarts where the in-memory buffer was lost.
1092
+ * The session is owned by the profile (by id), not by cwd: two profiles
1093
+ * pointing at the same repo each keep their own conversation, and a launch
1094
+ * with no profile never resumes so an unrelated session in the same repo
1095
+ * can't bleed in. The channel never enters into it; sessions belong to the
1096
+ * launch layer (profiles), keeping the transport layer ignorant of them.
889
1097
  *
890
- * Result is sorted ascending by offset and de-duplicated against the in-memory buffer.
1098
+ * A persisted id is only resumed when its session jsonl still exists on
1099
+ * disk. claude errors out on `--resume <id>` for a missing conversation, and
1100
+ * a persisted id can outlive its jsonl (claude pruned it, or the very first
1101
+ * launch was aborted after the id was written but before the jsonl
1102
+ * appeared). When the file is gone we mint a fresh session instead, which
1103
+ * overwrites the dangling entry — so the store self-heals.
891
1104
  */
892
- replaySince(since: number, data: ClientData): ReplayableEvent[];
893
- private matchesClient;
1105
+ private resolveSession;
894
1106
  /**
895
- * Returns the list of WS clients that should receive `event`. Tap=all clients always
896
- * receive (passive observation). For each per-channel group:
897
- * - fanout every matching client receives
898
- * - exclusive exactly one client receives, picked round-robin per channel
1107
+ * Mirrors claude's session storage path
1108
+ * (`<config-dir>/projects/<cwd-with-slashes-as-dashes>/<id>.jsonl`) to check
1109
+ * whether a recorded session still exists AND is non-empty. Reads the same
1110
+ * `CLAUDE_CONFIG_DIR` the child will run under so the check matches reality; a
1111
+ * wrong guess can only ever produce a false negative (start fresh), never a
1112
+ * bad resume.
899
1113
  */
900
- private pickRecipients;
901
- addClient(ws: ServerWebSocket<unknown>, data: ClientData): void;
902
- removeClient(ws: ServerWebSocket<unknown>): void;
903
- getClientCount(): number;
904
- listChannels(): {
905
- channel: string;
906
- connectors: string[];
907
- }[];
908
- subscribe(handler: BroadcastSubscriber): () => void;
909
- broadcast(content: string, meta?: Record<string, string>): ReplayableEvent;
910
- /** Forward-seed the offset counter (used at startup from the persisted event store). */
911
- seedLatestOffset(offset: number): void;
1114
+ private sessionFileExists;
1115
+ private buildEnv;
912
1116
  }
913
1117
  //#endregion
914
- //#region lib/gateway/listener-supervisor.d.ts
915
- type ConnectorRegistry = {
916
- listAllConnectors(): ChannelConnectorView[];
917
- createListener(channelName: string, connectorName: string): {
918
- config: ConnectorConfig;
919
- channelId: string;
920
- listener: FunnelConnectorListener;
921
- } | null;
922
- };
923
- type SupervisorNotify = (channelName: string, connectorName: string, content: string, meta?: Record<string, string>) => Promise<void>;
924
- type Deps$5 = {
925
- channels: ConnectorRegistry;
926
- notify: SupervisorNotify;
927
- logger?: FunnelLogger; /** Host hook for surfacing listener lifecycle exceptions. Defaults to no-op. */
928
- onError?: OnFunnelError;
929
- healthCheckIntervalMs?: number;
930
- maxBackoffMs?: number;
931
- sleep?: (ms: number) => Promise<void>;
932
- now?: () => number;
933
- };
934
- type ListenerEntryStatus = {
935
- channelName: string;
936
- channelId: string;
937
- name: string;
938
- type: ConnectorConfig["type"];
939
- alive: boolean;
940
- events: number;
941
- errors: number;
942
- failureCount: number;
943
- lastEventAt: string | null;
944
- };
945
- /**
946
- * Owns the running listener instances and their lifecycle.
947
- *
948
- * Lives in the gateway process and is the only place that calls
949
- * `listener.start()` / `listener.stop()`. Each entry is keyed by
950
- * `${channelName}/${connectorName}` so the same connector name can exist in
951
- * multiple channels without colliding.
952
- *
953
- * Periodically polls each running listener's `isAlive()` and auto-restarts
954
- * dead listeners with exponential backoff (1s, 2s, 4s, ... capped). Resets
955
- * the backoff counter on successful restart.
956
- */
957
- declare class FunnelListenerSupervisor {
958
- private readonly channels;
959
- private readonly notify;
960
- private readonly logger;
961
- private readonly onError;
962
- private readonly running;
963
- private readonly failureCounts;
964
- private readonly stats;
965
- private readonly healthCheckIntervalMs;
966
- private readonly maxBackoffMs;
967
- private readonly sleep;
968
- private readonly now;
969
- private healthCheckTimer;
970
- private healthCheckInFlight;
971
- constructor(deps: Deps$5);
972
- static keyOf(channelName: string, connectorName: string): string;
973
- isRunning(channelName: string, connectorName: string): boolean;
974
- list(): ListenerEntryStatus[];
975
- start(channelName: string, connectorName: string): Promise<{
976
- ok: boolean;
977
- reason?: string;
978
- }>;
979
- stop(channelName: string, connectorName: string): Promise<{
980
- ok: boolean;
981
- reason?: string;
982
- }>;
983
- restart(channelName: string, connectorName: string): Promise<{
984
- ok: boolean;
985
- reason?: string;
986
- }>;
987
- startAll(): Promise<void>;
988
- stopAll(): Promise<void>;
989
- private ensureStats;
990
- private recordEvent;
991
- private recordError;
992
- private startHealthCheck;
993
- private stopHealthCheck;
994
- private runHealthCheck;
995
- private recoverDead;
996
- }
997
- //#endregion
998
- //#region lib/gateway/routes/route-deps.d.ts
999
- type GatewayEmitInput = {
1000
- channel: string;
1001
- connector?: string;
1002
- content: string;
1003
- meta?: Record<string, string>;
1004
- };
1005
- type GatewayRouteDeps = {
1006
- selfPid: number;
1007
- broadcaster: FunnelBroadcaster;
1008
- supervisor: FunnelListenerSupervisor;
1009
- channels: FunnelChannels;
1010
- uptimeMs: () => number;
1011
- emit: (input: GatewayEmitInput) => {
1012
- offset: number;
1013
- };
1014
- };
1118
+ //#region lib/engine/local-config/local-config-schema.d.ts
1119
+ declare const connectorSpecSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1120
+ type: z.ZodLiteral<"slack">;
1121
+ name: z.ZodString;
1122
+ minify: z.ZodOptional<z.ZodBoolean>;
1123
+ }, z.core.$strip>, z.ZodObject<{
1124
+ type: z.ZodLiteral<"discord">;
1125
+ name: z.ZodString;
1126
+ }, z.core.$strip>, z.ZodObject<{
1127
+ type: z.ZodLiteral<"gh">;
1128
+ name: z.ZodString;
1129
+ pollInterval: z.ZodOptional<z.ZodNumber>;
1130
+ }, z.core.$strip>, z.ZodObject<{
1131
+ type: z.ZodLiteral<"schedule">;
1132
+ name: z.ZodString;
1133
+ }, z.core.$strip>], "type">;
1134
+ type ConnectorSpec = z.infer<typeof connectorSpecSchema>;
1135
+ declare const channelSpecSchema: z.ZodObject<{
1136
+ name: z.ZodString;
1137
+ connectors: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
1138
+ type: z.ZodLiteral<"slack">;
1139
+ name: z.ZodString;
1140
+ minify: z.ZodOptional<z.ZodBoolean>;
1141
+ }, z.core.$strip>, z.ZodObject<{
1142
+ type: z.ZodLiteral<"discord">;
1143
+ name: z.ZodString;
1144
+ }, z.core.$strip>, z.ZodObject<{
1145
+ type: z.ZodLiteral<"gh">;
1146
+ name: z.ZodString;
1147
+ pollInterval: z.ZodOptional<z.ZodNumber>;
1148
+ }, z.core.$strip>, z.ZodObject<{
1149
+ type: z.ZodLiteral<"schedule">;
1150
+ name: z.ZodString;
1151
+ }, z.core.$strip>], "type">>>;
1152
+ }, z.core.$strip>;
1153
+ type ChannelSpec = z.infer<typeof channelSpecSchema>;
1154
+ declare const profileSpecSchema: z.ZodObject<{
1155
+ name: z.ZodString;
1156
+ channel: z.ZodString;
1157
+ options: z.ZodOptional<z.ZodArray<z.ZodString>>;
1158
+ env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1159
+ resume: z.ZodOptional<z.ZodBoolean>;
1160
+ }, z.core.$strip>;
1161
+ type ProfileSpec = z.infer<typeof profileSpecSchema>;
1162
+ declare const localConfigSchema: z.ZodObject<{
1163
+ $schema: z.ZodOptional<z.ZodString>;
1164
+ id: z.ZodOptional<z.ZodString>;
1165
+ channels: z.ZodArray<z.ZodObject<{
1166
+ name: z.ZodString;
1167
+ connectors: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
1168
+ type: z.ZodLiteral<"slack">;
1169
+ name: z.ZodString;
1170
+ minify: z.ZodOptional<z.ZodBoolean>;
1171
+ }, z.core.$strip>, z.ZodObject<{
1172
+ type: z.ZodLiteral<"discord">;
1173
+ name: z.ZodString;
1174
+ }, z.core.$strip>, z.ZodObject<{
1175
+ type: z.ZodLiteral<"gh">;
1176
+ name: z.ZodString;
1177
+ pollInterval: z.ZodOptional<z.ZodNumber>;
1178
+ }, z.core.$strip>, z.ZodObject<{
1179
+ type: z.ZodLiteral<"schedule">;
1180
+ name: z.ZodString;
1181
+ }, z.core.$strip>], "type">>>;
1182
+ }, z.core.$strip>>;
1183
+ profiles: z.ZodOptional<z.ZodArray<z.ZodObject<{
1184
+ name: z.ZodString;
1185
+ channel: z.ZodString;
1186
+ options: z.ZodOptional<z.ZodArray<z.ZodString>>;
1187
+ env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1188
+ resume: z.ZodOptional<z.ZodBoolean>;
1189
+ }, z.core.$strip>>>;
1190
+ }, z.core.$strip>;
1191
+ type LocalConfig = z.infer<typeof localConfigSchema>;
1192
+ declare const LOCAL_CONFIG_FILENAME = "funnel.json";
1015
1193
  //#endregion
1016
- //#region lib/gateway/factory.d.ts
1017
- type Env$1 = {
1018
- Variables: {
1019
- deps: GatewayRouteDeps;
1020
- };
1194
+ //#region lib/engine/local-config/local-config.d.ts
1195
+ type Deps$8 = {
1196
+ fs: FunnelFileSystem;
1021
1197
  };
1198
+ /**
1199
+ * Reads `funnel.json` from a directory. Returns `null` when the file is
1200
+ * absent so callers can fall through to other resolution paths (default
1201
+ * profile, help). Throws on present-but-invalid files so misconfiguration
1202
+ * surfaces loudly instead of silently launching the wrong channel.
1203
+ */
1204
+ declare class FunnelLocalConfig {
1205
+ private readonly fs;
1206
+ constructor(deps: Deps$8);
1207
+ read(cwd: string): LocalConfig | null;
1208
+ private assertProfilesValid;
1209
+ }
1022
1210
  //#endregion
1023
- //#region lib/gateway/funnel-event-log.d.ts
1211
+ //#region lib/engine/token-prompter/token-prompter.d.ts
1024
1212
  /**
1025
- * Replayable event payload persisted by the gateway. Domain events the
1026
- * broadcaster emits to WS clients land here so reconnects across daemon
1027
- * restarts can be served from disk. System events (gateway start, channel
1028
- * connected, etc.) are routed to `FunnelLogger` instead they never go
1029
- * through this log, which keeps the offset space clean for replay.
1213
+ * Asks the user for a secret value on stdin. Used as a last resort when a
1214
+ * funnel.json token field is absent and not present in `~/.funnel`. The Node
1215
+ * implementation refuses to prompt when stdin is not a TTY so non-interactive
1216
+ * launches (CI, agent spawning agent, daemons) fail fast instead of hanging.
1030
1217
  */
1031
- declare const funnelEventSchema: z.ZodObject<{
1032
- type: z.ZodString;
1033
- content: z.ZodString;
1034
- channel_id: z.ZodNullable<z.ZodString>;
1035
- connector_id: z.ZodNullable<z.ZodString>;
1036
- meta: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodString>>;
1037
- }, z.core.$strip>;
1038
- type FunnelEvent = z.infer<typeof funnelEventSchema>;
1039
- /** One broadcast event to persist, carrying the offset the broadcaster assigned. */
1040
- type FunnelEventRecord = {
1041
- content: string;
1042
- channelId: string | null;
1043
- connectorId: string | null;
1044
- meta: Record<string, string> | null;
1045
- offset: number;
1218
+ declare abstract class FunnelTokenPrompter {
1219
+ abstract promptSecret(label: string): Promise<string>;
1220
+ }
1221
+ //#endregion
1222
+ //#region lib/engine/local-config/local-config-sync.d.ts
1223
+ type Deps$7 = {
1224
+ channels: FunnelChannels;
1225
+ prompter: FunnelTokenPrompter;
1226
+ };
1227
+ type ConnectorSyncOutcome = {
1228
+ name: string;
1229
+ changed: boolean;
1230
+ };
1231
+ type LocalConfigSyncResult = {
1232
+ touched: ConnectorSyncOutcome[];
1233
+ removed: string[];
1046
1234
  };
1047
1235
  /**
1048
- * Durable, append-only log of broadcaster events keyed by the offset the
1049
- * broadcaster assigns. The gateway persists every domain event here, and
1050
- * across restarts it both seeds the broadcaster's offset counter
1051
- * (`findMaxOffset`) and serves reconnect replay (`loadSince`) from it.
1236
+ * Reconciles a single funnel.json channel spec with `~/.funnel/settings.json`.
1237
+ * The spec is the source of truth for the channel it declares:
1052
1238
  *
1053
- * `loadSince` is the only method the broadcaster itself needs, which makes
1054
- * any implementation assignable to the broadcaster's narrow `ReplaySource`.
1239
+ * - missing channel created
1240
+ * - declared connector matched by name tokens reconciled
1241
+ * - declared connector matched by token in the same channel under a
1242
+ * different name → renamed in place (then tokens reconciled)
1243
+ * - declared connector with no match → added
1244
+ * - any connector left in the channel that the spec did not touch → removed
1055
1245
  *
1056
- * Implementations:
1057
- * - `SqliteFunnelEventLog` the default; durable across daemon restarts.
1058
- * - `MemoryFunnelEventLog` an in-process double for tests and embedders
1059
- * that do not need durability (replay is lost when the process exits).
1246
+ * Removal only fires when the channel spec has a `connectors` field. An
1247
+ * absent field means "do not manage connectors from here" and leaves
1248
+ * everything in `~/.funnel` alone. Other channels in funnel.json (not
1249
+ * passed to this call) are untouched.
1250
+ *
1251
+ * Returns the per-connector change set so callers (e.g. the claude launcher)
1252
+ * can drive listener hot-reload on the gateway after settings are written.
1060
1253
  */
1061
- declare abstract class FunnelEventLog {
1062
- abstract record(record: FunnelEventRecord): void;
1063
- abstract loadSince(since: number): ReplayableEvent[];
1064
- abstract findMaxOffset(): number;
1065
- /** Drop every stored event and reclaim the file. The broadcaster's in-memory
1066
- * offset counter is unaffected, so offsets keep increasing after a clear. */
1067
- abstract clear(): void;
1068
- abstract close(): void;
1254
+ declare class FunnelLocalConfigSync {
1255
+ private readonly channels;
1256
+ private readonly prompter;
1257
+ constructor(deps: Deps$7);
1258
+ ensure(channel: ChannelSpec): Promise<LocalConfigSyncResult>;
1259
+ private ensureConnector;
1260
+ private ensureSlack;
1261
+ private ensureDiscord;
1262
+ private ensureGh;
1263
+ private ensureSchedule;
1264
+ private findExistingSlack;
1265
+ private findExistingDiscord;
1266
+ private removeExtras;
1267
+ /**
1268
+ * Decides how a single token slot is stored in settings.json. funnel.json
1269
+ * never carries tokens, so the only sources are a value already in
1270
+ * settings.json (carried over verbatim, whichever form it was — literal or an
1271
+ * `env`-var reference set via the CLI) or, on first sync, a TTY prompt for a
1272
+ * literal (throws when stdin is not a TTY). Either way the secret lands in the
1273
+ * repo-scoped settings, never in the repo itself.
1274
+ */
1275
+ private resolveSlot;
1069
1276
  }
1070
1277
  //#endregion
1071
- //#region lib/gateway/gateway.d.ts
1072
- type Deps$4 = {
1073
- process?: FunnelProcessRunner;
1074
- fs?: FunnelFileSystem;
1075
- clock?: FunnelClock;
1076
- dir?: string;
1077
- tmpDir?: string;
1078
- port?: number;
1278
+ //#region lib/engine/local-config/local-config-writer.d.ts
1279
+ type Deps$6 = {
1280
+ fs: FunnelFileSystem;
1281
+ };
1282
+ /**
1283
+ * The one path that mutates the repo-committed funnel.json, and it only ever
1284
+ * inserts `id`. On first launch a repo has no `id`; funnel generates one and
1285
+ * writes it back here so future launches resolve the same `~/.funnel/projects/<id>/`.
1286
+ * Idempotent — a no-op once `id` is present. Kept separate from the read-only
1287
+ * FunnelLocalConfig so reads stay side-effect free.
1288
+ */
1289
+ declare class FunnelLocalConfigWriter {
1290
+ private readonly fs;
1291
+ constructor(deps: Deps$6);
1292
+ ensureId(cwd: string, id: string): void;
1293
+ }
1294
+ //#endregion
1295
+ //#region lib/gateway/publish-schema.d.ts
1296
+ /**
1297
+ * Shared schema for `POST /channels/:channel/publish` — used by both the
1298
+ * gateway route handler (input validation) and the CLI / programmable client
1299
+ * (request shape). The route resolves `channel` from the path; this body
1300
+ * covers everything else.
1301
+ */
1302
+ declare const publishRequestSchema: z.ZodObject<{
1303
+ content: z.ZodString;
1304
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1305
+ connector: z.ZodOptional<z.ZodString>;
1306
+ }, z.core.$strip>;
1307
+ type PublishRequest = z.infer<typeof publishRequestSchema>;
1308
+ declare const publishResponseSchema: z.ZodObject<{
1309
+ ok: z.ZodLiteral<true>;
1310
+ offset: z.ZodNumber;
1311
+ }, z.core.$strip>;
1312
+ type PublishResponse = z.infer<typeof publishResponseSchema>;
1313
+ type PublishResult = {
1314
+ state: "ok";
1315
+ offset: number;
1316
+ } | {
1317
+ state: "offline";
1318
+ } | {
1319
+ state: "error";
1320
+ reason: string;
1321
+ };
1322
+ //#endregion
1323
+ //#region lib/gateway/channel-publisher.d.ts
1324
+ type Deps$5 = {
1325
+ port: number;
1326
+ isDaemonRunning: () => boolean; /** Returns the daemon's gateway token, or null if unavailable. Sent as `Authorization: Bearer`. */
1327
+ getToken?: () => string | null;
1328
+ };
1329
+ /**
1330
+ * HTTP client for `POST /channels/:channel/publish` on a running gateway
1331
+ * daemon. Returns `{ state: "offline" }` when the daemon isn't up so callers
1332
+ * can branch without exceptions, mirroring `FunnelListenersClient`.
1333
+ */
1334
+ declare class FunnelChannelPublisher {
1335
+ private readonly port;
1336
+ private readonly isDaemonRunning;
1337
+ private readonly getToken;
1338
+ constructor(deps: Deps$5);
1339
+ publish(channelName: string, request: PublishRequest): Promise<PublishResult>;
1340
+ private authHeaders;
1341
+ }
1342
+ //#endregion
1343
+ //#region lib/gateway/funnel-event-log.d.ts
1344
+ /**
1345
+ * Replayable event payload persisted by the gateway. Domain events the
1346
+ * broadcaster emits to WS clients land here so reconnects across daemon
1347
+ * restarts can be served from disk. System events (gateway start, channel
1348
+ * connected, etc.) are routed to `FunnelLogger` instead — they never go
1349
+ * through this log, which keeps the offset space clean for replay.
1350
+ */
1351
+ declare const funnelEventSchema: z.ZodObject<{
1352
+ type: z.ZodString;
1353
+ content: z.ZodString;
1354
+ channel_id: z.ZodNullable<z.ZodString>;
1355
+ connector_id: z.ZodNullable<z.ZodString>;
1356
+ meta: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodString>>;
1357
+ }, z.core.$strip>;
1358
+ type FunnelEvent = z.infer<typeof funnelEventSchema>;
1359
+ /** One broadcast event to persist, carrying the offset the broadcaster assigned. */
1360
+ type FunnelEventRecord = {
1361
+ content: string;
1362
+ channelId: string | null;
1363
+ connectorId: string | null;
1364
+ meta: Record<string, string> | null;
1365
+ offset: number;
1366
+ };
1367
+ /**
1368
+ * Durable, append-only log of broadcaster events keyed by the offset the
1369
+ * broadcaster assigns. The gateway persists every domain event here, and
1370
+ * across restarts it both seeds the broadcaster's offset counter
1371
+ * (`findMaxOffset`) and serves reconnect replay (`loadSince`) from it.
1372
+ *
1373
+ * `loadSince` is the only method the broadcaster itself needs, which makes
1374
+ * any implementation assignable to the broadcaster's narrow `ReplaySource`.
1375
+ *
1376
+ * Implementations:
1377
+ * - `SqliteFunnelEventLog` — the default; durable across daemon restarts.
1378
+ * - `MemoryFunnelEventLog` — an in-process double for tests and embedders
1379
+ * that do not need durability (replay is lost when the process exits).
1380
+ */
1381
+ declare abstract class FunnelEventLog {
1382
+ abstract record(record: FunnelEventRecord): void;
1383
+ abstract loadSince(since: number): ReplayableEvent[];
1384
+ abstract findMaxOffset(): number;
1385
+ /** Drop every stored event and reclaim the file. The broadcaster's in-memory
1386
+ * offset counter is unaffected, so offsets keep increasing after a clear. */
1387
+ abstract clear(): void;
1388
+ abstract close(): void;
1389
+ }
1390
+ //#endregion
1391
+ //#region lib/gateway/gateway.d.ts
1392
+ type Deps$4 = {
1393
+ process?: FunnelProcessRunner;
1394
+ fs?: FunnelFileSystem;
1395
+ clock?: FunnelClock;
1396
+ dir?: string;
1397
+ tmpDir?: string;
1398
+ port?: number;
1079
1399
  sleep?: (ms: number) => Promise<void>;
1080
1400
  };
1081
1401
  /**
@@ -1324,6 +1644,34 @@ declare class FunnelListenersClient {
1324
1644
  private call;
1325
1645
  }
1326
1646
  //#endregion
1647
+ //#region lib/gateway/funnel-debug.d.ts
1648
+ type FunnelDebugReport = {
1649
+ gateway: {
1650
+ running: boolean;
1651
+ pid: number | null;
1652
+ port: number | null;
1653
+ uptimeMs: number | null;
1654
+ };
1655
+ channels: Array<{
1656
+ name: string;
1657
+ connectors: string[];
1658
+ listener: {
1659
+ alive: boolean;
1660
+ events: number;
1661
+ errors: number;
1662
+ lastEventAt: string | null;
1663
+ } | null;
1664
+ claudeConnected: boolean;
1665
+ claudeClientCount: number;
1666
+ }>;
1667
+ recentEvents: Array<{
1668
+ ts: number;
1669
+ outcome: string;
1670
+ payload: string | null;
1671
+ preview: string | null;
1672
+ }> | null;
1673
+ };
1674
+ //#endregion
1327
1675
  //#region lib/funnel.d.ts
1328
1676
  type Props$8 = {
1329
1677
  /** Settings persistence (channels with nested connectors / profiles). Defaults to a FunnelSettingsStore rooted at `dir`. */store?: FunnelSettingsReader; /** Filesystem boundary. Replace with MemoryFunnelFileSystem to sandbox all disk I/O. */
@@ -1466,6 +1814,8 @@ declare class Funnel {
1466
1814
  */
1467
1815
  extraRoutes?: Hono<Env$1>;
1468
1816
  }): FunnelGatewayServer;
1817
+ debug(channelName?: string): Promise<FunnelDebugReport>;
1818
+ gatewayClient(): ReturnType<typeof hc<GatewayApp>>;
1469
1819
  }
1470
1820
  //#endregion
1471
1821
  //#region lib/engine/mcp/channel-server.d.ts
@@ -1742,1543 +2092,219 @@ type Props$3 = {
1742
2092
  };
1743
2093
  /**
1744
2094
  * Pre-seeded answers keyed by prompt label. Tests configure the map up front;
1745
- * unmapped labels throw so the test surfaces unexpected prompts loudly.
1746
- */
1747
- declare class MemoryFunnelTokenPrompter extends FunnelTokenPrompter {
1748
- private readonly answers;
1749
- readonly asked: string[];
1750
- constructor(props?: Props$3);
1751
- promptSecret(label: string): Promise<string>;
1752
- }
1753
- //#endregion
1754
- //#region lib/gateway/sqlite-funnel-event-log.d.ts
1755
- type Props$2 = {
1756
- /** SQLite database file path. Created on first write. ":memory:" for tests. */path: string; /** Override for tests. Defaults to `Date.now`. */
1757
- now?: () => number; /** Optional row cap. Pruned on every insert. */
1758
- maxRows?: number; /** Optional age cap in ms. Pruned on every insert. */
1759
- maxAgeMs?: number; /** Optional on-disk byte cap. Checked periodically; on overflow the oldest rows are dropped toward targetBytes and the file is VACUUMed. */
1760
- maxBytes?: number; /** Shrink target when maxBytes is exceeded. Defaults to maxBytes/4. */
1761
- targetBytes?: number;
1762
- };
1763
- /**
1764
- * SQLite-backed `FunnelEventLog`. One indexed table holds every broadcaster
1765
- * event with `channel_id` and `connector_id` as dedicated columns, so
1766
- * per-channel and per-connector replay is an indexed range scan.
1767
- *
1768
- * Concurrency: `seq` is `INTEGER PRIMARY KEY`, so SQLite assigns it
1769
- * atomically. The broadcaster owns its own offset counter at runtime
1770
- * (seeded from `findMaxOffset()` at startup); each broadcaster event
1771
- * flows in here via `record()` with that pre-assigned offset, which the
1772
- * sink stores via `write()` — PK uniqueness catches double-emit bugs.
1773
- *
1774
- * System events (gateway lifecycle, channel connect/disconnect, etc.) do
1775
- * NOT go through this store. They are diagnostic only and live in
1776
- * `FunnelLogger`'s file so the seq space here stays exclusive to
1777
- * broadcaster traffic. This is what makes the broadcaster's seq seeding
1778
- * (`getMaxSeq()` at startup) correct without per-event coordination.
1779
- */
1780
- declare class SqliteFunnelEventLog extends FunnelEventLog {
1781
- private readonly sink;
1782
- private readonly now;
1783
- constructor(props: Props$2);
1784
- /**
1785
- * Persist a broadcaster-driven event with its assigned offset. Caller
1786
- * (the gateway-server) supplies the offset from `broadcaster.broadcast()`
1787
- * so this store and the broadcaster's in-memory ring stay aligned.
1788
- */
1789
- record(record: FunnelEventRecord): void;
1790
- /**
1791
- * Returns events with offset > since. Filtering by channel/connector is
1792
- * the broadcaster's responsibility (it knows the client's subscription),
1793
- * so this returns the full slice and lets the caller filter.
1794
- */
1795
- loadSince(since: number): ReplayableEvent[];
1796
- /**
1797
- * Returns events for one channel (and optionally one connector). Used
1798
- * by the gateway logs CLI for scoped queries. Channel/connector filters
1799
- * are indexed columns, so this is an indexed range scan.
1800
- */
1801
- loadForChannel(props: {
1802
- channelId: string;
1803
- connectorId?: string;
1804
- sinceSeq?: number;
1805
- limit?: number;
1806
- }): ReplayableEvent[];
1807
- findMaxOffset(): number;
1808
- clear(): void;
1809
- close(): void;
1810
- }
1811
- //#endregion
1812
- //#region lib/gateway/memory-funnel-event-log.d.ts
1813
- /**
1814
- * In-process `FunnelEventLog` backed by a plain array. Used by tests and by
1815
- * embedders that do not need durability — replay works within the process
1816
- * lifetime but is lost when the process exits. Unlike the SQLite log it does
1817
- * not truncate content or prune, so it is not meant for unbounded production
1818
- * traffic.
1819
- */
1820
- declare class MemoryFunnelEventLog extends FunnelEventLog {
1821
- private readonly events;
1822
- constructor();
1823
- record(record: FunnelEventRecord): void;
1824
- loadSince(since: number): ReplayableEvent[];
1825
- findMaxOffset(): number;
1826
- clear(): void;
1827
- close(): void;
1828
- }
1829
- //#endregion
1830
- //#region lib/gateway/sqlite-connector-diagnostic-log.d.ts
1831
- type Props$1 = {
1832
- /** SQLite file for the raw (pre-filter) table. ":memory:" for tests. */rawPath: string; /** SQLite file for the processed (verdict) table. ":memory:" for tests. */
1833
- processedPath: string; /** SQLite file for the connection (lifecycle) table. ":memory:" for tests. */
1834
- connectionPath: string;
1835
- now?: () => number; /** Row cap for the processed and connection tables. Pruned on every insert. */
1836
- maxRows?: number;
1837
- /**
1838
- * Row cap for the raw table specifically. Raw rows can each hold up to
1839
- * `RAW_PAYLOAD_CAP` bytes, so they want a tighter cap than the small
1840
- * processed/connection verdict rows. Defaults to `maxRows` when unset.
1841
- */
1842
- rawMaxRows?: number; /** Age cap in ms for all tables — bounds how long untouched payloads (with PII) live. Pruned on every insert. */
1843
- maxAgeMs?: number; /** When set, `insert()` errors (disk full, WAL lock) are logged instead of silently dropped. */
1844
- logger?: FunnelLogger;
1845
- };
1846
- /**
1847
- * Default `ConnectorDiagnosticLog`: three independent `LeucoLoggerSqliteSink`s, one
1848
- * per table (raw / processed / connection), in separate files. Each sink
1849
- * indexes the columns its queries filter on — `event_id` / `connector_id` /
1850
- * `channel_id` for raw, plus `outcome` for processed and `status` for
1851
- * connection — so those lookups are indexed scans (`type` is a fixed column
1852
- * the sink extracts separately, not an index, so filtering by it is a scan).
1853
- *
1854
- * The raw table offloads any payload over `RAW_PAYLOAD_CAP`: rather than
1855
- * truncating mid-string (which yields unparseable JSON), it replaces the
1856
- * body with a small JSON object that keeps the diagnostic essentials and
1857
- * records the dropped size under `_funnel_oversized`. Every stored payload
1858
- * therefore stays valid JSON.
1859
- */
1860
- declare class SqliteConnectorDiagnosticLog extends ConnectorDiagnosticLog {
1861
- private readonly raw;
1862
- private readonly processed;
1863
- private readonly connection;
1864
- private readonly now;
1865
- private readonly logger;
1866
- constructor(props: Props$1);
1867
- recordRaw(record: ConnectorRawRecord): void;
1868
- recordProcessed(record: ConnectorProcessedRecord): void;
1869
- recordConnection(record: ConnectorConnectionRecord): void;
1870
- private report;
1871
- queryRaw(query: ConnectorRawQuery): StoredRawEvent[];
1872
- queryProcessed(query: ConnectorProcessedQuery): StoredProcessedEvent[];
1873
- queryConnection(query: ConnectorConnectionQuery): StoredConnectionEvent[];
1874
- clear(): void;
1875
- close(): void;
1876
- }
1877
- //#endregion
1878
- //#region lib/gateway/memory-connector-diagnostic-log.d.ts
1879
- /**
1880
- * In-process `ConnectorDiagnosticLog` backed by one array per table. Used by tests
1881
- * and embedders that do not need durability. Like the SQLite log it keeps
1882
- * `seq` per-table (each array's 1-based position) and returns the most recent
1883
- * `limit` rows oldest-first; unlike it, it never prunes and never offloads
1884
- * oversized payloads — it keeps whatever the caller hands it, which is fine
1885
- * for the bounded volumes a test produces. Payload-validity is therefore a
1886
- * SQLite-only guarantee; do not write a test that leans on this double
1887
- * rejecting a malformed payload.
1888
- */
1889
- declare class MemoryConnectorDiagnosticLog extends ConnectorDiagnosticLog {
1890
- private readonly now;
1891
- private readonly raws;
1892
- private readonly processeds;
1893
- private readonly connections;
1894
- constructor(now?: () => number);
1895
- recordRaw(record: ConnectorRawRecord): void;
1896
- recordProcessed(record: ConnectorProcessedRecord): void;
1897
- recordConnection(record: ConnectorConnectionRecord): void;
1898
- queryRaw(query: ConnectorRawQuery): StoredRawEvent[];
1899
- queryProcessed(query: ConnectorProcessedQuery): StoredProcessedEvent[];
1900
- queryConnection(query: ConnectorConnectionQuery): StoredConnectionEvent[];
1901
- clear(): void;
1902
- close(): void;
1903
- }
1904
- //#endregion
1905
- //#region lib/gateway/connector-diagnostic-sql-reader.d.ts
1906
- type Props = {
1907
- /** SQLite file holding the raw (pre-filter) table. */rawPath: string; /** SQLite file holding the processed (verdict) table. */
1908
- processedPath: string; /** SQLite file holding the connection (lifecycle) table. */
1909
- connectionPath: string;
1910
- };
1911
- type Row = Record<string, unknown>;
1912
- /**
1913
- * Read-only SQL surface over the three diagnostic tables, for Claude to query
1914
- * the log with arbitrary `SELECT`s. It opens all files read-only and exposes
1915
- * three views — `raw`, `processed`, `connection` — that hide the storage
1916
- * details (the physical table is `leuco_log` and each row's columns live
1917
- * inside a JSON `event` blob): the views surface the columns as plain fields,
1918
- * with `payload` already pulled out of the nested JSON.
1919
- *
1920
- * The tables are separate files. `raw` and `processed` share an `event_id`,
1921
- * so a `JOIN` answers "the event arrived, but what verdict did it get?";
1922
- * `connection` answers the other half — "did the listener ever connect at
1923
- * all?". Writes are impossible: the connection is read-only and `query`
1924
- * rejects anything but a single `SELECT`.
1925
- */
1926
- declare class ConnectorDiagnosticSqlReader {
1927
- private readonly db;
1928
- constructor(props: Props);
1929
- /**
1930
- * Run one read-only `SELECT` and return the rows. Returns an `Error` (rather
1931
- * than throwing) for a non-SELECT statement or a SQL error, so the caller
1932
- * can surface the message without a stack trace.
1933
- */
1934
- query(sql: string): Row[] | Error;
1935
- close(): void;
1936
- }
1937
- //#endregion
1938
- //#region lib/cli/factory.d.ts
1939
- type Env = {
1940
- Variables: {
1941
- funnel: Funnel;
1942
- };
1943
- };
1944
- declare const factory: _$hono_factory0.Factory<Env, string>;
1945
- //#endregion
1946
- //#region lib/cli/router/to-request.d.ts
1947
- declare const toRequest: (args: string[]) => {
1948
- method: string;
1949
- path: string;
1950
- url: string;
1951
- };
1952
- //#endregion
1953
- //#region lib/cli/router/query-to-cli-args.d.ts
1954
- declare const queryToCliArgs: (url: string, reservedKeys?: string[]) => string[];
1955
- //#endregion
1956
- //#region lib/cli/routes/index.d.ts
1957
- /**
1958
- * Build the CLI Hono app wired to a specific Funnel instance.
1959
- * Exposed so library consumers can mount the same routes their `fnl` CLI
1960
- * uses against a custom Funnel (e.g. one with sandboxed boundaries).
1961
- *
1962
- * All CLI verbs (`add` / `remove` / `set` / `rename` / `as-default` / `request`) map to POST in
1963
- * to-request.ts and stay in the URL as a literal segment. Read paths (list / show / launch) keep GET.
1964
- * Help shortcuts at parameterless URLs return the help text directly so `funnel <verb>` (no args) is
1965
- * informative instead of 404.
1966
- */
1967
- declare const createCliApp: (funnel: Funnel) => _$hono_hono_base0.HonoBase<Env, {
1968
- "/claude": {
1969
- $get: {
1970
- input: {
1971
- query: {
1972
- [x: string]: string | string[];
1973
- profile?: string | undefined;
1974
- channel?: string | undefined;
1975
- };
1976
- };
1977
- output: string;
1978
- outputFormat: "text";
1979
- status: _$hono_utils_http_status0.ContentfulStatusCode;
1980
- } | {
1981
- input: {
1982
- query: {
1983
- [x: string]: string | string[];
1984
- profile?: string | undefined;
1985
- channel?: string | undefined;
1986
- };
1987
- };
1988
- output: "funnel claude — launch Claude Code\n\nusage:\n funnel claude launch the first channel from funnel.json, or the default profile\n funnel claude --channel <name> with funnel.json: select that channel; without: raw launch\n funnel claude -p <name> launch a named profile\n funnel claude --profile <name> (long form)\n funnel claude [...] any other argument is forwarded to the claude CLI\n\nresolution order:\n 1. --help print this help\n 2. --profile <name> named profile (ignores funnel.json)\n 3. ./funnel.json in the current directory + --channel selects (or first wins)\n 4. --channel <name> with no funnel.json → raw launch using an existing settings.json channel\n 5. the default profile (first entry in fnl profiles)\n\nfunnel-specific options (everything else passes through to claude verbatim):\n -p, --profile profile name to launch\n --channel channel name (selects from funnel.json, or raw-launches if no funnel.json)\n -h, --help show this help\n\nPositional args, unknown short flags (e.g. -c, -r), and claude's own flags\n(--agent, --resume, --model, --print, --output-format ...) are all forwarded.\nOn launch the FUNNEL_CHANNEL_ID env var is set and MCP connects to the gateway.";
1989
- outputFormat: "text";
1990
- status: _$hono_utils_http_status0.ContentfulStatusCode;
1991
- };
1992
- };
1993
- } & {
1994
- "/channels": {
1995
- $get: {
1996
- input: {
1997
- query: Record<string, never>;
1998
- };
1999
- output: string;
2000
- outputFormat: "text";
2001
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2002
- };
2003
- };
2004
- } & {
2005
- "/channels/add": {
2006
- $post: {
2007
- input: {};
2008
- output: string;
2009
- outputFormat: "text";
2010
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2011
- };
2012
- };
2013
- } & {
2014
- "/channels/add/:channel": {
2015
- $post: {
2016
- input: {
2017
- param: {
2018
- channel: string;
2019
- };
2020
- } & {
2021
- query: {
2022
- delivery?: "fanout" | "exclusive" | undefined;
2023
- };
2024
- };
2025
- output: string;
2026
- outputFormat: "text";
2027
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2028
- } | {
2029
- input: {
2030
- param: {
2031
- channel: string;
2032
- };
2033
- } & {
2034
- query: {
2035
- delivery?: "fanout" | "exclusive" | undefined;
2036
- };
2037
- };
2038
- output: `added channel "${string}" (id: ${string})`;
2039
- outputFormat: "text";
2040
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2041
- };
2042
- };
2043
- } & {
2044
- "/channels/remove": {
2045
- $post: {
2046
- input: {};
2047
- output: string;
2048
- outputFormat: "text";
2049
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2050
- };
2051
- };
2052
- } & {
2053
- "/channels/remove/:channel": {
2054
- $post: {
2055
- input: {
2056
- param: {
2057
- channel: string;
2058
- };
2059
- } & {
2060
- query: Record<string, never>;
2061
- };
2062
- output: string;
2063
- outputFormat: "text";
2064
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2065
- } | {
2066
- input: {
2067
- param: {
2068
- channel: string;
2069
- };
2070
- } & {
2071
- query: Record<string, never>;
2072
- };
2073
- output: `removed channel "${string}"`;
2074
- outputFormat: "text";
2075
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2076
- };
2077
- };
2078
- } & {
2079
- "/channels/rename/:channel/:newName": {
2080
- $post: {
2081
- input: {
2082
- param: {
2083
- channel: string;
2084
- newName: string;
2085
- };
2086
- } & {
2087
- query: Record<string, never>;
2088
- };
2089
- output: string;
2090
- outputFormat: "text";
2091
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2092
- } | {
2093
- input: {
2094
- param: {
2095
- channel: string;
2096
- newName: string;
2097
- };
2098
- } & {
2099
- query: Record<string, never>;
2100
- };
2101
- output: `renamed channel "${string}" to "${string}"`;
2102
- outputFormat: "text";
2103
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2104
- };
2105
- };
2106
- } & {
2107
- "/channels/:channel/rename/:newName": {
2108
- $post: {
2109
- input: {
2110
- param: {
2111
- channel: string;
2112
- newName: string;
2113
- };
2114
- } & {
2115
- query: Record<string, never>;
2116
- };
2117
- output: string;
2118
- outputFormat: "text";
2119
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2120
- } | {
2121
- input: {
2122
- param: {
2123
- channel: string;
2124
- newName: string;
2125
- };
2126
- } & {
2127
- query: Record<string, never>;
2128
- };
2129
- output: `renamed channel "${string}" to "${string}"`;
2130
- outputFormat: "text";
2131
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2132
- };
2133
- };
2134
- } & {
2135
- "/channels/rename": {
2136
- $post: {
2137
- input: {};
2138
- output: string;
2139
- outputFormat: "text";
2140
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2141
- };
2142
- };
2143
- } & {
2144
- "/channels/:channel/rename": {
2145
- $post: {
2146
- input: {
2147
- param: {
2148
- channel: string;
2149
- };
2150
- };
2151
- output: string;
2152
- outputFormat: "text";
2153
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2154
- };
2155
- };
2156
- } & {
2157
- "/channels/:channel/set/delivery/:mode": {
2158
- $post: {
2159
- input: {
2160
- param: {
2161
- channel: string;
2162
- mode: "fanout" | "exclusive";
2163
- };
2164
- };
2165
- output: string;
2166
- outputFormat: "text";
2167
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2168
- } | {
2169
- input: {
2170
- param: {
2171
- channel: string;
2172
- mode: "fanout" | "exclusive";
2173
- };
2174
- };
2175
- output: `channel "${string}" delivery set to fanout` | `channel "${string}" delivery set to exclusive`;
2176
- outputFormat: "text";
2177
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2178
- };
2179
- };
2180
- } & {
2181
- "/channels/publish": {
2182
- $post: {
2183
- input: {};
2184
- output: string;
2185
- outputFormat: "text";
2186
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2187
- };
2188
- };
2189
- } & {
2190
- "/channels/:channel/publish": {
2191
- $post: {
2192
- input: {
2193
- param: {
2194
- channel: string;
2195
- };
2196
- } & {
2197
- query: {
2198
- [x: string]: string | string[];
2199
- content: string | string[];
2200
- connector?: string | undefined;
2201
- };
2202
- };
2203
- output: string;
2204
- outputFormat: "text";
2205
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2206
- } | {
2207
- input: {
2208
- param: {
2209
- channel: string;
2210
- };
2211
- } & {
2212
- query: {
2213
- [x: string]: string | string[];
2214
- content: string | string[];
2215
- connector?: string | undefined;
2216
- };
2217
- };
2218
- output: `published (offset=${number})`;
2219
- outputFormat: "text";
2220
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2221
- };
2222
- };
2223
- } & {
2224
- "/channels/:channel": {
2225
- $get: {
2226
- input: {
2227
- param: {
2228
- channel: string;
2229
- };
2230
- } & {
2231
- query: Record<string, never>;
2232
- };
2233
- output: string;
2234
- outputFormat: "text";
2235
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2236
- };
2237
- };
2238
- } & {
2239
- "/channels/:channel/connectors": {
2240
- $get: {
2241
- input: {
2242
- param: {
2243
- channel: string;
2244
- };
2245
- } & {
2246
- query: Record<string, never>;
2247
- };
2248
- output: string;
2249
- outputFormat: "text";
2250
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2251
- };
2252
- };
2253
- } & {
2254
- "/channels/:channel/connectors/add": {
2255
- $post: {
2256
- input: {
2257
- param: {
2258
- channel: string;
2259
- };
2260
- };
2261
- output: string;
2262
- outputFormat: "text";
2263
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2264
- };
2265
- };
2266
- } & {
2267
- "/channels/:channel/connectors/add/:connector": {
2268
- $post: {
2269
- input: {
2270
- param: {
2271
- channel: string;
2272
- connector: string;
2273
- };
2274
- } & {
2275
- query: {
2276
- type: string | string[];
2277
- "bot-token": string | string[];
2278
- "app-token": string | string[];
2279
- } | {
2280
- type: string | string[];
2281
- "poll-interval"?: string | string[] | undefined;
2282
- } | {
2283
- type: string | string[];
2284
- "bot-token": string | string[];
2285
- } | {
2286
- type: string | string[];
2287
- };
2288
- };
2289
- output: string;
2290
- outputFormat: "text";
2291
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2292
- } | {
2293
- input: {
2294
- param: {
2295
- channel: string;
2296
- connector: string;
2297
- };
2298
- } & {
2299
- query: {
2300
- type: string | string[];
2301
- "bot-token": string | string[];
2302
- "app-token": string | string[];
2303
- } | {
2304
- type: string | string[];
2305
- "poll-interval"?: string | string[] | undefined;
2306
- } | {
2307
- type: string | string[];
2308
- "bot-token": string | string[];
2309
- } | {
2310
- type: string | string[];
2311
- };
2312
- };
2313
- output: `added slack connector "${string}" to channel "${string}"`;
2314
- outputFormat: "text";
2315
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2316
- } | {
2317
- input: {
2318
- param: {
2319
- channel: string;
2320
- connector: string;
2321
- };
2322
- } & {
2323
- query: {
2324
- type: string | string[];
2325
- "bot-token": string | string[];
2326
- "app-token": string | string[];
2327
- } | {
2328
- type: string | string[];
2329
- "poll-interval"?: string | string[] | undefined;
2330
- } | {
2331
- type: string | string[];
2332
- "bot-token": string | string[];
2333
- } | {
2334
- type: string | string[];
2335
- };
2336
- };
2337
- output: `added gh connector "${string}" to channel "${string}"`;
2338
- outputFormat: "text";
2339
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2340
- } | {
2341
- input: {
2342
- param: {
2343
- channel: string;
2344
- connector: string;
2345
- };
2346
- } & {
2347
- query: {
2348
- type: string | string[];
2349
- "bot-token": string | string[];
2350
- "app-token": string | string[];
2351
- } | {
2352
- type: string | string[];
2353
- "poll-interval"?: string | string[] | undefined;
2354
- } | {
2355
- type: string | string[];
2356
- "bot-token": string | string[];
2357
- } | {
2358
- type: string | string[];
2359
- };
2360
- };
2361
- output: `added discord connector "${string}" to channel "${string}"`;
2362
- outputFormat: "text";
2363
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2364
- } | {
2365
- input: {
2366
- param: {
2367
- channel: string;
2368
- connector: string;
2369
- };
2370
- } & {
2371
- query: {
2372
- type: string | string[];
2373
- "bot-token": string | string[];
2374
- "app-token": string | string[];
2375
- } | {
2376
- type: string | string[];
2377
- "poll-interval"?: string | string[] | undefined;
2378
- } | {
2379
- type: string | string[];
2380
- "bot-token": string | string[];
2381
- } | {
2382
- type: string | string[];
2383
- };
2384
- };
2385
- output: `added schedule connector "${string}" to channel "${string}"`;
2386
- outputFormat: "text";
2387
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2388
- };
2389
- };
2390
- } & {
2391
- "/channels/:channel/connectors/remove": {
2392
- $post: {
2393
- input: {
2394
- param: {
2395
- channel: string;
2396
- };
2397
- };
2398
- output: string;
2399
- outputFormat: "text";
2400
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2401
- };
2402
- };
2403
- } & {
2404
- "/channels/:channel/connectors/remove/:connector": {
2405
- $post: {
2406
- input: {
2407
- param: {
2408
- channel: string;
2409
- connector: string;
2410
- };
2411
- } & {
2412
- query: Record<string, never>;
2413
- };
2414
- output: string;
2415
- outputFormat: "text";
2416
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2417
- } | {
2418
- input: {
2419
- param: {
2420
- channel: string;
2421
- connector: string;
2422
- };
2423
- } & {
2424
- query: Record<string, never>;
2425
- };
2426
- output: `removed connector "${string}" from channel "${string}"`;
2427
- outputFormat: "text";
2428
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2429
- };
2430
- };
2431
- } & {
2432
- "/channels/:channel/connectors/set": {
2433
- $post: {
2434
- input: {
2435
- param: {
2436
- channel: string;
2437
- };
2438
- };
2439
- output: string;
2440
- outputFormat: "text";
2441
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2442
- };
2443
- };
2444
- } & {
2445
- "/channels/:channel/connectors/set/:connector": {
2446
- $post: {
2447
- input: {
2448
- param: {
2449
- channel: string;
2450
- connector: string;
2451
- };
2452
- } & {
2453
- query: {
2454
- [x: string]: string | string[];
2455
- "bot-token"?: string | undefined;
2456
- "app-token"?: string | undefined;
2457
- "poll-interval"?: string | string[] | undefined;
2458
- };
2459
- };
2460
- output: string;
2461
- outputFormat: "text";
2462
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2463
- } | {
2464
- input: {
2465
- param: {
2466
- channel: string;
2467
- connector: string;
2468
- };
2469
- } & {
2470
- query: {
2471
- [x: string]: string | string[];
2472
- "bot-token"?: string | undefined;
2473
- "app-token"?: string | undefined;
2474
- "poll-interval"?: string | string[] | undefined;
2475
- };
2476
- };
2477
- output: `updated connector "${string}" in channel "${string}"`;
2478
- outputFormat: "text";
2479
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2480
- };
2481
- };
2482
- } & {
2483
- "/channels/:channel/connectors/rename/:connector/:newName": {
2484
- $post: {
2485
- input: {
2486
- param: {
2487
- channel: string;
2488
- connector: string;
2489
- newName: string;
2490
- };
2491
- } & {
2492
- query: Record<string, never>;
2493
- };
2494
- output: string;
2495
- outputFormat: "text";
2496
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2497
- } | {
2498
- input: {
2499
- param: {
2500
- channel: string;
2501
- connector: string;
2502
- newName: string;
2503
- };
2504
- } & {
2505
- query: Record<string, never>;
2506
- };
2507
- output: `renamed connector "${string}" to "${string}"`;
2508
- outputFormat: "text";
2509
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2510
- };
2511
- };
2512
- } & {
2513
- "/channels/:channel/connectors/:connector/rename/:newName": {
2514
- $post: {
2515
- input: {
2516
- param: {
2517
- channel: string;
2518
- connector: string;
2519
- newName: string;
2520
- };
2521
- } & {
2522
- query: Record<string, never>;
2523
- };
2524
- output: string;
2525
- outputFormat: "text";
2526
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2527
- } | {
2528
- input: {
2529
- param: {
2530
- channel: string;
2531
- connector: string;
2532
- newName: string;
2533
- };
2534
- } & {
2535
- query: Record<string, never>;
2536
- };
2537
- output: `renamed connector "${string}" to "${string}"`;
2538
- outputFormat: "text";
2539
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2540
- };
2541
- };
2542
- } & {
2543
- "/channels/:channel/connectors/rename": {
2544
- $post: {
2545
- input: {
2546
- param: {
2547
- channel: string;
2548
- };
2549
- };
2550
- output: string;
2551
- outputFormat: "text";
2552
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2553
- };
2554
- };
2555
- } & {
2556
- "/channels/:channel/connectors/:connector/rename": {
2557
- $post: {
2558
- input: {
2559
- param: {
2560
- channel: string;
2561
- } & {
2562
- connector: string;
2563
- };
2564
- };
2565
- output: string;
2566
- outputFormat: "text";
2567
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2568
- };
2569
- };
2570
- } & {
2571
- "/channels/:channel/connectors/:connector/request": {
2572
- $post: {
2573
- input: {
2574
- param: {
2575
- channel: string;
2576
- connector: string;
2577
- };
2578
- } & {
2579
- query: {
2580
- [x: string]: string | string[];
2581
- method: string | string[];
2582
- };
2583
- };
2584
- output: string;
2585
- outputFormat: "text";
2586
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2587
- };
2588
- };
2589
- } & {
2590
- "/channels/:channel/connectors/:connector": {
2591
- $get: {
2592
- input: {
2593
- param: {
2594
- channel: string;
2595
- connector: string;
2596
- };
2597
- } & {
2598
- query: Record<string, never>;
2599
- };
2600
- output: string;
2601
- outputFormat: "text";
2602
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2603
- };
2604
- };
2605
- } & {
2606
- "/channels/:channel/connectors/:connector/schedules": {
2607
- $get: {
2608
- input: {
2609
- param: {
2610
- channel: string;
2611
- connector: string;
2612
- };
2613
- } & {
2614
- query: Record<string, never>;
2615
- };
2616
- output: string;
2617
- outputFormat: "text";
2618
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2619
- };
2620
- };
2621
- } & {
2622
- "/channels/:channel/connectors/:connector/schedules/add": {
2623
- $post: {
2624
- input: {
2625
- param: {
2626
- channel: string;
2627
- } & {
2628
- connector: string;
2629
- };
2630
- };
2631
- output: string;
2632
- outputFormat: "text";
2633
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2634
- };
2635
- };
2636
- } & {
2637
- "/channels/:channel/connectors/:connector/schedules/add/:id": {
2638
- $post: {
2639
- input: {
2640
- param: {
2641
- channel: string;
2642
- connector: string;
2643
- id: string;
2644
- };
2645
- } & {
2646
- query: {
2647
- cron: string | string[];
2648
- prompt: string | string[];
2649
- enabled?: string | string[] | undefined;
2650
- "catchup-policy"?: "latest" | "all" | "skip" | undefined;
2651
- };
2652
- };
2653
- output: string;
2654
- outputFormat: "text";
2655
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2656
- } | {
2657
- input: {
2658
- param: {
2659
- channel: string;
2660
- connector: string;
2661
- id: string;
2662
- };
2663
- } & {
2664
- query: {
2665
- cron: string | string[];
2666
- prompt: string | string[];
2667
- enabled?: string | string[] | undefined;
2668
- "catchup-policy"?: "latest" | "all" | "skip" | undefined;
2669
- };
2670
- };
2671
- output: `added schedule entry "${string}"`;
2672
- outputFormat: "text";
2673
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2674
- };
2675
- };
2676
- } & {
2677
- "/channels/:channel/connectors/:connector/schedules/remove": {
2678
- $post: {
2679
- input: {
2680
- param: {
2681
- channel: string;
2682
- } & {
2683
- connector: string;
2684
- };
2685
- };
2686
- output: string;
2687
- outputFormat: "text";
2688
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2689
- };
2690
- };
2691
- } & {
2692
- "/channels/:channel/connectors/:connector/schedules/remove/:id": {
2693
- $post: {
2694
- input: {
2695
- param: {
2696
- channel: string;
2697
- connector: string;
2698
- id: string;
2699
- };
2700
- } & {
2701
- query: Record<string, never>;
2702
- };
2703
- output: string;
2704
- outputFormat: "text";
2705
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2706
- } | {
2707
- input: {
2708
- param: {
2709
- channel: string;
2710
- connector: string;
2711
- id: string;
2712
- };
2713
- } & {
2714
- query: Record<string, never>;
2715
- };
2716
- output: `removed schedule entry "${string}"`;
2717
- outputFormat: "text";
2718
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2719
- };
2720
- };
2721
- } & {
2722
- "/profiles": {
2723
- $get: {
2724
- input: {
2725
- query: Record<string, never>;
2726
- };
2727
- output: string;
2728
- outputFormat: "text";
2729
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2730
- };
2731
- };
2732
- } & {
2733
- "/profiles/add": {
2734
- $post: {
2735
- input: {};
2736
- output: string;
2737
- outputFormat: "text";
2738
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2739
- };
2740
- };
2741
- } & {
2742
- "/profiles/add/:profile": {
2743
- $post: {
2744
- input: {
2745
- param: {
2746
- profile: string;
2747
- };
2748
- } & {
2749
- query: {
2750
- path: string;
2751
- channel: string;
2752
- agent?: string | undefined;
2753
- options?: string | undefined;
2754
- env?: string | undefined;
2755
- resume?: string | undefined;
2756
- "no-resume"?: string | undefined;
2757
- };
2758
- };
2759
- output: string;
2760
- outputFormat: "text";
2761
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2762
- } | {
2763
- input: {
2764
- param: {
2765
- profile: string;
2766
- };
2767
- } & {
2768
- query: {
2769
- path: string;
2770
- channel: string;
2771
- agent?: string | undefined;
2772
- options?: string | undefined;
2773
- env?: string | undefined;
2774
- resume?: string | undefined;
2775
- "no-resume"?: string | undefined;
2776
- };
2777
- };
2778
- output: `added profile "${string}"`;
2779
- outputFormat: "text";
2780
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2781
- };
2782
- };
2783
- } & {
2784
- "/profiles/set": {
2785
- $post: {
2786
- input: {};
2787
- output: string;
2788
- outputFormat: "text";
2789
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2790
- };
2791
- };
2792
- } & {
2793
- "/profiles/set/:profile": {
2794
- $post: {
2795
- input: {
2796
- param: {
2797
- profile: string;
2798
- };
2799
- } & {
2800
- query: {
2801
- path?: string | undefined;
2802
- channel?: string | undefined;
2803
- agent?: string | undefined;
2804
- options?: string | undefined;
2805
- env?: string | undefined;
2806
- resume?: string | undefined;
2807
- "no-resume"?: string | undefined;
2808
- };
2809
- };
2810
- output: string;
2811
- outputFormat: "text";
2812
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2813
- } | {
2814
- input: {
2815
- param: {
2816
- profile: string;
2817
- };
2818
- } & {
2819
- query: {
2820
- path?: string | undefined;
2821
- channel?: string | undefined;
2822
- agent?: string | undefined;
2823
- options?: string | undefined;
2824
- env?: string | undefined;
2825
- resume?: string | undefined;
2826
- "no-resume"?: string | undefined;
2827
- };
2828
- };
2829
- output: `updated profile "${string}"`;
2830
- outputFormat: "text";
2831
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2832
- };
2833
- };
2834
- } & {
2835
- "/profiles/remove": {
2836
- $post: {
2837
- input: {};
2838
- output: string;
2839
- outputFormat: "text";
2840
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2841
- };
2842
- };
2843
- } & {
2844
- "/profiles/remove/:profile": {
2845
- $post: {
2846
- input: {
2847
- param: {
2848
- profile: string;
2849
- };
2850
- } & {
2851
- query: Record<string, never>;
2852
- };
2853
- output: string;
2854
- outputFormat: "text";
2855
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2856
- } | {
2857
- input: {
2858
- param: {
2859
- profile: string;
2860
- };
2861
- } & {
2862
- query: Record<string, never>;
2863
- };
2864
- output: `removed profile "${string}"`;
2865
- outputFormat: "text";
2866
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2867
- };
2868
- };
2869
- } & {
2870
- "/profiles/rename/:profile/:newName": {
2871
- $post: {
2872
- input: {
2873
- param: {
2874
- profile: string;
2875
- newName: string;
2876
- };
2877
- } & {
2878
- query: Record<string, never>;
2879
- };
2880
- output: string;
2881
- outputFormat: "text";
2882
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2883
- } | {
2884
- input: {
2885
- param: {
2886
- profile: string;
2887
- newName: string;
2888
- };
2889
- } & {
2890
- query: Record<string, never>;
2891
- };
2892
- output: `renamed profile "${string}" to "${string}"`;
2893
- outputFormat: "text";
2894
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2895
- };
2896
- };
2897
- } & {
2898
- "/profiles/:profile/rename/:newName": {
2899
- $post: {
2900
- input: {
2901
- param: {
2902
- profile: string;
2903
- newName: string;
2904
- };
2905
- } & {
2906
- query: Record<string, never>;
2907
- };
2908
- output: string;
2909
- outputFormat: "text";
2910
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2911
- } | {
2912
- input: {
2913
- param: {
2914
- profile: string;
2915
- newName: string;
2916
- };
2917
- } & {
2918
- query: Record<string, never>;
2919
- };
2920
- output: `renamed profile "${string}" to "${string}"`;
2921
- outputFormat: "text";
2922
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2923
- };
2924
- };
2925
- } & {
2926
- "/profiles/rename": {
2927
- $post: {
2928
- input: {};
2929
- output: string;
2930
- outputFormat: "text";
2931
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2932
- };
2933
- };
2934
- } & {
2935
- "/profiles/:profile/rename": {
2936
- $post: {
2937
- input: {
2938
- param: {
2939
- profile: string;
2940
- };
2941
- };
2942
- output: string;
2943
- outputFormat: "text";
2944
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2945
- };
2946
- };
2947
- } & {
2948
- "/profiles/:profile/as-default": {
2949
- $post: {
2950
- input: {
2951
- param: {
2952
- profile: string;
2953
- };
2954
- } & {
2955
- query: Record<string, never>;
2956
- };
2957
- output: string;
2958
- outputFormat: "text";
2959
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2960
- } | {
2961
- input: {
2962
- param: {
2963
- profile: string;
2964
- };
2965
- } & {
2966
- query: Record<string, never>;
2967
- };
2968
- output: `profile "${string}" is now the default`;
2969
- outputFormat: "text";
2970
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2971
- };
2972
- };
2973
- } & {
2974
- "/profiles/:profile/run": {
2975
- $get: {
2976
- input: {
2977
- param: {
2978
- profile: string;
2979
- };
2980
- } & {
2981
- query: {
2982
- [x: string]: string | string[];
2983
- };
2984
- };
2985
- output: string;
2986
- outputFormat: "text";
2987
- status: _$hono_utils_http_status0.ContentfulStatusCode;
2988
- } | {
2989
- input: {
2990
- param: {
2991
- profile: string;
2992
- };
2993
- } & {
2994
- query: {
2995
- [x: string]: string | string[];
2996
- };
2997
- };
2998
- output: Promise<never>;
2999
- outputFormat: "json";
3000
- status: _$hono_utils_http_status0.StatusCode;
3001
- };
3002
- };
3003
- } & {
3004
- "/profiles/:profile": {
3005
- $get: {
3006
- input: {
3007
- param: {
3008
- profile: string;
3009
- };
3010
- } & {
3011
- query: {
3012
- [x: string]: string | string[];
3013
- };
3014
- };
3015
- output: string;
3016
- outputFormat: "text";
3017
- status: _$hono_utils_http_status0.ContentfulStatusCode;
3018
- } | {
3019
- input: {
3020
- param: {
3021
- profile: string;
3022
- };
3023
- } & {
3024
- query: {
3025
- [x: string]: string | string[];
3026
- };
3027
- };
3028
- output: Promise<never>;
3029
- outputFormat: "json";
3030
- status: _$hono_utils_http_status0.StatusCode;
3031
- };
3032
- };
3033
- } & {
3034
- "/gateway": {
3035
- $get: {
3036
- input: {};
3037
- output: string;
3038
- outputFormat: "text";
3039
- status: _$hono_utils_http_status0.ContentfulStatusCode;
3040
- } | {
3041
- input: {};
3042
- output: "funnel gateway: running (pid null) — health check failed" | `funnel gateway: running (pid ${number}) \u2014 health check failed`;
3043
- outputFormat: "text";
3044
- status: _$hono_utils_http_status0.ContentfulStatusCode;
3045
- } | {
3046
- input: {};
3047
- output: `funnel gateway: running (pid null)
3048
- port: ${number}
3049
- clients: ${string}` | `funnel gateway: running (pid ${number})
3050
- port: ${number}
3051
- clients: ${string}`;
3052
- outputFormat: "text";
3053
- status: _$hono_utils_http_status0.ContentfulStatusCode;
3054
- };
3055
- };
3056
- } & {
3057
- "/gateway/status": {
3058
- $get: {
3059
- input: {};
3060
- output: string;
3061
- outputFormat: "text";
3062
- status: _$hono_utils_http_status0.ContentfulStatusCode;
3063
- } | {
3064
- input: {};
3065
- output: "funnel gateway: running (pid null) — health check failed" | `funnel gateway: running (pid ${number}) \u2014 health check failed`;
3066
- outputFormat: "text";
3067
- status: _$hono_utils_http_status0.ContentfulStatusCode;
3068
- } | {
3069
- input: {};
3070
- output: `funnel gateway: running (pid null)
3071
- port: ${number}
3072
- clients: ${string}` | `funnel gateway: running (pid ${number})
3073
- port: ${number}
3074
- clients: ${string}`;
3075
- outputFormat: "text";
3076
- status: _$hono_utils_http_status0.ContentfulStatusCode;
3077
- };
3078
- };
3079
- } & {
3080
- "/gateway/start": {
3081
- $get: {
3082
- input: {
3083
- query: {
3084
- "no-caffeine"?: string | undefined;
3085
- };
3086
- };
3087
- output: string;
3088
- outputFormat: "text";
3089
- status: _$hono_utils_http_status0.ContentfulStatusCode;
3090
- } | {
3091
- input: {
3092
- query: {
3093
- "no-caffeine"?: string | undefined;
3094
- };
3095
- };
3096
- output: "funnel gateway: already running (pid null)" | `funnel gateway: already running (pid ${number})`;
3097
- outputFormat: "text";
3098
- status: _$hono_utils_http_status0.ContentfulStatusCode;
3099
- } | {
3100
- input: {
3101
- query: {
3102
- "no-caffeine"?: string | undefined;
3103
- };
3104
- };
3105
- output: "funnel gateway: started";
3106
- outputFormat: "text";
3107
- status: _$hono_utils_http_status0.ContentfulStatusCode;
3108
- };
3109
- };
3110
- } & {
3111
- "/gateway/stop": {
3112
- $get: {
3113
- input: {
3114
- query: Record<string, never>;
3115
- };
3116
- output: string;
3117
- outputFormat: "text";
3118
- status: _$hono_utils_http_status0.ContentfulStatusCode;
3119
- } | {
3120
- input: {
3121
- query: Record<string, never>;
3122
- };
3123
- output: "funnel gateway: no running process";
3124
- outputFormat: "text";
3125
- status: _$hono_utils_http_status0.ContentfulStatusCode;
3126
- } | {
3127
- input: {
3128
- query: Record<string, never>;
3129
- };
3130
- output: "funnel gateway: stopped";
3131
- outputFormat: "text";
3132
- status: _$hono_utils_http_status0.ContentfulStatusCode;
3133
- };
3134
- };
3135
- } & {
3136
- "/gateway/restart": {
3137
- $get: {
3138
- input: {
3139
- query: {
3140
- "no-caffeine"?: string | undefined;
3141
- };
3142
- };
3143
- output: string;
3144
- outputFormat: "text";
3145
- status: _$hono_utils_http_status0.ContentfulStatusCode;
3146
- };
3147
- };
3148
- } & {
3149
- "/gateway/run": {
3150
- $get: {
3151
- input: {
3152
- query: {
3153
- "no-caffeine"?: string | undefined;
3154
- };
3155
- };
3156
- output: string;
3157
- outputFormat: "text";
3158
- status: _$hono_utils_http_status0.ContentfulStatusCode;
3159
- } | {
3160
- input: {
3161
- query: {
3162
- "no-caffeine"?: string | undefined;
3163
- };
3164
- };
3165
- output: Promise<never>;
3166
- outputFormat: "json";
3167
- status: _$hono_utils_http_status0.StatusCode;
3168
- };
3169
- };
3170
- } & {
3171
- "/gateway/logs": {
3172
- $get: {
3173
- input: {
3174
- query: {
3175
- n?: string | undefined;
3176
- };
3177
- };
3178
- output: string;
3179
- outputFormat: "text";
3180
- status: _$hono_utils_http_status0.ContentfulStatusCode;
3181
- } | {
3182
- input: {
3183
- query: {
3184
- n?: string | undefined;
3185
- };
3186
- };
3187
- output: "no logs";
3188
- outputFormat: "text";
3189
- status: _$hono_utils_http_status0.ContentfulStatusCode;
3190
- };
3191
- };
3192
- } & {
3193
- "/gateway/sql": {
3194
- $get: {
3195
- input: {
3196
- query: {
3197
- query?: string | undefined;
3198
- };
3199
- };
3200
- output: string;
3201
- outputFormat: "text";
3202
- status: _$hono_utils_http_status0.ContentfulStatusCode;
3203
- };
3204
- };
3205
- } & {
3206
- "/gateway/listeners": {
3207
- $get: {
3208
- input: {
3209
- query: Record<string, never>;
3210
- };
3211
- output: string;
3212
- outputFormat: "text";
3213
- status: _$hono_utils_http_status0.ContentfulStatusCode;
3214
- } | {
3215
- input: {
3216
- query: Record<string, never>;
3217
- };
3218
- output: "funnel gateway: no running listeners";
3219
- outputFormat: "text";
3220
- status: _$hono_utils_http_status0.ContentfulStatusCode;
3221
- } | {
3222
- input: {
3223
- query: Record<string, never>;
3224
- };
3225
- output: `funnel gateway: running listeners
3226
- ${string}`;
3227
- outputFormat: "text";
3228
- status: _$hono_utils_http_status0.ContentfulStatusCode;
3229
- };
3230
- };
3231
- } & {
3232
- "/schema": {
3233
- $get: {
3234
- input: {
3235
- query: Record<string, never>;
3236
- };
3237
- output: string;
3238
- outputFormat: "text";
3239
- status: _$hono_utils_http_status0.ContentfulStatusCode;
3240
- } | {
3241
- input: {
3242
- query: Record<string, never>;
3243
- };
3244
- output: `${string}
3245
- `;
3246
- outputFormat: "text";
3247
- status: _$hono_utils_http_status0.ContentfulStatusCode;
3248
- };
3249
- };
3250
- } & {
3251
- "/status": {
3252
- $get: {
3253
- input: {
3254
- query: Record<string, never>;
3255
- };
3256
- output: string;
3257
- outputFormat: "text";
3258
- status: _$hono_utils_http_status0.ContentfulStatusCode;
3259
- };
3260
- };
3261
- } & {
3262
- "/update": {
3263
- $get: {
3264
- input: {
3265
- query: Record<string, never>;
3266
- };
3267
- output: string;
3268
- outputFormat: "text";
3269
- status: _$hono_utils_http_status0.ContentfulStatusCode;
3270
- } | {
3271
- input: {
3272
- query: Record<string, never>;
3273
- };
3274
- output: "updated @interactive-inc/claude-funnel";
3275
- outputFormat: "text";
3276
- status: _$hono_utils_http_status0.ContentfulStatusCode;
3277
- };
2095
+ * unmapped labels throw so the test surfaces unexpected prompts loudly.
2096
+ */
2097
+ declare class MemoryFunnelTokenPrompter extends FunnelTokenPrompter {
2098
+ private readonly answers;
2099
+ readonly asked: string[];
2100
+ constructor(props?: Props$3);
2101
+ promptSecret(label: string): Promise<string>;
2102
+ }
2103
+ //#endregion
2104
+ //#region lib/gateway/sqlite-funnel-event-log.d.ts
2105
+ type Props$2 = {
2106
+ /** SQLite database file path. Created on first write. ":memory:" for tests. */path: string; /** Override for tests. Defaults to `Date.now`. */
2107
+ now?: () => number; /** Optional row cap. Pruned on every insert. */
2108
+ maxRows?: number; /** Optional age cap in ms. Pruned on every insert. */
2109
+ maxAgeMs?: number; /** Optional on-disk byte cap. Checked periodically; on overflow the oldest rows are dropped toward targetBytes and the file is VACUUMed. */
2110
+ maxBytes?: number; /** Shrink target when maxBytes is exceeded. Defaults to maxBytes/4. */
2111
+ targetBytes?: number;
2112
+ };
2113
+ /**
2114
+ * SQLite-backed `FunnelEventLog`. One indexed table holds every broadcaster
2115
+ * event with `channel_id` and `connector_id` as dedicated columns, so
2116
+ * per-channel and per-connector replay is an indexed range scan.
2117
+ *
2118
+ * Concurrency: `seq` is `INTEGER PRIMARY KEY`, so SQLite assigns it
2119
+ * atomically. The broadcaster owns its own offset counter at runtime
2120
+ * (seeded from `findMaxOffset()` at startup); each broadcaster event
2121
+ * flows in here via `record()` with that pre-assigned offset, which the
2122
+ * sink stores via `write()` — PK uniqueness catches double-emit bugs.
2123
+ *
2124
+ * System events (gateway lifecycle, channel connect/disconnect, etc.) do
2125
+ * NOT go through this store. They are diagnostic only and live in
2126
+ * `FunnelLogger`'s file so the seq space here stays exclusive to
2127
+ * broadcaster traffic. This is what makes the broadcaster's seq seeding
2128
+ * (`getMaxSeq()` at startup) correct without per-event coordination.
2129
+ */
2130
+ declare class SqliteFunnelEventLog extends FunnelEventLog {
2131
+ private readonly sink;
2132
+ private readonly now;
2133
+ constructor(props: Props$2);
2134
+ /**
2135
+ * Persist a broadcaster-driven event with its assigned offset. Caller
2136
+ * (the gateway-server) supplies the offset from `broadcaster.broadcast()`
2137
+ * so this store and the broadcaster's in-memory ring stay aligned.
2138
+ */
2139
+ record(record: FunnelEventRecord): void;
2140
+ /**
2141
+ * Returns events with offset > since. Filtering by channel/connector is
2142
+ * the broadcaster's responsibility (it knows the client's subscription),
2143
+ * so this returns the full slice and lets the caller filter.
2144
+ */
2145
+ loadSince(since: number): ReplayableEvent[];
2146
+ /**
2147
+ * Returns events for one channel (and optionally one connector). Used
2148
+ * by the gateway logs CLI for scoped queries. Channel/connector filters
2149
+ * are indexed columns, so this is an indexed range scan.
2150
+ */
2151
+ loadForChannel(props: {
2152
+ channelId: string;
2153
+ connectorId?: string;
2154
+ sinceSeq?: number;
2155
+ limit?: number;
2156
+ }): ReplayableEvent[];
2157
+ findMaxOffset(): number;
2158
+ clear(): void;
2159
+ close(): void;
2160
+ }
2161
+ //#endregion
2162
+ //#region lib/gateway/memory-funnel-event-log.d.ts
2163
+ /**
2164
+ * In-process `FunnelEventLog` backed by a plain array. Used by tests and by
2165
+ * embedders that do not need durability — replay works within the process
2166
+ * lifetime but is lost when the process exits. Unlike the SQLite log it does
2167
+ * not truncate content or prune, so it is not meant for unbounded production
2168
+ * traffic.
2169
+ */
2170
+ declare class MemoryFunnelEventLog extends FunnelEventLog {
2171
+ private readonly events;
2172
+ constructor();
2173
+ record(record: FunnelEventRecord): void;
2174
+ loadSince(since: number): ReplayableEvent[];
2175
+ findMaxOffset(): number;
2176
+ clear(): void;
2177
+ close(): void;
2178
+ }
2179
+ //#endregion
2180
+ //#region lib/gateway/sqlite-connector-diagnostic-log.d.ts
2181
+ type Props$1 = {
2182
+ /** SQLite file for the raw (pre-filter) table. ":memory:" for tests. */rawPath: string; /** SQLite file for the processed (verdict) table. ":memory:" for tests. */
2183
+ processedPath: string; /** SQLite file for the connection (lifecycle) table. ":memory:" for tests. */
2184
+ connectionPath: string;
2185
+ now?: () => number; /** Row cap for the processed and connection tables. Pruned on every insert. */
2186
+ maxRows?: number;
2187
+ /**
2188
+ * Row cap for the raw table specifically. Raw rows can each hold up to
2189
+ * `RAW_PAYLOAD_CAP` bytes, so they want a tighter cap than the small
2190
+ * processed/connection verdict rows. Defaults to `maxRows` when unset.
2191
+ */
2192
+ rawMaxRows?: number; /** Age cap in ms for all tables — bounds how long untouched payloads (with PII) live. Pruned on every insert. */
2193
+ maxAgeMs?: number; /** When set, `insert()` errors (disk full, WAL lock) are logged instead of silently dropped. */
2194
+ logger?: FunnelLogger;
2195
+ };
2196
+ /**
2197
+ * Default `ConnectorDiagnosticLog`: three independent `LeucoLoggerSqliteSink`s, one
2198
+ * per table (raw / processed / connection), in separate files. Each sink
2199
+ * indexes the columns its queries filter on — `event_id` / `connector_id` /
2200
+ * `channel_id` for raw, plus `outcome` for processed and `status` for
2201
+ * connection — so those lookups are indexed scans (`type` is a fixed column
2202
+ * the sink extracts separately, not an index, so filtering by it is a scan).
2203
+ *
2204
+ * The raw table offloads any payload over `RAW_PAYLOAD_CAP`: rather than
2205
+ * truncating mid-string (which yields unparseable JSON), it replaces the
2206
+ * body with a small JSON object that keeps the diagnostic essentials and
2207
+ * records the dropped size under `_funnel_oversized`. Every stored payload
2208
+ * therefore stays valid JSON.
2209
+ */
2210
+ declare class SqliteConnectorDiagnosticLog extends ConnectorDiagnosticLog {
2211
+ private readonly raw;
2212
+ private readonly processed;
2213
+ private readonly connection;
2214
+ private readonly now;
2215
+ private readonly logger;
2216
+ constructor(props: Props$1);
2217
+ recordRaw(record: ConnectorRawRecord): void;
2218
+ recordProcessed(record: ConnectorProcessedRecord): void;
2219
+ recordConnection(record: ConnectorConnectionRecord): void;
2220
+ private report;
2221
+ queryRaw(query: ConnectorRawQuery): StoredRawEvent[];
2222
+ queryProcessed(query: ConnectorProcessedQuery): StoredProcessedEvent[];
2223
+ queryConnection(query: ConnectorConnectionQuery): StoredConnectionEvent[];
2224
+ clear(): void;
2225
+ close(): void;
2226
+ }
2227
+ //#endregion
2228
+ //#region lib/gateway/memory-connector-diagnostic-log.d.ts
2229
+ /**
2230
+ * In-process `ConnectorDiagnosticLog` backed by one array per table. Used by tests
2231
+ * and embedders that do not need durability. Like the SQLite log it keeps
2232
+ * `seq` per-table (each array's 1-based position) and returns the most recent
2233
+ * `limit` rows oldest-first; unlike it, it never prunes and never offloads
2234
+ * oversized payloads — it keeps whatever the caller hands it, which is fine
2235
+ * for the bounded volumes a test produces. Payload-validity is therefore a
2236
+ * SQLite-only guarantee; do not write a test that leans on this double
2237
+ * rejecting a malformed payload.
2238
+ */
2239
+ declare class MemoryConnectorDiagnosticLog extends ConnectorDiagnosticLog {
2240
+ private readonly now;
2241
+ private readonly raws;
2242
+ private readonly processeds;
2243
+ private readonly connections;
2244
+ constructor(now?: () => number);
2245
+ recordRaw(record: ConnectorRawRecord): void;
2246
+ recordProcessed(record: ConnectorProcessedRecord): void;
2247
+ recordConnection(record: ConnectorConnectionRecord): void;
2248
+ queryRaw(query: ConnectorRawQuery): StoredRawEvent[];
2249
+ queryProcessed(query: ConnectorProcessedQuery): StoredProcessedEvent[];
2250
+ queryConnection(query: ConnectorConnectionQuery): StoredConnectionEvent[];
2251
+ clear(): void;
2252
+ close(): void;
2253
+ }
2254
+ //#endregion
2255
+ //#region lib/gateway/connector-diagnostic-sql-reader.d.ts
2256
+ type Props = {
2257
+ /** SQLite file holding the raw (pre-filter) table. */rawPath: string; /** SQLite file holding the processed (verdict) table. */
2258
+ processedPath: string; /** SQLite file holding the connection (lifecycle) table. */
2259
+ connectionPath: string;
2260
+ };
2261
+ type Row = Record<string, unknown>;
2262
+ /**
2263
+ * Read-only SQL surface over the three diagnostic tables, for Claude to query
2264
+ * the log with arbitrary `SELECT`s. It opens all files read-only and exposes
2265
+ * three views — `raw`, `processed`, `connection` — that hide the storage
2266
+ * details (the physical table is `leuco_log` and each row's columns live
2267
+ * inside a JSON `event` blob): the views surface the columns as plain fields,
2268
+ * with `payload` already pulled out of the nested JSON.
2269
+ *
2270
+ * The tables are separate files. `raw` and `processed` share an `event_id`,
2271
+ * so a `JOIN` answers "the event arrived, but what verdict did it get?";
2272
+ * `connection` answers the other half — "did the listener ever connect at
2273
+ * all?". Writes are impossible: the connection is read-only and `query`
2274
+ * rejects anything but a single `SELECT`.
2275
+ */
2276
+ declare class ConnectorDiagnosticSqlReader {
2277
+ private readonly db;
2278
+ constructor(props: Props);
2279
+ /**
2280
+ * Run one read-only `SELECT` and return the rows. Returns an `Error` (rather
2281
+ * than throwing) for a non-SELECT statement or a SQL error, so the caller
2282
+ * can surface the message without a stack trace.
2283
+ */
2284
+ query(sql: string, params?: (string | number | null)[]): Row[] | Error;
2285
+ close(): void;
2286
+ }
2287
+ //#endregion
2288
+ //#region lib/cli/factory.d.ts
2289
+ type Env = {
2290
+ Bindings: {
2291
+ funnel: Funnel;
3278
2292
  };
3279
- }, "/", "/update">;
3280
- /** CLI Hono app wired to a default `new Funnel()`. For embedding with a custom Funnel use `createCliApp`. */
3281
- declare const app: _$hono_hono_base0.HonoBase<Env, {
2293
+ };
2294
+ declare const factory: _$hono_factory0.Factory<Env, string>;
2295
+ //#endregion
2296
+ //#region lib/cli/router/to-request.d.ts
2297
+ declare const toRequest: (args: string[]) => {
2298
+ method: string;
2299
+ path: string;
2300
+ url: string;
2301
+ };
2302
+ //#endregion
2303
+ //#region lib/cli/router/query-to-cli-args.d.ts
2304
+ declare const queryToCliArgs: (url: string, reservedKeys?: string[]) => string[];
2305
+ //#endregion
2306
+ //#region lib/cli/routes/index.d.ts
2307
+ declare const routes: _$hono_hono_base0.HonoBase<Env, {
3282
2308
  "/claude": {
3283
2309
  $get: {
3284
2310
  input: {
@@ -3308,18 +2334,38 @@ declare const app: _$hono_hono_base0.HonoBase<Env, {
3308
2334
  "/channels": {
3309
2335
  $get: {
3310
2336
  input: {
3311
- query: Record<string, never>;
2337
+ query: {
2338
+ json?: "" | "true" | "false" | undefined;
2339
+ };
3312
2340
  };
3313
2341
  output: string;
3314
2342
  outputFormat: "text";
3315
2343
  status: _$hono_utils_http_status0.ContentfulStatusCode;
2344
+ } | {
2345
+ input: {
2346
+ query: {
2347
+ json?: "" | "true" | "false" | undefined;
2348
+ };
2349
+ };
2350
+ output: {
2351
+ id: string;
2352
+ name: string;
2353
+ delivery: "fanout" | "exclusive";
2354
+ connectors: {
2355
+ id: string;
2356
+ name: string;
2357
+ type: "schedule" | "slack" | "gh" | "discord";
2358
+ }[];
2359
+ }[];
2360
+ outputFormat: "json";
2361
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
3316
2362
  };
3317
2363
  };
3318
2364
  } & {
3319
2365
  "/channels/add": {
3320
2366
  $post: {
3321
2367
  input: {};
3322
- output: string;
2368
+ output: "funnel channels add — add a channel\n\nusage: funnel channels add <name> [--delivery fanout|exclusive]\n\noptions:\n --delivery routing mode (default fanout):\n fanout every connected client receives every event\n exclusive each event delivered to exactly one client (round-robin)";
3323
2369
  outputFormat: "text";
3324
2370
  status: _$hono_utils_http_status0.ContentfulStatusCode;
3325
2371
  };
@@ -3358,7 +2404,7 @@ declare const app: _$hono_hono_base0.HonoBase<Env, {
3358
2404
  "/channels/remove": {
3359
2405
  $post: {
3360
2406
  input: {};
3361
- output: string;
2407
+ output: "funnel channels remove — remove a channel\n\nusage: funnel channels remove <name>";
3362
2408
  outputFormat: "text";
3363
2409
  status: _$hono_utils_http_status0.ContentfulStatusCode;
3364
2410
  };
@@ -3449,7 +2495,7 @@ declare const app: _$hono_hono_base0.HonoBase<Env, {
3449
2495
  "/channels/rename": {
3450
2496
  $post: {
3451
2497
  input: {};
3452
- output: string;
2498
+ output: "funnel channels rename — rename a channel\n\nusage:\n funnel channels rename <old> <new>\n funnel channels <old> rename <new>";
3453
2499
  outputFormat: "text";
3454
2500
  status: _$hono_utils_http_status0.ContentfulStatusCode;
3455
2501
  };
@@ -3462,7 +2508,7 @@ declare const app: _$hono_hono_base0.HonoBase<Env, {
3462
2508
  channel: string;
3463
2509
  };
3464
2510
  };
3465
- output: string;
2511
+ output: "funnel channels rename — rename a channel\n\nusage:\n funnel channels rename <old> <new>\n funnel channels <old> rename <new>";
3466
2512
  outputFormat: "text";
3467
2513
  status: _$hono_utils_http_status0.ContentfulStatusCode;
3468
2514
  };
@@ -3495,7 +2541,7 @@ declare const app: _$hono_hono_base0.HonoBase<Env, {
3495
2541
  "/channels/publish": {
3496
2542
  $post: {
3497
2543
  input: {};
3498
- output: string;
2544
+ output: "funnel channels <channel> publish — push arbitrary content into a channel\n\nusage: funnel channels <channel> publish --content=\"<text>\" [--connector=<name>] [--meta-<key>=<value> ...]\n\noptions:\n --content Required. The event body delivered to subscribers.\n --connector Optional. Stamp the event with a connector name (resolved to id when found).\n --meta-<key> Optional. Repeatable. Added to meta. Example: --meta-source=cron";
3499
2545
  outputFormat: "text";
3500
2546
  status: _$hono_utils_http_status0.ContentfulStatusCode;
3501
2547
  };
@@ -3534,6 +2580,53 @@ declare const app: _$hono_hono_base0.HonoBase<Env, {
3534
2580
  status: _$hono_utils_http_status0.ContentfulStatusCode;
3535
2581
  };
3536
2582
  };
2583
+ } & {
2584
+ "/channels/:channel/validate": {
2585
+ $get: {
2586
+ input: {
2587
+ param: {
2588
+ channel: string;
2589
+ };
2590
+ } & {
2591
+ query: {
2592
+ json?: "" | "true" | "false" | undefined;
2593
+ };
2594
+ };
2595
+ output: string;
2596
+ outputFormat: "text";
2597
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
2598
+ } | {
2599
+ input: {
2600
+ param: {
2601
+ channel: string;
2602
+ };
2603
+ } & {
2604
+ query: {
2605
+ json?: "" | "true" | "false" | undefined;
2606
+ };
2607
+ };
2608
+ output: {
2609
+ channel: string;
2610
+ valid: boolean;
2611
+ issues: {
2612
+ connector: string;
2613
+ field: string;
2614
+ message: string;
2615
+ }[];
2616
+ };
2617
+ outputFormat: "json";
2618
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
2619
+ };
2620
+ };
2621
+ } & {
2622
+ "/channels/validate": {
2623
+ $get: {
2624
+ input: {};
2625
+ output: "funnel channels <channel> validate — check connector configuration\n\nusage: funnel channels <channel> validate [--json]\n\noptions:\n --json output as JSON\n\nChecks that each connector has the required tokens and fields set.\nDoes not make any network calls — static config check only.\n\nexamples:\n funnel channels open-karte validate\n funnel channels open-karte validate --json";
2626
+ outputFormat: "text";
2627
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
2628
+ };
2629
+ };
3537
2630
  } & {
3538
2631
  "/channels/:channel": {
3539
2632
  $get: {
@@ -3572,7 +2665,7 @@ declare const app: _$hono_hono_base0.HonoBase<Env, {
3572
2665
  channel: string;
3573
2666
  };
3574
2667
  };
3575
- output: string;
2668
+ output: "funnel channels <channel> connectors add <connector> — add a connector to a channel\n\nusage:\n funnel channels <channel> connectors add <connector> --type=slack --bot-token=xoxb-... --app-token=xapp-...\n funnel channels <channel> connectors add <connector> --type=gh [--poll-interval=60]\n funnel channels <channel> connectors add <connector> --type=discord --bot-token=...\n funnel channels <channel> connectors add <connector> --type=schedule\n\nToken uniqueness is enforced across all channels.";
3576
2669
  outputFormat: "text";
3577
2670
  status: _$hono_utils_http_status0.ContentfulStatusCode;
3578
2671
  };
@@ -3709,7 +2802,7 @@ declare const app: _$hono_hono_base0.HonoBase<Env, {
3709
2802
  channel: string;
3710
2803
  };
3711
2804
  };
3712
- output: string;
2805
+ output: "funnel channels <channel> connectors remove <connector> — remove a connector\n\nusage: funnel channels <channel> connectors remove <connector>";
3713
2806
  outputFormat: "text";
3714
2807
  status: _$hono_utils_http_status0.ContentfulStatusCode;
3715
2808
  };
@@ -3750,7 +2843,7 @@ declare const app: _$hono_hono_base0.HonoBase<Env, {
3750
2843
  channel: string;
3751
2844
  };
3752
2845
  };
3753
- output: string;
2846
+ output: "funnel channels <channel> connectors set <connector> — update connector fields\n\nusage:\n funnel channels <ch> connectors set <conn> [--bot-token=...] [--app-token=...] # slack\n funnel channels <ch> connectors set <conn> [--bot-token=...] # discord\n funnel channels <ch> connectors set <conn> [--poll-interval=N] # gh";
3754
2847
  outputFormat: "text";
3755
2848
  status: _$hono_utils_http_status0.ContentfulStatusCode;
3756
2849
  };
@@ -3861,7 +2954,7 @@ declare const app: _$hono_hono_base0.HonoBase<Env, {
3861
2954
  channel: string;
3862
2955
  };
3863
2956
  };
3864
- output: string;
2957
+ output: "funnel channels <channel> connectors rename <connector> <new-name>\n\nusage: funnel channels <channel> connectors rename <connector> <new-name>";
3865
2958
  outputFormat: "text";
3866
2959
  status: _$hono_utils_http_status0.ContentfulStatusCode;
3867
2960
  };
@@ -3876,7 +2969,7 @@ declare const app: _$hono_hono_base0.HonoBase<Env, {
3876
2969
  connector: string;
3877
2970
  };
3878
2971
  };
3879
- output: string;
2972
+ output: "funnel channels <channel> connectors rename <connector> <new-name>\n\nusage: funnel channels <channel> connectors rename <connector> <new-name>";
3880
2973
  outputFormat: "text";
3881
2974
  status: _$hono_utils_http_status0.ContentfulStatusCode;
3882
2975
  };
@@ -3942,7 +3035,7 @@ declare const app: _$hono_hono_base0.HonoBase<Env, {
3942
3035
  connector: string;
3943
3036
  };
3944
3037
  };
3945
- output: string;
3038
+ output: "funnel channels <ch> connectors <conn> schedules add <id> — add a schedule entry\n\nusage: funnel channels <ch> connectors <conn> schedules add <id> --cron=\"*/5 * * * *\" --prompt=\"...\" [--enabled=true] [--catchup-policy=latest|all|skip]";
3946
3039
  outputFormat: "text";
3947
3040
  status: _$hono_utils_http_status0.ContentfulStatusCode;
3948
3041
  };
@@ -3997,7 +3090,7 @@ declare const app: _$hono_hono_base0.HonoBase<Env, {
3997
3090
  connector: string;
3998
3091
  };
3999
3092
  };
4000
- output: string;
3093
+ output: "funnel channels <ch> connectors <conn> schedules remove <id>\n\nusage: funnel channels <ch> connectors <conn> schedules remove <id>";
4001
3094
  outputFormat: "text";
4002
3095
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4003
3096
  };
@@ -4047,7 +3140,7 @@ declare const app: _$hono_hono_base0.HonoBase<Env, {
4047
3140
  "/profiles/add": {
4048
3141
  $post: {
4049
3142
  input: {};
4050
- output: string;
3143
+ output: "funnel profiles add — add a profile\n\nusage: funnel profiles add <name> --path <path> --channel <channel-name> [recipe]\n\noptions:\n --path working directory passed to claude as cwd\n --channel channel name (resolved to channel id internally)\n --agent sub-agent name, prepended to the launch argv as --agent <name>\n --options extra launch argv as one whitespace-split string (e.g. \"--brief\")\n --env env vars layered under the process, as \"KEY=VAL,KEY2=VAL2\"\n --no-resume start a fresh claude session every launch (default resumes)\n\nThe launch recipe (--agent / --options / --env / --resume) lives on the\nprofile; the channel only declares transport (connectors / delivery).";
4051
3144
  outputFormat: "text";
4052
3145
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4053
3146
  };
@@ -4098,7 +3191,7 @@ declare const app: _$hono_hono_base0.HonoBase<Env, {
4098
3191
  "/profiles/set": {
4099
3192
  $post: {
4100
3193
  input: {};
4101
- output: string;
3194
+ output: "funnel profiles <name> set — update a profile\n\nusage: funnel profiles <name> set [--path <path>] [--channel <channel-name>] [recipe]\n\noptions:\n --path working directory passed to claude as cwd\n --channel channel name (resolved to channel id internally)\n --agent sub-agent name, prepended to the launch argv as --agent <name>\n --options extra launch argv as one whitespace-split string (e.g. \"--brief\")\n --env env vars layered under the process, as \"KEY=VAL,KEY2=VAL2\"\n --resume / --no-resume toggle claude session reuse\n\nOnly the flags you pass are changed; --agent and --options together replace\nthe profile's whole options list.";
4102
3195
  outputFormat: "text";
4103
3196
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4104
3197
  };
@@ -4149,7 +3242,7 @@ declare const app: _$hono_hono_base0.HonoBase<Env, {
4149
3242
  "/profiles/remove": {
4150
3243
  $post: {
4151
3244
  input: {};
4152
- output: string;
3245
+ output: "funnel profiles remove — remove a profile\n\nusage: funnel profiles remove <name>";
4153
3246
  outputFormat: "text";
4154
3247
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4155
3248
  };
@@ -4240,7 +3333,7 @@ declare const app: _$hono_hono_base0.HonoBase<Env, {
4240
3333
  "/profiles/rename": {
4241
3334
  $post: {
4242
3335
  input: {};
4243
- output: string;
3336
+ output: "funnel profiles rename — rename a profile\n\nusage:\n funnel profiles rename <old> <new>\n funnel profiles <old> rename <new>";
4244
3337
  outputFormat: "text";
4245
3338
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4246
3339
  };
@@ -4253,19 +3346,266 @@ declare const app: _$hono_hono_base0.HonoBase<Env, {
4253
3346
  profile: string;
4254
3347
  };
4255
3348
  };
3349
+ output: "funnel profiles rename — rename a profile\n\nusage:\n funnel profiles rename <old> <new>\n funnel profiles <old> rename <new>";
3350
+ outputFormat: "text";
3351
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
3352
+ };
3353
+ };
3354
+ } & {
3355
+ "/profiles/:profile/as-default": {
3356
+ $post: {
3357
+ input: {
3358
+ param: {
3359
+ profile: string;
3360
+ };
3361
+ } & {
3362
+ query: Record<string, never>;
3363
+ };
3364
+ output: string;
3365
+ outputFormat: "text";
3366
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
3367
+ } | {
3368
+ input: {
3369
+ param: {
3370
+ profile: string;
3371
+ };
3372
+ } & {
3373
+ query: Record<string, never>;
3374
+ };
3375
+ output: `profile "${string}" is now the default`;
3376
+ outputFormat: "text";
3377
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
3378
+ };
3379
+ };
3380
+ } & {
3381
+ "/profiles/:profile/run": {
3382
+ $get: {
3383
+ input: {
3384
+ param: {
3385
+ profile: string;
3386
+ };
3387
+ } & {
3388
+ query: {
3389
+ [x: string]: string | string[];
3390
+ };
3391
+ };
3392
+ output: string;
3393
+ outputFormat: "text";
3394
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
3395
+ } | {
3396
+ input: {
3397
+ param: {
3398
+ profile: string;
3399
+ };
3400
+ } & {
3401
+ query: {
3402
+ [x: string]: string | string[];
3403
+ };
3404
+ };
3405
+ output: Promise<never>;
3406
+ outputFormat: "json";
3407
+ status: _$hono_utils_http_status0.StatusCode;
3408
+ };
3409
+ };
3410
+ } & {
3411
+ "/profiles/:profile": {
3412
+ $get: {
3413
+ input: {
3414
+ param: {
3415
+ profile: string;
3416
+ };
3417
+ } & {
3418
+ query: {
3419
+ [x: string]: string | string[];
3420
+ };
3421
+ };
3422
+ output: string;
3423
+ outputFormat: "text";
3424
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
3425
+ } | {
3426
+ input: {
3427
+ param: {
3428
+ profile: string;
3429
+ };
3430
+ } & {
3431
+ query: {
3432
+ [x: string]: string | string[];
3433
+ };
3434
+ };
3435
+ output: Promise<never>;
3436
+ outputFormat: "json";
3437
+ status: _$hono_utils_http_status0.StatusCode;
3438
+ };
3439
+ };
3440
+ } & {
3441
+ "/gateway": {
3442
+ $get: {
3443
+ input: {};
3444
+ output: string;
3445
+ outputFormat: "text";
3446
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
3447
+ };
3448
+ };
3449
+ } & {
3450
+ "/gateway/status": {
3451
+ $get: {
3452
+ input: {
3453
+ query: {
3454
+ json?: "" | "true" | "false" | undefined;
3455
+ };
3456
+ };
3457
+ output: string;
3458
+ outputFormat: "text";
3459
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
3460
+ } | {
3461
+ input: {
3462
+ query: {
3463
+ json?: "" | "true" | "false" | undefined;
3464
+ };
3465
+ };
3466
+ output: {
3467
+ running: true;
3468
+ port: number;
3469
+ };
3470
+ outputFormat: "json";
3471
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
3472
+ };
3473
+ };
3474
+ } & {
3475
+ "/gateway/start": {
3476
+ $get: {
3477
+ input: {
3478
+ query: {
3479
+ "no-caffeine"?: string | undefined;
3480
+ };
3481
+ };
3482
+ output: string;
3483
+ outputFormat: "text";
3484
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
3485
+ } | {
3486
+ input: {
3487
+ query: {
3488
+ "no-caffeine"?: string | undefined;
3489
+ };
3490
+ };
3491
+ output: "funnel gateway: already running (pid null)" | `funnel gateway: already running (pid ${number})`;
3492
+ outputFormat: "text";
3493
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
3494
+ } | {
3495
+ input: {
3496
+ query: {
3497
+ "no-caffeine"?: string | undefined;
3498
+ };
3499
+ };
3500
+ output: "funnel gateway: started";
3501
+ outputFormat: "text";
3502
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
3503
+ };
3504
+ };
3505
+ } & {
3506
+ "/gateway/stop": {
3507
+ $get: {
3508
+ input: {
3509
+ query: Record<string, never>;
3510
+ };
3511
+ output: string;
3512
+ outputFormat: "text";
3513
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
3514
+ } | {
3515
+ input: {
3516
+ query: Record<string, never>;
3517
+ };
3518
+ output: "funnel gateway: no running process";
3519
+ outputFormat: "text";
3520
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
3521
+ } | {
3522
+ input: {
3523
+ query: Record<string, never>;
3524
+ };
3525
+ output: "funnel gateway: stopped";
3526
+ outputFormat: "text";
3527
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
3528
+ };
3529
+ };
3530
+ } & {
3531
+ "/gateway/restart": {
3532
+ $get: {
3533
+ input: {
3534
+ query: {
3535
+ "no-caffeine"?: string | undefined;
3536
+ };
3537
+ };
3538
+ output: string;
3539
+ outputFormat: "text";
3540
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
3541
+ };
3542
+ };
3543
+ } & {
3544
+ "/gateway/run": {
3545
+ $get: {
3546
+ input: {
3547
+ query: {
3548
+ "no-caffeine"?: string | undefined;
3549
+ };
3550
+ };
3551
+ output: string;
3552
+ outputFormat: "text";
3553
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
3554
+ } | {
3555
+ input: {
3556
+ query: {
3557
+ "no-caffeine"?: string | undefined;
3558
+ };
3559
+ };
3560
+ output: Promise<never>;
3561
+ outputFormat: "json";
3562
+ status: _$hono_utils_http_status0.StatusCode;
3563
+ };
3564
+ };
3565
+ } & {
3566
+ "/gateway/logs": {
3567
+ $get: {
3568
+ input: {
3569
+ query: {
3570
+ n?: string | undefined;
3571
+ format?: "json" | "plain" | undefined;
3572
+ };
3573
+ };
3574
+ output: string;
3575
+ outputFormat: "text";
3576
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
3577
+ } | {
3578
+ input: {
3579
+ query: {
3580
+ n?: string | undefined;
3581
+ format?: "json" | "plain" | undefined;
3582
+ };
3583
+ };
3584
+ output: "no logs";
3585
+ outputFormat: "text";
3586
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
3587
+ };
3588
+ };
3589
+ } & {
3590
+ "/gateway/sql": {
3591
+ $get: {
3592
+ input: {
3593
+ query: {
3594
+ query?: string | undefined;
3595
+ preset?: string | undefined;
3596
+ channel?: string | undefined;
3597
+ limit?: string | undefined;
3598
+ };
3599
+ };
4256
3600
  output: string;
4257
3601
  outputFormat: "text";
4258
3602
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4259
3603
  };
4260
3604
  };
4261
3605
  } & {
4262
- "/profiles/:profile/as-default": {
4263
- $post: {
3606
+ "/gateway/listeners": {
3607
+ $get: {
4264
3608
  input: {
4265
- param: {
4266
- profile: string;
4267
- };
4268
- } & {
4269
3609
  query: Record<string, never>;
4270
3610
  };
4271
3611
  output: string;
@@ -4273,27 +3613,30 @@ declare const app: _$hono_hono_base0.HonoBase<Env, {
4273
3613
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4274
3614
  } | {
4275
3615
  input: {
4276
- param: {
4277
- profile: string;
4278
- };
4279
- } & {
4280
3616
  query: Record<string, never>;
4281
3617
  };
4282
- output: `profile "${string}" is now the default`;
3618
+ output: "funnel gateway: no running listeners";
3619
+ outputFormat: "text";
3620
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
3621
+ } | {
3622
+ input: {
3623
+ query: Record<string, never>;
3624
+ };
3625
+ output: `funnel gateway: running listeners
3626
+ ${string}`;
4283
3627
  outputFormat: "text";
4284
3628
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4285
3629
  };
4286
3630
  };
4287
3631
  } & {
4288
- "/profiles/:profile/run": {
3632
+ "/debug": {
4289
3633
  $get: {
4290
3634
  input: {
4291
- param: {
4292
- profile: string;
4293
- };
4294
- } & {
4295
3635
  query: {
4296
- [x: string]: string | string[];
3636
+ channel?: string | undefined;
3637
+ all?: "" | "true" | "false" | undefined;
3638
+ json?: "" | "true" | "false" | undefined;
3639
+ limit?: string | undefined;
4297
3640
  };
4298
3641
  };
4299
3642
  output: string;
@@ -4301,101 +3644,246 @@ declare const app: _$hono_hono_base0.HonoBase<Env, {
4301
3644
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4302
3645
  } | {
4303
3646
  input: {
4304
- param: {
4305
- profile: string;
4306
- };
4307
- } & {
4308
3647
  query: {
4309
- [x: string]: string | string[];
3648
+ channel?: string | undefined;
3649
+ all?: "" | "true" | "false" | undefined;
3650
+ json?: "" | "true" | "false" | undefined;
3651
+ limit?: string | undefined;
4310
3652
  };
4311
3653
  };
4312
- output: Promise<never>;
3654
+ output: {
3655
+ error: string;
3656
+ nextAction: string;
3657
+ };
4313
3658
  outputFormat: "json";
4314
- status: _$hono_utils_http_status0.StatusCode;
4315
- };
4316
- };
4317
- } & {
4318
- "/profiles/:profile": {
4319
- $get: {
3659
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
3660
+ } | {
4320
3661
  input: {
4321
- param: {
4322
- profile: string;
4323
- };
4324
- } & {
4325
3662
  query: {
4326
- [x: string]: string | string[];
4327
- };
3663
+ channel?: string | undefined;
3664
+ all?: "" | "true" | "false" | undefined;
3665
+ json?: "" | "true" | "false" | undefined;
3666
+ limit?: string | undefined;
3667
+ };
3668
+ };
3669
+ output: {
3670
+ summary: {
3671
+ total: number;
3672
+ ok: number;
3673
+ warn: number;
3674
+ error: number;
3675
+ criticalChannels: string[];
3676
+ warnChannels: string[];
3677
+ suggestedActions: string[];
3678
+ };
3679
+ channels: {
3680
+ channel: string;
3681
+ gateway: {
3682
+ running: boolean;
3683
+ pid: number | null;
3684
+ port: number | null;
3685
+ uptimeMs: number | null;
3686
+ };
3687
+ listeners: {
3688
+ name: string;
3689
+ type: string;
3690
+ alive: boolean;
3691
+ events: number;
3692
+ errors: number;
3693
+ lastEventAt: string | null;
3694
+ }[];
3695
+ claudeClients: number;
3696
+ channelId: string;
3697
+ recentEvents: {
3698
+ seq: number | null;
3699
+ ts: number | null;
3700
+ type: string;
3701
+ outcome: string;
3702
+ eventId: string | null;
3703
+ payload: string | null;
3704
+ payloadParsed: {
3705
+ [x: string]: _$hono_utils_types0.JSONValue;
3706
+ } | null;
3707
+ preview: string | null;
3708
+ }[];
3709
+ connectionErrors: {
3710
+ seq: number | null;
3711
+ ts: number | null;
3712
+ type: string;
3713
+ status: string;
3714
+ detail: string | null;
3715
+ }[];
3716
+ diagnosis: {
3717
+ status: "ok" | "warn" | "error";
3718
+ message: string;
3719
+ nextActions: string[];
3720
+ rootCause: string | null;
3721
+ };
3722
+ }[];
4328
3723
  };
4329
- output: string;
4330
- outputFormat: "text";
3724
+ outputFormat: "json";
4331
3725
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4332
3726
  } | {
4333
3727
  input: {
4334
- param: {
4335
- profile: string;
4336
- };
4337
- } & {
4338
3728
  query: {
4339
- [x: string]: string | string[];
3729
+ channel?: string | undefined;
3730
+ all?: "" | "true" | "false" | undefined;
3731
+ json?: "" | "true" | "false" | undefined;
3732
+ limit?: string | undefined;
4340
3733
  };
4341
3734
  };
4342
- output: Promise<never>;
3735
+ output: {
3736
+ error: string;
3737
+ availableChannels: string[];
3738
+ };
4343
3739
  outputFormat: "json";
4344
- status: _$hono_utils_http_status0.StatusCode;
4345
- };
4346
- };
4347
- } & {
4348
- "/gateway": {
4349
- $get: {
4350
- input: {};
4351
- output: string;
4352
- outputFormat: "text";
4353
3740
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4354
3741
  } | {
4355
- input: {};
4356
- output: "funnel gateway: running (pid null) — health check failed" | `funnel gateway: running (pid ${number}) \u2014 health check failed`;
4357
- outputFormat: "text";
3742
+ input: {
3743
+ query: {
3744
+ channel?: string | undefined;
3745
+ all?: "" | "true" | "false" | undefined;
3746
+ json?: "" | "true" | "false" | undefined;
3747
+ limit?: string | undefined;
3748
+ };
3749
+ };
3750
+ output: {
3751
+ error: string;
3752
+ channels: string[];
3753
+ hint: string;
3754
+ };
3755
+ outputFormat: "json";
4358
3756
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4359
3757
  } | {
4360
- input: {};
4361
- output: `funnel gateway: running (pid null)
4362
- port: ${number}
4363
- clients: ${string}` | `funnel gateway: running (pid ${number})
4364
- port: ${number}
4365
- clients: ${string}`;
4366
- outputFormat: "text";
3758
+ input: {
3759
+ query: {
3760
+ channel?: string | undefined;
3761
+ all?: "" | "true" | "false" | undefined;
3762
+ json?: "" | "true" | "false" | undefined;
3763
+ limit?: string | undefined;
3764
+ };
3765
+ };
3766
+ output: {
3767
+ channel: string;
3768
+ gateway: {
3769
+ running: boolean;
3770
+ pid: number | null;
3771
+ port: number | null;
3772
+ uptimeMs: number | null;
3773
+ };
3774
+ listeners: {
3775
+ name: string;
3776
+ type: string;
3777
+ alive: boolean;
3778
+ events: number;
3779
+ errors: number;
3780
+ lastEventAt: string | null;
3781
+ }[];
3782
+ claudeClients: number;
3783
+ channelId: string;
3784
+ recentEvents: {
3785
+ seq: number | null;
3786
+ ts: number | null;
3787
+ type: string;
3788
+ outcome: string;
3789
+ eventId: string | null;
3790
+ payload: string | null;
3791
+ payloadParsed: {
3792
+ [x: string]: _$hono_utils_types0.JSONValue;
3793
+ } | null;
3794
+ preview: string | null;
3795
+ }[];
3796
+ connectionErrors: {
3797
+ seq: number | null;
3798
+ ts: number | null;
3799
+ type: string;
3800
+ status: string;
3801
+ detail: string | null;
3802
+ }[];
3803
+ diagnosis: {
3804
+ status: "ok" | "warn" | "error";
3805
+ message: string;
3806
+ nextActions: string[];
3807
+ rootCause: string | null;
3808
+ };
3809
+ };
3810
+ outputFormat: "json";
4367
3811
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4368
3812
  };
4369
3813
  };
4370
3814
  } & {
4371
- "/gateway/status": {
3815
+ "/debug/events": {
4372
3816
  $get: {
4373
- input: {};
3817
+ input: {
3818
+ query: {
3819
+ channel?: string | undefined;
3820
+ limit?: string | undefined;
3821
+ json?: "" | "true" | "false" | undefined;
3822
+ };
3823
+ };
4374
3824
  output: string;
4375
3825
  outputFormat: "text";
4376
3826
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4377
3827
  } | {
4378
- input: {};
4379
- output: "funnel gateway: running (pid null) — health check failed" | `funnel gateway: running (pid ${number}) \u2014 health check failed`;
4380
- outputFormat: "text";
3828
+ input: {
3829
+ query: {
3830
+ channel?: string | undefined;
3831
+ limit?: string | undefined;
3832
+ json?: "" | "true" | "false" | undefined;
3833
+ };
3834
+ };
3835
+ output: {
3836
+ error: string;
3837
+ availableChannels: string[];
3838
+ };
3839
+ outputFormat: "json";
4381
3840
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4382
3841
  } | {
4383
- input: {};
4384
- output: `funnel gateway: running (pid null)
4385
- port: ${number}
4386
- clients: ${string}` | `funnel gateway: running (pid ${number})
4387
- port: ${number}
4388
- clients: ${string}`;
4389
- outputFormat: "text";
3842
+ input: {
3843
+ query: {
3844
+ channel?: string | undefined;
3845
+ limit?: string | undefined;
3846
+ json?: "" | "true" | "false" | undefined;
3847
+ };
3848
+ };
3849
+ output: {
3850
+ error: string;
3851
+ channels: string[];
3852
+ };
3853
+ outputFormat: "json";
3854
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
3855
+ } | {
3856
+ input: {
3857
+ query: {
3858
+ channel?: string | undefined;
3859
+ limit?: string | undefined;
3860
+ json?: "" | "true" | "false" | undefined;
3861
+ };
3862
+ };
3863
+ output: {
3864
+ seq: number | null;
3865
+ ts: number | null;
3866
+ type: string;
3867
+ outcome: string;
3868
+ eventId: string | null;
3869
+ payload: string | null;
3870
+ payloadParsed: {
3871
+ [x: string]: _$hono_utils_types0.JSONValue;
3872
+ } | null;
3873
+ preview: string | null;
3874
+ }[];
3875
+ outputFormat: "json";
4390
3876
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4391
3877
  };
4392
3878
  };
4393
3879
  } & {
4394
- "/gateway/start": {
3880
+ "/debug/dropped": {
4395
3881
  $get: {
4396
3882
  input: {
4397
3883
  query: {
4398
- "no-caffeine"?: string | undefined;
3884
+ channel?: string | undefined;
3885
+ limit?: string | undefined;
3886
+ json?: "" | "true" | "false" | undefined;
4399
3887
  };
4400
3888
  };
4401
3889
  output: string;
@@ -4404,140 +3892,244 @@ declare const app: _$hono_hono_base0.HonoBase<Env, {
4404
3892
  } | {
4405
3893
  input: {
4406
3894
  query: {
4407
- "no-caffeine"?: string | undefined;
3895
+ channel?: string | undefined;
3896
+ limit?: string | undefined;
3897
+ json?: "" | "true" | "false" | undefined;
3898
+ };
3899
+ };
3900
+ output: {
3901
+ seq: number | null;
3902
+ ts: number | null;
3903
+ type: string;
3904
+ outcome: string;
3905
+ eventId: string | null;
3906
+ payload: string | null;
3907
+ payloadParsed: {
3908
+ [x: string]: _$hono_utils_types0.JSONValue;
3909
+ } | null;
3910
+ preview: string | null;
3911
+ }[];
3912
+ outputFormat: "json";
3913
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
3914
+ } | {
3915
+ input: {
3916
+ query: {
3917
+ channel?: string | undefined;
3918
+ limit?: string | undefined;
3919
+ json?: "" | "true" | "false" | undefined;
4408
3920
  };
4409
3921
  };
4410
- output: "funnel gateway: already running (pid null)" | `funnel gateway: already running (pid ${number})`;
4411
- outputFormat: "text";
3922
+ output: {
3923
+ error: string;
3924
+ availableChannels: string[];
3925
+ };
3926
+ outputFormat: "json";
4412
3927
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4413
3928
  } | {
4414
3929
  input: {
4415
3930
  query: {
4416
- "no-caffeine"?: string | undefined;
3931
+ channel?: string | undefined;
3932
+ limit?: string | undefined;
3933
+ json?: "" | "true" | "false" | undefined;
4417
3934
  };
4418
3935
  };
4419
- output: "funnel gateway: started";
4420
- outputFormat: "text";
3936
+ output: {
3937
+ error: string;
3938
+ channels: string[];
3939
+ };
3940
+ outputFormat: "json";
4421
3941
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4422
3942
  };
4423
3943
  };
4424
3944
  } & {
4425
- "/gateway/stop": {
3945
+ "/debug/errors": {
4426
3946
  $get: {
4427
3947
  input: {
4428
- query: Record<string, never>;
3948
+ query: {
3949
+ channel?: string | undefined;
3950
+ limit?: string | undefined;
3951
+ json?: "" | "true" | "false" | undefined;
3952
+ };
4429
3953
  };
4430
3954
  output: string;
4431
3955
  outputFormat: "text";
4432
3956
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4433
3957
  } | {
4434
3958
  input: {
4435
- query: Record<string, never>;
3959
+ query: {
3960
+ channel?: string | undefined;
3961
+ limit?: string | undefined;
3962
+ json?: "" | "true" | "false" | undefined;
3963
+ };
4436
3964
  };
4437
- output: "funnel gateway: no running process";
4438
- outputFormat: "text";
3965
+ output: {
3966
+ error: string;
3967
+ availableChannels: string[];
3968
+ };
3969
+ outputFormat: "json";
4439
3970
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4440
3971
  } | {
4441
3972
  input: {
4442
- query: Record<string, never>;
3973
+ query: {
3974
+ channel?: string | undefined;
3975
+ limit?: string | undefined;
3976
+ json?: "" | "true" | "false" | undefined;
3977
+ };
4443
3978
  };
4444
- output: "funnel gateway: stopped";
4445
- outputFormat: "text";
3979
+ output: {
3980
+ error: string;
3981
+ channels: string[];
3982
+ };
3983
+ outputFormat: "json";
3984
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
3985
+ } | {
3986
+ input: {
3987
+ query: {
3988
+ channel?: string | undefined;
3989
+ limit?: string | undefined;
3990
+ json?: "" | "true" | "false" | undefined;
3991
+ };
3992
+ };
3993
+ output: {
3994
+ seq: number | null;
3995
+ ts: number | null;
3996
+ type: string;
3997
+ status: string;
3998
+ detail: string | null;
3999
+ }[];
4000
+ outputFormat: "json";
4446
4001
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4447
4002
  };
4448
4003
  };
4449
4004
  } & {
4450
- "/gateway/restart": {
4005
+ "/debug/replay": {
4451
4006
  $get: {
4452
4007
  input: {
4453
4008
  query: {
4454
- "no-caffeine"?: string | undefined;
4009
+ channel?: string | undefined;
4010
+ seq?: string | undefined;
4011
+ json?: "" | "true" | "false" | undefined;
4455
4012
  };
4456
4013
  };
4457
4014
  output: string;
4458
4015
  outputFormat: "text";
4459
4016
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4460
- };
4461
- };
4462
- } & {
4463
- "/gateway/run": {
4464
- $get: {
4017
+ } | {
4465
4018
  input: {
4466
4019
  query: {
4467
- "no-caffeine"?: string | undefined;
4020
+ channel?: string | undefined;
4021
+ seq?: string | undefined;
4022
+ json?: "" | "true" | "false" | undefined;
4468
4023
  };
4469
4024
  };
4470
- output: string;
4025
+ output: `error: ${string}`;
4471
4026
  outputFormat: "text";
4472
4027
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4473
4028
  } | {
4474
4029
  input: {
4475
4030
  query: {
4476
- "no-caffeine"?: string | undefined;
4031
+ channel?: string | undefined;
4032
+ seq?: string | undefined;
4033
+ json?: "" | "true" | "false" | undefined;
4477
4034
  };
4478
4035
  };
4479
- output: Promise<never>;
4480
- outputFormat: "json";
4481
- status: _$hono_utils_http_status0.StatusCode;
4482
- };
4483
- };
4484
- } & {
4485
- "/gateway/logs": {
4486
- $get: {
4036
+ output: `channel not found: ${string}`;
4037
+ outputFormat: "text";
4038
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
4039
+ } | {
4487
4040
  input: {
4488
4041
  query: {
4489
- n?: string | undefined;
4042
+ channel?: string | undefined;
4043
+ seq?: string | undefined;
4044
+ json?: "" | "true" | "false" | undefined;
4490
4045
  };
4491
4046
  };
4492
- output: string;
4047
+ output: "no diagnostic store yet (start the gateway first)";
4493
4048
  outputFormat: "text";
4494
4049
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4495
4050
  } | {
4496
4051
  input: {
4497
4052
  query: {
4498
- n?: string | undefined;
4053
+ channel?: string | undefined;
4054
+ seq?: string | undefined;
4055
+ json?: "" | "true" | "false" | undefined;
4499
4056
  };
4500
4057
  };
4501
- output: "no logs";
4058
+ output: `multiple channels \u2014 specify one with --channel:
4059
+ ${string}`;
4502
4060
  outputFormat: "text";
4503
4061
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4504
- };
4505
- };
4506
- } & {
4507
- "/gateway/sql": {
4508
- $get: {
4062
+ } | {
4509
4063
  input: {
4510
4064
  query: {
4511
- query?: string | undefined;
4065
+ channel?: string | undefined;
4066
+ seq?: string | undefined;
4067
+ json?: "" | "true" | "false" | undefined;
4512
4068
  };
4513
4069
  };
4514
- output: string;
4070
+ output: "no channels configured";
4515
4071
  outputFormat: "text";
4516
4072
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4517
- };
4518
- };
4519
- } & {
4520
- "/gateway/listeners": {
4521
- $get: {
4073
+ } | {
4522
4074
  input: {
4523
- query: Record<string, never>;
4075
+ query: {
4076
+ channel?: string | undefined;
4077
+ seq?: string | undefined;
4078
+ json?: "" | "true" | "false" | undefined;
4079
+ };
4524
4080
  };
4525
- output: string;
4081
+ output: {
4082
+ error: string;
4083
+ };
4084
+ outputFormat: "json";
4085
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
4086
+ } | {
4087
+ input: {
4088
+ query: {
4089
+ channel?: string | undefined;
4090
+ seq?: string | undefined;
4091
+ json?: "" | "true" | "false" | undefined;
4092
+ };
4093
+ };
4094
+ output: "no matching event found";
4526
4095
  outputFormat: "text";
4527
4096
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4528
4097
  } | {
4529
4098
  input: {
4530
- query: Record<string, never>;
4099
+ query: {
4100
+ channel?: string | undefined;
4101
+ seq?: string | undefined;
4102
+ json?: "" | "true" | "false" | undefined;
4103
+ };
4531
4104
  };
4532
- output: "funnel gateway: no running listeners";
4105
+ output: "event has no payload to replay";
4533
4106
  outputFormat: "text";
4534
4107
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4535
4108
  } | {
4536
4109
  input: {
4537
- query: Record<string, never>;
4110
+ query: {
4111
+ channel?: string | undefined;
4112
+ seq?: string | undefined;
4113
+ json?: "" | "true" | "false" | undefined;
4114
+ };
4538
4115
  };
4539
- output: `funnel gateway: running listeners
4540
- ${string}`;
4116
+ output: {
4117
+ replayed: true;
4118
+ seq: number | null;
4119
+ offset: number;
4120
+ preview: string | null;
4121
+ };
4122
+ outputFormat: "json";
4123
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
4124
+ } | {
4125
+ input: {
4126
+ query: {
4127
+ channel?: string | undefined;
4128
+ seq?: string | undefined;
4129
+ json?: "" | "true" | "false" | undefined;
4130
+ };
4131
+ };
4132
+ output: `replayed seq=${number} \u2192 offset=${number}${string}` | `replayed seq=? \u2192 offset=${number}${string}`;
4541
4133
  outputFormat: "text";
4542
4134
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4543
4135
  };
@@ -4565,7 +4157,10 @@ ${string}`;
4565
4157
  "/status": {
4566
4158
  $get: {
4567
4159
  input: {
4568
- query: Record<string, never>;
4160
+ query: {
4161
+ watch?: "" | "true" | "false" | undefined;
4162
+ interval?: string | undefined;
4163
+ };
4569
4164
  };
4570
4165
  output: string;
4571
4166
  outputFormat: "text";
@@ -4591,5 +4186,6 @@ ${string}`;
4591
4186
  };
4592
4187
  };
4593
4188
  }, "/", "/update">;
4189
+ type CliApp = typeof routes;
4594
4190
  //#endregion
4595
- export { AliveStub, AttachOptions, BroadcastEvent, BroadcastSubscriber, CONNECTOR_CONNECTION_STATUSES, ChannelConfig, ChannelConnectorView, ChannelDeliveryMode, ChannelServerOptions, ChannelSpec, ConnectorConfig, ConnectorConnectionEvent, ConnectorConnectionQuery, ConnectorConnectionRecord, ConnectorConnectionStatus, ConnectorDiagnosticLog, ConnectorDiagnosticSqlReader, ConnectorProcessedEvent, ConnectorProcessedQuery, ConnectorProcessedRecord, ConnectorQuery, ConnectorRawEvent, ConnectorRawQuery, ConnectorRawRecord, ConnectorSpec, ConnectorSyncOutcome, ConnectorType, DEFAULT_GATEWAY_PORT, DEFAULT_GATEWAY_TOKEN_PATH, DetachOptions, DiscordConnectorConfig, Env, FUNNEL_DIR, FUNNEL_MCP_ARGS, FUNNEL_MCP_COMMAND, FUNNEL_MCP_NAME, FileStat, Funnel, FunnelBroadcaster, FunnelChannelPublisher, FunnelChannels, FunnelClaude, FunnelClock, FunnelConnectorFactory, FunnelConnectorListener, FunnelEvent, FunnelEventLog, FunnelEventRecord, FunnelFileSystem, FunnelGateway, FunnelGatewayServer, FunnelGatewayToken, FunnelIdGenerator, FunnelListenerSupervisor, FunnelListenersClient, FunnelLocalConfig, FunnelLocalConfigSync, FunnelLocalConfigWriter, FunnelLogger, FunnelMcp, FunnelProcessRunner, FunnelProfiles, FunnelSettingsReader, FunnelSettingsStore, FunnelSlackEventProcessor, FunnelTokenPrompter, type GatewayEmitInput, type GatewayRouteDeps, type Env$1 as GatewayServerEnv, GhConnectorConfig, LOCAL_CONFIG_FILENAME, LaunchOptions, ListListenersResult, ListenerEntry, ListenerOpResult, LocalConfig, LocalConfigSyncResult, LogEntry, MemoryConnectorDiagnosticLog, MemoryFunnelClock, MemoryFunnelEventLog, MemoryFunnelFileSystem, MemoryFunnelIdGenerator, MemoryFunnelLogger, MemoryFunnelProcessRunner, MemoryFunnelTokenPrompter, MemoryProcessCall, MemoryProcessHandler, MemoryProcessResponse, MemoryProcessSyncHandler, MockFunnelSettingsReader, NodeFunnelClock, NodeFunnelFileSystem, NodeFunnelIdGenerator, NodeFunnelLogger, NodeFunnelProcessRunner, NodeFunnelTokenPrompter, NoopFunnelLogger, NotifyFn, OnFunnelError, ProcessListStub, ProcessSnapshot, ProfileConfig, ProfileSpec, PublishRequest, PublishResponse, PublishResult, ReplayableEvent, RunOptions, RunResult, SETTINGS_PATH, SETTINGS_VERSION, ScheduleCatchupPolicy, ScheduleConnectorConfig, ScheduleEntry, ScheduleListenerOptions, Settings, SlackConnectorConfig, SlackListenerOptions, SlackProcessed, SlackProcessedEmit, SlackProcessedSkip, SlackRawEvent, SlackSkipReason, SqliteConnectorDiagnosticLog, SqliteFunnelEventLog, StoredConnectionEvent, StoredProcessedEvent, StoredRawEvent, channelConfigSchema, channelDeliveryModeSchema, channelSpecSchema, app as cliApp, connectorConfigSchema, connectorConnectionEventSchema, connectorProcessedEventSchema, connectorRawEventSchema, connectorSpecSchema, createCliApp, createSettings, discordConnectorSchema, factory, funnelEventSchema, funnelJsonSchema, ghConnectorSchema, localConfigSchema, profileConfigSchema, profileSpecSchema, publishRequestSchema, publishResponseSchema, queryToCliArgs, resolveFunnelDir, resolveFunnelPort, scheduleCatchupPolicySchema, scheduleConnectorSchema, scheduleEntrySchema, settingsSchema, slackConnectorSchema, startChannelServer, toRequest };
4191
+ export { AliveStub, AttachOptions, BroadcastEvent, BroadcastSubscriber, CONNECTOR_CONNECTION_STATUSES, ChannelConfig, ChannelConnectorView, ChannelDeliveryMode, ChannelServerOptions, ChannelSpec, type CliApp, ConnectorConfig, ConnectorConnectionEvent, ConnectorConnectionQuery, ConnectorConnectionRecord, ConnectorConnectionStatus, ConnectorDiagnosticLog, ConnectorDiagnosticSqlReader, ConnectorProcessedEvent, ConnectorProcessedQuery, ConnectorProcessedRecord, ConnectorQuery, ConnectorRawEvent, ConnectorRawQuery, ConnectorRawRecord, ConnectorSpec, ConnectorSyncOutcome, ConnectorType, DEFAULT_GATEWAY_PORT, DEFAULT_GATEWAY_TOKEN_PATH, DetachOptions, DiscordConnectorConfig, Env, FUNNEL_DIR, FUNNEL_MCP_ARGS, FUNNEL_MCP_COMMAND, FUNNEL_MCP_NAME, FileStat, Funnel, FunnelBroadcaster, FunnelChannelPublisher, FunnelChannels, FunnelClaude, FunnelClock, FunnelConnectorFactory, FunnelConnectorListener, type FunnelDebugReport, FunnelEvent, FunnelEventLog, FunnelEventRecord, FunnelFileSystem, FunnelGateway, FunnelGatewayServer, FunnelGatewayToken, FunnelIdGenerator, FunnelListenerSupervisor, FunnelListenersClient, FunnelLocalConfig, FunnelLocalConfigSync, FunnelLocalConfigWriter, FunnelLogger, FunnelMcp, FunnelProcessRunner, FunnelProfiles, FunnelSettingsReader, FunnelSettingsStore, FunnelSlackEventProcessor, FunnelTokenPrompter, type GatewayApp, type GatewayEmitInput, type GatewayRouteDeps, type Env$1 as GatewayServerEnv, GhConnectorConfig, LOCAL_CONFIG_FILENAME, LaunchOptions, ListListenersResult, ListenerEntry, ListenerOpResult, LocalConfig, LocalConfigSyncResult, LogEntry, MemoryConnectorDiagnosticLog, MemoryFunnelClock, MemoryFunnelEventLog, MemoryFunnelFileSystem, MemoryFunnelIdGenerator, MemoryFunnelLogger, MemoryFunnelProcessRunner, MemoryFunnelTokenPrompter, MemoryProcessCall, MemoryProcessHandler, MemoryProcessResponse, MemoryProcessSyncHandler, MockFunnelSettingsReader, NodeFunnelClock, NodeFunnelFileSystem, NodeFunnelIdGenerator, NodeFunnelLogger, NodeFunnelProcessRunner, NodeFunnelTokenPrompter, NoopFunnelLogger, NotifyFn, OnFunnelError, ProcessListStub, ProcessSnapshot, ProfileConfig, ProfileSpec, PublishRequest, PublishResponse, PublishResult, ReplayableEvent, RunOptions, RunResult, SETTINGS_PATH, SETTINGS_VERSION, ScheduleCatchupPolicy, ScheduleConnectorConfig, ScheduleEntry, ScheduleListenerOptions, Settings, SlackConnectorConfig, SlackListenerOptions, SlackProcessed, SlackProcessedEmit, SlackProcessedSkip, SlackRawEvent, SlackSkipReason, SqliteConnectorDiagnosticLog, SqliteFunnelEventLog, StoredConnectionEvent, StoredProcessedEvent, StoredRawEvent, channelConfigSchema, channelDeliveryModeSchema, channelSpecSchema, routes as cliRoutes, connectorConfigSchema, connectorConnectionEventSchema, connectorProcessedEventSchema, connectorRawEventSchema, connectorSpecSchema, createSettings, discordConnectorSchema, factory, funnelEventSchema, funnelJsonSchema, ghConnectorSchema, localConfigSchema, profileConfigSchema, profileSpecSchema, publishRequestSchema, publishResponseSchema, queryToCliArgs, resolveFunnelDir, resolveFunnelPort, scheduleCatchupPolicySchema, scheduleConnectorSchema, scheduleEntrySchema, settingsSchema, slackConnectorSchema, startChannelServer, toRequest };