@yagni-app/code 1.0.6 → 1.0.8
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 +91 -46
- package/dist/claudeCompat.d.ts +59 -0
- package/dist/claudeCompat.js +109 -2
- package/dist/claudePlugins.d.ts +45 -5
- package/dist/claudePlugins.js +129 -21
- package/dist/cli.js +16 -4
- package/dist/doctor.d.ts +21 -8
- package/dist/doctor.js +53 -28
- package/dist/extension/askUserQuestionTool.js +7 -2
- package/dist/extension/condensedTools.d.ts +12 -1
- package/dist/extension/condensedTools.js +17 -9
- package/dist/extension/config.d.ts +6 -0
- package/dist/extension/hooks.d.ts +3 -3
- package/dist/extension/hooks.js +30 -5
- package/dist/extension/index.d.ts +19 -0
- package/dist/extension/index.js +248 -43
- package/dist/extension/mcp/cliConfig.d.ts +1 -1
- package/dist/extension/mcp/cliConfig.js +1 -1
- package/dist/extension/mcp/config.d.ts +24 -2
- package/dist/extension/mcp/config.js +75 -3
- package/dist/extension/mcp/manager.d.ts +3 -1
- package/dist/extension/mcp/manager.js +2 -2
- package/dist/extension/mcp/panel.d.ts +0 -1
- package/dist/extension/mcp/panel.js +13 -3
- package/dist/extension/mcp/startup.js +8 -6
- package/dist/extension/permission/gate.d.ts +101 -2
- package/dist/extension/permission/gate.js +485 -42
- package/dist/extension/permissionRules/bashFileArgs.d.ts +39 -0
- package/dist/extension/permissionRules/bashFileArgs.js +236 -0
- package/dist/extension/permissionRules/engine.d.ts +50 -0
- package/dist/extension/permissionRules/engine.js +238 -0
- package/dist/extension/permissionRules/loadConfig.d.ts +64 -0
- package/dist/extension/permissionRules/loadConfig.js +105 -0
- package/dist/extension/permissionRules/parser.d.ts +38 -0
- package/dist/extension/permissionRules/parser.js +136 -0
- package/dist/extension/permissionRules/pathRules.d.ts +60 -0
- package/dist/extension/permissionRules/pathRules.js +122 -0
- package/dist/extension/permissionRules/shellRules.d.ts +52 -0
- package/dist/extension/permissionRules/shellRules.js +221 -0
- package/dist/extension/pipeline/invocation.d.ts +3 -6
- package/dist/extension/pipeline/invocation.js +3 -6
- package/dist/extension/pipeline/runner.d.ts +0 -1
- package/dist/extension/pipeline/runner.js +6 -14
- package/dist/extension/plugins/inventory.d.ts +88 -0
- package/dist/extension/plugins/inventory.js +144 -0
- package/dist/extension/plugins/panel.d.ts +45 -0
- package/dist/extension/plugins/panel.js +293 -0
- package/dist/extension/sandbox/bash.d.ts +99 -0
- package/dist/extension/sandbox/bash.js +190 -0
- package/dist/extension/sandbox/config.d.ts +115 -0
- package/dist/extension/sandbox/config.js +388 -0
- package/dist/extension/sandbox/manager.d.ts +108 -0
- package/dist/extension/sandbox/manager.js +243 -0
- package/dist/extension/sandbox/panel.d.ts +111 -0
- package/dist/extension/sandbox/panel.js +342 -0
- package/dist/extension/sandbox/session.d.ts +85 -0
- package/dist/extension/sandbox/session.js +829 -0
- package/dist/extension/settingsFiles.d.ts +50 -0
- package/dist/extension/settingsFiles.js +206 -0
- package/dist/extension/telemetry/attrs.d.ts +96 -0
- package/dist/extension/telemetry/attrs.js +149 -0
- package/dist/extension/telemetry/config.d.ts +103 -0
- package/dist/extension/telemetry/config.js +193 -0
- package/dist/extension/telemetry/index.d.ts +7 -0
- package/dist/extension/telemetry/index.js +7 -0
- package/dist/extension/telemetry/probe.d.ts +29 -0
- package/dist/extension/telemetry/probe.js +122 -0
- package/dist/extension/telemetry/register.d.ts +47 -0
- package/dist/extension/telemetry/register.js +207 -0
- package/dist/extension/telemetry/sdk.d.ts +63 -0
- package/dist/extension/telemetry/sdk.js +207 -0
- package/dist/extension/telemetry/tracker.d.ts +131 -0
- package/dist/extension/telemetry/tracker.js +551 -0
- package/dist/extension/vendor/IGNORE-LICENSE-MIT +21 -0
- package/dist/extension/vendor/ignore.d.ts +86 -0
- package/dist/extension/vendor/ignore.js +788 -0
- package/dist/goHeadless.d.ts +1 -1
- package/dist/goHeadless.js +2 -2
- package/dist/launch.d.ts +4 -3
- package/dist/launch.js +7 -4
- package/dist/mcpCommand.d.ts +10 -1
- package/dist/mcpCommand.js +42 -10
- package/dist/otel.d.ts +67 -90
- package/dist/otel.js +152 -195
- package/dist/paths.d.ts +13 -0
- package/dist/paths.js +18 -0
- package/dist/pluginCommand.d.ts +43 -0
- package/dist/pluginCommand.js +499 -0
- package/dist/pluginStore.d.ts +170 -0
- package/dist/pluginStore.js +554 -0
- package/dist/upgrade.js +10 -1
- package/package.json +19 -3
package/dist/otel.js
CHANGED
|
@@ -1,54 +1,72 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OTel export
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* OTel export gate (Updater pilot ask, now Claude Code parity): decide
|
|
3
|
+
* whether a launch streams session telemetry — traces, metrics, and log
|
|
4
|
+
* events — to the workspace's own OTLP collector (Datadog, Grafana, an
|
|
5
|
+
* in-house collector), and hand the session the standard OpenTelemetry env
|
|
6
|
+
* it needs. The exporter itself lives in the bundled extension
|
|
7
|
+
* (`pi-extension-yagni/src/telemetry`), so there is nothing extra to load and
|
|
8
|
+
* nothing that can go missing on a customer's `npm install`.
|
|
6
9
|
*
|
|
7
|
-
* Three deliberate policies,
|
|
10
|
+
* Three deliberate policies, enforced here rather than left to defaults:
|
|
8
11
|
*
|
|
9
|
-
* 1. GATED, not always-on.
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* 1. GATED, not always-on. Nothing exports unless an OTLP destination is
|
|
13
|
+
* configured: the user's own OTEL_* env, the workspace's admin-set config
|
|
14
|
+
* (fetched fail-soft and cached), or the repo's committed
|
|
15
|
+
* `.pi/settings.json`. The gate result travels to the session as
|
|
16
|
+
* `YAGNI_OTEL_EXPORT=1` plus the OTLP variables; children (/go stages,
|
|
17
|
+
* subagents) inherit the env and export too.
|
|
15
18
|
*
|
|
16
|
-
* 2. METADATA-ONLY,
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* precedence — a repo settings file asking for "full" is overridden, not
|
|
20
|
-
* honored. Session content leaving the machine is a contract change, not a
|
|
21
|
-
* config knob.
|
|
19
|
+
* 2. METADATA-ONLY, in code. Token counts, cost, tier, durations, tool names,
|
|
20
|
+
* and sizes export; prompt, response, and tool content never do. The
|
|
21
|
+
* extension enforces this without a knob — no env var re-enables content.
|
|
22
22
|
*
|
|
23
|
-
* 3. Cost is the customer's SELL rate.
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* exported model name is the opaque tier id, never the backing model.
|
|
23
|
+
* 3. Cost is the customer's SELL rate. Usage cost is pi's `usage.cost.total`,
|
|
24
|
+
* computed from the catalog's tier rates (YAG-381), and the exported model
|
|
25
|
+
* name is the opaque tier id, never the backing model.
|
|
27
26
|
*
|
|
28
|
-
* Everything here is fail-soft: telemetry must never block a launch, so
|
|
29
|
-
*
|
|
27
|
+
* Everything here is fail-soft: telemetry must never block a launch, so an
|
|
28
|
+
* unreadable settings file or an offline backend resolves to "disabled" or
|
|
29
|
+
* to last-known config.
|
|
30
30
|
*/
|
|
31
|
-
import {
|
|
32
|
-
import { createConnection } from "node:net";
|
|
31
|
+
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
33
32
|
import { join } from "node:path";
|
|
34
|
-
import { fileURLToPath } from "node:url";
|
|
35
33
|
import { credentialsDir } from "./credentials.js";
|
|
36
|
-
/**
|
|
37
|
-
|
|
38
|
-
* subagents: they build their own pi argv inside pi-extension-yagni (which
|
|
39
|
-
* cannot import this package), so the path crosses over env. The extension
|
|
40
|
-
* reads the same literal in `pipeline/runner.ts`.
|
|
41
|
-
*/
|
|
42
|
-
export const OTEL_EXTENSION_PATH_ENV = "YAGNI_OTEL_EXTENSION_PATH";
|
|
34
|
+
/** The launcher's "this session exports" gate, read by the bundled extension. */
|
|
35
|
+
export const OTEL_EXPORT_ENV = "YAGNI_OTEL_EXPORT";
|
|
43
36
|
/** The service name a collector sees unless the user set their own. */
|
|
44
37
|
export const DEFAULT_OTEL_SERVICE_NAME = "yagni-code";
|
|
45
38
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
39
|
+
* Headers that belong on the TRACES request only. Datadog's LLM Observability
|
|
40
|
+
* routes OTLP traces to its LLM product when `dd-otlp-source=llmobs` rides
|
|
41
|
+
* along; the same header on the metrics and logs intakes is meaningless at
|
|
42
|
+
* best, so the launcher scopes it to `OTEL_EXPORTER_OTLP_TRACES_HEADERS`.
|
|
43
|
+
*/
|
|
44
|
+
const TRACE_ONLY_HEADERS = new Set(["dd-otlp-source"]);
|
|
45
|
+
/** The standard per-signal endpoint variables, generic first (its precedence). */
|
|
46
|
+
const ENDPOINT_ENV_VARS = [
|
|
47
|
+
"OTEL_EXPORTER_OTLP_ENDPOINT",
|
|
48
|
+
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT",
|
|
49
|
+
"OTEL_EXPORTER_OTLP_METRICS_ENDPOINT",
|
|
50
|
+
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT",
|
|
51
|
+
];
|
|
52
|
+
/** Trailing `/v1/<signal>` on a URL — the shape pre-parity configs stored. */
|
|
53
|
+
const SIGNAL_PATH_RE = /\/v1\/(traces|metrics|logs)\/?$/i;
|
|
54
|
+
/**
|
|
55
|
+
* Reduce an endpoint that carries a signal path to its base
|
|
56
|
+
* (`https://otlp.datadoghq.com/v1/traces` → `https://otlp.datadoghq.com`).
|
|
57
|
+
* The extension appends `/v1/<signal>` per signal for http transports, so a
|
|
58
|
+
* workspace config saved before logs and metrics existed keeps exporting all
|
|
59
|
+
* three without an admin re-save. Mirrors the extension's own helper.
|
|
60
|
+
*/
|
|
61
|
+
export function stripSignalPath(endpoint) {
|
|
62
|
+
return endpoint.replace(SIGNAL_PATH_RE, "").replace(/\/+$/, "");
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Read the `otel` block from the repo's `.pi/settings.json`, the project-level
|
|
66
|
+
* file a team commits once so every clone exports. Returns undefined on any
|
|
67
|
+
* problem — a malformed settings file must not block a launch.
|
|
50
68
|
*/
|
|
51
|
-
function
|
|
69
|
+
function projectOtelSettings(cwd, readFile) {
|
|
52
70
|
try {
|
|
53
71
|
const raw = readFile(join(cwd, ".pi", "settings.json"), "utf8");
|
|
54
72
|
const parsed = JSON.parse(String(raw));
|
|
@@ -57,167 +75,126 @@ function projectOtelEndpoint(cwd, readFile) {
|
|
|
57
75
|
const otel = parsed.otel;
|
|
58
76
|
if (typeof otel !== "object" || otel === null)
|
|
59
77
|
return undefined;
|
|
60
|
-
const
|
|
61
|
-
|
|
78
|
+
const o = otel;
|
|
79
|
+
if (o.enabled === false)
|
|
80
|
+
return undefined;
|
|
81
|
+
const endpoint = typeof o.endpoint === "string" && o.endpoint.trim() !== "" ? o.endpoint.trim() : undefined;
|
|
82
|
+
if (!endpoint)
|
|
83
|
+
return undefined;
|
|
84
|
+
const headers = {};
|
|
85
|
+
if (typeof o.headers === "object" && o.headers !== null) {
|
|
86
|
+
for (const [k, v] of Object.entries(o.headers)) {
|
|
87
|
+
// Headers travel to the session as the comma-separated
|
|
88
|
+
// OTEL_EXPORTER_OTLP_HEADERS list; a comma in a value would corrupt
|
|
89
|
+
// every pair after it, so such a header is dropped (same rule the
|
|
90
|
+
// workspace settings enforce at save time).
|
|
91
|
+
if (typeof v === "string" && !v.includes(",") && !k.includes(","))
|
|
92
|
+
headers[k] = v;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
endpoint,
|
|
97
|
+
...(typeof o.protocol === "string" && o.protocol.trim() ? { protocol: o.protocol.trim() } : {}),
|
|
98
|
+
...(Object.keys(headers).length > 0 ? { headers } : {}),
|
|
99
|
+
...(typeof o.serviceName === "string" && o.serviceName.trim() ? { serviceName: o.serviceName.trim() } : {}),
|
|
100
|
+
};
|
|
62
101
|
}
|
|
63
102
|
catch {
|
|
64
103
|
return undefined;
|
|
65
104
|
}
|
|
66
105
|
}
|
|
67
|
-
/**
|
|
68
|
-
function envDisabled(env) {
|
|
69
|
-
|
|
106
|
+
/** Personal kill switch: beats every source. Both spellings accepted. */
|
|
107
|
+
export function envDisabled(env) {
|
|
108
|
+
const truthy = (v) => v === "1" || v === "true";
|
|
109
|
+
return truthy(env.PI_OTEL_DISABLED) || truthy(env.YAGNI_OTEL_DISABLED);
|
|
110
|
+
}
|
|
111
|
+
/** The first configured endpoint variable in the user's env, generic first. */
|
|
112
|
+
function envEndpoint(env) {
|
|
113
|
+
for (const envVar of ENDPOINT_ENV_VARS) {
|
|
114
|
+
const value = env[envVar]?.trim();
|
|
115
|
+
if (value)
|
|
116
|
+
return { envVar, endpoint: value };
|
|
117
|
+
}
|
|
118
|
+
return undefined;
|
|
70
119
|
}
|
|
71
120
|
/**
|
|
72
|
-
* Decide whether this launch exports
|
|
73
|
-
* Returns undefined when export stays off: no endpoint configured
|
|
74
|
-
*
|
|
121
|
+
* Decide whether this launch exports, from the user's env and the repo's
|
|
122
|
+
* settings. Returns undefined when export stays off: no endpoint configured,
|
|
123
|
+
* or a kill switch set.
|
|
75
124
|
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* over the same way. Logs/metrics per-signal vars are deliberately NOT read:
|
|
82
|
-
* sessions export traces only, and pi-otel's metrics/logs signals are opt-in
|
|
83
|
-
* elsewhere.
|
|
125
|
+
* Any of the standard endpoint variables enables export — the generic
|
|
126
|
+
* `OTEL_EXPORTER_OTLP_ENDPOINT` or a per-signal one — so a machine that
|
|
127
|
+
* carries a Claude Code telemetry env (typically all three per-signal
|
|
128
|
+
* endpoints) lights up with no translation. The extension reads the same
|
|
129
|
+
* variables, per signal, following the OTLP exporter spec.
|
|
84
130
|
*/
|
|
85
131
|
export function resolveOtelLaunch(opts) {
|
|
86
132
|
const { env, cwd } = opts;
|
|
87
133
|
if (envDisabled(env))
|
|
88
134
|
return undefined;
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
if (!
|
|
135
|
+
const fromEnv = envEndpoint(env);
|
|
136
|
+
if (fromEnv)
|
|
137
|
+
return { source: "env", envVar: fromEnv.envVar, endpoint: fromEnv.endpoint };
|
|
138
|
+
const settings = projectOtelSettings(cwd, opts.readFile ?? readFileSync);
|
|
139
|
+
if (!settings)
|
|
94
140
|
return undefined;
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
if (envEndpoint) {
|
|
103
|
-
return {
|
|
104
|
-
extensionPath,
|
|
105
|
-
source: "env",
|
|
106
|
-
envVar: "OTEL_EXPORTER_OTLP_ENDPOINT",
|
|
107
|
-
endpoint: envEndpoint,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
if (usingPerSignal) {
|
|
111
|
-
return {
|
|
112
|
-
extensionPath,
|
|
113
|
-
source: "env",
|
|
114
|
-
envVar: "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT",
|
|
115
|
-
endpoint: tracesEndpoint,
|
|
116
|
-
...(env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL?.trim()
|
|
117
|
-
? { protocol: env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL.trim() }
|
|
118
|
-
: {}),
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
return { extensionPath, source: "project-settings", endpoint: settingsEndpoint };
|
|
141
|
+
return {
|
|
142
|
+
source: "project-settings",
|
|
143
|
+
endpoint: settings.endpoint,
|
|
144
|
+
...(settings.protocol ? { protocol: settings.protocol } : {}),
|
|
145
|
+
...(settings.headers ? { headers: settings.headers } : {}),
|
|
146
|
+
...(settings.serviceName ? { serviceName: settings.serviceName } : {}),
|
|
147
|
+
};
|
|
122
148
|
}
|
|
123
|
-
/**
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
export function resolveOtelExtensionPath() {
|
|
129
|
-
const path = fileURLToPath(import.meta.resolve("pi-otel"));
|
|
130
|
-
if (!existsSync(path)) {
|
|
131
|
-
throw new Error(`pi-otel entry not found at ${path}`);
|
|
132
|
-
}
|
|
133
|
-
return path;
|
|
149
|
+
/** Render a header record as the comma-separated `k=v` list the OTel env carries. */
|
|
150
|
+
function headerList(headers) {
|
|
151
|
+
return Object.entries(headers)
|
|
152
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
153
|
+
.join(",");
|
|
134
154
|
}
|
|
135
155
|
/**
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
* connect failure — reachability is advisory, never fatal. Mirrors the probe
|
|
140
|
-
* inside (patched) pi-otel but lives here so `doctor` can use it without
|
|
141
|
-
* importing the extension's SDK graph.
|
|
142
|
-
*/
|
|
143
|
-
export async function probeOtelEndpoint(endpoint, timeoutMs = 1_000) {
|
|
144
|
-
let host;
|
|
145
|
-
let port;
|
|
146
|
-
try {
|
|
147
|
-
const u = new URL(endpoint);
|
|
148
|
-
host = u.hostname;
|
|
149
|
-
port = u.port
|
|
150
|
-
? Number(u.port)
|
|
151
|
-
: u.protocol === "https:"
|
|
152
|
-
? 443
|
|
153
|
-
: u.protocol === "http:"
|
|
154
|
-
? 80
|
|
155
|
-
: null;
|
|
156
|
-
}
|
|
157
|
-
catch {
|
|
158
|
-
return false;
|
|
159
|
-
}
|
|
160
|
-
if (!host || !port)
|
|
161
|
-
return false;
|
|
162
|
-
return new Promise((resolve) => {
|
|
163
|
-
const sock = createConnection({ host, port });
|
|
164
|
-
const done = (ok) => {
|
|
165
|
-
sock.destroy();
|
|
166
|
-
resolve(ok);
|
|
167
|
-
};
|
|
168
|
-
sock.setTimeout(timeoutMs);
|
|
169
|
-
sock.once("connect", () => done(true));
|
|
170
|
-
sock.once("timeout", () => done(false));
|
|
171
|
-
sock.once("error", () => done(false));
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* The env keys an OTel-exporting child must carry. `PI_OTEL_CAPTURE_CONTENT`
|
|
176
|
-
* is pinned unconditionally (policy 2 above); the service name only fills in
|
|
177
|
-
* when the user has not chosen their own.
|
|
156
|
+
* The env keys an exporting child must carry. `YAGNI_OTEL_EXPORT=1` is the
|
|
157
|
+
* gate the extension reads; the service name only fills in when the user has
|
|
158
|
+
* not chosen their own.
|
|
178
159
|
*
|
|
179
|
-
* A "workspace"-
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
160
|
+
* A "workspace" or "project-settings" config delivers its endpoint (as the
|
|
161
|
+
* generic BASE endpoint — the extension derives the per-signal URLs),
|
|
162
|
+
* protocol, headers, and service name over the standard OTel env vars — but
|
|
163
|
+
* any of those the USER already set in their own environment wins (local
|
|
164
|
+
* env > workspace config), so an engineer can point one session at a
|
|
165
|
+
* scratch collector without an admin change. An "env" config is the user's
|
|
166
|
+
* own setup and passes through untouched.
|
|
184
167
|
*/
|
|
185
168
|
export function otelChildEnv(config, baseEnv) {
|
|
186
|
-
const
|
|
187
|
-
if (config.source === "workspace") {
|
|
188
|
-
|
|
189
|
-
|
|
169
|
+
const out = {};
|
|
170
|
+
if (config.source === "workspace" || config.source === "project-settings") {
|
|
171
|
+
const userHasEndpoint = ENDPOINT_ENV_VARS.some((v) => baseEnv[v]?.trim());
|
|
172
|
+
if (!userHasEndpoint) {
|
|
173
|
+
out.OTEL_EXPORTER_OTLP_ENDPOINT = stripSignalPath(config.endpoint);
|
|
190
174
|
}
|
|
191
175
|
if (config.protocol && !baseEnv.OTEL_EXPORTER_OTLP_PROTOCOL) {
|
|
192
|
-
|
|
176
|
+
out.OTEL_EXPORTER_OTLP_PROTOCOL = config.protocol;
|
|
193
177
|
}
|
|
194
178
|
if (config.headers && Object.keys(config.headers).length > 0 && !baseEnv.OTEL_EXPORTER_OTLP_HEADERS) {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
179
|
+
const shared = {};
|
|
180
|
+
const traceOnly = {};
|
|
181
|
+
for (const [k, v] of Object.entries(config.headers)) {
|
|
182
|
+
(TRACE_ONLY_HEADERS.has(k.toLowerCase()) ? traceOnly : shared)[k] = v;
|
|
183
|
+
}
|
|
184
|
+
if (Object.keys(shared).length > 0)
|
|
185
|
+
out.OTEL_EXPORTER_OTLP_HEADERS = headerList(shared);
|
|
186
|
+
if (Object.keys(traceOnly).length > 0 && !baseEnv.OTEL_EXPORTER_OTLP_TRACES_HEADERS) {
|
|
187
|
+
out.OTEL_EXPORTER_OTLP_TRACES_HEADERS = headerList(traceOnly);
|
|
188
|
+
}
|
|
198
189
|
}
|
|
199
190
|
if (config.serviceName && !baseEnv.OTEL_SERVICE_NAME) {
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
else if (config.source === "env" && config.protocol) {
|
|
204
|
-
// The per-signal bridge: the endpoint came from
|
|
205
|
-
// OTEL_EXPORTER_OTLP_TRACES_ENDPOINT (+ TRACES_PROTOCOL), which pi-otel
|
|
206
|
-
// does not read — deliver them as the generic vars the extension resolves.
|
|
207
|
-
if (!baseEnv.OTEL_EXPORTER_OTLP_ENDPOINT) {
|
|
208
|
-
workspace.OTEL_EXPORTER_OTLP_ENDPOINT = config.endpoint;
|
|
209
|
-
}
|
|
210
|
-
if (!baseEnv.OTEL_EXPORTER_OTLP_PROTOCOL) {
|
|
211
|
-
workspace.OTEL_EXPORTER_OTLP_PROTOCOL = config.protocol;
|
|
191
|
+
out.OTEL_SERVICE_NAME = config.serviceName;
|
|
212
192
|
}
|
|
213
193
|
}
|
|
214
194
|
return {
|
|
215
|
-
...
|
|
216
|
-
[
|
|
217
|
-
|
|
218
|
-
...(baseEnv.OTEL_SERVICE_NAME || workspace.OTEL_SERVICE_NAME
|
|
219
|
-
? {}
|
|
220
|
-
: { OTEL_SERVICE_NAME: DEFAULT_OTEL_SERVICE_NAME }),
|
|
195
|
+
...out,
|
|
196
|
+
[OTEL_EXPORT_ENV]: "1",
|
|
197
|
+
...(baseEnv.OTEL_SERVICE_NAME || out.OTEL_SERVICE_NAME ? {} : { OTEL_SERVICE_NAME: DEFAULT_OTEL_SERVICE_NAME }),
|
|
221
198
|
};
|
|
222
199
|
}
|
|
223
200
|
// ── Workspace-configured export (admin sets it once in the web app) ─────────
|
|
@@ -313,10 +290,8 @@ function readWorkspaceOtelCache(cachePath) {
|
|
|
313
290
|
/**
|
|
314
291
|
* Full launch-time resolution, all four sources in precedence order:
|
|
315
292
|
*
|
|
316
|
-
* 1. `PI_OTEL_DISABLED`
|
|
317
|
-
* 2. env `
|
|
318
|
-
* — the user's own setup, untouched (generic wins
|
|
319
|
-
* over per-signal).
|
|
293
|
+
* 1. `PI_OTEL_DISABLED` / `YAGNI_OTEL_DISABLED` — personal kill switch.
|
|
294
|
+
* 2. env `OTEL_EXPORTER_OTLP_*_ENDPOINT` — the user's own setup, untouched.
|
|
320
295
|
* 3. workspace config — admin-set in the web app, fetched/cached.
|
|
321
296
|
* 4. repo `.pi/settings.json` — committed per-repo config.
|
|
322
297
|
*
|
|
@@ -327,27 +302,14 @@ export async function resolveOtelLaunchWithWorkspace(opts) {
|
|
|
327
302
|
if (envDisabled(env))
|
|
328
303
|
return undefined;
|
|
329
304
|
// Source 2: the user's own env config short-circuits — no fetch needed.
|
|
330
|
-
if (
|
|
331
|
-
return resolveOtelLaunch({
|
|
332
|
-
env,
|
|
333
|
-
cwd,
|
|
334
|
-
...(opts.resolveExtension ? { resolveExtension: opts.resolveExtension } : {}),
|
|
335
|
-
...(opts.readFile ? { readFile: opts.readFile } : {}),
|
|
336
|
-
});
|
|
305
|
+
if (envEndpoint(env)) {
|
|
306
|
+
return resolveOtelLaunch({ env, cwd, ...(opts.readFile ? { readFile: opts.readFile } : {}) });
|
|
337
307
|
}
|
|
338
308
|
// Source 3: workspace config.
|
|
339
309
|
if (opts.creds?.token && opts.profileName) {
|
|
340
310
|
const workspace = await (opts.fetchWorkspace ?? fetchWorkspaceOtel)({ baseUrl: opts.creds.baseUrl, token: opts.creds.token }, opts.profileName, opts.fetchDeps ?? {});
|
|
341
311
|
if (workspace) {
|
|
342
|
-
let extensionPath;
|
|
343
|
-
try {
|
|
344
|
-
extensionPath = (opts.resolveExtension ?? resolveOtelExtensionPath)();
|
|
345
|
-
}
|
|
346
|
-
catch {
|
|
347
|
-
return undefined;
|
|
348
|
-
}
|
|
349
312
|
return {
|
|
350
|
-
extensionPath,
|
|
351
313
|
source: "workspace",
|
|
352
314
|
endpoint: workspace.endpoint,
|
|
353
315
|
...(workspace.protocol ? { protocol: workspace.protocol } : {}),
|
|
@@ -359,11 +321,6 @@ export async function resolveOtelLaunchWithWorkspace(opts) {
|
|
|
359
321
|
}
|
|
360
322
|
}
|
|
361
323
|
// Source 4: repo-committed settings.
|
|
362
|
-
return resolveOtelLaunch({
|
|
363
|
-
env,
|
|
364
|
-
cwd,
|
|
365
|
-
...(opts.resolveExtension ? { resolveExtension: opts.resolveExtension } : {}),
|
|
366
|
-
...(opts.readFile ? { readFile: opts.readFile } : {}),
|
|
367
|
-
});
|
|
324
|
+
return resolveOtelLaunch({ env, cwd, ...(opts.readFile ? { readFile: opts.readFile } : {}) });
|
|
368
325
|
}
|
|
369
326
|
//# sourceMappingURL=otel.js.map
|
package/dist/paths.d.ts
CHANGED
|
@@ -19,6 +19,12 @@
|
|
|
19
19
|
* fall back to resolving the workspace-linked package, which keeps `pnpm test`
|
|
20
20
|
* and a fresh checkout working before anything is bundled.
|
|
21
21
|
*/
|
|
22
|
+
/**
|
|
23
|
+
* Absolute path to this CLI's entry (`dist/cli.js`). Forwarded to sessions as
|
|
24
|
+
* `YAGNI_CLI_PATH` so the extension's `/plugin` panel can run
|
|
25
|
+
* `yagni plugin …` with the exact binary that launched it.
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveCliPath(): string;
|
|
22
28
|
export declare function resolveExtensionPath(): string;
|
|
23
29
|
/**
|
|
24
30
|
* Absolute path to the extension's headless pipeline entry
|
|
@@ -29,6 +35,13 @@ export declare function resolveExtensionPath(): string;
|
|
|
29
35
|
* it pulls in the pipeline alone, with none of pi's TUI surface.
|
|
30
36
|
*/
|
|
31
37
|
export declare function resolveHeadlessGoPath(): string;
|
|
38
|
+
/**
|
|
39
|
+
* Absolute path to the extension's telemetry probe (`telemetry/probe.js`),
|
|
40
|
+
* the module `yagni doctor` imports to send one real record per signal.
|
|
41
|
+
* Same derivation as `resolveHeadlessGoPath`; only this module (and the OTel
|
|
42
|
+
* SDK it pulls in) loads, none of pi's TUI surface.
|
|
43
|
+
*/
|
|
44
|
+
export declare function resolveTelemetryProbePath(): string;
|
|
32
45
|
/**
|
|
33
46
|
* Absolute path to the extension's session-worktree entry
|
|
34
47
|
* (`pipeline/sessionWorktree.js`), the module `yagni -w` imports.
|
package/dist/paths.js
CHANGED
|
@@ -25,6 +25,14 @@ function resolveModulePath(specifier) {
|
|
|
25
25
|
* fall back to resolving the workspace-linked package, which keeps `pnpm test`
|
|
26
26
|
* and a fresh checkout working before anything is bundled.
|
|
27
27
|
*/
|
|
28
|
+
/**
|
|
29
|
+
* Absolute path to this CLI's entry (`dist/cli.js`). Forwarded to sessions as
|
|
30
|
+
* `YAGNI_CLI_PATH` so the extension's `/plugin` panel can run
|
|
31
|
+
* `yagni plugin …` with the exact binary that launched it.
|
|
32
|
+
*/
|
|
33
|
+
export function resolveCliPath() {
|
|
34
|
+
return fileURLToPath(new URL("./cli.js", import.meta.url));
|
|
35
|
+
}
|
|
28
36
|
export function resolveExtensionPath() {
|
|
29
37
|
const bundled = fileURLToPath(new URL("./extension/index.js", import.meta.url));
|
|
30
38
|
if (existsSync(bundled))
|
|
@@ -43,6 +51,16 @@ export function resolveHeadlessGoPath() {
|
|
|
43
51
|
const entry = resolveExtensionPath();
|
|
44
52
|
return join(dirname(entry), "pipeline", "headlessGo.js");
|
|
45
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Absolute path to the extension's telemetry probe (`telemetry/probe.js`),
|
|
56
|
+
* the module `yagni doctor` imports to send one real record per signal.
|
|
57
|
+
* Same derivation as `resolveHeadlessGoPath`; only this module (and the OTel
|
|
58
|
+
* SDK it pulls in) loads, none of pi's TUI surface.
|
|
59
|
+
*/
|
|
60
|
+
export function resolveTelemetryProbePath() {
|
|
61
|
+
const entry = resolveExtensionPath();
|
|
62
|
+
return join(dirname(entry), "telemetry", "probe.js");
|
|
63
|
+
}
|
|
46
64
|
/**
|
|
47
65
|
* Absolute path to the extension's session-worktree entry
|
|
48
66
|
* (`pipeline/sessionWorktree.js`), the module `yagni -w` imports.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `yagni plugin …` — install, remove, and update Claude Code plugins from
|
|
3
|
+
* marketplaces without leaving YAGNI Code, mirroring Claude Code's own
|
|
4
|
+
* `claude plugin` surface (marketplace add/list/update/remove, install,
|
|
5
|
+
* uninstall, enable, disable, update, list).
|
|
6
|
+
*
|
|
7
|
+
* State lives in YAGNI's own ledger (pluginStore.ts). Plugins Claude Code
|
|
8
|
+
* installed are still mirrored read-only (claudePlugins.ts) and show up in
|
|
9
|
+
* `list` labelled as such, so a developer sees one honest inventory: what
|
|
10
|
+
* loads, where it came from, and which components (hooks) are not bridged.
|
|
11
|
+
*/
|
|
12
|
+
import { type PluginAssets } from "./claudePlugins.js";
|
|
13
|
+
import { type GitRunner, type PluginInstallScope } from "./pluginStore.js";
|
|
14
|
+
export interface PluginCommandDeps {
|
|
15
|
+
cwd?: string;
|
|
16
|
+
/** The user's home dir (`~`); the ledger lives under `$YAGNI_CODE_HOME` or `~/.yagni-code`. */
|
|
17
|
+
home?: string;
|
|
18
|
+
env?: NodeJS.ProcessEnv;
|
|
19
|
+
git?: GitRunner;
|
|
20
|
+
stdout?: (text: string) => void;
|
|
21
|
+
stderr?: (text: string) => void;
|
|
22
|
+
/** pi agent dir holding trust.json (defaults to the active profile's). */
|
|
23
|
+
agentDir?: () => Promise<string>;
|
|
24
|
+
now?: () => Date;
|
|
25
|
+
}
|
|
26
|
+
export interface ParsedPluginArgs {
|
|
27
|
+
subcommand: string | undefined;
|
|
28
|
+
positionals: string[];
|
|
29
|
+
scope: PluginInstallScope;
|
|
30
|
+
scopeExplicit: boolean;
|
|
31
|
+
available: boolean;
|
|
32
|
+
json: boolean;
|
|
33
|
+
}
|
|
34
|
+
export declare function parsePluginArgs(args: string[]): ParsedPluginArgs;
|
|
35
|
+
/** The repo root a project-scope install applies to: nearest `.git` ancestor, else cwd. */
|
|
36
|
+
export declare function resolveRepoRoot(cwd: string): string;
|
|
37
|
+
export declare function pluginCommand(args: string[], deps?: PluginCommandDeps): Promise<number>;
|
|
38
|
+
export declare function componentSummary(p: PluginAssets): string;
|
|
39
|
+
/** Whether a stdio `command` resolves on PATH (or is an existing path). Unknown when it has an unexpanded ${VAR}. */
|
|
40
|
+
export declare function commandOnPath(command: string, env: NodeJS.ProcessEnv): boolean | null;
|
|
41
|
+
/** Per-server notes for a plugin's MCP block: names, and a missing-binary warning for stdio servers. */
|
|
42
|
+
export declare function mcpNotes(p: PluginAssets, env: NodeJS.ProcessEnv): string[];
|
|
43
|
+
//# sourceMappingURL=pluginCommand.d.ts.map
|