@jongleberry/atel 0.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/README.md +168 -0
- package/dist/cli/append.d.mts +3 -0
- package/dist/cli/append.mjs +38 -0
- package/dist/cli/args.d.mts +14 -0
- package/dist/cli/args.mjs +49 -0
- package/dist/cli/context.d.mts +15 -0
- package/dist/cli/context.mjs +1 -0
- package/dist/cli/credentials.d.mts +6 -0
- package/dist/cli/credentials.mjs +45 -0
- package/dist/cli/env.d.mts +5 -0
- package/dist/cli/env.mjs +21 -0
- package/dist/cli/errors.d.mts +3 -0
- package/dist/cli/errors.mjs +3 -0
- package/dist/cli/get.d.mts +9 -0
- package/dist/cli/get.mjs +55 -0
- package/dist/cli/index.d.mts +18 -0
- package/dist/cli/index.mjs +69 -0
- package/dist/cli/output.d.mts +4 -0
- package/dist/cli/output.mjs +4 -0
- package/dist/cli/patch.d.mts +7 -0
- package/dist/cli/patch.mjs +57 -0
- package/dist/cli/usage.d.mts +1 -0
- package/dist/cli/usage.mjs +21 -0
- package/dist/client/append.d.mts +5 -0
- package/dist/client/append.mjs +18 -0
- package/dist/client/auth.d.mts +26 -0
- package/dist/client/auth.mjs +23 -0
- package/dist/client/credentials.d.mts +13 -0
- package/dist/client/credentials.mjs +19 -0
- package/dist/client/errors.d.mts +8 -0
- package/dist/client/errors.mjs +13 -0
- package/dist/client/http.d.mts +22 -0
- package/dist/client/http.mjs +66 -0
- package/dist/client/journal.d.mts +24 -0
- package/dist/client/journal.mjs +35 -0
- package/dist/client/journals.d.mts +18 -0
- package/dist/client/journals.mjs +28 -0
- package/dist/client/patch.d.mts +6 -0
- package/dist/client/patch.mjs +12 -0
- package/dist/client/stream.d.mts +30 -0
- package/dist/client/stream.mjs +90 -0
- package/dist/client/types.d.mts +65 -0
- package/dist/client/types.mjs +8 -0
- package/dist/format-error.d.mts +6 -0
- package/dist/format-error.mjs +15 -0
- package/dist/index.d.mts +20 -0
- package/dist/index.mjs +16 -0
- package/dist/mcp/dispatch.d.mts +3 -0
- package/dist/mcp/dispatch.mjs +16 -0
- package/dist/mcp/schemas.d.mts +2 -0
- package/dist/mcp/schemas.mjs +69 -0
- package/dist/mcp/server.d.mts +10 -0
- package/dist/mcp/server.mjs +35 -0
- package/dist/mcp/tool-append.d.mts +3 -0
- package/dist/mcp/tool-append.mjs +17 -0
- package/dist/mcp/tool-get.d.mts +13 -0
- package/dist/mcp/tool-get.mjs +30 -0
- package/dist/mcp/tool-patch.d.mts +5 -0
- package/dist/mcp/tool-patch.mjs +22 -0
- package/dist/mcp/validate.d.mts +6 -0
- package/dist/mcp/validate.mjs +28 -0
- package/dist/session.d.mts +24 -0
- package/dist/session.mjs +68 -0
- package/package.json +45 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { requestJson } from './http.mjs';
|
|
2
|
+
const JSON_HEADERS = { 'content-type': 'application/json' };
|
|
3
|
+
/** Posts a single telemetry entry. `POST /telemetry` echoes back the created entry. */
|
|
4
|
+
export async function appendEntry(config, input) {
|
|
5
|
+
return requestJson(config, '/telemetry', {
|
|
6
|
+
method: 'POST',
|
|
7
|
+
headers: JSON_HEADERS,
|
|
8
|
+
body: JSON.stringify(input),
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
/** Posts a batch of telemetry entries. `POST /telemetry` echoes back the created entries. */
|
|
12
|
+
export async function appendEntriesBatch(config, inputs) {
|
|
13
|
+
return requestJson(config, '/telemetry', {
|
|
14
|
+
method: 'POST',
|
|
15
|
+
headers: JSON_HEADERS,
|
|
16
|
+
body: JSON.stringify(inputs),
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CredentialCreated, CredentialSummary } from './types.mjs';
|
|
2
|
+
export interface AuthOptions {
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
/** Admin token (`atl_admin_<name>_<secret>`) — never a telemetry token. */
|
|
5
|
+
adminToken: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Credential (tenant) management — admin-only. Never used by the MCP
|
|
9
|
+
* server; wired up for the CLI's `credentials` subcommands only.
|
|
10
|
+
*/
|
|
11
|
+
export declare class Auth {
|
|
12
|
+
#private;
|
|
13
|
+
constructor(options: AuthOptions);
|
|
14
|
+
/** Creates a new telemetry credential. The token is returned once. */
|
|
15
|
+
createCredentials(input: {
|
|
16
|
+
name: string;
|
|
17
|
+
}): Promise<CredentialCreated>;
|
|
18
|
+
/** Lists known credentials (never includes tokens). */
|
|
19
|
+
listCredentials(): Promise<CredentialSummary[]>;
|
|
20
|
+
/** Deletes a credential by id or by name. */
|
|
21
|
+
deleteCredentials(selector: {
|
|
22
|
+
id: string;
|
|
23
|
+
} | {
|
|
24
|
+
name: string;
|
|
25
|
+
}): Promise<void>;
|
|
26
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createCredential, deleteCredential, listCredentials } from './credentials.mjs';
|
|
2
|
+
/**
|
|
3
|
+
* Credential (tenant) management — admin-only. Never used by the MCP
|
|
4
|
+
* server; wired up for the CLI's `credentials` subcommands only.
|
|
5
|
+
*/
|
|
6
|
+
export class Auth {
|
|
7
|
+
#config;
|
|
8
|
+
constructor(options) {
|
|
9
|
+
this.#config = { baseUrl: options.baseUrl, token: options.adminToken };
|
|
10
|
+
}
|
|
11
|
+
/** Creates a new telemetry credential. The token is returned once. */
|
|
12
|
+
async createCredentials(input) {
|
|
13
|
+
return createCredential(this.#config, input);
|
|
14
|
+
}
|
|
15
|
+
/** Lists known credentials (never includes tokens). */
|
|
16
|
+
async listCredentials() {
|
|
17
|
+
return listCredentials(this.#config);
|
|
18
|
+
}
|
|
19
|
+
/** Deletes a credential by id or by name. */
|
|
20
|
+
async deleteCredentials(selector) {
|
|
21
|
+
return deleteCredential(this.#config, selector);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ClientConfig, CredentialCreated, CredentialSummary } from './types.mjs';
|
|
2
|
+
/** `POST /credentials` (admin-only) — creates a telemetry credential. The token is shown once. */
|
|
3
|
+
export declare function createCredential(config: ClientConfig, input: {
|
|
4
|
+
name: string;
|
|
5
|
+
}): Promise<CredentialCreated>;
|
|
6
|
+
/** `GET /credentials` (admin-only) — lists credentials; never includes tokens. */
|
|
7
|
+
export declare function listCredentials(config: ClientConfig): Promise<CredentialSummary[]>;
|
|
8
|
+
/** `DELETE /credentials?id=` or `?name=` (admin-only). */
|
|
9
|
+
export declare function deleteCredential(config: ClientConfig, selector: {
|
|
10
|
+
id: string;
|
|
11
|
+
} | {
|
|
12
|
+
name: string;
|
|
13
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { rawRequest, requestJson } from './http.mjs';
|
|
2
|
+
/** `POST /credentials` (admin-only) — creates a telemetry credential. The token is shown once. */
|
|
3
|
+
export async function createCredential(config, input) {
|
|
4
|
+
return requestJson(config, '/credentials', {
|
|
5
|
+
method: 'POST',
|
|
6
|
+
headers: { 'content-type': 'application/json' },
|
|
7
|
+
body: JSON.stringify(input),
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
/** `GET /credentials` (admin-only) — lists credentials; never includes tokens. */
|
|
11
|
+
export async function listCredentials(config) {
|
|
12
|
+
return requestJson(config, '/credentials', { method: 'GET' });
|
|
13
|
+
}
|
|
14
|
+
/** `DELETE /credentials?id=` or `?name=` (admin-only). */
|
|
15
|
+
export async function deleteCredential(config, selector) {
|
|
16
|
+
const query = 'id' in selector ? { id: selector.id } : { name: selector.name };
|
|
17
|
+
const response = await rawRequest(config, '/credentials', { method: 'DELETE', query });
|
|
18
|
+
await response.text();
|
|
19
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Thrown for any non-2xx response from the atel HTTP API. */
|
|
2
|
+
export declare class AtelError extends Error {
|
|
3
|
+
/** HTTP status code of the failed response. */
|
|
4
|
+
readonly status: number;
|
|
5
|
+
/** Parsed error body — JSON if the response was JSON, raw text otherwise. */
|
|
6
|
+
readonly body: unknown;
|
|
7
|
+
constructor(message: string, status: number, body: unknown);
|
|
8
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Thrown for any non-2xx response from the atel HTTP API. */
|
|
2
|
+
export class AtelError extends Error {
|
|
3
|
+
/** HTTP status code of the failed response. */
|
|
4
|
+
status;
|
|
5
|
+
/** Parsed error body — JSON if the response was JSON, raw text otherwise. */
|
|
6
|
+
body;
|
|
7
|
+
constructor(message, status, body) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = 'AtelError';
|
|
10
|
+
this.status = status;
|
|
11
|
+
this.body = body;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ClientConfig, GetRawQuery, TelemetryWireFormat } from './types.mjs';
|
|
2
|
+
/** Query params as they go on the wire — every value is a string. */
|
|
3
|
+
type WireQuery = Record<string, string>;
|
|
4
|
+
/** Converts a typed `GET /telemetry` query into wire-format string params. */
|
|
5
|
+
export declare function buildTelemetriesQuery(query: GetRawQuery): WireQuery;
|
|
6
|
+
/** The `Accept` header that matches a given wire format. */
|
|
7
|
+
export declare function acceptHeaderFor(format: TelemetryWireFormat): string;
|
|
8
|
+
interface RequestOptions {
|
|
9
|
+
method: string;
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
body?: string;
|
|
12
|
+
query?: WireQuery;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Issues an authenticated HTTP request and returns the raw `Response`,
|
|
16
|
+
* throwing `AtelError` on any non-2xx status. Callers that need the
|
|
17
|
+
* body streamed (not buffered) should read `response.body` directly.
|
|
18
|
+
*/
|
|
19
|
+
export declare function rawRequest(config: ClientConfig, path: string, options: RequestOptions): Promise<Response>;
|
|
20
|
+
/** Issues an authenticated HTTP request and parses the response body as JSON. */
|
|
21
|
+
export declare function requestJson<T>(config: ClientConfig, path: string, options: RequestOptions): Promise<T>;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { AtelError } from './errors.mjs';
|
|
2
|
+
/** Converts a typed `GET /telemetry` query into wire-format string params. */
|
|
3
|
+
export function buildTelemetriesQuery(query) {
|
|
4
|
+
const wire = {};
|
|
5
|
+
if (query.sessionId !== undefined)
|
|
6
|
+
wire.sessionId = query.sessionId;
|
|
7
|
+
if (query.agent !== undefined)
|
|
8
|
+
wire.agent = query.agent;
|
|
9
|
+
if (query.archived !== undefined)
|
|
10
|
+
wire.archived = String(query.archived);
|
|
11
|
+
if (query.format !== undefined)
|
|
12
|
+
wire.format = query.format;
|
|
13
|
+
return wire;
|
|
14
|
+
}
|
|
15
|
+
const ACCEPT_HEADERS = {
|
|
16
|
+
json: 'application/json',
|
|
17
|
+
jsonl: 'application/x-ndjson',
|
|
18
|
+
markdown: 'text/markdown',
|
|
19
|
+
};
|
|
20
|
+
/** The `Accept` header that matches a given wire format. */
|
|
21
|
+
export function acceptHeaderFor(format) {
|
|
22
|
+
return ACCEPT_HEADERS[format];
|
|
23
|
+
}
|
|
24
|
+
function buildUrl(baseUrl, path, query) {
|
|
25
|
+
const base = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
|
|
26
|
+
const url = new URL(path.replace(/^\//, ''), base);
|
|
27
|
+
for (const [key, value] of Object.entries(query)) {
|
|
28
|
+
url.searchParams.set(key, value);
|
|
29
|
+
}
|
|
30
|
+
return url;
|
|
31
|
+
}
|
|
32
|
+
async function parseErrorBody(response) {
|
|
33
|
+
const text = await response.text();
|
|
34
|
+
if (!text)
|
|
35
|
+
return undefined;
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(text);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return text;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Issues an authenticated HTTP request and returns the raw `Response`,
|
|
45
|
+
* throwing `AtelError` on any non-2xx status. Callers that need the
|
|
46
|
+
* body streamed (not buffered) should read `response.body` directly.
|
|
47
|
+
*/
|
|
48
|
+
export async function rawRequest(config, path, options) {
|
|
49
|
+
const url = buildUrl(config.baseUrl, path, options.query ?? {});
|
|
50
|
+
const headers = new Headers(options.headers);
|
|
51
|
+
headers.set('authorization', `Bearer ${config.token}`);
|
|
52
|
+
const init = { method: options.method, headers };
|
|
53
|
+
if (options.body !== undefined)
|
|
54
|
+
init.body = options.body;
|
|
55
|
+
const response = await fetch(url, init);
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
const body = await parseErrorBody(response);
|
|
58
|
+
throw new AtelError(`atel request failed: ${options.method} ${path} -> ${response.status}`, response.status, body);
|
|
59
|
+
}
|
|
60
|
+
return response;
|
|
61
|
+
}
|
|
62
|
+
/** Issues an authenticated HTTP request and parses the response body as JSON. */
|
|
63
|
+
export async function requestJson(config, path, options) {
|
|
64
|
+
const response = await rawRequest(config, path, options);
|
|
65
|
+
return (await response.json());
|
|
66
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { GetEntriesQuery, TelemetryEntry } from './types.mjs';
|
|
2
|
+
export interface TelemetryOptions {
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
token: string;
|
|
5
|
+
/** Explicit session id; falls back through `resolveSessionId` if omitted. */
|
|
6
|
+
sessionId?: string;
|
|
7
|
+
/** Agent identifier attached to every entry. Defaults to `'claude-code'`. */
|
|
8
|
+
agent?: string;
|
|
9
|
+
}
|
|
10
|
+
export type TelemetryGetOptions = Omit<GetEntriesQuery, 'sessionId' | 'agent'>;
|
|
11
|
+
/**
|
|
12
|
+
* A telemetry stream bound to one session + agent — the ergonomic entry
|
|
13
|
+
* point for "record what I'm doing, then read it back later."
|
|
14
|
+
*/
|
|
15
|
+
export declare class Telemetry {
|
|
16
|
+
#private;
|
|
17
|
+
readonly sessionId: string;
|
|
18
|
+
readonly agent: string;
|
|
19
|
+
constructor(options: TelemetryOptions);
|
|
20
|
+
/** Appends one unstructured entry to this session's telemetry stream. */
|
|
21
|
+
append(data: Record<string, unknown>): Promise<TelemetryEntry>;
|
|
22
|
+
/** Reads this session's telemetry back, streaming entries as they arrive. */
|
|
23
|
+
get(options?: TelemetryGetOptions): AsyncIterable<TelemetryEntry>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { appendEntry } from './append.mjs';
|
|
2
|
+
import { streamEntries } from './stream.mjs';
|
|
3
|
+
import { resolveSessionId } from '../session.mjs';
|
|
4
|
+
const DEFAULT_AGENT = 'claude-code';
|
|
5
|
+
/**
|
|
6
|
+
* A telemetry stream bound to one session + agent — the ergonomic entry
|
|
7
|
+
* point for "record what I'm doing, then read it back later."
|
|
8
|
+
*/
|
|
9
|
+
export class Telemetry {
|
|
10
|
+
sessionId;
|
|
11
|
+
agent;
|
|
12
|
+
#config;
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.#config = { baseUrl: options.baseUrl, token: options.token };
|
|
15
|
+
this.sessionId = resolveSessionId(options.sessionId);
|
|
16
|
+
this.agent = options.agent ?? DEFAULT_AGENT;
|
|
17
|
+
}
|
|
18
|
+
/** Appends one unstructured entry to this session's telemetry stream. */
|
|
19
|
+
async append(data) {
|
|
20
|
+
return appendEntry(this.#config, {
|
|
21
|
+
sessionId: this.sessionId,
|
|
22
|
+
agent: this.agent,
|
|
23
|
+
data,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/** Reads this session's telemetry back, streaming entries as they arrive. */
|
|
27
|
+
get(options = {}) {
|
|
28
|
+
const query = { sessionId: this.sessionId, agent: this.agent };
|
|
29
|
+
if (options.archived !== undefined)
|
|
30
|
+
query.archived = options.archived;
|
|
31
|
+
if (options.format !== undefined)
|
|
32
|
+
query.format = options.format;
|
|
33
|
+
return streamEntries(this.#config, query);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AppendInput, ClientConfig, GetEntriesQuery, PatchOp, TelemetryEntry } from './types.mjs';
|
|
2
|
+
/**
|
|
3
|
+
* Cross-session telemetry operations — batch append, filtered reads across
|
|
4
|
+
* sessions/agents, and archive/data patches. Use this instead of
|
|
5
|
+
* `Telemetry` when you're not bound to one session (e.g. a distill pass
|
|
6
|
+
* over a credential's whole history).
|
|
7
|
+
*/
|
|
8
|
+
export declare class Telemetries {
|
|
9
|
+
#private;
|
|
10
|
+
constructor(options: ClientConfig);
|
|
11
|
+
/** Appends one entry, or a batch — mirrors what `POST /telemetry` accepts. */
|
|
12
|
+
append(input: AppendInput): Promise<TelemetryEntry>;
|
|
13
|
+
append(input: AppendInput[]): Promise<TelemetryEntry[]>;
|
|
14
|
+
/** Reads entries across sessions/agents, streaming as they arrive. */
|
|
15
|
+
get(query?: GetEntriesQuery): AsyncIterable<TelemetryEntry>;
|
|
16
|
+
/** Batch-patches entries by id; `data` is a shallow merge, not a replace. */
|
|
17
|
+
patch(patches: PatchOp[]): Promise<TelemetryEntry[]>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { appendEntry, appendEntriesBatch } from './append.mjs';
|
|
2
|
+
import { patchEntries } from './patch.mjs';
|
|
3
|
+
import { streamEntries } from './stream.mjs';
|
|
4
|
+
/**
|
|
5
|
+
* Cross-session telemetry operations — batch append, filtered reads across
|
|
6
|
+
* sessions/agents, and archive/data patches. Use this instead of
|
|
7
|
+
* `Telemetry` when you're not bound to one session (e.g. a distill pass
|
|
8
|
+
* over a credential's whole history).
|
|
9
|
+
*/
|
|
10
|
+
export class Telemetries {
|
|
11
|
+
#config;
|
|
12
|
+
constructor(options) {
|
|
13
|
+
this.#config = options;
|
|
14
|
+
}
|
|
15
|
+
append(input) {
|
|
16
|
+
return Array.isArray(input)
|
|
17
|
+
? appendEntriesBatch(this.#config, input)
|
|
18
|
+
: appendEntry(this.#config, input);
|
|
19
|
+
}
|
|
20
|
+
/** Reads entries across sessions/agents, streaming as they arrive. */
|
|
21
|
+
get(query = {}) {
|
|
22
|
+
return streamEntries(this.#config, query);
|
|
23
|
+
}
|
|
24
|
+
/** Batch-patches entries by id; `data` is a shallow merge, not a replace. */
|
|
25
|
+
async patch(patches) {
|
|
26
|
+
return patchEntries(this.#config, patches);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ClientConfig, PatchOp, TelemetryEntry } from './types.mjs';
|
|
2
|
+
/**
|
|
3
|
+
* Batch-patches telemetry entries. `data` is a shallow merge into each
|
|
4
|
+
* entry's existing `data` blob, not a replace.
|
|
5
|
+
*/
|
|
6
|
+
export declare function patchEntries(config: ClientConfig, patches: PatchOp[]): Promise<TelemetryEntry[]>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { requestJson } from './http.mjs';
|
|
2
|
+
/**
|
|
3
|
+
* Batch-patches telemetry entries. `data` is a shallow merge into each
|
|
4
|
+
* entry's existing `data` blob, not a replace.
|
|
5
|
+
*/
|
|
6
|
+
export async function patchEntries(config, patches) {
|
|
7
|
+
return requestJson(config, '/telemetry', {
|
|
8
|
+
method: 'PATCH',
|
|
9
|
+
headers: { 'content-type': 'application/json' },
|
|
10
|
+
body: JSON.stringify(patches),
|
|
11
|
+
});
|
|
12
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ClientConfig, GetEntriesQuery, GetRawQuery, TelemetryEntry } from './types.mjs';
|
|
2
|
+
/**
|
|
3
|
+
* Issues `GET /telemetry` and returns the raw `Response`, without reading
|
|
4
|
+
* the body. Used both for parsed-entry reads (below) and for the CLI's raw
|
|
5
|
+
* passthrough of whatever wire format the user asked for.
|
|
6
|
+
*/
|
|
7
|
+
export declare function fetchTelemetriesRaw(config: ClientConfig, query: GetRawQuery): Promise<Response>;
|
|
8
|
+
/**
|
|
9
|
+
* Parses a `GET /telemetry` response body as newline-delimited JSON,
|
|
10
|
+
* genuinely incrementally: each line is parsed and yielded as soon as it
|
|
11
|
+
* has fully arrived, via the underlying `ReadableStream` reader — not after
|
|
12
|
+
* buffering the whole response.
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseNdjsonStream(response: Response): AsyncGenerator<TelemetryEntry>;
|
|
15
|
+
/**
|
|
16
|
+
* Parses a `GET /telemetry` response body as a single JSON array. This
|
|
17
|
+
* cannot be genuinely incremental — a JSON array isn't a valid JSON
|
|
18
|
+
* document until its closing `]` arrives, so this buffers the full body
|
|
19
|
+
* before yielding anything. Prefer `format: 'jsonl'` (the default) for real
|
|
20
|
+
* incremental delivery; this path exists for parity with the server's
|
|
21
|
+
* default wire format.
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseJsonArrayBuffered(response: Response): AsyncGenerator<TelemetryEntry>;
|
|
24
|
+
/**
|
|
25
|
+
* Reads `GET /telemetry` as parsed, structured entries. Always requests
|
|
26
|
+
* `jsonl` on the wire by default (true incremental parsing); pass
|
|
27
|
+
* `format: 'json'` to match the server's default array format instead, at
|
|
28
|
+
* the cost of buffering the full response (see `parseJsonArrayBuffered`).
|
|
29
|
+
*/
|
|
30
|
+
export declare function streamEntries(config: ClientConfig, query?: GetEntriesQuery): AsyncGenerator<TelemetryEntry>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { acceptHeaderFor, buildTelemetriesQuery, rawRequest } from './http.mjs';
|
|
2
|
+
/**
|
|
3
|
+
* Issues `GET /telemetry` and returns the raw `Response`, without reading
|
|
4
|
+
* the body. Used both for parsed-entry reads (below) and for the CLI's raw
|
|
5
|
+
* passthrough of whatever wire format the user asked for.
|
|
6
|
+
*/
|
|
7
|
+
export async function fetchTelemetriesRaw(config, query) {
|
|
8
|
+
const format = query.format ?? 'json';
|
|
9
|
+
return rawRequest(config, '/telemetry', {
|
|
10
|
+
method: 'GET',
|
|
11
|
+
headers: { accept: acceptHeaderFor(format) },
|
|
12
|
+
query: buildTelemetriesQuery(query),
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Parses a `GET /telemetry` response body as newline-delimited JSON,
|
|
17
|
+
* genuinely incrementally: each line is parsed and yielded as soon as it
|
|
18
|
+
* has fully arrived, via the underlying `ReadableStream` reader — not after
|
|
19
|
+
* buffering the whole response.
|
|
20
|
+
*/
|
|
21
|
+
export async function* parseNdjsonStream(response) {
|
|
22
|
+
if (!response.body)
|
|
23
|
+
return;
|
|
24
|
+
const reader = response.body.getReader();
|
|
25
|
+
const decoder = new TextDecoder();
|
|
26
|
+
let buffer = '';
|
|
27
|
+
try {
|
|
28
|
+
for (;;) {
|
|
29
|
+
const { done, value } = await reader.read();
|
|
30
|
+
if (value) {
|
|
31
|
+
buffer += decoder.decode(value, { stream: true });
|
|
32
|
+
let newlineIndex = buffer.indexOf('\n');
|
|
33
|
+
while (newlineIndex !== -1) {
|
|
34
|
+
const line = buffer.slice(0, newlineIndex).trim();
|
|
35
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
36
|
+
if (line.length > 0)
|
|
37
|
+
yield JSON.parse(line);
|
|
38
|
+
newlineIndex = buffer.indexOf('\n');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (done)
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
const trailing = buffer.trim();
|
|
45
|
+
if (trailing.length > 0)
|
|
46
|
+
yield JSON.parse(trailing);
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
reader.releaseLock();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Parses a `GET /telemetry` response body as a single JSON array. This
|
|
54
|
+
* cannot be genuinely incremental — a JSON array isn't a valid JSON
|
|
55
|
+
* document until its closing `]` arrives, so this buffers the full body
|
|
56
|
+
* before yielding anything. Prefer `format: 'jsonl'` (the default) for real
|
|
57
|
+
* incremental delivery; this path exists for parity with the server's
|
|
58
|
+
* default wire format.
|
|
59
|
+
*/
|
|
60
|
+
export async function* parseJsonArrayBuffered(response) {
|
|
61
|
+
const text = (await response.text()).trim();
|
|
62
|
+
if (text.length === 0)
|
|
63
|
+
return;
|
|
64
|
+
const entries = JSON.parse(text);
|
|
65
|
+
for (const entry of entries)
|
|
66
|
+
yield entry;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Reads `GET /telemetry` as parsed, structured entries. Always requests
|
|
70
|
+
* `jsonl` on the wire by default (true incremental parsing); pass
|
|
71
|
+
* `format: 'json'` to match the server's default array format instead, at
|
|
72
|
+
* the cost of buffering the full response (see `parseJsonArrayBuffered`).
|
|
73
|
+
*/
|
|
74
|
+
export async function* streamEntries(config, query = {}) {
|
|
75
|
+
const format = query.format ?? 'jsonl';
|
|
76
|
+
const wireQuery = { format };
|
|
77
|
+
if (query.sessionId !== undefined)
|
|
78
|
+
wireQuery.sessionId = query.sessionId;
|
|
79
|
+
if (query.agent !== undefined)
|
|
80
|
+
wireQuery.agent = query.agent;
|
|
81
|
+
if (query.archived !== undefined)
|
|
82
|
+
wireQuery.archived = query.archived;
|
|
83
|
+
const response = await fetchTelemetriesRaw(config, wireQuery);
|
|
84
|
+
if (format === 'jsonl') {
|
|
85
|
+
yield* parseNdjsonStream(response);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
yield* parseJsonArrayBuffered(response);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared wire types for the atel HTTP client.
|
|
3
|
+
*
|
|
4
|
+
* These mirror the server's documented contract (see the plan doc / server
|
|
5
|
+
* `packages/server/src/core/handle-request.mts`) but are intentionally
|
|
6
|
+
* decoupled from it — this package must never import from `packages/server`.
|
|
7
|
+
*/
|
|
8
|
+
/** Config needed to reach the server: a base URL and a bearer token. */
|
|
9
|
+
export interface ClientConfig {
|
|
10
|
+
baseUrl: string;
|
|
11
|
+
token: string;
|
|
12
|
+
}
|
|
13
|
+
/** A single telemetry entry as returned by the server. */
|
|
14
|
+
export interface TelemetryEntry {
|
|
15
|
+
id: string;
|
|
16
|
+
sessionId: string;
|
|
17
|
+
agent: string;
|
|
18
|
+
createdAt: string;
|
|
19
|
+
archived: boolean;
|
|
20
|
+
data: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
/** Body accepted by `POST /telemetry` for a single entry. */
|
|
23
|
+
export interface AppendInput {
|
|
24
|
+
sessionId: string;
|
|
25
|
+
agent: string;
|
|
26
|
+
data: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Wire format for `GET /telemetry`. `'markdown'` is only meaningful for raw
|
|
30
|
+
* (unparsed) reads — see `client/stream.mts` for why entry-returning reads
|
|
31
|
+
* are restricted to `'json' | 'jsonl'`.
|
|
32
|
+
*/
|
|
33
|
+
export type TelemetryWireFormat = 'json' | 'jsonl' | 'markdown';
|
|
34
|
+
/** Format restricted to values that parse into structured entries. */
|
|
35
|
+
export type TelemetryEntryFormat = Extract<TelemetryWireFormat, 'json' | 'jsonl'>;
|
|
36
|
+
/** Query params accepted by `GET /telemetry` when reading parsed entries. */
|
|
37
|
+
export interface GetEntriesQuery {
|
|
38
|
+
sessionId?: string;
|
|
39
|
+
agent?: string;
|
|
40
|
+
archived?: boolean;
|
|
41
|
+
format?: TelemetryEntryFormat;
|
|
42
|
+
}
|
|
43
|
+
/** Query params accepted by `GET /telemetry` for a raw (unparsed) read. */
|
|
44
|
+
export interface GetRawQuery {
|
|
45
|
+
sessionId?: string;
|
|
46
|
+
agent?: string;
|
|
47
|
+
archived?: boolean;
|
|
48
|
+
format?: TelemetryWireFormat;
|
|
49
|
+
}
|
|
50
|
+
/** One patch operation accepted by `PATCH /telemetry`. `data` is a shallow merge. */
|
|
51
|
+
export interface PatchOp {
|
|
52
|
+
id: string;
|
|
53
|
+
archived?: boolean;
|
|
54
|
+
data?: Record<string, unknown>;
|
|
55
|
+
}
|
|
56
|
+
/** A telemetry or admin credential, as listed by `GET /credentials`. */
|
|
57
|
+
export interface CredentialSummary {
|
|
58
|
+
id: string;
|
|
59
|
+
name: string;
|
|
60
|
+
createdAt: string;
|
|
61
|
+
}
|
|
62
|
+
/** The one-time response from `POST /credentials`, including the secret token. */
|
|
63
|
+
export interface CredentialCreated extends CredentialSummary {
|
|
64
|
+
token: string;
|
|
65
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared wire types for the atel HTTP client.
|
|
3
|
+
*
|
|
4
|
+
* These mirror the server's documented contract (see the plan doc / server
|
|
5
|
+
* `packages/server/src/core/handle-request.mts`) but are intentionally
|
|
6
|
+
* decoupled from it — this package must never import from `packages/server`.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats any thrown value as a single-line, human-readable message — never
|
|
3
|
+
* a raw stack trace. Shared by the CLI (stderr) and the MCP server (tool
|
|
4
|
+
* error content), since a caller can throw anything in JS, not just `Error`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function formatError(err: unknown): string;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { AtelError } from './client/errors.mjs';
|
|
2
|
+
/**
|
|
3
|
+
* Formats any thrown value as a single-line, human-readable message — never
|
|
4
|
+
* a raw stack trace. Shared by the CLI (stderr) and the MCP server (tool
|
|
5
|
+
* error content), since a caller can throw anything in JS, not just `Error`.
|
|
6
|
+
*/
|
|
7
|
+
export function formatError(err) {
|
|
8
|
+
if (err instanceof AtelError) {
|
|
9
|
+
const bodyText = err.body === undefined ? '' : ` ${JSON.stringify(err.body)}`;
|
|
10
|
+
return `${err.message}${bodyText}`;
|
|
11
|
+
}
|
|
12
|
+
if (err instanceof Error)
|
|
13
|
+
return err.message;
|
|
14
|
+
return String(err);
|
|
15
|
+
}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public API of `@jongleberry/atel`: a thin `fetch`-based client for the
|
|
3
|
+
* atel HTTP service.
|
|
4
|
+
*
|
|
5
|
+
* - `Telemetry` — append + read, bound to one session + agent.
|
|
6
|
+
* - `Telemetries` — batch append/read/patch across sessions.
|
|
7
|
+
* - `Auth` — admin-only credential management.
|
|
8
|
+
*
|
|
9
|
+
* This module must never import `@aws-sdk/*` or anything from
|
|
10
|
+
* `packages/server` (enforced by `.dependency-cruiser.cjs`).
|
|
11
|
+
*/
|
|
12
|
+
export { Auth } from './client/auth.mjs';
|
|
13
|
+
export type { AuthOptions } from './client/auth.mjs';
|
|
14
|
+
export { AtelError } from './client/errors.mjs';
|
|
15
|
+
export { Telemetry } from './client/journal.mjs';
|
|
16
|
+
export type { TelemetryGetOptions, TelemetryOptions } from './client/journal.mjs';
|
|
17
|
+
export { Telemetries } from './client/journals.mjs';
|
|
18
|
+
export type { AppendInput, ClientConfig, CredentialCreated, CredentialSummary, GetEntriesQuery, GetRawQuery, PatchOp, TelemetryEntry, TelemetryEntryFormat, TelemetryWireFormat, } from './client/types.mjs';
|
|
19
|
+
export { resolveSessionId } from './session.mjs';
|
|
20
|
+
export type { ResolveSessionIdOptions } from './session.mjs';
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public API of `@jongleberry/atel`: a thin `fetch`-based client for the
|
|
3
|
+
* atel HTTP service.
|
|
4
|
+
*
|
|
5
|
+
* - `Telemetry` — append + read, bound to one session + agent.
|
|
6
|
+
* - `Telemetries` — batch append/read/patch across sessions.
|
|
7
|
+
* - `Auth` — admin-only credential management.
|
|
8
|
+
*
|
|
9
|
+
* This module must never import `@aws-sdk/*` or anything from
|
|
10
|
+
* `packages/server` (enforced by `.dependency-cruiser.cjs`).
|
|
11
|
+
*/
|
|
12
|
+
export { Auth } from './client/auth.mjs';
|
|
13
|
+
export { AtelError } from './client/errors.mjs';
|
|
14
|
+
export { Telemetry } from './client/journal.mjs';
|
|
15
|
+
export { Telemetries } from './client/journals.mjs';
|
|
16
|
+
export { resolveSessionId } from './session.mjs';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { handleTelemetryAppend } from './tool-append.mjs';
|
|
2
|
+
import { handleTelemetryGet } from './tool-get.mjs';
|
|
3
|
+
import { handleTelemetryPatch } from './tool-patch.mjs';
|
|
4
|
+
/** Routes a `tools/call` request to the matching handler by tool name. */
|
|
5
|
+
export async function dispatchTool(name, args, config) {
|
|
6
|
+
switch (name) {
|
|
7
|
+
case 'telemetry_append':
|
|
8
|
+
return handleTelemetryAppend(args, config);
|
|
9
|
+
case 'telemetry_get':
|
|
10
|
+
return handleTelemetryGet(args, config);
|
|
11
|
+
case 'telemetry_patch':
|
|
12
|
+
return handleTelemetryPatch(args, config);
|
|
13
|
+
default:
|
|
14
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
15
|
+
}
|
|
16
|
+
}
|