@codetelemetry/connect 0.1.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 +21 -0
- package/README.md +26 -0
- package/dist/bin/connect.d.ts +2 -0
- package/dist/bin/connect.js +6 -0
- package/dist/src/cli.d.ts +10 -0
- package/dist/src/cli.js +172 -0
- package/dist/src/codexConfig.d.ts +23 -0
- package/dist/src/codexConfig.js +265 -0
- package/dist/src/config.d.ts +26 -0
- package/dist/src/config.js +32 -0
- package/dist/src/connectivity.d.ts +27 -0
- package/dist/src/connectivity.js +54 -0
- package/dist/src/deviceClient.d.ts +32 -0
- package/dist/src/deviceClient.js +112 -0
- package/dist/src/index.d.ts +8 -0
- package/dist/src/index.js +8 -0
- package/dist/src/keys.d.ts +5 -0
- package/dist/src/keys.js +24 -0
- package/dist/src/onboard.d.ts +47 -0
- package/dist/src/onboard.js +84 -0
- package/dist/src/otlp.d.ts +18 -0
- package/dist/src/otlp.js +60 -0
- package/dist/src/repositoryHook.d.ts +17 -0
- package/dist/src/repositoryHook.js +631 -0
- package/dist/src/settings.d.ts +28 -0
- package/dist/src/settings.js +97 -0
- package/package.json +25 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { FetchLike } from "./otlp.ts";
|
|
2
|
+
import type { Endpoints } from "./config.ts";
|
|
3
|
+
export type DeviceCodeResponse = {
|
|
4
|
+
deviceCode: string;
|
|
5
|
+
userCode: string;
|
|
6
|
+
verificationUri: string;
|
|
7
|
+
expiresIn: number;
|
|
8
|
+
interval: number;
|
|
9
|
+
};
|
|
10
|
+
export declare function requestDeviceCode(endpoints: Endpoints, fetchImpl?: FetchLike, timeoutMs?: number): Promise<DeviceCodeResponse>;
|
|
11
|
+
export type DeviceKey = {
|
|
12
|
+
apiKey: string;
|
|
13
|
+
ingestEndpoint: string;
|
|
14
|
+
};
|
|
15
|
+
export type PollOnce = {
|
|
16
|
+
status: "authorized";
|
|
17
|
+
apiKey: string;
|
|
18
|
+
ingestEndpoint: string;
|
|
19
|
+
} | {
|
|
20
|
+
status: "pending";
|
|
21
|
+
} | {
|
|
22
|
+
status: "error";
|
|
23
|
+
error: string;
|
|
24
|
+
};
|
|
25
|
+
export declare function pollOnce(endpoints: Endpoints, deviceCode: string, fetchImpl?: FetchLike, timeoutMs?: number): Promise<PollOnce>;
|
|
26
|
+
export declare function pollForKey(endpoints: Endpoints, deviceCode: string, opts?: {
|
|
27
|
+
intervalMs?: number;
|
|
28
|
+
timeoutMs?: number;
|
|
29
|
+
fetchImpl?: FetchLike;
|
|
30
|
+
now?: () => number;
|
|
31
|
+
sleepImpl?: (ms: number) => Promise<void>;
|
|
32
|
+
}): Promise<DeviceKey>;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const TERMINAL_ERRORS = new Set(["expired_token", "invalid_grant", "access_denied"]);
|
|
2
|
+
export async function requestDeviceCode(endpoints, fetchImpl = fetch, timeoutMs = 10_000) {
|
|
3
|
+
const ac = new AbortController();
|
|
4
|
+
const timer = setTimeout(() => ac.abort(), Math.max(1, timeoutMs));
|
|
5
|
+
try {
|
|
6
|
+
let res;
|
|
7
|
+
try {
|
|
8
|
+
res = await fetchImpl(endpoints.deviceCodeUrl, {
|
|
9
|
+
method: "POST",
|
|
10
|
+
headers: { "content-type": "application/json" },
|
|
11
|
+
body: JSON.stringify({ client: "codetelemetry-connect" }),
|
|
12
|
+
signal: ac.signal,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
if (ac.signal.aborted)
|
|
17
|
+
throw new Error("device authorization request timed out");
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
if (!res.ok)
|
|
21
|
+
throw new Error(`device authorization failed (HTTP ${res.status})`);
|
|
22
|
+
let j;
|
|
23
|
+
try {
|
|
24
|
+
j = (await res.json());
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (ac.signal.aborted)
|
|
28
|
+
throw new Error("device authorization request timed out");
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
deviceCode: String(j.device_code),
|
|
33
|
+
userCode: String(j.user_code),
|
|
34
|
+
verificationUri: String(j.verification_uri ?? endpoints.verificationUri),
|
|
35
|
+
expiresIn: Number(j.expires_in ?? 600),
|
|
36
|
+
interval: Number(j.interval ?? 2),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
clearTimeout(timer);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async function tokenRequest(endpoints, deviceCode, fetchImpl, timeoutMs) {
|
|
44
|
+
const ac = new AbortController();
|
|
45
|
+
const timer = setTimeout(() => ac.abort(), Math.max(1, timeoutMs));
|
|
46
|
+
try {
|
|
47
|
+
let response;
|
|
48
|
+
try {
|
|
49
|
+
response = await fetchImpl(endpoints.deviceTokenUrl, {
|
|
50
|
+
method: "POST",
|
|
51
|
+
headers: { "content-type": "application/json" },
|
|
52
|
+
body: JSON.stringify({ device_code: deviceCode }),
|
|
53
|
+
signal: ac.signal,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (ac.signal.aborted)
|
|
58
|
+
return null;
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
const body = (await response.json().catch(() => ({})));
|
|
62
|
+
return { response, body };
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
clearTimeout(timer);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export async function pollOnce(endpoints, deviceCode, fetchImpl = fetch, timeoutMs = 8_000) {
|
|
69
|
+
const result = await tokenRequest(endpoints, deviceCode, fetchImpl, timeoutMs);
|
|
70
|
+
if (result === null)
|
|
71
|
+
return { status: "pending" };
|
|
72
|
+
const { response: res, body: j } = result;
|
|
73
|
+
if (res.ok && j.api_key) {
|
|
74
|
+
return { status: "authorized", apiKey: String(j.api_key), ingestEndpoint: String(j.ingest_endpoint) };
|
|
75
|
+
}
|
|
76
|
+
const error = String(j.error ?? "authorization_pending");
|
|
77
|
+
return TERMINAL_ERRORS.has(error) ? { status: "error", error } : { status: "pending" };
|
|
78
|
+
}
|
|
79
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
80
|
+
export async function pollForKey(endpoints, deviceCode, opts = {}) {
|
|
81
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
82
|
+
const now = opts.now ?? (() => Date.now());
|
|
83
|
+
const napFor = opts.sleepImpl ?? sleep;
|
|
84
|
+
let interval = opts.intervalMs ?? 2_000;
|
|
85
|
+
const timeout = opts.timeoutMs ?? 10 * 60 * 1000;
|
|
86
|
+
const start = now();
|
|
87
|
+
while (true) {
|
|
88
|
+
const remaining = timeout - (now() - start);
|
|
89
|
+
if (remaining <= 0) {
|
|
90
|
+
throw new Error("device authorization timed out — approval not completed in time");
|
|
91
|
+
}
|
|
92
|
+
const result = await tokenRequest(endpoints, deviceCode, fetchImpl, remaining);
|
|
93
|
+
if (result === null) {
|
|
94
|
+
throw new Error("device authorization timed out — approval not completed in time");
|
|
95
|
+
}
|
|
96
|
+
const { response: res, body: j } = result;
|
|
97
|
+
if (res.ok && j.api_key) {
|
|
98
|
+
return { apiKey: String(j.api_key), ingestEndpoint: String(j.ingest_endpoint) };
|
|
99
|
+
}
|
|
100
|
+
const error = String(j.error ?? (res.status >= 500 ? "server_error" : "authorization_pending"));
|
|
101
|
+
if (TERMINAL_ERRORS.has(error)) {
|
|
102
|
+
throw new Error(`device authorization ${error}`);
|
|
103
|
+
}
|
|
104
|
+
if (error === "slow_down") {
|
|
105
|
+
interval += 2_000;
|
|
106
|
+
}
|
|
107
|
+
if (now() - start + interval >= timeout) {
|
|
108
|
+
throw new Error("device authorization timed out — approval not completed in time");
|
|
109
|
+
}
|
|
110
|
+
await napFor(interval);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { onboard, type OnboardClient, type OnboardOptions, type OnboardResult } from "./onboard.ts";
|
|
2
|
+
export { resolveEndpoints, type Endpoints, type SettingsScope } from "./config.ts";
|
|
3
|
+
export { writeSettings, buildEnvBlock } from "./settings.ts";
|
|
4
|
+
export { writeCodexConfig, buildOtelSection, readCodexKey } from "./codexConfig.ts";
|
|
5
|
+
export { sendTestEvent } from "./otlp.ts";
|
|
6
|
+
export { confirmLanded } from "./connectivity.ts";
|
|
7
|
+
export { requestDeviceCode, pollForKey, pollOnce } from "./deviceClient.ts";
|
|
8
|
+
export { looksLikeKey, mask, keyHash } from "./keys.ts";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { onboard } from "./onboard.js";
|
|
2
|
+
export { resolveEndpoints } from "./config.js";
|
|
3
|
+
export { writeSettings, buildEnvBlock } from "./settings.js";
|
|
4
|
+
export { writeCodexConfig, buildOtelSection, readCodexKey } from "./codexConfig.js";
|
|
5
|
+
export { sendTestEvent } from "./otlp.js";
|
|
6
|
+
export { confirmLanded } from "./connectivity.js";
|
|
7
|
+
export { requestDeviceCode, pollForKey, pollOnce } from "./deviceClient.js";
|
|
8
|
+
export { looksLikeKey, mask, keyHash } from "./keys.js";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function looksLikeKey(value: unknown): value is string;
|
|
2
|
+
export declare function assertKey(value: unknown): string;
|
|
3
|
+
export declare function mask(value: unknown): string;
|
|
4
|
+
export declare function keyHash(value: string): string;
|
|
5
|
+
export declare function scrub(text: string): string;
|
package/dist/src/keys.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
const KEY_RE = /^cot_[A-Za-z0-9_-]{16,}$/;
|
|
3
|
+
const SECRET_RE = /cot_[A-Za-z0-9_-]{12,}/g;
|
|
4
|
+
export function looksLikeKey(value) {
|
|
5
|
+
return typeof value === "string" && KEY_RE.test(value.trim());
|
|
6
|
+
}
|
|
7
|
+
export function assertKey(value) {
|
|
8
|
+
if (!looksLikeKey(value)) {
|
|
9
|
+
throw new Error("not a valid CodeTelemetry ingest key (expected cot_…)");
|
|
10
|
+
}
|
|
11
|
+
return value.trim();
|
|
12
|
+
}
|
|
13
|
+
export function mask(value) {
|
|
14
|
+
if (typeof value !== "string" || !value.startsWith("cot_") || value.length < 8) {
|
|
15
|
+
return "cot_…";
|
|
16
|
+
}
|
|
17
|
+
return `cot_…${value.slice(-4)}`;
|
|
18
|
+
}
|
|
19
|
+
export function keyHash(value) {
|
|
20
|
+
return createHash("sha256").update(value).digest("hex");
|
|
21
|
+
}
|
|
22
|
+
export function scrub(text) {
|
|
23
|
+
return text.replace(SECRET_RE, (m) => mask(m));
|
|
24
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type SettingsScope } from "./config.ts";
|
|
2
|
+
import { type WriteResult } from "./settings.ts";
|
|
3
|
+
import { type SendTestResult, type FetchLike } from "./otlp.ts";
|
|
4
|
+
import { type ConfirmResult } from "./connectivity.ts";
|
|
5
|
+
export type OnboardStep = {
|
|
6
|
+
name: string;
|
|
7
|
+
ok: boolean;
|
|
8
|
+
detail: string;
|
|
9
|
+
};
|
|
10
|
+
export type OnboardResult = {
|
|
11
|
+
ok: boolean;
|
|
12
|
+
key: {
|
|
13
|
+
masked: string;
|
|
14
|
+
keyHash: string;
|
|
15
|
+
};
|
|
16
|
+
settings: WriteResult;
|
|
17
|
+
test: SendTestResult | null;
|
|
18
|
+
verify: ConfirmResult | null;
|
|
19
|
+
steps: OnboardStep[];
|
|
20
|
+
};
|
|
21
|
+
export type OnboardClient = "claude" | "codex";
|
|
22
|
+
export type OnboardOptions = {
|
|
23
|
+
key?: string;
|
|
24
|
+
getKey?: () => Promise<{
|
|
25
|
+
apiKey: string;
|
|
26
|
+
ingestEndpoint?: string;
|
|
27
|
+
}>;
|
|
28
|
+
client?: OnboardClient;
|
|
29
|
+
endpoints?: Partial<{
|
|
30
|
+
apiBase: string;
|
|
31
|
+
ingestEndpoint: string;
|
|
32
|
+
}>;
|
|
33
|
+
settings?: {
|
|
34
|
+
scope?: SettingsScope;
|
|
35
|
+
path?: string;
|
|
36
|
+
enrich?: boolean;
|
|
37
|
+
repositoryObservation?: boolean;
|
|
38
|
+
backup?: boolean;
|
|
39
|
+
};
|
|
40
|
+
sendTest?: boolean;
|
|
41
|
+
verify?: boolean;
|
|
42
|
+
verifyTimeoutMs?: number;
|
|
43
|
+
verifyIntervalMs?: number;
|
|
44
|
+
fetchImpl?: FetchLike;
|
|
45
|
+
logger?: (msg: string) => void;
|
|
46
|
+
};
|
|
47
|
+
export declare function onboard(opts: OnboardOptions): Promise<OnboardResult>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { resolveEndpoints, resolveSettingsPath } from "./config.js";
|
|
2
|
+
import { writeSettings } from "./settings.js";
|
|
3
|
+
import { resolveCodexConfigPath, writeCodexConfig } from "./codexConfig.js";
|
|
4
|
+
import { sendTestEvent } from "./otlp.js";
|
|
5
|
+
import { confirmLanded } from "./connectivity.js";
|
|
6
|
+
import { keyHash, mask, assertKey } from "./keys.js";
|
|
7
|
+
export async function onboard(opts) {
|
|
8
|
+
const log = opts.logger ?? (() => { });
|
|
9
|
+
const steps = [];
|
|
10
|
+
let apiKey;
|
|
11
|
+
let deviceIngest;
|
|
12
|
+
if (opts.key) {
|
|
13
|
+
apiKey = assertKey(opts.key);
|
|
14
|
+
steps.push({ name: "authenticate", ok: true, detail: "used provided ingest key" });
|
|
15
|
+
}
|
|
16
|
+
else if (opts.getKey) {
|
|
17
|
+
const got = await opts.getKey();
|
|
18
|
+
apiKey = assertKey(got.apiKey);
|
|
19
|
+
deviceIngest = got.ingestEndpoint;
|
|
20
|
+
steps.push({ name: "authenticate", ok: true, detail: "obtained ingest key via device flow" });
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
throw new Error("onboard requires either `key` or `getKey`");
|
|
24
|
+
}
|
|
25
|
+
const endpoints = resolveEndpoints({
|
|
26
|
+
apiBase: opts.endpoints?.apiBase,
|
|
27
|
+
ingestEndpoint: deviceIngest ?? opts.endpoints?.ingestEndpoint,
|
|
28
|
+
});
|
|
29
|
+
log(`Using ingest key ${mask(apiKey)} → ${endpoints.ingestEndpoint}`);
|
|
30
|
+
const settings = opts.client === "codex"
|
|
31
|
+
? writeCodexConfig({
|
|
32
|
+
key: apiKey,
|
|
33
|
+
ingestEndpoint: endpoints.ingestEndpoint,
|
|
34
|
+
configPath: resolveCodexConfigPath({ path: opts.settings?.path }),
|
|
35
|
+
enrich: opts.settings?.enrich,
|
|
36
|
+
repositoryObservation: opts.settings?.repositoryObservation,
|
|
37
|
+
backup: opts.settings?.backup,
|
|
38
|
+
})
|
|
39
|
+
: writeSettings({
|
|
40
|
+
key: apiKey,
|
|
41
|
+
ingestEndpoint: endpoints.ingestEndpoint,
|
|
42
|
+
settingsPath: resolveSettingsPath({ scope: opts.settings?.scope, path: opts.settings?.path }),
|
|
43
|
+
enrich: opts.settings?.enrich,
|
|
44
|
+
repositoryObservation: opts.settings?.repositoryObservation,
|
|
45
|
+
backup: opts.settings?.backup,
|
|
46
|
+
});
|
|
47
|
+
const varsNoun = opts.client === "codex" ? "OTEL keys" : "OTEL vars";
|
|
48
|
+
steps.push({
|
|
49
|
+
name: "write_config",
|
|
50
|
+
ok: true,
|
|
51
|
+
detail: `${settings.created ? "created" : "updated"} ${settings.path} (${settings.wrote.length} ${varsNoun})`,
|
|
52
|
+
});
|
|
53
|
+
log(`Wrote ${settings.wrote.length} ${varsNoun} to ${settings.path}${settings.backupPath ? ` (backup: ${settings.backupPath})` : ""}`);
|
|
54
|
+
let test = null;
|
|
55
|
+
if (opts.sendTest !== false) {
|
|
56
|
+
test = await sendTestEvent({ key: apiKey, ingestEndpoint: endpoints.ingestEndpoint, fetchImpl: opts.fetchImpl });
|
|
57
|
+
steps.push({
|
|
58
|
+
name: "send_test_event",
|
|
59
|
+
ok: test.ok,
|
|
60
|
+
detail: test.ok ? `probe accepted (HTTP ${test.httpStatus})` : `ingest returned HTTP ${test.httpStatus}`,
|
|
61
|
+
});
|
|
62
|
+
log(test.ok ? `Test event accepted (probe ${test.probeId})` : `Test event rejected (HTTP ${test.httpStatus})`);
|
|
63
|
+
}
|
|
64
|
+
let verify = null;
|
|
65
|
+
if (opts.verify !== false && test?.ok) {
|
|
66
|
+
log("Waiting for the probe to land…");
|
|
67
|
+
verify = await confirmLanded({
|
|
68
|
+
key: apiKey,
|
|
69
|
+
connectivityUrl: endpoints.connectivityUrl,
|
|
70
|
+
probeId: test.probeId,
|
|
71
|
+
fetchImpl: opts.fetchImpl,
|
|
72
|
+
timeoutMs: opts.verifyTimeoutMs,
|
|
73
|
+
intervalMs: opts.verifyIntervalMs,
|
|
74
|
+
});
|
|
75
|
+
steps.push({
|
|
76
|
+
name: "verify_telemetry",
|
|
77
|
+
ok: verify.landed,
|
|
78
|
+
detail: verify.landed ? `landed in ${verify.elapsedMs}ms` : `not seen within timeout (${verify.elapsedMs}ms)`,
|
|
79
|
+
});
|
|
80
|
+
log(verify.landed ? `✓ Telemetry landed in ${verify.elapsedMs}ms` : "Telemetry not confirmed yet — it can take a moment to appear");
|
|
81
|
+
}
|
|
82
|
+
const ok = steps.every((s) => s.ok);
|
|
83
|
+
return { ok, key: { masked: mask(apiKey), keyHash: keyHash(apiKey) }, settings, test, verify, steps };
|
|
84
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type FetchLike = typeof fetch;
|
|
2
|
+
export declare const PROBE_ATTR = "codetelemetry.probe.id";
|
|
3
|
+
export declare const PROBE_EVENT = "codetelemetry_probe";
|
|
4
|
+
export declare const PROBE_SERVICE = "codetelemetry-connect";
|
|
5
|
+
export declare function buildProbeLog(probeId: string, nowMs?: number): unknown;
|
|
6
|
+
export type SendTestResult = {
|
|
7
|
+
probeId: string;
|
|
8
|
+
httpStatus: number;
|
|
9
|
+
ok: boolean;
|
|
10
|
+
};
|
|
11
|
+
export type SendTestOptions = {
|
|
12
|
+
key: string;
|
|
13
|
+
ingestEndpoint: string;
|
|
14
|
+
probeId?: string;
|
|
15
|
+
fetchImpl?: FetchLike;
|
|
16
|
+
timeoutMs?: number;
|
|
17
|
+
};
|
|
18
|
+
export declare function sendTestEvent(opts: SendTestOptions): Promise<SendTestResult>;
|
package/dist/src/otlp.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { assertKey } from "./keys.js";
|
|
3
|
+
export const PROBE_ATTR = "codetelemetry.probe.id";
|
|
4
|
+
export const PROBE_EVENT = "codetelemetry_probe";
|
|
5
|
+
export const PROBE_SERVICE = "codetelemetry-connect";
|
|
6
|
+
export function buildProbeLog(probeId, nowMs = Date.now()) {
|
|
7
|
+
const nano = String(BigInt(Math.floor(nowMs)) * 1000000n);
|
|
8
|
+
return {
|
|
9
|
+
resourceLogs: [
|
|
10
|
+
{
|
|
11
|
+
resource: {
|
|
12
|
+
attributes: [
|
|
13
|
+
{ key: "service.name", value: { stringValue: PROBE_SERVICE } },
|
|
14
|
+
],
|
|
15
|
+
},
|
|
16
|
+
scopeLogs: [
|
|
17
|
+
{
|
|
18
|
+
scope: { name: PROBE_SERVICE },
|
|
19
|
+
logRecords: [
|
|
20
|
+
{
|
|
21
|
+
timeUnixNano: nano,
|
|
22
|
+
observedTimeUnixNano: nano,
|
|
23
|
+
severityNumber: 9,
|
|
24
|
+
severityText: "INFO",
|
|
25
|
+
body: { stringValue: "CodeTelemetry onboarding connectivity probe" },
|
|
26
|
+
attributes: [
|
|
27
|
+
{ key: "event.name", value: { stringValue: PROBE_EVENT } },
|
|
28
|
+
{ key: PROBE_ATTR, value: { stringValue: probeId } },
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export async function sendTestEvent(opts) {
|
|
39
|
+
const key = assertKey(opts.key);
|
|
40
|
+
const probeId = opts.probeId ?? randomUUID();
|
|
41
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
42
|
+
const url = `${opts.ingestEndpoint.replace(/\/$/, "")}/v1/logs`;
|
|
43
|
+
const ac = new AbortController();
|
|
44
|
+
const timer = setTimeout(() => ac.abort(), opts.timeoutMs ?? 10_000);
|
|
45
|
+
try {
|
|
46
|
+
const res = await fetchImpl(url, {
|
|
47
|
+
method: "POST",
|
|
48
|
+
headers: {
|
|
49
|
+
"content-type": "application/json",
|
|
50
|
+
authorization: `Bearer ${key}`,
|
|
51
|
+
},
|
|
52
|
+
body: JSON.stringify(buildProbeLog(probeId)),
|
|
53
|
+
signal: ac.signal,
|
|
54
|
+
});
|
|
55
|
+
return { probeId, httpStatus: res.status, ok: res.status >= 200 && res.status < 300 };
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
clearTimeout(timer);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const REPOSITORY_HOOK_MARKER = "codetelemetry-repository-observation";
|
|
2
|
+
export declare const REPOSITORY_HOOK_FILE = "codetelemetry-repository-hook.cjs";
|
|
3
|
+
export declare const REPOSITORY_HOOK_STATE_FILE = "codetelemetry-repository-hook-state.json";
|
|
4
|
+
export declare const REPOSITORY_HOOK_STATE_MAX_ENTRIES = 64;
|
|
5
|
+
export declare const CODEX_HOOKS_FILE = "hooks.json";
|
|
6
|
+
export declare function normalizeRepositoryRemote(value: string): string;
|
|
7
|
+
export declare function selectCodexOtelExporterLine(raw: string): string;
|
|
8
|
+
export declare function repositoryHookSource(options?: {
|
|
9
|
+
codexConfigPath?: string;
|
|
10
|
+
}): string;
|
|
11
|
+
export declare function quotedCommand(filePath: string, platform?: string): string;
|
|
12
|
+
export declare function validateCodexRepositoryObservationHook(configPath: string): void;
|
|
13
|
+
export declare function installCodexRepositoryObservationHook(configPath: string): void;
|
|
14
|
+
export declare function removeCodexRepositoryObservationHook(configPath: string): void;
|
|
15
|
+
export declare function installRepositoryObservationHook(settings: Record<string, unknown>, settingsPath: string): Record<string, unknown>;
|
|
16
|
+
export declare function removeRepositoryObservationHook(settings: Record<string, unknown>): Record<string, unknown>;
|
|
17
|
+
export declare function removeRepositoryObservationFiles(settingsPath: string): void;
|