@mmerterden/multi-agent-pipeline 15.1.0 → 15.3.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/CHANGELOG.md CHANGED
@@ -16,6 +16,39 @@ Internal file-layout changes that don't affect the slash-command surface are sti
16
16
 
17
17
  ## [Unreleased]
18
18
 
19
+ ## [15.3.0] - 2026-08-15
20
+
21
+ ### Added
22
+
23
+ - **`/multi-agent:setup` onboards the usage-dashboard token (opt-in, one-time).**
24
+ When a team runs the private dashboard and the admin issued this user a personal
25
+ ingest token, setup stores it in the OS credential store ONLY (never a file,
26
+ prefs value, git, or synced tree), maps `keychainMapping.usage_ingest`, and
27
+ turns logging on. After that single paste, `/multi-agent:update` keeps it active
28
+ and every run reports itself - no further steps. The token is write-only,
29
+ per-user (independently revocable), and read at runtime from the credential
30
+ store, transmitted only over TLS. Skipping the prompt leaves logging fully off.
31
+ True zero-touch is deliberately not offered: a machine that authenticates with
32
+ no prior onboarding would require an embeddable secret, which is the attack
33
+ vector - one-time onboarding is the security floor.
34
+
35
+ ## [15.2.0] - 2026-08-15
36
+
37
+ ### Added
38
+
39
+ - **`/multi-agent:update` auto-enables usage logging when a token is onboarded.**
40
+ The private dashboard stays opt-in and off by default, but once the shared
41
+ ingest token exists on a machine, an update flips `usageLog.enabled` on so the
42
+ admin's trusted group does not each have to toggle it by hand. It NEVER
43
+ fabricates or ships a secret: it activates only when a token resolves from
44
+ `MULTI_AGENT_USAGE_TOKEN`, `usageLog.token`, or the Keychain item named by the
45
+ new generic `keychainMapping.usage_ingest` slot - a machine that was never
46
+ given the token stays silent.
47
+ - **`usage-report.mjs` reads the ingest token from the Keychain** (via
48
+ `credential-store.sh`, resolved through `keychainMapping.usage_ingest`) as a
49
+ fallback after env and `usageLog.token`, so the secret never has to live in a
50
+ synced or plaintext file for logging to work.
51
+
19
52
  ## [15.1.0] - 2026-08-14
20
53
 
21
54
  Opt-in run telemetry to a private dashboard, and a refactor band that mines it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmerterden/multi-agent-pipeline",
3
- "version": "15.1.0",
3
+ "version": "15.3.0",
4
4
  "description": "8-phase AI development pipeline with full orchestration on Claude Code, Copilot CLI and Codex CLI. Analysis, planning, TDD, CLI-aware parallel review with consensus surfacing + Fable triage, default-FAIL evidence gates, secret + intent guards, per-phase cost ledger, persistent learnings memory, wiki generation, commit automation. Token-preserving uninstall.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -206,6 +206,40 @@ Save the resolved mapping to preferences:
206
206
 
207
207
  `null` = not mapped (missing or skipped). Pipeline phases read this mapping to retrieve tokens dynamically - never hardcoded key names.
208
208
 
209
+ ### Step 2.7 - Usage dashboard token (optional, opt-in)
210
+
211
+ Only relevant when the team runs a private usage dashboard and the admin has issued this user a personal ingest token. This is the ONE-TIME onboarding that makes usage logging automatic afterwards: once the token is in the credential store, `/multi-agent:update` auto-enables `usageLog` and every run reports itself. Skipping it leaves logging fully off.
212
+
213
+ Ask (in `outputLanguage`), and proceed only on an explicit yes:
214
+
215
+ ```
216
+ Do you have a usage-dashboard ingest token from your admin?
217
+ It turns on private run telemetry (who/what/when/tokens) for the team dashboard.
218
+ [ Paste token / Skip ]
219
+ ```
220
+
221
+ On paste, store the secret in the credential store ONLY - never in a file, prefs value, git, or any synced/published tree. Use the standard per-user key name so it is revocable independently and consistent across the user's machines:
222
+
223
+ ```bash
224
+ ~/.claude/lib/credential-store.sh set "${USER}_Usage_Ingest_Token" "<pasted-token>"
225
+ ```
226
+
227
+ Then map it and enable logging (the token itself stays in the credential store; only the logical mapping + the on-switch land in prefs):
228
+
229
+ ```bash
230
+ node -e '
231
+ const fs=require("fs"),os=require("os"),p=os.homedir()+"/.claude/multi-agent-preferences.json";
232
+ const j=JSON.parse(fs.readFileSync(p,"utf8"));
233
+ j.global=j.global||{}; j.global.keychainMapping=j.global.keychainMapping||{};
234
+ j.global.keychainMapping.usage_ingest=process.argv[1];
235
+ j.global.usageLog=Object.assign({enabled:true},j.global.usageLog||{},{enabled:true});
236
+ fs.writeFileSync(p,JSON.stringify(j,null,2)+"\n");
237
+ ' "${USER}_Usage_Ingest_Token"
238
+ echo " -> usage logging onboarded (token in credential store; logging on)"
239
+ ```
240
+
241
+ Security notes to surface to the user: the token is **write-only** (it can only append activity metadata to the dashboard - no read access, no other scope), it is stored **only in the OS credential store**, and it is **per-user** so the admin can revoke this one token without affecting anyone else. `usage-report.mjs` reads it from the credential store at runtime via the `usage_ingest` mapping; it is never written to a file or transmitted except over TLS to the ingest endpoint.
242
+
209
243
  ### Auto-learned fields (no setup step needed)
210
244
 
211
245
  **Jira Project Key**: auto-learned from pipeline usage. User runs `/multi-agent ABC-123` → `ABC` is extracted and saved to `prefs.global.defaultJiraKey` (first ever) and `prefs.projects[{project}].jiraProjectKeys` (per-project). Multiple keys accumulate over time - most recent first, deduped. Phase 0 Step 1 (Parse Input) handles extraction.
@@ -71,6 +71,33 @@ Update the pipeline in one command. Existing preferences are preserved; only ski
71
71
  fi
72
72
  ```
73
73
 
74
+ 5b. **Auto-enable usage logging when a token is already onboarded.** The private
75
+ usage dashboard is opt-in and off by default. This step flips it ON only when a
76
+ token can be resolved - it NEVER fabricates a secret or ships one, so a machine
77
+ that was never given the token stays silent. Resolution order: env
78
+ `MULTI_AGENT_USAGE_TOKEN`, then `usageLog.token`, then the Keychain item named by
79
+ `keychainMapping.usage_ingest`. Endpoint is left to the emitter's default.
80
+ ```bash
81
+ PREFS="$HOME/.claude/multi-agent-preferences.json"
82
+ if [ -f "$PREFS" ] && command -v jq >/dev/null 2>&1; then
83
+ ENABLED=$(jq -r '.global.usageLog.enabled // false' "$PREFS" 2>/dev/null)
84
+ if [ "$ENABLED" != "true" ]; then
85
+ TOK="${MULTI_AGENT_USAGE_TOKEN:-}"
86
+ [ -z "$TOK" ] && TOK=$(jq -r '.global.usageLog.token // empty' "$PREFS" 2>/dev/null)
87
+ if [ -z "$TOK" ]; then
88
+ KNAME=$(jq -r '.global.keychainMapping.usage_ingest // empty' "$PREFS" 2>/dev/null)
89
+ [ -n "$KNAME" ] && TOK=$(bash "$HOME/.claude/lib/credential-store.sh" get "$KNAME" 2>/dev/null)
90
+ fi
91
+ if [ -n "$TOK" ]; then
92
+ node -e 'const fs=require("fs"),p=process.argv[1];const j=JSON.parse(fs.readFileSync(p,"utf8"));j.global=j.global||{};j.global.usageLog=j.global.usageLog||{};j.global.usageLog.enabled=true;fs.writeFileSync(p,JSON.stringify(j,null,2)+"\n");' "$PREFS"
93
+ echo " -> usage logging activated (ingest token found)"
94
+ else
95
+ echo " -> usage logging left off (no ingest token onboarded)"
96
+ fi
97
+ fi
98
+ fi
99
+ ```
100
+
74
101
  6. **Show the new version**:
75
102
  ```bash
76
103
  NEW_VERSION=$(node -p "require('$PIPE_DIR/package.json').version" 2>/dev/null || echo "unknown")
@@ -12,6 +12,7 @@
12
12
  "figma_mcp": null,
13
13
  "fortify": null,
14
14
  "graylog": null,
15
+ "usage_ingest": null,
15
16
  "firebase": null,
16
17
  "jenkins": null
17
18
  },
@@ -188,6 +188,13 @@
188
188
  "null"
189
189
  ]
190
190
  },
191
+ "usage_ingest": {
192
+ "type": [
193
+ "string",
194
+ "null"
195
+ ],
196
+ "description": "Keychain item holding the shared ingest token for the private usage dashboard. When set, usage-report.mjs reads the token from here (never from a synced file), and /multi-agent:update auto-enables usageLog once the token is onboarded. Absent = usage logging stays off."
197
+ },
191
198
  "figma_pat": {
192
199
  "type": [
193
200
  "string",
@@ -32,6 +32,7 @@ import { readFileSync, existsSync, readdirSync, appendFileSync } from "fs";
32
32
  import { homedir, platform } from "os";
33
33
  import { dirname, join } from "path";
34
34
  import { fileURLToPath } from "url";
35
+ import { execFileSync } from "child_process";
35
36
  import { costUsd } from "./_cost.mjs";
36
37
 
37
38
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -70,6 +71,36 @@ function resolvePrefs() {
70
71
  return resolveGlobalPrefs().usageLog ?? {};
71
72
  }
72
73
 
74
+ /**
75
+ * Resolve the ingest token without ever storing it in a synced file.
76
+ * Order: env, then prefs.usageLog.token, then the Keychain entry mapped by
77
+ * prefs.global.keychainMapping.usage_ingest (via credential-store.sh). This
78
+ * lets update flip usageLog.enabled on while the secret stays in the Keychain.
79
+ */
80
+ function resolveToken() {
81
+ if (process.env.MULTI_AGENT_USAGE_TOKEN) return process.env.MULTI_AGENT_USAGE_TOKEN;
82
+ const g = resolveGlobalPrefs();
83
+ const inline = g.usageLog?.token;
84
+ if (typeof inline === "string" && inline.trim()) return inline.trim();
85
+ const name = g.keychainMapping?.usage_ingest;
86
+ if (typeof name === "string" && name.trim()) {
87
+ const store = join(homedir(), ".claude", "lib", "credential-store.sh");
88
+ if (existsSync(store)) {
89
+ try {
90
+ const out = execFileSync("bash", [store, "get", name.trim()], {
91
+ encoding: "utf-8",
92
+ timeout: 4000,
93
+ stdio: ["ignore", "pipe", "ignore"],
94
+ }).trim();
95
+ if (out) return out;
96
+ } catch {
97
+ /* keychain unavailable - stay silent */
98
+ }
99
+ }
100
+ }
101
+ return "";
102
+ }
103
+
73
104
  function enabledPlugins(state) {
74
105
  const root = state.projectRoot || process.cwd();
75
106
  const settings = readJson(join(root, ".claude", "settings.json"));
@@ -393,7 +424,7 @@ async function post(endpoint, token, event) {
393
424
  async function main() {
394
425
  const args = parseArgs(process.argv.slice(2));
395
426
  const prefs = resolvePrefs();
396
- const token = process.env.MULTI_AGENT_USAGE_TOKEN || prefs.token || "";
427
+ const token = resolveToken();
397
428
  const endpoint = prefs.endpoint || ENDPOINT_DEFAULT;
398
429
 
399
430
  if (!args.dryRun && (prefs.enabled !== true || !token)) return;