@onlooker-community/adapter-sdk 1.0.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/LICENSE ADDED
@@ -0,0 +1,17 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
16
+
17
+ Copyright 2026 Onlooker Community
package/README.md ADDED
@@ -0,0 +1,134 @@
1
+ # @onlooker-community/adapter-sdk
2
+
3
+ Foundational SDK for Onlooker **runtime adapters**. Defines the contract every adapter implements, the abstract base class that handles the cross-runtime concerns, and the canonical event writer.
4
+
5
+ An adapter sits between a runtime (Claude Code, Cursor, Copilot, ...) and the canonical Onlooker event bus. It does two things:
6
+
7
+ 1. **Translate** runtime-native events into canonical [`OnlookerEvent`](https://github.com/onlooker-community/schema) envelopes.
8
+ 2. **Deliver** plugin decisions back to the runtime in its native format (block / allow / inject context).
9
+
10
+ ## Install
11
+
12
+ ```sh
13
+ npm install @onlooker-community/adapter-sdk
14
+ ```
15
+
16
+ This package declares `@onlooker-community/schema` as a peer dependency. Install it explicitly:
17
+
18
+ ```sh
19
+ npm install @onlooker-community/schema
20
+ ```
21
+
22
+ ## The contract
23
+
24
+ ```ts
25
+ import type { OnlookerRuntimeAdapter } from "@onlooker-community/adapter-sdk";
26
+ ```
27
+
28
+ Every adapter declares:
29
+
30
+ - `runtimeId` — one of the canonical strings from `schema.event.v1.json` (`claude-code`, `cursor`, `copilot`, `gemini`, `custom`).
31
+ - `version` — the adapter's own semver string.
32
+
33
+ …and implements two clusters of methods:
34
+
35
+ **Event ingestion (runtime → canonical events):**
36
+
37
+ - `onSessionStart`, `onSessionEnd`
38
+ - `onFileWrite`, `onFileEdit`
39
+ - `onShellExec`, `onWebFetch`
40
+ - `onAgentSpawn`, `onAgentComplete`
41
+
42
+ Each receives the runtime-native payload as `unknown`, normalises it, and returns an `OnlookerEvent`.
43
+
44
+ **Decision delivery (plugin decisions → runtime):**
45
+
46
+ - `blockOperation(reason)`
47
+ - `allowOperation()`
48
+ - `injectContext(content)`
49
+
50
+ These are best-effort by contract — adapters must never throw out of them.
51
+
52
+ ## Building one — extend `BaseAdapter`
53
+
54
+ ```ts
55
+ import { BaseAdapter } from "@onlooker-community/adapter-sdk";
56
+ import type { OnlookerEvent } from "@onlooker-community/schema";
57
+
58
+ export class ClaudeCodeAdapter extends BaseAdapter {
59
+ constructor(machineId: string) {
60
+ super({ runtimeId: "claude-code", version: "0.1.0", machineId });
61
+ }
62
+
63
+ onSessionStart(raw: unknown): OnlookerEvent {
64
+ const sid = (raw as { session_id: string }).session_id;
65
+ this.startSession(sid);
66
+ return this.writeEvent({
67
+ plugin: "claude-code",
68
+ event_type: "session.start",
69
+ payload: {
70
+ cwd: process.cwd(),
71
+ project_name: null,
72
+ git_branch: null,
73
+ git_commit: null,
74
+ },
75
+ session_id: sid,
76
+ });
77
+ }
78
+
79
+ // ... rest of the on* + decision-delivery methods
80
+ }
81
+ ```
82
+
83
+ `BaseAdapter` handles:
84
+
85
+ - **Session ID generation and tracking** — `newSessionId()` mints UUIDs; `startSession(sid)` initialises a per-session sequence counter.
86
+ - **Sequence counter per session** — each call to `buildEvent` / `writeEvent` reads-then-increments the counter for `session_id` so events within a session carry monotonic sequence numbers.
87
+ - **`createEventBase()` equivalent** — the shared envelope fields (`id`, `schema_version`, `runtime`, `machine_id`, `timestamp`, `session_id`, `sequence`) come from `buildEvent`.
88
+ - **`writeEvent(event)`** — appends to `~/.onlooker/<runtimeId>/<plugin>/<session_id>.jsonl`.
89
+
90
+ ## Canonical event layout
91
+
92
+ Events land at:
93
+
94
+ ```text
95
+ ~/.onlooker/<runtimeId>/<plugin>/<session_id>.jsonl
96
+ ```
97
+
98
+ …one JSON line per event, matching the [canonical envelope](https://github.com/onlooker-community/schema/blob/main/schemas/event.v1.json).
99
+
100
+ Tests and alternate installs can override the root by passing `eventWriterOptions: { rootDir }` to the `BaseAdapter` constructor.
101
+
102
+ ## Test helpers
103
+
104
+ `EventWriter` and `eventLogPath` are exported for adapters that want to bypass `BaseAdapter` (e.g. integration tests, runtime probes):
105
+
106
+ ```ts
107
+ import { EventWriter, eventLogPath } from "@onlooker-community/adapter-sdk";
108
+
109
+ const writer = new EventWriter({ rootDir: "/tmp/onl-test" });
110
+ writer.write(event);
111
+ console.log(writer.pathFor(event));
112
+ ```
113
+
114
+ ## Local development
115
+
116
+ After cloning, `npm install` runs `simple-git-hooks` via `prepare` and
117
+ installs a `pre-push` hook that mirrors CI:
118
+
119
+ ```sh
120
+ npm run verify # biome ci → typecheck → test → build
121
+ ```
122
+
123
+ The same four commands run in GitHub Actions, so a green `verify` is a
124
+ green PR. To skip the hook in an emergency, `SKIP_SIMPLE_GIT_HOOKS=1 git push`.
125
+
126
+ To auto-fix Biome lint, format, and import-sort findings in one go:
127
+
128
+ ```sh
129
+ npm run fix # biome check --write
130
+ ```
131
+
132
+ ## License
133
+
134
+ Apache-2.0
@@ -0,0 +1,108 @@
1
+ import type { EventType, OnlookerEvent, PayloadFor, RuntimeId } from "@onlooker-community/schema";
2
+ import { EventWriter, type EventWriterOptions } from "./event-writer.js";
3
+ import type { OnlookerRuntimeAdapter } from "./types.js";
4
+ export interface BaseAdapterOptions {
5
+ /**
6
+ * Optional override for the event writer. Tests pass a writer
7
+ * configured against a temp dir; production code leaves this off
8
+ * and gets the default ~/.onlooker writer.
9
+ */
10
+ eventWriter?: EventWriter;
11
+ /**
12
+ * Equivalent to passing `new EventWriter({ rootDir })`. Kept as a
13
+ * convenience so adapters don't have to import EventWriter just to
14
+ * change the root.
15
+ */
16
+ eventWriterOptions?: EventWriterOptions;
17
+ }
18
+ /**
19
+ * Shape the subclass passes to `buildEvent` when creating a canonical
20
+ * event. The base class fills in everything else (id, schema_version,
21
+ * runtime, machine_id, timestamp, session_id, sequence).
22
+ */
23
+ export interface BuildEventParams<T extends EventType> {
24
+ plugin: string;
25
+ event_type: T;
26
+ payload: PayloadFor<T>;
27
+ session_id: string;
28
+ adapter_id?: string;
29
+ cost_usd?: number;
30
+ token_count?: number;
31
+ }
32
+ /**
33
+ * Abstract base every OnlookerRuntimeAdapter extends.
34
+ *
35
+ * Subclasses must:
36
+ * 1. Pass `runtimeId`, `version`, and `machineId` to `super()`.
37
+ * 2. Call `this.startSession(sessionId)` from `onSessionStart` (and
38
+ * whenever they otherwise observe a session begin) so the
39
+ * sequence counter exists.
40
+ * 3. Implement the abstract `on*` callbacks and the
41
+ * RuntimeDecisionDelivery methods.
42
+ * 4. Use `this.buildEvent()` to construct events — that's what
43
+ * keeps id/schema_version/sequence consistent.
44
+ */
45
+ export declare abstract class BaseAdapter implements OnlookerRuntimeAdapter {
46
+ readonly runtimeId: RuntimeId;
47
+ readonly version: string;
48
+ /** Stable machine identifier (UUID). One per host install. */
49
+ protected readonly machineId: string;
50
+ /**
51
+ * Per-session sequence counters. Each `createEvent` call inside
52
+ * `buildEvent` reads-then-increments the counter for that session
53
+ * id so events within a session carry monotonic sequence numbers.
54
+ */
55
+ private readonly sequence;
56
+ private readonly writer;
57
+ constructor(args: {
58
+ runtimeId: RuntimeId;
59
+ version: string;
60
+ machineId: string;
61
+ options?: BaseAdapterOptions;
62
+ });
63
+ /**
64
+ * Initialise (or reset) the sequence counter for `sessionId`.
65
+ * Called from `onSessionStart`. Idempotent — calling twice resets
66
+ * the counter, which is the desired behavior for resumed sessions
67
+ * that issue a fresh session-start event.
68
+ */
69
+ protected startSession(sessionId: string): void;
70
+ /**
71
+ * Generate a new session id. Adapters can pull this if the runtime
72
+ * doesn't surface one of its own (e.g. a stateless tool invocation).
73
+ */
74
+ protected newSessionId(): string;
75
+ /**
76
+ * Read-and-increment the sequence counter for `sessionId`. Lazily
77
+ * initialises if the session wasn't seen before — adapters
78
+ * occasionally receive events out of order (session-end fires
79
+ * before session-start due to clock skew), and we'd rather emit
80
+ * those with sequence=0 than crash.
81
+ */
82
+ protected nextSequence(sessionId: string): number;
83
+ /**
84
+ * Shared envelope fields. Concrete `on*` callbacks call this to
85
+ * build the canonical event, then pass the result to `writeEvent`
86
+ * (or yield it from the callback for the caller to write).
87
+ */
88
+ protected buildEvent<T extends EventType>(params: BuildEventParams<T>): OnlookerEvent<T>;
89
+ /**
90
+ * Convenience: build + persist. Concrete adapters typically end
91
+ * their `on*` callbacks with this — `return this.writeEvent(...)`.
92
+ */
93
+ protected writeEvent<T extends EventType>(params: BuildEventParams<T>): OnlookerEvent<T>;
94
+ /** Test/inspection helper — exposes the resolved JSONL path. */
95
+ pathFor(event: OnlookerEvent): string;
96
+ abstract onSessionStart(rawEvent: unknown): OnlookerEvent;
97
+ abstract onSessionEnd(rawEvent: unknown): OnlookerEvent;
98
+ abstract onFileWrite(rawEvent: unknown): OnlookerEvent;
99
+ abstract onFileEdit(rawEvent: unknown): OnlookerEvent;
100
+ abstract onShellExec(rawEvent: unknown): OnlookerEvent;
101
+ abstract onWebFetch(rawEvent: unknown): OnlookerEvent;
102
+ abstract onAgentSpawn(rawEvent: unknown): OnlookerEvent;
103
+ abstract onAgentComplete(rawEvent: unknown): OnlookerEvent;
104
+ abstract blockOperation(reason: string): void;
105
+ abstract allowOperation(): void;
106
+ abstract injectContext(content: string): void;
107
+ }
108
+ //# sourceMappingURL=base-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base-adapter.d.ts","sourceRoot":"","sources":["../src/base-adapter.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAEX,SAAS,EACT,aAAa,EACb,UAAU,EACV,SAAS,EACT,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEzD,MAAM,WAAW,kBAAkB;IAClC;;;;OAIG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;CACxC;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,SAAS;IACpD,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,CAAC,CAAC;IACd,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;GAYG;AACH,8BAAsB,WAAY,YAAW,sBAAsB;IAClE,SAAgB,SAAS,EAAE,SAAS,CAAC;IACrC,SAAgB,OAAO,EAAE,MAAM,CAAC;IAEhC,8DAA8D;IAC9D,SAAS,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAErC;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;IAE3D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;gBAEzB,IAAI,EAAE;QACjB,SAAS,EAAE,SAAS,CAAC;QACrB,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,CAAC,EAAE,kBAAkB,CAAC;KAC7B;IAWD;;;;;OAKG;IACH,SAAS,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAI/C;;;OAGG;IACH,SAAS,CAAC,YAAY,IAAI,MAAM;IAIhC;;;;;;OAMG;IACH,SAAS,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAQjD;;;;OAIG;IACH,SAAS,CAAC,UAAU,CAAC,CAAC,SAAS,SAAS,EACvC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,GACzB,aAAa,CAAC,CAAC,CAAC;IAsBnB;;;OAGG;IACH,SAAS,CAAC,UAAU,CAAC,CAAC,SAAS,SAAS,EACvC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,GACzB,aAAa,CAAC,CAAC,CAAC;IAMnB,gEAAgE;IACzD,OAAO,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM;IAM5C,QAAQ,CAAC,cAAc,CAAC,QAAQ,EAAE,OAAO,GAAG,aAAa;IACzD,QAAQ,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,GAAG,aAAa;IACvD,QAAQ,CAAC,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,aAAa;IACtD,QAAQ,CAAC,UAAU,CAAC,QAAQ,EAAE,OAAO,GAAG,aAAa;IACrD,QAAQ,CAAC,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,aAAa;IACtD,QAAQ,CAAC,UAAU,CAAC,QAAQ,EAAE,OAAO,GAAG,aAAa;IACrD,QAAQ,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,GAAG,aAAa;IACvD,QAAQ,CAAC,eAAe,CAAC,QAAQ,EAAE,OAAO,GAAG,aAAa;IAE1D,QAAQ,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAC7C,QAAQ,CAAC,cAAc,IAAI,IAAI;IAC/B,QAAQ,CAAC,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;CAC7C"}
@@ -0,0 +1,123 @@
1
+ // BaseAdapter — abstract base class every runtime adapter extends.
2
+ //
3
+ // Owns the cross-runtime concerns so concrete adapters can focus on the
4
+ // runtime-specific translation:
5
+ // * session id tracking with one monotonic sequence counter per
6
+ // session
7
+ // * createEventBase() — shared envelope fields (id, schema_version,
8
+ // timestamp, sequence, etc.)
9
+ // * writeEvent() — append to ~/.onlooker/<runtimeId>/<plugin>/<sid>.jsonl
10
+ //
11
+ // Subclasses implement the `on*` callbacks and the decision-delivery
12
+ // methods. Subclasses MUST call `this.startSession(sessionId)` from
13
+ // their session-start handler so the sequence counter is initialised
14
+ // before any other event for that session is created.
15
+ import { randomUUID } from "node:crypto";
16
+ import { createEvent } from "@onlooker-community/schema";
17
+ import { EventWriter } from "./event-writer.js";
18
+ /**
19
+ * Abstract base every OnlookerRuntimeAdapter extends.
20
+ *
21
+ * Subclasses must:
22
+ * 1. Pass `runtimeId`, `version`, and `machineId` to `super()`.
23
+ * 2. Call `this.startSession(sessionId)` from `onSessionStart` (and
24
+ * whenever they otherwise observe a session begin) so the
25
+ * sequence counter exists.
26
+ * 3. Implement the abstract `on*` callbacks and the
27
+ * RuntimeDecisionDelivery methods.
28
+ * 4. Use `this.buildEvent()` to construct events — that's what
29
+ * keeps id/schema_version/sequence consistent.
30
+ */
31
+ export class BaseAdapter {
32
+ runtimeId;
33
+ version;
34
+ /** Stable machine identifier (UUID). One per host install. */
35
+ machineId;
36
+ /**
37
+ * Per-session sequence counters. Each `createEvent` call inside
38
+ * `buildEvent` reads-then-increments the counter for that session
39
+ * id so events within a session carry monotonic sequence numbers.
40
+ */
41
+ sequence = new Map();
42
+ writer;
43
+ constructor(args) {
44
+ this.runtimeId = args.runtimeId;
45
+ this.version = args.version;
46
+ this.machineId = args.machineId;
47
+ this.writer =
48
+ args.options?.eventWriter ??
49
+ new EventWriter(args.options?.eventWriterOptions ?? {});
50
+ }
51
+ // ── Session tracking ────────────────────────────────────────────────────
52
+ /**
53
+ * Initialise (or reset) the sequence counter for `sessionId`.
54
+ * Called from `onSessionStart`. Idempotent — calling twice resets
55
+ * the counter, which is the desired behavior for resumed sessions
56
+ * that issue a fresh session-start event.
57
+ */
58
+ startSession(sessionId) {
59
+ this.sequence.set(sessionId, 0);
60
+ }
61
+ /**
62
+ * Generate a new session id. Adapters can pull this if the runtime
63
+ * doesn't surface one of its own (e.g. a stateless tool invocation).
64
+ */
65
+ newSessionId() {
66
+ return randomUUID();
67
+ }
68
+ /**
69
+ * Read-and-increment the sequence counter for `sessionId`. Lazily
70
+ * initialises if the session wasn't seen before — adapters
71
+ * occasionally receive events out of order (session-end fires
72
+ * before session-start due to clock skew), and we'd rather emit
73
+ * those with sequence=0 than crash.
74
+ */
75
+ nextSequence(sessionId) {
76
+ const current = this.sequence.get(sessionId) ?? 0;
77
+ this.sequence.set(sessionId, current + 1);
78
+ return current;
79
+ }
80
+ // ── Event construction ──────────────────────────────────────────────────
81
+ /**
82
+ * Shared envelope fields. Concrete `on*` callbacks call this to
83
+ * build the canonical event, then pass the result to `writeEvent`
84
+ * (or yield it from the callback for the caller to write).
85
+ */
86
+ buildEvent(params) {
87
+ const base = {
88
+ runtime: this.runtimeId,
89
+ plugin: params.plugin,
90
+ machine_id: this.machineId,
91
+ session_id: params.session_id,
92
+ event_type: params.event_type,
93
+ payload: params.payload,
94
+ };
95
+ if (params.adapter_id !== undefined)
96
+ base.adapter_id = params.adapter_id;
97
+ if (params.cost_usd !== undefined)
98
+ base.cost_usd = params.cost_usd;
99
+ if (params.token_count !== undefined)
100
+ base.token_count = params.token_count;
101
+ const event = createEvent(base);
102
+ // `createEvent` uses a module-scoped global counter; we override
103
+ // with our per-session counter so adapters running in long-lived
104
+ // processes (Claude Code daemon) keep per-session sequences
105
+ // instead of one monotonic counter for everything.
106
+ event.sequence = this.nextSequence(params.session_id);
107
+ return event;
108
+ }
109
+ /**
110
+ * Convenience: build + persist. Concrete adapters typically end
111
+ * their `on*` callbacks with this — `return this.writeEvent(...)`.
112
+ */
113
+ writeEvent(params) {
114
+ const event = this.buildEvent(params);
115
+ this.writer.write(event);
116
+ return event;
117
+ }
118
+ /** Test/inspection helper — exposes the resolved JSONL path. */
119
+ pathFor(event) {
120
+ return this.writer.pathFor(event);
121
+ }
122
+ }
123
+ //# sourceMappingURL=base-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base-adapter.js","sourceRoot":"","sources":["../src/base-adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import type { OnlookerEvent } from "@onlooker-community/schema";
2
+ export interface EventWriterOptions {
3
+ /**
4
+ * Root directory for canonical event JSONL. Defaults to
5
+ * `~/.onlooker`. Override for tests or alternate installs.
6
+ */
7
+ rootDir?: string;
8
+ }
9
+ /**
10
+ * Resolve the canonical JSONL path for a single event.
11
+ *
12
+ * `rootDir` defaults to `~/.onlooker` — callers passing a custom root
13
+ * (typically tests) get full control.
14
+ */
15
+ export declare function eventLogPath(event: OnlookerEvent, rootDir?: string): string;
16
+ /**
17
+ * Append a single canonical event to its per-session JSONL file. Side
18
+ * effect only — returns nothing. Errors are swallowed and reported via
19
+ * `onError` (default: stderr) so adapters can stay non-blocking.
20
+ */
21
+ export declare class EventWriter {
22
+ private readonly rootDir;
23
+ private readonly onError;
24
+ constructor(options?: EventWriterOptions, onError?: (err: unknown, event: OnlookerEvent) => void);
25
+ write(event: OnlookerEvent): void;
26
+ /**
27
+ * Path the next write for this event would land at. Useful for tests
28
+ * and for adapters that want to surface the log location to users.
29
+ */
30
+ pathFor(event: OnlookerEvent): string;
31
+ }
32
+ //# sourceMappingURL=event-writer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-writer.d.ts","sourceRoot":"","sources":["../src/event-writer.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAEhE,MAAM,WAAW,kBAAkB;IAClC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC3B,KAAK,EAAE,aAAa,EACpB,OAAO,GAAE,MAAqC,GAC5C,MAAM,CAOR;AAED;;;;GAIG;AACH,qBAAa,WAAW;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+C;gBAGtE,OAAO,GAAE,kBAAuB,EAChC,OAAO,GAAE,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa,KAAK,IAOhD;IAMF,KAAK,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAUjC;;;OAGG;IACH,OAAO,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM;CAGrC"}
@@ -0,0 +1,56 @@
1
+ // EventWriter — appends canonical events to per-session JSONL files.
2
+ //
3
+ // File layout, by contract:
4
+ // ~/.onlooker/<runtimeId>/<plugin>/<session_id>.jsonl
5
+ //
6
+ // Each event becomes a single JSON line. Writes are append-only and
7
+ // best-effort: if the filesystem is unavailable, the writer logs to
8
+ // stderr and continues so the runtime doesn't crash on disk pressure.
9
+ import { appendFileSync, mkdirSync } from "node:fs";
10
+ import { homedir } from "node:os";
11
+ import { dirname, join } from "node:path";
12
+ /**
13
+ * Resolve the canonical JSONL path for a single event.
14
+ *
15
+ * `rootDir` defaults to `~/.onlooker` — callers passing a custom root
16
+ * (typically tests) get full control.
17
+ */
18
+ export function eventLogPath(event, rootDir = join(homedir(), ".onlooker")) {
19
+ return join(rootDir, event.runtime, event.plugin, `${event.session_id}.jsonl`);
20
+ }
21
+ /**
22
+ * Append a single canonical event to its per-session JSONL file. Side
23
+ * effect only — returns nothing. Errors are swallowed and reported via
24
+ * `onError` (default: stderr) so adapters can stay non-blocking.
25
+ */
26
+ export class EventWriter {
27
+ rootDir;
28
+ onError;
29
+ constructor(options = {}, onError = (err, event) => {
30
+ // One-line stderr is enough to be greppable; full event would
31
+ // be noisy and may contain large payloads.
32
+ console.error(`[adapter-sdk] event write failed for ${event.event_type} ` +
33
+ `session=${event.session_id}: ${err.message}`);
34
+ }) {
35
+ this.rootDir = options.rootDir ?? join(homedir(), ".onlooker");
36
+ this.onError = onError;
37
+ }
38
+ write(event) {
39
+ const path = eventLogPath(event, this.rootDir);
40
+ try {
41
+ mkdirSync(dirname(path), { recursive: true });
42
+ appendFileSync(path, `${JSON.stringify(event)}\n`);
43
+ }
44
+ catch (err) {
45
+ this.onError(err, event);
46
+ }
47
+ }
48
+ /**
49
+ * Path the next write for this event would land at. Useful for tests
50
+ * and for adapters that want to surface the log location to users.
51
+ */
52
+ pathFor(event) {
53
+ return eventLogPath(event, this.rootDir);
54
+ }
55
+ }
56
+ //# sourceMappingURL=event-writer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-writer.js","sourceRoot":"","sources":["../src/event-writer.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,EAAE;AACF,4BAA4B;AAC5B,wDAAwD;AACxD,EAAE;AACF,oEAAoE;AACpE,oEAAoE;AACpE,sEAAsE;AAEtE,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAW1C;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAC3B,KAAoB,EACpB,UAAkB,IAAI,CAAC,OAAO,EAAE,EAAE,WAAW,CAAC;IAE9C,OAAO,IAAI,CACV,OAAO,EACP,KAAK,CAAC,OAAO,EACb,KAAK,CAAC,MAAM,EACZ,GAAG,KAAK,CAAC,UAAU,QAAQ,CAC3B,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,OAAO,WAAW;IACN,OAAO,CAAS;IAChB,OAAO,CAA+C;IAEvE,YACC,UAA8B,EAAE,EAChC,UAAwD,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE;QACtE,8DAA8D;QAC9D,2CAA2C;QAC3C,OAAO,CAAC,KAAK,CACZ,wCAAwC,KAAK,CAAC,UAAU,GAAG;YAC1D,WAAW,KAAK,CAAC,UAAU,KAAM,GAAa,CAAC,OAAO,EAAE,CACzD,CAAC;IACH,CAAC;QAED,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,WAAW,CAAC,CAAC;QAC/D,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACxB,CAAC;IAED,KAAK,CAAC,KAAoB;QACzB,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAC/C,IAAI,CAAC;YACJ,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC9C,cAAc,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACpD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC1B,CAAC;IACF,CAAC;IAED;;;OAGG;IACH,OAAO,CAAC,KAAoB;QAC3B,OAAO,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAC1C,CAAC;CACD"}
@@ -0,0 +1,4 @@
1
+ export { BaseAdapter, type BaseAdapterOptions, type BuildEventParams, } from "./base-adapter.js";
2
+ export { EventWriter, type EventWriterOptions, eventLogPath, } from "./event-writer.js";
3
+ export type { AdapterEventCallbacks, OnlookerEvent, OnlookerRuntimeAdapter, RuntimeDecisionDelivery, RuntimeId, } from "./types.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACN,WAAW,EACX,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACN,WAAW,EACX,KAAK,kBAAkB,EACvB,YAAY,GACZ,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACX,qBAAqB,EACrB,aAAa,EACb,sBAAsB,EACtB,uBAAuB,EACvB,SAAS,GACT,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ // Public API barrel.
2
+ export { BaseAdapter, } from "./base-adapter.js";
3
+ export { EventWriter, eventLogPath, } from "./event-writer.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qBAAqB;AAErB,OAAO,EACN,WAAW,GAGX,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACN,WAAW,EAEX,YAAY,GACZ,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,58 @@
1
+ import type { OnlookerEvent, RuntimeId } from "@onlooker-community/schema";
2
+ /**
3
+ * Mapping from canonical lifecycle event types to the OnlookerEvent
4
+ * payload shape the schema expects for each. Adapter callbacks are
5
+ * typed against this map so a future schema change surfaces as a type
6
+ * error in adapters before it surfaces in production.
7
+ */
8
+ export type AdapterEventCallbacks = {
9
+ onSessionStart: (rawEvent: unknown) => OnlookerEvent;
10
+ onSessionEnd: (rawEvent: unknown) => OnlookerEvent;
11
+ onFileWrite: (rawEvent: unknown) => OnlookerEvent;
12
+ onFileEdit: (rawEvent: unknown) => OnlookerEvent;
13
+ onShellExec: (rawEvent: unknown) => OnlookerEvent;
14
+ onWebFetch: (rawEvent: unknown) => OnlookerEvent;
15
+ onAgentSpawn: (rawEvent: unknown) => OnlookerEvent;
16
+ onAgentComplete: (rawEvent: unknown) => OnlookerEvent;
17
+ };
18
+ /**
19
+ * Plugin → runtime decision delivery. The plugin layer calls these to
20
+ * tell the runtime adapter how to act on a tool invocation. Adapters
21
+ * are free to translate the call to whatever native primitive the
22
+ * runtime exposes (Claude Code hook stdout, MCP response, IDE notify,
23
+ * etc.). All three are best-effort by contract — adapters must never
24
+ * throw out of these methods.
25
+ */
26
+ export interface RuntimeDecisionDelivery {
27
+ /** Block the in-flight operation. `reason` is shown to the user. */
28
+ blockOperation(reason: string): void;
29
+ /** Allow the in-flight operation to proceed unchanged. */
30
+ allowOperation(): void;
31
+ /**
32
+ * Inject context into the runtime's prompt / conversation surface.
33
+ * The exact mechanism depends on the runtime: Claude Code uses
34
+ * SessionStart `additionalContext`, Cursor a system message, etc.
35
+ */
36
+ injectContext(content: string): void;
37
+ }
38
+ /**
39
+ * The OnlookerRuntimeAdapter contract.
40
+ *
41
+ * Implementations sit between a runtime (Claude Code, Cursor, ...) and
42
+ * the canonical event bus. They:
43
+ * - normalise runtime-native events into OnlookerEvent envelopes via
44
+ * the `on*` callbacks
45
+ * - deliver plugin decisions back to the runtime via the
46
+ * RuntimeDecisionDelivery methods
47
+ *
48
+ * Every adapter declares its `runtimeId` (one of the canonical strings
49
+ * in the schema enum) and a semantic `version` so consumers can pin or
50
+ * gate by adapter capability.
51
+ */
52
+ export interface OnlookerRuntimeAdapter extends AdapterEventCallbacks, RuntimeDecisionDelivery {
53
+ readonly runtimeId: RuntimeId;
54
+ readonly version: string;
55
+ }
56
+ /** Re-export RuntimeId so adapters don't have to import from schema. */
57
+ export type { OnlookerEvent, RuntimeId };
58
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAE3E;;;;;GAKG;AACH,MAAM,MAAM,qBAAqB,GAAG;IACnC,cAAc,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,aAAa,CAAC;IACrD,YAAY,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,aAAa,CAAC;IACnD,WAAW,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,aAAa,CAAC;IAClD,UAAU,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,aAAa,CAAC;IACjD,WAAW,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,aAAa,CAAC;IAClD,UAAU,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,aAAa,CAAC;IACjD,YAAY,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,aAAa,CAAC;IACnD,eAAe,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,aAAa,CAAC;CACtD,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,WAAW,uBAAuB;IACvC,oEAAoE;IACpE,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,0DAA0D;IAC1D,cAAc,IAAI,IAAI,CAAC;IACvB;;;;OAIG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,sBAChB,SAAQ,qBAAqB,EAC5B,uBAAuB;IACxB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB;AAED,wEAAwE;AACxE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,9 @@
1
+ // Public types for the Onlooker adapter SDK.
2
+ //
3
+ // `OnlookerRuntimeAdapter` is the contract every runtime adapter implements.
4
+ // Adapters translate runtime-native events (Claude Code hook payloads,
5
+ // Cursor tool calls, GitHub Copilot completions, etc.) into canonical
6
+ // OnlookerEvent envelopes from @onlooker-community/schema, and deliver
7
+ // plugin decisions back to the runtime in its native format.
8
+ export {};
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,EAAE;AACF,6EAA6E;AAC7E,uEAAuE;AACvE,sEAAsE;AACtE,uEAAuE;AACvE,6DAA6D"}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@onlooker-community/adapter-sdk",
3
+ "version": "1.0.0",
4
+ "description": "Foundational SDK for Onlooker runtime adapters — defines the OnlookerRuntimeAdapter interface, base class, and canonical event writer.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc -p tsconfig.build.json",
19
+ "ci": "npx @biomejs/biome ci",
20
+ "lint": "npx @biomejs/biome lint",
21
+ "lint:fix": "npx @biomejs/biome lint --fix",
22
+ "format": "npx @biomejs/biome format",
23
+ "format:fix": "npx @biomejs/biome format --write",
24
+ "fix": "npx @biomejs/biome check --write",
25
+ "test": "vitest run",
26
+ "typecheck": "tsc --noEmit",
27
+ "verify": "npm run ci && npm run typecheck && npm run test && npm run build",
28
+ "prepare": "simple-git-hooks",
29
+ "prepublishOnly": "npm run build && npm test"
30
+ },
31
+ "simple-git-hooks": {
32
+ "pre-push": "npm run verify"
33
+ },
34
+ "peerDependencies": {
35
+ "@onlooker-community/schema": "^0.1.0"
36
+ },
37
+ "devDependencies": {
38
+ "@biomejs/biome": "2.4.14",
39
+ "@onlooker-community/schema": "^0.1.0",
40
+ "@types/node": "^20.0.0",
41
+ "simple-git-hooks": "^2.13.1",
42
+ "typescript": "^5.0.0",
43
+ "vitest": "^1.0.0"
44
+ },
45
+ "keywords": [
46
+ "onlooker",
47
+ "adapter",
48
+ "runtime",
49
+ "sdk",
50
+ "canonical-events"
51
+ ],
52
+ "license": "Apache-2.0",
53
+ "author": "Onlooker Community",
54
+ "repository": {
55
+ "type": "git",
56
+ "url": "https://github.com/onlooker-community/adapter-sdk.git"
57
+ },
58
+ "publishConfig": {
59
+ "access": "public"
60
+ }
61
+ }