@ciandt-flow/flow-ai-obs 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.md ADDED
@@ -0,0 +1,13 @@
1
+ # Proprietary License
2
+
3
+ Copyright (c) 2024 CI&T Inc. All rights reserved.
4
+
5
+ This software and its source code are proprietary and confidential.
6
+ Unauthorized copying, distribution, modification, or use of this software,
7
+ via any medium, is strictly prohibited.
8
+
9
+ This package is made available on the npm public registry solely for
10
+ installation and use within authorized CI&T products and services.
11
+
12
+ No open-source rights are granted. Use is subject to a separate written
13
+ agreement with CI&T Inc.
package/README.md ADDED
@@ -0,0 +1,161 @@
1
+ # flow-ai-obs
2
+
3
+ > Automatic observability for Claude Code sessions — powered by [Flow by CI&T](https://flow.ciandt.com).
4
+
5
+ `flow-ai-obs` captures every Claude Code session as a structured trace in Langfuse with zero configuration changes to your workflow.
6
+
7
+ - **Root span** per prompt — input, output, token usage, estimated cost
8
+ - **Child span** per tool call — timing, arguments, output, error detection
9
+ - **Skill context** — detects `/skill-name` invocations and enriches traces with skill metadata
10
+ - **Offline buffer** — traces are persisted locally and replayed automatically when the gateway is back online
11
+
12
+ ---
13
+
14
+ ## Requirements
15
+
16
+ - Node.js >= 18.0.0
17
+ - [Claude Code](https://docs.anthropic.com/en/docs/claude-code) with hooks enabled
18
+ - A valid [Flow by CI&T](https://flow.ciandt.com) account (for gateway mode)
19
+
20
+ ---
21
+
22
+ ## Quick Start
23
+
24
+ ```bash
25
+ # 1. Install globally
26
+ npm install -g @ciandt-flow/flow-ai-obs
27
+
28
+ # 2. Authenticate with your Flow credentials
29
+ flow-ai-obs auth login
30
+
31
+ # 3. Register hooks in Claude Code
32
+ flow-ai-obs install
33
+
34
+ # 4. Verify
35
+ flow-ai-obs auth status
36
+ ```
37
+
38
+ That's it. Every Claude Code session with Skills or Agents now generates traces automatically.
39
+
40
+ > **Already using `flow-plugins-cli`?** If you're authenticated there, step 2 is skipped automatically — credentials are shared.
41
+
42
+ ---
43
+
44
+ ## Installation
45
+
46
+ ### Global install (recommended)
47
+
48
+ ```bash
49
+ npm install -g @ciandt-flow/flow-ai-obs
50
+ ```
51
+
52
+ The `postinstall` script runs automatically and guides you through authentication and hook registration.
53
+
54
+ ### npx (no install)
55
+
56
+ ```bash
57
+ # Replace hook commands in .claude/settings.local.json with:
58
+ npx --yes @ciandt-flow/flow-ai-obs UserPromptSubmit
59
+ ```
60
+
61
+ ### Pre-release / beta
62
+
63
+ ```bash
64
+ npm install -g @ciandt-flow/flow-ai-obs@beta
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Authentication
70
+
71
+ Langfuse credentials are embedded — you only authenticate with your Flow account once.
72
+
73
+ ```bash
74
+ # Interactive login
75
+ flow-ai-obs auth login
76
+
77
+ # Non-interactive (CI/CD)
78
+ flow-ai-obs auth login \
79
+ --client-id YOUR_CLIENT_ID \
80
+ --client-secret YOUR_CLIENT_SECRET \
81
+ --tenant YOUR_TENANT
82
+
83
+ # Check status
84
+ flow-ai-obs auth status
85
+
86
+ # Remove credentials
87
+ flow-ai-obs auth logout
88
+ ```
89
+
90
+ Credentials are stored securely in the OS native keychain (macOS Keychain, Linux Secret Service, Windows Credential Manager). The same credentials are shared with `flow-plugins-cli` — no re-authentication needed if you're already logged in.
91
+
92
+ ---
93
+
94
+ ## Hook Configuration
95
+
96
+ The `flow-ai-obs install` command writes this to `~/.claude/settings.json` automatically.
97
+ To configure manually, add to `.claude/settings.local.json`:
98
+
99
+ ```json
100
+ {
101
+ "hooks": {
102
+ "UserPromptSubmit": [{ "type": "command", "command": "flow-ai-obs UserPromptSubmit", "timeout": 5 }],
103
+ "PreToolUse": [{ "type": "command", "command": "flow-ai-obs PreToolUse", "timeout": 3 }],
104
+ "PostToolUse": [{ "type": "command", "command": "flow-ai-obs PostToolUse", "timeout": 10 }],
105
+ "Stop": [{ "type": "command", "command": "flow-ai-obs Stop", "timeout": 15 }]
106
+ }
107
+ }
108
+ ```
109
+
110
+ ---
111
+
112
+ ## What Gets Tracked
113
+
114
+ Only sessions with at least one **Skill** or **Agent** call generate traces. Each trace includes:
115
+
116
+ | Data | Source |
117
+ |------|--------|
118
+ | User prompt | `UserPromptSubmit` hook |
119
+ | Tool calls with timing | `PreToolUse` + `PostToolUse` pair |
120
+ | Assistant response | Claude transcript |
121
+ | Token usage (input/output) | Claude transcript |
122
+ | Estimated cost (USD) | Model pricing table |
123
+ | User identity | Flow OAuth JWT |
124
+ | Skill name | Detected from `/skill-name` prompt pattern |
125
+ | Session ID and working directory | Claude Code runtime |
126
+
127
+ ---
128
+
129
+ ## Environment Variables
130
+
131
+ No env vars are needed for standard gateway mode. Optional overrides:
132
+
133
+ | Variable | Description |
134
+ |----------|-------------|
135
+ | `FLOW_TELEMETRY_GATEWAY_URL` | Override the default Flow gateway URL |
136
+ | `FLOW_TELEMETRY` | Set to `"true"` to enable direct-to-Langfuse mode |
137
+ | `LANGFUSE_BASE_URL` | Your Langfuse instance URL (direct mode only) |
138
+ | `LANGFUSE_PUBLIC_KEY` | Langfuse public key (direct mode only) |
139
+ | `LANGFUSE_SECRET_KEY` | Langfuse secret key (direct mode only) |
140
+ | `ANTHROPIC_MODEL` | Model name for cost estimation |
141
+ | `FLOW_TELEMETRY_DEBUG` | Set to `"1"` for verbose logs at `/tmp/flow-ai-obs-debug.log` |
142
+ | `OTEL_RESOURCE_ATTRIBUTES` | Extra trace metadata (e.g. `team=my-team,project=my-project`) |
143
+
144
+ ---
145
+
146
+ ## Offline Resilience
147
+
148
+ If the gateway is temporarily unavailable, traces are buffered in `/tmp` and replayed automatically on the next session. Traces older than 24 hours or exceeding 50 files are discarded automatically.
149
+
150
+ ---
151
+
152
+ ## Documentation
153
+
154
+ - [Getting Started](docs/getting-started.md) — full walkthrough including direct mode and advanced configuration
155
+ - [ADRs](docs/ADRs/) — architectural decision records
156
+
157
+ ---
158
+
159
+ ## License
160
+
161
+ See [LICENSE.md](LICENSE.md). This package is proprietary software by CI&T Inc., made available on the npm public registry for use within authorized CI&T products and services.
package/dist/cli.js ADDED
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';var c=(e=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(t,n)=>(typeof require<"u"?require:t)[n]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+e+'" is not supported')});var d=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var rt=d((sa,Qn)=>{Qn.exports={name:"@ciandt-flow/flow-ai-obs",version:"0.1.0",description:"Claude Code hooks for Langfuse OTLP tracing \u2014 cross-platform",main:"dist/index.js",bin:{"flow-ai-obs":"dist/cli.js","flow-ai-obs-setup":"dist/setup.js"},scripts:{build:"tsup",test:"node --test src/**/*.test.js",typecheck:"node --check src/**/*.js bin/cli.js",lint:"node --check src/**/*.js bin/cli.js","security-scan":"trufflehog filesystem src/ --fail --no-update --exclude-paths .trufflehogignore",postinstall:"node dist/setup.js",prepublishOnly:"npm run security-scan && npm run build","release:npm":"changeset publish",changeset:"changeset","version-packages":"changeset version","install:hooks":"node bin/cli.js install","auth:login":"node bin/cli.js auth login","auth:logout":"node bin/cli.js auth logout","auth:status":"node bin/cli.js auth status",smoke:"bash scripts/smoke-test.sh","smoke:debug":"bash scripts/smoke-test.sh --debug","smoke:send":"bash scripts/smoke-test.sh --send --allow-http --debug"},files:["dist/","README.md","LICENSE.md"],keywords:["claude-code","langfuse","observability","otlp","hooks","flow","ciandt"],author:"CI&T Flow Team",license:"SEE LICENSE IN LICENSE.md",private:false,dependencies:{keytar:"7.9.0"},devDependencies:{"@changesets/changelog-github":"^0.6.0","@changesets/cli":"^2.30.0",tsup:"^8.5.1",typescript:"^6.0.3"},engines:{node:">=18.0.0"},publishConfig:{registry:"https://registry.npmjs.org",access:"public"},repository:{type:"git",url:"git+https://github.com/CI-T-HyperX/flow-codeassistant-observability.git"}};});var Ie=d((ra,ut)=>{var es=c("https"),at=c("fs"),ts=c("os"),ns=c("path"),{spawn:ss}=c("child_process"),Le;function R(){return Le||(Le=rt()),Le}var ct=ns.join(ts.tmpdir(),"flow-obs-update-cache.json"),rs=1440*60*1e3,os=3e3;function Ae(e,t){let n=r=>{let i=String(r).replace(/^v/,"").match(/^(\d+)\.(\d+)\.(\d+)/);return i?[+i[1],+i[2],+i[3]]:null},s=n(e),o=n(t);return !s||!o?false:o[0]!==s[0]?o[0]>s[0]:o[1]!==s[1]?o[1]>s[1]:o[2]>s[2]}function is(){try{let e=JSON.parse(at.readFileSync(ct,"utf8"));return typeof e.latestVersion!="string"||Date.now()-e.checkedAt>=rs?null:e}catch{return null}}function as(e,t){try{at.writeFileSync(ct,JSON.stringify({latestVersion:e,updatePriority:t||null,checkedAt:Date.now()}),{mode:384});}catch{}}function cs(e){let t=R().name,n=es.get(`https://registry.npmjs.org/${encodeURIComponent(t)}/latest`,{timeout:os},s=>{if(s.statusCode!==200)return s.resume(),e(new Error(`HTTP ${s.statusCode}`));let o="";s.setEncoding("utf8"),s.on("data",r=>{o+=r;}),s.on("end",()=>{try{let r=JSON.parse(o);if(typeof r.version!="string")throw new Error("no version field");e(null,{version:r.version,updatePriority:typeof r.updatePriority=="string"?r.updatePriority:null});}catch(r){e(r);}});});n.on("error",e),n.on("timeout",()=>{n.destroy(new Error("timeout"));});}function ot(e){let t=R().name,s=` Update available: ${R().version} \u2192 ${e}`,o=` Run: npm install -g ${t}`,r=Math.max(s.length,o.length)+2,i="\u2500".repeat(r);process.stderr.write(`
3
+ \u256D${i}\u256E
4
+ \u2502${s.padEnd(r)}\u2502
5
+ \u2502${o.padEnd(r)}\u2502
6
+ \u2570${i}\u256F
7
+
8
+ `);}function it(e){let t=R().name;process.stderr.write(`
9
+ [${t}] Critical update detected \u2014 auto-updating to ${e}...
10
+
11
+ `),ss("npm",["install","-g",`${t}@${e}`],{detached:true,stdio:"ignore",windowsHide:true}).unref();}function us(){try{let e=is();if(e){Ae(R().version,e.latestVersion)&&(e.updatePriority==="auto"?it(e.latestVersion):ot(e.latestVersion));return}setImmediate(()=>{cs((t,n)=>{t||!n||(as(n.version,n.updatePriority),Ae(R().version,n.version)&&(n.updatePriority==="auto"?it(n.version):ot(n.version)));});});}catch{}}ut.exports={checkForUpdate:us,isNewer:Ae};});var w=d((oa,dt)=>{var Y=process.env.FLOW_BASE_URL||"https://flow.ciandt.com",lt={AUTH_ENGINE:process.env.FLOW_AUTH_ENGINE_URL||`${Y}/auth-engine-api/v2/api-key/token`,GATEWAY_TRACES:process.env.FLOW_GATEWAY_TRACES_URL||`${Y}/flow-langfuse-gateway/v1/traces`,GATEWAY_HEALTH:process.env.FLOW_GATEWAY_HEALTH_URL||`${Y}/flow-langfuse-gateway/v1/health`,OTEL_ENDPOINT:process.env.FLOW_OTEL_ENDPOINT_URL||`${Y}/otel`},ls={SERVICE:"flow-plugins-cli",ACCOUNT_CREDS:"credentials",ACCOUNT_TOKEN:"token-cache"},ds="FLOW-nodejs",ps="config.json",fs={SCOPE_NAME:"flow-ai-obs",SCOPE_VERSION:"0.1.0"},hs={CLAUDE_CODE_ENABLE_TELEMETRY:"1",OTEL_EXPORTER_OTLP_ENDPOINT:lt.OTEL_ENDPOINT,OTEL_EXPORTER_OTLP_PROTOCOL:"http/json",OTEL_METRICS_EXPORTER:"otlp",OTEL_LOGS_EXPORTER:"otlp",OTEL_LOG_TOOL_DETAILS:"1",OTEL_LOG_USER_PROMPTS:"1"},Ss={FLOW_TELEMETRY:"FLOW_TELEMETRY",FLOW_TELEMETRY_GATEWAY:"FLOW_TELEMETRY_GATEWAY_URL",LANGFUSE_BASE_URL:"LANGFUSE_BASE_URL",LANGFUSE_PUBLIC_KEY:"LANGFUSE_PUBLIC_KEY",LANGFUSE_SECRET_KEY:"LANGFUSE_SECRET_KEY",ANTHROPIC_MODEL:"ANTHROPIC_MODEL",FLOW_OBS_DEBUG:"FLOW_TELEMETRY_DEBUG",FLOW_OBS_ALLOW_HTTP:"FLOW_TELEMETRY_ALLOW_HTTP"},Es={LOCK_STALE_MS:3e4,CONNECTIVITY_MAX_RETRY:3,CONNECTIVITY_RETRY_MS:2e3},Ts={UserPromptSubmit:5,PreToolUse:3,PostToolUse:10,Stop:15},ws={FILE_PREFIX:"flow-obs-buffer-",MAX_AGE_MS:5760*60*1e3,MAX_FILES:50},gs="flow-ai-obs-debug.log",_s={TOOL_INPUT:2e4,TOOL_OUTPUT:2e4,TRACE_OUTPUT:2e4,USER_INSTRUCTION:1e4,TRACE_INPUT:5e4,TOOL_BRIEF:60},ms="flow-obs-";dt.exports={FLOW_BASE_URL:Y,FLOW_URLS:lt,VAULT:ls,CONFIG_DIR_NAME:ds,CONFIG_FILE_NAME:ps,OTLP:fs,NATIVE_OTEL:hs,ENV:Ss,INSTALLER:Es,HOOK_TIMEOUTS:Ts,BUFFER:ws,DEBUG_LOG_FILENAME:gs,STATE_FILE_PREFIX:ms,TRACE_LIMITS:_s};});var W=d((ia,ft)=>{var ys=c("https"),Os=c("http"),{FLOW_URLS:Ls}=w(),As=/^[a-z0-9][a-z0-9-]{0,62}[a-z0-9]$|^[a-z0-9]$/;function Is(e){if(!As.test(e))throw new Error(`Invalid tenant value: "${e}". Tenant must be lowercase alphanumeric and hyphens (1-64 chars).`)}var pt={INVALID_CLIENT_SECRET:"Invalid Client Secret \u2014 check the value and try again",INVALID_CLIENT_ID:"Invalid Client ID \u2014 check the value and try again",INVALID_TENANT:"Invalid Tenant \u2014 check the value and try again",TENANT_NOT_FOUND:"Tenant not found \u2014 verify the tenant identifier"};function ks(e,t){try{let n=JSON.parse(t);if(typeof n.error=="string"&&pt[n.error])return new Error(pt[n.error])}catch{}return e===401||e===403||e===500?new Error("Invalid credentials"):e>=400&&e<500?new Error("Authentication request was rejected"):new Error("Authentication service unavailable, please try again later")}function Ns(e){return new Promise((t,n)=>{try{Is(e.tenant);}catch(l){return n(l)}let s=JSON.stringify({clientSecret:e.clientSecret}),o=new URL(Ls.AUTH_ENGINE),r=o.protocol==="https:",i={hostname:o.hostname,port:o.port||(r?443:80),path:o.pathname+o.search,method:"POST",headers:{"Content-Type":"application/json","Content-Length":Buffer.byteLength(s),FlowTenant:e.tenant}},p=(r?ys:Os).request(i,l=>{let u=[];l.on("data",h=>u.push(h)),l.on("end",()=>{let h=u.join("");if(l.statusCode>=400)return n(ks(l.statusCode,h));let E;try{E=JSON.parse(h);}catch{return n(new Error("Invalid response from auth engine"))}let m=E.expires_at??new Date(Date.now()+(E.expires_in??3600)*1e3).toISOString();t({accessToken:E.access_token,expiresAt:m});});});p.on("error",l=>n(new Error(`Network error: ${l.message}`))),p.write(s),p.end();})}ft.exports={getToken:Ns};});var Ne=d((aa,Et)=>{var y=c("fs"),fe=c("path"),ht=c("os"),{FLOW_URLS:Cs,INSTALLER:H,HOOK_TIMEOUTS:pe,NATIVE_OTEL:bs}=w(),{getToken:Us}=W();function St(){return process.platform==="win32"?fe.join(process.env.APPDATA||fe.join(ht.homedir(),"AppData","Roaming"),"Claude","settings.json"):fe.join(ht.homedir(),".claude","settings.json")}var Rs=H.LOCK_STALE_MS;function vs(e){try{let t=y.statSync(e);Date.now()-t.mtimeMs>Rs&&y.unlinkSync(e);}catch{}try{y.writeFileSync(e,String(process.pid),{flag:"wx"});}catch(t){throw t.code==="EEXIST"?new Error("Another install is already in progress. If this is stale, remove: "+e):t}}function Ps(e){try{y.unlinkSync(e);}catch{}}function $s(){let e=St(),t=e+".lock";y.mkdirSync(fe.dirname(e),{recursive:true}),vs(t);try{y.existsSync(e)&&y.copyFileSync(e,e+".bkp");let n={};try{n=JSON.parse(y.readFileSync(e,"utf8"));}catch{}(!n.env||typeof n.env!="object")&&(n.env={}),Object.assign(n.env,bs),n.otelHeadersHelper="flow-ai-obs otel-headers",(!n.hooks||typeof n.hooks!="object")&&(n.hooks={});let s=(o,r)=>({matcher:"",hooks:[{type:"command",command:`flow-ai-obs ${o}`,timeout:r}]});n.hooks.UserPromptSubmit=[s("UserPromptSubmit",pe.UserPromptSubmit)],n.hooks.PreToolUse=[s("PreToolUse",pe.PreToolUse)],n.hooks.PostToolUse=[s("PostToolUse",pe.PostToolUse)],n.hooks.Stop=[s("Stop",pe.Stop)],y.writeFileSync(e,JSON.stringify(n,null,2)+`
12
+ `,"utf8");}finally{Ps(t);}return e}async function qs(e){let t;try{t=(await Us(e)).accessToken;}catch{return false}return t?new Promise(n=>{ke(`Bearer ${t}`,n,1);}):false}function ke(e,t,n){let s=new URL(Cs.GATEWAY_TRACES),o=JSON.stringify({resourceSpans:[],validation_run:true}),r=s.protocol==="https:",i={hostname:s.hostname,port:s.port||(r?443:80),path:s.pathname,method:"POST",headers:{"Content-Type":"application/json",Authorization:e,"Content-Length":Buffer.byteLength(o)}},p=(r?c("https"):c("http")).request(i,l=>{l.resume(),l.statusCode===200||l.statusCode===204?t(true):n<H.CONNECTIVITY_MAX_RETRY?setTimeout(()=>ke(e,t,n+1),H.CONNECTIVITY_RETRY_MS):t(false);});p.on("error",()=>{n<H.CONNECTIVITY_MAX_RETRY?setTimeout(()=>ke(e,t,n+1),H.CONNECTIVITY_RETRY_MS):t(false);}),p.write(o),p.end();}Et.exports={getClaudeSettingsPath:St,writeClaudeSettings:$s,validateConnectivity:qs};});var gt=d((ca,wt)=>{var J=c("fs"),v=c("path"),Fs=c("os"),{CONFIG_DIR_NAME:Ce,CONFIG_FILE_NAME:be}=w(),Ue={credentials:"credentials","token-cache":"tokenCache"};function K(){let e=Fs.homedir();if(process.platform==="darwin")return v.join(e,"Library","Preferences",Ce,be);if(process.platform==="win32"){let n=process.env.APPDATA||v.join(e,"AppData","Roaming");return v.join(n,Ce,be)}let t=process.env.XDG_CONFIG_HOME||v.join(e,".config");return v.join(t,Ce,be)}function Re(){try{let e=J.readFileSync(K(),"utf8");return JSON.parse(e)}catch{return {}}}function Tt(e){let t=K();J.mkdirSync(v.dirname(t),{recursive:true}),J.writeFileSync(t,JSON.stringify(e,null,2),{encoding:"utf8",mode:384});}var ve=class{static isAvailable(){return true}static hasExistingData(){try{if(!J.existsSync(K()))return !1;let t=JSON.parse(J.readFileSync(K(),"utf8"));return !!(t&&t.credentials)}catch{return false}}async getSecret(t,n){let s=Re(),o=Ue[n]||n,r=s[o];return r?typeof r=="string"?r:JSON.stringify(r):null}async setSecret(t,n,s){let o=Re(),r=Ue[n]||n;if(n==="token-cache")try{o[r]=JSON.parse(s);}catch{o[r]=s;}else o[r]=s;Tt(o);}async deleteSecret(t,n){let s=Re(),o=Ue[n]||n;return o in s?(delete s[o],Tt(s),true):false}getStoragePath(){return `config.json (unencrypted) \u2014 ${K()}`}};function xs(){}wt.exports={ScryptFallbackProvider:ve,_resetWarningState:xs};});var mt=d((ua,_t)=>{var{VAULT:z}=w();async function Ms(e,t){let n=[z.ACCOUNT_CREDS,z.ACCOUNT_TOKEN];for(let s of n)try{if(await t.getSecret(z.SERVICE,s)!==null)continue;let r=await e.getSecret(z.SERVICE,s);r!==null&&await t.setSecret(z.SERVICE,s,r);}catch{}}_t.exports={migrateFromJsonFallback:Ms};});var Lt=d((la,Ot)=>{var{execFile:Bs,exec:Ds}=c("child_process"),{promisify:yt}=c("util"),Pe=yt(Bs),Gs=yt(Ds),$e=class{static async isAvailable(){try{return await Gs("command -v security"),!0}catch{return false}}async getSecret(t,n){try{let{stdout:s}=await Pe("security",["find-generic-password","-w","-a",n,"-s",t]);return s.trim()||null}catch{return null}}async setSecret(t,n,s){await Pe("security",["add-generic-password","-U","-a",n,"-s",t,"-w",s]);}async deleteSecret(t,n){try{return await Pe("security",["delete-generic-password","-a",n,"-s",t]),!0}catch{return false}}getStoragePath(){return "macOS Keychain"}};Ot.exports={MacOsKeyVaultProvider:$e};});var Nt=d((da,kt)=>{var{execFile:js,exec:Vs,spawn:Ys}=c("child_process"),{promisify:It}=c("util"),At=It(js),Ws=It(Vs),qe=class{static async isAvailable(){try{return await Ws("command -v secret-tool"),!0}catch{return false}}async getSecret(t,n){try{let{stdout:s}=await At("secret-tool",["lookup","service",t,"account",n]);return s.trim()||null}catch{return null}}async setSecret(t,n,s){await new Promise((o,r)=>{let i=Ys("secret-tool",["store","--label",t,"service",t,"account",n]);i.on("error",r),i.on("close",a=>{a===0?o():r(new Error(`secret-tool exited with code ${a}`));}),i.stdin.write(s),i.stdin.end();});}async deleteSecret(t,n){try{return await At("secret-tool",["clear","service",t,"account",n]),!0}catch{return false}}getStoragePath(){return "Linux Secret Service (libsecret)"}};kt.exports={LinuxKeyVaultProvider:qe};});var bt=d((pa,Ct)=>{var Fe=class{static async isAvailable(){try{return await c("keytar").getPassword("__flow_probe__","__flow_probe__"),!0}catch{return false}}async getSecret(t,n){return c("keytar").getPassword(t,n)}async setSecret(t,n,s){await c("keytar").setPassword(t,n,s);}async deleteSecret(t,n){return c("keytar").deletePassword(t,n)}getStoragePath(){return "Windows Credential Manager"}};Ct.exports={WindowsKeyVaultProvider:Fe};});var Pt=d((fa,vt)=>{async function Rt(){let{ScryptFallbackProvider:e}=gt();if(e.hasExistingData()){let t=await Ut();if(t){let{migrateFromJsonFallback:n}=mt(),{VAULT:s}=w();if(await n(new e,t),await t.getSecret(s.SERVICE,s.ACCOUNT_CREDS)!==null)return t}return new e}return await Ut()??new e}async function Ut(){switch(process.platform){case "darwin":{let{MacOsKeyVaultProvider:e}=Lt();return await e.isAvailable()?new e:null}case "linux":{let{LinuxKeyVaultProvider:e}=Nt();return await e.isAvailable()?new e:null}case "win32":{let{WindowsKeyVaultProvider:e}=bt();return await e.isAvailable()?new e:null}default:return null}}var Hs=Rt;vt.exports={createSecretVaultProvider:Rt,createKeyVaultProvider:Hs};});var q=d((ha,qt)=>{var{createSecretVaultProvider:Ks}=Pt(),{VAULT:Me}=w(),P=Me.SERVICE,Be=Me.ACCOUNT_CREDS,De=Me.ACCOUNT_TOKEN,xe=null;function $(){return xe||(xe=Ks()),xe}async function $t(){let t=await(await $()).getSecret(P,Be);if(!t)return null;try{return JSON.parse(t)}catch{return null}}async function Js(e){if(!e.clientId||!e.clientSecret||!e.tenant)throw new Error("clientId, clientSecret and tenant are required");await(await $()).setSecret(P,Be,JSON.stringify(e));}async function zs(){let e=await $();await e.deleteSecret(P,Be),await e.deleteSecret(P,De);}async function Xs(e){await(await $()).setSecret(P,De,JSON.stringify({accessToken:e.accessToken,expiresAt:e.expiresAt}));}async function Ge(){let t=await(await $()).getSecret(P,De);if(!t)return null;try{let n=JSON.parse(t);return {accessToken:n.accessToken,expiresAt:n.expiresAt}}catch{return null}}async function Zs(){let e=await Ge();return e?new Date(e.expiresAt)>new Date:false}async function Qs(){let e=await Ge();if(!e||!e.accessToken)return null;try{let t=e.accessToken.split(".");if(t.length<2)return null;let n=JSON.parse(Buffer.from(t[1].replace(/-/g,"+").replace(/_/g,"/"),"base64").toString("utf8"));return n.email||n.preferred_username||n.upn||n.sub||null}catch{return null}}async function er(){let e=await $();if(typeof e.getStoragePath=="function")return e.getStoragePath();let t=process.platform;return t==="darwin"?"macOS Keychain":t==="win32"?"Windows Credential Manager":"Linux Secret Service (libsecret)"}async function tr(){let e=await $t();return !!(e&&e.clientId&&e.clientSecret&&e.tenant)}qt.exports={getFlowCredentials:$t,saveFlowCredentials:Js,clearFlowCredentials:zs,saveTokenCache:Xs,getTokenCache:Ge,isTokenValid:Zs,getEmailFromToken:Qs,getConfigPath:er,hasCredentials:tr};});var he=d((Sa,Ft)=>{var nr=!process.env.NO_COLOR&&process.stdout.isTTY;function F(e,t){return nr?n=>`\x1B[${e}m${n}\x1B[${t}m`:n=>n}var sr=F("32","39"),rr=F("31","39"),or=F("33","39"),ir=F("36","39"),ar=F("1","22"),cr=F("2","22");Ft.exports={green:sr,red:rr,yellow:or,cyan:ir,bold:ar,dim:cr};});var Bt=d((Ea,Mt)=>{var{writeClaudeSettings:ur}=Ne(),{hasCredentials:lr}=q(),{NATIVE_OTEL:dr}=w(),{green:pr,yellow:fr,bold:xt,dim:O,cyan:hr}=he();async function Sr(){let e=await lr(),t;try{t=ur();}catch(n){return process.stderr.write(` \u2717 Failed to write settings: ${n.message}
13
+ `),1}process.stdout.write(`
14
+ `+xt(" Native OTEL configured")+` (metrics & logs)
15
+ `);for(let[n,s]of Object.entries(dr))process.stdout.write(O(` ${n.padEnd(36)}`)+s+`
16
+ `);return process.stdout.write(`
17
+ `),process.stdout.write(xt(" Hooks registered")+` (Langfuse trace capture)
18
+ `),process.stdout.write(O(" UserPromptSubmit")+` \u2192 flow-ai-obs UserPromptSubmit (timeout: 5s)
19
+ `),process.stdout.write(O(" PreToolUse ")+` \u2192 flow-ai-obs PreToolUse (timeout: 3s)
20
+ `),process.stdout.write(O(" PostToolUse ")+` \u2192 flow-ai-obs PostToolUse (timeout: 10s)
21
+ `),process.stdout.write(O(" Stop ")+` \u2192 flow-ai-obs Stop (timeout: 15s)
22
+ `),process.stdout.write(`
23
+ `),process.stdout.write(O(" Settings: ")+t+`
24
+ `),process.stdout.write(O(" Backup: ")+t+`.bkp
25
+ `),process.stdout.write(O(" OTEL auth: flow-ai-obs otel-headers")+O(` (reads keychain at runtime)
26
+ `)),process.stdout.write(`
27
+ `),e?process.stdout.write(pr(` \u2713 Ready. Native OTEL + Langfuse trace capture active.
28
+
29
+ `)):process.stdout.write(fr(` \u26A0 Not authenticated \u2014 hooks registered, but trace forwarding is inactive.
30
+ `)+" Run "+hr("flow-ai-obs auth login")+` to activate Langfuse tracing.
31
+
32
+ `),0}Mt.exports={runInstall:Sr};});var Ht=d((Ta,Wt)=>{var{getFlowCredentials:Dt,saveFlowCredentials:Er,clearFlowCredentials:Tr,saveTokenCache:wr,isTokenValid:gr,getConfigPath:Gt,hasCredentials:jt}=q(),{getToken:_r}=W(),{green:Ee,red:X,yellow:Se,cyan:Z,bold:Vt,dim:x}=he(),mr=/[\x00-\x08\x0B-\x1F\x7F]|\x1b\[[0-9;]*[a-zA-Z]/g;function I(e){return typeof e=="string"?e.replace(mr,""):e}function Q(e,t={}){return new Promise((n,s)=>{let{masked:o=false,defaultValue:r=""}=t;process.stdout.write(e+r);let i=r;function a(){try{process.stdin.setRawMode(!1);}catch{}}process.once("uncaughtException",a),process.once("unhandledRejection",a);let p=u=>{if(u===""){l(),s(new Error("SIGINT"));return}if(u==="\r"||u===`
33
+ `){l(),process.stdout.write(`
34
+ `),n(i);return}if(u==="\x7F"){i.length>0&&(i=i.slice(0,-1),process.stdout.write("\b \b"));return}u.startsWith("\x1B")||(i+=u,process.stdout.write(o?"*".repeat(u.length):u));};function l(){process.stdin.setRawMode(false),process.stdin.removeListener("data",p),process.removeListener("uncaughtException",a),process.removeListener("unhandledRejection",a);}process.stdin.setRawMode(true),process.stdin.resume(),process.stdin.setEncoding("utf8"),process.stdin.on("data",p);})}async function Yt(e){let{accessToken:t,expiresAt:n}=await _r(e);await Er(e),await wr({accessToken:t,expiresAt:n});}async function yr(e){try{return await Yt(e),process.stdout.write(Ee(` \u2713 Setup complete. Tenant: ${I(e.tenant)}
35
+
36
+ `)),0}catch(t){return process.stderr.write(X(` \u2717 Authentication failed: ${I(t.message)}
37
+ `)),1}}async function Or(){try{process.stdout.write(`
38
+ `);let e=(await Q(Z(" ? ")+"Client ID: ")).trim(),t=(await Q(Z(" ? ")+"Client Secret: ",{masked:!0})).trim(),n=(await Q(Z(" ? ")+"Tenant: ")).trim();if(!e||!t||!n)return process.stderr.write(X(` \u2717 All fields are required
39
+ `)),1;try{await Yt({clientId:e,clientSecret:t,tenant:n});}catch(s){return process.stderr.write(X(` \u2717 Authentication failed: ${I(s.message)}
40
+ `)),1}return process.stdout.write(Ee(` \u2713 Setup complete. Tenant: ${I(n)}
41
+ `)),process.stdout.write(` Storage: ${await Gt()}
42
+
43
+ `),0}catch(e){if(e.message==="SIGINT")throw e;return process.stderr.write(X(` \u2717 Unexpected error: ${e.message}
44
+ `)),1}}async function Lr(e={}){if(e.clientId&&e.clientSecret&&e.tenant)return yr({clientId:e.clientId,clientSecret:e.clientSecret,tenant:e.tenant});if(await jt()){let t=await Dt();process.stdout.write(`
45
+ `),process.stdout.write(Vt(` Already authenticated
46
+ `)),process.stdout.write(x(" Tenant: ")+I(t.tenant)+`
47
+ `),process.stdout.write(x(" Client ID: ")+I(t.clientId)+`
48
+ `),process.stdout.write(`
49
+ `);let n;try{n=await Q(Z(" ? ")+"Re-authenticate? (y/N): ");}catch(s){if(s.message==="SIGINT")return process.stdout.write(`
50
+ `),130;throw s}if(n.trim().toLowerCase()!=="y")return process.stdout.write(Ee(` \u2713 Using existing credentials.
51
+
52
+ `)),0}try{return await Or()}catch(t){if(t.message==="SIGINT")return process.stdout.write(`
53
+ `),130;throw t}}async function Ar(){try{return process.stdout.write(`
54
+ `),(await Q(Z(" ? ")+"Are you sure? This will remove your local credentials. (y/N): ")).toLowerCase()!=="y"?(process.stdout.write(Se(` \u26A0 Logout cancelled
55
+ `)),0):null}catch(e){if(e.message==="SIGINT")return process.stdout.write(`
56
+ `),130;throw e}}async function Ir(e=false){if(!await jt())return process.stdout.write(Se(` \u26A0 You are not authenticated
57
+ `)),0;if(!e){let t=await Ar();if(t!==null)return t}try{return await Tr(),process.stdout.write(Ee(` \u2713 Credentials removed successfully
58
+ `)),0}catch{return process.stderr.write(X(` \u2717 Error removing credentials
59
+ `)),1}}async function kr(){let e=await Dt();if(!e)return process.stdout.write(Se(" \u26A0 Not authenticated. Run `flow-ai-obs auth login` to set up.\n")),0;if(!await gr())return process.stdout.write(Se(" \u26A0 Session expired. Run `flow-ai-obs auth login` to re-authenticate.\n")),0;let t=e.clientSecret.length>4?"****...****"+e.clientSecret.slice(-4):"****";return process.stdout.write(`
60
+ `+Vt(` Authenticated
61
+ `)),process.stdout.write(x(" Tenant: ")+I(e.tenant)+`
62
+ `),process.stdout.write(x(" Client ID: ")+I(e.clientId)+`
63
+ `),process.stdout.write(x(" Client Secret: ")+t+`
64
+ `),process.stdout.write(x(" Storage: ")+await Gt()+`
65
+
66
+ `),0}Wt.exports={runLogin:Lr,runLogout:Ir,runStatus:kr};});var Jt=d((wa,Kt)=>{var{getFlowCredentials:Nr}=q(),{getToken:Cr}=W();async function br(){let e;try{e=await Nr();}catch{return process.stderr.write(`[flow-ai-obs otel-headers] Failed to read credentials
67
+ `),1}if(!e)return process.stderr.write(`[flow-ai-obs otel-headers] Not authenticated \u2014 run: flow-ai-obs auth login
68
+ `),1;let t;try{t=(await Cr(e)).accessToken;}catch(n){return process.stderr.write(`[flow-ai-obs otel-headers] Token fetch failed: ${n.message}
69
+ `),1}return t?(process.stdout.write(`{"Authorization": "Bearer ${t}"}
70
+ `),0):(process.stderr.write(`[flow-ai-obs otel-headers] Empty token received
71
+ `),1)}Kt.exports={runOtelHeaders:br};});var k=d((ga,Xt)=>{var{writeFileSync:Ur}=c("fs"),{tmpdir:Rr}=c("os"),{join:vr}=c("path"),{DEBUG_LOG_FILENAME:Pr,ENV:$r}=w(),zt=vr(Rr(),Pr);function qr(...e){if(process.env[$r.FLOW_OBS_DEBUG]!=="1")return;let t=`[${new Date().toISOString()}] ${e.join(" ")}
72
+ `;try{Ur(zt,t,{flag:"a"});}catch(n){process.stderr.write(`[flow-obs debug ERROR] ${n.message}
73
+ `);}}Xt.exports={dbg:qr,DEBUG_LOG:zt};});var ee=d((_a,Qt)=>{var{getFlowCredentials:je,isTokenValid:Fr,getEmailFromToken:Ve,getTokenCache:xr,saveTokenCache:Mr}=q(),{getToken:Br}=W(),{FLOW_BASE_URL:Dr,ENV:b}=w(),{dbg:Te}=k(),Gr=[/^localhost\.?$/i,/^127\./,/^10\./,/^172\.(1[6-9]|2\d|3[01])\./,/^192\.168\./,/^169\.254\./,/^0\.0\.0\.0$/,/^::1$/,/^\[::1\]$/,/^\[::ffff:/i,/^\[f[cd]/i,/^\[fe80:/i,/^metadata\.google\.internal\.?$/i,/^metadata\.azure\.internal\.?$/i,/(?:^|\.)localtest\.me\.?$/i,/(?:^|\.)lvh\.me\.?$/i,/(?:^|\.)vcap\.me\.?$/i];function jr(e){return Gr.some(t=>t.test(e))}function Ye(e){let t;try{t=new URL(e);}catch{throw new Error(`Invalid URL: ${e}`)}if(!(process.env[b.FLOW_OBS_ALLOW_HTTP]==="true")&&t.protocol!=="https:")throw new Error(`URL must use HTTPS: ${e}`);if(t.username||t.password)throw new Error(`URL must not contain embedded credentials: ${e}`);if(jr(t.hostname))throw new Error(`SSRF: private/loopback host not allowed: ${t.hostname}`);return e}async function Vr(){try{let e=await je();if(!e)return {user:"",tenant:""};let t=await Ve()||e.clientId||"",n=e.tenant||"";return {user:t,tenant:n}}catch{return {user:"",tenant:""}}}async function Yr(e){if(await Fr())return (await xr()).accessToken;Te("getOrRefreshToken","token expired \u2014 refreshing via auth-engine");let t=await Br(e);return await Mr(t),Te("getOrRefreshToken",`token refreshed, expires=${t.expiresAt}`),t.accessToken}function Wr(e,t){let n=t&&t.accessToken?t.accessToken:"";return {endpoint:e.replace(/\/$/,"")+"/langfuse/traces",authHeader:`Bearer ${n}`}}function Hr(e,t,n){let s=Buffer.from(`${t}:${n}`).toString("base64");return {endpoint:e.replace(/\/$/,"")+"/api/public/otel/v1/traces",authHeader:`Basic ${s}`}}async function Zt(){let e=[],t=process.env[b.ANTHROPIC_MODEL]||"",n=process.env[b.FLOW_TELEMETRY]==="true",s=process.env[b.LANGFUSE_BASE_URL],o=process.env[b.LANGFUSE_PUBLIC_KEY],r=process.env[b.LANGFUSE_SECRET_KEY];if(n&&s&&o&&r){let u="",h="";try{let m=await je();m&&(u=await Ve()||m.clientId,h=m.tenant);}catch{}let E={user:u||"",tenant:h||"",team:"",project:"",model:t};e.push({mode:"direct",...Hr(Ye(s),o,r),...E}),Te("resolveCredentials","direct mode active",`user=${E.user}`,`tenant=${E.tenant}`);}let i=await je();if(!i){if(e.length===0){let u=[];return u.authError=false,u}return e}let a;try{a=await Yr(i);}catch(u){if(Te("resolveCredentials",`token refresh failed: ${u.message}`),e.length>0)return e;let h=[];return h.authError=true,h}let p={user:await Ve()||i.clientId,tenant:i.tenant,team:"",project:"",model:t},l=process.env[b.FLOW_TELEMETRY_GATEWAY]||Dr;return e.push({mode:"gateway",...Wr(Ye(l),{accessToken:a}),...p}),e}async function Kr(){return (await Zt()).length>0}Qt.exports={credentialsAvailable:Kr,getIdentityFromToken:Vr,resolveCredentials:Zt,validateUrl:Ye};});var tn=d((ma,en)=>{var{getFlowCredentials:Jr,isTokenValid:zr,getEmailFromToken:Xr,getTokenCache:Zr}=q(),{resolveCredentials:Qr}=ee(),{getClaudeSettingsPath:eo}=Ne(),{ENV:M,NATIVE_OTEL:to}=w(),no=c("fs");function We(e){return !e||e.length<=8?"****":e.slice(0,4)+"****...****"+e.slice(-4)}function te(e){process.stdout.write(` \u2713 ${e}
74
+ `);}function He(e){process.stdout.write(` \u26A0 ${e}
75
+ `);}function we(e){process.stdout.write(` \u2717 ${e}
76
+ `);}function g(e){process.stdout.write(` ${e}
77
+ `);}function L(){process.stdout.write(`
78
+ `);}async function so(){L(),process.stdout.write(` flow-claude-observability \u2014 configuration check
79
+ `),L(),process.stdout.write(` Auth
80
+ `);let e=await Jr();if(!e)return we("Not authenticated. Run: flow-claude-observability auth login"),L(),1;if(!await zr())return He("Session expired. Run: flow-claude-observability auth login"),g(`Tenant: ${e.tenant}`),g(`Client ID: ${e.clientId}`),L(),1;let t=await Xr()||e.clientId;te("Authenticated"),g(`Tenant: ${e.tenant}`),g(`User: ${t}`),L(),process.stdout.write(` Native OTEL
81
+ `);let n={};try{let i=no.readFileSync(eo(),"utf8");n=JSON.parse(i).env||{};}catch{}let s=Object.keys(to).filter(i=>!n[i]);if(s.length===0?(te("All OTEL env vars present in settings.json"),g(`Endpoint: ${n.OTEL_EXPORTER_OTLP_ENDPOINT}`)):(He(`${s.length} OTEL var(s) missing from settings.json \u2014 native metrics/logs disabled`),s.forEach(i=>we(`Missing: ${i}`)),g("Run: flow-ai-obs install (restores missing vars automatically)")),L(),process.stdout.write(` Tracing
82
+ `),process.env[M.FLOW_TELEMETRY]==="true"){te("FLOW_TELEMETRY=true (direct mode enabled)");let i=[M.LANGFUSE_BASE_URL,M.LANGFUSE_PUBLIC_KEY,M.LANGFUSE_SECRET_KEY].filter(a=>!process.env[a]);i.length>0&&i.forEach(a=>He(`Missing: ${a}`));}else g("FLOW_TELEMETRY not set \u2014 direct mode inactive (gateway mode may still be active)");let r=await Qr();if(!r||r.length===0)return r&&r.authError?we("Token refresh failed \u2014 gateway mode could not activate"):(we("No active destinations \u2014 no traces will be sent"),g("For direct mode: set FLOW_TELEMETRY=true + LANGFUSE_BASE_URL + LANGFUSE_PUBLIC_KEY + LANGFUSE_SECRET_KEY"),g("For gateway mode: run flow-claude-observability auth login")),L(),1;for(let i of r)if(i.mode==="gateway"){let a=await Zr();te("Mode: gateway"),g(`Endpoint: ${i.endpoint}`),g(`Token: ${We(a&&a.accessToken)}`);}else i.mode==="direct"&&(te("Mode: direct"),g(`Endpoint: ${i.endpoint}`),g(`PK: ${We(process.env[M.LANGFUSE_PUBLIC_KEY])}`),g(`SK: ${We(process.env[M.LANGFUSE_SECRET_KEY])}`));return L(),process.stdout.write(` All checks passed \u2014 observability is active.
83
+ `),L(),0}en.exports={runCheck:so};});var B=d((ya,nn)=>{function ro(e,t){let n=typeof e=="object"?JSON.stringify(e):String(e??"");return n.length>t?n.slice(0,t)+" \u2026[truncated]":n}function oo(e,t=100){return String(e??"").split(`
84
+ `)[0].slice(0,t).trim()}function io(){return new Promise(e=>{let t="";process.stdin.setEncoding("utf8"),process.stdin.on("data",n=>t+=n),process.stdin.on("end",()=>{try{e(JSON.parse(t));}catch{e({});}});})}function ao(e){process.stdout.write(JSON.stringify(e));}nn.exports={truncate:ro,firstLine:oo,readInput:io,passthrough:ao};});var ne=d((Oa,rn)=>{var{randomBytes:sn}=c("crypto"),co=()=>sn(16).toString("hex"),uo=()=>sn(8).toString("hex");function lo(){return String(BigInt(Date.now())*1000000n)}rn.exports={generateTraceId:co,generateSpanId:uo,nowNs:lo};});var se=d((Aa,an)=>{var{readFileSync:po,writeFileSync:fo,unlinkSync:ho,existsSync:So,realpathSync:La}=c("fs"),{tmpdir:on}=c("os"),{join:Eo,resolve:To,normalize:wo,sep:go}=c("path"),_o=/^[0-9a-f-]{1,64}$/i,mo=wo(on())+go;function yo(e,t){let n=To(e);if(!n.startsWith(t))throw new Error(`Path traversal detected: ${e}`);return n}function Ke(e){if(!_o.test(e))throw new Error(`Invalid session ID: ${e}`);return yo(Eo(on(),`flow-obs-${e}.json`),mo)}function Oo(e){let t=Ke(e);if(!So(t))return null;try{return JSON.parse(po(t,"utf8"))}catch{return null}}function Lo(e,t){fo(Ke(e),JSON.stringify(t),"utf8");}function Ao(e){try{ho(Ke(e));}catch{}}an.exports={loadState:Oo,saveState:Lo,deleteState:Ao};});var Je=d((Ia,ln)=>{var{readFileSync:cn,existsSync:un}=c("fs");function Io(e){if(!e||!un(e))return null;try{let t=cn(e,"utf8").trimEnd().split(`
85
+ `);for(let n=t.length-1;n>=0;n--)try{let s=JSON.parse(t[n]);if(s.type==="user"&&s.uuid)return s.uuid}catch{}}catch{}return null}function ko(e){if(!e||!un(e))return null;try{let t=cn(e,"utf8").trimEnd().split(`
86
+ `),n=[],s=0,o=0,r=!1;for(let i=t.length-1;i>=0;i--)try{let a=JSON.parse(t[i]);if(a.type==="user"){if(r)break;continue}if(a.type==="assistant"){r=!0;let l=(a.message?.content??[]).filter(h=>h.type==="text").map(h=>h.text).join("");l&&n.unshift(l);let u=a.message?.usage??{};s+=(u.input_tokens??0)+(u.cache_read_input_tokens??0)+(u.cache_creation_input_tokens??0),o+=u.output_tokens??0;}}catch{}return !n.length&&s===0?null:{text:n.join(`
87
+
88
+ `),inputTokens:s,outputTokens:o}}catch{}return null}ln.exports={readPromptUuid:Io,readLastAssistantOutput:ko};});var ge=d((ka,hn)=>{var{readFileSync:No,existsSync:dn,readdirSync:Co}=c("fs"),{homedir:pn}=c("os"),{join:D,extname:bo}=c("path"),Uo=new Set([".venv","__pycache__",".git","node_modules"]),Ro=new Set([".pyc",".pyo"]),vo=new Set(["SKILL.md",".DS_Store",".gitignore"]);function Po(e,t){if(!e)return null;let n=[D(pn(),".claude","skills",e,"SKILL.md"),D(t||"",".claude","skills",e,"SKILL.md")];for(let s of n)if(dn(s))try{return No(s,"utf8")}catch{}return null}function $o(e){return e.match(/^\/([a-z][a-z0-9-]*)(?:\s.*)?$/i)?.[1]||null}var fn=new Set(["Read","Write","Edit","MultiEdit","Bash","Glob","Grep","TodoWrite","WebFetch","WebSearch","Agent","Task"]);function qo(e){return !!e.slashSkill||e.toolCalls.some(s=>s.tool==="Skill")?e.toolCalls.some(s=>fn.has(s.tool))?"complete":"prerequisite_required":null}function Fo(e,t){if(!e)return null;let n=[D(pn(),".claude","skills",e),D(t||"",".claude","skills",e)],s=null;for(let i of n)if(dn(D(i,"SKILL.md"))){s=i;break}if(!s)return null;let o=[];function r(i,a){for(let p of Co(i,{withFileTypes:true})){if(Uo.has(p.name)||vo.has(p.name)||Ro.has(bo(p.name)))continue;let l=a?`${a}/${p.name}`:p.name;p.isDirectory()?r(D(i,p.name),l):o.push(l);}}return r(s,""),o.length>0?o:null}hn.exports={readSkillContent:Po,readSkillArtifacts:Fo,detectSlashSkill:$o,EXECUTION_TOOLS:fn,detectExecutionStatus:qo};});var Tn=d((Na,En)=>{var{generateTraceId:Sn,generateSpanId:xo,nowNs:Mo}=ne(),{saveState:Bo}=se(),{readPromptUuid:Do}=Je(),{detectSlashSkill:Go,readSkillContent:jo,readSkillArtifacts:Vo}=ge(),{passthrough:Yo}=B(),{dbg:Wo}=k(),{ENV:Ho}=w();async function Ko(e){let t=e.session_id||"unknown",n=e.prompt||"",s=e.cwd||"",o=Do(e.transcript_path)||Sn(),r=Go(n),i=r?jo(r,s):null,a=r?Vo(r,s):null;Bo(t,{traceId:Sn(),spanId:xo(),startNs:Mo(),sessionId:t,prompt:n,cwd:s,traceName:o,transcriptPath:e.transcript_path||"",model:process.env[Ho.ANTHROPIC_MODEL]||"",toolCalls:[],spans:[],pendingTool:null,slashSkill:r,skillContent:i,skillArtifacts:a}),Wo("UserPromptSubmit",`sid=${t}`,`traceName=${o}`,`slashSkill=${r}`),Yo(e);}En.exports={onUserPromptSubmit:Ko};});var gn=d((Ca,wn)=>{var{nowNs:Jo}=ne(),{loadState:zo,saveState:Xo}=se(),{truncate:Zo,passthrough:Qo}=B(),{dbg:ei}=k(),{TRACE_LIMITS:ti}=w();async function ni(e){let t=e.session_id||"unknown",n=e.tool_name||"unknown",s=e.tool_input||{},o=zo(t);o&&(o.pendingTool={name:n,inputStr:Zo(s,ti.TOOL_INPUT),startNs:Jo()},Xo(t,o)),ei("PreToolUse",`sid=${t}`,`tool=${n}`,`stateFound=${!!o}`),Qo(e);}wn.exports={onPreToolUse:ni};});var yn=d((ba,mn)=>{var si=c("https"),ri=c("http"),{FLOW_URLS:oi}=w();function ii(e){let t=new URL(e),n=new URL(oi.GATEWAY_HEALTH);return n.hostname=t.hostname,n.port=t.port,n.protocol=t.protocol,n}function _n(e,t){return new Promise((n,s)=>{let r=(e._isSSL?si:ri).request(e,i=>{let a=[];i.on("data",p=>a.push(p)),i.on("end",()=>n({statusCode:i.statusCode,body:a.join("")}));});r.on("error",i=>s(new Error(`Network error: ${i.message}`))),t&&r.write(t),r.end();})}async function ai(e){let t;try{t=ii(e);}catch{return false}let n=t.protocol==="https:";try{let s=await _n({hostname:t.hostname,port:t.port||(n?443:80),path:t.pathname,method:"GET",_isSSL:n},null);return s.statusCode>=200&&s.statusCode<300}catch{return false}}async function ci(e,t,n){let s=JSON.stringify(e),o=new URL(t),r=o.protocol==="https:",i=String(n).replace(/[\r\n]/g,"");try{return (await _n({hostname:o.hostname,port:o.port||(r?443:80),path:o.pathname,method:"POST",headers:{"Content-Type":"application/json",Authorization:i,"Content-Length":Buffer.byteLength(s)},_isSSL:r},s)).statusCode}catch{return 0}}mn.exports={checkHealth:ai,sendTraces:ci};});var In=d((Ua,An)=>{var N=c("fs"),ze=c("path"),On=c("os"),{BUFFER:re}=w(),{dbg:oe}=k();function Ln(e){if(/[/\\]/.test(e))throw new Error(`Invalid buffer fileId: ${e}`);return ze.join(On.tmpdir(),`${re.FILE_PREFIX}${e}.json`)}function Xe(){let e=On.tmpdir(),t;try{t=N.readdirSync(e);}catch{return []}return t.filter(n=>n.startsWith(re.FILE_PREFIX)&&n.endsWith(".json")).map(n=>{let s=ze.join(e,n),o=0;try{o=N.statSync(s).mtimeMs;}catch{}return {full:s,mtime:o}}).sort((n,s)=>n.mtime-s.mtime).map(n=>n.full)}function ui(e){try{let t=Xe();if(t.length>=re.MAX_FILES){let o=t.slice(0,t.length-re.MAX_FILES+1);for(let r of o)try{N.unlinkSync(r);}catch{}}let n=`${Date.now()}-${Math.random().toString(36).slice(2,8)}`,s={fileId:n,bufferedAt:Date.now(),payload:e};return N.writeFileSync(Ln(n),JSON.stringify(s),"utf8"),oe("buffer",`saved fileId=${n}`),n}catch(t){return oe("buffer",`saveToBuffer failed: ${t.message}`),null}}function li(){let e=Xe(),t=[];for(let n of e)try{let s=N.readFileSync(n,"utf8"),o=JSON.parse(s);o&&o.fileId&&o.payload&&t.push(o);}catch{oe("buffer",`skipping malformed file: ${n}`);}return t}function di(e){try{N.unlinkSync(Ln(e)),oe("buffer",`deleted fileId=${e}`);}catch{}}function pi(){let e=Xe(),t=Date.now()-re.MAX_AGE_MS;for(let n of e)try{let{mtimeMs:s}=N.statSync(n);s<t&&(N.unlinkSync(n),oe("buffer",`evicted stale: ${ze.basename(n)}`));}catch{}}An.exports={saveToBuffer:ui,loadBuffer:li,deleteBuffer:di,cleanStaleBuffers:pi};});var Ze=d((Ra,bn)=>{var{resolveCredentials:fi}=ee(),{OTLP:kn}=w(),{dbg:G}=k(),{checkHealth:hi,sendTraces:Cn}=yn(),{saveToBuffer:Nn,loadBuffer:Si,deleteBuffer:Ei,cleanStaleBuffers:Ti}=In(),wi={name:kn.SCOPE_NAME,version:kn.SCOPE_VERSION},gi={TRACE_NAME:"langfuse.trace.name",TRACE_INPUT:"langfuse.trace.input",TRACE_OUTPUT:"langfuse.trace.output",TRACE_METADATA:"langfuse.trace.metadata",TRACE_TAGS:"langfuse.trace.tags",USER_ID:"langfuse.user.id",SESSION_ID:"langfuse.session.id",OBS_NAME:"langfuse.observation.name",OBS_INPUT:"langfuse.observation.input",OBS_OUTPUT:"langfuse.observation.output",OBS_LEVEL:"langfuse.observation.level",OBS_TYPE:"langfuse.observation.type",MODEL:"gen_ai.request.model",INPUT_TOKENS:"gen_ai.usage.input_tokens",OUTPUT_TOKENS:"gen_ai.usage.output_tokens"};function _i(e,t){return t==null||t===""?null:{key:e,value:{stringValue:String(t)}}}function mi(...e){return e.filter(Boolean)}function yi(e,t){return {resourceSpans:[{resource:{attributes:e},scopeSpans:[{scope:wi,spans:Array.isArray(t)?t:[t]}]}]}}function Oi(e,t){return new Promise(n=>{let s=JSON.stringify(e),o=new URL(t.endpoint),r=o.protocol==="https:",i=u=>String(u).replace(/[\r\n]/g,""),a={hostname:o.hostname,port:o.port||(r?443:80),path:o.pathname,method:"POST",headers:{"Content-Type":"application/json",Authorization:i(t.authHeader),"Content-Length":Buffer.byteLength(s)}},l=(r?c("https"):c("http")).request(a,u=>{u.resume(),n(u.statusCode);});l.on("error",()=>n(0)),l.write(s),l.end();})}async function Li(e){Ti();let t=Si();if(t.length){G("replayBufferedTraces",`replaying ${t.length} buffered trace(s)`);for(let n of t)try{let s=await Cn(n.payload,e.endpoint,e.authHeader);s>=200&&s<300?(Ei(n.fileId),G("replayBufferedTraces",`replayed fileId=${n.fileId} status=${s}`)):G("replayBufferedTraces",`replay failed fileId=${n.fileId} status=${s}`);}catch(s){G("replayBufferedTraces",`replay error fileId=${n.fileId}: ${s.message}`);}}}async function Ai(e){let t=await fi();return t.length?(await Promise.all(t.map(async s=>s.mode==="gateway"?await hi(s.endpoint)?(await Li(s),Cn(e,s.endpoint,s.authHeader)):(G("postToLangfuse","gateway health check failed \u2014 buffering trace"),Nn(e),0):Oi(e,s)))).find(s=>s!==0)||0:(t.authError&&(G("postToLangfuse","auth refresh failed \u2014 buffering trace"),Nn(e)),0)}bn.exports={LF:gi,a:_i,attrs:mi,buildPayload:yi,postToLangfuse:Ai};});var Qe=d((va,Un)=>{var _="[REDACTED]",Ii=[{re:/\b(sk-[A-Za-z0-9_\-]{20,})/g,replace:()=>`sk-${_}`},{re:/\b(pk-[A-Za-z0-9_\-]{20,})/g,replace:()=>`pk-${_}`},{re:/\b(Bearer\s+)[A-Za-z0-9\-._~+/]+=*/gi,replace:(e,t)=>`${t}${_}`},{re:/\b(Basic\s+)[A-Za-z0-9+/]+=*/gi,replace:(e,t)=>`${t}${_}`},{re:/(-----BEGIN [A-Z ]+-----)[^-]*(-----END [A-Z ]+-----)/g,replace:(e,t,n)=>`${t}
89
+ ${_}
90
+ ${n}`},{re:/\b(AKIA[0-9A-Z]{16})\b/g,replace:()=>`AKIA${_}`},{re:/(aws_secret_access_key\s*[=:]\s*)([A-Za-z0-9/+]{40})/gi,replace:(e,t)=>`${t}${_}`},{re:/\b(ghp_[A-Za-z0-9]{36,})/g,replace:()=>`ghp_${_}`},{re:/\b(github_pat_[A-Za-z0-9_]{36,})/gi,replace:()=>`github_pat_${_}`},{re:/\b(eyJ[A-Za-z0-9_\-]+\.eyJ[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+)/g,replace:()=>`eyJ.${_}`},{re:/\b([A-Z0-9_]*(?:SECRET|TOKEN|KEY|PASSWORD|PASSWD|PWD|CREDENTIAL|API)[A-Z0-9_]*\s*=\s*)(['"]?)([^\s'"#\n]+)\2/gi,replace:(e,t,n)=>`${t}${n}${_}${n}`},{re:/([a-z][a-z0-9+\-.]*:\/\/[^:/?#\s]*):([^@\s]+)@/gi,replace:(e,t)=>`${t}:${_}@`}];function ki(e){if(e==null)return "";let t=typeof e=="string"?e:JSON.stringify(e);return Ii.reduce((n,{re:s,replace:o})=>n.replace(s,o),t)}Un.exports={sanitize:ki,REDACTED:_};});var xn=d((Pa,Fn)=>{var{generateSpanId:Ni,nowNs:Ci}=ne(),{loadState:bi,saveState:Ui}=se(),{LF:ie,a:U,attrs:Ri}=Ze(),{truncate:Rn,firstLine:vi,passthrough:vn}=B(),{readSkillContent:Pi,readSkillArtifacts:$i}=ge(),{dbg:Pn}=k(),{TRACE_LIMITS:$n}=w(),{sanitize:qn}=Qe();async function qi(e){let t=e.session_id||"unknown",n=e.tool_name||"unknown",s=e.tool_input||{},o=e.tool_response||"",r=bi(t);if(!r){vn(e);return}let i=Ci(),a=r.pendingTool?.name===n,p=a?r.pendingTool.startNs:String(BigInt(i)-200000000n),l=a?r.pendingTool.inputStr:Rn(s,$n.TOOL_INPUT),u=l;if(n==="Skill"){let C=typeof s=="object"&&s!==null?s.skill||s.name||s.skillName||String(Object.values(s)[0]??""):String(s),A=C?Pi(C.trim(),r.cwd):null;A&&(u=A,r.skillContent||(r.skillContent=A),r.slashSkill||(r.slashSkill=C.trim()),r.skillArtifacts||(r.skillArtifacts=$i(C.trim(),r.cwd))),Pn("PostToolUse","Skill tool detected",`skillName=${C}`,`contentFound=${!!A}`);}let h=qn(Rn(o,$n.TOOL_OUTPUT)),E=/\b(error|exception|failed|failure|traceback|errno)\b/i.test(h.slice(0,500)),m=E?"ERROR":"DEFAULT",j=Ni(),le=Math.round(Number(BigInt(i)-BigInt(p))/1e6),Oe={Agent:"AGENT",Task:"AGENT"}[n]??"TOOL";r.toolCalls.push({tool:n,brief:vi(l,60),error:E,durationMs:le}),r.spans.push({traceId:r.traceId,spanId:j,parentSpanId:r.spanId,name:n,kind:3,startTimeUnixNano:p,endTimeUnixNano:i,attributes:Ri(U(ie.OBS_NAME,n),U(ie.OBS_TYPE,Oe),U(ie.OBS_LEVEL,m),U(ie.OBS_INPUT,qn(u)),U(ie.OBS_OUTPUT,h),U("tool.name",n),U("tool.error",String(E))),status:{code:E?2:1}}),r.pendingTool=null,Ui(t,r),Pn("PostToolUse",`sid=${t}`,`tool=${n}`,`error=${E}`,`durationMs=${le}`,`totalSpans=${r.spans.length}`),vn(e);}Fn.exports={onPostToolUse:qi};});var Bn=d(($a,Mn)=>{var et={"claude-opus-4-6":{input:15,output:75},"claude-opus-4-5":{input:15,output:75},"claude-sonnet-4-6":{input:3,output:15},"claude-sonnet-4-5":{input:3,output:15},"claude-haiku-4-5":{input:.8,output:4},"claude-4-6-opus":{input:15,output:75},"claude-4-6-sonnet":{input:3,output:15},"claude-4-5-opus":{input:15,output:75},"claude-4-5-sonnet":{input:3,output:15},"claude-4-5-haiku":{input:.8,output:4},"claude-3-5-sonnet-20241022":{input:3,output:15},"claude-3-5-haiku-20241022":{input:.8,output:4},"claude-3-opus-20240229":{input:15,output:75}};function Fi(e,t,n){let s=Object.keys(et).find(i=>(e||"").toLowerCase().includes(i));if(!s)return;let o=et[s],r=(t*o.input+n*o.output)/1e6;return Math.round(r*1e6)/1e6}Mn.exports={MODEL_PRICING:et,calcCostUsd:Fi};});var Yn=d((qa,Vn)=>{var{generateSpanId:Dn,nowNs:xi}=ne(),{loadState:Mi,deleteState:Gn}=se(),{LF:T,a:f,attrs:_e,buildPayload:Bi,postToLangfuse:Di}=Ze(),{getIdentityFromToken:Gi}=ee(),{calcCostUsd:ji}=Bn(),{readLastAssistantOutput:Vi}=Je(),{truncate:me,firstLine:Yi,passthrough:tt}=B(),{dbg:ae}=k(),{detectExecutionStatus:Wi}=ge(),{sanitize:jn}=Qe(),{TRACE_LIMITS:ye}=w();async function Hi(e){let t=e.session_id||"unknown",n=e.stop_reason||"end_turn",s=Mi(t);if(!s){tt(e);return}let o=xi(),r=await Gi();if(s.slashSkill&&!s.toolCalls.some(S=>S.tool==="Skill")){let S=Dn(),V=s.skillContent?s.skillContent:JSON.stringify({skill:s.slashSkill});s.toolCalls.push({tool:"Skill",brief:s.slashSkill,error:false,durationMs:0,synthetic:true}),s.spans.push({traceId:s.traceId,spanId:S,parentSpanId:s.spanId,name:"Skill",kind:3,startTimeUnixNano:s.startNs,endTimeUnixNano:s.startNs,attributes:_e(f(T.OBS_NAME,"Skill"),f(T.OBS_TYPE,"TOOL"),f(T.OBS_LEVEL,"DEFAULT"),f(T.OBS_INPUT,V),f(T.OBS_OUTPUT,"loaded via slash command"),f("tool.name","Skill"),f("tool.error","false"),f("skill.name",s.slashSkill),f("skill.invocation","slash-command")),status:{code:1}}),ae("Stop",`slashSkill=${s.slashSkill}`,"synthetic span injected");}let i=e.transcript_path||s.transcriptPath||"",a=Vi(i),p=s.toolCalls.length,l=s.toolCalls.filter(S=>S.error).length,u=jn(a?.text?me(a.text,ye.TRACE_OUTPUT):p===0?"No tools used":s.toolCalls.map((S,V)=>`${V+1}. ${S.tool}${S.brief?` \u2192 ${S.brief}`:""}${S.error?" [ERROR]":""}`).join(`
91
+ `)),h=a?.inputTokens??0,E=a?.outputTokens??0,m=s.toolCalls.map((S,V)=>`${V+1}. ${S.tool}${S.brief?` \u2192 ${S.brief}`:""}${S.error?" [ERROR]":""} (${S.durationMs}ms)`),j=!!(s.slashSkill||s.toolCalls.some(S=>S.tool==="Skill")),le=s.toolCalls.some(S=>S.tool==="Agent");if(!j&&!le){ae("Stop",`sid=${t}`,"skipping trace \u2014 no Skill or Agent calls found"),Gn(t),tt(e);return}let de=j?"skill":"agent",Oe=Math.round(Number(BigInt(o)-BigInt(s.startNs))/1e6),C=ji(s.model,h,E),A=Wi(s);A==="prerequisite_required"&&(s.spans.push({traceId:s.traceId,spanId:Dn(),parentSpanId:s.spanId,name:"prerequisite-check",kind:3,startTimeUnixNano:s.startNs,endTimeUnixNano:o,attributes:_e(f(T.OBS_NAME,"prerequisite-check"),f(T.OBS_TYPE,"EVENT"),f(T.OBS_LEVEL,"WARNING"),f(T.OBS_OUTPUT,u),f("skill.name",s.slashSkill||void 0),f("execution_status","prerequisite_required")),status:{code:1}}),ae("Stop",`sid=${t}`,"prerequisite-check span injected",`skill=${s.slashSkill}`));let nt=(()=>{if(!(!j||!s.prompt)){if(s.slashSkill){let S=s.prompt.replace(new RegExp(`^\\/${s.slashSkill}\\s*`,"i"),"").trim();return S?me(S,ye.USER_INSTRUCTION):void 0}return me(s.prompt,ye.USER_INSTRUCTION)||void 0}})(),Hn=JSON.stringify({trace_type:de,...A!==null&&{execution_status:A},...s.slashSkill&&{skill_name:s.slashSkill},...s.skillArtifacts?.length&&{skill_artifacts:s.skillArtifacts},...nt!==void 0&&{user_instruction:nt},latency_ms:Oe,...h&&{tokens_input:h},...E&&{tokens_output:E},cost_usd:C,...s.model&&{model:s.model},toolCount:p,errorCount:l,stopReason:n,sessionId:s.sessionId,tools:m.length?m:void 0}),Kn=jn(me(s.prompt,ye.TRACE_INPUT)),Jn=_e(f(T.TRACE_NAME,s.traceName),f(T.TRACE_INPUT,Kn),f(T.TRACE_OUTPUT,u),f(T.TRACE_METADATA,Hn),f(T.SESSION_ID,s.sessionId),f(T.USER_ID,r.user),f(T.OBS_TYPE,"GENERATION"),f(T.MODEL,s.model),h?f(T.INPUT_TOKENS,h):null,E?f(T.OUTPUT_TOKENS,E):null,f("claude.stop_reason",n),f("claude.tool_count",String(p)),f("claude.error_count",String(l)),f("claude.session_id",s.sessionId),f("flow.trace_type",de),{key:T.TRACE_TAGS,value:{arrayValue:{values:[{stringValue:de}]}}}),zn={traceId:s.traceId,spanId:s.spanId,name:s.traceName,kind:1,startTimeUnixNano:s.startNs,endTimeUnixNano:o,attributes:Jn,status:{code:l>0?2:1}},Xn=_e(f("service.name",process.env.LANGFUSE_PROJECT_NAME||"claude-code"),f("deployment.environment",process.env.LANGFUSE_ENVIRONMENT||"default")),st=[...s.spans||[],zn];ae("Stop",`sid=${t}`,`totalTools=${p}`,`errorTools=${l}`,`spans=${st.length}`,`inputTokens=${h}`,`outputTokens=${E}`,`traceOutput="${Yi(u,80)}"`);let Zn=await Di(Bi(Xn,st));ae("Stop",`POST status=${Zn}`,`traceId=${s.traceId}`),Gn(t),tt(e);}Vn.exports={onStop:Hi};});var Wn=d(()=>{var{credentialsAvailable:Ki}=ee(),{readInput:Ji}=B(),{onUserPromptSubmit:zi}=Tn(),{onPreToolUse:Xi}=gn(),{onPostToolUse:Zi}=xn(),{onStop:Qi}=Yn();async function ea(){let e=process.argv[2];if(!await Ki()){let n=[];process.stdin.on("data",s=>n.push(s)),process.stdin.on("end",()=>process.stdout.write(n.join("")));return}let t=await Ji();switch(e){case "UserPromptSubmit":await zi(t);break;case "PreToolUse":await Xi(t);break;case "PostToolUse":await Zi(t);break;case "Stop":await Qi(t);break;default:process.stderr.write(`[flow-obs] unknown event: ${e}
92
+ `),process.stdout.write(JSON.stringify(t));}}ea().catch(e=>{process.stderr.write(`[flow-obs] fatal: ${e.message}
93
+ `),process.exit(0);});});var ta=new Set(["UserPromptSubmit","PreToolUse","PostToolUse","Stop"]),[,,ce,ue]=process.argv;if(ce==="install"){Ie().checkForUpdate();let{runInstall:e}=Bt();e().then(t=>process.exit(t)).catch(t=>{process.stderr.write(`Unexpected error: ${t.message}
94
+ `),process.exit(1);});}else if(ce==="auth"){Ie().checkForUpdate();let{runLogin:e,runLogout:t,runStatus:n}=Ht();(async()=>{let s=0;if(ue==="login"||!ue){let o=process.argv.slice(4),r=i=>{let a=o.indexOf(i);return a!==-1?o[a+1]:void 0};s=await e({clientId:r("--client-id"),clientSecret:r("--client-secret"),tenant:r("--tenant")});}else if(ue==="logout"){let o=process.argv.includes("--force");s=await t(o);}else ue==="status"?s=await n():(process.stderr.write(`Unknown auth subcommand: ${ue}
95
+ `),process.stderr.write(`Usage: flow-ai-obs auth [login|logout|status]
96
+ `),s=1);process.exit(s);})();}else if(ce==="otel-headers"){let{runOtelHeaders:e}=Jt();e().then(t=>process.exit(t)).catch(()=>process.exit(1));}else if(ce==="check"){let{runCheck:e}=tn();e().then(t=>process.exit(t)).catch(t=>{process.stderr.write(`Unexpected error: ${t.message}
97
+ `),process.exit(1);});}else if(ta.has(ce))Wn();else {let{bold:e,cyan:t,dim:n}=he();process.stdout.write(`
98
+ `+e(" flow-ai-obs")+`
99
+
100
+ `),process.stdout.write(t(" install")+n(` Register Claude Code hooks in ~/.claude/settings.json
101
+ `)),process.stdout.write(t(" auth login")+n(` Authenticate with Flow (clientId / clientSecret / tenant)
102
+ `)),process.stdout.write(t(" auth logout")+n(` Remove credentials
103
+ `)),process.stdout.write(t(" auth status")+n(` Show current auth status
104
+ `)),process.stdout.write(t(" check")+n(` Validate full observability setup
105
+
106
+ `)),process.stdout.write(n(` (Hook events: UserPromptSubmit | PreToolUse | PostToolUse | Stop)
107
+
108
+ `)),process.exit(0);}
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ 'use strict';var c=(t=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(t,{get:(e,r)=>(typeof require<"u"?require:e)[r]}):t)(function(t){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')});var f=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var h=f((Zs,kt)=>{var v=process.env.FLOW_BASE_URL||"https://flow.ciandt.com",Nt={AUTH_ENGINE:process.env.FLOW_AUTH_ENGINE_URL||`${v}/auth-engine-api/v2/api-key/token`,GATEWAY_TRACES:process.env.FLOW_GATEWAY_TRACES_URL||`${v}/flow-langfuse-gateway/v1/traces`,GATEWAY_HEALTH:process.env.FLOW_GATEWAY_HEALTH_URL||`${v}/flow-langfuse-gateway/v1/health`,OTEL_ENDPOINT:process.env.FLOW_OTEL_ENDPOINT_URL||`${v}/otel`},Ze={SERVICE:"flow-plugins-cli",ACCOUNT_CREDS:"credentials",ACCOUNT_TOKEN:"token-cache"},Qe="FLOW-nodejs",tn="config.json",en={SCOPE_NAME:"flow-ai-obs",SCOPE_VERSION:"0.1.0"},nn={CLAUDE_CODE_ENABLE_TELEMETRY:"1",OTEL_EXPORTER_OTLP_ENDPOINT:Nt.OTEL_ENDPOINT,OTEL_EXPORTER_OTLP_PROTOCOL:"http/json",OTEL_METRICS_EXPORTER:"otlp",OTEL_LOGS_EXPORTER:"otlp",OTEL_LOG_TOOL_DETAILS:"1",OTEL_LOG_USER_PROMPTS:"1"},rn={FLOW_TELEMETRY:"FLOW_TELEMETRY",FLOW_TELEMETRY_GATEWAY:"FLOW_TELEMETRY_GATEWAY_URL",LANGFUSE_BASE_URL:"LANGFUSE_BASE_URL",LANGFUSE_PUBLIC_KEY:"LANGFUSE_PUBLIC_KEY",LANGFUSE_SECRET_KEY:"LANGFUSE_SECRET_KEY",ANTHROPIC_MODEL:"ANTHROPIC_MODEL",FLOW_OBS_DEBUG:"FLOW_TELEMETRY_DEBUG",FLOW_OBS_ALLOW_HTTP:"FLOW_TELEMETRY_ALLOW_HTTP"},sn={LOCK_STALE_MS:3e4,CONNECTIVITY_MAX_RETRY:3,CONNECTIVITY_RETRY_MS:2e3},on={UserPromptSubmit:5,PreToolUse:3,PostToolUse:10,Stop:15},an={FILE_PREFIX:"flow-obs-buffer-",MAX_AGE_MS:5760*60*1e3,MAX_FILES:50},cn="flow-ai-obs-debug.log",un={TOOL_INPUT:2e4,TOOL_OUTPUT:2e4,TRACE_OUTPUT:2e4,USER_INSTRUCTION:1e4,TRACE_INPUT:5e4,TOOL_BRIEF:60},ln="flow-obs-";kt.exports={FLOW_BASE_URL:v,FLOW_URLS:Nt,VAULT:Ze,CONFIG_DIR_NAME:Qe,CONFIG_FILE_NAME:tn,OTLP:en,NATIVE_OTEL:nn,ENV:rn,INSTALLER:sn,HOOK_TIMEOUTS:on,BUFFER:an,DEBUG_LOG_FILENAME:cn,STATE_FILE_PREFIX:ln,TRACE_LIMITS:un};});var Rt=f((Qs,Ct)=>{var F=c("fs"),N=c("path"),dn=c("os"),{CONFIG_DIR_NAME:tt,CONFIG_FILE_NAME:et}=h(),nt={credentials:"credentials","token-cache":"tokenCache"};function q(){let t=dn.homedir();if(process.platform==="darwin")return N.join(t,"Library","Preferences",tt,et);if(process.platform==="win32"){let r=process.env.APPDATA||N.join(t,"AppData","Roaming");return N.join(r,tt,et)}let e=process.env.XDG_CONFIG_HOME||N.join(t,".config");return N.join(e,tt,et)}function rt(){try{let t=F.readFileSync(q(),"utf8");return JSON.parse(t)}catch{return {}}}function Ut(t){let e=q();F.mkdirSync(N.dirname(e),{recursive:true}),F.writeFileSync(e,JSON.stringify(t,null,2),{encoding:"utf8",mode:384});}var st=class{static isAvailable(){return true}static hasExistingData(){try{if(!F.existsSync(q()))return !1;let e=JSON.parse(F.readFileSync(q(),"utf8"));return !!(e&&e.credentials)}catch{return false}}async getSecret(e,r){let n=rt(),s=nt[r]||r,o=n[s];return o?typeof o=="string"?o:JSON.stringify(o):null}async setSecret(e,r,n){let s=rt(),o=nt[r]||r;if(r==="token-cache")try{s[o]=JSON.parse(n);}catch{s[o]=n;}else s[o]=n;Ut(s);}async deleteSecret(e,r){let n=rt(),s=nt[r]||r;return s in n?(delete n[s],Ut(n),true):false}getStoragePath(){return `config.json (unencrypted) \u2014 ${q()}`}};function pn(){}Ct.exports={ScryptFallbackProvider:st,_resetWarningState:pn};});var $t=f((to,Pt)=>{var{VAULT:x}=h();async function fn(t,e){let r=[x.ACCOUNT_CREDS,x.ACCOUNT_TOKEN];for(let n of r)try{if(await e.getSecret(x.SERVICE,n)!==null)continue;let o=await t.getSecret(x.SERVICE,n);o!==null&&await e.setSecret(x.SERVICE,n,o);}catch{}}Pt.exports={migrateFromJsonFallback:fn};});var qt=f((eo,vt)=>{var{execFile:Sn,exec:_n}=c("child_process"),{promisify:bt}=c("util"),ot=bt(Sn),En=bt(_n),at=class{static async isAvailable(){try{return await En("command -v security"),!0}catch{return false}}async getSecret(e,r){try{let{stdout:n}=await ot("security",["find-generic-password","-w","-a",r,"-s",e]);return n.trim()||null}catch{return null}}async setSecret(e,r,n){await ot("security",["add-generic-password","-U","-a",r,"-s",e,"-w",n]);}async deleteSecret(e,r){try{return await ot("security",["delete-generic-password","-a",r,"-s",e]),!0}catch{return false}}getStoragePath(){return "macOS Keychain"}};vt.exports={MacOsKeyVaultProvider:at};});var Dt=f((no,Bt)=>{var{execFile:Tn,exec:hn,spawn:gn}=c("child_process"),{promisify:xt}=c("util"),Ft=xt(Tn),yn=xt(hn),it=class{static async isAvailable(){try{return await yn("command -v secret-tool"),!0}catch{return false}}async getSecret(e,r){try{let{stdout:n}=await Ft("secret-tool",["lookup","service",e,"account",r]);return n.trim()||null}catch{return null}}async setSecret(e,r,n){await new Promise((s,o)=>{let a=gn("secret-tool",["store","--label",e,"service",e,"account",r]);a.on("error",o),a.on("close",u=>{u===0?s():o(new Error(`secret-tool exited with code ${u}`));}),a.stdin.write(n),a.stdin.end();});}async deleteSecret(e,r){try{return await Ft("secret-tool",["clear","service",e,"account",r]),!0}catch{return false}}getStoragePath(){return "Linux Secret Service (libsecret)"}};Bt.exports={LinuxKeyVaultProvider:it};});var Gt=f((ro,Mt)=>{var ct=class{static async isAvailable(){try{return await c("keytar").getPassword("__flow_probe__","__flow_probe__"),!0}catch{return false}}async getSecret(e,r){return c("keytar").getPassword(e,r)}async setSecret(e,r,n){await c("keytar").setPassword(e,r,n);}async deleteSecret(e,r){return c("keytar").deletePassword(e,r)}getStoragePath(){return "Windows Credential Manager"}};Mt.exports={WindowsKeyVaultProvider:ct};});var jt=f((so,Yt)=>{async function Vt(){let{ScryptFallbackProvider:t}=Rt();if(t.hasExistingData()){let e=await Wt();if(e){let{migrateFromJsonFallback:r}=$t(),{VAULT:n}=h();if(await r(new t,e),await e.getSecret(n.SERVICE,n.ACCOUNT_CREDS)!==null)return e}return new t}return await Wt()??new t}async function Wt(){switch(process.platform){case "darwin":{let{MacOsKeyVaultProvider:t}=qt();return await t.isAvailable()?new t:null}case "linux":{let{LinuxKeyVaultProvider:t}=Dt();return await t.isAvailable()?new t:null}case "win32":{let{WindowsKeyVaultProvider:t}=Gt();return await t.isAvailable()?new t:null}default:return null}}var On=Vt;Yt.exports={createSecretVaultProvider:Vt,createKeyVaultProvider:On};});var Kt=f((oo,Jt)=>{var{createSecretVaultProvider:mn}=jt(),{VAULT:lt}=h(),k=lt.SERVICE,dt=lt.ACCOUNT_CREDS,pt=lt.ACCOUNT_TOKEN,ut=null;function U(){return ut||(ut=mn()),ut}async function Ht(){let e=await(await U()).getSecret(k,dt);if(!e)return null;try{return JSON.parse(e)}catch{return null}}async function An(t){if(!t.clientId||!t.clientSecret||!t.tenant)throw new Error("clientId, clientSecret and tenant are required");await(await U()).setSecret(k,dt,JSON.stringify(t));}async function wn(){let t=await U();await t.deleteSecret(k,dt),await t.deleteSecret(k,pt);}async function Ln(t){await(await U()).setSecret(k,pt,JSON.stringify({accessToken:t.accessToken,expiresAt:t.expiresAt}));}async function ft(){let e=await(await U()).getSecret(k,pt);if(!e)return null;try{let r=JSON.parse(e);return {accessToken:r.accessToken,expiresAt:r.expiresAt}}catch{return null}}async function In(){let t=await ft();return t?new Date(t.expiresAt)>new Date:false}async function Nn(){let t=await ft();if(!t||!t.accessToken)return null;try{let e=t.accessToken.split(".");if(e.length<2)return null;let r=JSON.parse(Buffer.from(e[1].replace(/-/g,"+").replace(/_/g,"/"),"base64").toString("utf8"));return r.email||r.preferred_username||r.upn||r.sub||null}catch{return null}}async function kn(){let t=await U();if(typeof t.getStoragePath=="function")return t.getStoragePath();let e=process.platform;return e==="darwin"?"macOS Keychain":e==="win32"?"Windows Credential Manager":"Linux Secret Service (libsecret)"}async function Un(){let t=await Ht();return !!(t&&t.clientId&&t.clientSecret&&t.tenant)}Jt.exports={getFlowCredentials:Ht,saveFlowCredentials:An,clearFlowCredentials:wn,saveTokenCache:Ln,getTokenCache:ft,isTokenValid:In,getEmailFromToken:Nn,getConfigPath:kn,hasCredentials:Un};});var Zt=f((ao,Xt)=>{var Cn=c("https"),Rn=c("http"),{FLOW_URLS:Pn}=h(),$n=/^[a-z0-9][a-z0-9-]{0,62}[a-z0-9]$|^[a-z0-9]$/;function bn(t){if(!$n.test(t))throw new Error(`Invalid tenant value: "${t}". Tenant must be lowercase alphanumeric and hyphens (1-64 chars).`)}var zt={INVALID_CLIENT_SECRET:"Invalid Client Secret \u2014 check the value and try again",INVALID_CLIENT_ID:"Invalid Client ID \u2014 check the value and try again",INVALID_TENANT:"Invalid Tenant \u2014 check the value and try again",TENANT_NOT_FOUND:"Tenant not found \u2014 verify the tenant identifier"};function vn(t,e){try{let r=JSON.parse(e);if(typeof r.error=="string"&&zt[r.error])return new Error(zt[r.error])}catch{}return t===401||t===403||t===500?new Error("Invalid credentials"):t>=400&&t<500?new Error("Authentication request was rejected"):new Error("Authentication service unavailable, please try again later")}function qn(t){return new Promise((e,r)=>{try{bn(t.tenant);}catch(l){return r(l)}let n=JSON.stringify({clientSecret:t.clientSecret}),s=new URL(Pn.AUTH_ENGINE),o=s.protocol==="https:",a={hostname:s.hostname,port:s.port||(o?443:80),path:s.pathname+s.search,method:"POST",headers:{"Content-Type":"application/json","Content-Length":Buffer.byteLength(n),FlowTenant:t.tenant}},p=(o?Cn:Rn).request(a,l=>{let d=[];l.on("data",S=>d.push(S)),l.on("end",()=>{let S=d.join("");if(l.statusCode>=400)return r(vn(l.statusCode,S));let E;try{E=JSON.parse(S);}catch{return r(new Error("Invalid response from auth engine"))}let y=E.expires_at??new Date(Date.now()+(E.expires_in??3600)*1e3).toISOString();e({accessToken:E.access_token,expiresAt:y});});});p.on("error",l=>r(new Error(`Network error: ${l.message}`))),p.write(n),p.end();})}Xt.exports={getToken:qn};});var m=f((io,te)=>{var{writeFileSync:Fn}=c("fs"),{tmpdir:xn}=c("os"),{join:Bn}=c("path"),{DEBUG_LOG_FILENAME:Dn,ENV:Mn}=h(),Qt=Bn(xn(),Dn);function Gn(...t){if(process.env[Mn.FLOW_OBS_DEBUG]!=="1")return;let e=`[${new Date().toISOString()}] ${t.join(" ")}
2
+ `;try{Fn(Qt,e,{flag:"a"});}catch(r){process.stderr.write(`[flow-obs debug ERROR] ${r.message}
3
+ `);}}te.exports={dbg:Gn,DEBUG_LOG:Qt};});var J=f((co,ne)=>{var{getFlowCredentials:St,isTokenValid:Wn,getEmailFromToken:_t,getTokenCache:Vn,saveTokenCache:Yn}=Kt(),{getToken:jn}=Zt(),{FLOW_BASE_URL:Hn,ENV:L}=h(),{dbg:H}=m(),Jn=[/^localhost\.?$/i,/^127\./,/^10\./,/^172\.(1[6-9]|2\d|3[01])\./,/^192\.168\./,/^169\.254\./,/^0\.0\.0\.0$/,/^::1$/,/^\[::1\]$/,/^\[::ffff:/i,/^\[f[cd]/i,/^\[fe80:/i,/^metadata\.google\.internal\.?$/i,/^metadata\.azure\.internal\.?$/i,/(?:^|\.)localtest\.me\.?$/i,/(?:^|\.)lvh\.me\.?$/i,/(?:^|\.)vcap\.me\.?$/i];function Kn(t){return Jn.some(e=>e.test(t))}function Et(t){let e;try{e=new URL(t);}catch{throw new Error(`Invalid URL: ${t}`)}if(!(process.env[L.FLOW_OBS_ALLOW_HTTP]==="true")&&e.protocol!=="https:")throw new Error(`URL must use HTTPS: ${t}`);if(e.username||e.password)throw new Error(`URL must not contain embedded credentials: ${t}`);if(Kn(e.hostname))throw new Error(`SSRF: private/loopback host not allowed: ${e.hostname}`);return t}async function zn(){try{let t=await St();if(!t)return {user:"",tenant:""};let e=await _t()||t.clientId||"",r=t.tenant||"";return {user:e,tenant:r}}catch{return {user:"",tenant:""}}}async function Xn(t){if(await Wn())return (await Vn()).accessToken;H("getOrRefreshToken","token expired \u2014 refreshing via auth-engine");let e=await jn(t);return await Yn(e),H("getOrRefreshToken",`token refreshed, expires=${e.expiresAt}`),e.accessToken}function Zn(t,e){let r=e&&e.accessToken?e.accessToken:"";return {endpoint:t.replace(/\/$/,"")+"/langfuse/traces",authHeader:`Bearer ${r}`}}function Qn(t,e,r){let n=Buffer.from(`${e}:${r}`).toString("base64");return {endpoint:t.replace(/\/$/,"")+"/api/public/otel/v1/traces",authHeader:`Basic ${n}`}}async function ee(){let t=[],e=process.env[L.ANTHROPIC_MODEL]||"",r=process.env[L.FLOW_TELEMETRY]==="true",n=process.env[L.LANGFUSE_BASE_URL],s=process.env[L.LANGFUSE_PUBLIC_KEY],o=process.env[L.LANGFUSE_SECRET_KEY];if(r&&n&&s&&o){let d="",S="";try{let y=await St();y&&(d=await _t()||y.clientId,S=y.tenant);}catch{}let E={user:d||"",tenant:S||"",team:"",project:"",model:e};t.push({mode:"direct",...Qn(Et(n),s,o),...E}),H("resolveCredentials","direct mode active",`user=${E.user}`,`tenant=${E.tenant}`);}let a=await St();if(!a){if(t.length===0){let d=[];return d.authError=false,d}return t}let u;try{u=await Xn(a);}catch(d){if(H("resolveCredentials",`token refresh failed: ${d.message}`),t.length>0)return t;let S=[];return S.authError=true,S}let p={user:await _t()||a.clientId,tenant:a.tenant,team:"",project:"",model:e},l=process.env[L.FLOW_TELEMETRY_GATEWAY]||Hn;return t.push({mode:"gateway",...Zn(Et(l),{accessToken:u}),...p}),t}async function tr(){return (await ee()).length>0}ne.exports={credentialsAvailable:tr,getIdentityFromToken:zn,resolveCredentials:ee,validateUrl:Et};});var C=f((uo,re)=>{function er(t,e){let r=typeof t=="object"?JSON.stringify(t):String(t??"");return r.length>e?r.slice(0,e)+" \u2026[truncated]":r}function nr(t,e=100){return String(t??"").split(`
4
+ `)[0].slice(0,e).trim()}function rr(){return new Promise(t=>{let e="";process.stdin.setEncoding("utf8"),process.stdin.on("data",r=>e+=r),process.stdin.on("end",()=>{try{t(JSON.parse(e));}catch{t({});}});})}function sr(t){process.stdout.write(JSON.stringify(t));}re.exports={truncate:er,firstLine:nr,readInput:rr,passthrough:sr};});var B=f((lo,oe)=>{var{randomBytes:se}=c("crypto"),or=()=>se(16).toString("hex"),ar=()=>se(8).toString("hex");function ir(){return String(BigInt(Date.now())*1000000n)}oe.exports={generateTraceId:or,generateSpanId:ar,nowNs:ir};});var D=f((fo,ie)=>{var{readFileSync:cr,writeFileSync:ur,unlinkSync:lr,existsSync:dr,realpathSync:po}=c("fs"),{tmpdir:ae}=c("os"),{join:pr,resolve:fr,normalize:Sr,sep:_r}=c("path"),Er=/^[0-9a-f-]{1,64}$/i,Tr=Sr(ae())+_r;function hr(t,e){let r=fr(t);if(!r.startsWith(e))throw new Error(`Path traversal detected: ${t}`);return r}function Tt(t){if(!Er.test(t))throw new Error(`Invalid session ID: ${t}`);return hr(pr(ae(),`flow-obs-${t}.json`),Tr)}function gr(t){let e=Tt(t);if(!dr(e))return null;try{return JSON.parse(cr(e,"utf8"))}catch{return null}}function yr(t,e){ur(Tt(t),JSON.stringify(e),"utf8");}function Or(t){try{lr(Tt(t));}catch{}}ie.exports={loadState:gr,saveState:yr,deleteState:Or};});var ht=f((So,le)=>{var{readFileSync:ce,existsSync:ue}=c("fs");function mr(t){if(!t||!ue(t))return null;try{let e=ce(t,"utf8").trimEnd().split(`
5
+ `);for(let r=e.length-1;r>=0;r--)try{let n=JSON.parse(e[r]);if(n.type==="user"&&n.uuid)return n.uuid}catch{}}catch{}return null}function Ar(t){if(!t||!ue(t))return null;try{let e=ce(t,"utf8").trimEnd().split(`
6
+ `),r=[],n=0,s=0,o=!1;for(let a=e.length-1;a>=0;a--)try{let u=JSON.parse(e[a]);if(u.type==="user"){if(o)break;continue}if(u.type==="assistant"){o=!0;let l=(u.message?.content??[]).filter(S=>S.type==="text").map(S=>S.text).join("");l&&r.unshift(l);let d=u.message?.usage??{};n+=(d.input_tokens??0)+(d.cache_read_input_tokens??0)+(d.cache_creation_input_tokens??0),s+=d.output_tokens??0;}}catch{}return !r.length&&n===0?null:{text:r.join(`
7
+
8
+ `),inputTokens:n,outputTokens:s}}catch{}return null}le.exports={readPromptUuid:mr,readLastAssistantOutput:Ar};});var K=f((_o,Se)=>{var{readFileSync:wr,existsSync:de,readdirSync:Lr}=c("fs"),{homedir:pe}=c("os"),{join:R,extname:Ir}=c("path"),Nr=new Set([".venv","__pycache__",".git","node_modules"]),kr=new Set([".pyc",".pyo"]),Ur=new Set(["SKILL.md",".DS_Store",".gitignore"]);function Cr(t,e){if(!t)return null;let r=[R(pe(),".claude","skills",t,"SKILL.md"),R(e||"",".claude","skills",t,"SKILL.md")];for(let n of r)if(de(n))try{return wr(n,"utf8")}catch{}return null}function Rr(t){return t.match(/^\/([a-z][a-z0-9-]*)(?:\s.*)?$/i)?.[1]||null}var fe=new Set(["Read","Write","Edit","MultiEdit","Bash","Glob","Grep","TodoWrite","WebFetch","WebSearch","Agent","Task"]);function Pr(t){return !!t.slashSkill||t.toolCalls.some(n=>n.tool==="Skill")?t.toolCalls.some(n=>fe.has(n.tool))?"complete":"prerequisite_required":null}function $r(t,e){if(!t)return null;let r=[R(pe(),".claude","skills",t),R(e||"",".claude","skills",t)],n=null;for(let a of r)if(de(R(a,"SKILL.md"))){n=a;break}if(!n)return null;let s=[];function o(a,u){for(let p of Lr(a,{withFileTypes:true})){if(Nr.has(p.name)||Ur.has(p.name)||kr.has(Ir(p.name)))continue;let l=u?`${u}/${p.name}`:p.name;p.isDirectory()?o(R(a,p.name),l):s.push(l);}}return o(n,""),s.length>0?s:null}Se.exports={readSkillContent:Cr,readSkillArtifacts:$r,detectSlashSkill:Rr,EXECUTION_TOOLS:fe,detectExecutionStatus:Pr};});var Te=f((Eo,Ee)=>{var{generateTraceId:_e,generateSpanId:br,nowNs:vr}=B(),{saveState:qr}=D(),{readPromptUuid:Fr}=ht(),{detectSlashSkill:xr,readSkillContent:Br,readSkillArtifacts:Dr}=K(),{passthrough:Mr}=C(),{dbg:Gr}=m(),{ENV:Wr}=h();async function Vr(t){let e=t.session_id||"unknown",r=t.prompt||"",n=t.cwd||"",s=Fr(t.transcript_path)||_e(),o=xr(r),a=o?Br(o,n):null,u=o?Dr(o,n):null;qr(e,{traceId:_e(),spanId:br(),startNs:vr(),sessionId:e,prompt:r,cwd:n,traceName:s,transcriptPath:t.transcript_path||"",model:process.env[Wr.ANTHROPIC_MODEL]||"",toolCalls:[],spans:[],pendingTool:null,slashSkill:o,skillContent:a,skillArtifacts:u}),Gr("UserPromptSubmit",`sid=${e}`,`traceName=${s}`,`slashSkill=${o}`),Mr(t);}Ee.exports={onUserPromptSubmit:Vr};});var ge=f((To,he)=>{var{nowNs:Yr}=B(),{loadState:jr,saveState:Hr}=D(),{truncate:Jr,passthrough:Kr}=C(),{dbg:zr}=m(),{TRACE_LIMITS:Xr}=h();async function Zr(t){let e=t.session_id||"unknown",r=t.tool_name||"unknown",n=t.tool_input||{},s=jr(e);s&&(s.pendingTool={name:r,inputStr:Jr(n,Xr.TOOL_INPUT),startNs:Yr()},Hr(e,s)),zr("PreToolUse",`sid=${e}`,`tool=${r}`,`stateFound=${!!s}`),Kr(t);}he.exports={onPreToolUse:Zr};});var me=f((ho,Oe)=>{var Qr=c("https"),ts=c("http"),{FLOW_URLS:es}=h();function ns(t){let e=new URL(t),r=new URL(es.GATEWAY_HEALTH);return r.hostname=e.hostname,r.port=e.port,r.protocol=e.protocol,r}function ye(t,e){return new Promise((r,n)=>{let o=(t._isSSL?Qr:ts).request(t,a=>{let u=[];a.on("data",p=>u.push(p)),a.on("end",()=>r({statusCode:a.statusCode,body:u.join("")}));});o.on("error",a=>n(new Error(`Network error: ${a.message}`))),e&&o.write(e),o.end();})}async function rs(t){let e;try{e=ns(t);}catch{return false}let r=e.protocol==="https:";try{let n=await ye({hostname:e.hostname,port:e.port||(r?443:80),path:e.pathname,method:"GET",_isSSL:r},null);return n.statusCode>=200&&n.statusCode<300}catch{return false}}async function ss(t,e,r){let n=JSON.stringify(t),s=new URL(e),o=s.protocol==="https:",a=String(r).replace(/[\r\n]/g,"");try{return (await ye({hostname:s.hostname,port:s.port||(o?443:80),path:s.pathname,method:"POST",headers:{"Content-Type":"application/json",Authorization:a,"Content-Length":Buffer.byteLength(n)},_isSSL:o},n)).statusCode}catch{return 0}}Oe.exports={checkHealth:rs,sendTraces:ss};});var Ie=f((go,Le)=>{var A=c("fs"),gt=c("path"),Ae=c("os"),{BUFFER:M}=h(),{dbg:G}=m();function we(t){if(/[/\\]/.test(t))throw new Error(`Invalid buffer fileId: ${t}`);return gt.join(Ae.tmpdir(),`${M.FILE_PREFIX}${t}.json`)}function yt(){let t=Ae.tmpdir(),e;try{e=A.readdirSync(t);}catch{return []}return e.filter(r=>r.startsWith(M.FILE_PREFIX)&&r.endsWith(".json")).map(r=>{let n=gt.join(t,r),s=0;try{s=A.statSync(n).mtimeMs;}catch{}return {full:n,mtime:s}}).sort((r,n)=>r.mtime-n.mtime).map(r=>r.full)}function os(t){try{let e=yt();if(e.length>=M.MAX_FILES){let s=e.slice(0,e.length-M.MAX_FILES+1);for(let o of s)try{A.unlinkSync(o);}catch{}}let r=`${Date.now()}-${Math.random().toString(36).slice(2,8)}`,n={fileId:r,bufferedAt:Date.now(),payload:t};return A.writeFileSync(we(r),JSON.stringify(n),"utf8"),G("buffer",`saved fileId=${r}`),r}catch(e){return G("buffer",`saveToBuffer failed: ${e.message}`),null}}function as(){let t=yt(),e=[];for(let r of t)try{let n=A.readFileSync(r,"utf8"),s=JSON.parse(n);s&&s.fileId&&s.payload&&e.push(s);}catch{G("buffer",`skipping malformed file: ${r}`);}return e}function is(t){try{A.unlinkSync(we(t)),G("buffer",`deleted fileId=${t}`);}catch{}}function cs(){let t=yt(),e=Date.now()-M.MAX_AGE_MS;for(let r of t)try{let{mtimeMs:n}=A.statSync(r);n<e&&(A.unlinkSync(r),G("buffer",`evicted stale: ${gt.basename(r)}`));}catch{}}Le.exports={saveToBuffer:os,loadBuffer:as,deleteBuffer:is,cleanStaleBuffers:cs};});var Ot=f((yo,Ce)=>{var{resolveCredentials:us}=J(),{OTLP:Ne}=h(),{dbg:P}=m(),{checkHealth:ls,sendTraces:Ue}=me(),{saveToBuffer:ke,loadBuffer:ds,deleteBuffer:ps,cleanStaleBuffers:fs}=Ie(),Ss={name:Ne.SCOPE_NAME,version:Ne.SCOPE_VERSION},_s={TRACE_NAME:"langfuse.trace.name",TRACE_INPUT:"langfuse.trace.input",TRACE_OUTPUT:"langfuse.trace.output",TRACE_METADATA:"langfuse.trace.metadata",TRACE_TAGS:"langfuse.trace.tags",USER_ID:"langfuse.user.id",SESSION_ID:"langfuse.session.id",OBS_NAME:"langfuse.observation.name",OBS_INPUT:"langfuse.observation.input",OBS_OUTPUT:"langfuse.observation.output",OBS_LEVEL:"langfuse.observation.level",OBS_TYPE:"langfuse.observation.type",MODEL:"gen_ai.request.model",INPUT_TOKENS:"gen_ai.usage.input_tokens",OUTPUT_TOKENS:"gen_ai.usage.output_tokens"};function Es(t,e){return e==null||e===""?null:{key:t,value:{stringValue:String(e)}}}function Ts(...t){return t.filter(Boolean)}function hs(t,e){return {resourceSpans:[{resource:{attributes:t},scopeSpans:[{scope:Ss,spans:Array.isArray(e)?e:[e]}]}]}}function gs(t,e){return new Promise(r=>{let n=JSON.stringify(t),s=new URL(e.endpoint),o=s.protocol==="https:",a=d=>String(d).replace(/[\r\n]/g,""),u={hostname:s.hostname,port:s.port||(o?443:80),path:s.pathname,method:"POST",headers:{"Content-Type":"application/json",Authorization:a(e.authHeader),"Content-Length":Buffer.byteLength(n)}},l=(o?c("https"):c("http")).request(u,d=>{d.resume(),r(d.statusCode);});l.on("error",()=>r(0)),l.write(n),l.end();})}async function ys(t){fs();let e=ds();if(e.length){P("replayBufferedTraces",`replaying ${e.length} buffered trace(s)`);for(let r of e)try{let n=await Ue(r.payload,t.endpoint,t.authHeader);n>=200&&n<300?(ps(r.fileId),P("replayBufferedTraces",`replayed fileId=${r.fileId} status=${n}`)):P("replayBufferedTraces",`replay failed fileId=${r.fileId} status=${n}`);}catch(n){P("replayBufferedTraces",`replay error fileId=${r.fileId}: ${n.message}`);}}}async function Os(t){let e=await us();return e.length?(await Promise.all(e.map(async n=>n.mode==="gateway"?await ls(n.endpoint)?(await ys(n),Ue(t,n.endpoint,n.authHeader)):(P("postToLangfuse","gateway health check failed \u2014 buffering trace"),ke(t),0):gs(t,n)))).find(n=>n!==0)||0:(e.authError&&(P("postToLangfuse","auth refresh failed \u2014 buffering trace"),ke(t)),0)}Ce.exports={LF:_s,a:Es,attrs:Ts,buildPayload:hs,postToLangfuse:Os};});var mt=f((Oo,Re)=>{var g="[REDACTED]",ms=[{re:/\b(sk-[A-Za-z0-9_\-]{20,})/g,replace:()=>`sk-${g}`},{re:/\b(pk-[A-Za-z0-9_\-]{20,})/g,replace:()=>`pk-${g}`},{re:/\b(Bearer\s+)[A-Za-z0-9\-._~+/]+=*/gi,replace:(t,e)=>`${e}${g}`},{re:/\b(Basic\s+)[A-Za-z0-9+/]+=*/gi,replace:(t,e)=>`${e}${g}`},{re:/(-----BEGIN [A-Z ]+-----)[^-]*(-----END [A-Z ]+-----)/g,replace:(t,e,r)=>`${e}
9
+ ${g}
10
+ ${r}`},{re:/\b(AKIA[0-9A-Z]{16})\b/g,replace:()=>`AKIA${g}`},{re:/(aws_secret_access_key\s*[=:]\s*)([A-Za-z0-9/+]{40})/gi,replace:(t,e)=>`${e}${g}`},{re:/\b(ghp_[A-Za-z0-9]{36,})/g,replace:()=>`ghp_${g}`},{re:/\b(github_pat_[A-Za-z0-9_]{36,})/gi,replace:()=>`github_pat_${g}`},{re:/\b(eyJ[A-Za-z0-9_\-]+\.eyJ[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+)/g,replace:()=>`eyJ.${g}`},{re:/\b([A-Z0-9_]*(?:SECRET|TOKEN|KEY|PASSWORD|PASSWD|PWD|CREDENTIAL|API)[A-Z0-9_]*\s*=\s*)(['"]?)([^\s'"#\n]+)\2/gi,replace:(t,e,r)=>`${e}${r}${g}${r}`},{re:/([a-z][a-z0-9+\-.]*:\/\/[^:/?#\s]*):([^@\s]+)@/gi,replace:(t,e)=>`${e}:${g}@`}];function As(t){if(t==null)return "";let e=typeof t=="string"?t:JSON.stringify(t);return ms.reduce((r,{re:n,replace:s})=>r.replace(n,s),e)}Re.exports={sanitize:As,REDACTED:g};});var xe=f((mo,Fe)=>{var{generateSpanId:ws,nowNs:Ls}=B(),{loadState:Is,saveState:Ns}=D(),{LF:W,a:I,attrs:ks}=Ot(),{truncate:Pe,firstLine:Us,passthrough:$e}=C(),{readSkillContent:Cs,readSkillArtifacts:Rs}=K(),{dbg:be}=m(),{TRACE_LIMITS:ve}=h(),{sanitize:qe}=mt();async function Ps(t){let e=t.session_id||"unknown",r=t.tool_name||"unknown",n=t.tool_input||{},s=t.tool_response||"",o=Is(e);if(!o){$e(t);return}let a=Ls(),u=o.pendingTool?.name===r,p=u?o.pendingTool.startNs:String(BigInt(a)-200000000n),l=u?o.pendingTool.inputStr:Pe(n,ve.TOOL_INPUT),d=l;if(r==="Skill"){let w=typeof n=="object"&&n!==null?n.skill||n.name||n.skillName||String(Object.values(n)[0]??""):String(n),O=w?Cs(w.trim(),o.cwd):null;O&&(d=O,o.skillContent||(o.skillContent=O),o.slashSkill||(o.slashSkill=w.trim()),o.skillArtifacts||(o.skillArtifacts=Rs(w.trim(),o.cwd))),be("PostToolUse","Skill tool detected",`skillName=${w}`,`contentFound=${!!O}`);}let S=qe(Pe(s,ve.TOOL_OUTPUT)),E=/\b(error|exception|failed|failure|traceback|errno)\b/i.test(S.slice(0,500)),y=E?"ERROR":"DEFAULT",$=ws(),Y=Math.round(Number(BigInt(a)-BigInt(p))/1e6),Q={Agent:"AGENT",Task:"AGENT"}[r]??"TOOL";o.toolCalls.push({tool:r,brief:Us(l,60),error:E,durationMs:Y}),o.spans.push({traceId:o.traceId,spanId:$,parentSpanId:o.spanId,name:r,kind:3,startTimeUnixNano:p,endTimeUnixNano:a,attributes:ks(I(W.OBS_NAME,r),I(W.OBS_TYPE,Q),I(W.OBS_LEVEL,y),I(W.OBS_INPUT,qe(d)),I(W.OBS_OUTPUT,S),I("tool.name",r),I("tool.error",String(E))),status:{code:E?2:1}}),o.pendingTool=null,Ns(e,o),be("PostToolUse",`sid=${e}`,`tool=${r}`,`error=${E}`,`durationMs=${Y}`,`totalSpans=${o.spans.length}`),$e(t);}Fe.exports={onPostToolUse:Ps};});var De=f((Ao,Be)=>{var At={"claude-opus-4-6":{input:15,output:75},"claude-opus-4-5":{input:15,output:75},"claude-sonnet-4-6":{input:3,output:15},"claude-sonnet-4-5":{input:3,output:15},"claude-haiku-4-5":{input:.8,output:4},"claude-4-6-opus":{input:15,output:75},"claude-4-6-sonnet":{input:3,output:15},"claude-4-5-opus":{input:15,output:75},"claude-4-5-sonnet":{input:3,output:15},"claude-4-5-haiku":{input:.8,output:4},"claude-3-5-sonnet-20241022":{input:3,output:15},"claude-3-5-haiku-20241022":{input:.8,output:4},"claude-3-opus-20240229":{input:15,output:75}};function $s(t,e,r){let n=Object.keys(At).find(a=>(t||"").toLowerCase().includes(a));if(!n)return;let s=At[n],o=(e*s.input+r*s.output)/1e6;return Math.round(o*1e6)/1e6}Be.exports={MODEL_PRICING:At,calcCostUsd:$s};});var Ye=f((wo,Ve)=>{var{generateSpanId:Me,nowNs:bs}=B(),{loadState:vs,deleteState:Ge}=D(),{LF:T,a:i,attrs:z,buildPayload:qs,postToLangfuse:Fs}=Ot(),{getIdentityFromToken:xs}=J(),{calcCostUsd:Bs}=De(),{readLastAssistantOutput:Ds}=ht(),{truncate:X,firstLine:Ms,passthrough:wt}=C(),{dbg:V}=m(),{detectExecutionStatus:Gs}=K(),{sanitize:We}=mt(),{TRACE_LIMITS:Z}=h();async function Ws(t){let e=t.session_id||"unknown",r=t.stop_reason||"end_turn",n=vs(e);if(!n){wt(t);return}let s=bs(),o=await xs();if(n.slashSkill&&!n.toolCalls.some(_=>_.tool==="Skill")){let _=Me(),b=n.skillContent?n.skillContent:JSON.stringify({skill:n.slashSkill});n.toolCalls.push({tool:"Skill",brief:n.slashSkill,error:false,durationMs:0,synthetic:true}),n.spans.push({traceId:n.traceId,spanId:_,parentSpanId:n.spanId,name:"Skill",kind:3,startTimeUnixNano:n.startNs,endTimeUnixNano:n.startNs,attributes:z(i(T.OBS_NAME,"Skill"),i(T.OBS_TYPE,"TOOL"),i(T.OBS_LEVEL,"DEFAULT"),i(T.OBS_INPUT,b),i(T.OBS_OUTPUT,"loaded via slash command"),i("tool.name","Skill"),i("tool.error","false"),i("skill.name",n.slashSkill),i("skill.invocation","slash-command")),status:{code:1}}),V("Stop",`slashSkill=${n.slashSkill}`,"synthetic span injected");}let a=t.transcript_path||n.transcriptPath||"",u=Ds(a),p=n.toolCalls.length,l=n.toolCalls.filter(_=>_.error).length,d=We(u?.text?X(u.text,Z.TRACE_OUTPUT):p===0?"No tools used":n.toolCalls.map((_,b)=>`${b+1}. ${_.tool}${_.brief?` \u2192 ${_.brief}`:""}${_.error?" [ERROR]":""}`).join(`
11
+ `)),S=u?.inputTokens??0,E=u?.outputTokens??0,y=n.toolCalls.map((_,b)=>`${b+1}. ${_.tool}${_.brief?` \u2192 ${_.brief}`:""}${_.error?" [ERROR]":""} (${_.durationMs}ms)`),$=!!(n.slashSkill||n.toolCalls.some(_=>_.tool==="Skill")),Y=n.toolCalls.some(_=>_.tool==="Agent");if(!$&&!Y){V("Stop",`sid=${e}`,"skipping trace \u2014 no Skill or Agent calls found"),Ge(e),wt(t);return}let j=$?"skill":"agent",Q=Math.round(Number(BigInt(s)-BigInt(n.startNs))/1e6),w=Bs(n.model,S,E),O=Gs(n);O==="prerequisite_required"&&(n.spans.push({traceId:n.traceId,spanId:Me(),parentSpanId:n.spanId,name:"prerequisite-check",kind:3,startTimeUnixNano:n.startNs,endTimeUnixNano:s,attributes:z(i(T.OBS_NAME,"prerequisite-check"),i(T.OBS_TYPE,"EVENT"),i(T.OBS_LEVEL,"WARNING"),i(T.OBS_OUTPUT,d),i("skill.name",n.slashSkill||void 0),i("execution_status","prerequisite_required")),status:{code:1}}),V("Stop",`sid=${e}`,"prerequisite-check span injected",`skill=${n.slashSkill}`));let Lt=(()=>{if(!(!$||!n.prompt)){if(n.slashSkill){let _=n.prompt.replace(new RegExp(`^\\/${n.slashSkill}\\s*`,"i"),"").trim();return _?X(_,Z.USER_INSTRUCTION):void 0}return X(n.prompt,Z.USER_INSTRUCTION)||void 0}})(),je=JSON.stringify({trace_type:j,...O!==null&&{execution_status:O},...n.slashSkill&&{skill_name:n.slashSkill},...n.skillArtifacts?.length&&{skill_artifacts:n.skillArtifacts},...Lt!==void 0&&{user_instruction:Lt},latency_ms:Q,...S&&{tokens_input:S},...E&&{tokens_output:E},cost_usd:w,...n.model&&{model:n.model},toolCount:p,errorCount:l,stopReason:r,sessionId:n.sessionId,tools:y.length?y:void 0}),He=We(X(n.prompt,Z.TRACE_INPUT)),Je=z(i(T.TRACE_NAME,n.traceName),i(T.TRACE_INPUT,He),i(T.TRACE_OUTPUT,d),i(T.TRACE_METADATA,je),i(T.SESSION_ID,n.sessionId),i(T.USER_ID,o.user),i(T.OBS_TYPE,"GENERATION"),i(T.MODEL,n.model),S?i(T.INPUT_TOKENS,S):null,E?i(T.OUTPUT_TOKENS,E):null,i("claude.stop_reason",r),i("claude.tool_count",String(p)),i("claude.error_count",String(l)),i("claude.session_id",n.sessionId),i("flow.trace_type",j),{key:T.TRACE_TAGS,value:{arrayValue:{values:[{stringValue:j}]}}}),Ke={traceId:n.traceId,spanId:n.spanId,name:n.traceName,kind:1,startTimeUnixNano:n.startNs,endTimeUnixNano:s,attributes:Je,status:{code:l>0?2:1}},ze=z(i("service.name",process.env.LANGFUSE_PROJECT_NAME||"claude-code"),i("deployment.environment",process.env.LANGFUSE_ENVIRONMENT||"default")),It=[...n.spans||[],Ke];V("Stop",`sid=${e}`,`totalTools=${p}`,`errorTools=${l}`,`spans=${It.length}`,`inputTokens=${S}`,`outputTokens=${E}`,`traceOutput="${Ms(d,80)}"`);let Xe=await Fs(qs(ze,It));V("Stop",`POST status=${Xe}`,`traceId=${n.traceId}`),Ge(e),wt(t);}Ve.exports={onStop:Ws};});var{credentialsAvailable:Vs}=J(),{readInput:Ys}=C(),{onUserPromptSubmit:js}=Te(),{onPreToolUse:Hs}=ge(),{onPostToolUse:Js}=xe(),{onStop:Ks}=Ye();async function zs(){let t=process.argv[2];if(!await Vs()){let r=[];process.stdin.on("data",n=>r.push(n)),process.stdin.on("end",()=>process.stdout.write(r.join("")));return}let e=await Ys();switch(t){case "UserPromptSubmit":await js(e);break;case "PreToolUse":await Hs(e);break;case "PostToolUse":await Js(e);break;case "Stop":await Ks(e);break;default:process.stderr.write(`[flow-obs] unknown event: ${t}
12
+ `),process.stdout.write(JSON.stringify(e));}}zs().catch(t=>{process.stderr.write(`[flow-obs] fatal: ${t.message}
13
+ `),process.exit(0);});
package/dist/setup.js ADDED
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';var i=(e=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(t,n)=>(typeof require<"u"?require:t)[n]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+e+'" is not supported')});var u=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var E=u((sn,Q)=>{var h=process.env.FLOW_BASE_URL||"https://flow.ciandt.com",Z={AUTH_ENGINE:process.env.FLOW_AUTH_ENGINE_URL||`${h}/auth-engine-api/v2/api-key/token`,GATEWAY_TRACES:process.env.FLOW_GATEWAY_TRACES_URL||`${h}/flow-langfuse-gateway/v1/traces`,GATEWAY_HEALTH:process.env.FLOW_GATEWAY_HEALTH_URL||`${h}/flow-langfuse-gateway/v1/health`,OTEL_ENDPOINT:process.env.FLOW_OTEL_ENDPOINT_URL||`${h}/otel`},Me={SERVICE:"flow-plugins-cli",ACCOUNT_CREDS:"credentials",ACCOUNT_TOKEN:"token-cache"},Ge="FLOW-nodejs",Ve="config.json",Ye={SCOPE_NAME:"flow-ai-obs",SCOPE_VERSION:"0.1.0"},We={CLAUDE_CODE_ENABLE_TELEMETRY:"1",OTEL_EXPORTER_OTLP_ENDPOINT:Z.OTEL_ENDPOINT,OTEL_EXPORTER_OTLP_PROTOCOL:"http/json",OTEL_METRICS_EXPORTER:"otlp",OTEL_LOGS_EXPORTER:"otlp",OTEL_LOG_TOOL_DETAILS:"1",OTEL_LOG_USER_PROMPTS:"1"},$e={FLOW_TELEMETRY:"FLOW_TELEMETRY",FLOW_TELEMETRY_GATEWAY:"FLOW_TELEMETRY_GATEWAY_URL",LANGFUSE_BASE_URL:"LANGFUSE_BASE_URL",LANGFUSE_PUBLIC_KEY:"LANGFUSE_PUBLIC_KEY",LANGFUSE_SECRET_KEY:"LANGFUSE_SECRET_KEY",ANTHROPIC_MODEL:"ANTHROPIC_MODEL",FLOW_OBS_DEBUG:"FLOW_TELEMETRY_DEBUG",FLOW_OBS_ALLOW_HTTP:"FLOW_TELEMETRY_ALLOW_HTTP"},Be={LOCK_STALE_MS:3e4,CONNECTIVITY_MAX_RETRY:3,CONNECTIVITY_RETRY_MS:2e3},je={UserPromptSubmit:5,PreToolUse:3,PostToolUse:10,Stop:15},Je={FILE_PREFIX:"flow-obs-buffer-",MAX_AGE_MS:5760*60*1e3,MAX_FILES:50},Ke="flow-ai-obs-debug.log",He={TOOL_INPUT:2e4,TOOL_OUTPUT:2e4,TRACE_OUTPUT:2e4,USER_INSTRUCTION:1e4,TRACE_INPUT:5e4,TOOL_BRIEF:60},Xe="flow-obs-";Q.exports={FLOW_BASE_URL:h,FLOW_URLS:Z,VAULT:Me,CONFIG_DIR_NAME:Ge,CONFIG_FILE_NAME:Ve,OTLP:Ye,NATIVE_OTEL:We,ENV:$e,INSTALLER:Be,HOOK_TIMEOUTS:je,BUFFER:Je,DEBUG_LOG_FILENAME:Ke,STATE_FILE_PREFIX:Xe,TRACE_LIMITS:He};});var ne=u((on,te)=>{var O=i("fs"),T=i("path"),ze=i("os"),{CONFIG_DIR_NAME:x,CONFIG_FILE_NAME:b}=E(),D={credentials:"credentials","token-cache":"tokenCache"};function L(){let e=ze.homedir();if(process.platform==="darwin")return T.join(e,"Library","Preferences",x,b);if(process.platform==="win32"){let n=process.env.APPDATA||T.join(e,"AppData","Roaming");return T.join(n,x,b)}let t=process.env.XDG_CONFIG_HOME||T.join(e,".config");return T.join(t,x,b)}function q(){try{let e=O.readFileSync(L(),"utf8");return JSON.parse(e)}catch{return {}}}function ee(e){let t=L();O.mkdirSync(T.dirname(t),{recursive:true}),O.writeFileSync(t,JSON.stringify(e,null,2),{encoding:"utf8",mode:384});}var M=class{static isAvailable(){return true}static hasExistingData(){try{if(!O.existsSync(L()))return !1;let t=JSON.parse(O.readFileSync(L(),"utf8"));return !!(t&&t.credentials)}catch{return false}}async getSecret(t,n){let r=q(),s=D[n]||n,o=r[s];return o?typeof o=="string"?o:JSON.stringify(o):null}async setSecret(t,n,r){let s=q(),o=D[n]||n;if(n==="token-cache")try{s[o]=JSON.parse(r);}catch{s[o]=r;}else s[o]=r;ee(s);}async deleteSecret(t,n){let r=q(),s=D[n]||n;return s in r?(delete r[s],ee(r),true):false}getStoragePath(){return `config.json (unencrypted) \u2014 ${L()}`}};function Ze(){}te.exports={ScryptFallbackProvider:M,_resetWarningState:Ze};});var se=u((an,re)=>{var{VAULT:A}=E();async function Qe(e,t){let n=[A.ACCOUNT_CREDS,A.ACCOUNT_TOKEN];for(let r of n)try{if(await t.getSecret(A.SERVICE,r)!==null)continue;let o=await e.getSecret(A.SERVICE,r);o!==null&&await t.setSecret(A.SERVICE,r,o);}catch{}}re.exports={migrateFromJsonFallback:Qe};});var ae=u((cn,ie)=>{var{execFile:et,exec:tt}=i("child_process"),{promisify:oe}=i("util"),G=oe(et),nt=oe(tt),V=class{static async isAvailable(){try{return await nt("command -v security"),!0}catch{return false}}async getSecret(t,n){try{let{stdout:r}=await G("security",["find-generic-password","-w","-a",n,"-s",t]);return r.trim()||null}catch{return null}}async setSecret(t,n,r){await G("security",["add-generic-password","-U","-a",n,"-s",t,"-w",r]);}async deleteSecret(t,n){try{return await G("security",["delete-generic-password","-a",n,"-s",t]),!0}catch{return false}}getStoragePath(){return "macOS Keychain"}};ie.exports={MacOsKeyVaultProvider:V};});var de=u((un,le)=>{var{execFile:rt,exec:st,spawn:ot}=i("child_process"),{promisify:ue}=i("util"),ce=ue(rt),it=ue(st),Y=class{static async isAvailable(){try{return await it("command -v secret-tool"),!0}catch{return false}}async getSecret(t,n){try{let{stdout:r}=await ce("secret-tool",["lookup","service",t,"account",n]);return r.trim()||null}catch{return null}}async setSecret(t,n,r){await new Promise((s,o)=>{let c=ot("secret-tool",["store","--label",t,"service",t,"account",n]);c.on("error",o),c.on("close",w=>{w===0?s():o(new Error(`secret-tool exited with code ${w}`));}),c.stdin.write(r),c.stdin.end();});}async deleteSecret(t,n){try{return await ce("secret-tool",["clear","service",t,"account",n]),!0}catch{return false}}getStoragePath(){return "Linux Secret Service (libsecret)"}};le.exports={LinuxKeyVaultProvider:Y};});var pe=u((ln,we)=>{var W=class{static async isAvailable(){try{return await i("keytar").getPassword("__flow_probe__","__flow_probe__"),!0}catch{return false}}async getSecret(t,n){return i("keytar").getPassword(t,n)}async setSecret(t,n,r){await i("keytar").setPassword(t,n,r);}async deleteSecret(t,n){return i("keytar").deletePassword(t,n)}getStoragePath(){return "Windows Credential Manager"}};we.exports={WindowsKeyVaultProvider:W};});var _e=u((dn,Te)=>{async function Ee(){let{ScryptFallbackProvider:e}=ne();if(e.hasExistingData()){let t=await fe();if(t){let{migrateFromJsonFallback:n}=se(),{VAULT:r}=E();if(await n(new e,t),await t.getSecret(r.SERVICE,r.ACCOUNT_CREDS)!==null)return t}return new e}return await fe()??new e}async function fe(){switch(process.platform){case "darwin":{let{MacOsKeyVaultProvider:e}=ae();return await e.isAvailable()?new e:null}case "linux":{let{LinuxKeyVaultProvider:e}=de();return await e.isAvailable()?new e:null}case "win32":{let{WindowsKeyVaultProvider:e}=pe();return await e.isAvailable()?new e:null}default:return null}}var at=Ee;Te.exports={createSecretVaultProvider:Ee,createKeyVaultProvider:at};});var H=u((wn,ye)=>{var{createSecretVaultProvider:ct}=_e(),{VAULT:B}=E(),_=B.SERVICE,j=B.ACCOUNT_CREDS,J=B.ACCOUNT_TOKEN,$=null;function S(){return $||($=ct()),$}async function Se(){let t=await(await S()).getSecret(_,j);if(!t)return null;try{return JSON.parse(t)}catch{return null}}async function ut(e){if(!e.clientId||!e.clientSecret||!e.tenant)throw new Error("clientId, clientSecret and tenant are required");await(await S()).setSecret(_,j,JSON.stringify(e));}async function lt(){let e=await S();await e.deleteSecret(_,j),await e.deleteSecret(_,J);}async function dt(e){await(await S()).setSecret(_,J,JSON.stringify({accessToken:e.accessToken,expiresAt:e.expiresAt}));}async function K(){let t=await(await S()).getSecret(_,J);if(!t)return null;try{let n=JSON.parse(t);return {accessToken:n.accessToken,expiresAt:n.expiresAt}}catch{return null}}async function wt(){let e=await K();return e?new Date(e.expiresAt)>new Date:false}async function pt(){let e=await K();if(!e||!e.accessToken)return null;try{let t=e.accessToken.split(".");if(t.length<2)return null;let n=JSON.parse(Buffer.from(t[1].replace(/-/g,"+").replace(/_/g,"/"),"base64").toString("utf8"));return n.email||n.preferred_username||n.upn||n.sub||null}catch{return null}}async function ft(){let e=await S();if(typeof e.getStoragePath=="function")return e.getStoragePath();let t=process.platform;return t==="darwin"?"macOS Keychain":t==="win32"?"Windows Credential Manager":"Linux Secret Service (libsecret)"}async function Et(){let e=await Se();return !!(e&&e.clientId&&e.clientSecret&&e.tenant)}ye.exports={getFlowCredentials:Se,saveFlowCredentials:ut,clearFlowCredentials:lt,saveTokenCache:dt,getTokenCache:K,isTokenValid:wt,getEmailFromToken:pt,getConfigPath:ft,hasCredentials:Et};});var X=u((pn,he)=>{var Tt=i("https"),_t=i("http"),{FLOW_URLS:St}=E(),yt=/^[a-z0-9][a-z0-9-]{0,62}[a-z0-9]$|^[a-z0-9]$/;function gt(e){if(!yt.test(e))throw new Error(`Invalid tenant value: "${e}". Tenant must be lowercase alphanumeric and hyphens (1-64 chars).`)}var ge={INVALID_CLIENT_SECRET:"Invalid Client Secret \u2014 check the value and try again",INVALID_CLIENT_ID:"Invalid Client ID \u2014 check the value and try again",INVALID_TENANT:"Invalid Tenant \u2014 check the value and try again",TENANT_NOT_FOUND:"Tenant not found \u2014 verify the tenant identifier"};function ht(e,t){try{let n=JSON.parse(t);if(typeof n.error=="string"&&ge[n.error])return new Error(ge[n.error])}catch{}return e===401||e===403||e===500?new Error("Invalid credentials"):e>=400&&e<500?new Error("Authentication request was rejected"):new Error("Authentication service unavailable, please try again later")}function Lt(e){return new Promise((t,n)=>{try{gt(e.tenant);}catch(a){return n(a)}let r=JSON.stringify({clientSecret:e.clientSecret}),s=new URL(St.AUTH_ENGINE),o=s.protocol==="https:",c={hostname:s.hostname,port:s.port||(o?443:80),path:s.pathname+s.search,method:"POST",headers:{"Content-Type":"application/json","Content-Length":Buffer.byteLength(r),FlowTenant:e.tenant}},d=(o?Tt:_t).request(c,a=>{let l=[];a.on("data",R=>l.push(R)),a.on("end",()=>{let R=l.join("");if(a.statusCode>=400)return n(ht(a.statusCode,R));let v;try{v=JSON.parse(R);}catch{return n(new Error("Invalid response from auth engine"))}let qe=v.expires_at??new Date(Date.now()+(v.expires_in??3600)*1e3).toISOString();t({accessToken:v.access_token,expiresAt:qe});});});d.on("error",a=>n(new Error(`Network error: ${a.message}`))),d.write(r),d.end();})}he.exports={getToken:Lt};});var Oe=u((fn,Le)=>{var Ot=!process.env.NO_COLOR&&process.stdout.isTTY;function y(e,t){return Ot?n=>`\x1B[${e}m${n}\x1B[${t}m`:n=>n}var At=y("32","39"),Ct=y("31","39"),Nt=y("33","39"),It=y("36","39"),mt=y("1","22"),Rt=y("2","22");Le.exports={green:At,red:Ct,yellow:Nt,cyan:It,bold:mt,dim:Rt};});var ve=u((En,Re)=>{var{getFlowCredentials:Ae,saveFlowCredentials:vt,clearFlowCredentials:Ut,saveTokenCache:Pt,isTokenValid:Ft,getConfigPath:Ce,hasCredentials:Ne}=H(),{getToken:kt}=X(),{green:P,red:C,yellow:U,cyan:N,bold:Ie,dim:g}=Oe(),xt=/[\x00-\x08\x0B-\x1F\x7F]|\x1b\[[0-9;]*[a-zA-Z]/g;function f(e){return typeof e=="string"?e.replace(xt,""):e}function I(e,t={}){return new Promise((n,r)=>{let{masked:s=false,defaultValue:o=""}=t;process.stdout.write(e+o);let c=o;function w(){try{process.stdin.setRawMode(!1);}catch{}}process.once("uncaughtException",w),process.once("unhandledRejection",w);let d=l=>{if(l===""){a(),r(new Error("SIGINT"));return}if(l==="\r"||l===`
3
+ `){a(),process.stdout.write(`
4
+ `),n(c);return}if(l==="\x7F"){c.length>0&&(c=c.slice(0,-1),process.stdout.write("\b \b"));return}l.startsWith("\x1B")||(c+=l,process.stdout.write(s?"*".repeat(l.length):l));};function a(){process.stdin.setRawMode(false),process.stdin.removeListener("data",d),process.removeListener("uncaughtException",w),process.removeListener("unhandledRejection",w);}process.stdin.setRawMode(true),process.stdin.resume(),process.stdin.setEncoding("utf8"),process.stdin.on("data",d);})}async function me(e){let{accessToken:t,expiresAt:n}=await kt(e);await vt(e),await Pt({accessToken:t,expiresAt:n});}async function bt(e){try{return await me(e),process.stdout.write(P(` \u2713 Setup complete. Tenant: ${f(e.tenant)}
5
+
6
+ `)),0}catch(t){return process.stderr.write(C(` \u2717 Authentication failed: ${f(t.message)}
7
+ `)),1}}async function Dt(){try{process.stdout.write(`
8
+ `);let e=(await I(N(" ? ")+"Client ID: ")).trim(),t=(await I(N(" ? ")+"Client Secret: ",{masked:!0})).trim(),n=(await I(N(" ? ")+"Tenant: ")).trim();if(!e||!t||!n)return process.stderr.write(C(` \u2717 All fields are required
9
+ `)),1;try{await me({clientId:e,clientSecret:t,tenant:n});}catch(r){return process.stderr.write(C(` \u2717 Authentication failed: ${f(r.message)}
10
+ `)),1}return process.stdout.write(P(` \u2713 Setup complete. Tenant: ${f(n)}
11
+ `)),process.stdout.write(` Storage: ${await Ce()}
12
+
13
+ `),0}catch(e){if(e.message==="SIGINT")throw e;return process.stderr.write(C(` \u2717 Unexpected error: ${e.message}
14
+ `)),1}}async function qt(e={}){if(e.clientId&&e.clientSecret&&e.tenant)return bt({clientId:e.clientId,clientSecret:e.clientSecret,tenant:e.tenant});if(await Ne()){let t=await Ae();process.stdout.write(`
15
+ `),process.stdout.write(Ie(` Already authenticated
16
+ `)),process.stdout.write(g(" Tenant: ")+f(t.tenant)+`
17
+ `),process.stdout.write(g(" Client ID: ")+f(t.clientId)+`
18
+ `),process.stdout.write(`
19
+ `);let n;try{n=await I(N(" ? ")+"Re-authenticate? (y/N): ");}catch(r){if(r.message==="SIGINT")return process.stdout.write(`
20
+ `),130;throw r}if(n.trim().toLowerCase()!=="y")return process.stdout.write(P(` \u2713 Using existing credentials.
21
+
22
+ `)),0}try{return await Dt()}catch(t){if(t.message==="SIGINT")return process.stdout.write(`
23
+ `),130;throw t}}async function Mt(){try{return process.stdout.write(`
24
+ `),(await I(N(" ? ")+"Are you sure? This will remove your local credentials. (y/N): ")).toLowerCase()!=="y"?(process.stdout.write(U(` \u26A0 Logout cancelled
25
+ `)),0):null}catch(e){if(e.message==="SIGINT")return process.stdout.write(`
26
+ `),130;throw e}}async function Gt(e=false){if(!await Ne())return process.stdout.write(U(` \u26A0 You are not authenticated
27
+ `)),0;if(!e){let t=await Mt();if(t!==null)return t}try{return await Ut(),process.stdout.write(P(` \u2713 Credentials removed successfully
28
+ `)),0}catch{return process.stderr.write(C(` \u2717 Error removing credentials
29
+ `)),1}}async function Vt(){let e=await Ae();if(!e)return process.stdout.write(U(" \u26A0 Not authenticated. Run `flow-ai-obs auth login` to set up.\n")),0;if(!await Ft())return process.stdout.write(U(" \u26A0 Session expired. Run `flow-ai-obs auth login` to re-authenticate.\n")),0;let t=e.clientSecret.length>4?"****...****"+e.clientSecret.slice(-4):"****";return process.stdout.write(`
30
+ `+Ie(` Authenticated
31
+ `)),process.stdout.write(g(" Tenant: ")+f(e.tenant)+`
32
+ `),process.stdout.write(g(" Client ID: ")+f(e.clientId)+`
33
+ `),process.stdout.write(g(" Client Secret: ")+t+`
34
+ `),process.stdout.write(g(" Storage: ")+await Ce()+`
35
+
36
+ `),0}Re.exports={runLogin:qt,runLogout:Gt,runStatus:Vt};});var ke=u((Tn,Fe)=>{var p=i("fs"),k=i("path"),Ue=i("os"),{FLOW_URLS:Yt,INSTALLER:m,HOOK_TIMEOUTS:F,NATIVE_OTEL:Wt}=E(),{getToken:$t}=X();function Pe(){return process.platform==="win32"?k.join(process.env.APPDATA||k.join(Ue.homedir(),"AppData","Roaming"),"Claude","settings.json"):k.join(Ue.homedir(),".claude","settings.json")}var Bt=m.LOCK_STALE_MS;function jt(e){try{let t=p.statSync(e);Date.now()-t.mtimeMs>Bt&&p.unlinkSync(e);}catch{}try{p.writeFileSync(e,String(process.pid),{flag:"wx"});}catch(t){throw t.code==="EEXIST"?new Error("Another install is already in progress. If this is stale, remove: "+e):t}}function Jt(e){try{p.unlinkSync(e);}catch{}}function Kt(){let e=Pe(),t=e+".lock";p.mkdirSync(k.dirname(e),{recursive:true}),jt(t);try{p.existsSync(e)&&p.copyFileSync(e,e+".bkp");let n={};try{n=JSON.parse(p.readFileSync(e,"utf8"));}catch{}(!n.env||typeof n.env!="object")&&(n.env={}),Object.assign(n.env,Wt),n.otelHeadersHelper="flow-ai-obs otel-headers",(!n.hooks||typeof n.hooks!="object")&&(n.hooks={});let r=(s,o)=>({matcher:"",hooks:[{type:"command",command:`flow-ai-obs ${s}`,timeout:o}]});n.hooks.UserPromptSubmit=[r("UserPromptSubmit",F.UserPromptSubmit)],n.hooks.PreToolUse=[r("PreToolUse",F.PreToolUse)],n.hooks.PostToolUse=[r("PostToolUse",F.PostToolUse)],n.hooks.Stop=[r("Stop",F.Stop)],p.writeFileSync(e,JSON.stringify(n,null,2)+`
37
+ `,"utf8");}finally{Jt(t);}return e}async function Ht(e){let t;try{t=(await $t(e)).accessToken;}catch{return false}return t?new Promise(n=>{z(`Bearer ${t}`,n,1);}):false}function z(e,t,n){let r=new URL(Yt.GATEWAY_TRACES),s=JSON.stringify({resourceSpans:[],validation_run:true}),o=r.protocol==="https:",c={hostname:r.hostname,port:r.port||(o?443:80),path:r.pathname,method:"POST",headers:{"Content-Type":"application/json",Authorization:e,"Content-Length":Buffer.byteLength(s)}},d=(o?i("https"):i("http")).request(c,a=>{a.resume(),a.statusCode===200||a.statusCode===204?t(true):n<m.CONNECTIVITY_MAX_RETRY?setTimeout(()=>z(e,t,n+1),m.CONNECTIVITY_RETRY_MS):t(false);});d.on("error",()=>{n<m.CONNECTIVITY_MAX_RETRY?setTimeout(()=>z(e,t,n+1),m.CONNECTIVITY_RETRY_MS):t(false);}),d.write(s),d.end();}Fe.exports={getClaudeSettingsPath:Pe,writeClaudeSettings:Kt,validateConnectivity:Ht};});var xe=i("path"),{hasCredentials:be,getConfigPath:De,isTokenValid:Xt,getFlowCredentials:zt}=H(),{runLogin:Zt}=ve(),{writeClaudeSettings:Qt,validateConnectivity:en}=ke();function tn(){let e=xe.resolve(__dirname,".."),n=(process.env.INIT_CWD?xe.resolve(process.env.INIT_CWD):null)===e;return !process.stdin.isTTY||n||process.env.CI==="true"||process.env.CI==="1"||process.env.npm_config_yes==="true"||process.env.DEBIAN_FRONTEND==="noninteractive"}async function nn(){if(tn()){await be()&&(console.log(`
38
+ [flow-ai-obs] Already authenticated \u2014 skipping setup wizard.`),console.log(" Run `flow-ai-obs install` to register Claude Code hooks.\n"));return}if(console.log(`
39
+ \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557`),console.log("\u2551 flow-ai-obs \u2014 setup wizard \u2551"),console.log(`\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
40
+ `),await be()&&await Xt())console.log(` \u2713 Already authenticated. Config: ${await De()}`),console.log(" Run `flow-ai-obs auth status` to inspect."),console.log(" Run `flow-ai-obs auth login` to re-authenticate.\n");else {console.log(` Authenticate with your Flow credentials (clientId / clientSecret / tenant).
41
+ `),console.log(" Credentials will be stored in:"),console.log(` ${await De()}
42
+ `);let n=await Zt();n!==0&&(console.error("\n Setup aborted. Run `flow-ai-obs auth login` to retry.\n"),process.exit(n));}try{let n=Qt();console.log(` \u2713 Claude settings updated at ${n}`);}catch(n){console.error(`
43
+ [warn] Could not update Claude settings: ${n.message}`);}console.log(`
44
+ Validating connectivity to Flow OTEL endpoint\u2026`);let e=await zt();e||(console.error(`
45
+ [error] Could not read stored credentials.
46
+ `),process.exit(1));let t=await en(e);console.log(t?` \u2713 Connectivity validated \u2014 telemetry is active.
47
+ `:` \u26A0 Could not reach the OTEL endpoint. Telemetry will activate once connectivity is restored.
48
+ `),console.log(` [done] Setup complete!
49
+ `);}nn().catch(e=>{console.error(`
50
+ [error] ${e.message}
51
+ `),process.exit(1);});
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@ciandt-flow/flow-ai-obs",
3
+ "version": "0.1.0",
4
+ "description": "Claude Code hooks for Langfuse OTLP tracing — cross-platform",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "flow-ai-obs": "dist/cli.js",
8
+ "flow-ai-obs-setup": "dist/setup.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsup",
12
+ "test": "node --test src/**/*.test.js",
13
+ "typecheck": "node --check src/**/*.js bin/cli.js",
14
+ "lint": "node --check src/**/*.js bin/cli.js",
15
+ "security-scan": "trufflehog filesystem src/ --fail --no-update --exclude-paths .trufflehogignore",
16
+ "postinstall": "node dist/setup.js",
17
+ "prepublishOnly": "npm run security-scan && npm run build",
18
+ "release:npm": "changeset publish",
19
+ "changeset": "changeset",
20
+ "version-packages": "changeset version",
21
+ "install:hooks": "node bin/cli.js install",
22
+ "auth:login": "node bin/cli.js auth login",
23
+ "auth:logout": "node bin/cli.js auth logout",
24
+ "auth:status": "node bin/cli.js auth status",
25
+ "smoke": "bash scripts/smoke-test.sh",
26
+ "smoke:debug": "bash scripts/smoke-test.sh --debug",
27
+ "smoke:send": "bash scripts/smoke-test.sh --send --allow-http --debug"
28
+ },
29
+ "files": [
30
+ "dist/",
31
+ "README.md",
32
+ "LICENSE.md"
33
+ ],
34
+ "keywords": [
35
+ "claude-code",
36
+ "langfuse",
37
+ "observability",
38
+ "otlp",
39
+ "hooks",
40
+ "flow",
41
+ "ciandt"
42
+ ],
43
+ "author": "CI&T Flow Team",
44
+ "license": "SEE LICENSE IN LICENSE.md",
45
+ "private": false,
46
+ "dependencies": {
47
+ "keytar": "7.9.0"
48
+ },
49
+ "devDependencies": {
50
+ "@changesets/changelog-github": "^0.6.0",
51
+ "@changesets/cli": "^2.30.0",
52
+ "tsup": "^8.5.1",
53
+ "typescript": "^6.0.3"
54
+ },
55
+ "engines": {
56
+ "node": ">=18.0.0"
57
+ },
58
+ "publishConfig": {
59
+ "registry": "https://registry.npmjs.org",
60
+ "access": "public"
61
+ },
62
+ "repository": {
63
+ "type": "git",
64
+ "url": "git+https://github.com/CI-T-HyperX/flow-codeassistant-observability.git"
65
+ }
66
+ }