@interactive-inc/claude-funnel 0.41.0 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -9
- package/dist/bin.js +255 -256
- package/dist/claude-CB1WkV77.d.ts +115 -0
- package/dist/claude.d.ts +59 -0
- package/dist/claude.js +322 -0
- package/dist/{connector-diagnostic-log-OPpPi9V9.d.ts → connector-diagnostic-log-yTOojKUR.d.ts} +14 -14
- package/dist/{logger-Czli2OKh.js → connector-listener-DU54DN-f.js} +1 -9
- package/dist/connectors/discord.d.ts +3 -3
- package/dist/connectors/discord.js +2 -1
- package/dist/connectors/gh.d.ts +4 -3
- package/dist/connectors/gh.js +2 -1
- package/dist/connectors/schedule.d.ts +1 -1
- package/dist/connectors/schedule.js +2 -1
- package/dist/connectors/slack.d.ts +2 -2
- package/dist/connectors/slack.js +2 -1
- package/dist/discord-connector-schema-CBDyGdOI.js +21 -0
- package/dist/{discord-connector-schema-BeThExJp.js → discord-listener-_jSE3HsQ.js} +2 -22
- package/dist/file-system-BeOKXjlV.d.ts +26 -0
- package/dist/file-system-PWKKU7lA.js +9 -0
- package/dist/gateway/daemon.js +151 -152
- package/dist/gateway.d.ts +3 -0
- package/dist/gateway.js +2 -0
- package/dist/gh-connector-schema-eoTtHbY6.d.ts +14 -0
- package/dist/{gh-connector-schema-eYE4g77K.js → gh-connector-schema-o3Q1-ojL.js} +1 -176
- package/dist/gh-listener-DH-fClQm.js +178 -0
- package/dist/index-ChomoTZ5.d.ts +3404 -0
- package/dist/index.d.ts +11 -4214
- package/dist/index.js +195 -3869
- package/dist/local-config-json-schema-8IHjS4Q7.js +439 -0
- package/dist/local-config-sync-BdsrDZOu.d.ts +381 -0
- package/dist/local-config.d.ts +3 -0
- package/dist/local-config.js +3 -0
- package/dist/logger-BP6SisKt.js +9 -0
- package/dist/mcp-Dr-nIBwN.js +253 -0
- package/dist/memory-connector-diagnostic-log-CrW1ltLM.js +2245 -0
- package/dist/memory-token-prompter-B5FFCsGP.d.ts +57 -0
- package/dist/memory-token-prompter-CLerGsgM.js +61 -0
- package/dist/node-file-system-BcrmWN9I.js +48 -0
- package/dist/{gh-connector-schema-CQmEWzdV.d.ts → process-runner-DfniuWVU.d.ts} +1 -14
- package/dist/profiles-f0mNmEyP.d.ts +64 -0
- package/dist/profiles-wMRnjSid.js +129 -0
- package/dist/profiles.d.ts +2 -0
- package/dist/profiles.js +2 -0
- package/dist/schedule-connector-schema-iCI61gzU.js +31 -0
- package/dist/{schedule-listener-3M6WkH1Y.d.ts → schedule-listener-CUyUFFR1.d.ts} +22 -46
- package/dist/{schedule-connector-schema-CM-sRkac.js → schedule-listener-ePAjians.js} +3 -86
- package/dist/settings-reader-BSU6JyvM.d.ts +167 -0
- package/dist/settings-reader-DPqrpV7s.js +11 -0
- package/dist/settings-store-D2XSXTyt.js +186 -0
- package/dist/slack-connector-schema-BCNWluHM.js +32 -0
- package/dist/{slack-listener-9UdAn_ui.d.ts → slack-listener-Bv5xI9gC.d.ts} +31 -31
- package/dist/{slack-connector-schema-DDbSGPZn.js → slack-listener-ClQuHhEF.js} +2 -32
- package/package.json +16 -1
- /package/dist/{connector-adapter-VA6undzc.d.ts → connector-adapter-DKgsVuMH.d.ts} +0 -0
- /package/dist/{discord-connector-schema-DF4pL3Sc.d.ts → discord-connector-schema-R0Uu-3ns.d.ts} +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { n as FunnelFileSystem } from "./file-system-BeOKXjlV.js";
|
|
2
|
+
import { i as FunnelTokenPrompter } from "./local-config-sync-BdsrDZOu.js";
|
|
3
|
+
|
|
4
|
+
//#region lib/engine/local-config/local-config-json-schema.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Generates the JSON Schema (draft 2020-12) for `funnel.json`. Useful for
|
|
7
|
+
* `$schema` references in committed `funnel.json` files so editors can give
|
|
8
|
+
* autocomplete and validation for channels[] (transport) and profiles[]
|
|
9
|
+
* (launch recipe) without anyone hand-maintaining a separate schema.
|
|
10
|
+
*/
|
|
11
|
+
declare const funnelJsonSchema: () => Record<string, unknown>;
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region lib/engine/local-config/local-config-writer.d.ts
|
|
14
|
+
type Deps = {
|
|
15
|
+
fs: FunnelFileSystem;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* The one path that mutates the repo-committed funnel.json, and it only ever
|
|
19
|
+
* inserts `id`. On first launch a repo has no `id`; funnel generates one and
|
|
20
|
+
* writes it back here so future launches resolve the same `~/.funnel/projects/<id>/`.
|
|
21
|
+
* Idempotent — a no-op once `id` is present. Kept separate from the read-only
|
|
22
|
+
* FunnelLocalConfig so reads stay side-effect free.
|
|
23
|
+
*/
|
|
24
|
+
declare class FunnelLocalConfigWriter {
|
|
25
|
+
private readonly fs;
|
|
26
|
+
constructor(deps: Deps);
|
|
27
|
+
ensureId(cwd: string, id: string): void;
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region lib/engine/token-prompter/node-token-prompter.d.ts
|
|
31
|
+
/**
|
|
32
|
+
* Reads a secret from stdin in raw mode. Echoes a `*` per byte so the user
|
|
33
|
+
* can see progress without exposing the token. Refuses to prompt when stdin
|
|
34
|
+
* is not a TTY — callers should surface the resulting error with a hint
|
|
35
|
+
* pointing at the corresponding env var or CLI command.
|
|
36
|
+
*/
|
|
37
|
+
declare class NodeFunnelTokenPrompter extends FunnelTokenPrompter {
|
|
38
|
+
promptSecret(label: string): Promise<string>;
|
|
39
|
+
private readSecret;
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region lib/engine/token-prompter/memory-token-prompter.d.ts
|
|
43
|
+
type Props = {
|
|
44
|
+
answers?: Record<string, string>;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Pre-seeded answers keyed by prompt label. Tests configure the map up front;
|
|
48
|
+
* unmapped labels throw so the test surfaces unexpected prompts loudly.
|
|
49
|
+
*/
|
|
50
|
+
declare class MemoryFunnelTokenPrompter extends FunnelTokenPrompter {
|
|
51
|
+
private readonly answers;
|
|
52
|
+
readonly asked: string[];
|
|
53
|
+
constructor(props?: Props);
|
|
54
|
+
promptSecret(label: string): Promise<string>;
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
export { funnelJsonSchema as i, NodeFunnelTokenPrompter as n, FunnelLocalConfigWriter as r, MemoryFunnelTokenPrompter as t };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { i as FunnelTokenPrompter, o as LOCAL_CONFIG_FILENAME } from "./local-config-json-schema-8IHjS4Q7.js";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
//#region lib/engine/local-config/local-config-writer.ts
|
|
4
|
+
const isRecord = (value) => {
|
|
5
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6
|
+
};
|
|
7
|
+
const withIdFirst = (config, id) => {
|
|
8
|
+
const ordered = {};
|
|
9
|
+
if (config.$schema !== void 0) ordered.$schema = config.$schema;
|
|
10
|
+
ordered.id = id;
|
|
11
|
+
for (const key of Object.keys(config)) {
|
|
12
|
+
if (key === "$schema" || key === "id") continue;
|
|
13
|
+
ordered[key] = config[key];
|
|
14
|
+
}
|
|
15
|
+
return ordered;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* The one path that mutates the repo-committed funnel.json, and it only ever
|
|
19
|
+
* inserts `id`. On first launch a repo has no `id`; funnel generates one and
|
|
20
|
+
* writes it back here so future launches resolve the same `~/.funnel/projects/<id>/`.
|
|
21
|
+
* Idempotent — a no-op once `id` is present. Kept separate from the read-only
|
|
22
|
+
* FunnelLocalConfig so reads stay side-effect free.
|
|
23
|
+
*/
|
|
24
|
+
var FunnelLocalConfigWriter = class {
|
|
25
|
+
fs;
|
|
26
|
+
constructor(deps) {
|
|
27
|
+
this.fs = deps.fs;
|
|
28
|
+
Object.freeze(this);
|
|
29
|
+
}
|
|
30
|
+
ensureId(cwd, id) {
|
|
31
|
+
const path = join(cwd, LOCAL_CONFIG_FILENAME);
|
|
32
|
+
if (!this.fs.existsSync(path)) return;
|
|
33
|
+
const parsed = JSON.parse(this.fs.readFileSync(path));
|
|
34
|
+
if (!isRecord(parsed)) return;
|
|
35
|
+
if (typeof parsed.id === "string" && parsed.id !== "") return;
|
|
36
|
+
const ordered = withIdFirst(parsed, id);
|
|
37
|
+
this.fs.writeFileSync(path, `${JSON.stringify(ordered, null, 2)}\n`);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region lib/engine/token-prompter/memory-token-prompter.ts
|
|
42
|
+
/**
|
|
43
|
+
* Pre-seeded answers keyed by prompt label. Tests configure the map up front;
|
|
44
|
+
* unmapped labels throw so the test surfaces unexpected prompts loudly.
|
|
45
|
+
*/
|
|
46
|
+
var MemoryFunnelTokenPrompter = class extends FunnelTokenPrompter {
|
|
47
|
+
answers;
|
|
48
|
+
asked = [];
|
|
49
|
+
constructor(props = {}) {
|
|
50
|
+
super();
|
|
51
|
+
this.answers = new Map(Object.entries(props.answers ?? {}));
|
|
52
|
+
}
|
|
53
|
+
async promptSecret(label) {
|
|
54
|
+
this.asked.push(label);
|
|
55
|
+
const answer = this.answers.get(label);
|
|
56
|
+
if (answer === void 0) throw new Error(`no answer seeded for prompt "${label}"`);
|
|
57
|
+
return answer;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
//#endregion
|
|
61
|
+
export { FunnelLocalConfigWriter as n, MemoryFunnelTokenPrompter as t };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { t as FunnelFileSystem } from "./file-system-PWKKU7lA.js";
|
|
2
|
+
import { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
//#region lib/engine/fs/node-file-system.ts
|
|
4
|
+
const SECRET_MODE = 384;
|
|
5
|
+
var NodeFunnelFileSystem = class extends FunnelFileSystem {
|
|
6
|
+
constructor() {
|
|
7
|
+
super();
|
|
8
|
+
Object.freeze(this);
|
|
9
|
+
}
|
|
10
|
+
existsSync(path) {
|
|
11
|
+
return existsSync(path);
|
|
12
|
+
}
|
|
13
|
+
readFileSync(path) {
|
|
14
|
+
return readFileSync(path, "utf-8");
|
|
15
|
+
}
|
|
16
|
+
writeFileSync(path, data) {
|
|
17
|
+
writeFileSync(path, data);
|
|
18
|
+
}
|
|
19
|
+
writeSecretFileSync(path, data) {
|
|
20
|
+
writeFileSync(path, data, { mode: SECRET_MODE });
|
|
21
|
+
try {
|
|
22
|
+
chmodSync(path, SECRET_MODE);
|
|
23
|
+
} catch {}
|
|
24
|
+
}
|
|
25
|
+
appendFileSync(path, data) {
|
|
26
|
+
appendFileSync(path, data);
|
|
27
|
+
}
|
|
28
|
+
unlink(path) {
|
|
29
|
+
try {
|
|
30
|
+
unlinkSync(path);
|
|
31
|
+
} catch {}
|
|
32
|
+
}
|
|
33
|
+
mkdirSync(path, options) {
|
|
34
|
+
mkdirSync(path, { recursive: options?.recursive ?? false });
|
|
35
|
+
}
|
|
36
|
+
readdirSync(path) {
|
|
37
|
+
return readdirSync(path);
|
|
38
|
+
}
|
|
39
|
+
statSync(path) {
|
|
40
|
+
const stat = statSync(path);
|
|
41
|
+
return {
|
|
42
|
+
mtimeMs: stat.mtimeMs,
|
|
43
|
+
mode: stat.mode & 511
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
//#endregion
|
|
48
|
+
export { NodeFunnelFileSystem as t };
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
|
|
3
1
|
//#region lib/engine/process/process-runner.d.ts
|
|
4
2
|
type RunOptions = {
|
|
5
3
|
cwd?: string;
|
|
@@ -48,15 +46,4 @@ declare abstract class FunnelProcessRunner {
|
|
|
48
46
|
abstract listProcessesContaining(marker: string): ProcessSnapshot[];
|
|
49
47
|
}
|
|
50
48
|
//#endregion
|
|
51
|
-
|
|
52
|
-
declare const ghConnectorSchema: z.ZodObject<{
|
|
53
|
-
id: z.ZodString;
|
|
54
|
-
name: z.ZodString;
|
|
55
|
-
type: z.ZodLiteral<"gh">;
|
|
56
|
-
pollInterval: z.ZodOptional<z.ZodNumber>;
|
|
57
|
-
createdAt: z.ZodOptional<z.ZodString>;
|
|
58
|
-
updatedAt: z.ZodOptional<z.ZodString>;
|
|
59
|
-
}, z.core.$strip>;
|
|
60
|
-
type GhConnectorConfig = z.infer<typeof ghConnectorSchema>;
|
|
61
|
-
//#endregion
|
|
62
|
-
export { FunnelProcessRunner as a, RunResult as c, DetachOptions as i, ghConnectorSchema as n, ProcessSnapshot as o, AttachOptions as r, RunOptions as s, GhConnectorConfig as t };
|
|
49
|
+
export { RunOptions as a, ProcessSnapshot as i, DetachOptions as n, RunResult as o, FunnelProcessRunner as r, AttachOptions as t };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { a as ProfileConfig, n as FunnelIdGenerator, t as FunnelSettingsReader } from "./settings-reader-BSU6JyvM.js";
|
|
2
|
+
import { n as FunnelFileSystem } from "./file-system-BeOKXjlV.js";
|
|
3
|
+
|
|
4
|
+
//#region lib/engine/profiles/profiles.d.ts
|
|
5
|
+
type Deps = {
|
|
6
|
+
store: FunnelSettingsReader;
|
|
7
|
+
idGenerator: FunnelIdGenerator;
|
|
8
|
+
fs?: FunnelFileSystem;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Named launch presets for `fnl claude`. Each profile bundles a working
|
|
12
|
+
* directory, the channel id its Claude instance subscribes to, and the launch
|
|
13
|
+
* recipe (`options` prepended to the claude argv, `env` layered under the
|
|
14
|
+
* process, `resume` toggling session reuse). Implements ProfileChannelChecker
|
|
15
|
+
* so FunnelChannels can refuse to remove a channel that is still referenced.
|
|
16
|
+
*
|
|
17
|
+
* Each profile has a stable `id` (uuid) minted at `add`. That id is the unit
|
|
18
|
+
* everything internal keys on — the PID file, the resumable session id — so a
|
|
19
|
+
* rename never strands either. `name` is purely the CLI/TUI handle; the CRUD
|
|
20
|
+
* methods here take it because that is what the user types, but resolve to the
|
|
21
|
+
* id before touching id-keyed state. The first array entry is the default
|
|
22
|
+
* profile; `asDefault` reorders to put one first.
|
|
23
|
+
*
|
|
24
|
+
* `channelId` always stores the channel's stable id (uuid). CLI surfaces
|
|
25
|
+
* resolve channel name → id before calling `add`/`update` here.
|
|
26
|
+
*/
|
|
27
|
+
declare class FunnelProfiles {
|
|
28
|
+
private readonly store;
|
|
29
|
+
private readonly idGenerator;
|
|
30
|
+
private readonly fs;
|
|
31
|
+
constructor(deps: Deps);
|
|
32
|
+
list(): ProfileConfig[];
|
|
33
|
+
get(name: string): ProfileConfig | null;
|
|
34
|
+
getById(id: string): ProfileConfig | null;
|
|
35
|
+
getDefault(): ProfileConfig | null;
|
|
36
|
+
add(input: {
|
|
37
|
+
name: string;
|
|
38
|
+
path: string;
|
|
39
|
+
channelId: string;
|
|
40
|
+
options?: string[];
|
|
41
|
+
env?: Record<string, string>;
|
|
42
|
+
resume?: boolean;
|
|
43
|
+
}): void;
|
|
44
|
+
remove(name: string): void;
|
|
45
|
+
rename(oldName: string, newName: string): void;
|
|
46
|
+
asDefault(name: string): void;
|
|
47
|
+
hasChannelRef(channelId: string): boolean;
|
|
48
|
+
/** Resumable claude session id last launched by this profile (by id), or null. */
|
|
49
|
+
getSessionId(id: string): string | null;
|
|
50
|
+
/** Records the claude session id this profile launched, overwriting any prior one. */
|
|
51
|
+
setSessionId(id: string, sessionId: string): void;
|
|
52
|
+
/**
|
|
53
|
+
* Mirrors claude's session storage path
|
|
54
|
+
* (`<config-dir>/projects/<cwd-with-slashes-as-dashes>/<id>.jsonl`) to check
|
|
55
|
+
* whether a recorded session still exists AND is non-empty. Reads the same
|
|
56
|
+
* `CLAUDE_CONFIG_DIR` the child will run under so the check matches reality; a
|
|
57
|
+
* wrong guess can only ever produce a false negative (start fresh), never a
|
|
58
|
+
* bad resume.
|
|
59
|
+
*/
|
|
60
|
+
sessionFileExists(cwd: string, sessionId: string, env: Record<string, string>): boolean;
|
|
61
|
+
update(name: string, fields: Partial<Omit<ProfileConfig, "name">>): void;
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
export { FunnelProfiles as t };
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { t as NodeFunnelFileSystem } from "./node-file-system-BcrmWN9I.js";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
//#region lib/engine/profiles/profiles.ts
|
|
5
|
+
const defaultFs = new NodeFunnelFileSystem();
|
|
6
|
+
/**
|
|
7
|
+
* Named launch presets for `fnl claude`. Each profile bundles a working
|
|
8
|
+
* directory, the channel id its Claude instance subscribes to, and the launch
|
|
9
|
+
* recipe (`options` prepended to the claude argv, `env` layered under the
|
|
10
|
+
* process, `resume` toggling session reuse). Implements ProfileChannelChecker
|
|
11
|
+
* so FunnelChannels can refuse to remove a channel that is still referenced.
|
|
12
|
+
*
|
|
13
|
+
* Each profile has a stable `id` (uuid) minted at `add`. That id is the unit
|
|
14
|
+
* everything internal keys on — the PID file, the resumable session id — so a
|
|
15
|
+
* rename never strands either. `name` is purely the CLI/TUI handle; the CRUD
|
|
16
|
+
* methods here take it because that is what the user types, but resolve to the
|
|
17
|
+
* id before touching id-keyed state. The first array entry is the default
|
|
18
|
+
* profile; `asDefault` reorders to put one first.
|
|
19
|
+
*
|
|
20
|
+
* `channelId` always stores the channel's stable id (uuid). CLI surfaces
|
|
21
|
+
* resolve channel name → id before calling `add`/`update` here.
|
|
22
|
+
*/
|
|
23
|
+
var FunnelProfiles = class {
|
|
24
|
+
store;
|
|
25
|
+
idGenerator;
|
|
26
|
+
fs;
|
|
27
|
+
constructor(deps) {
|
|
28
|
+
this.store = deps.store;
|
|
29
|
+
this.idGenerator = deps.idGenerator;
|
|
30
|
+
this.fs = deps.fs ?? defaultFs;
|
|
31
|
+
Object.freeze(this);
|
|
32
|
+
}
|
|
33
|
+
list() {
|
|
34
|
+
return this.store.read().profiles;
|
|
35
|
+
}
|
|
36
|
+
get(name) {
|
|
37
|
+
return this.list().find((p) => p.name === name) ?? null;
|
|
38
|
+
}
|
|
39
|
+
getById(id) {
|
|
40
|
+
return this.list().find((p) => p.id === id) ?? null;
|
|
41
|
+
}
|
|
42
|
+
getDefault() {
|
|
43
|
+
return this.list()[0] ?? null;
|
|
44
|
+
}
|
|
45
|
+
add(input) {
|
|
46
|
+
const settings = this.store.read();
|
|
47
|
+
if (settings.profiles.some((p) => p.name === input.name)) throw new Error(`profile "${input.name}" already exists`);
|
|
48
|
+
if (!settings.channels.some((c) => c.id === input.channelId)) throw new Error(`channel id "${input.channelId}" not found`);
|
|
49
|
+
settings.profiles.push({
|
|
50
|
+
id: this.idGenerator.generate(),
|
|
51
|
+
name: input.name,
|
|
52
|
+
path: input.path,
|
|
53
|
+
channelId: input.channelId,
|
|
54
|
+
options: input.options ?? [],
|
|
55
|
+
env: input.env ?? {},
|
|
56
|
+
resume: input.resume ?? true
|
|
57
|
+
});
|
|
58
|
+
this.store.write(settings);
|
|
59
|
+
}
|
|
60
|
+
remove(name) {
|
|
61
|
+
const settings = this.store.read();
|
|
62
|
+
const index = settings.profiles.findIndex((p) => p.name === name);
|
|
63
|
+
if (index < 0) throw new Error(`profile "${name}" not found`);
|
|
64
|
+
settings.profiles.splice(index, 1);
|
|
65
|
+
this.store.write(settings);
|
|
66
|
+
}
|
|
67
|
+
rename(oldName, newName) {
|
|
68
|
+
const settings = this.store.read();
|
|
69
|
+
const profile = settings.profiles.find((p) => p.name === oldName);
|
|
70
|
+
if (!profile) throw new Error(`profile "${oldName}" not found`);
|
|
71
|
+
if (settings.profiles.some((p) => p.name === newName)) throw new Error(`profile "${newName}" already exists`);
|
|
72
|
+
profile.name = newName;
|
|
73
|
+
this.store.write(settings);
|
|
74
|
+
}
|
|
75
|
+
asDefault(name) {
|
|
76
|
+
const settings = this.store.read();
|
|
77
|
+
const index = settings.profiles.findIndex((p) => p.name === name);
|
|
78
|
+
if (index < 0) throw new Error(`profile "${name}" not found`);
|
|
79
|
+
if (index === 0) return;
|
|
80
|
+
const [profile] = settings.profiles.splice(index, 1);
|
|
81
|
+
if (!profile) return;
|
|
82
|
+
settings.profiles.unshift(profile);
|
|
83
|
+
this.store.write(settings);
|
|
84
|
+
}
|
|
85
|
+
hasChannelRef(channelId) {
|
|
86
|
+
return this.store.read().profiles.some((p) => p.channelId === channelId);
|
|
87
|
+
}
|
|
88
|
+
/** Resumable claude session id last launched by this profile (by id), or null. */
|
|
89
|
+
getSessionId(id) {
|
|
90
|
+
return this.getById(id)?.sessionId ?? null;
|
|
91
|
+
}
|
|
92
|
+
/** Records the claude session id this profile launched, overwriting any prior one. */
|
|
93
|
+
setSessionId(id, sessionId) {
|
|
94
|
+
const settings = this.store.read();
|
|
95
|
+
const profile = settings.profiles.find((p) => p.id === id);
|
|
96
|
+
if (!profile) throw new Error(`profile id "${id}" not found`);
|
|
97
|
+
profile.sessionId = sessionId;
|
|
98
|
+
this.store.write(settings);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Mirrors claude's session storage path
|
|
102
|
+
* (`<config-dir>/projects/<cwd-with-slashes-as-dashes>/<id>.jsonl`) to check
|
|
103
|
+
* whether a recorded session still exists AND is non-empty. Reads the same
|
|
104
|
+
* `CLAUDE_CONFIG_DIR` the child will run under so the check matches reality; a
|
|
105
|
+
* wrong guess can only ever produce a false negative (start fresh), never a
|
|
106
|
+
* bad resume.
|
|
107
|
+
*/
|
|
108
|
+
sessionFileExists(cwd, sessionId, env) {
|
|
109
|
+
const path = join(env.CLAUDE_CONFIG_DIR ?? globalThis.process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), ".claude"), "projects", cwd.replace(/\//g, "-"), `${sessionId}.jsonl`);
|
|
110
|
+
if (!this.fs.existsSync(path)) return false;
|
|
111
|
+
return this.fs.readFileSync(path).trim().length > 0;
|
|
112
|
+
}
|
|
113
|
+
update(name, fields) {
|
|
114
|
+
const settings = this.store.read();
|
|
115
|
+
const profile = settings.profiles.find((p) => p.name === name);
|
|
116
|
+
if (!profile) throw new Error(`profile "${name}" not found`);
|
|
117
|
+
if (fields.channelId !== void 0) {
|
|
118
|
+
if (!settings.channels.some((c) => c.id === fields.channelId)) throw new Error(`channel id "${fields.channelId}" not found`);
|
|
119
|
+
profile.channelId = fields.channelId;
|
|
120
|
+
}
|
|
121
|
+
if (fields.path !== void 0) profile.path = fields.path;
|
|
122
|
+
if (fields.options !== void 0) profile.options = fields.options;
|
|
123
|
+
if (fields.env !== void 0) profile.env = fields.env;
|
|
124
|
+
if (fields.resume !== void 0) profile.resume = fields.resume;
|
|
125
|
+
this.store.write(settings);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
//#endregion
|
|
129
|
+
export { FunnelProfiles as t };
|
package/dist/profiles.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region lib/connectors/schedule-connector-schema.ts
|
|
3
|
+
/**
|
|
4
|
+
* Catch-up behavior when the daemon was down past one or more matching minutes.
|
|
5
|
+
*
|
|
6
|
+
* - `latest`: fire once with the most recent missed match (default; preserves prior behavior).
|
|
7
|
+
* - `all`: fire once per missed minute, oldest first (capped at 24 h).
|
|
8
|
+
* - `skip`: never fire missed matches; only fire when the current minute matches.
|
|
9
|
+
*/
|
|
10
|
+
const scheduleCatchupPolicySchema = z.enum([
|
|
11
|
+
"latest",
|
|
12
|
+
"all",
|
|
13
|
+
"skip"
|
|
14
|
+
]);
|
|
15
|
+
const scheduleEntrySchema = z.object({
|
|
16
|
+
id: z.string(),
|
|
17
|
+
cron: z.string(),
|
|
18
|
+
prompt: z.string(),
|
|
19
|
+
enabled: z.boolean().default(true),
|
|
20
|
+
catchupPolicy: scheduleCatchupPolicySchema.default("latest")
|
|
21
|
+
});
|
|
22
|
+
const scheduleConnectorSchema = z.object({
|
|
23
|
+
id: z.string(),
|
|
24
|
+
name: z.string(),
|
|
25
|
+
type: z.literal("schedule"),
|
|
26
|
+
entries: z.array(scheduleEntrySchema).default([]),
|
|
27
|
+
createdAt: z.string().datetime().optional(),
|
|
28
|
+
updatedAt: z.string().datetime().optional()
|
|
29
|
+
});
|
|
30
|
+
//#endregion
|
|
31
|
+
export { scheduleConnectorSchema as n, scheduleEntrySchema as r, scheduleCatchupPolicySchema as t };
|
|
@@ -1,6 +1,26 @@
|
|
|
1
|
-
import { S as
|
|
1
|
+
import { S as FunnelLogger, b as FunnelConnectorListener, o as ConnectorDiagnosticLog, x as NotifyFn } from "./connector-diagnostic-log-yTOojKUR.js";
|
|
2
|
+
import { n as FunnelFileSystem } from "./file-system-BeOKXjlV.js";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
|
|
5
|
+
//#region lib/connectors/schedule-state-store.d.ts
|
|
6
|
+
type Deps$1 = {
|
|
7
|
+
path: string;
|
|
8
|
+
fs?: FunnelFileSystem;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Per-connector lastFiredAt persistence for the schedule listener. The path is
|
|
12
|
+
* passed in by FunnelConnectorFactory so this store does not know about the
|
|
13
|
+
* funnel directory layout (`channels/<id>/connectors/<id>/state.json` lives
|
|
14
|
+
* outside this class).
|
|
15
|
+
*/
|
|
16
|
+
declare class ScheduleStateStore {
|
|
17
|
+
private readonly path;
|
|
18
|
+
private readonly fs;
|
|
19
|
+
constructor(deps: Deps$1);
|
|
20
|
+
load(): Map<string, Date>;
|
|
21
|
+
save(state: Map<string, Date>): void;
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
4
24
|
//#region lib/connectors/schedule-connector-schema.d.ts
|
|
5
25
|
/**
|
|
6
26
|
* Catch-up behavior when the daemon was down past one or more matching minutes.
|
|
@@ -47,50 +67,6 @@ declare const scheduleConnectorSchema: z.ZodObject<{
|
|
|
47
67
|
}, z.core.$strip>;
|
|
48
68
|
type ScheduleConnectorConfig = z.infer<typeof scheduleConnectorSchema>;
|
|
49
69
|
//#endregion
|
|
50
|
-
//#region lib/engine/fs/file-system.d.ts
|
|
51
|
-
type FileStat = {
|
|
52
|
-
mtimeMs: number; /** POSIX mode bits (e.g. 0o600). `null` when the underlying FS does not expose mode. */
|
|
53
|
-
mode: number | null;
|
|
54
|
-
};
|
|
55
|
-
/**
|
|
56
|
-
* Filesystem boundary used everywhere funnel reads or writes.
|
|
57
|
-
* Default is NodeFunnelFileSystem (real `node:fs`); MemoryFunnelFileSystem
|
|
58
|
-
* provides a sandbox for tests and embedded use.
|
|
59
|
-
*/
|
|
60
|
-
declare abstract class FunnelFileSystem {
|
|
61
|
-
abstract existsSync(path: string): boolean;
|
|
62
|
-
abstract readFileSync(path: string): string;
|
|
63
|
-
abstract writeFileSync(path: string, data: string): void;
|
|
64
|
-
/** Write `data` and ensure the resulting file is owner-only (0600). Use for tokens and any file that may contain secrets. */
|
|
65
|
-
abstract writeSecretFileSync(path: string, data: string): void;
|
|
66
|
-
abstract appendFileSync(path: string, data: string): void;
|
|
67
|
-
abstract unlink(path: string): void;
|
|
68
|
-
abstract mkdirSync(path: string, options?: {
|
|
69
|
-
recursive?: boolean;
|
|
70
|
-
}): void;
|
|
71
|
-
abstract readdirSync(path: string): string[];
|
|
72
|
-
abstract statSync(path: string): FileStat;
|
|
73
|
-
}
|
|
74
|
-
//#endregion
|
|
75
|
-
//#region lib/connectors/schedule-state-store.d.ts
|
|
76
|
-
type Deps$1 = {
|
|
77
|
-
path: string;
|
|
78
|
-
fs?: FunnelFileSystem;
|
|
79
|
-
};
|
|
80
|
-
/**
|
|
81
|
-
* Per-connector lastFiredAt persistence for the schedule listener. The path is
|
|
82
|
-
* passed in by FunnelConnectorFactory so this store does not know about the
|
|
83
|
-
* funnel directory layout (`channels/<id>/connectors/<id>/state.json` lives
|
|
84
|
-
* outside this class).
|
|
85
|
-
*/
|
|
86
|
-
declare class ScheduleStateStore {
|
|
87
|
-
private readonly path;
|
|
88
|
-
private readonly fs;
|
|
89
|
-
constructor(deps: Deps$1);
|
|
90
|
-
load(): Map<string, Date>;
|
|
91
|
-
save(state: Map<string, Date>): void;
|
|
92
|
-
}
|
|
93
|
-
//#endregion
|
|
94
70
|
//#region lib/connectors/schedule-listener.d.ts
|
|
95
71
|
type ScheduleOnFired = (entry: ScheduleEntry, firedAt: Date) => void | Promise<void>;
|
|
96
72
|
type Deps = {
|
|
@@ -133,4 +109,4 @@ declare class FunnelScheduleListener extends FunnelConnectorListener {
|
|
|
133
109
|
private recordConnection;
|
|
134
110
|
}
|
|
135
111
|
//#endregion
|
|
136
|
-
export {
|
|
112
|
+
export { ScheduleEntry as a, scheduleEntrySchema as c, ScheduleConnectorConfig as i, ScheduleStateStore as l, ScheduleOnFired as n, scheduleCatchupPolicySchema as o, ScheduleCatchupPolicy as r, scheduleConnectorSchema as s, FunnelScheduleListener as t };
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as FunnelConnectorListener } from "./connector-listener-DU54DN-f.js";
|
|
2
|
+
import { t as NodeFunnelFileSystem } from "./node-file-system-BcrmWN9I.js";
|
|
2
3
|
import { dirname } from "node:path";
|
|
3
|
-
import { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { z } from "zod";
|
|
5
4
|
//#region lib/connectors/match-cron.ts
|
|
6
5
|
const parseField = (expr, min, max) => {
|
|
7
6
|
const values = /* @__PURE__ */ new Set();
|
|
@@ -67,59 +66,6 @@ const matchCron = (expr, date) => {
|
|
|
67
66
|
return true;
|
|
68
67
|
};
|
|
69
68
|
//#endregion
|
|
70
|
-
//#region lib/engine/fs/file-system.ts
|
|
71
|
-
/**
|
|
72
|
-
* Filesystem boundary used everywhere funnel reads or writes.
|
|
73
|
-
* Default is NodeFunnelFileSystem (real `node:fs`); MemoryFunnelFileSystem
|
|
74
|
-
* provides a sandbox for tests and embedded use.
|
|
75
|
-
*/
|
|
76
|
-
var FunnelFileSystem = class {};
|
|
77
|
-
//#endregion
|
|
78
|
-
//#region lib/engine/fs/node-file-system.ts
|
|
79
|
-
const SECRET_MODE = 384;
|
|
80
|
-
var NodeFunnelFileSystem = class extends FunnelFileSystem {
|
|
81
|
-
constructor() {
|
|
82
|
-
super();
|
|
83
|
-
Object.freeze(this);
|
|
84
|
-
}
|
|
85
|
-
existsSync(path) {
|
|
86
|
-
return existsSync(path);
|
|
87
|
-
}
|
|
88
|
-
readFileSync(path) {
|
|
89
|
-
return readFileSync(path, "utf-8");
|
|
90
|
-
}
|
|
91
|
-
writeFileSync(path, data) {
|
|
92
|
-
writeFileSync(path, data);
|
|
93
|
-
}
|
|
94
|
-
writeSecretFileSync(path, data) {
|
|
95
|
-
writeFileSync(path, data, { mode: SECRET_MODE });
|
|
96
|
-
try {
|
|
97
|
-
chmodSync(path, SECRET_MODE);
|
|
98
|
-
} catch {}
|
|
99
|
-
}
|
|
100
|
-
appendFileSync(path, data) {
|
|
101
|
-
appendFileSync(path, data);
|
|
102
|
-
}
|
|
103
|
-
unlink(path) {
|
|
104
|
-
try {
|
|
105
|
-
unlinkSync(path);
|
|
106
|
-
} catch {}
|
|
107
|
-
}
|
|
108
|
-
mkdirSync(path, options) {
|
|
109
|
-
mkdirSync(path, { recursive: options?.recursive ?? false });
|
|
110
|
-
}
|
|
111
|
-
readdirSync(path) {
|
|
112
|
-
return readdirSync(path);
|
|
113
|
-
}
|
|
114
|
-
statSync(path) {
|
|
115
|
-
const stat = statSync(path);
|
|
116
|
-
return {
|
|
117
|
-
mtimeMs: stat.mtimeMs,
|
|
118
|
-
mode: stat.mode & 511
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
//#endregion
|
|
123
69
|
//#region lib/connectors/schedule-state-store.ts
|
|
124
70
|
const defaultFs = new NodeFunnelFileSystem();
|
|
125
71
|
/**
|
|
@@ -353,33 +299,4 @@ var FunnelScheduleListener = class extends FunnelConnectorListener {
|
|
|
353
299
|
}
|
|
354
300
|
};
|
|
355
301
|
//#endregion
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* Catch-up behavior when the daemon was down past one or more matching minutes.
|
|
359
|
-
*
|
|
360
|
-
* - `latest`: fire once with the most recent missed match (default; preserves prior behavior).
|
|
361
|
-
* - `all`: fire once per missed minute, oldest first (capped at 24 h).
|
|
362
|
-
* - `skip`: never fire missed matches; only fire when the current minute matches.
|
|
363
|
-
*/
|
|
364
|
-
const scheduleCatchupPolicySchema = z.enum([
|
|
365
|
-
"latest",
|
|
366
|
-
"all",
|
|
367
|
-
"skip"
|
|
368
|
-
]);
|
|
369
|
-
const scheduleEntrySchema = z.object({
|
|
370
|
-
id: z.string(),
|
|
371
|
-
cron: z.string(),
|
|
372
|
-
prompt: z.string(),
|
|
373
|
-
enabled: z.boolean().default(true),
|
|
374
|
-
catchupPolicy: scheduleCatchupPolicySchema.default("latest")
|
|
375
|
-
});
|
|
376
|
-
const scheduleConnectorSchema = z.object({
|
|
377
|
-
id: z.string(),
|
|
378
|
-
name: z.string(),
|
|
379
|
-
type: z.literal("schedule"),
|
|
380
|
-
entries: z.array(scheduleEntrySchema).default([]),
|
|
381
|
-
createdAt: z.string().datetime().optional(),
|
|
382
|
-
updatedAt: z.string().datetime().optional()
|
|
383
|
-
});
|
|
384
|
-
//#endregion
|
|
385
|
-
export { ScheduleStateStore as a, matchCron as c, FunnelScheduleListener as i, scheduleConnectorSchema as n, NodeFunnelFileSystem as o, scheduleEntrySchema as r, FunnelFileSystem as s, scheduleCatchupPolicySchema as t };
|
|
302
|
+
export { ScheduleStateStore as n, matchCron as r, FunnelScheduleListener as t };
|