@yagni-app/code-staging 0.0.0 → 0.1.0-staging.1002.1
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 +55 -0
- package/README.md +166 -0
- package/dist/branding.d.ts +25 -0
- package/dist/branding.js +27 -0
- package/dist/claudeCompat.d.ts +78 -0
- package/dist/claudeCompat.js +198 -0
- package/dist/cli.d.ts +55 -0
- package/dist/cli.js +326 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.js +28 -0
- package/dist/credentials.d.ts +41 -0
- package/dist/credentials.js +75 -0
- package/dist/distribution.d.ts +15 -0
- package/dist/distribution.js +45 -0
- package/dist/doctor.d.ts +94 -0
- package/dist/doctor.js +343 -0
- package/dist/extension/advisor.d.ts +84 -0
- package/dist/extension/advisor.js +101 -0
- package/dist/extension/askAdvisorTool.d.ts +83 -0
- package/dist/extension/askAdvisorTool.js +188 -0
- package/dist/extension/askYagniTool.d.ts +39 -0
- package/dist/extension/askYagniTool.js +63 -0
- package/dist/extension/bless.d.ts +45 -0
- package/dist/extension/bless.js +74 -0
- package/dist/extension/branding.d.ts +48 -0
- package/dist/extension/branding.js +119 -0
- package/dist/extension/chipEditor.d.ts +117 -0
- package/dist/extension/chipEditor.js +369 -0
- package/dist/extension/config.d.ts +112 -0
- package/dist/extension/config.js +108 -0
- package/dist/extension/costHud.d.ts +73 -0
- package/dist/extension/costHud.js +123 -0
- package/dist/extension/decisionCapture.d.ts +52 -0
- package/dist/extension/decisionCapture.js +66 -0
- package/dist/extension/decisions.d.ts +83 -0
- package/dist/extension/decisions.js +200 -0
- package/dist/extension/diagnostics.d.ts +41 -0
- package/dist/extension/diagnostics.js +110 -0
- package/dist/extension/index.d.ts +116 -0
- package/dist/extension/index.js +388 -0
- package/dist/extension/initDone.d.ts +28 -0
- package/dist/extension/initDone.js +66 -0
- package/dist/extension/initPass.d.ts +152 -0
- package/dist/extension/initPass.js +394 -0
- package/dist/extension/nextWorkTool.d.ts +51 -0
- package/dist/extension/nextWorkTool.js +80 -0
- package/dist/extension/permission.d.ts +91 -0
- package/dist/extension/permission.js +236 -0
- package/dist/extension/pipeline/activity.d.ts +37 -0
- package/dist/extension/pipeline/activity.js +151 -0
- package/dist/extension/pipeline/activityFeed.d.ts +59 -0
- package/dist/extension/pipeline/activityFeed.js +175 -0
- package/dist/extension/pipeline/budget.d.ts +48 -0
- package/dist/extension/pipeline/budget.js +68 -0
- package/dist/extension/pipeline/checkpoint.d.ts +31 -0
- package/dist/extension/pipeline/checkpoint.js +176 -0
- package/dist/extension/pipeline/eval.d.ts +168 -0
- package/dist/extension/pipeline/eval.js +182 -0
- package/dist/extension/pipeline/events.d.ts +56 -0
- package/dist/extension/pipeline/events.js +147 -0
- package/dist/extension/pipeline/findings.d.ts +42 -0
- package/dist/extension/pipeline/findings.js +144 -0
- package/dist/extension/pipeline/finish.d.ts +128 -0
- package/dist/extension/pipeline/finish.js +307 -0
- package/dist/extension/pipeline/goCommand.d.ts +128 -0
- package/dist/extension/pipeline/goCommand.js +972 -0
- package/dist/extension/pipeline/goCompareCommand.d.ts +48 -0
- package/dist/extension/pipeline/goCompareCommand.js +184 -0
- package/dist/extension/pipeline/goFlags.d.ts +40 -0
- package/dist/extension/pipeline/goFlags.js +46 -0
- package/dist/extension/pipeline/goStatusCommands.d.ts +59 -0
- package/dist/extension/pipeline/goStatusCommands.js +188 -0
- package/dist/extension/pipeline/invocation.d.ts +45 -0
- package/dist/extension/pipeline/invocation.js +64 -0
- package/dist/extension/pipeline/orchestrator.d.ts +131 -0
- package/dist/extension/pipeline/orchestrator.js +636 -0
- package/dist/extension/pipeline/personas.d.ts +44 -0
- package/dist/extension/pipeline/personas.js +248 -0
- package/dist/extension/pipeline/resilience.d.ts +85 -0
- package/dist/extension/pipeline/resilience.js +166 -0
- package/dist/extension/pipeline/resume.d.ts +18 -0
- package/dist/extension/pipeline/resume.js +106 -0
- package/dist/extension/pipeline/runRegistry.d.ts +112 -0
- package/dist/extension/pipeline/runRegistry.js +202 -0
- package/dist/extension/pipeline/runSession.d.ts +152 -0
- package/dist/extension/pipeline/runSession.js +167 -0
- package/dist/extension/pipeline/runState.d.ts +158 -0
- package/dist/extension/pipeline/runState.js +264 -0
- package/dist/extension/pipeline/runner.d.ts +79 -0
- package/dist/extension/pipeline/runner.js +298 -0
- package/dist/extension/pipeline/scrubSecrets.d.ts +15 -0
- package/dist/extension/pipeline/scrubSecrets.js +32 -0
- package/dist/extension/pipeline/stages.d.ts +51 -0
- package/dist/extension/pipeline/stages.js +113 -0
- package/dist/extension/pipeline/ticketResolution.d.ts +34 -0
- package/dist/extension/pipeline/ticketResolution.js +75 -0
- package/dist/extension/pipeline/types.d.ts +436 -0
- package/dist/extension/pipeline/types.js +48 -0
- package/dist/extension/pipeline/verify.d.ts +277 -0
- package/dist/extension/pipeline/verify.js +758 -0
- package/dist/extension/pipeline/workspace.d.ts +32 -0
- package/dist/extension/pipeline/workspace.js +73 -0
- package/dist/extension/pipeline/worktree.d.ts +107 -0
- package/dist/extension/pipeline/worktree.js +200 -0
- package/dist/extension/provider.d.ts +11 -0
- package/dist/extension/provider.js +26 -0
- package/dist/extension/recall.d.ts +93 -0
- package/dist/extension/recall.js +187 -0
- package/dist/extension/recordContextTool.d.ts +38 -0
- package/dist/extension/recordContextTool.js +85 -0
- package/dist/extension/recordDecisionTool.d.ts +52 -0
- package/dist/extension/recordDecisionTool.js +102 -0
- package/dist/extension/repoDocs.d.ts +81 -0
- package/dist/extension/repoDocs.js +260 -0
- package/dist/extension/resilientFetch.d.ts +60 -0
- package/dist/extension/resilientFetch.js +133 -0
- package/dist/extension/reviewTool.d.ts +34 -0
- package/dist/extension/reviewTool.js +81 -0
- package/dist/extension/spool.d.ts +92 -0
- package/dist/extension/spool.js +266 -0
- package/dist/extension/stateHome.d.ts +2 -0
- package/dist/extension/stateHome.js +6 -0
- package/dist/extension/subagents.d.ts +135 -0
- package/dist/extension/subagents.js +281 -0
- package/dist/extension/surface.d.ts +10 -0
- package/dist/extension/surface.js +12 -0
- package/dist/extension/todos.d.ts +110 -0
- package/dist/extension/todos.js +217 -0
- package/dist/extension/tokenProvider.d.ts +93 -0
- package/dist/extension/tokenProvider.js +234 -0
- package/dist/launch.d.ts +76 -0
- package/dist/launch.js +111 -0
- package/dist/login.d.ts +45 -0
- package/dist/login.js +142 -0
- package/dist/logout.d.ts +14 -0
- package/dist/logout.js +34 -0
- package/dist/paths.d.ts +31 -0
- package/dist/paths.js +87 -0
- package/dist/piPackage.d.ts +33 -0
- package/dist/piPackage.js +71 -0
- package/dist/profiles.d.ts +80 -0
- package/dist/profiles.js +222 -0
- package/dist/refresh.d.ts +70 -0
- package/dist/refresh.js +117 -0
- package/dist/upgrade.d.ts +97 -0
- package/dist/upgrade.js +284 -0
- package/package.json +39 -3
package/LICENSE.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# YAGNI Code License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 YAGNI, Inc. (https://yagni.app). All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software is a proprietary client for the YAGNI service. It is licensed to
|
|
6
|
+
you, not sold. Installing it from a public package registry does not make it
|
|
7
|
+
open source and grants no rights beyond this license.
|
|
8
|
+
|
|
9
|
+
## License grant
|
|
10
|
+
|
|
11
|
+
Subject to this license and the YAGNI Terms of Service, YAGNI, Inc. ("YAGNI")
|
|
12
|
+
grants you a limited, revocable, non-exclusive, non-transferable license to
|
|
13
|
+
install and run this software, in unmodified form, solely to access and use
|
|
14
|
+
the YAGNI service under an active YAGNI account or workspace.
|
|
15
|
+
|
|
16
|
+
## Restrictions
|
|
17
|
+
|
|
18
|
+
You may not:
|
|
19
|
+
|
|
20
|
+
- copy, modify, adapt, or create derivative works of the software;
|
|
21
|
+
- distribute, sublicense, rent, lease, sell, or otherwise transfer the
|
|
22
|
+
software or access to it;
|
|
23
|
+
- reverse engineer, decompile, or disassemble the software, except to the
|
|
24
|
+
extent applicable law does not allow that restriction;
|
|
25
|
+
- remove or alter any copyright, trademark, or other proprietary notices; or
|
|
26
|
+
- use the software to build a product or service that competes with the YAGNI
|
|
27
|
+
service.
|
|
28
|
+
|
|
29
|
+
## Ownership
|
|
30
|
+
|
|
31
|
+
The software and all intellectual property rights in it remain the exclusive
|
|
32
|
+
property of YAGNI and its licensors. Third-party packages installed alongside
|
|
33
|
+
this one are licensed under their own terms.
|
|
34
|
+
|
|
35
|
+
## Termination
|
|
36
|
+
|
|
37
|
+
This license ends automatically if you breach it or when your access to the
|
|
38
|
+
YAGNI service ends. When it ends, you must stop using the software and
|
|
39
|
+
uninstall it.
|
|
40
|
+
|
|
41
|
+
## Disclaimer of warranty
|
|
42
|
+
|
|
43
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
44
|
+
IMPLIED, INCLUDING WITHOUT LIMITATION THE WARRANTIES OF MERCHANTABILITY,
|
|
45
|
+
FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
|
|
46
|
+
|
|
47
|
+
## Limitation of liability
|
|
48
|
+
|
|
49
|
+
TO THE MAXIMUM EXTENT PERMITTED BY LAW, YAGNI WILL NOT BE LIABLE FOR ANY
|
|
50
|
+
INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, OR EXEMPLARY DAMAGES ARISING
|
|
51
|
+
FROM OR RELATED TO THE SOFTWARE, AND YAGNI'S TOTAL LIABILITY WILL NOT EXCEED
|
|
52
|
+
THE AMOUNTS YOU PAID FOR THE YAGNI SERVICE IN THE TWELVE MONTHS BEFORE THE
|
|
53
|
+
CLAIM AROSE.
|
|
54
|
+
|
|
55
|
+
Questions about this license: jack@yagni.app.
|
package/README.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# YAGNI Code
|
|
2
|
+
|
|
3
|
+
A terminal coding agent that already knows your company.
|
|
4
|
+
|
|
5
|
+
YAGNI Code is a coding agent for your terminal, like Claude Code or Codex — but
|
|
6
|
+
it is wired into the YAGNI app, your team's shared source of truth for how your
|
|
7
|
+
company and codebase actually work. One YAGNI login routes everything:
|
|
8
|
+
|
|
9
|
+
- the **model** runs on YAGNI-routed open-weight inference (you bring no API
|
|
10
|
+
keys), and
|
|
11
|
+
- the agent **boots already grounded** in your company — conventions,
|
|
12
|
+
decisions, ownership, priorities, the "why" — and can consult it mid-task with
|
|
13
|
+
a built-in `ask_yagni` tool.
|
|
14
|
+
|
|
15
|
+
**The bet:** a cheaper open-weight model that is *grounded in your context*
|
|
16
|
+
beats a stronger model that is *guessing* on your own codebase. Because YAGNI
|
|
17
|
+
Code and the YAGNI app are connected, you intervene less and the agent does more
|
|
18
|
+
correct, autonomous work than a coding agent that starts blank.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install -g @yagni-app/code
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The command it installs is `yagni`:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
yagni login # device-code auth against your YAGNI workspace
|
|
30
|
+
yagni doctor # green/red readiness checklist
|
|
31
|
+
yagni # launch the agent in the current repo
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Your workspace must have YAGNI Code enabled (the `yagni_code.enabled` feature
|
|
35
|
+
flag) — ask your workspace admin. Maintainers: the alpha checklist is
|
|
36
|
+
`docs/yagni-code-alpha.md`; publish-day steps live in
|
|
37
|
+
`docs/yagni-code-publishing.md`.
|
|
38
|
+
|
|
39
|
+
## Develop
|
|
40
|
+
|
|
41
|
+
Working in this monorepo, install from the repo instead of the registry. Build
|
|
42
|
+
the extension first — the CLI's build copies `pi-extension-yagni`'s `dist/` into
|
|
43
|
+
its own `dist/extension/`, and fails loudly if the extension is not built — then
|
|
44
|
+
build and link the CLI:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pnpm --filter pi-extension-yagni build
|
|
48
|
+
pnpm --filter @yagni-app/code build
|
|
49
|
+
npm i -g ./packages/yagni-code-cli # or `pnpm --filter @yagni-app/code link --global`
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Both packages are plain `tsc` builds that emit to `dist/`. The global
|
|
53
|
+
`yagni` command is a symlink into this repo's `dist/`, so after the initial
|
|
54
|
+
`npm i -g` you **never reinstall** — a rebuild is enough, and the next launch
|
|
55
|
+
picks it up.
|
|
56
|
+
|
|
57
|
+
Iterate with watch mode (one terminal each — run the commands bare, an inline
|
|
58
|
+
`#` comment is passed through as a filename by zsh and breaks `tsc`):
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pnpm --filter pi-extension-yagni dev
|
|
62
|
+
pnpm --filter @yagni-app/code dev
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Notes:
|
|
66
|
+
|
|
67
|
+
- **Order matters when changing both.** The CLI bundles the extension, so build
|
|
68
|
+
(or have a watcher running) `pi-extension-yagni` before `@yagni-app/code`.
|
|
69
|
+
- **Watch mode does not re-bundle.** The copy into `dist/extension/` happens on
|
|
70
|
+
`build`, not on `tsc --watch`, and the launcher prefers that copy. After an
|
|
71
|
+
extension change, run `pnpm --filter @yagni-app/code bundle` (cheap, copy
|
|
72
|
+
only) or a full `build`, or you will keep running the previous extension.
|
|
73
|
+
- **`yagni` is long-lived.** A rebuild only takes effect the next time you
|
|
74
|
+
launch it — restart the CLI to pick up new code.
|
|
75
|
+
- **Frontend/backend changes** (e.g. the `/v1` dev proxy) are served by
|
|
76
|
+
`pnpm dev` — Vite restarts on `vite.config.ts` changes and the backend runs
|
|
77
|
+
under `tsx` watch, so no manual rebuild there.
|
|
78
|
+
- **Tests:** `pnpm --filter @yagni-app/code test` (`node:test` via `tsx`).
|
|
79
|
+
|
|
80
|
+
## Use
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
yagni login # device-code flow: open the URL, enter the code, approve
|
|
84
|
+
yagni doctor # green/red readiness checklist (engine, token, backend, perms)
|
|
85
|
+
cd ~/code/my-repo
|
|
86
|
+
yagni # launch the agent in this repo (interactive)
|
|
87
|
+
yagni -p "explain the deploy pipeline; ask_yagni if unsure" # one-shot / print mode
|
|
88
|
+
yagni logout # revoke the token and clear it locally
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The agent runs on three opaque model tiers routed by YAGNI: `advanced` (the strongest,
|
|
92
|
+
for judgment), `standard` (for execution) and `efficient` (cheapest).
|
|
93
|
+
Interactive sessions default to `advanced`; pass `--model standard` or `--model
|
|
94
|
+
efficient` to switch a run. The `/go` pipeline routes its own steps automatically —
|
|
95
|
+
judgment steps (plan, review) on `advanced`, execution steps (map, implement, fix) on
|
|
96
|
+
`standard`.
|
|
97
|
+
|
|
98
|
+
`login` opens a device-code flow: it prints a short code and a URL. Open the URL
|
|
99
|
+
in a browser where you are signed in to YAGNI, enter the code, and approve. The
|
|
100
|
+
token is stored in the active environment's profile (`~/.yagni-code/profiles/<name>.json`,
|
|
101
|
+
mode `0600`). Re-run `login` any time to refresh it. See **Environments** below to
|
|
102
|
+
point the CLI at a non-prod backend.
|
|
103
|
+
|
|
104
|
+
Any arguments after `yagni` are passed straight through to the agent, so
|
|
105
|
+
flags like `--model` and `--print/-p` work.
|
|
106
|
+
|
|
107
|
+
### What "knows your company" means in practice
|
|
108
|
+
|
|
109
|
+
- On startup, YAGNI Code loads a brief of your workspace (vision, goals,
|
|
110
|
+
conventions) and works from it immediately — no prompting required.
|
|
111
|
+
- When a choice depends on something organization- or codebase-specific, the
|
|
112
|
+
agent calls **`ask_yagni`** and gets a cited answer from the YAGNI app instead
|
|
113
|
+
of guessing.
|
|
114
|
+
|
|
115
|
+
## Environments (profiles)
|
|
116
|
+
|
|
117
|
+
YAGNI Code keeps a **sticky active environment**. Each environment ("profile") binds a
|
|
118
|
+
base URL to its own stored token, so you can point the CLI at prod, a local backend, or
|
|
119
|
+
a staging host and switch between them without re-authenticating each time.
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
yagni use local # switch to the local dev backend (preset)
|
|
123
|
+
yagni login # authorize the active environment
|
|
124
|
+
yagni # runs against `local` from now on (sticky)
|
|
125
|
+
|
|
126
|
+
yagni use staging --base-url https://staging.example.com # define a custom env
|
|
127
|
+
yagni login
|
|
128
|
+
yagni profiles # list environments; the active one is *-marked
|
|
129
|
+
yagni use prod # switch back (sticky); prod is the default
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
- `prod` (→ `https://yagni.app`) is the default environment and a built-in preset.
|
|
133
|
+
- `local` (→ `http://localhost:3456`, the `pnpm dev` backend) is the other preset.
|
|
134
|
+
- Any other name needs `--base-url <url>` the first time you `use` it.
|
|
135
|
+
- Each environment gets its own isolated pi state under `~/.yagni-code/agent/<name>`,
|
|
136
|
+
so prod sessions never bleed into staging.
|
|
137
|
+
- `login` / `logout` always act on the **active** environment. `logout` clears its token
|
|
138
|
+
but keeps the binding, so a later `login` re-authorizes the same environment.
|
|
139
|
+
|
|
140
|
+
## Configuration
|
|
141
|
+
|
|
142
|
+
| Variable | Default | Purpose |
|
|
143
|
+
| ---------------- | ------------------------ | -------------------------------------------------- |
|
|
144
|
+
| `YAGNI_BASE_URL` | active environment's URL | Override the base URL for a **single run** (escape hatch). Prefer `yagni use` for anything sticky. |
|
|
145
|
+
|
|
146
|
+
Credentials live in `~/.yagni-code/profiles/<name>.json` (mode `0600`); the active
|
|
147
|
+
environment is recorded in `~/.yagni-code/config.json`. A pre-profiles
|
|
148
|
+
`~/.yagni-code/credentials.json` is migrated automatically on first run.
|
|
149
|
+
|
|
150
|
+
## Troubleshooting
|
|
151
|
+
|
|
152
|
+
- **`Not logged in to environment "<name>" … Run \`yagni login\` first.`** — no
|
|
153
|
+
valid token for the active environment; run `yagni login` (or `yagni use`
|
|
154
|
+
a different one). Run `yagni doctor` for a full green/red readiness check.
|
|
155
|
+
- **`Could not start login … Is YAGNI Code enabled for your workspace?`** —
|
|
156
|
+
YAGNI Code is not enabled for your workspace. Ask an admin to enable it.
|
|
157
|
+
- **The login code expired** — approval wasn't completed in time; run
|
|
158
|
+
`yagni login` again.
|
|
159
|
+
- **Talking to staging** — `yagni use staging --base-url https://<staging-host>`,
|
|
160
|
+
then `yagni login`.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
Maintainers: YAGNI Code is built on the open-source `pi` coding agent, configured
|
|
165
|
+
entirely through a first-party extension (no fork). The engineering details live
|
|
166
|
+
in [`ARCHITECTURE.md`](./ARCHITECTURE.md).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* YAGNI Code launcher branding.
|
|
3
|
+
*
|
|
4
|
+
* pi derives its entire app identity — terminal title, `process.title`, and the
|
|
5
|
+
* name of the env var it reads for its config dir — from `piConfig.name` in the
|
|
6
|
+
* package.json it loads from `PI_PACKAGE_DIR` (pi config.ts: `APP_NAME`,
|
|
7
|
+
* `APP_TITLE`, `ENV_AGENT_DIR`). We rebrand "pi"/"π" out of the chrome by
|
|
8
|
+
* pointing pi at a shadow package whose `piConfig.name` is set to this value.
|
|
9
|
+
*
|
|
10
|
+
* Because the SAME name drives `ENV_AGENT_DIR = `${APP_NAME.toUpperCase()}` +
|
|
11
|
+
* "_CODING_AGENT_DIR"`, this module is the single source of truth: the shadow
|
|
12
|
+
* package.json and the launcher's hermetic-dir env wiring must agree on the
|
|
13
|
+
* name, or pi reads a different env var than the launcher sets and silently
|
|
14
|
+
* falls back to `~/.pi/agent` — breaking the hermetic boundary that keeps the
|
|
15
|
+
* user's local provider keys from bypassing the YAGNI proxy.
|
|
16
|
+
*/
|
|
17
|
+
/** The app name pi will compute as `APP_NAME` (shown in the terminal title/tab). */
|
|
18
|
+
export declare const PI_CONFIG_NAME = "YAGNI";
|
|
19
|
+
/**
|
|
20
|
+
* The env var name pi reads for its config dir, derived exactly as pi does
|
|
21
|
+
* (`${APP_NAME.toUpperCase()}_CODING_AGENT_DIR`). For the default name this is
|
|
22
|
+
* `"YAGNI_CODING_AGENT_DIR"`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function agentDirEnvVar(name?: string): string;
|
|
25
|
+
//# sourceMappingURL=branding.d.ts.map
|
package/dist/branding.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* YAGNI Code launcher branding.
|
|
3
|
+
*
|
|
4
|
+
* pi derives its entire app identity — terminal title, `process.title`, and the
|
|
5
|
+
* name of the env var it reads for its config dir — from `piConfig.name` in the
|
|
6
|
+
* package.json it loads from `PI_PACKAGE_DIR` (pi config.ts: `APP_NAME`,
|
|
7
|
+
* `APP_TITLE`, `ENV_AGENT_DIR`). We rebrand "pi"/"π" out of the chrome by
|
|
8
|
+
* pointing pi at a shadow package whose `piConfig.name` is set to this value.
|
|
9
|
+
*
|
|
10
|
+
* Because the SAME name drives `ENV_AGENT_DIR = `${APP_NAME.toUpperCase()}` +
|
|
11
|
+
* "_CODING_AGENT_DIR"`, this module is the single source of truth: the shadow
|
|
12
|
+
* package.json and the launcher's hermetic-dir env wiring must agree on the
|
|
13
|
+
* name, or pi reads a different env var than the launcher sets and silently
|
|
14
|
+
* falls back to `~/.pi/agent` — breaking the hermetic boundary that keeps the
|
|
15
|
+
* user's local provider keys from bypassing the YAGNI proxy.
|
|
16
|
+
*/
|
|
17
|
+
/** The app name pi will compute as `APP_NAME` (shown in the terminal title/tab). */
|
|
18
|
+
export const PI_CONFIG_NAME = "YAGNI";
|
|
19
|
+
/**
|
|
20
|
+
* The env var name pi reads for its config dir, derived exactly as pi does
|
|
21
|
+
* (`${APP_NAME.toUpperCase()}_CODING_AGENT_DIR`). For the default name this is
|
|
22
|
+
* `"YAGNI_CODING_AGENT_DIR"`.
|
|
23
|
+
*/
|
|
24
|
+
export function agentDirEnvVar(name = PI_CONFIG_NAME) {
|
|
25
|
+
return `${name.toUpperCase()}_CODING_AGENT_DIR`;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=branding.js.map
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-config Claude Code repo compat.
|
|
3
|
+
*
|
|
4
|
+
* A repo that already carries Claude Code assets keeps working under YAGNI
|
|
5
|
+
* Code with nothing to edit: `.claude/skills` loads through pi's skill
|
|
6
|
+
* discovery and `.claude/commands/*.md` through pi's prompt templates (the
|
|
7
|
+
* same markdown-with-frontmatter format Claude Code uses — filename becomes
|
|
8
|
+
* the `/name` command). The launcher passes both as per-run `--skill` /
|
|
9
|
+
* `--prompt-template` flags, so nothing repo-local is ever written and no
|
|
10
|
+
* per-profile settings file accumulates stale per-repo paths.
|
|
11
|
+
*
|
|
12
|
+
* Trust: pi gates project-local resources behind a per-folder trust decision
|
|
13
|
+
* stored in `<agentDir>/trust.json`. Explicit CLI paths bypass that gate, so
|
|
14
|
+
* the launcher enforces the SAME decision itself: project `.claude` dirs are
|
|
15
|
+
* wired only when the folder is trusted. With no recorded decision it asks
|
|
16
|
+
* once (interactive runs only) and persists the answer into pi's own
|
|
17
|
+
* trust.json — one trust model, shared with pi's prompt, and a "no" is
|
|
18
|
+
* remembered too. User-scope `~/.claude` dirs are the user's own machine and
|
|
19
|
+
* load without ceremony, matching how pi treats `~/.agents/skills`.
|
|
20
|
+
*
|
|
21
|
+
* Set YAGNI_DISABLE_CLAUDE_COMPAT=1 to turn the whole bridge off.
|
|
22
|
+
*/
|
|
23
|
+
export declare const CLAUDE_COMPAT_DISABLE_ENV = "YAGNI_DISABLE_CLAUDE_COMPAT";
|
|
24
|
+
export interface ClaudeAssetDirs {
|
|
25
|
+
skills: string | null;
|
|
26
|
+
commands: string | null;
|
|
27
|
+
}
|
|
28
|
+
export interface ClaudeCompatProbe {
|
|
29
|
+
project: ClaudeAssetDirs;
|
|
30
|
+
user: ClaudeAssetDirs;
|
|
31
|
+
/** Nearest recorded trust decision for the cwd (pi trust.json), if any. */
|
|
32
|
+
projectTrust: boolean | null;
|
|
33
|
+
interactive: boolean;
|
|
34
|
+
}
|
|
35
|
+
export interface ClaudeCompatDecision {
|
|
36
|
+
/** Flags for user-scope dirs; always safe to pass. */
|
|
37
|
+
userArgv: string[];
|
|
38
|
+
/** Flags for project dirs; pass only once trust is established. */
|
|
39
|
+
projectArgv: string[];
|
|
40
|
+
/** Ask the user for a trust decision before wiring projectArgv. */
|
|
41
|
+
needsPrompt: boolean;
|
|
42
|
+
}
|
|
43
|
+
/** Pure policy: what to wire, and whether a trust prompt is required first. */
|
|
44
|
+
export declare function decideClaudeCompat(probe: ClaudeCompatProbe): ClaudeCompatDecision;
|
|
45
|
+
/** The `.claude` asset dirs under `root` that actually exist. */
|
|
46
|
+
export declare function probeClaudeDirs(root: string): ClaudeAssetDirs;
|
|
47
|
+
/**
|
|
48
|
+
* The nearest recorded decision for `cwd` or an ancestor, else null. Read-only
|
|
49
|
+
* and fail-soft: a missing or corrupt trust file just means "not decided".
|
|
50
|
+
*/
|
|
51
|
+
export declare function readTrustDecision(agentDir: string, cwd: string): boolean | null;
|
|
52
|
+
/**
|
|
53
|
+
* Persist a decision into pi's trust.json, holding pi's mkdir-based lock
|
|
54
|
+
* (`trust.json.lock`) so a concurrently-running session can't lose a write.
|
|
55
|
+
* Fail-soft: if the lock never frees, skip the write — worst case the user is
|
|
56
|
+
* asked again next launch.
|
|
57
|
+
*/
|
|
58
|
+
export declare function writeTrustDecision(agentDir: string, cwd: string, decision: boolean): void;
|
|
59
|
+
export interface ClaudeCompatDeps {
|
|
60
|
+
cwd: string;
|
|
61
|
+
/** The hermetic pi agent dir for the active profile (holds trust.json). */
|
|
62
|
+
agentDir: string;
|
|
63
|
+
homeDir?: string;
|
|
64
|
+
env?: NodeJS.ProcessEnv;
|
|
65
|
+
interactive?: boolean;
|
|
66
|
+
/** The one-time trust question; injectable for tests. */
|
|
67
|
+
confirm?: (question: string) => Promise<boolean>;
|
|
68
|
+
}
|
|
69
|
+
export declare function compatDisabled(env: NodeJS.ProcessEnv): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* The extra pi argv for this launch: user-scope dirs plus project dirs once
|
|
72
|
+
* trusted, asking (and persisting) the trust decision when needed. Everything
|
|
73
|
+
* is fail-soft — compat can only add flags, never break a launch.
|
|
74
|
+
*/
|
|
75
|
+
export declare function claudeCompatArgs(deps: ClaudeCompatDeps): Promise<string[]>;
|
|
76
|
+
/** Whether pi's trust file exists yet (used only for messaging). */
|
|
77
|
+
export declare function trustFileExists(agentDir: string): boolean;
|
|
78
|
+
//# sourceMappingURL=claudeCompat.d.ts.map
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-config Claude Code repo compat.
|
|
3
|
+
*
|
|
4
|
+
* A repo that already carries Claude Code assets keeps working under YAGNI
|
|
5
|
+
* Code with nothing to edit: `.claude/skills` loads through pi's skill
|
|
6
|
+
* discovery and `.claude/commands/*.md` through pi's prompt templates (the
|
|
7
|
+
* same markdown-with-frontmatter format Claude Code uses — filename becomes
|
|
8
|
+
* the `/name` command). The launcher passes both as per-run `--skill` /
|
|
9
|
+
* `--prompt-template` flags, so nothing repo-local is ever written and no
|
|
10
|
+
* per-profile settings file accumulates stale per-repo paths.
|
|
11
|
+
*
|
|
12
|
+
* Trust: pi gates project-local resources behind a per-folder trust decision
|
|
13
|
+
* stored in `<agentDir>/trust.json`. Explicit CLI paths bypass that gate, so
|
|
14
|
+
* the launcher enforces the SAME decision itself: project `.claude` dirs are
|
|
15
|
+
* wired only when the folder is trusted. With no recorded decision it asks
|
|
16
|
+
* once (interactive runs only) and persists the answer into pi's own
|
|
17
|
+
* trust.json — one trust model, shared with pi's prompt, and a "no" is
|
|
18
|
+
* remembered too. User-scope `~/.claude` dirs are the user's own machine and
|
|
19
|
+
* load without ceremony, matching how pi treats `~/.agents/skills`.
|
|
20
|
+
*
|
|
21
|
+
* Set YAGNI_DISABLE_CLAUDE_COMPAT=1 to turn the whole bridge off.
|
|
22
|
+
*/
|
|
23
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, rmdirSync, statSync, writeFileSync } from "node:fs";
|
|
24
|
+
import { homedir } from "node:os";
|
|
25
|
+
import { dirname, join, resolve } from "node:path";
|
|
26
|
+
export const CLAUDE_COMPAT_DISABLE_ENV = "YAGNI_DISABLE_CLAUDE_COMPAT";
|
|
27
|
+
function dirsToArgv(dirs) {
|
|
28
|
+
const argv = [];
|
|
29
|
+
if (dirs.skills)
|
|
30
|
+
argv.push("--skill", dirs.skills);
|
|
31
|
+
if (dirs.commands)
|
|
32
|
+
argv.push("--prompt-template", dirs.commands);
|
|
33
|
+
return argv;
|
|
34
|
+
}
|
|
35
|
+
/** Pure policy: what to wire, and whether a trust prompt is required first. */
|
|
36
|
+
export function decideClaudeCompat(probe) {
|
|
37
|
+
const projectArgv = dirsToArgv(probe.project);
|
|
38
|
+
return {
|
|
39
|
+
userArgv: dirsToArgv(probe.user),
|
|
40
|
+
projectArgv,
|
|
41
|
+
needsPrompt: probe.interactive && projectArgv.length > 0 && probe.projectTrust === null,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function isDirectory(path) {
|
|
45
|
+
try {
|
|
46
|
+
return statSync(path).isDirectory();
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** The `.claude` asset dirs under `root` that actually exist. */
|
|
53
|
+
export function probeClaudeDirs(root) {
|
|
54
|
+
const skills = join(root, ".claude", "skills");
|
|
55
|
+
const commands = join(root, ".claude", "commands");
|
|
56
|
+
return {
|
|
57
|
+
skills: isDirectory(skills) ? skills : null,
|
|
58
|
+
commands: isDirectory(commands) ? commands : null,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
// ── pi trust.json interop ───────────────────────────────────────────────────
|
|
62
|
+
/** Match pi's normalization closely enough for key lookups: resolve + realpath. */
|
|
63
|
+
function normalizeCwd(path) {
|
|
64
|
+
const resolved = resolve(path);
|
|
65
|
+
try {
|
|
66
|
+
return realpathSync(resolved);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return resolved;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function trustPath(agentDir) {
|
|
73
|
+
return join(agentDir, "trust.json");
|
|
74
|
+
}
|
|
75
|
+
function readTrustFile(path) {
|
|
76
|
+
try {
|
|
77
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
78
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
79
|
+
return {};
|
|
80
|
+
const data = {};
|
|
81
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
82
|
+
if (value === true || value === false || value === null)
|
|
83
|
+
data[key] = value;
|
|
84
|
+
}
|
|
85
|
+
return data;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return {};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The nearest recorded decision for `cwd` or an ancestor, else null. Read-only
|
|
93
|
+
* and fail-soft: a missing or corrupt trust file just means "not decided".
|
|
94
|
+
*/
|
|
95
|
+
export function readTrustDecision(agentDir, cwd) {
|
|
96
|
+
const data = readTrustFile(trustPath(agentDir));
|
|
97
|
+
let current = normalizeCwd(cwd);
|
|
98
|
+
while (true) {
|
|
99
|
+
const decision = data[current];
|
|
100
|
+
if (decision === true || decision === false)
|
|
101
|
+
return decision;
|
|
102
|
+
const parent = dirname(current);
|
|
103
|
+
if (parent === current)
|
|
104
|
+
return null;
|
|
105
|
+
current = parent;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Persist a decision into pi's trust.json, holding pi's mkdir-based lock
|
|
110
|
+
* (`trust.json.lock`) so a concurrently-running session can't lose a write.
|
|
111
|
+
* Fail-soft: if the lock never frees, skip the write — worst case the user is
|
|
112
|
+
* asked again next launch.
|
|
113
|
+
*/
|
|
114
|
+
export function writeTrustDecision(agentDir, cwd, decision) {
|
|
115
|
+
const path = trustPath(agentDir);
|
|
116
|
+
const lock = `${path}.lock`;
|
|
117
|
+
mkdirSync(agentDir, { recursive: true });
|
|
118
|
+
let locked = false;
|
|
119
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
120
|
+
try {
|
|
121
|
+
mkdirSync(lock);
|
|
122
|
+
locked = true;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
const start = Date.now();
|
|
127
|
+
while (Date.now() - start < 20) {
|
|
128
|
+
// Brief synchronous wait between lock attempts.
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (!locked)
|
|
133
|
+
return;
|
|
134
|
+
try {
|
|
135
|
+
const data = readTrustFile(path);
|
|
136
|
+
data[normalizeCwd(cwd)] = decision;
|
|
137
|
+
const sorted = {};
|
|
138
|
+
for (const key of Object.keys(data).sort())
|
|
139
|
+
sorted[key] = data[key];
|
|
140
|
+
const tmp = `${path}.tmp`;
|
|
141
|
+
writeFileSync(tmp, `${JSON.stringify(sorted, null, 2)}\n`, "utf8");
|
|
142
|
+
renameSync(tmp, path);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// Fail-soft: an unwritable trust file only costs a repeat prompt.
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
try {
|
|
149
|
+
rmdirSync(lock);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// Already gone.
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
export function compatDisabled(env) {
|
|
157
|
+
const value = env[CLAUDE_COMPAT_DISABLE_ENV];
|
|
158
|
+
return value !== undefined && value !== "" && value !== "0";
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* The extra pi argv for this launch: user-scope dirs plus project dirs once
|
|
162
|
+
* trusted, asking (and persisting) the trust decision when needed. Everything
|
|
163
|
+
* is fail-soft — compat can only add flags, never break a launch.
|
|
164
|
+
*/
|
|
165
|
+
export async function claudeCompatArgs(deps) {
|
|
166
|
+
const env = deps.env ?? process.env;
|
|
167
|
+
if (compatDisabled(env))
|
|
168
|
+
return [];
|
|
169
|
+
const home = deps.homeDir ?? homedir();
|
|
170
|
+
const projectTrust = readTrustDecision(deps.agentDir, deps.cwd);
|
|
171
|
+
const decision = decideClaudeCompat({
|
|
172
|
+
project: probeClaudeDirs(deps.cwd),
|
|
173
|
+
user: probeClaudeDirs(home),
|
|
174
|
+
projectTrust,
|
|
175
|
+
interactive: deps.interactive ?? (process.stdin.isTTY === true && process.stdout.isTTY === true),
|
|
176
|
+
});
|
|
177
|
+
const argv = [...decision.userArgv];
|
|
178
|
+
if (decision.projectArgv.length > 0) {
|
|
179
|
+
let trusted = projectTrust;
|
|
180
|
+
if (decision.needsPrompt && deps.confirm) {
|
|
181
|
+
try {
|
|
182
|
+
trusted = await deps.confirm("This folder has Claude Code assets (.claude/skills, .claude/commands). Trust this folder and load them?");
|
|
183
|
+
writeTrustDecision(deps.agentDir, deps.cwd, trusted);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
trusted = null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (trusted === true)
|
|
190
|
+
argv.push(...decision.projectArgv);
|
|
191
|
+
}
|
|
192
|
+
return argv;
|
|
193
|
+
}
|
|
194
|
+
/** Whether pi's trust file exists yet (used only for messaging). */
|
|
195
|
+
export function trustFileExists(agentDir) {
|
|
196
|
+
return existsSync(trustPath(agentDir));
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=claudeCompat.js.map
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* yagni — the thin launcher (package `yagni-code`, command `yagni`).
|
|
4
|
+
*
|
|
5
|
+
* yagni login device-code auth, stores a scoped token
|
|
6
|
+
* yagni logout revoke + delete the local token
|
|
7
|
+
* yagni doctor green/red readiness checklist
|
|
8
|
+
* yagni [pi args…] launch pi configured with the yagni provider +
|
|
9
|
+
* the ask_yagni tool (default command)
|
|
10
|
+
*
|
|
11
|
+
* The launcher holds NO model or business logic — it is credential plumbing
|
|
12
|
+
* plus a configured pi spawn. Everything that makes this "YAGNI Code" lives in
|
|
13
|
+
* pi-extension-yagni and the YAGNI backend.
|
|
14
|
+
*/
|
|
15
|
+
export declare const HELP_TEXT: string;
|
|
16
|
+
/** Parse `use <name> [--base-url <url>]` argv into its parts. */
|
|
17
|
+
export declare function parseUseArgs(args: string[]): {
|
|
18
|
+
name?: string;
|
|
19
|
+
baseUrl?: string;
|
|
20
|
+
};
|
|
21
|
+
/** `yagni use <name> [--base-url <url>]` — switch the sticky environment. */
|
|
22
|
+
export declare function useCommand(args: string[]): Promise<number>;
|
|
23
|
+
/** `yagni profiles` — list saved environments, marking the active one. */
|
|
24
|
+
export declare function profilesCommand(): Promise<number>;
|
|
25
|
+
/**
|
|
26
|
+
* Whether to show YAGNI Code's own help instead of launching the agent.
|
|
27
|
+
*
|
|
28
|
+
* `--help`/`-h` are intercepted ANYWHERE in argv (not just position 0) so the
|
|
29
|
+
* underlying agent's branded help never leaks (e.g. `yagni <flag> --help`).
|
|
30
|
+
*/
|
|
31
|
+
export declare function wantsHelp(argv: string[]): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Whether to print YAGNI Code's own version instead of launching the agent.
|
|
34
|
+
*
|
|
35
|
+
* `--version` AND `-v` are intercepted ANYWHERE in argv for the same reason as
|
|
36
|
+
* `--help`: pi parses both as its version flag, so letting either through
|
|
37
|
+
* leaks the agent's branded version line (or a login error before it).
|
|
38
|
+
*/
|
|
39
|
+
export declare function wantsVersion(argv: string[]): boolean;
|
|
40
|
+
/** The CLI's own version (see `currentCliVersion` — upgrade.ts owns it). */
|
|
41
|
+
export declare function cliVersion(): string;
|
|
42
|
+
export declare function main(argv: string[]): Promise<number>;
|
|
43
|
+
/**
|
|
44
|
+
* Whether this module is the process entrypoint (vs. imported by a test).
|
|
45
|
+
*
|
|
46
|
+
* Symlink-safe: when `yagni-code` is installed globally, the shell runs it
|
|
47
|
+
* through a symlink in the bin dir, so `process.argv[1]` is that symlink path.
|
|
48
|
+
* Node resolves symlinks for `import.meta.url`, so a raw `argv[1] === metaPath`
|
|
49
|
+
* compare is ALWAYS false for a symlinked install and `main()` never runs (the
|
|
50
|
+
* "nothing happens" bug). We realpath both sides so the symlinked bin and a
|
|
51
|
+
* direct `node dist/cli.js` invocation both count as the entrypoint, while a
|
|
52
|
+
* test import (argv[1] points at the test runner) does not.
|
|
53
|
+
*/
|
|
54
|
+
export declare function isEntrypoint(argv1: string | undefined, moduleUrl: string): boolean;
|
|
55
|
+
//# sourceMappingURL=cli.d.ts.map
|