@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.
Files changed (147) hide show
  1. package/LICENSE.md +55 -0
  2. package/README.md +166 -0
  3. package/dist/branding.d.ts +25 -0
  4. package/dist/branding.js +27 -0
  5. package/dist/claudeCompat.d.ts +78 -0
  6. package/dist/claudeCompat.js +198 -0
  7. package/dist/cli.d.ts +55 -0
  8. package/dist/cli.js +326 -0
  9. package/dist/config.d.ts +19 -0
  10. package/dist/config.js +28 -0
  11. package/dist/credentials.d.ts +41 -0
  12. package/dist/credentials.js +75 -0
  13. package/dist/distribution.d.ts +15 -0
  14. package/dist/distribution.js +45 -0
  15. package/dist/doctor.d.ts +94 -0
  16. package/dist/doctor.js +343 -0
  17. package/dist/extension/advisor.d.ts +84 -0
  18. package/dist/extension/advisor.js +101 -0
  19. package/dist/extension/askAdvisorTool.d.ts +83 -0
  20. package/dist/extension/askAdvisorTool.js +188 -0
  21. package/dist/extension/askYagniTool.d.ts +39 -0
  22. package/dist/extension/askYagniTool.js +63 -0
  23. package/dist/extension/bless.d.ts +45 -0
  24. package/dist/extension/bless.js +74 -0
  25. package/dist/extension/branding.d.ts +48 -0
  26. package/dist/extension/branding.js +119 -0
  27. package/dist/extension/chipEditor.d.ts +117 -0
  28. package/dist/extension/chipEditor.js +369 -0
  29. package/dist/extension/config.d.ts +112 -0
  30. package/dist/extension/config.js +108 -0
  31. package/dist/extension/costHud.d.ts +73 -0
  32. package/dist/extension/costHud.js +123 -0
  33. package/dist/extension/decisionCapture.d.ts +52 -0
  34. package/dist/extension/decisionCapture.js +66 -0
  35. package/dist/extension/decisions.d.ts +83 -0
  36. package/dist/extension/decisions.js +200 -0
  37. package/dist/extension/diagnostics.d.ts +41 -0
  38. package/dist/extension/diagnostics.js +110 -0
  39. package/dist/extension/index.d.ts +116 -0
  40. package/dist/extension/index.js +388 -0
  41. package/dist/extension/initDone.d.ts +28 -0
  42. package/dist/extension/initDone.js +66 -0
  43. package/dist/extension/initPass.d.ts +152 -0
  44. package/dist/extension/initPass.js +394 -0
  45. package/dist/extension/nextWorkTool.d.ts +51 -0
  46. package/dist/extension/nextWorkTool.js +80 -0
  47. package/dist/extension/permission.d.ts +91 -0
  48. package/dist/extension/permission.js +236 -0
  49. package/dist/extension/pipeline/activity.d.ts +37 -0
  50. package/dist/extension/pipeline/activity.js +151 -0
  51. package/dist/extension/pipeline/activityFeed.d.ts +59 -0
  52. package/dist/extension/pipeline/activityFeed.js +175 -0
  53. package/dist/extension/pipeline/budget.d.ts +48 -0
  54. package/dist/extension/pipeline/budget.js +68 -0
  55. package/dist/extension/pipeline/checkpoint.d.ts +31 -0
  56. package/dist/extension/pipeline/checkpoint.js +176 -0
  57. package/dist/extension/pipeline/eval.d.ts +168 -0
  58. package/dist/extension/pipeline/eval.js +182 -0
  59. package/dist/extension/pipeline/events.d.ts +56 -0
  60. package/dist/extension/pipeline/events.js +147 -0
  61. package/dist/extension/pipeline/findings.d.ts +42 -0
  62. package/dist/extension/pipeline/findings.js +144 -0
  63. package/dist/extension/pipeline/finish.d.ts +128 -0
  64. package/dist/extension/pipeline/finish.js +307 -0
  65. package/dist/extension/pipeline/goCommand.d.ts +128 -0
  66. package/dist/extension/pipeline/goCommand.js +972 -0
  67. package/dist/extension/pipeline/goCompareCommand.d.ts +48 -0
  68. package/dist/extension/pipeline/goCompareCommand.js +184 -0
  69. package/dist/extension/pipeline/goFlags.d.ts +40 -0
  70. package/dist/extension/pipeline/goFlags.js +46 -0
  71. package/dist/extension/pipeline/goStatusCommands.d.ts +59 -0
  72. package/dist/extension/pipeline/goStatusCommands.js +188 -0
  73. package/dist/extension/pipeline/invocation.d.ts +45 -0
  74. package/dist/extension/pipeline/invocation.js +64 -0
  75. package/dist/extension/pipeline/orchestrator.d.ts +131 -0
  76. package/dist/extension/pipeline/orchestrator.js +636 -0
  77. package/dist/extension/pipeline/personas.d.ts +44 -0
  78. package/dist/extension/pipeline/personas.js +248 -0
  79. package/dist/extension/pipeline/resilience.d.ts +85 -0
  80. package/dist/extension/pipeline/resilience.js +166 -0
  81. package/dist/extension/pipeline/resume.d.ts +18 -0
  82. package/dist/extension/pipeline/resume.js +106 -0
  83. package/dist/extension/pipeline/runRegistry.d.ts +112 -0
  84. package/dist/extension/pipeline/runRegistry.js +202 -0
  85. package/dist/extension/pipeline/runSession.d.ts +152 -0
  86. package/dist/extension/pipeline/runSession.js +167 -0
  87. package/dist/extension/pipeline/runState.d.ts +158 -0
  88. package/dist/extension/pipeline/runState.js +264 -0
  89. package/dist/extension/pipeline/runner.d.ts +79 -0
  90. package/dist/extension/pipeline/runner.js +298 -0
  91. package/dist/extension/pipeline/scrubSecrets.d.ts +15 -0
  92. package/dist/extension/pipeline/scrubSecrets.js +32 -0
  93. package/dist/extension/pipeline/stages.d.ts +51 -0
  94. package/dist/extension/pipeline/stages.js +113 -0
  95. package/dist/extension/pipeline/ticketResolution.d.ts +34 -0
  96. package/dist/extension/pipeline/ticketResolution.js +75 -0
  97. package/dist/extension/pipeline/types.d.ts +436 -0
  98. package/dist/extension/pipeline/types.js +48 -0
  99. package/dist/extension/pipeline/verify.d.ts +277 -0
  100. package/dist/extension/pipeline/verify.js +758 -0
  101. package/dist/extension/pipeline/workspace.d.ts +32 -0
  102. package/dist/extension/pipeline/workspace.js +73 -0
  103. package/dist/extension/pipeline/worktree.d.ts +107 -0
  104. package/dist/extension/pipeline/worktree.js +200 -0
  105. package/dist/extension/provider.d.ts +11 -0
  106. package/dist/extension/provider.js +26 -0
  107. package/dist/extension/recall.d.ts +93 -0
  108. package/dist/extension/recall.js +187 -0
  109. package/dist/extension/recordContextTool.d.ts +38 -0
  110. package/dist/extension/recordContextTool.js +85 -0
  111. package/dist/extension/recordDecisionTool.d.ts +52 -0
  112. package/dist/extension/recordDecisionTool.js +102 -0
  113. package/dist/extension/repoDocs.d.ts +81 -0
  114. package/dist/extension/repoDocs.js +260 -0
  115. package/dist/extension/resilientFetch.d.ts +60 -0
  116. package/dist/extension/resilientFetch.js +133 -0
  117. package/dist/extension/reviewTool.d.ts +34 -0
  118. package/dist/extension/reviewTool.js +81 -0
  119. package/dist/extension/spool.d.ts +92 -0
  120. package/dist/extension/spool.js +266 -0
  121. package/dist/extension/stateHome.d.ts +2 -0
  122. package/dist/extension/stateHome.js +6 -0
  123. package/dist/extension/subagents.d.ts +135 -0
  124. package/dist/extension/subagents.js +281 -0
  125. package/dist/extension/surface.d.ts +10 -0
  126. package/dist/extension/surface.js +12 -0
  127. package/dist/extension/todos.d.ts +110 -0
  128. package/dist/extension/todos.js +217 -0
  129. package/dist/extension/tokenProvider.d.ts +93 -0
  130. package/dist/extension/tokenProvider.js +234 -0
  131. package/dist/launch.d.ts +76 -0
  132. package/dist/launch.js +111 -0
  133. package/dist/login.d.ts +45 -0
  134. package/dist/login.js +142 -0
  135. package/dist/logout.d.ts +14 -0
  136. package/dist/logout.js +34 -0
  137. package/dist/paths.d.ts +31 -0
  138. package/dist/paths.js +87 -0
  139. package/dist/piPackage.d.ts +33 -0
  140. package/dist/piPackage.js +71 -0
  141. package/dist/profiles.d.ts +80 -0
  142. package/dist/profiles.js +222 -0
  143. package/dist/refresh.d.ts +70 -0
  144. package/dist/refresh.js +117 -0
  145. package/dist/upgrade.d.ts +97 -0
  146. package/dist/upgrade.js +284 -0
  147. 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
@@ -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