siesa-agents 2.1.79 → 2.1.80
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.
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sa-init-observability
|
|
3
|
+
description: 'Initialize a `.siesa-project` file that consolidates observability metrics across multi-repo logical projects (docs/backend/frontend sub-repos cloned side by side). Use whenever the user wants to set up observability for a new project, mentions `.siesa-project`, asks how to consolidate fragmented `project_id` values in GCP, sees metrics split across `business-*-docs-*`/`business-*-backend-*`/`business-*-frontend-*` repos, or starts working in a project that follows the multi-repo convention. Run this once per logical project, before workflows emit telemetry, so `sa-emit.js` reports a unified `project_id` instead of one per sub-repo.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Init Observability — `.siesa-project` Setup
|
|
7
|
+
|
|
8
|
+
**Goal:** Create a `.siesa-project` file in the correct parent directory so `sa-emit.js` reports a single, stable `project_id` for every workflow that runs inside any sub-repo of the same logical project.
|
|
9
|
+
|
|
10
|
+
**When this matters:** Projects following the Siesa multi-repo convention split docs, backend and frontend into separate GitHub repos (`business-pos-docs-pdv`, `business-pos-backend-pdv`, `business-pos-frontend-pdv`). Without `.siesa-project`, `sa-emit.js` falls back to `git remote get-url origin` and emits three different `project_id` values for what is conceptually one project. That fragments dashboards in GCP and inflates per-engineer epic counts because the same epic gets counted once per sub-repo it touches.
|
|
11
|
+
|
|
12
|
+
## When to use this skill
|
|
13
|
+
|
|
14
|
+
Trigger immediately when the user:
|
|
15
|
+
|
|
16
|
+
- Says "initialize observability", "set up observability", "init `.siesa-project`", or "configure metrics" for a project
|
|
17
|
+
- Mentions that GCP shows the same engineer/epic across multiple `business-*-docs-*` / `business-*-backend-*` / `business-*-frontend-*` rows
|
|
18
|
+
- Asks how to make several sub-repos report under one logical project name
|
|
19
|
+
- Has just cloned a multi-repo project (e.g. `docs/` + `apps/backend/` + `apps/frontend/`) and is about to run BMAD workflows that emit telemetry
|
|
20
|
+
- References the `.siesa-project` file by name
|
|
21
|
+
|
|
22
|
+
Do **not** trigger for: editing existing skills, changing OTLP endpoints, debugging a specific event payload (that is `sa-emit.js` territory), or anything unrelated to the `.siesa-project` mechanism.
|
|
23
|
+
|
|
24
|
+
## Prerequisites
|
|
25
|
+
|
|
26
|
+
- The user is sitting inside a git repository that belongs to a logical Siesa project (typically a clone of `business-*-{docs|backend|frontend}-*`).
|
|
27
|
+
- Node.js is on the PATH (the scripts are invoked via `node`).
|
|
28
|
+
- Two helper scripts live in the observability scripts dir and are mirrored to the npm copy:
|
|
29
|
+
- `sa-init-env.js` — ensures `~/.claude/observability/.env` has the OTLP variables sa-emit needs.
|
|
30
|
+
- `sa-init-project.js` — detects the multi-repo convention and writes `.siesa-project`.
|
|
31
|
+
- Source-of-truth path is `_siesa-agents/observability/scripts/`; the npm mirror is `siesa-agents/observability/scripts/`. Prefer the source-of-truth path when both exist.
|
|
32
|
+
|
|
33
|
+
If a script is missing, stop and tell the user — do not improvise the logic inline. The scripts are the authoritative source for slug derivation and env scaffolding.
|
|
34
|
+
|
|
35
|
+
## Workflow
|
|
36
|
+
|
|
37
|
+
The skill is a three-step orchestration: **ensure global env → detect → write**. Step 0 is idempotent and almost always silent. Steps 1 and 2 do the real work for the `.siesa-project` file.
|
|
38
|
+
|
|
39
|
+
### Step 0 — Ensure global env
|
|
40
|
+
|
|
41
|
+
Run `sa-init-env.js` first, every time the skill is invoked:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
node <observability-scripts>/sa-init-env.js
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The script verifies that `~/.claude/observability/.env` exists and declares both `SA_OTLP_ENDPOINT` and `SA_OTLP_HEADERS`. It does **not** modify `sa-emit.js` and does **not** copy values from any project `.env` — it only seeds clearly-fake placeholders that the user must replace before workflows emit real telemetry.
|
|
48
|
+
|
|
49
|
+
Parse the JSON `status` field:
|
|
50
|
+
|
|
51
|
+
- **`status: "already-configured"`** — Both variables are already present. Do not mention this to the user unless they explicitly asked about env setup; it is the silent happy path. Continue to Step 1.
|
|
52
|
+
- **`status: "created"`** — The file did not exist; the script wrote a fresh template at `env_path`. Tell the user once: "Created `~/.claude/observability/.env` with placeholder values. Edit it with your real OTLP endpoint and headers before running workflows that emit telemetry." Then continue to Step 1.
|
|
53
|
+
- **`status: "updated"`** — The file existed but was missing one of the two variables; placeholders were appended for `appended_vars`. Tell the user which vars were added and remind them to replace the placeholders. Continue to Step 1.
|
|
54
|
+
- **`status: "error"`** — Surface the error verbatim and stop. Do not proceed to Step 1 — if the env scaffolding is broken, the project-level setup is the wrong place to spend the user's attention.
|
|
55
|
+
|
|
56
|
+
The placeholders the script writes are intentionally bogus (`https://your-otel-collector.example.com`, `Authorization=Bearer YOUR_TOKEN_HERE`). They will not connect to anything. The user is expected to receive real values from the observability team and paste them in.
|
|
57
|
+
|
|
58
|
+
### Step 1 — Detect
|
|
59
|
+
|
|
60
|
+
Run `sa-init-project.js` from the user's current directory (or from any directory inside the target sub-repo if the user told you where they want to initialize):
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
node <observability-scripts>/sa-init-project.js --detect
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Parse the JSON object printed to stdout. The `status` field selects the next branch.
|
|
67
|
+
|
|
68
|
+
**`status: "ready"`** — Multi-repo convention detected. The JSON contains everything needed to act:
|
|
69
|
+
|
|
70
|
+
- `proposed_slug` — the logical project name to write (e.g. `pos-pdv`)
|
|
71
|
+
- `recommended_dir` — the parent directory chosen by the script (1 level up for `docs` clones, 2 levels up for `backend`/`frontend` clones)
|
|
72
|
+
- `convention_breakdown` — how the slug was derived (`module`, `role`, `rest`)
|
|
73
|
+
|
|
74
|
+
Proceed straight to Step 2 with `proposed_slug` and `recommended_dir`. Do **not** ask the user to confirm — the user has already accepted the auto-detection policy by invoking this skill.
|
|
75
|
+
|
|
76
|
+
**`status: "single-repo"`** — The repo name has no `docs`/`backend`/`frontend` segment, so the git remote already produces an unambiguous `project_id`. Stop and tell the user no `.siesa-project` is needed (mention the `fallback_project_id` value so they know what GCP will see). Do not write anything. If they later say "no, write one anyway with slug X", run Step 2 with that explicit slug and the directory they specify.
|
|
77
|
+
|
|
78
|
+
**`status: "already-configured"`** — A `.siesa-project` exists above the current git root. Stop and report `existing_siesa_project.path` and `existing_siesa_project.value` to the user. Do not overwrite implicitly. If the user replies that they want it changed, run `--write` with `--force`, the new slug, and the same directory.
|
|
79
|
+
|
|
80
|
+
**`status: "no-git-repo"`** — The cwd is not inside a git repo. Tell the user to `cd` into the clone they want to initialize and re-run. Do not invent a directory.
|
|
81
|
+
|
|
82
|
+
**`status: "no-remote"`** — Git repo without an `origin` remote. Stop and ask the user for a slug + directory before proceeding, since the script has no signal to auto-derive a name.
|
|
83
|
+
|
|
84
|
+
### Step 2 — Write
|
|
85
|
+
|
|
86
|
+
When `status == "ready"`, run write immediately with the values from the detect output — no user prompt in between:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
node <observability-scripts>/sa-init-project.js --write --slug "<proposed_slug>" --dir "<recommended_dir>"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Parse the JSON response:
|
|
93
|
+
|
|
94
|
+
- **`status: "written"`** — Success. Report a single concise line to the user: the absolute `path` and the slug that was written. Optionally suggest the verification command from Step 3.
|
|
95
|
+
- **`status: "exists"`** — A file is already there with a different value (the detect step missed it because the user pointed at a non-default dir, or a race). Show `current_value` vs `requested_slug` and ask the user whether to overwrite. If yes, re-run with `--force`.
|
|
96
|
+
- **`status: "error"`** — Surface the error message verbatim and stop.
|
|
97
|
+
|
|
98
|
+
For the `single-repo`, `already-configured` and `no-remote` branches where the user explicitly opts into writing, gather slug + directory inline and run the same `--write` command.
|
|
99
|
+
|
|
100
|
+
### Step 3 — Verify (optional)
|
|
101
|
+
|
|
102
|
+
Suggest the user run a quick sanity check from any sub-repo of the project:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
node <observability-scripts>/sa-emit.js --event workflow.started --story "0-verify" --phase create-story 2>&1 | grep project_id
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
If the buffered event shows `project_id=<confirmed-slug>`, the consolidation works. If it still shows the git-remote-derived value, the user is running from a directory that is **not** a descendant of the chosen parent — point them at the right cwd.
|
|
109
|
+
|
|
110
|
+
## Edge cases
|
|
111
|
+
|
|
112
|
+
- **Nested logical projects.** If the user runs the skill inside a directory that is already under an existing `.siesa-project`, the script reports `already-configured`. Do not stack `.siesa-project` files — the lowest one wins during walk-up, which can silently mask the consolidation.
|
|
113
|
+
- **Slugs with dots, underscores or uppercase.** The script rejects them. Stick to lowercase + dashes (`pos-pdv`, `finance-billing`). If the user pushes back, explain that GCP labels work best with kebab-case and that other Siesa scripts assume this shape.
|
|
114
|
+
- **Convention without `business-` prefix.** The parser handles both `business-pos-backend-pdv` and a hypothetical `pos-backend-pdv` — both produce `pos-pdv`. Future modules (`finance-*`, `hcm-*`) work without any code change.
|
|
115
|
+
- **Parent dir that is itself a git repo.** Sometimes the docs repo *is* the logical parent (e.g. `comercial/` is a git repo and `comercial/apps/{backend,frontend}/` are clones inside it). That is fine — writing `.siesa-project` in `comercial/` will (a) commit-able in the docs repo, and (b) walked up correctly by `sa-emit.js` from `comercial/apps/backend/`. Mention this to the user so they can decide whether to commit the file (recommended, since it documents the convention for teammates).
|
|
116
|
+
- **Windows path separators.** The script uses `path.join`, so output paths may contain backslashes. Treat them as opaque and pass them back to `--write --dir` verbatim.
|
|
117
|
+
|
|
118
|
+
## Convention reference
|
|
119
|
+
|
|
120
|
+
The Siesa multi-repo naming convention is:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
business-<module>-<role>-<rest>
|
|
124
|
+
└ org prefix
|
|
125
|
+
└ logical module (pos, finance, hcm, …)
|
|
126
|
+
└ role: docs | backend | frontend
|
|
127
|
+
└ project-specific suffix (pdv, tomapedido, billing, …)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The logical slug collapses `<role>` away: `business-pos-backend-pdv` → `pos-pdv`.
|
|
131
|
+
|
|
132
|
+
For repo names that do not contain `docs`/`backend`/`frontend`, the script returns `single-repo` and no `.siesa-project` is needed.
|
|
133
|
+
|
|
134
|
+
## Examples
|
|
135
|
+
|
|
136
|
+
**Example 1 — Happy path on a `pos-pdv` clone (env already configured)**
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
User: I just cloned business-pos-backend-pdv into comercial/apps/backend. Set up observability.
|
|
140
|
+
Model: <runs sa-init-env.js — status "already-configured", silent>
|
|
141
|
+
<runs sa-init-project.js --detect from comercial/apps/backend/>
|
|
142
|
+
<reads proposed_slug="pos-pdv", recommended_dir=C:\…\comercial>
|
|
143
|
+
<runs --write --slug pos-pdv --dir C:\…\comercial without asking>
|
|
144
|
+
Wrote C:\…\comercial\.siesa-project. sa-emit.js will now report project_id="pos-pdv".
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Example 1b — Same flow on a fresh machine (env missing)**
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
User: Set up observability for this clone.
|
|
151
|
+
Model: <runs sa-init-env.js — status "created">
|
|
152
|
+
Created ~/.claude/observability/.env with placeholder values. Edit it with your real OTLP endpoint and headers before running workflows that emit telemetry.
|
|
153
|
+
<continues with detect + write as in Example 1>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Example 2 — Single-repo project**
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
User: Set up .siesa-project for Siesa-Agents.
|
|
160
|
+
Model: <runs --detect from Siesa-Agents/>
|
|
161
|
+
Repo name "Siesa-Agents" has no docs/backend/frontend segment, so the git remote already produces an unambiguous project_id ("SiesaTeams/Siesa-Agents"). No .siesa-project is needed.
|
|
162
|
+
Want to set a custom slug anyway?
|
|
163
|
+
User: no, thanks
|
|
164
|
+
Model: Nothing written. Existing fallback (SiesaTeams/Siesa-Agents) will continue to be reported.
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Example 3 — Already configured**
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
User: Init observability here.
|
|
171
|
+
Model: <runs --detect>
|
|
172
|
+
A .siesa-project already exists at C:\…\comercial\.siesa-project with value "pos-pdv". sa-emit.js is already consolidating under that slug. No action needed unless you want to change the value.
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Reminders
|
|
176
|
+
|
|
177
|
+
- Step 0 (`sa-init-env.js`) always runs first and is idempotent. Skip the user-facing message when its status is `already-configured`; surface it only on `created` / `updated` / `error`.
|
|
178
|
+
- On `status: "ready"` from `sa-init-project.js --detect`, write immediately using `proposed_slug` and `recommended_dir` — no confirmation prompt. The user invoked this skill knowing it auto-creates the file.
|
|
179
|
+
- Never invent a slug. If the detect step returns `single-repo`, `no-remote`, or any branch other than `ready`, stop and let the user drive the next decision.
|
|
180
|
+
- Never call `--write --force` automatically. Overwriting an existing `.siesa-project` always requires an explicit user request.
|
|
181
|
+
- Do not modify `sa-emit.js` or other observability scripts from this skill — its only side effects are (a) creating `~/.claude/observability/.env` with placeholders if absent and (b) creating or overwriting (with consent) a single `.siesa-project` file.
|
|
182
|
+
- The scripts' JSON output is the contract. If a future field appears (e.g. an extra status), surface it to the user rather than guessing what it means.
|
package/package.json
CHANGED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// sa-init-env.js — Ensure `~/.claude/observability/.env` exists with placeholders for the two
|
|
3
|
+
// OTLP variables the Siesa observability stack needs:
|
|
4
|
+
// - SA_OTLP_ENDPOINT
|
|
5
|
+
// - SA_OTLP_HEADERS
|
|
6
|
+
//
|
|
7
|
+
// This script is idempotent. If the file already contains both variables, it exits without
|
|
8
|
+
// touching anything. If the file is missing, it creates it with clearly-fake placeholder
|
|
9
|
+
// values that the user is expected to edit before workflows emit real telemetry. If the file
|
|
10
|
+
// exists but is missing one of the two variables, the missing one is appended — existing
|
|
11
|
+
// values for unrelated keys are preserved exactly.
|
|
12
|
+
//
|
|
13
|
+
// Note: this script does NOT change how sa-emit.js loads configuration. sa-emit.js continues
|
|
14
|
+
// to load only the project-local .env (`process.cwd()/.env`). This script keeps
|
|
15
|
+
// `~/.claude/observability/.env` as a discoverable per-machine template so engineers know
|
|
16
|
+
// which variables they need without having to read sa-emit.js.
|
|
17
|
+
//
|
|
18
|
+
// Modes:
|
|
19
|
+
// <no args> Default = --ensure. Creates the file (and parent dir) when needed.
|
|
20
|
+
// --check Read-only check. Reports the current state without writing anything.
|
|
21
|
+
//
|
|
22
|
+
// Exit codes:
|
|
23
|
+
// 0 Success (ensure created/updated/no-op, or check ran cleanly)
|
|
24
|
+
// 1 Write or filesystem error
|
|
25
|
+
|
|
26
|
+
'use strict'
|
|
27
|
+
|
|
28
|
+
const fs = require('fs')
|
|
29
|
+
const os = require('os')
|
|
30
|
+
const path = require('path')
|
|
31
|
+
|
|
32
|
+
const TARGET_VARS = ['SA_OTLP_ENDPOINT', 'SA_OTLP_HEADERS']
|
|
33
|
+
|
|
34
|
+
const PLACEHOLDERS = {
|
|
35
|
+
SA_OTLP_ENDPOINT: 'https://your-otel-collector.example.com',
|
|
36
|
+
SA_OTLP_HEADERS: 'Authorization=Bearer YOUR_TOKEN_HERE',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parseArgs(argv) {
|
|
40
|
+
const out = {}
|
|
41
|
+
for (const a of argv) {
|
|
42
|
+
if (a.startsWith('--')) out[a.slice(2)] = true
|
|
43
|
+
}
|
|
44
|
+
return out
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function emit(obj) {
|
|
48
|
+
process.stdout.write(JSON.stringify(obj, null, 2) + '\n')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function resolveTarget() {
|
|
52
|
+
// sa-emit.js uses os.homedir() + '.claude' + 'observability' for its state dir; we mirror
|
|
53
|
+
// that path so engineers see a single, predictable location across both tools.
|
|
54
|
+
const dir = path.join(os.homedir(), '.claude', 'observability')
|
|
55
|
+
const file = path.join(dir, '.env')
|
|
56
|
+
return { dir, file }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Parse an existing .env file into an ordered list of entries plus a key index. We track
|
|
60
|
+
// order so we can rewrite the file deterministically without scrambling the user's edits.
|
|
61
|
+
function readEnvFile(filePath) {
|
|
62
|
+
if (!fs.existsSync(filePath)) {
|
|
63
|
+
return { exists: false, lines: [], keys: new Set() }
|
|
64
|
+
}
|
|
65
|
+
const raw = fs.readFileSync(filePath, 'utf8')
|
|
66
|
+
const lines = raw.split(/\r?\n/)
|
|
67
|
+
const keys = new Set()
|
|
68
|
+
for (const line of lines) {
|
|
69
|
+
const trimmed = line.trim()
|
|
70
|
+
if (!trimmed || trimmed.startsWith('#')) continue
|
|
71
|
+
const eq = trimmed.indexOf('=')
|
|
72
|
+
if (eq === -1) continue
|
|
73
|
+
keys.add(trimmed.slice(0, eq).trim())
|
|
74
|
+
}
|
|
75
|
+
return { exists: true, lines, keys, raw }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function findMissingVars(keys) {
|
|
79
|
+
return TARGET_VARS.filter(v => !keys.has(v))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function buildFreshFile() {
|
|
83
|
+
return [
|
|
84
|
+
'# Siesa observability — OTLP exporter configuration.',
|
|
85
|
+
'# This file is a per-machine template. Replace the placeholder values with the real',
|
|
86
|
+
'# endpoint and headers you received from the observability team. sa-emit.js itself',
|
|
87
|
+
'# reads only the project-local .env; this file documents which variables are required',
|
|
88
|
+
'# and gives you one canonical place to keep your personal copy of them.',
|
|
89
|
+
'',
|
|
90
|
+
`SA_OTLP_ENDPOINT=${PLACEHOLDERS.SA_OTLP_ENDPOINT}`,
|
|
91
|
+
`SA_OTLP_HEADERS=${PLACEHOLDERS.SA_OTLP_HEADERS}`,
|
|
92
|
+
'',
|
|
93
|
+
].join('\n')
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function appendMissing(existingRaw, missing) {
|
|
97
|
+
// Append missing vars at the bottom under a clear delimiter so the user can spot the new
|
|
98
|
+
// additions and replace the placeholders.
|
|
99
|
+
const needsLeadingNewline = existingRaw.length > 0 && !existingRaw.endsWith('\n')
|
|
100
|
+
const parts = [existingRaw]
|
|
101
|
+
if (needsLeadingNewline) parts.push('\n')
|
|
102
|
+
parts.push('\n# --- added by sa-init-env.js (placeholders, edit before use) ---\n')
|
|
103
|
+
for (const v of missing) {
|
|
104
|
+
parts.push(`${v}=${PLACEHOLDERS[v]}\n`)
|
|
105
|
+
}
|
|
106
|
+
return parts.join('')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function runCheck() {
|
|
110
|
+
const { dir, file } = resolveTarget()
|
|
111
|
+
const env = readEnvFile(file)
|
|
112
|
+
if (!env.exists) {
|
|
113
|
+
emit({
|
|
114
|
+
status: 'missing',
|
|
115
|
+
message: `~/.claude/observability/.env does not exist. Run without --check to create it with placeholders.`,
|
|
116
|
+
env_path: file,
|
|
117
|
+
env_dir: dir,
|
|
118
|
+
target_vars: TARGET_VARS,
|
|
119
|
+
present_vars: [],
|
|
120
|
+
missing_vars: TARGET_VARS.slice(),
|
|
121
|
+
})
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
const missing = findMissingVars(env.keys)
|
|
125
|
+
if (missing.length === 0) {
|
|
126
|
+
emit({
|
|
127
|
+
status: 'already-configured',
|
|
128
|
+
message: `~/.claude/observability/.env already declares both SA_OTLP_ENDPOINT and SA_OTLP_HEADERS.`,
|
|
129
|
+
env_path: file,
|
|
130
|
+
env_dir: dir,
|
|
131
|
+
target_vars: TARGET_VARS,
|
|
132
|
+
present_vars: TARGET_VARS.slice(),
|
|
133
|
+
missing_vars: [],
|
|
134
|
+
})
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
emit({
|
|
138
|
+
status: 'partial',
|
|
139
|
+
message: `~/.claude/observability/.env exists but is missing: ${missing.join(', ')}. Run without --check to append placeholders for the missing variables.`,
|
|
140
|
+
env_path: file,
|
|
141
|
+
env_dir: dir,
|
|
142
|
+
target_vars: TARGET_VARS,
|
|
143
|
+
present_vars: TARGET_VARS.filter(v => env.keys.has(v)),
|
|
144
|
+
missing_vars: missing,
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function runEnsure() {
|
|
149
|
+
const { dir, file } = resolveTarget()
|
|
150
|
+
|
|
151
|
+
if (!fs.existsSync(dir)) {
|
|
152
|
+
try {
|
|
153
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
154
|
+
} catch (err) {
|
|
155
|
+
emit({ status: 'error', message: `Failed to create ${dir}: ${err.message}`, env_path: file })
|
|
156
|
+
process.exit(1)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const env = readEnvFile(file)
|
|
161
|
+
|
|
162
|
+
if (!env.exists) {
|
|
163
|
+
try {
|
|
164
|
+
fs.writeFileSync(file, buildFreshFile(), { encoding: 'utf8' })
|
|
165
|
+
} catch (err) {
|
|
166
|
+
emit({ status: 'error', message: `Failed to write ${file}: ${err.message}`, env_path: file })
|
|
167
|
+
process.exit(1)
|
|
168
|
+
}
|
|
169
|
+
emit({
|
|
170
|
+
status: 'created',
|
|
171
|
+
message: `Wrote ${file} with placeholder values. Edit the file and replace the placeholders with the real OTLP endpoint and headers before running workflows that emit telemetry.`,
|
|
172
|
+
env_path: file,
|
|
173
|
+
env_dir: dir,
|
|
174
|
+
target_vars: TARGET_VARS,
|
|
175
|
+
wrote_vars: TARGET_VARS.slice(),
|
|
176
|
+
placeholders: PLACEHOLDERS,
|
|
177
|
+
})
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const missing = findMissingVars(env.keys)
|
|
182
|
+
if (missing.length === 0) {
|
|
183
|
+
emit({
|
|
184
|
+
status: 'already-configured',
|
|
185
|
+
message: `~/.claude/observability/.env already declares both SA_OTLP_ENDPOINT and SA_OTLP_HEADERS. Nothing to do.`,
|
|
186
|
+
env_path: file,
|
|
187
|
+
env_dir: dir,
|
|
188
|
+
target_vars: TARGET_VARS,
|
|
189
|
+
present_vars: TARGET_VARS.slice(),
|
|
190
|
+
})
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// File exists but is missing one or more target vars. Append the missing ones with
|
|
195
|
+
// placeholders; never modify existing entries — the user may have already filled in
|
|
196
|
+
// real values for the keys that are present.
|
|
197
|
+
try {
|
|
198
|
+
fs.writeFileSync(file, appendMissing(env.raw, missing), { encoding: 'utf8' })
|
|
199
|
+
} catch (err) {
|
|
200
|
+
emit({ status: 'error', message: `Failed to update ${file}: ${err.message}`, env_path: file })
|
|
201
|
+
process.exit(1)
|
|
202
|
+
}
|
|
203
|
+
emit({
|
|
204
|
+
status: 'updated',
|
|
205
|
+
message: `Appended placeholders for missing variables to ${file}: ${missing.join(', ')}. Replace the placeholders with real values before running workflows that emit telemetry.`,
|
|
206
|
+
env_path: file,
|
|
207
|
+
env_dir: dir,
|
|
208
|
+
target_vars: TARGET_VARS,
|
|
209
|
+
appended_vars: missing,
|
|
210
|
+
placeholders: PLACEHOLDERS,
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const args = parseArgs(process.argv.slice(2))
|
|
215
|
+
if (args['check']) {
|
|
216
|
+
runCheck()
|
|
217
|
+
} else {
|
|
218
|
+
runEnsure()
|
|
219
|
+
}
|