@getbourdon/participants 0.2.3 → 0.3.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.
Files changed (48) hide show
  1. package/LICENSE +1 -1
  2. package/LICENSE_FAQ.md +1 -1
  3. package/dist/base.d.ts +109 -0
  4. package/dist/base.js +88 -0
  5. package/dist/index.cjs +12 -7
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.ts +42 -1083
  8. package/dist/index.js +53 -6590
  9. package/dist/index.js.map +1 -1
  10. package/dist/network-base.d.ts +108 -0
  11. package/dist/network-base.js +248 -0
  12. package/dist/readers/cascade.d.ts +49 -0
  13. package/dist/readers/cascade.js +428 -0
  14. package/dist/readers/claude-code-automations.d.ts +62 -0
  15. package/dist/readers/claude-code-automations.js +693 -0
  16. package/dist/readers/claude-code.d.ts +50 -0
  17. package/dist/readers/claude-code.js +590 -0
  18. package/dist/readers/claude-desktop-code.d.ts +54 -0
  19. package/dist/readers/claude-desktop-code.js +526 -0
  20. package/dist/readers/claude-desktop-cowork.d.ts +47 -0
  21. package/dist/readers/claude-desktop-cowork.js +659 -0
  22. package/dist/readers/codex-automations.d.ts +55 -0
  23. package/dist/readers/codex-automations.js +519 -0
  24. package/dist/readers/codex-sessions.d.ts +88 -0
  25. package/dist/readers/codex-sessions.js +194 -0
  26. package/dist/readers/copilot-automations.d.ts +64 -0
  27. package/dist/readers/copilot-automations.js +530 -0
  28. package/dist/readers/copilot-cli.d.ts +45 -0
  29. package/dist/readers/copilot-cli.js +416 -0
  30. package/dist/readers/copilot-vscode.d.ts +54 -0
  31. package/dist/readers/copilot-vscode.js +553 -0
  32. package/dist/readers/copilot.d.ts +59 -0
  33. package/dist/readers/copilot.js +413 -0
  34. package/dist/readers/cursor-automations.d.ts +57 -0
  35. package/dist/readers/cursor-automations.js +685 -0
  36. package/dist/readers/cursor.d.ts +50 -0
  37. package/dist/readers/cursor.js +670 -0
  38. package/dist/readers/github-copilot.d.ts +34 -0
  39. package/dist/readers/github-copilot.js +242 -0
  40. package/dist/readers/hermes.d.ts +37 -0
  41. package/dist/readers/hermes.js +426 -0
  42. package/dist/readers/openclaw.d.ts +78 -0
  43. package/dist/readers/openclaw.js +386 -0
  44. package/dist/registry.d.ts +39 -0
  45. package/dist/registry.js +98 -0
  46. package/dist/sqlite-base.d.ts +65 -0
  47. package/dist/sqlite-base.js +163 -0
  48. package/package.json +6 -6
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Network-shaped participant base — port of `participants/_network_base.py`
3
+ * (v0.3 adapter contract). Same `exportL5` / `exportSessions` half as a file
4
+ * participant; only the discovery half differs (fetch from an authenticated
5
+ * API). The base provides the three guarantees so adapters don't reimplement:
6
+ *
7
+ * 1. Local TTL cache under `~/.cache/bourdon/<slug>/payload.json` storing ONLY
8
+ * `{fetched_at, payload}` — NEVER the token. A cache hit = zero network.
9
+ * 2. Graceful degradation: network down → serve the most recent cached payload
10
+ * (even expired) and report `degraded` (not silence).
11
+ * 3. Auth boundary: an injected lazy {@link AuthProvider} (env / keychain).
12
+ * An invalid token (401/403 non-ratelimit) ALWAYS propagates as `blocked`
13
+ * and is NEVER masked by stale cache; a missing token serves stale when a
14
+ * cache exists, else blocks; rate-limit degrades.
15
+ *
16
+ * A subclass sets `participantSlug` / `agentId` / `agentType` (+ `nativePath`)
17
+ * and implements `fetchPayload(token)` + `payloadToL5(payload)` (pure, no I/O).
18
+ */
19
+ import type { L5ManifestModel, SessionModel } from "@getbourdon/l5";
20
+ import { ParticipantError, type AgentStore, type BourdonParticipant, type HealthStatus } from "./base.js";
21
+ export declare const NETWORK_CONTRACT_VERSION = "0.3";
22
+ /** Default cache TTL — adapters override via `cacheTtlSeconds`. */
23
+ export declare const DEFAULT_CACHE_TTL_SECONDS: number;
24
+ /** Transport failure / timeout / 5xx / rate-limit — recoverable via cache fallback. */
25
+ export declare class NetworkUnavailable extends ParticipantError {
26
+ }
27
+ /** 401/403 / missing credential — NOT recoverable via cache. The user must fix
28
+ * auth; surfaced as `blocked`, never silently served from stale cache (stale
29
+ * data would mask a broken token indefinitely). */
30
+ export declare class ParticipantAuthError extends ParticipantError {
31
+ }
32
+ /** A zero-arg callable returning the token string, or null if no credential is
33
+ * available. Kept a callable (not a bare string) so the token is fetched lazily
34
+ * at call time and never sits in the participant's fields or the cache. */
35
+ export type AuthProvider = () => string | null;
36
+ /** Auth provider that reads a token from an environment variable. */
37
+ export declare function envAuthProvider(varName: string): AuthProvider;
38
+ /** `$XDG_CACHE_HOME/bourdon` or `~/.cache/bourdon`. */
39
+ export declare function defaultCacheRoot(): string;
40
+ /** A cached payload + the wall-clock time it was fetched (epoch seconds). */
41
+ export declare class CacheEntry {
42
+ readonly fetchedAt: number;
43
+ readonly payload: Record<string, unknown>;
44
+ constructor(fetchedAt: number, payload: Record<string, unknown>);
45
+ get ageSeconds(): number;
46
+ isFresh(ttlSeconds: number): boolean;
47
+ }
48
+ /**
49
+ * A tiny JSON file cache, one file per participant slug. Stores only
50
+ * `{fetched_at, payload}` — never the auth token. Read/write are best-effort: a
51
+ * corrupt or unwritable cache degrades to "no cache", never raises.
52
+ */
53
+ export declare class PayloadCache {
54
+ readonly slug: string;
55
+ readonly root: string;
56
+ readonly path: string;
57
+ constructor(slug: string, rootDir?: string);
58
+ read(): CacheEntry | null;
59
+ write(payload: Record<string, unknown>): void;
60
+ }
61
+ export interface NetworkParticipantOptions {
62
+ authProvider?: AuthProvider | null;
63
+ cacheRoot?: string;
64
+ }
65
+ /**
66
+ * Base for participants whose native state lives behind an authenticated API.
67
+ * Caching, degradation, the auth boundary, and the
68
+ * `discover`/`exportL5`/`exportSessions`/`healthCheck` plumbing live here;
69
+ * subclasses provide only `fetchPayload` + `payloadToL5`.
70
+ */
71
+ export declare abstract class NetworkParticipant implements BourdonParticipant {
72
+ participantSlug: string;
73
+ agentId: string;
74
+ agentType: string;
75
+ cacheTtlSeconds: number;
76
+ nativePath: string;
77
+ protected readonly authProvider: AuthProvider | null;
78
+ private readonly cacheRoot?;
79
+ private cacheInstance?;
80
+ constructor(opts?: NetworkParticipantOptions);
81
+ /** Lazily built so it reads the SUBCLASS's `participantSlug` — subclass field
82
+ * initializers run after `super()`, so building the cache in the constructor
83
+ * would capture the base "network" slug instead. */
84
+ protected get cache(): PayloadCache;
85
+ /** Do the authenticated API call; return a JSON-serializable dict. Raise
86
+ * {@link NetworkUnavailable} on transport/5xx/rate-limit (→ cache fallback) or
87
+ * {@link ParticipantAuthError} on 401/403 (→ blocked, no fallback). */
88
+ abstract fetchPayload(token: string): Record<string, unknown>;
89
+ /** Normalize a (fresh or cached) payload into an L5 manifest. Pure, no I/O. */
90
+ abstract payloadToL5(payload: Record<string, unknown>): L5ManifestModel;
91
+ /**
92
+ * Resolve (payload, source) where source is 'cache' | 'network' | 'stale-cache'.
93
+ * 1. Fresh cache hit → 'cache' (no network)
94
+ * 2. Miss/expired + token → 'network' (fetch + refresh cache)
95
+ * 3. Net fails + any cache → 'stale-cache' (serve last good payload)
96
+ * 4. Net fails + no cache → raise
97
+ * Auth errors are never swallowed — they propagate so health surfaces
98
+ * 'blocked' rather than serving stale data behind a dead token.
99
+ */
100
+ protected getPayload(): {
101
+ payload: Record<string, unknown>;
102
+ source: string;
103
+ };
104
+ discover(): AgentStore;
105
+ exportL5(_since?: Date): L5ManifestModel;
106
+ exportSessions(since?: Date, limit?: number): SessionModel[];
107
+ healthCheck(): HealthStatus;
108
+ }
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Network-shaped participant base — port of `participants/_network_base.py`
3
+ * (v0.3 adapter contract). Same `exportL5` / `exportSessions` half as a file
4
+ * participant; only the discovery half differs (fetch from an authenticated
5
+ * API). The base provides the three guarantees so adapters don't reimplement:
6
+ *
7
+ * 1. Local TTL cache under `~/.cache/bourdon/<slug>/payload.json` storing ONLY
8
+ * `{fetched_at, payload}` — NEVER the token. A cache hit = zero network.
9
+ * 2. Graceful degradation: network down → serve the most recent cached payload
10
+ * (even expired) and report `degraded` (not silence).
11
+ * 3. Auth boundary: an injected lazy {@link AuthProvider} (env / keychain).
12
+ * An invalid token (401/403 non-ratelimit) ALWAYS propagates as `blocked`
13
+ * and is NEVER masked by stale cache; a missing token serves stale when a
14
+ * cache exists, else blocks; rate-limit degrades.
15
+ *
16
+ * A subclass sets `participantSlug` / `agentId` / `agentType` (+ `nativePath`)
17
+ * and implements `fetchPayload(token)` + `payloadToL5(payload)` (pure, no I/O).
18
+ */
19
+ import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
20
+ import { homedir } from "node:os";
21
+ import { join } from "node:path";
22
+ import { ParticipantError, } from "./base.js";
23
+ export const NETWORK_CONTRACT_VERSION = "0.3";
24
+ /** Default cache TTL — adapters override via `cacheTtlSeconds`. */
25
+ export const DEFAULT_CACHE_TTL_SECONDS = 15 * 60; // 15 minutes
26
+ // -- Network-specific errors --------------------------------------------------
27
+ /** Transport failure / timeout / 5xx / rate-limit — recoverable via cache fallback. */
28
+ export class NetworkUnavailable extends ParticipantError {
29
+ }
30
+ /** 401/403 / missing credential — NOT recoverable via cache. The user must fix
31
+ * auth; surfaced as `blocked`, never silently served from stale cache (stale
32
+ * data would mask a broken token indefinitely). */
33
+ export class ParticipantAuthError extends ParticipantError {
34
+ }
35
+ /** Auth provider that reads a token from an environment variable. */
36
+ export function envAuthProvider(varName) {
37
+ return () => process.env[varName] || null;
38
+ }
39
+ // -- Cache --------------------------------------------------------------------
40
+ /** `$XDG_CACHE_HOME/bourdon` or `~/.cache/bourdon`. */
41
+ export function defaultCacheRoot() {
42
+ const xdg = process.env.XDG_CACHE_HOME;
43
+ const base = xdg ? xdg : join(homedir(), ".cache");
44
+ return join(base, "bourdon");
45
+ }
46
+ /** A cached payload + the wall-clock time it was fetched (epoch seconds). */
47
+ export class CacheEntry {
48
+ fetchedAt;
49
+ payload;
50
+ constructor(fetchedAt, payload) {
51
+ this.fetchedAt = fetchedAt;
52
+ this.payload = payload;
53
+ }
54
+ get ageSeconds() {
55
+ return Math.max(0, Date.now() / 1000 - this.fetchedAt);
56
+ }
57
+ isFresh(ttlSeconds) {
58
+ return this.ageSeconds < ttlSeconds;
59
+ }
60
+ }
61
+ /**
62
+ * A tiny JSON file cache, one file per participant slug. Stores only
63
+ * `{fetched_at, payload}` — never the auth token. Read/write are best-effort: a
64
+ * corrupt or unwritable cache degrades to "no cache", never raises.
65
+ */
66
+ export class PayloadCache {
67
+ slug;
68
+ root;
69
+ path;
70
+ constructor(slug, rootDir) {
71
+ this.slug = slug;
72
+ this.root = join(rootDir ?? defaultCacheRoot(), slug);
73
+ this.path = join(this.root, "payload.json");
74
+ }
75
+ read() {
76
+ let raw;
77
+ try {
78
+ raw = JSON.parse(readFileSync(this.path, "utf8"));
79
+ }
80
+ catch {
81
+ return null;
82
+ }
83
+ if (typeof raw !== "object" || raw === null || !("payload" in raw))
84
+ return null;
85
+ const obj = raw;
86
+ let fetchedAt = Number(obj["fetched_at"] ?? 0);
87
+ if (!Number.isFinite(fetchedAt))
88
+ fetchedAt = 0;
89
+ const payload = obj["payload"];
90
+ if (typeof payload !== "object" || payload === null || Array.isArray(payload))
91
+ return null;
92
+ return new CacheEntry(fetchedAt, payload);
93
+ }
94
+ write(payload) {
95
+ try {
96
+ mkdirSync(this.root, { recursive: true });
97
+ const tmp = `${this.path}.tmp`;
98
+ writeFileSync(tmp, JSON.stringify({ fetched_at: Date.now() / 1000, payload }), "utf8");
99
+ renameSync(tmp, this.path); // atomic
100
+ }
101
+ catch {
102
+ /* best-effort; a write failure degrades to "no cache" */
103
+ }
104
+ }
105
+ }
106
+ // -- Base participant ---------------------------------------------------------
107
+ /**
108
+ * Base for participants whose native state lives behind an authenticated API.
109
+ * Caching, degradation, the auth boundary, and the
110
+ * `discover`/`exportL5`/`exportSessions`/`healthCheck` plumbing live here;
111
+ * subclasses provide only `fetchPayload` + `payloadToL5`.
112
+ */
113
+ export class NetworkParticipant {
114
+ participantSlug = "network";
115
+ agentId = "network";
116
+ agentType = "other";
117
+ cacheTtlSeconds = DEFAULT_CACHE_TTL_SECONDS;
118
+ nativePath = "network://";
119
+ authProvider;
120
+ cacheRoot;
121
+ cacheInstance;
122
+ constructor(opts = {}) {
123
+ this.authProvider = opts.authProvider ?? null;
124
+ this.cacheRoot = opts.cacheRoot;
125
+ }
126
+ /** Lazily built so it reads the SUBCLASS's `participantSlug` — subclass field
127
+ * initializers run after `super()`, so building the cache in the constructor
128
+ * would capture the base "network" slug instead. */
129
+ get cache() {
130
+ if (!this.cacheInstance) {
131
+ this.cacheInstance = new PayloadCache(this.participantSlug, this.cacheRoot);
132
+ }
133
+ return this.cacheInstance;
134
+ }
135
+ // -- Core fetch-with-cache-and-degrade -------------------------------------
136
+ /**
137
+ * Resolve (payload, source) where source is 'cache' | 'network' | 'stale-cache'.
138
+ * 1. Fresh cache hit → 'cache' (no network)
139
+ * 2. Miss/expired + token → 'network' (fetch + refresh cache)
140
+ * 3. Net fails + any cache → 'stale-cache' (serve last good payload)
141
+ * 4. Net fails + no cache → raise
142
+ * Auth errors are never swallowed — they propagate so health surfaces
143
+ * 'blocked' rather than serving stale data behind a dead token.
144
+ */
145
+ getPayload() {
146
+ const cached = this.cache.read();
147
+ if (cached !== null && cached.isFresh(this.cacheTtlSeconds)) {
148
+ return { payload: cached.payload, source: "cache" };
149
+ }
150
+ const token = this.authProvider ? this.authProvider() : null;
151
+ if (!token) {
152
+ // No credential: if we have any cache, serve it stale; else blocked.
153
+ if (cached !== null)
154
+ return { payload: cached.payload, source: "stale-cache" };
155
+ throw new ParticipantAuthError(`${this.participantSlug}: no auth token available and no cache`);
156
+ }
157
+ let payload;
158
+ try {
159
+ payload = this.fetchPayload(token);
160
+ }
161
+ catch (err) {
162
+ if (err instanceof ParticipantAuthError)
163
+ throw err; // never mask a bad token
164
+ if (err instanceof NetworkUnavailable) {
165
+ if (cached !== null)
166
+ return { payload: cached.payload, source: "stale-cache" };
167
+ throw err;
168
+ }
169
+ throw err;
170
+ }
171
+ this.cache.write(payload);
172
+ return { payload, source: "network" };
173
+ }
174
+ // -- Participant protocol ---------------------------------------------------
175
+ discover() {
176
+ const { source } = this.getPayload();
177
+ return {
178
+ path: this.nativePath,
179
+ version: `${this.participantSlug}-network-v1`,
180
+ metadata: { source, contract_version: NETWORK_CONTRACT_VERSION },
181
+ };
182
+ }
183
+ exportL5(_since) {
184
+ const { payload } = this.getPayload();
185
+ return this.payloadToL5(payload);
186
+ }
187
+ exportSessions(since, limit = 100) {
188
+ const manifest = this.exportL5(since);
189
+ let sessions = manifest.recent_sessions ?? [];
190
+ if (since) {
191
+ const cutoff = since.toISOString().slice(0, 10);
192
+ sessions = sessions.filter((s) => (s.date || "") >= cutoff);
193
+ }
194
+ return sessions.slice(0, limit);
195
+ }
196
+ healthCheck() {
197
+ let source;
198
+ try {
199
+ source = this.getPayload().source;
200
+ }
201
+ catch (err) {
202
+ if (err instanceof ParticipantAuthError) {
203
+ return {
204
+ status: "blocked",
205
+ reason: String(err.message),
206
+ details: { participant: this.participantSlug },
207
+ proposedFix: `Provide a valid credential for ${this.participantSlug} ` +
208
+ "(see the adapter docs for the auth path) and re-run `bourdon export-all`.",
209
+ };
210
+ }
211
+ if (err instanceof NetworkUnavailable) {
212
+ return {
213
+ status: "degraded",
214
+ reason: `network unavailable and no cache: ${err.message}`,
215
+ details: { participant: this.participantSlug },
216
+ proposedFix: "Check connectivity, then re-run `bourdon export-all`.",
217
+ };
218
+ }
219
+ // Defense in depth: health_check must NEVER raise. An adapter's
220
+ // fetchPayload should only raise the two contract errors; anything else is
221
+ // reported as blocked rather than propagated.
222
+ return {
223
+ status: "blocked",
224
+ reason: `unexpected error resolving payload: ${String(err)}`,
225
+ details: { participant: this.participantSlug },
226
+ proposedFix: `${this.participantSlug} raised an unexpected error during healthCheck; ` +
227
+ "its fetchPayload should only raise ParticipantAuthError / NetworkUnavailable.",
228
+ };
229
+ }
230
+ const cached = this.cache.read();
231
+ const details = { participant: this.participantSlug, source };
232
+ if (cached !== null) {
233
+ details["cache_age_seconds"] = Math.round(cached.ageSeconds * 10) / 10;
234
+ details["cache_fetched_at"] = new Date(cached.fetchedAt * 1000)
235
+ .toISOString()
236
+ .replace(/\.\d{3}Z$/, "+00:00");
237
+ }
238
+ if (source === "stale-cache") {
239
+ return {
240
+ status: "degraded",
241
+ reason: "serving stale cache (network unavailable or no token)",
242
+ details,
243
+ proposedFix: "Restore connectivity / refresh the token to update.",
244
+ };
245
+ }
246
+ return { status: "ok", details };
247
+ }
248
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Cascade (Windsurf) participant — convention-file reader. Port of
3
+ * `participants/cascade.py`.
4
+ *
5
+ * Cascade's internal state is not exposed on the filesystem in a standardized
6
+ * format (like cloud Copilot). Instead this participant uses a CONVENTION FILE:
7
+ * Cascade maintains a structured memory file at `~/.cascade-bourdon/memory.md`
8
+ * with YAML front-matter carrying `entities` + `sessions`. Cascade owns that
9
+ * projection explicitly (it writes what it knows at session end); the
10
+ * participant normalizes the front-matter into an L5 manifest for federation.
11
+ *
12
+ * Privacy: entity/session summaries pass through the redaction SSOT; the
13
+ * visibility policy (private_tags) filters PRIVATE entities before emission.
14
+ */
15
+ import { type L5ManifestModel, type SessionModel, type VisibilityPolicyModel } from "@getbourdon/l5";
16
+ import { type AgentStore, type BourdonParticipant, type HealthStatus } from "../base.js";
17
+ /** Return the default Cascade-Bourdon convention directory (`~/.cascade-bourdon`). */
18
+ export declare function defaultCascadeDir(): string;
19
+ /** Return the default path to the Cascade memory file. */
20
+ export declare function defaultCascadeMemoryPath(): string;
21
+ /**
22
+ * Create a starter memory.md in the Cascade-Bourdon convention directory.
23
+ *
24
+ * @param cascadeDir Override the convention directory (default `~/.cascade-bourdon`).
25
+ * @param force Overwrite an existing file; otherwise throws.
26
+ * @returns the path to the created memory file.
27
+ */
28
+ export declare function initMemoryFile(cascadeDir?: string, force?: boolean): string;
29
+ /**
30
+ * Convention-based Bourdon participant for Cascade (Windsurf). Reads structured
31
+ * memory from `~/.cascade-bourdon/memory.md` and normalizes it into L5.
32
+ */
33
+ export declare class CascadeParticipant implements BourdonParticipant {
34
+ agentId: string;
35
+ agentType: string;
36
+ displayName: string;
37
+ private readonly dir;
38
+ private readonly policy;
39
+ static defaultNativePath(home?: string): string;
40
+ constructor(cascadeDir?: string, policy?: VisibilityPolicyModel);
41
+ /** Path to the Cascade-Bourdon convention directory. */
42
+ get nativePath(): string;
43
+ private memoryPath;
44
+ private readFrontmatter;
45
+ discover(): AgentStore;
46
+ exportL5(since?: Date): L5ManifestModel;
47
+ exportSessions(since?: Date, limit?: number): SessionModel[];
48
+ healthCheck(): HealthStatus;
49
+ }