@dezycro-ai/agent-plugin 2.1.1 → 2.2.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/README.md +14 -98
- package/dist/cli/dezycro.js +50 -0
- package/dist/cli/install.js +7 -0
- package/dist/dezycro-config.d.ts +52 -0
- package/dist/dezycro-config.js +167 -0
- package/dist/gateway/compact-cli.js +8 -0
- package/dist/gateway/proxy.js +35 -0
- package/dist/hooks/lib/router.js +14 -782
- package/dist/schemas/anthropic.d.ts +119 -0
- package/dist/schemas/anthropic.js +43 -0
- package/dist/schemas/dezycro.d.ts +19 -0
- package/dist/schemas/dezycro.js +22 -0
- package/dist/schemas/gateway.d.ts +40 -0
- package/dist/schemas/gateway.js +30 -0
- package/dist/schemas/gating.d.ts +32 -0
- package/dist/schemas/gating.js +15 -0
- package/dist/schemas/index.d.ts +14 -0
- package/dist/schemas/index.js +29 -0
- package/package.json +21 -13
- package/LICENSE +0 -18
- package/bin/resolve-auth.js +0 -112
- package/dist/hooks/lib/authoring.d.ts +0 -118
- package/dist/hooks/lib/authoring.js +0 -277
- package/dist/hooks/lib/command-bus.d.ts +0 -53
- package/dist/hooks/lib/command-bus.js +0 -318
- package/dist/hooks/lib/config.d.ts +0 -28
- package/dist/hooks/lib/config.js +0 -175
- package/dist/hooks/lib/controller-match.d.ts +0 -16
- package/dist/hooks/lib/controller-match.js +0 -96
- package/dist/hooks/lib/coverage.d.ts +0 -17
- package/dist/hooks/lib/coverage.js +0 -66
- package/dist/hooks/lib/hashing.d.ts +0 -8
- package/dist/hooks/lib/hashing.js +0 -49
- package/dist/hooks/lib/logging.d.ts +0 -13
- package/dist/hooks/lib/logging.js +0 -72
- package/dist/hooks/lib/publish-spec.d.ts +0 -17
- package/dist/hooks/lib/publish-spec.js +0 -64
- package/dist/hooks/lib/quality-gate.d.ts +0 -67
- package/dist/hooks/lib/quality-gate.js +0 -187
- package/dist/hooks/lib/router.d.ts +0 -32
- package/dist/hooks/lib/state.d.ts +0 -34
- package/dist/hooks/lib/state.js +0 -244
- package/dist/hooks/lib/undo.d.ts +0 -11
- package/dist/hooks/lib/undo.js +0 -71
- package/dist/hooks/lib/verify.d.ts +0 -28
- package/dist/hooks/lib/verify.js +0 -94
- package/dist/hooks/lib/workbook.d.ts +0 -21
- package/dist/hooks/lib/workbook.js +0 -77
- package/dist/hooks/types.d.ts +0 -278
- package/dist/hooks/types.js +0 -14
- package/install.js +0 -84
- package/setup.js +0 -53
package/README.md
CHANGED
|
@@ -4,26 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
`@dezycro-ai/agent-plugin` is the official [Dezycro](https://dezycro.ai) plugin for AI coding agents — starting with [Claude Code](https://claude.com/claude-code), with more agents on the way. It plugs your agent into the Dezycro platform so you can reverse-engineer an existing codebase into a tree of **features**, each with a user-facing **PRD**, a matching technical **TRD**, generated **test cases**, and live **verification** runs — all without leaving your editor.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## What you get
|
|
12
|
-
|
|
13
|
-
| Surface | What it does |
|
|
14
|
-
|---|---|
|
|
15
|
-
| **`/dezycro:import-features`** | Scans your repo and proposes a tree of user-facing features with PRDs and TRDs. |
|
|
16
|
-
| **`/dezycro:init`** | One-time per-repo setup. Binds the working directory to a workspace + project, writes `.dezycro/config.json`, and helps you wire up persona credentials. |
|
|
17
|
-
| **`/dezycro:work [feature]`** | Loads a feature's full context (PRD, TRD, workbook, journeys, open tasks/issues) and makes it the active feature for the session. |
|
|
18
|
-
| **`/dezycro:status`** | Compact summary of the active feature — journeys, tasks, issues, latest verification. |
|
|
19
|
-
| **`/dezycro:sync`** | Reviews work done since you started, records decisions, opens tasks/issues, links commits in the workbook. |
|
|
20
|
-
| **`/dezycro:publish-spec`** | Detects and uploads your OpenAPI spec for the active feature; triggers JEP (journey execution plan) generation. |
|
|
21
|
-
| **`/dezycro:verify [--env]`** | Pulls the latest verifier binary, runs generated tests against the configured environment, and uploads results to the workbook. |
|
|
22
|
-
| **Companion** | Background guardrails that block tasks from going to DONE without recent passing verifier evidence, nudge you to re-publish your OpenAPI spec when you touch a controller, and surface coverage gaps before you push. |
|
|
23
|
-
|
|
24
|
-
Together, the plugin lets a single developer keep a project's product documentation, technical documentation, test coverage, and verification status all in sync with the code — incrementally, as they work.
|
|
25
|
-
|
|
26
|
-
---
|
|
7
|
+
Drive everything with `/dezycro:*` slash commands, or just describe what you want and your agent will pick the right one.
|
|
27
8
|
|
|
28
9
|
## Install
|
|
29
10
|
|
|
@@ -31,94 +12,29 @@ Together, the plugin lets a single developer keep a project's product documentat
|
|
|
31
12
|
npm install -g @dezycro-ai/agent-plugin
|
|
32
13
|
```
|
|
33
14
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
**→ [docs.dezycro.ai/claude-code/install](https://docs.dezycro.ai/claude-code/install/)**
|
|
37
|
-
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
## Quick start
|
|
41
|
-
|
|
42
|
-
The first run on a new repo is always the same three commands:
|
|
15
|
+
Then connect to Dezycro (one time):
|
|
43
16
|
|
|
44
17
|
```bash
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
# In Claude Code:
|
|
48
|
-
/dezycro:init # bind this repo to a workspace + project
|
|
49
|
-
/dezycro:import-features # reverse-engineer the code into features + PRDs + TRDs
|
|
50
|
-
/dezycro:work <feature> # pick a feature and start working on it
|
|
18
|
+
npx dezycro setup
|
|
51
19
|
```
|
|
52
20
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
```bash
|
|
56
|
-
/dezycro:status # what's open, what's tracked
|
|
57
|
-
/dezycro:sync # push decisions/tasks/commits up to the workbook
|
|
58
|
-
/dezycro:verify # run the latest tests against your environment
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
You don't have to memorize the names — typing things like *"sync my work to Dezycro"* or *"verify this feature against staging"* will land on the right command.
|
|
62
|
-
|
|
63
|
-
---
|
|
64
|
-
|
|
65
|
-
## Commands
|
|
66
|
-
|
|
67
|
-
### `/dezycro:init`
|
|
68
|
-
|
|
69
|
-
One-time per-repo setup. Connects this working directory to a Dezycro workspace and project, detects the dev environment from your code (base URL, OpenAPI spec path, auth flow), writes `.dezycro/config.json`, and walks you through configuring credentials for any personas the project defines (`admin1`, `user1`, etc).
|
|
70
|
-
|
|
71
|
-
Re-runnable — won't overwrite existing configuration, just fills gaps.
|
|
72
|
-
|
|
73
|
-
### `/dezycro:import-features`
|
|
74
|
-
|
|
75
|
-
Scans the codebase (frontend routes, backend controllers, jobs, integrations), groups what it finds into **user-facing features**, proposes a directory tree, asks for your approval, then creates the features and writes a PRD for each one based purely on user behavior — not on implementation details. After PRDs land, it can optionally trigger TRD and test-case generation for the same features.
|
|
76
|
-
|
|
77
|
-
The PRDs read like product specs, not code summaries. The TRDs link back to the PRDs and document the implementation.
|
|
78
|
-
|
|
79
|
-
### `/dezycro:work [feature-name-or-uuid]`
|
|
80
|
-
|
|
81
|
-
Loads everything the agent needs to safely work on a feature: PRD, TRD, approved journey graph, JEP, recent verifier runs, open issues and tasks. **Gates on PRD + TRD completion and approved graph** — refuses to start on a feature that isn't ready. Without an argument, it lists features and lets you pick interactively.
|
|
82
|
-
|
|
83
|
-
### `/dezycro:status`
|
|
84
|
-
|
|
85
|
-
Compact one-screen summary of the active feature: journeys (and which are confirmed), open and resolved issues, open tasks with assignees, latest verifier run, blockers. Useful as a "where am I" check after a context reset.
|
|
86
|
-
|
|
87
|
-
### `/dezycro:sync`
|
|
88
|
-
|
|
89
|
-
The "checkpoint" command. Reviews what happened in the session — files touched, decisions made, problems encountered — and writes that back to the workbook. Idempotent: re-running won't duplicate entries.
|
|
90
|
-
|
|
91
|
-
Run periodically while developing. PMs see the workbook update in near real time.
|
|
92
|
-
|
|
93
|
-
### `/dezycro:publish-spec`
|
|
94
|
-
|
|
95
|
-
Auto-detects your project's OpenAPI spec (Spring's `/v3/api-docs`, FastAPI's `/openapi.json`, a checked-in YAML, etc), optionally filters it to the active feature's endpoints, and uploads it. The upload triggers JEP generation in Dezycro — turning the spec into journey execution plans that drive test generation.
|
|
96
|
-
|
|
97
|
-
### `/dezycro:verify [--env <name>]`
|
|
98
|
-
|
|
99
|
-
Pulls the latest verifier binary for the active feature, resolves persona credentials, runs it against the chosen environment, and uploads the resulting step traces and assertion results to the workbook.
|
|
100
|
-
|
|
101
|
-
Pass `--env staging` to target a non-default environment.
|
|
102
|
-
|
|
103
|
-
---
|
|
104
|
-
|
|
105
|
-
## Persona auth (for `/dezycro:verify`)
|
|
106
|
-
|
|
107
|
-
When `/dezycro:verify` calls authenticated endpoints, it needs credentials for the personas your project defines (`admin1`, `user1`, `readonly1`, …). Configuration lives in `.dezycro/auth.local.json` — **always gitignored** — and references real secrets via files, env vars, `op read`, `pass show`, etc.
|
|
108
|
-
|
|
109
|
-
See **[PERSONAS.md](./PERSONAS.md)** for the full schema, all supported auth types, every supported secret backend, and troubleshooting.
|
|
21
|
+
## Documentation
|
|
110
22
|
|
|
111
|
-
|
|
23
|
+
Everything lives in the docs — installation, setup, the full slash-command reference, the gateway, the background companion, and troubleshooting:
|
|
112
24
|
|
|
113
|
-
|
|
25
|
+
**→ [docs.dezycro.ai/claude-code](https://docs.dezycro.ai/claude-code/)**
|
|
114
26
|
|
|
115
|
-
|
|
27
|
+
Jump to:
|
|
116
28
|
|
|
117
|
-
|
|
29
|
+
- **[Installation](https://docs.dezycro.ai/claude-code/install/)** — prerequisites, MCP setup, updating, uninstalling
|
|
30
|
+
- **[Slash commands](https://docs.dezycro.ai/claude-code/skills/)** — `/dezycro:init`, `work`, `author`, `status`, `sync`, `publish-spec`, `verify`, `import-features`
|
|
31
|
+
- **[Gateway](https://docs.dezycro.ai/claude-code/gateway/)** — run any agent through the local Dezycro gateway (`dezycro run`)
|
|
32
|
+
- **[Companion](https://docs.dezycro.ai/claude-code/companion/)** — the background loop that guards your work
|
|
33
|
+
- **[Personas & auth](https://docs.dezycro.ai/guides/personas/)** — credentials for `dezycro:verify`
|
|
118
34
|
|
|
119
|
-
|
|
35
|
+
## Support
|
|
120
36
|
|
|
121
|
-
|
|
37
|
+
For anything not covered in the docs, email [support@dezycro.io](mailto:support@dezycro.io).
|
|
122
38
|
|
|
123
39
|
## License
|
|
124
40
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";var pe=Object.create;var H=Object.defineProperty;var ue=Object.getOwnPropertyDescriptor;var de=Object.getOwnPropertyNames;var me=Object.getPrototypeOf,he=Object.prototype.hasOwnProperty;var fe=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of de(e))!he.call(r,o)&&o!==t&&H(r,o,{get:()=>e[o],enumerable:!(n=ue(e,o))||n.enumerable});return r};var a=(r,e,t)=>(t=r!=null?pe(me(r)):{},fe(e||!r||!r.__esModule?H(t,"default",{value:r,enumerable:!0}):t,r));var V=a(require("node:http")),v=a(require("node:path")),w=a(require("node:fs")),q=a(require("node:os")),_=require("node:child_process");var d={MessageStart:"message_start",ContentBlockStart:"content_block_start",ContentBlockDelta:"content_block_delta",ContentBlockStop:"content_block_stop",MessageDelta:"message_delta",MessageStop:"message_stop"};var Z={UpdateTask:"update_task"},G={Status:"status"},B={Done:"DONE",Pushed:"PUSHED"};var m={SpecPublished:"specPublished",VerifiedRecently:"verifiedRecently"};var U={Claude:"claude",Codex:"codex"},O={Health:"/__dz/health",Stop:"/__dz/stop"};var W=a(require("node:fs")),h=a(require("node:path")),b=class r{static MAX_DEPTH=6;static dir(){let e=__dirname;for(let t=0;t<r.MAX_DEPTH;t+=1){if(W.existsSync(h.join(e,"package.json")))return e;e=h.dirname(e)}return h.resolve(__dirname,"..","..")}static file(...e){return h.join(r.dir(),...e)}};var x=class r{static DEFAULT_SHIM_CMDS=["npm","pnpm","yarn","pip","pip3","poetry","apt-get","apt","brew","gem","bundle","mvn","gradle","make"];port=Number(process.env.DZ_PORT||13579);proxyPath=b.file("dist","gateway","proxy.js");compactPath=b.file("dist","gateway","compact-cli.js");async run(e){let t=this.applyPortFlag(e),n=t[0];if(n==="--stop"){console.error(await this.control(O.Stop)?"dezycro: stopped":"dezycro: not running");return}if(n==="--status"){console.error(await this.health()?"dezycro: up":"dezycro: down");return}let o=n||U.Claude,s=t.slice(1),i=await this.ensureDaemon();console.error(i?`dezycro: gateway on :${this.port} \u2014 launching ${o}`:`dezycro: gateway unavailable \u2014 launching ${o} DIRECTLY (fail-open)`);let c=this.setupShims(),l={...process.env,...i?this.gatewayUrlEnv(o):{},...c?.env??{}},y=()=>{if(c)try{w.default.rmSync(c.dir,{recursive:!0,force:!0})}catch{}},M=(0,_.spawn)(o,s,{env:l,stdio:"inherit"});M.on("exit",T=>{y(),process.exit(T??0)}),M.on("error",T=>{y(),console.error(`dezycro: failed to launch ${o}: ${T.message}`),process.exit(127)})}applyPortFlag(e){let t=[];for(let n=0;n<e.length;n+=1){let o=e[n];if(o==="--port"){let s=Number(e[n+1]);e[n+1]!==void 0&&Number.isInteger(s)&&s>0&&(this.port=s,n+=1);continue}if(o.startsWith("--port=")){let s=Number(o.slice(7));Number.isInteger(s)&&s>0&&(this.port=s);continue}t.push(o)}return t}gatewayUrlEnv(e){let t=`http://127.0.0.1:${this.port}`;return e===U.Codex?{OPENAI_BASE_URL:`${t}/v1`}:{ANTHROPIC_BASE_URL:t}}async ensureDaemon(){if(await this.health())return!0;let e={...process.env,PORT:String(this.port),INJECT:process.env.INJECT||"1",GATE:process.env.GATE||"0",CAPTURE:process.env.CAPTURE||"0",DZ_STATE_FILE:process.env.DZ_STATE_FILE||v.default.join(process.cwd(),".dezycro","companion-state.json")};(0,_.spawn)(process.execPath,[this.proxyPath],{env:e,detached:!0,stdio:"ignore"}).unref();for(let n=0;n<30;n+=1){if(await this.health())return!0;await r.sleep(100)}return!1}setupShims(){if(process.env.DZ_NO_SHIM==="1"||process.env.DZ_SHIMS==="0")return null;let e=(process.env.DZ_SHIM_CMDS||r.DEFAULT_SHIM_CMDS.join(",")).split(",").map(t=>t.trim()).filter(Boolean);if(!e.length)return null;try{let t=w.default.mkdtempSync(v.default.join(q.default.tmpdir(),"dz-shims-")),n=process.env.PATH||"";for(let o of e){let s=`#!/usr/bin/env bash
|
|
3
|
+
real="$(PATH="$DZ_REAL_PATH" command -v ${o} 2>/dev/null)"
|
|
4
|
+
if [ -z "$real" ] || [ "\${DZ_NO_SHIM:-0}" = "1" ]; then exec "${o}" "$@"; fi
|
|
5
|
+
out="$("$real" "$@" 2>&1)"; rc=$?
|
|
6
|
+
printf '%s\\n' "$out" | DZ_CMD=${o} node "$DZ_COMPACT_PATH"
|
|
7
|
+
exit $rc
|
|
8
|
+
`;w.default.writeFileSync(v.default.join(t,o),s,{mode:493})}return{dir:t,env:{PATH:`${t}${v.default.delimiter}${n}`,DZ_REAL_PATH:n,DZ_COMPACT_PATH:this.compactPath}}}catch{return null}}health(){return this.get(O.Health)}control(e){return this.get(e,()=>!0)}get(e,t=n=>n.statusCode===200){return new Promise(n=>{let o=V.default.get({host:"127.0.0.1",port:this.port,path:e,timeout:800},s=>{s.resume(),n(t(s))});o.on("error",()=>n(!1)),o.on("timeout",()=>{o.destroy(),n(!1)})})}static sleep(e){return new Promise(t=>setTimeout(t,e))}};var Y=require("child_process"),N=a(require("fs")),K=a(require("os")),F=a(require("path")),X=a(require("readline")),R=class r{static MCP_URL="https://mcp.dezycro.ai";run(){console.log(`
|
|
9
|
+
Dezycro Setup`),console.log(` Get your API key from Dezycro \u2192 Settings \u2192 API Keys
|
|
10
|
+
`);let e=X.default.createInterface({input:process.stdin,output:process.stdout});e.question(" API key (dzy_...): ",t=>{let n=(t||"").trim();(!n||!n.startsWith("dzy_"))&&(console.log(`
|
|
11
|
+
Invalid key. Must start with dzy_
|
|
12
|
+
`),e.close(),process.exit(1)),this.configureMcp(n),this.saveToken(n),console.log(`
|
|
13
|
+
You're all set! In Claude Code run: /dezycro:import-features
|
|
14
|
+
`),e.close()})}configureMcp(e){let t=`claude mcp add --transport http dezycro "${r.MCP_URL}/mcp" --header "Authorization: Bearer ${e}"`;try{(0,Y.execSync)(t,{stdio:"inherit"}),console.log(`
|
|
15
|
+
\u2713 Dezycro MCP configured`)}catch{console.error(`
|
|
16
|
+
Failed to configure MCP. Run manually:`),console.error(` ${t}
|
|
17
|
+
`)}}saveToken(e){try{let t=F.default.join(K.default.homedir(),".dezycro"),n=F.default.join(t,"token.json");N.default.mkdirSync(t,{recursive:!0,mode:448}),N.default.writeFileSync(n,JSON.stringify({token:e},null,2),{mode:384}),console.log(` \u2713 Companion token saved to ${n}`)}catch(t){console.error(` Failed to save companion token: ${t.message}`),console.error(` Drop it manually at ~/.dezycro/token.json: {"token": "${e}"}
|
|
18
|
+
`)}}};var E=a(require("fs")),Q=a(require("os")),ee=require("child_process"),D=class r{static DEFAULT_PATH=".dezycro/auth.local.json";static PLACEHOLDER=/\$\{([A-Z0-9_]+)\}/g;run(e=r.DEFAULT_PATH){if(!E.default.existsSync(e)){this.warn(`no auth file at ${e}; emitting nothing`);return}let t;try{t=JSON.parse(E.default.readFileSync(e,"utf8"))}catch(s){this.warn(`parse error in ${e}: ${s.message}`),process.exit(1)}let n=this.resolveSecrets(t.secrets||{}),o=this.personaExports(t.personas||{},n);o.length&&process.stdout.write(o.join(`
|
|
19
|
+
`)+`
|
|
20
|
+
`)}resolveSecrets(e){let t={};for(let[n,o]of Object.entries(e))try{t[n]=this.resolveSecret(o)}catch(s){this.warn(`secret ${n}: ${s.message}`)}return t}personaExports(e,t){let n=[];for(let[o,s]of Object.entries(e))if(!(!s||!s.envVar))try{let i=this.substitute(s.config||{},t);n.push(`export ${s.envVar}=${r.shQuote(JSON.stringify(i))}`)}catch(i){this.warn(`persona '${o}' skipped: ${i.message}`)}return n}resolveSecret(e){if(typeof e!="string")return e;if(e.startsWith("!file:")){let t=e.slice(6);return(t==="~"||t.startsWith("~/"))&&(t=Q.default.homedir()+t.slice(1)),E.default.readFileSync(t,"utf8").trimEnd()}if(e.startsWith("!env:")){let t=e.slice(5);if(!(t in process.env))throw new Error(`env var not set: ${t}`);return process.env[t]}if(e.startsWith("!cmd:")){let t=e.slice(5),o=(0,ee.execSync)(t,{encoding:"utf8",stdio:["ignore","pipe","pipe"]}).trimEnd();if(!o)throw new Error(`empty stdout from: ${t}`);return o}return e}substitute(e,t){if(typeof e=="string")return e.replace(r.PLACEHOLDER,(n,o)=>{if(!(o in t))throw new Error(`\${${o}} not in secrets`);return String(t[o])});if(Array.isArray(e))return e.map(n=>this.substitute(n,t));if(e&&typeof e=="object"){let n={};for(let[o,s]of Object.entries(e))n[o]=this.substitute(s,t);return n}return e}warn(e){process.stderr.write(`[dezycro resolve-auth] ${e}
|
|
21
|
+
`)}static shQuote(e){return"'"+String(e).replaceAll("'","'\\''")+"'"}};var $=class r{run(e){let t=e[0],n=r.scriptFor(t);n===null&&(process.stderr.write(`Usage: dezycro completion <bash|zsh|fish>
|
|
22
|
+
`),process.stderr.write(`Then load it in your shell, e.g.: eval "$(dezycro completion zsh)"
|
|
23
|
+
`),process.exit(2)),process.stdout.write(n)}static scriptFor(e){switch(e){case"bash":return r.bash();case"zsh":return r.zsh();case"fish":return r.fish();default:return null}}static bash(){return["_dezycro() {"," local cur prev cmd",' cur="${COMP_WORDS[COMP_CWORD]}"',' prev="${COMP_WORDS[COMP_CWORD-1]}"',' if [ "$COMP_CWORD" -eq 1 ]; then',' COMPREPLY=( $(compgen -W "run setup resolve-auth completion help --help" -- "$cur") )'," return 0"," fi",' cmd="${COMP_WORDS[1]}"',' case "$cmd" in'," run)",' if [ "$prev" = "--port" ]; then return 0; fi',' COMPREPLY=( $(compgen -W "claude --status --stop --port" -- "$cur") ) ;;'," completion)",' COMPREPLY=( $(compgen -W "bash zsh fish" -- "$cur") ) ;;'," resolve-auth)",' COMPREPLY=( $(compgen -f -- "$cur") ) ;;'," esac"," return 0","}","complete -F _dezycro dezycro",""].join(`
|
|
24
|
+
`)}static zsh(){return["_dezycro() {"," local -a commands"," commands=(run setup resolve-auth completion help)"," if (( CURRENT == 2 )); then"," compadd -- $commands"," return"," fi"," case ${words[2]} in"," run)"," if [[ ${words[CURRENT-1]} == --port ]]; then return; fi"," compadd -- claude --status --stop --port ;;"," completion) compadd -- bash zsh fish ;;"," resolve-auth) _files ;;"," esac","}","compdef _dezycro dezycro",""].join(`
|
|
25
|
+
`)}static fish(){return["complete -c dezycro -f","complete -c dezycro -n __fish_use_subcommand -a run -d 'Run an agent through the gateway'","complete -c dezycro -n __fish_use_subcommand -a setup -d 'Configure the Dezycro MCP connection'","complete -c dezycro -n __fish_use_subcommand -a resolve-auth -d 'Print verifier credential exports'","complete -c dezycro -n __fish_use_subcommand -a completion -d 'Print a shell completion script'","complete -c dezycro -n '__fish_seen_subcommand_from run' -a 'claude --status --stop --port'","complete -c dezycro -n '__fish_seen_subcommand_from completion' -a 'bash zsh fish'",""].join(`
|
|
26
|
+
`)}};var ce=a(require("node:path"));var A=a(require("node:fs")),te=a(require("node:os")),f=a(require("node:path")),ge="DZ_METRICS_DIR",ke="DZ_METRICS",g=class r{file;constructor(e="gateway"){this.file=r.prepare(e)}static enabled(){return process.env[ke]!=="0"}static dir(){let e=process.env[ge];if(e)return e;let t=te.homedir();switch(process.platform){case"win32":return f.join(process.env.LOCALAPPDATA||f.join(t,"AppData","Local"),"Dezycro","metrics");case"darwin":return f.join(t,"Library","Application Support","Dezycro","metrics");default:return f.join(process.env.XDG_STATE_HOME||f.join(t,".local","state"),"dezycro","metrics")}}record(e){if(this.file)try{A.appendFileSync(this.file,`${JSON.stringify({ts:new Date().toISOString(),...e})}
|
|
27
|
+
`)}catch{}}static prepare(e){if(!r.enabled())return null;try{let t=r.dir();return A.mkdirSync(t,{recursive:!0}),f.join(t,`${e}.jsonl`)}catch{return null}}};var ye={rules:[{nameIncludes:Z.UpdateTask,inputField:G.Status,inputValues:[B.Done,B.Pushed],requireAll:[m.SpecPublished,m.VerifiedRecently],reason:"Cannot mark a task DONE/PUSHED yet."}]},j=class r{constructor(e,t){this.policy=e;this.state=t}policy;state;static STATE_PREDICATES={[m.SpecPublished]:e=>!!e&&e.needsSpecPublish!==!0,[m.VerifiedRecently]:e=>!e||!e.lastVerifyCompletionTs?!1:e.lastEditTs?Date.parse(e.lastVerifyCompletionTs)>=Date.parse(e.lastEditTs):!0};static FAILED_HINT={[m.SpecPublished]:"spec not published (controller/API changed) \u2014 run /dezycro:publish-spec",[m.VerifiedRecently]:"no passing verify since last edit \u2014 run /dezycro:verify"};decide(e,t){for(let n of this.policy?.rules??[]){if(!e||!e.includes(n.nameIncludes))continue;if(n.inputField){let c=t?.[n.inputField],l=(n.inputValues??[]).map(y=>String(y).toUpperCase());if(c==null||!l.includes(String(c).toUpperCase()))continue}let o=n.requireAll??[];if(o.length===0)return this.blocked(e,n.reason);let s=o.filter(c=>!r.STATE_PREDICATES[c]?.(this.state));if(s.length===0)continue;let i=s.map(c=>r.FAILED_HINT[c]||c).join("; ");return this.blocked(e,`${n.reason||"Blocked by Dezycro policy."} ${i}.`)}return null}blocked(e,t){return{toolName:e||"?",detail:t||"Blocked by Dezycro policy."}}};var Je=new Set([d.ContentBlockStart,d.ContentBlockDelta,d.ContentBlockStop]);var oe=a(require("node:fs")),se=a(require("node:path"));var k=class r{static BIG_TOOL_CHARS=4e3;static defaultFile(){return se.join(g.dir(),"gateway.jsonl")}static fromFile(e=r.defaultFile()){let t;try{t=oe.readFileSync(e,"utf8")}catch{return r.empty()}return r.aggregate(t.split(`
|
|
28
|
+
`))}static aggregate(e){let t=r.empty();for(let n of e){let o=r.parse(n);o&&(o.type==="turn"?r.addTurn(t,o):o.type==="compaction"&&r.addCompaction(t,o),r.trackTime(t,o.ts))}return t}static addTurn(e,t){e.turns+=1,e.inputTokens+=r.num(t.inputTokens),e.outputTokens+=r.num(t.outputTokens),e.cacheReadTokens+=r.num(t.cacheReadTokens),e.cacheCreationTokens+=r.num(t.cacheCreationTokens),e.totalDurationMs+=r.num(t.durationMs),e.gateBlocks+=r.num(t.blockedToolCalls);let n=r.num(t.freshToolChars);if(e.maxFreshToolChars=Math.max(e.maxFreshToolChars,n),n>r.BIG_TOOL_CHARS&&(e.bigToolTurns+=1),e.maxToolDefChars=Math.max(e.maxToolDefChars,r.num(t.toolDefChars)),e.maxSystemChars=Math.max(e.maxSystemChars,r.num(t.systemChars)),e.tailResultChars+=r.num(t.tailResultChars),e.tailTextChars+=r.num(t.tailTextChars),e.maxCacheCreationTokens=Math.max(e.maxCacheCreationTokens,r.num(t.cacheCreationTokens)),typeof t.status=="number"&&t.status!==200&&(e.errorTurns+=1),r.bump(e.models,typeof t.model=="string"?t.model:"(unknown)"),Array.isArray(t.skillsTriggered))for(let o of t.skillsTriggered)r.bump(e.skillsTriggered,String(o))}static addCompaction(e,t){e.compactions+=1,e.charsSaved+=r.num(t.charsSaved),e.estTokensSaved+=r.num(t.estTokensSaved),e.linesElided+=r.num(t.elidedLines),r.bump(e.commands,typeof t.command=="string"?t.command:"(unknown)")}static trackTime(e,t){typeof t=="string"&&((!e.firstTs||t<e.firstTs)&&(e.firstTs=t),(!e.lastTs||t>e.lastTs)&&(e.lastTs=t))}static parse(e){if(!e.trim())return null;try{return JSON.parse(e)}catch{return null}}static bump(e,t){e[t]=(e[t]??0)+1}static num(e){return typeof e=="number"&&Number.isFinite(e)?e:0}static empty(){return{turns:0,inputTokens:0,outputTokens:0,cacheReadTokens:0,cacheCreationTokens:0,totalDurationMs:0,errorTurns:0,gateBlocks:0,maxFreshToolChars:0,bigToolTurns:0,maxToolDefChars:0,maxSystemChars:0,tailResultChars:0,tailTextChars:0,maxCacheCreationTokens:0,models:{},skillsTriggered:{},compactions:0,charsSaved:0,estTokensSaved:0,linesElided:0,commands:{},firstTs:null,lastTs:null}}};var p=a(require("node:fs")),ie=a(require("node:path")),C=class r{apiUrl;workspaceId;token;metricsFile;cursorFile;batchSize;timeoutMs;fetchImpl;constructor(e){this.apiUrl=e.apiUrl,this.workspaceId=e.workspaceId,this.token=e.token,this.metricsFile=e.metricsFile,this.cursorFile=e.cursorFile,this.batchSize=e.batchSize??1e3,this.timeoutMs=e.timeoutMs??15e3,this.fetchImpl=e.fetchImpl??fetch}async upload({dryRun:e=!1}={}){let{lines:t,newOffset:n}=this.readNewLines(),o=r.toBatch(t),s=o.turns.length+o.compactions.length,i={turns:o.turns.length,compactions:o.compactions.length,newOffset:n,sent:!1,dryRun:e};return e?i:s===0?(n!==this.readCursor()&&this.writeCursor(n),i):(await this.postAll(o),this.writeCursor(n),{...i,sent:!0})}static toBatch(e){let t=[],n=[];for(let o of e){let s;try{s=JSON.parse(o)}catch{continue}typeof s.ts=="string"&&(s.type==="turn"?t.push(r.mapTurn(s)):s.type==="compaction"&&n.push(r.mapCompaction(s)))}return{turns:t,compactions:n}}static mapTurn(e){return{timestamp:e.ts,provider:e.provider??null,model:e.model??null,status:e.status??null,durationMs:e.durationMs??null,inputTokens:e.inputTokens??null,outputTokens:e.outputTokens??null,cacheReadTokens:e.cacheReadTokens??null,cacheCreationTokens:e.cacheCreationTokens??null,toolDefChars:e.toolDefChars??null,systemChars:e.systemChars??null,freshToolChars:e.freshToolChars??null,tailResultChars:e.tailResultChars??null,tailTextChars:e.tailTextChars??null,blockedToolCalls:e.blockedToolCalls??null,skillsFired:Array.isArray(e.skillsTriggered)?e.skillsTriggered:[]}}static mapCompaction(e){return{timestamp:e.ts,command:e.command??null,originalChars:e.originalChars??null,outputChars:e.outputChars??null,charsSaved:e.charsSaved??null,estTokensSaved:e.estTokensSaved??null,originalLines:e.originalLines??null,outputLines:e.outputLines??null,elidedLines:e.elidedLines??null}}readNewLines(){let e=this.readCursor(),t;try{t=p.statSync(this.metricsFile).size}catch{return{lines:[],newOffset:e}}if(e>t&&(e=0),e===t)return{lines:[],newOffset:e};let n=p.openSync(this.metricsFile,"r");try{let o=t-e,s=Buffer.alloc(o);p.readSync(n,s,0,o,e);let i=s.toString("utf8"),c=i.lastIndexOf(`
|
|
29
|
+
`);if(c<0)return{lines:[],newOffset:e};let l=i.slice(0,c+1),y=e+Buffer.byteLength(l,"utf8");return{lines:l.split(`
|
|
30
|
+
`).filter(T=>T.trim().length>0),newOffset:y}}finally{p.closeSync(n)}}readCursor(){try{let e=JSON.parse(p.readFileSync(this.cursorFile,"utf8"));return typeof e.offset=="number"&&e.offset>=0?e.offset:0}catch{return 0}}writeCursor(e){try{p.mkdirSync(ie.dirname(this.cursorFile),{recursive:!0}),p.writeFileSync(this.cursorFile,JSON.stringify({offset:e,uploadedAt:new Date().toISOString()}))}catch{}}async postAll(e){for(let t=0;t<e.turns.length;t+=this.batchSize)await this.postBatch({turns:e.turns.slice(t,t+this.batchSize),compactions:[]});for(let t=0;t<e.compactions.length;t+=this.batchSize)await this.postBatch({turns:[],compactions:e.compactions.slice(t,t+this.batchSize)})}async postBatch(e){let t=`${this.apiUrl}/api/v1/workspaces/${this.workspaceId}/usage-telemetry`,n=new AbortController,o=setTimeout(()=>n.abort(),this.timeoutMs);try{let s=await this.fetchImpl(t,{method:"POST",headers:{Authorization:`Bearer ${this.token}`,"content-type":"application/json",accept:"application/json"},body:JSON.stringify(e),signal:n.signal});if(!s.ok)throw new Error(`usage-telemetry upload returned ${s.status}`)}finally{clearTimeout(o)}}};var I=a(require("fs")),ae=a(require("os")),u=a(require("path")),P=class r{constructor(e){this.repoRoot=e;this.config=r.readJson(u.join(e,".dezycro","config.json"))}repoRoot;config;static at(e=process.cwd()){return new r(r.findRepoRoot(e)??u.resolve(e))}get workspaceId(){return r.env("DZ_WORKSPACE_ID")??r.str(this.config?.workspaceId)}get tenantId(){return r.env("DZ_TENANT_ID")??r.str(this.config?.tenantId)}get projectId(){return r.str(this.config?.projectId)}get apiUrl(){let e=r.env("DZ_API_URL");if(e)return r.trimSlash(e);let t=Array.isArray(this.config?.environments)?this.config?.environments:[],n=t.find(s=>r.isObj(s)&&s.default===!0)??t[0],o=r.isObj(n)?r.str(n.apiUrl):null;return o?r.trimSlash(o):null}get token(){let e=r.env("DZ_TOKEN");if(e)return e;let t=[u.join(this.repoRoot,".dezycro","companion-token.local.json"),u.join(ae.homedir(),".dezycro","token.json")];for(let n of t){let o=r.str(r.readJson(n)?.token);if(o)return o}return null}activeFeature(){let e=r.readJson(u.join(this.repoRoot,".dezycro","active-feature.json")),t=r.str(e?.featureId);if(!t)return null;let n=r.str(e?.workspaceId)??this.workspaceId;return n?{workspaceId:n,featureId:t}:null}connection(){let e=this.apiUrl;if(!e)return{error:"no apiUrl (set DZ_API_URL or .dezycro/config.json environments[].apiUrl)"};let t=this.workspaceId;if(!t)return{error:"no workspaceId (set DZ_WORKSPACE_ID or .dezycro/config.json workspaceId)"};let n=this.token;return n?{apiUrl:e,workspaceId:t,token:n}:{error:"no token (run `dezycro setup` or set DZ_TOKEN)"}}static findRepoRoot(e){let t=u.resolve(e||process.cwd());for(let n=0;n<64;n+=1){if(I.existsSync(u.join(t,".dezycro")))return t;let o=u.dirname(t);if(o===t)return null;t=o}return null}static readJson(e){try{return JSON.parse(I.readFileSync(e,"utf8"))}catch{return null}}static isObj(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}static str(e){return typeof e=="string"&&e.length>0?e:null}static env(e){let t=process.env[e];return t&&t.length>0?t:null}static trimSlash(e){return e.replace(/\/$/,"")}};var z=class r{run(e){if(e.includes("--upload-dry-run")){this.uploadDryRun();return}let t=k.defaultFile(),n=k.fromFile(t);if(e.includes("--json")){process.stdout.write(`${JSON.stringify(n,null,2)}
|
|
31
|
+
`);return}process.stdout.write(r.format(n,t))}async uploadDryRun(){let e=l=>{process.stdout.write(`${l}
|
|
32
|
+
`)},t=P.at(process.cwd()),n=t.connection(),o=k.defaultFile(),s=ce.join(g.dir(),"gateway.upload-cursor.json"),i="error"in n?{apiUrl:"(unset)",workspaceId:t.workspaceId??"(unset)",token:"(unset)"}:n,c=await new C({...i,metricsFile:o,cursorFile:s}).upload({dryRun:!0});e("Dezycro telemetry \u2014 dry run (nothing sent)"),e(` metrics file ${o}`),e(` target ${i.apiUrl}/api/v1/workspaces/${i.workspaceId}/usage-telemetry`),"error"in n&&e(` connection NOT configured \u2014 ${n.error}`),e(` pending upload ${c.turns} turn + ${c.compactions} compaction event(s) since the cursor`)}static format(e,t){if(e.turns===0&&e.compactions===0)return`No metrics yet at ${t}
|
|
33
|
+
(They're written as you run agents through \`dezycro run\`. DZ_METRICS=0 disables.)
|
|
34
|
+
`;let n=l=>l.toLocaleString("en-US"),o=l=>n(Math.round(l/4)),s=l=>e.turns>0?n(Math.round(l/e.turns)):"0",i=[];i.push("Dezycro gateway metrics"),i.push(` ${t}`),e.firstTs&&e.lastTs&&i.push(` ${e.firstTs} \u2192 ${e.lastTs}`),i.push(""),i.push(`Turns: ${n(e.turns)} errors: ${n(e.errorTurns)} model time: ${(e.totalDurationMs/1e3).toFixed(0)}s`);let c=[`fresh input ~${s(e.inputTokens)} tok/turn`,`output ~${s(e.outputTokens)} tok/turn`];if(e.cacheReadTokens+e.inputTokens>0){let l=100*e.cacheReadTokens/(e.cacheReadTokens+e.inputTokens);c.push(`cache hit ${l.toFixed(1)}%`)}return i.push(c.join(" \xB7 ")),i.push(""),i.push("Where the tokens are (per request \u2014 content-free)"),i.push(` tool definitions ${n(e.maxToolDefChars)} chars (~${o(e.maxToolDefChars)} tok) \u2190 fixed per-request cost; schema-bloat lever`),i.push(` system prompt ${n(e.maxSystemChars)} chars (~${o(e.maxSystemChars)} tok)`),i.push(` fresh tool results ${n(e.tailResultChars)} chars (~${o(e.tailResultChars)} tok) total \xB7 biggest single ${n(e.maxFreshToolChars)} chars (~${o(e.maxFreshToolChars)} tok)`),i.push(` fresh user text ${n(e.tailTextChars)} chars (~${o(e.tailTextChars)} tok) total`),i.push(""),i.push("Cache"),i.push(` read ${n(e.cacheReadTokens)} tok \xB7 create ${n(e.cacheCreationTokens)} tok`),e.maxCacheCreationTokens>0&&i.push(` largest prefix write ${n(e.maxCacheCreationTokens)} tok (a spike here = cache-bust \u2014 the costly case)`),i.push(""),i.push("Request-side compaction opportunity (not yet compacted)"),i.push(` turns with >1k-tok fresh tool output ${n(e.bigToolTurns)}`),i.push(" (fresh tool results are the safe headroom; reversible elision is the lever \u2014 needs sizing first)"),i.push(""),i.push("Source-side compaction (shell output \u2014 already applied)"),i.push(` est. tokens saved ${n(e.estTokensSaved)} over ${n(e.compactions)} compaction(s)${r.breakdown(e.commands)}`),i.push(` lines elided ${n(e.linesElided)}`),i.push(""),i.push(`Totals: input ${n(e.inputTokens)} \xB7 output ${n(e.outputTokens)} \xB7 cache read ${n(e.cacheReadTokens)} \xB7 cache create ${n(e.cacheCreationTokens)}`),i.push(`Gate blocks: ${n(e.gateBlocks)}`),i.push(`Skills fired:${r.breakdown(e.skillsTriggered)||" none"}`),i.push(`Models:${r.breakdown(e.models)||" (none)"}`),i.push(""),`${i.join(`
|
|
35
|
+
`)}
|
|
36
|
+
`}static breakdown(e){let t=Object.entries(e).sort((n,o)=>o[1]-n[1]);return t.length===0?"":` ${t.map(([n,o])=>`${n}=${o}`).join(", ")}`}};var le=`dezycro \u2014 Dezycro for AI coding agents
|
|
37
|
+
|
|
38
|
+
Usage: dezycro <command> [args]
|
|
39
|
+
|
|
40
|
+
Commands:
|
|
41
|
+
run [--port <n>] <agent> [args] Run a coding agent (e.g. claude) through the local gateway
|
|
42
|
+
setup Configure the Dezycro MCP connection and save your API key
|
|
43
|
+
resolve-auth [path] Print persona credential exports for the verifier (use with eval)
|
|
44
|
+
completion <bash|zsh|fish> Print a shell-completion script (e.g. eval "$(dezycro completion zsh)")
|
|
45
|
+
metrics [--json] Summarize local usage metrics (tokens used + saved, skills, models)
|
|
46
|
+
metrics --upload-dry-run Preview the content-free telemetry the daemon would upload (sends nothing)
|
|
47
|
+
|
|
48
|
+
Gateway control: dezycro run --status dezycro run --stop
|
|
49
|
+
Custom port: dezycro run --port <n> <agent> (overrides DZ_PORT, default 13579)`,J=class{run(e){let[t,...n]=e;switch(t){case"run":new x().run(n);return;case"setup":new R().run();return;case"resolve-auth":new D().run(n[0]);return;case"completion":new $().run(n);return;case"metrics":new z().run(n);return;case void 0:case"help":case"-h":case"--help":console.log(le);return;default:console.error(`dezycro: unknown command '${t}'
|
|
50
|
+
`),console.error(le),process.exit(2)}}};new J().run(process.argv.slice(2));
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";var E=Object.create;var k=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var N=Object.getOwnPropertyNames;var R=Object.getPrototypeOf,j=Object.prototype.hasOwnProperty;var C=(s,t,e,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of N(t))!j.call(s,n)&&n!==e&&k(s,n,{get:()=>t[n],enumerable:!(o=I(t,n))||o.enumerable});return s};var a=(s,t,e)=>(e=s!=null?E(R(s)):{},C(t||!s||!s.__esModule?k(e,"default",{value:s,enumerable:!0}):e,s));var r=a(require("node:fs")),b=a(require("node:os")),p=a(require("node:path"));var S=a(require("node:fs")),c=a(require("node:path")),l=class s{static MAX_DEPTH=6;static dir(){let t=__dirname;for(let e=0;e<s.MAX_DEPTH;e+=1){if(S.existsSync(c.join(t,"package.json")))return t;t=c.dirname(t)}return c.resolve(__dirname,"..","..")}static file(...t){return c.join(s.dir(),...t)}};var h=a(require("node:fs")),P=a(require("node:path")),m=class{writeCommandFiles(t,e,o){h.mkdirSync(t,{recursive:!0});let n=[];for(let i of e)h.copyFileSync(i.sourcePath,P.join(t,o(i.name))),n.push(i.name);return n}};var d=class s extends m{static PLUGIN_ROOT_TOKEN="${CLAUDE_PLUGIN_ROOT}";id="claude-code";displayName="Claude Code";home=b.homedir();commandsDir=p.join(this.home,".claude","commands","dezycro");settingsPath=p.join(this.home,".claude","settings.json");hooksJsonPath=l.file("hooks","hooks.json");runnerPath=l.file("hooks","companion-runner");isPresent(){return!0}install(t){return{commands:this.writeCommandFiles(this.commandsDir,t,o=>`${o}.md`),messages:["commands \u2192 ~/.claude/commands/dezycro/",this.mergeHooks()]}}mergeHooks(){try{let t=JSON.parse(r.readFileSync(this.hooksJsonPath,"utf8")),e=s.inlinePluginRoot(t.hooks||{},l.dir()),o={hooks:{}};if(r.existsSync(this.settingsPath))try{o=JSON.parse(r.readFileSync(this.settingsPath,"utf8"))}catch{o={hooks:{}}}o.hooks||(o.hooks={});let n=JSON.stringify(o.hooks);for(let f of Object.keys(e)){let x=(Array.isArray(o.hooks[f])?o.hooks[f]:[]).filter(O=>!s.isDezycroEntry(O));o.hooks[f]=[...x,...e[f]]}let i="Companion hooks already present in ~/.claude/settings.json";n!==JSON.stringify(o.hooks)&&(r.mkdirSync(p.dirname(this.settingsPath),{recursive:!0}),r.writeFileSync(this.settingsPath,JSON.stringify(o,null,2)+`
|
|
3
|
+
`),i="Companion hooks merged into ~/.claude/settings.json");try{r.chmodSync(this.runnerPath,493)}catch{}return i}catch(t){return`could not install Companion hooks automatically (${t.message}) \u2014 see ${this.hooksJsonPath}`}}static inlinePluginRoot(t,e){return JSON.parse(JSON.stringify(t,(o,n)=>typeof n=="string"?n.replaceAll(`"${s.PLUGIN_ROOT_TOKEN}"`,e).replaceAll(s.PLUGIN_ROOT_TOKEN,e):n))}static isDezycroEntry(t){return!t||typeof t!="object"?!1:(Array.isArray(t.hooks)?t.hooks:[]).some(o=>o&&typeof o.command=="string"&&o.command.includes("/hooks/companion-runner"))}};var u=a(require("node:fs")),v=a(require("node:path"));var g=class{static load(){let t=l.file("skills"),e;try{e=u.readdirSync(t,{withFileTypes:!0})}catch{return[]}let o=[];for(let n of e){if(!n.isDirectory())continue;let i=v.join(t,n.name,"SKILL.md");u.existsSync(i)&&o.push({name:n.name,sourcePath:i})}return o}};function A(){return[new d]}var y=class{run(){let t=g.load(),e=!1;for(let o of A())o.isPresent()&&(e=!0,this.report(o.displayName,o.install(t)));e&&this.footer()}report(t,e){console.log(`
|
|
4
|
+
\u2713 ${t}: ${e.commands.length} Dezycro commands installed`),console.log(` ${e.commands.map(o=>`/dezycro:${o}`).join(", ")}`);for(let o of e.messages)console.log(` ${o}`)}footer(){console.log(`
|
|
5
|
+
Next: npx dezycro setup`),console.log(` Then: /dezycro:import-features
|
|
6
|
+
`),console.log(" Optional \u2014 run an agent through the local Dezycro gateway to get"),console.log(` skills/commands + verify guardrails on any agent: npx dezycro run claude
|
|
7
|
+
`)}};new y().run();
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dezycro-config.ts — one place to resolve a repo's Dezycro identity + platform
|
|
3
|
+
* connection from `.dezycro/config.json` (+ token files), with env overrides.
|
|
4
|
+
*
|
|
5
|
+
* Both the Companion hooks (command bus) and the gateway (metrics uploader) need
|
|
6
|
+
* the same handful of facts — workspace, api url, bearer token, active feature —
|
|
7
|
+
* so they all go through this class instead of re-reading the files ad hoc.
|
|
8
|
+
*
|
|
9
|
+
* Resolution precedence (env wins, so CI / a dev box can point elsewhere):
|
|
10
|
+
* apiUrl DZ_API_URL > config.environments[default].apiUrl
|
|
11
|
+
* workspaceId DZ_WORKSPACE_ID > config.workspaceId
|
|
12
|
+
* tenantId DZ_TENANT_ID > config.tenantId
|
|
13
|
+
* token DZ_TOKEN > .dezycro/companion-token.local.json > ~/.dezycro/token.json
|
|
14
|
+
*
|
|
15
|
+
* Reads are cheap and fail-open: a missing/unparseable file yields null, never throws.
|
|
16
|
+
*/
|
|
17
|
+
/** Everything required to make an authenticated platform call. */
|
|
18
|
+
export interface PlatformConnection {
|
|
19
|
+
apiUrl: string;
|
|
20
|
+
workspaceId: string;
|
|
21
|
+
token: string;
|
|
22
|
+
}
|
|
23
|
+
/** The active feature bound by `/dezycro:work`. */
|
|
24
|
+
export interface ActiveFeature {
|
|
25
|
+
workspaceId: string;
|
|
26
|
+
featureId: string;
|
|
27
|
+
}
|
|
28
|
+
export declare class DezycroConfig {
|
|
29
|
+
readonly repoRoot: string;
|
|
30
|
+
private readonly config;
|
|
31
|
+
constructor(repoRoot: string);
|
|
32
|
+
/** Build from a starting dir, walking up to the nearest `.dezycro/` (else the dir itself). */
|
|
33
|
+
static at(startDir?: string): DezycroConfig;
|
|
34
|
+
get workspaceId(): string | null;
|
|
35
|
+
get tenantId(): string | null;
|
|
36
|
+
get projectId(): string | null;
|
|
37
|
+
get apiUrl(): string | null;
|
|
38
|
+
get token(): string | null;
|
|
39
|
+
/** The active feature written by `/dezycro:work`, with workspace fallback to config. */
|
|
40
|
+
activeFeature(): ActiveFeature | null;
|
|
41
|
+
/** Everything needed for a platform call, or an error naming the first gap. */
|
|
42
|
+
connection(): PlatformConnection | {
|
|
43
|
+
error: string;
|
|
44
|
+
};
|
|
45
|
+
/** Nearest ancestor of `startDir` containing a `.dezycro/` dir, or null. */
|
|
46
|
+
static findRepoRoot(startDir: string | null | undefined): string | null;
|
|
47
|
+
private static readJson;
|
|
48
|
+
private static isObj;
|
|
49
|
+
private static str;
|
|
50
|
+
private static env;
|
|
51
|
+
private static trimSlash;
|
|
52
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* dezycro-config.ts — one place to resolve a repo's Dezycro identity + platform
|
|
4
|
+
* connection from `.dezycro/config.json` (+ token files), with env overrides.
|
|
5
|
+
*
|
|
6
|
+
* Both the Companion hooks (command bus) and the gateway (metrics uploader) need
|
|
7
|
+
* the same handful of facts — workspace, api url, bearer token, active feature —
|
|
8
|
+
* so they all go through this class instead of re-reading the files ad hoc.
|
|
9
|
+
*
|
|
10
|
+
* Resolution precedence (env wins, so CI / a dev box can point elsewhere):
|
|
11
|
+
* apiUrl DZ_API_URL > config.environments[default].apiUrl
|
|
12
|
+
* workspaceId DZ_WORKSPACE_ID > config.workspaceId
|
|
13
|
+
* tenantId DZ_TENANT_ID > config.tenantId
|
|
14
|
+
* token DZ_TOKEN > .dezycro/companion-token.local.json > ~/.dezycro/token.json
|
|
15
|
+
*
|
|
16
|
+
* Reads are cheap and fail-open: a missing/unparseable file yields null, never throws.
|
|
17
|
+
*/
|
|
18
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
21
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
22
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
23
|
+
}
|
|
24
|
+
Object.defineProperty(o, k2, desc);
|
|
25
|
+
}) : (function(o, m, k, k2) {
|
|
26
|
+
if (k2 === undefined) k2 = k;
|
|
27
|
+
o[k2] = m[k];
|
|
28
|
+
}));
|
|
29
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
30
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
31
|
+
}) : function(o, v) {
|
|
32
|
+
o["default"] = v;
|
|
33
|
+
});
|
|
34
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
35
|
+
var ownKeys = function(o) {
|
|
36
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
37
|
+
var ar = [];
|
|
38
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
39
|
+
return ar;
|
|
40
|
+
};
|
|
41
|
+
return ownKeys(o);
|
|
42
|
+
};
|
|
43
|
+
return function (mod) {
|
|
44
|
+
if (mod && mod.__esModule) return mod;
|
|
45
|
+
var result = {};
|
|
46
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
47
|
+
__setModuleDefault(result, mod);
|
|
48
|
+
return result;
|
|
49
|
+
};
|
|
50
|
+
})();
|
|
51
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
|
+
exports.DezycroConfig = void 0;
|
|
53
|
+
const fs = __importStar(require("fs"));
|
|
54
|
+
const os = __importStar(require("os"));
|
|
55
|
+
const path = __importStar(require("path"));
|
|
56
|
+
class DezycroConfig {
|
|
57
|
+
repoRoot;
|
|
58
|
+
config;
|
|
59
|
+
constructor(repoRoot) {
|
|
60
|
+
this.repoRoot = repoRoot;
|
|
61
|
+
this.config = DezycroConfig.readJson(path.join(repoRoot, '.dezycro', 'config.json'));
|
|
62
|
+
}
|
|
63
|
+
/** Build from a starting dir, walking up to the nearest `.dezycro/` (else the dir itself). */
|
|
64
|
+
static at(startDir = process.cwd()) {
|
|
65
|
+
return new DezycroConfig(DezycroConfig.findRepoRoot(startDir) ?? path.resolve(startDir));
|
|
66
|
+
}
|
|
67
|
+
get workspaceId() {
|
|
68
|
+
return DezycroConfig.env('DZ_WORKSPACE_ID') ?? DezycroConfig.str(this.config?.workspaceId);
|
|
69
|
+
}
|
|
70
|
+
get tenantId() {
|
|
71
|
+
return DezycroConfig.env('DZ_TENANT_ID') ?? DezycroConfig.str(this.config?.tenantId);
|
|
72
|
+
}
|
|
73
|
+
get projectId() {
|
|
74
|
+
return DezycroConfig.str(this.config?.projectId);
|
|
75
|
+
}
|
|
76
|
+
get apiUrl() {
|
|
77
|
+
const override = DezycroConfig.env('DZ_API_URL');
|
|
78
|
+
if (override) {
|
|
79
|
+
return DezycroConfig.trimSlash(override);
|
|
80
|
+
}
|
|
81
|
+
const environments = Array.isArray(this.config?.environments) ? this.config?.environments : [];
|
|
82
|
+
const def = environments.find((e) => DezycroConfig.isObj(e) && e.default === true) ?? environments[0];
|
|
83
|
+
const url = DezycroConfig.isObj(def) ? DezycroConfig.str(def.apiUrl) : null;
|
|
84
|
+
return url ? DezycroConfig.trimSlash(url) : null;
|
|
85
|
+
}
|
|
86
|
+
get token() {
|
|
87
|
+
const override = DezycroConfig.env('DZ_TOKEN');
|
|
88
|
+
if (override) {
|
|
89
|
+
return override;
|
|
90
|
+
}
|
|
91
|
+
// Project-local override first, then the user-global token from `dezycro setup`.
|
|
92
|
+
const candidates = [
|
|
93
|
+
path.join(this.repoRoot, '.dezycro', 'companion-token.local.json'),
|
|
94
|
+
path.join(os.homedir(), '.dezycro', 'token.json'),
|
|
95
|
+
];
|
|
96
|
+
for (const candidate of candidates) {
|
|
97
|
+
const token = DezycroConfig.str(DezycroConfig.readJson(candidate)?.token);
|
|
98
|
+
if (token) {
|
|
99
|
+
return token;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
/** The active feature written by `/dezycro:work`, with workspace fallback to config. */
|
|
105
|
+
activeFeature() {
|
|
106
|
+
const raw = DezycroConfig.readJson(path.join(this.repoRoot, '.dezycro', 'active-feature.json'));
|
|
107
|
+
const featureId = DezycroConfig.str(raw?.featureId);
|
|
108
|
+
if (!featureId) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
const workspaceId = DezycroConfig.str(raw?.workspaceId) ?? this.workspaceId;
|
|
112
|
+
return workspaceId ? { workspaceId, featureId } : null;
|
|
113
|
+
}
|
|
114
|
+
/** Everything needed for a platform call, or an error naming the first gap. */
|
|
115
|
+
connection() {
|
|
116
|
+
const apiUrl = this.apiUrl;
|
|
117
|
+
if (!apiUrl) {
|
|
118
|
+
return { error: 'no apiUrl (set DZ_API_URL or .dezycro/config.json environments[].apiUrl)' };
|
|
119
|
+
}
|
|
120
|
+
const workspaceId = this.workspaceId;
|
|
121
|
+
if (!workspaceId) {
|
|
122
|
+
return { error: 'no workspaceId (set DZ_WORKSPACE_ID or .dezycro/config.json workspaceId)' };
|
|
123
|
+
}
|
|
124
|
+
const token = this.token;
|
|
125
|
+
if (!token) {
|
|
126
|
+
return { error: 'no token (run `dezycro setup` or set DZ_TOKEN)' };
|
|
127
|
+
}
|
|
128
|
+
return { apiUrl, workspaceId, token };
|
|
129
|
+
}
|
|
130
|
+
/** Nearest ancestor of `startDir` containing a `.dezycro/` dir, or null. */
|
|
131
|
+
static findRepoRoot(startDir) {
|
|
132
|
+
let dir = path.resolve(startDir || process.cwd());
|
|
133
|
+
for (let i = 0; i < 64; i += 1) {
|
|
134
|
+
if (fs.existsSync(path.join(dir, '.dezycro'))) {
|
|
135
|
+
return dir;
|
|
136
|
+
}
|
|
137
|
+
const parent = path.dirname(dir);
|
|
138
|
+
if (parent === dir) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
dir = parent;
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
static readJson(file) {
|
|
146
|
+
try {
|
|
147
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
static isObj(v) {
|
|
154
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
155
|
+
}
|
|
156
|
+
static str(v) {
|
|
157
|
+
return typeof v === 'string' && v.length > 0 ? v : null;
|
|
158
|
+
}
|
|
159
|
+
static env(name) {
|
|
160
|
+
const v = process.env[name];
|
|
161
|
+
return v && v.length > 0 ? v : null;
|
|
162
|
+
}
|
|
163
|
+
static trimSlash(s) {
|
|
164
|
+
return s.replace(/\/$/, '');
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
exports.DezycroConfig = DezycroConfig;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";var A=Object.create;var x=Object.defineProperty;var D=Object.getOwnPropertyDescriptor;var M=Object.getOwnPropertyNames;var I=Object.getPrototypeOf,U=Object.prototype.hasOwnProperty;var N=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of M(e))!U.call(r,o)&&o!==t&&x(r,o,{get:()=>e[o],enumerable:!(n=D(e,o))||n.enumerable});return r};var k=(r,e,t)=>(t=r!=null?A(I(r)):{},N(e||!r||!r.__esModule?x(t,"default",{value:r,enumerable:!0}):t,r));var f=class r{static DEFAULTS={maxLines:400,head:160,tail:120,collapseRepeats:!0,collapseTests:!0};static IMPORTANT=/error|fail|fatal|exception|panic|traceback|warning|\bwarn\b|✗/i;static MAX_IMPORTANT=80;static PASS_MARKER=/(?:^\s*[✓√✔])|(?:^\s*--- PASS\b)|(?:^\s*PASS\s)|(?:^ok\s+\S)|(?:\.\.\.\s*ok\s*$)|(?:\bPASSED\b)/;static MIN_PASS_RUN=3;options;constructor(e={}){this.options={...r.DEFAULTS,...e}}compact(e){if(typeof e!="string"||e.length===0)return{text:e||"",originalLines:0,outputLines:0,elided:0};let t=e.split(`
|
|
3
|
+
`),n=t.length,o=t.map(r.applyCarriageReturns);this.options.collapseRepeats&&(o=r.collapseRepeats(o)),this.options.collapseTests&&(o=r.collapsePassingTests(o));let s=0;if(o.length>this.options.maxLines&&this.options.head+this.options.tail<o.length){let i=o.slice(0,this.options.head),a=o.slice(o.length-this.options.tail),h=o.slice(this.options.head,o.length-this.options.tail),d=r.importantLines(h);s=h.length-d.length;let B=`\u22EF ${s} lines hidden by Dezycro`+(d.length?` (kept ${d.length} error/warning line(s))`:"")+" (set DZ_NO_SHIM=1 for full output) \u22EF";o=[...i,B,...d,...a]}return{text:o.join(`
|
|
4
|
+
`),originalLines:n,outputLines:o.length,elided:s}}static importantLines(e){let t=[];for(let n of e)if(r.IMPORTANT.test(n)&&(t.push(n),t.length>=r.MAX_IMPORTANT))break;return t}static applyCarriageReturns(e){let t=e.lastIndexOf("\r");return t===-1?e:e.slice(t+1)}static collapsePassingTests(e){let t=[],n=0;for(;n<e.length;){if(!r.isPassingTestLine(e[n])){t.push(e[n]),n+=1;continue}let o=n+1;for(;o<e.length&&r.isPassingTestLine(e[o]);)o+=1;let s=o-n;if(s>=r.MIN_PASS_RUN)t.push(e[n]),t.push(` \u22EF ${s-1} more passing test(s) hidden by Dezycro \u22EF`);else for(let i=n;i<o;i+=1)t.push(e[i]);n=o}return t}static isPassingTestLine(e){return r.PASS_MARKER.test(e)&&!r.IMPORTANT.test(e)}static collapseRepeats(e){let t=[],n=0;for(;n<e.length;){let o=n+1;for(;o<e.length&&e[o]===e[n];)o+=1;let s=o-n;if(t.push(e[n]),s>2)t.push(` \u22EF (previous line repeated ${s}\xD7)`);else for(let i=1;i<s;i+=1)t.push(e[n]);n=o}return t}};var g=k(require("node:fs")),C=k(require("node:os")),l=k(require("node:path")),O="DZ_METRICS_DIR",P="DZ_METRICS",c=class r{file;constructor(e="gateway"){this.file=r.prepare(e)}static enabled(){return process.env[P]!=="0"}static dir(){let e=process.env[O];if(e)return e;let t=C.homedir();switch(process.platform){case"win32":return l.join(process.env.LOCALAPPDATA||l.join(t,"AppData","Local"),"Dezycro","metrics");case"darwin":return l.join(t,"Library","Application Support","Dezycro","metrics");default:return l.join(process.env.XDG_STATE_HOME||l.join(t,".local","state"),"dezycro","metrics")}}record(e){if(this.file)try{g.appendFileSync(this.file,`${JSON.stringify({ts:new Date().toISOString(),...e})}
|
|
5
|
+
`)}catch{}}static prepare(e){if(!r.enabled())return null;try{let t=r.dir();return g.mkdirSync(t,{recursive:!0}),l.join(t,`${e}.jsonl`)}catch{return null}}};var p={MessageStart:"message_start",ContentBlockStart:"content_block_start",ContentBlockDelta:"content_block_delta",ContentBlockStop:"content_block_stop",MessageDelta:"message_delta",MessageStop:"message_stop"};var v={UpdateTask:"update_task"},w={Status:"status"},T={Done:"DONE",Pushed:"PUSHED"};var u={SpecPublished:"specPublished",VerifiedRecently:"verifiedRecently"};var L={rules:[{nameIncludes:v.UpdateTask,inputField:w.Status,inputValues:[T.Done,T.Pushed],requireAll:[u.SpecPublished,u.VerifiedRecently],reason:"Cannot mark a task DONE/PUSHED yet."}]},y=class r{constructor(e,t){this.policy=e;this.state=t}policy;state;static STATE_PREDICATES={[u.SpecPublished]:e=>!!e&&e.needsSpecPublish!==!0,[u.VerifiedRecently]:e=>!e||!e.lastVerifyCompletionTs?!1:e.lastEditTs?Date.parse(e.lastVerifyCompletionTs)>=Date.parse(e.lastEditTs):!0};static FAILED_HINT={[u.SpecPublished]:"spec not published (controller/API changed) \u2014 run /dezycro:publish-spec",[u.VerifiedRecently]:"no passing verify since last edit \u2014 run /dezycro:verify"};decide(e,t){for(let n of this.policy?.rules??[]){if(!e||!e.includes(n.nameIncludes))continue;if(n.inputField){let a=t?.[n.inputField],h=(n.inputValues??[]).map(d=>String(d).toUpperCase());if(a==null||!h.includes(String(a).toUpperCase()))continue}let o=n.requireAll??[];if(o.length===0)return this.blocked(e,n.reason);let s=o.filter(a=>!r.STATE_PREDICATES[a]?.(this.state));if(s.length===0)continue;let i=s.map(a=>r.FAILED_HINT[a]||a).join("; ");return this.blocked(e,`${n.reason||"Blocked by Dezycro policy."} ${i}.`)}return null}blocked(e,t){return{toolName:e||"?",detail:t||"Blocked by Dezycro policy."}}};var ne=new Set([p.ContentBlockStart,p.ContentBlockDelta,p.ContentBlockStop]);var S=class r{run(){let e="";process.stdin.setEncoding("utf8"),process.stdin.on("data",t=>{e+=t}),process.stdin.on("end",()=>process.stdout.write(this.compact(e)))}compact(e){if(process.env.DZ_NO_SHIM==="1")return e;try{let t=new f().compact(e);return t.elided>0&&process.stderr.write(`[dezycro] compacted ${t.originalLines}\u2192${t.outputLines} lines
|
|
6
|
+
`),r.recordSavings(e,t),t.text.length&&!t.text.endsWith(`
|
|
7
|
+
`)?`${t.text}
|
|
8
|
+
`:t.text}catch{return e}}static recordSavings(e,t){if(!c.enabled())return;let n=Math.max(0,e.length-t.text.length);new c("gateway").record({type:"compaction",command:process.env.DZ_CMD??null,originalChars:e.length,outputChars:t.text.length,charsSaved:n,estTokensSaved:Math.round(n/4),originalLines:t.originalLines,outputLines:t.outputLines,elidedLines:t.elided})}};new S().run();
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";var ve=Object.create;var z=Object.defineProperty;var Ce=Object.getOwnPropertyDescriptor;var xe=Object.getOwnPropertyNames;var we=Object.getPrototypeOf,Re=Object.prototype.hasOwnProperty;var Ee=(n,e)=>{for(var t in e)z(n,t,{get:e[t],enumerable:!0})},oe=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of xe(e))!Re.call(n,o)&&o!==t&&z(n,o,{get:()=>e[o],enumerable:!(r=Ce(e,o))||r.enumerable});return n};var c=(n,e,t)=>(t=n!=null?ve(we(n)):{},oe(e||!n||!n.__esModule?z(t,"default",{value:n,enumerable:!0}):t,n)),Ae=n=>oe(z({},"__esModule",{value:!0}),n);var Pe={};Ee(Pe,{Gateway:()=>F,createGatewayServer:()=>Oe});module.exports=Ae(Pe);var ke=c(require("node:http")),Te=c(require("node:fs")),be=c(require("node:path")),Se=require("node:stream");var G=c(require("fs")),se=c(require("os")),g=c(require("path")),J=class n{constructor(e){this.repoRoot=e;this.config=n.readJson(g.join(e,".dezycro","config.json"))}repoRoot;config;static at(e=process.cwd()){return new n(n.findRepoRoot(e)??g.resolve(e))}get workspaceId(){return n.env("DZ_WORKSPACE_ID")??n.str(this.config?.workspaceId)}get tenantId(){return n.env("DZ_TENANT_ID")??n.str(this.config?.tenantId)}get projectId(){return n.str(this.config?.projectId)}get apiUrl(){let e=n.env("DZ_API_URL");if(e)return n.trimSlash(e);let t=Array.isArray(this.config?.environments)?this.config?.environments:[],r=t.find(s=>n.isObj(s)&&s.default===!0)??t[0],o=n.isObj(r)?n.str(r.apiUrl):null;return o?n.trimSlash(o):null}get token(){let e=n.env("DZ_TOKEN");if(e)return e;let t=[g.join(this.repoRoot,".dezycro","companion-token.local.json"),g.join(se.homedir(),".dezycro","token.json")];for(let r of t){let o=n.str(n.readJson(r)?.token);if(o)return o}return null}activeFeature(){let e=n.readJson(g.join(this.repoRoot,".dezycro","active-feature.json")),t=n.str(e?.featureId);if(!t)return null;let r=n.str(e?.workspaceId)??this.workspaceId;return r?{workspaceId:r,featureId:t}:null}connection(){let e=this.apiUrl;if(!e)return{error:"no apiUrl (set DZ_API_URL or .dezycro/config.json environments[].apiUrl)"};let t=this.workspaceId;if(!t)return{error:"no workspaceId (set DZ_WORKSPACE_ID or .dezycro/config.json workspaceId)"};let r=this.token;return r?{apiUrl:e,workspaceId:t,token:r}:{error:"no token (run `dezycro setup` or set DZ_TOKEN)"}}static findRepoRoot(e){let t=g.resolve(e||process.cwd());for(let r=0;r<64;r+=1){if(G.existsSync(g.join(t,".dezycro")))return t;let o=g.dirname(t);if(o===t)return null;t=o}return null}static readJson(e){try{return JSON.parse(G.readFileSync(e,"utf8"))}catch{return null}}static isObj(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}static str(e){return typeof e=="string"&&e.length>0?e:null}static env(e){let t=process.env[e];return t&&t.length>0?t:null}static trimSlash(e){return e.replace(/\/$/,"")}};var d={MessageStart:"message_start",ContentBlockStart:"content_block_start",ContentBlockDelta:"content_block_delta",ContentBlockStop:"content_block_stop",MessageDelta:"message_delta",MessageStop:"message_stop"},$={User:"user",Assistant:"assistant",System:"system"},y={Text:"text",ToolUse:"tool_use"},H={Text:"text_delta",InputJson:"input_json_delta"},x={ToolUse:"tool_use",EndTurn:"end_turn"};var ie={UpdateTask:"update_task"},ae={Status:"status"},q={Done:"DONE",Pushed:"PUSHED"};var b={SpecPublished:"specPublished",VerifiedRecently:"verifiedRecently"};var w={Anthropic:"anthropic",OpenAI:"openai"};var X={Health:"/__dz/health",Stop:"/__dz/stop"},R={EventStream:"text/event-stream",Json:"application/json"};var le="[Dezycro] If you changed any controller/API files, publish the spec and verify before marking work done.",k=class n{constructor(e,t){this.skills=e;this.policyLine=t}skills;policyLine;apply(e,t){let r=n.lastUserText(e),o=this.triggeredSkills(r),s=this.build(r);return s&&n.appendToLastUserMessage(e,t,s),{text:s,triggers:o}}triggeredSkills(e){let t=[];for(let r of Object.keys(this.skills??{}))e.includes(r)&&t.push(r);return t}static composition(e){return{toolDefChars:n.toolDefChars(e),systemChars:n.systemChars(e),...n.tailComposition(e)}}static emptyComposition(){return{toolDefChars:0,systemChars:0,tailResultChars:0,tailTextChars:0,freshToolChars:0}}static maxToolResultChars(e){return n.tailComposition(e).freshToolChars}static toolDefChars(e){return Array.isArray(e.tools)&&e.tools.length?JSON.stringify(e.tools).length:0}static systemChars(e){let t=e.system;if(typeof t=="string")return t.length;if(!Array.isArray(t))return 0;let r=0;for(let o of t)o&&typeof o.text=="string"&&(r+=o.text.length);return r}static tailComposition(e){let t=n.lastUserContent(e);if(typeof t=="string")return{tailResultChars:0,tailTextChars:t.length,freshToolChars:0};if(!Array.isArray(t))return{tailResultChars:0,tailTextChars:0,freshToolChars:0};let r=0,o=0,s=0;for(let i of t)if(i)if(i.type==="tool_result"){let a=n.textLength(i.content);r+=a,s=Math.max(s,a)}else typeof i.text=="string"&&(o+=i.text.length);return{tailResultChars:r,tailTextChars:o,freshToolChars:s}}static lastUserContent(e){let t=Array.isArray(e.messages)?e.messages:null;if(!t)return null;let r=t.length-1;for(;r>=0&&t[r].role!==$.User;)r-=1;if(r<0)return null;let o=t[r].content;return typeof o=="string"||Array.isArray(o)?o:null}static textLength(e){if(typeof e=="string")return e.length;if(!Array.isArray(e))return 0;let t=0;for(let r of e)r&&typeof r.text=="string"&&(t+=r.text.length);return t}build(e){let t=[];this.policyLine&&t.push(this.policyLine);for(let[r,o]of Object.entries(this.skills??{}))e.includes(r)&&t.push(o);return t.join(`
|
|
2
|
+
`)}static lastUserText(e){let t=Array.isArray(e.messages)?e.messages:null;if(!t)return"";for(let r=t.length-1;r>=0;r-=1){let o=t[r];if(o.role===$.User){if(typeof o.content=="string")return o.content;if(Array.isArray(o.content))return o.content.map(s=>typeof s=="string"?s:"text"in s&&typeof s.text=="string"?s.text:"").join(" ")}}return""}static appendToLastUserMessage(e,t,r){if(!r)return e;let o=Array.isArray(e.messages)?e.messages:null;if(!o||!o.length)return t===w.OpenAI&&typeof e.input=="string"&&(e.input+=`
|
|
3
|
+
|
|
4
|
+
${n.tagged(r)}`),e;let s=o.length-1;for(;s>=0&&o[s].role!==$.User;)s-=1;if(s<0)return e;let i=o[s],a=n.tagged(r);return typeof i.content=="string"?i.content+=`
|
|
5
|
+
|
|
6
|
+
${a}`:Array.isArray(i.content)&&i.content.push({type:y.Text,text:a}),e}static tagged(e){return`<dezycro-companion>
|
|
7
|
+
${e}
|
|
8
|
+
</dezycro-companion>`}};var Q={rules:[{nameIncludes:ie.UpdateTask,inputField:ae.Status,inputValues:[q.Done,q.Pushed],requireAll:[b.SpecPublished,b.VerifiedRecently],reason:"Cannot mark a task DONE/PUSHED yet."}]},A=class n{constructor(e,t){this.policy=e;this.state=t}policy;state;static STATE_PREDICATES={[b.SpecPublished]:e=>!!e&&e.needsSpecPublish!==!0,[b.VerifiedRecently]:e=>!e||!e.lastVerifyCompletionTs?!1:e.lastEditTs?Date.parse(e.lastVerifyCompletionTs)>=Date.parse(e.lastEditTs):!0};static FAILED_HINT={[b.SpecPublished]:"spec not published (controller/API changed) \u2014 run /dezycro:publish-spec",[b.VerifiedRecently]:"no passing verify since last edit \u2014 run /dezycro:verify"};decide(e,t){for(let r of this.policy?.rules??[]){if(!e||!e.includes(r.nameIncludes))continue;if(r.inputField){let a=t?.[r.inputField],l=(r.inputValues??[]).map(u=>String(u).toUpperCase());if(a==null||!l.includes(String(a).toUpperCase()))continue}let o=r.requireAll??[];if(o.length===0)return this.blocked(e,r.reason);let s=o.filter(a=>!n.STATE_PREDICATES[a]?.(this.state));if(s.length===0)continue;let i=s.map(a=>n.FAILED_HINT[a]||a).join("; ");return this.blocked(e,`${r.reason||"Blocked by Dezycro policy."} ${i}.`)}return null}blocked(e,t){return{toolName:e||"?",detail:t||"Blocked by Dezycro policy."}}};var De=new Set([d.ContentBlockStart,d.ContentBlockDelta,d.ContentBlockStop]),m=class n{constructor(e,t){this.name=e;this.data=t}name;data;static parse(e){let t=null,r=null;for(let s of e.split(`
|
|
9
|
+
`))s.startsWith("event:")?t=s.slice(6).trim():s.startsWith("data:")&&(r=s.slice(5).trim());let o=null;try{o=r?JSON.parse(r):null}catch{o=null}return o?new n(t??String(o.type??""),o):null}static parseStream(e){return e.split(`
|
|
10
|
+
|
|
11
|
+
`).filter(t=>t.trim().length>0).map(t=>n.parse(t)).filter(t=>t!==null)}static of(e){return new n(e.type,e)}static toolUseOf(e){return e?.type===y.ToolUse?e:null}get type(){return String(this.data.type??"")}get index(){return Number(this.data.index)}is(e){return this.type===e}get isContentBlockEvent(){return De.has(this.type)}get toolUse(){return n.toolUseOf(this.data.content_block)}get inputJsonChunk(){let e=this.data.delta;return e?.type===H.InputJson?e.partial_json??"":""}get usage(){let e=this.data.message,t=this.data.usage??e?.usage;return t?{inputTokens:t.input_tokens,outputTokens:t.output_tokens,cacheReadTokens:t.cache_read_input_tokens,cacheCreationTokens:t.cache_creation_input_tokens}:null}get model(){return this.data.message?.model??null}withIndex(e){return new n(this.name,{...this.data,index:e})}withStopReasonEnded(){let e=JSON.parse(JSON.stringify(this.data));return e.delta?.stop_reason===x.ToolUse&&(e.delta.stop_reason=x.EndTurn),new n(this.name,e)}serialize(){return`event: ${this.name}
|
|
12
|
+
data: ${JSON.stringify(this.data)}
|
|
13
|
+
|
|
14
|
+
`}};var D=class{constructor(e){this.reasons=e}reasons;get isEmpty(){return this.reasons.length===0}text(){let e=this.reasons.map(t=>`\u2022 ${t.toolName}: ${t.detail}`).join(`
|
|
15
|
+
`);return`\u26D4 Dezycro blocked ${this.reasons.length} tool call(s):
|
|
16
|
+
${e}
|
|
17
|
+
Do the required step, then retry.`}sseEvents(e){return[m.of({type:d.ContentBlockStart,index:e,content_block:{type:y.Text,text:""}}),m.of({type:d.ContentBlockDelta,index:e,delta:{type:H.Text,text:this.text()}}),m.of({type:d.ContentBlockStop,index:e})].map(t=>t.serialize()).join("")}};var E=class{next=0;mapped=new Map;take(e){let t=this.next++;return this.mapped.set(e,t),t}of(e){return this.mapped.get(e)??e}reserve(){return this.next++}};var T=class{constructor(e){this.engine=e}engine;blockedReasons=[];sawAllowedToolUse=!1;judge(e,t){let r=this.engine.decide(e,t);return r?(this.blockedReasons.push(r),!0):(this.sawAllowedToolUse=!0,!1)}parseToolInput(e){try{return e?JSON.parse(e):{}}catch{return{}}}get denial(){return new D(this.blockedReasons)}get blockedCount(){return this.blockedReasons.length}};var B=class extends T{rewrite(e){try{if(!e||!Array.isArray(e.content))return e;let t=e.content.filter(o=>{let s=m.toolUseOf(o);return s?!this.judge(s.name,s.input??{}):!0}),r=this.denial;return r.isEmpty||(t.push({type:y.Text,text:r.text()}),e.content=t,!this.sawAllowedToolUse&&e.stop_reason===x.ToolUse&&(e.stop_reason=x.EndTurn)),e}catch{return e}}};var I=class extends T{pending="";remapper=new E;openTool=null;denialEmitted=!1;push(e){this.pending+=e;let t="",r;for(;(r=this.pending.indexOf(`
|
|
18
|
+
|
|
19
|
+
`))!==-1;){let o=this.pending.slice(0,r);this.pending=this.pending.slice(r+2),o.trim()&&(t+=this.handle(o))}return t}flush(){let e="";return this.pending.trim()&&(e+=this.handle(this.pending),this.pending=""),e+this.emitDenialIfNeeded()}handle(e){let t=m.parse(e);if(!t)return`${e}
|
|
20
|
+
|
|
21
|
+
`;switch(t.type){case d.ContentBlockStart:return this.onBlockStart(t);case d.ContentBlockDelta:return this.onBlockDelta(t);case d.ContentBlockStop:return this.onBlockStop(t);case d.MessageDelta:return this.emitDenialIfNeeded()+(this.sawAllowedToolUse?t:t.withStopReasonEnded()).serialize();default:return t.serialize()}}onBlockStart(e){let t=e.toolUse;return t?(this.openTool={originalIndex:e.index,toolName:t.name,inputJson:"",bufferedEvents:[e]},""):e.withIndex(this.remapper.take(e.index)).serialize()}onBlockDelta(e){return this.openTool&&e.index===this.openTool.originalIndex?(this.openTool.inputJson+=e.inputJsonChunk,this.openTool.bufferedEvents.push(e),""):e.withIndex(this.remapper.of(e.index)).serialize()}onBlockStop(e){return this.openTool&&e.index===this.openTool.originalIndex?this.closeOpenTool(e):e.withIndex(this.remapper.of(e.index)).serialize()}closeOpenTool(e){let t=this.openTool;if(this.openTool=null,this.judge(t.toolName,this.parseToolInput(t.inputJson)))return"";let r=this.remapper.take(t.originalIndex);return t.bufferedEvents.map(s=>s.withIndex(r).serialize()).join("")+e.withIndex(r).serialize()}emitDenialIfNeeded(){let e=this.denial;return e.isEmpty||this.denialEmitted?"":(this.denialEmitted=!0,e.sseEvents(this.remapper.reserve()))}};var pe=require("node:util"),ue=require("tslog");var V=c(require("node:fs")),ce=c(require("node:os")),S=c(require("node:path")),Be="DZ_LOG_DIR",L=class n{stream;constructor(e){this.stream=n.open(e)}static dir(){let e=process.env[Be];if(e)return e;let t=ce.homedir();switch(process.platform){case"win32":return S.join(process.env.LOCALAPPDATA||S.join(t,"AppData","Local"),"Dezycro","logs");case"darwin":return S.join(t,"Library","Logs","Dezycro");default:return S.join(process.env.XDG_STATE_HOME||S.join(t,".local","state"),"dezycro","logs")}}write(e){(this.stream??process.stderr).write(e)}static open(e){try{let t=n.dir();return V.mkdirSync(t,{recursive:!0}),V.createWriteStream(S.join(t,`${e}.log`),{flags:"a"})}catch{return null}}};var ee={silly:0,trace:1,debug:2,info:3,warn:4,error:5,fatal:6},W=3,te="DZ_LOG_LEVEL";var Ie="{{dateIsoStr}} {{logLevelName}} [{{name}}] ",U=class n{constructor(e,t=new L(e),r=n.resolveMinLevel(process.env[te])){this.tag=e;this.impl=new ue.Logger({name:e,type:"pretty",minLevel:r,stylePrettyLogs:!1,prettyLogTimeZone:"UTC",prettyLogTemplate:Ie,hideLogPositionForProduction:!0,overwrite:{transportFormatted:(o,s,i)=>t.write(n.format(o,s,i))}})}tag;impl;silly(...e){this.impl.silly(...e)}trace(...e){this.impl.trace(...e)}debug(...e){this.impl.debug(...e)}info(...e){this.impl.info(...e)}warn(...e){this.impl.warn(...e)}error(...e){this.impl.error(...e)}fatal(...e){this.impl.fatal(...e)}static resolveMinLevel(e){return e?ee[e.trim().toLowerCase()]??W:W}static format(e,t,r){return`${[e.trimEnd(),...t.map(n.stringify),...r].join(" ")}
|
|
22
|
+
`}static stringify(e){return typeof e=="string"?e:(0,pe.inspect)(e,{depth:3,breakLength:1/0})}};var Z=c(require("node:fs")),me=c(require("node:path"));var de=c(require("node:fs")),v=c(require("node:path")),Y=class n{static MAX_DEPTH=6;static dir(){let e=__dirname;for(let t=0;t<n.MAX_DEPTH;t+=1){if(de.existsSync(v.join(e,"package.json")))return e;e=v.dirname(e)}return v.resolve(__dirname,"..","..")}static file(...e){return v.join(n.dir(),...e)}};var O=class n{constructor(e=n.defaultDir()){this.dir=e}dir;load(){return Promise.resolve(n.scan(this.dir))}static defaultDir(){return Y.file("skills")}static scan(e){let t={},r;try{r=Z.readdirSync(e,{withFileTypes:!0})}catch{return t}for(let o of r){if(!o.isDirectory())continue;let s=me.join(e,o.name,"SKILL.md"),i;try{i=Z.readFileSync(s,"utf8")}catch{continue}let{name:a,body:l}=n.parseSkill(i),u=a||o.name;t[`/dezycro:${u}`]=n.instructionFor(u,l)}return t}static parseSkill(e){if(e.startsWith("---")){let t=e.indexOf(`
|
|
23
|
+
---`,3);if(t!==-1){let r=e.slice(3,t),o=e.slice(t+4).trimStart();return{name:n.frontmatterName(r),body:o}}}return{name:null,body:e}}static frontmatterName(e){let t="name:";for(let r of e.split(`
|
|
24
|
+
`)){let o=r.trim();if(o.startsWith(t))return o.slice(t.length).trim()||null}return null}static instructionFor(e,t){return`The user invoked the Dezycro \`/dezycro:${e}\` command. Your harness may not support it natively \u2014 follow these instructions:
|
|
25
|
+
|
|
26
|
+
${t}`}};var P=class{constructor(e){this.sources=e}sources;async load(){return(await Promise.all(this.sources.map(t=>t.load()))).reduce((t,r)=>({...t,...r}),{})}};var K=c(require("node:fs")),fe=c(require("node:os")),C=c(require("node:path")),Le="DZ_METRICS_DIR",Ue="DZ_METRICS",h=class n{file;constructor(e="gateway"){this.file=n.prepare(e)}static enabled(){return process.env[Ue]!=="0"}static dir(){let e=process.env[Le];if(e)return e;let t=fe.homedir();switch(process.platform){case"win32":return C.join(process.env.LOCALAPPDATA||C.join(t,"AppData","Local"),"Dezycro","metrics");case"darwin":return C.join(t,"Library","Application Support","Dezycro","metrics");default:return C.join(process.env.XDG_STATE_HOME||C.join(t,".local","state"),"dezycro","metrics")}}record(e){if(this.file)try{K.appendFileSync(this.file,`${JSON.stringify({ts:new Date().toISOString(),...e})}
|
|
27
|
+
`)}catch{}}static prepare(e){if(!n.enabled())return null;try{let t=n.dir();return K.mkdirSync(t,{recursive:!0}),C.join(t,`${e}.jsonl`)}catch{return null}}};var N=class{pending="";modelId=null;totals={inputTokens:0,outputTokens:0,cacheReadTokens:0,cacheCreationTokens:0};feed(e){this.pending+=e;let t=this.pending.indexOf(`
|
|
28
|
+
|
|
29
|
+
`);for(;t!==-1;)this.observe(this.pending.slice(0,t)),this.pending=this.pending.slice(t+2),t=this.pending.indexOf(`
|
|
30
|
+
|
|
31
|
+
`)}get model(){return this.modelId}get usage(){return{...this.totals}}observe(e){let t=m.parse(e);if(!t)return;this.modelId??=t.model;let r=t.usage;r&&(r.inputTokens!=null&&(this.totals.inputTokens=r.inputTokens),r.outputTokens!=null&&(this.totals.outputTokens=r.outputTokens),r.cacheReadTokens!=null&&(this.totals.cacheReadTokens=r.cacheReadTokens),r.cacheCreationTokens!=null&&(this.totals.cacheCreationTokens=r.cacheCreationTokens))}};var he=c(require("node:fs")),ge=c(require("node:path"));var M=class n{static BIG_TOOL_CHARS=4e3;static defaultFile(){return ge.join(h.dir(),"gateway.jsonl")}static fromFile(e=n.defaultFile()){let t;try{t=he.readFileSync(e,"utf8")}catch{return n.empty()}return n.aggregate(t.split(`
|
|
32
|
+
`))}static aggregate(e){let t=n.empty();for(let r of e){let o=n.parse(r);o&&(o.type==="turn"?n.addTurn(t,o):o.type==="compaction"&&n.addCompaction(t,o),n.trackTime(t,o.ts))}return t}static addTurn(e,t){e.turns+=1,e.inputTokens+=n.num(t.inputTokens),e.outputTokens+=n.num(t.outputTokens),e.cacheReadTokens+=n.num(t.cacheReadTokens),e.cacheCreationTokens+=n.num(t.cacheCreationTokens),e.totalDurationMs+=n.num(t.durationMs),e.gateBlocks+=n.num(t.blockedToolCalls);let r=n.num(t.freshToolChars);if(e.maxFreshToolChars=Math.max(e.maxFreshToolChars,r),r>n.BIG_TOOL_CHARS&&(e.bigToolTurns+=1),e.maxToolDefChars=Math.max(e.maxToolDefChars,n.num(t.toolDefChars)),e.maxSystemChars=Math.max(e.maxSystemChars,n.num(t.systemChars)),e.tailResultChars+=n.num(t.tailResultChars),e.tailTextChars+=n.num(t.tailTextChars),e.maxCacheCreationTokens=Math.max(e.maxCacheCreationTokens,n.num(t.cacheCreationTokens)),typeof t.status=="number"&&t.status!==200&&(e.errorTurns+=1),n.bump(e.models,typeof t.model=="string"?t.model:"(unknown)"),Array.isArray(t.skillsTriggered))for(let o of t.skillsTriggered)n.bump(e.skillsTriggered,String(o))}static addCompaction(e,t){e.compactions+=1,e.charsSaved+=n.num(t.charsSaved),e.estTokensSaved+=n.num(t.estTokensSaved),e.linesElided+=n.num(t.elidedLines),n.bump(e.commands,typeof t.command=="string"?t.command:"(unknown)")}static trackTime(e,t){typeof t=="string"&&((!e.firstTs||t<e.firstTs)&&(e.firstTs=t),(!e.lastTs||t>e.lastTs)&&(e.lastTs=t))}static parse(e){if(!e.trim())return null;try{return JSON.parse(e)}catch{return null}}static bump(e,t){e[t]=(e[t]??0)+1}static num(e){return typeof e=="number"&&Number.isFinite(e)?e:0}static empty(){return{turns:0,inputTokens:0,outputTokens:0,cacheReadTokens:0,cacheCreationTokens:0,totalDurationMs:0,errorTurns:0,gateBlocks:0,maxFreshToolChars:0,bigToolTurns:0,maxToolDefChars:0,maxSystemChars:0,tailResultChars:0,tailTextChars:0,maxCacheCreationTokens:0,models:{},skillsTriggered:{},compactions:0,charsSaved:0,estTokensSaved:0,linesElided:0,commands:{},firstTs:null,lastTs:null}}};var f=c(require("node:fs")),ye=c(require("node:path")),j=class n{apiUrl;workspaceId;token;metricsFile;cursorFile;batchSize;timeoutMs;fetchImpl;constructor(e){this.apiUrl=e.apiUrl,this.workspaceId=e.workspaceId,this.token=e.token,this.metricsFile=e.metricsFile,this.cursorFile=e.cursorFile,this.batchSize=e.batchSize??1e3,this.timeoutMs=e.timeoutMs??15e3,this.fetchImpl=e.fetchImpl??fetch}async upload({dryRun:e=!1}={}){let{lines:t,newOffset:r}=this.readNewLines(),o=n.toBatch(t),s=o.turns.length+o.compactions.length,i={turns:o.turns.length,compactions:o.compactions.length,newOffset:r,sent:!1,dryRun:e};return e?i:s===0?(r!==this.readCursor()&&this.writeCursor(r),i):(await this.postAll(o),this.writeCursor(r),{...i,sent:!0})}static toBatch(e){let t=[],r=[];for(let o of e){let s;try{s=JSON.parse(o)}catch{continue}typeof s.ts=="string"&&(s.type==="turn"?t.push(n.mapTurn(s)):s.type==="compaction"&&r.push(n.mapCompaction(s)))}return{turns:t,compactions:r}}static mapTurn(e){return{timestamp:e.ts,provider:e.provider??null,model:e.model??null,status:e.status??null,durationMs:e.durationMs??null,inputTokens:e.inputTokens??null,outputTokens:e.outputTokens??null,cacheReadTokens:e.cacheReadTokens??null,cacheCreationTokens:e.cacheCreationTokens??null,toolDefChars:e.toolDefChars??null,systemChars:e.systemChars??null,freshToolChars:e.freshToolChars??null,tailResultChars:e.tailResultChars??null,tailTextChars:e.tailTextChars??null,blockedToolCalls:e.blockedToolCalls??null,skillsFired:Array.isArray(e.skillsTriggered)?e.skillsTriggered:[]}}static mapCompaction(e){return{timestamp:e.ts,command:e.command??null,originalChars:e.originalChars??null,outputChars:e.outputChars??null,charsSaved:e.charsSaved??null,estTokensSaved:e.estTokensSaved??null,originalLines:e.originalLines??null,outputLines:e.outputLines??null,elidedLines:e.elidedLines??null}}readNewLines(){let e=this.readCursor(),t;try{t=f.statSync(this.metricsFile).size}catch{return{lines:[],newOffset:e}}if(e>t&&(e=0),e===t)return{lines:[],newOffset:e};let r=f.openSync(this.metricsFile,"r");try{let o=t-e,s=Buffer.alloc(o);f.readSync(r,s,0,o,e);let i=s.toString("utf8"),a=i.lastIndexOf(`
|
|
33
|
+
`);if(a<0)return{lines:[],newOffset:e};let l=i.slice(0,a+1),u=e+Buffer.byteLength(l,"utf8");return{lines:l.split(`
|
|
34
|
+
`).filter(_=>_.trim().length>0),newOffset:u}}finally{f.closeSync(r)}}readCursor(){try{let e=JSON.parse(f.readFileSync(this.cursorFile,"utf8"));return typeof e.offset=="number"&&e.offset>=0?e.offset:0}catch{return 0}}writeCursor(e){try{f.mkdirSync(ye.dirname(this.cursorFile),{recursive:!0}),f.writeFileSync(this.cursorFile,JSON.stringify({offset:e,uploadedAt:new Date().toISOString()}))}catch{}}async postAll(e){for(let t=0;t<e.turns.length;t+=this.batchSize)await this.postBatch({turns:e.turns.slice(t,t+this.batchSize),compactions:[]});for(let t=0;t<e.compactions.length;t+=this.batchSize)await this.postBatch({turns:[],compactions:e.compactions.slice(t,t+this.batchSize)})}async postBatch(e){let t=`${this.apiUrl}/api/v1/workspaces/${this.workspaceId}/usage-telemetry`,r=new AbortController,o=setTimeout(()=>r.abort(),this.timeoutMs);try{let s=await this.fetchImpl(t,{method:"POST",headers:{Authorization:`Bearer ${this.token}`,"content-type":"application/json",accept:"application/json"},body:JSON.stringify(e),signal:r.signal});if(!s.ok)throw new Error(`usage-telemetry upload returned ${s.status}`)}finally{clearTimeout(o)}}};var F=class n{constructor(e,t){this.config=e;this.deps=t;this.injector=new k(t.skills,t.policyLine)}config;deps;static UPSTREAMS=[{match:e=>e.startsWith("/v1/messages"),base:"https://api.anthropic.com",provider:w.Anthropic},{match:e=>e.startsWith("/v1/chat/completions")||e.startsWith("/v1/responses")||e.startsWith("/v1/completions"),base:"https://api.openai.com",provider:w.OpenAI}];static HOP_BY_HOP_REQUEST=["host","content-length","connection","accept-encoding","transfer-encoding"];static HOP_BY_HOP_RESPONSE=["content-encoding","content-length","transfer-encoding","connection"];injector;logger=new U("gateway");metrics=new h("gateway");telemetryTimer=null;telemetryUploader=null;static async fromEnv(){let e={port:Number(process.env.PORT||13579),inject:process.env.INJECT==="1",gate:process.env.GATE==="1",capture:process.env.CAPTURE==="1",stateFile:process.env.DZ_STATE_FILE||null},t={skills:await n.loadSkills(),policy:n.loadJson(process.env.DZ_POLICY_FILE)||Q,policyLine:process.env.INJECT_POLICY_LINE==="0"?null:le,loadState:()=>n.loadJson(e.stateFile)};return new n(e,t)}static loadSkills(){return new P(n.skillSources()).load()}static skillSources(){return[new O]}createServer(){return ke.createServer((e,t)=>this.handle(e,t))}listen(){let e=this.createServer();return e.on("error",t=>{t.code==="EADDRINUSE"&&(this.logger.warn(`port ${this.config.port} already in use \u2014 a gateway is probably running.`),process.exit(1)),this.logger.error(`server error: ${t.message}`),process.exit(1)}),e.listen(this.config.port,"127.0.0.1",()=>{this.logger.info(`listening on http://127.0.0.1:${this.config.port} (inject=${this.config.inject} gate=${this.config.gate}) skills=${Object.keys(this.deps.skills).length}`)}),this.startTelemetry(),e}startTelemetry(){if(!h.enabled()||process.env.DZ_TELEMETRY==="0")return;let e=J.at(process.cwd()).connection();if("error"in e){this.logger.debug(`telemetry: off (${e.error})`);return}this.telemetryUploader=new j({...e,metricsFile:M.defaultFile(),cursorFile:be.join(h.dir(),"gateway.upload-cursor.json")});let t=Math.max(3e4,Number(process.env.DZ_TELEMETRY_INTERVAL_MS)||3e5),r=()=>{this.flushTelemetry()};this.telemetryTimer=setInterval(r,t),this.telemetryTimer.unref?.(),setTimeout(r,15e3).unref?.(),this.logger.info(`telemetry: background upload every ${Math.round(t/1e3)}s \u2192 ws ${e.workspaceId}`)}async flushTelemetry(){if(this.telemetryUploader)try{let e=await this.telemetryUploader.upload();e.sent&&this.logger.info(`telemetry: uploaded ${e.turns} turn + ${e.compactions} compaction event(s)`)}catch(e){this.logger.warn(`telemetry upload failed: ${e.message}`)}}handle(e,t){if(this.handleControlPlane(e,t))return;let r=n.pickUpstream(e.url||""),o=[];e.on("data",s=>o.push(s)),e.on("end",()=>this.forward(e,t,r,o))}handleControlPlane(e,t){return e.url===X.Health?(t.writeHead(200,{"content-type":R.Json}),t.end(JSON.stringify({ok:!0,dz:!0,pid:process.pid,port:this.config.port,inject:this.config.inject,gate:this.config.gate})),!0):e.url===X.Stop?(t.writeHead(200,{"content-type":R.Json}),t.end(JSON.stringify({ok:!0,stopping:!0})),this.telemetryTimer&&clearInterval(this.telemetryTimer),this.flushTelemetry().finally(()=>setTimeout(()=>process.exit(0),50)),setTimeout(()=>process.exit(0),3e3).unref?.(),!0):!1}async forward(e,t,r,o){let s=Date.now(),i=Buffer.concat(o),a=[],l=k.emptyComposition();if(this.config.inject&&r&&e.method==="POST"&&i.length){let p=this.injectBody(i.toString("utf8"),r.provider);i=Buffer.from(p.bodyText,"utf8"),a=p.triggers,l=p.composition}let u=process.env.FORCE_UPSTREAM||(r?r.base:"https://api.anthropic.com");try{let p=await fetch(u+e.url,{method:e.method,headers:n.buildForwardHeaders(e.headers),body:["GET","HEAD"].includes(e.method||"")?void 0:i,redirect:"manual"}),_=(p.headers.get("content-type")||"").toLowerCase();if(r?.provider===w.Anthropic&&p.ok&&p.body&&(this.config.gate||h.enabled())){let re=this.config.gate?new A(this.deps.policy,this.deps.loadState()):null,ne={startedAt:s,provider:r.provider,status:p.status,triggers:a,...l};if(_.includes(R.EventStream))return this.streamResponse(p,t,re,ne);if(_.includes(R.Json))return this.jsonResponse(p,t,re,ne)}t.writeHead(p.status,n.filterResponseHeaders(p.headers)),p.body?n.webToNode(p.body).pipe(t):t.end()}catch(p){this.logger.error(`upstream error: ${p.message}`),t.writeHead(502,{"content-type":R.Json}),t.end(JSON.stringify({error:"gateway upstream failed",detail:String(p)}))}}injectBody(e,t){try{let r=JSON.parse(e),{text:o,triggers:s}=this.injector.apply(r,t),i=h.enabled()?k.composition(r):k.emptyComposition();return o?(this.logger.debug(`-> ${t} injected(${o.split(`
|
|
35
|
+
`).length} line(s))`),{bodyText:JSON.stringify(r),triggers:s,composition:i}):{bodyText:e,triggers:s,composition:i}}catch{return{bodyText:e,triggers:[],composition:k.emptyComposition()}}}streamResponse(e,t,r,o){t.writeHead(e.status,n.filterResponseHeaders(e.headers));let s=r?new I(r):null,i=h.enabled()?new N:null,a=n.webToNode(e.body);a.setEncoding("utf8"),a.on("data",l=>{if(i&&i.feed(l),!s){t.write(l);return}try{let u=s.push(l);u&&t.write(u)}catch{t.write(l)}}),a.on("end",()=>{if(s)try{let l=s.flush();l&&t.write(l)}catch{}t.end(),this.recordTurn(o,i,s)}),a.on("error",()=>{try{t.end()}catch{}this.recordTurn(o,i,s)})}async jsonResponse(e,t,r,o){let s=await e.text(),i=s,a=null,l={model:null,usage:{}};try{let u=JSON.parse(s);l={model:u.model??null,usage:n.usageOf(u)},r&&(a=new B(r),i=JSON.stringify(a.rewrite(u)))}catch{i=s}a&&i!==s&&this.logger.info("\u26D4 gate rewrote response (blocked tool_use)"),t.writeHead(e.status,n.filterResponseHeaders(e.headers)),t.end(i),this.recordTurn(o,l,a)}recordTurn(e,t,r){let o=t?.usage??{};this.metrics.record({type:"turn",provider:e.provider,model:t?.model??null,durationMs:Date.now()-e.startedAt,status:e.status,inputTokens:o.inputTokens,outputTokens:o.outputTokens,cacheReadTokens:o.cacheReadTokens,cacheCreationTokens:o.cacheCreationTokens,skillsTriggered:e.triggers.length?e.triggers:void 0,blockedToolCalls:r?r.blockedCount:void 0,gated:r?r.blockedCount>0:void 0,freshToolChars:e.freshToolChars||void 0,toolDefChars:e.toolDefChars||void 0,systemChars:e.systemChars||void 0,tailResultChars:e.tailResultChars||void 0,tailTextChars:e.tailTextChars||void 0})}static usageOf(e){let t=e.usage;return t?{inputTokens:t.input_tokens,outputTokens:t.output_tokens,cacheReadTokens:t.cache_read_input_tokens,cacheCreationTokens:t.cache_creation_input_tokens}:{}}static pickUpstream(e){return n.UPSTREAMS.find(t=>t.match(e))??null}static buildForwardHeaders(e){let t={};for(let[r,o]of Object.entries(e))n.HOP_BY_HOP_REQUEST.includes(r.toLowerCase())||(typeof o=="string"?t[r]=o:Array.isArray(o)&&(t[r]=o.join(", ")));return t}static filterResponseHeaders(e){let t={};return e.forEach((r,o)=>{n.HOP_BY_HOP_RESPONSE.includes(o.toLowerCase())||(t[o]=r)}),t}static webToNode(e){return Se.Readable.fromWeb(e)}static loadJson(e){try{return e?JSON.parse(Te.readFileSync(e,"utf8")):null}catch{return null}}};function Oe(n,e){return new F(n,e).createServer()}require.main===module&&F.fromEnv().then(n=>n.listen());0&&(module.exports={Gateway,createGatewayServer});
|