@yagni-app/code-staging 0.1.0-staging.997.1 → 0.2.0-staging.1025.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/README.md +58 -9
- package/dist/claudeCompat.d.ts +36 -5
- package/dist/claudeCompat.js +85 -23
- package/dist/claudePlugins.d.ts +109 -0
- package/dist/claudePlugins.js +336 -0
- package/dist/cli.js +14 -4
- package/dist/crashReport.d.ts +135 -0
- package/dist/crashReport.js +291 -0
- package/dist/doctor.d.ts +21 -0
- package/dist/doctor.js +52 -0
- package/dist/extension/askAdvisorTool.js +7 -1
- package/dist/extension/bless.js +16 -3
- package/dist/extension/boostCommand.d.ts +144 -0
- package/dist/extension/boostCommand.js +263 -0
- package/dist/extension/branding.d.ts +31 -0
- package/dist/extension/branding.js +37 -0
- package/dist/extension/chipEditor.js +7 -3
- package/dist/extension/claudeRules.d.ts +54 -0
- package/dist/extension/claudeRules.js +180 -0
- package/dist/extension/config.d.ts +61 -0
- package/dist/extension/config.js +86 -0
- package/dist/extension/costHud.d.ts +128 -15
- package/dist/extension/costHud.js +189 -19
- package/dist/extension/crashReport.d.ts +89 -0
- package/dist/extension/crashReport.js +241 -0
- package/dist/extension/index.d.ts +43 -4
- package/dist/extension/index.js +241 -32
- package/dist/extension/initPass.d.ts +65 -47
- package/dist/extension/initPass.js +145 -145
- package/dist/extension/mcpTools.d.ts +57 -0
- package/dist/extension/mcpTools.js +132 -0
- package/dist/extension/pipeline/eval.d.ts +42 -5
- package/dist/extension/pipeline/eval.js +44 -0
- package/dist/extension/pipeline/goCommand.d.ts +18 -0
- package/dist/extension/pipeline/goCommand.js +139 -26
- package/dist/extension/pipeline/goCompareCommand.d.ts +18 -8
- package/dist/extension/pipeline/goCompareCommand.js +42 -23
- package/dist/extension/pipeline/orchestrator.js +9 -0
- package/dist/extension/pipeline/runCostTable.d.ts +37 -0
- package/dist/extension/pipeline/runCostTable.js +165 -0
- package/dist/extension/pipeline/runState.d.ts +19 -0
- package/dist/extension/pipeline/runState.js +11 -0
- package/dist/extension/pipeline/runner.d.ts +19 -0
- package/dist/extension/pipeline/runner.js +13 -1
- package/dist/extension/pipeline/scrubSecrets.js +2 -2
- package/dist/extension/pipeline/stages.d.ts +3 -1
- package/dist/extension/pipeline/stages.js +3 -1
- package/dist/extension/pipeline/types.d.ts +7 -4
- package/dist/extension/pipeline/verify.js +6 -1
- package/dist/extension/pipeline/worktree.js +3 -1
- package/dist/extension/provider.d.ts +7 -1
- package/dist/extension/provider.js +8 -1
- package/dist/extension/recall.js +5 -2
- package/dist/extension/rerouteNotice.d.ts +42 -0
- package/dist/extension/rerouteNotice.js +67 -0
- package/dist/extension/sessionRuns.d.ts +45 -0
- package/dist/extension/sessionRuns.js +77 -0
- package/dist/extension/subagents.d.ts +17 -7
- package/dist/extension/subagents.js +52 -7
- package/dist/launch.d.ts +17 -3
- package/dist/launch.js +22 -9
- package/dist/login.d.ts +7 -0
- package/dist/login.js +3 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -88,12 +88,16 @@ yagni -p "explain the deploy pipeline; ask_yagni if unsure" # one-shot / print
|
|
|
88
88
|
yagni logout # revoke the token and clear it locally
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
The agent runs on
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
91
|
+
The agent runs on five opaque model tiers routed by YAGNI: `balanced` (the
|
|
92
|
+
interactive default), `peak` (the strongest, for hard judgment), `advanced`,
|
|
93
|
+
`standard` (for execution) and `efficient` (cheapest). `balanced` is a strategy
|
|
94
|
+
rather than a single rung: the session drives on the `advanced` tier and
|
|
95
|
+
escalates a bounded number of hard judgment calls per session to `peak`, so the
|
|
96
|
+
strongest model is consulted where being wrong is expensive instead of being
|
|
97
|
+
paid for on every turn. Pass `--model peak`, `--model standard`, or `--model
|
|
98
|
+
efficient` to pin a run to one tier. The `/go` pipeline routes its own steps
|
|
99
|
+
automatically: judgment steps (plan, review) on `peak`, execution steps (map,
|
|
100
|
+
implement, fix) on `standard`.
|
|
97
101
|
|
|
98
102
|
`login` opens a device-code flow: it prints a short code and a URL. Open the URL
|
|
99
103
|
in a browser where you are signed in to YAGNI, enter the code, and approve. The
|
|
@@ -112,6 +116,22 @@ flags like `--model` and `--print/-p` work.
|
|
|
112
116
|
agent calls **`ask_yagni`** and gets a cited answer from the YAGNI app instead
|
|
113
117
|
of guessing.
|
|
114
118
|
|
|
119
|
+
### Works with your existing repo setup
|
|
120
|
+
|
|
121
|
+
- A repo that already carries Claude Code assets keeps working with nothing to
|
|
122
|
+
edit: `.claude/skills` and `.claude/commands/*.md` load automatically (each
|
|
123
|
+
command becomes a `/name` slash command). Project-local assets load only
|
|
124
|
+
after a one-time per-folder trust prompt; set `YAGNI_DISABLE_CLAUDE_COMPAT=1`
|
|
125
|
+
to turn the bridge off.
|
|
126
|
+
- `AGENTS.md` and `CLAUDE.md` context files load from the working directory and
|
|
127
|
+
its ancestors, unmodified.
|
|
128
|
+
- MCP servers registered in your YAGNI workspace are available in-session: run
|
|
129
|
+
`/mcp` to list them. Calls are proxied through the YAGNI backend with your
|
|
130
|
+
own identity, so server credentials never reach the laptop and every call is
|
|
131
|
+
audited; mutating tools go through the same permission gate as file edits.
|
|
132
|
+
There is no local MCP config file: servers and credentials are managed in the
|
|
133
|
+
web app (Connections).
|
|
134
|
+
|
|
115
135
|
## Environments (profiles)
|
|
116
136
|
|
|
117
137
|
YAGNI Code keeps a **sticky active environment**. Each environment ("profile") binds a
|
|
@@ -139,14 +159,43 @@ yagni use prod # switch back (sticky); prod is the def
|
|
|
139
159
|
|
|
140
160
|
## Configuration
|
|
141
161
|
|
|
142
|
-
| Variable
|
|
143
|
-
|
|
|
144
|
-
| `YAGNI_BASE_URL`
|
|
162
|
+
| Variable | Default | Purpose |
|
|
163
|
+
| ----------------------------- | ------------------------ | -------------------------------------------------- |
|
|
164
|
+
| `YAGNI_BASE_URL` | active environment's URL | Override the base URL for a **single run** (escape hatch). Prefer `yagni use` for anything sticky. |
|
|
165
|
+
| `YAGNI_DISABLE_BRANDING` | unset | `1` skips the YAGNI Code system-prompt rewrite entirely, so the engine's assembled prompt passes through byte-exact (no identity swap, no company-brief injection). |
|
|
166
|
+
| `YAGNI_DISABLE_UPDATE_CHECK` | unset | `1` silences the new-version notice and the background update check. |
|
|
167
|
+
| `YAGNI_DISABLE_CLAUDE_COMPAT` | unset | `1` turns off the zero-config `.claude` assets bridge (skills and commands). |
|
|
168
|
+
| `YAGNI_DISABLE_CRASH_REPORTS` | unset | `1` disables crash reporting (see "What leaves your machine"). |
|
|
145
169
|
|
|
146
170
|
Credentials live in `~/.yagni-code/profiles/<name>.json` (mode `0600`); the active
|
|
147
171
|
environment is recorded in `~/.yagni-code/config.json`. A pre-profiles
|
|
148
172
|
`~/.yagni-code/credentials.json` is migrated automatically on first run.
|
|
149
173
|
|
|
174
|
+
Device tokens are revocable from both ends: `yagni logout` revokes the current
|
|
175
|
+
one, and a workspace admin can list every connected device and revoke any token
|
|
176
|
+
from the web app (Settings, YAGNI Code, Connected devices). Tokens are stored
|
|
177
|
+
hashed on the server and a revoked token stops validating immediately.
|
|
178
|
+
|
|
179
|
+
### What leaves your machine
|
|
180
|
+
|
|
181
|
+
Everything the agent needs (model calls, grounding lookups, MCP calls, usage
|
|
182
|
+
metering) goes to your YAGNI backend over the authenticated session. No
|
|
183
|
+
third-party analytics or telemetry SDK runs on your laptop, and the underlying
|
|
184
|
+
engine's own telemetry and version pings are disabled at launch. Two outbound
|
|
185
|
+
flows exist beyond your YAGNI backend and your own repo tooling, disclosed
|
|
186
|
+
precisely:
|
|
187
|
+
|
|
188
|
+
- **Update check.** In the background after launch, the CLI asks the npm
|
|
189
|
+
registry (or your configured private registry mirror) whether a newer
|
|
190
|
+
version of this package exists, so the next launch can show a one-line
|
|
191
|
+
nudge. Package metadata only; no code or workspace data. Disable with
|
|
192
|
+
`YAGNI_DISABLE_UPDATE_CHECK=1`.
|
|
193
|
+
- **Crash reports.** When the client itself crashes, a sanitized crash report
|
|
194
|
+
(error class, scrubbed message and stack trace; secrets and env values
|
|
195
|
+
redacted, paths reduced) is sent to your YAGNI backend, not to any
|
|
196
|
+
third-party crash service, so we can fix the crash before you have to report
|
|
197
|
+
it. Disable with `YAGNI_DISABLE_CRASH_REPORTS=1`.
|
|
198
|
+
|
|
150
199
|
## Troubleshooting
|
|
151
200
|
|
|
152
201
|
- **`Not logged in to environment "<name>" … Run \`yagni login\` first.`** — no
|
package/dist/claudeCompat.d.ts
CHANGED
|
@@ -9,6 +9,14 @@
|
|
|
9
9
|
* `--prompt-template` flags, so nothing repo-local is ever written and no
|
|
10
10
|
* per-profile settings file accumulates stale per-repo paths.
|
|
11
11
|
*
|
|
12
|
+
* The same seams carry installed/enabled Claude Code plugins and the repo's
|
|
13
|
+
* own `.claude-plugin/marketplace.json` plugins (see claudePlugins.ts):
|
|
14
|
+
* plugin skills → `--skill`, plugin commands → `--prompt-template`, plugin
|
|
15
|
+
* `agents/` dirs → YAGNI_CLAUDE_AGENT_DIRS for the extension's subagent
|
|
16
|
+
* discovery. `.claude/rules` dirs travel as YAGNI_CLAUDE_RULES_DIRS for the
|
|
17
|
+
* extension's system-prompt rules injection. Design + on-disk evidence:
|
|
18
|
+
* docs/superpowers/specs/2026-08-08-claude-plugins-design.md.
|
|
19
|
+
*
|
|
12
20
|
* Trust: pi gates project-local resources behind a per-folder trust decision
|
|
13
21
|
* stored in `<agentDir>/trust.json`. Explicit CLI paths bypass that gate, so
|
|
14
22
|
* the launcher enforces the SAME decision itself: project `.claude` dirs are
|
|
@@ -20,14 +28,24 @@
|
|
|
20
28
|
*
|
|
21
29
|
* Set YAGNI_DISABLE_CLAUDE_COMPAT=1 to turn the whole bridge off.
|
|
22
30
|
*/
|
|
31
|
+
import { type PluginAssets } from "./claudePlugins.js";
|
|
23
32
|
export declare const CLAUDE_COMPAT_DISABLE_ENV = "YAGNI_DISABLE_CLAUDE_COMPAT";
|
|
33
|
+
/** Plugin `agents/` dirs for the extension's subagent discovery (delimiter-joined). */
|
|
34
|
+
export declare const CLAUDE_AGENT_DIRS_ENV = "YAGNI_CLAUDE_AGENT_DIRS";
|
|
35
|
+
/** `.claude/rules` dirs for the extension's rules injection (delimiter-joined). */
|
|
36
|
+
export declare const CLAUDE_RULES_DIRS_ENV = "YAGNI_CLAUDE_RULES_DIRS";
|
|
24
37
|
export interface ClaudeAssetDirs {
|
|
25
38
|
skills: string | null;
|
|
26
39
|
commands: string | null;
|
|
40
|
+
rules: string | null;
|
|
27
41
|
}
|
|
28
42
|
export interface ClaudeCompatProbe {
|
|
29
43
|
project: ClaudeAssetDirs;
|
|
30
44
|
user: ClaudeAssetDirs;
|
|
45
|
+
/** Locally-present plugins sourced from repo config; trust-gated. */
|
|
46
|
+
projectPlugins: PluginAssets[];
|
|
47
|
+
/** Plugins the user enabled in their own `~/.claude`; no ceremony. */
|
|
48
|
+
userPlugins: PluginAssets[];
|
|
31
49
|
/** Nearest recorded trust decision for the cwd (pi trust.json), if any. */
|
|
32
50
|
projectTrust: boolean | null;
|
|
33
51
|
interactive: boolean;
|
|
@@ -37,7 +55,13 @@ export interface ClaudeCompatDecision {
|
|
|
37
55
|
userArgv: string[];
|
|
38
56
|
/** Flags for project dirs; pass only once trust is established. */
|
|
39
57
|
projectArgv: string[];
|
|
40
|
-
/**
|
|
58
|
+
/** Plugin agent dirs, split by the same trust posture. */
|
|
59
|
+
userAgentDirs: string[];
|
|
60
|
+
projectAgentDirs: string[];
|
|
61
|
+
/** `.claude/rules` dirs, user first so project rules win in the prompt. */
|
|
62
|
+
userRulesDirs: string[];
|
|
63
|
+
projectRulesDirs: string[];
|
|
64
|
+
/** Ask the user for a trust decision before wiring any project content. */
|
|
41
65
|
needsPrompt: boolean;
|
|
42
66
|
}
|
|
43
67
|
/** Pure policy: what to wire, and whether a trust prompt is required first. */
|
|
@@ -67,12 +91,19 @@ export interface ClaudeCompatDeps {
|
|
|
67
91
|
confirm?: (question: string) => Promise<boolean>;
|
|
68
92
|
}
|
|
69
93
|
export declare function compatDisabled(env: NodeJS.ProcessEnv): boolean;
|
|
94
|
+
export interface ClaudeCompatLaunch {
|
|
95
|
+
/** Extra pi flags (`--skill` / `--prompt-template` paths). */
|
|
96
|
+
argv: string[];
|
|
97
|
+
/** Extra child env (plugin agent dirs, rules dirs) for the extension. */
|
|
98
|
+
env: Record<string, string>;
|
|
99
|
+
}
|
|
70
100
|
/**
|
|
71
|
-
* The extra pi argv for this launch: user-scope dirs
|
|
72
|
-
*
|
|
73
|
-
*
|
|
101
|
+
* The extra pi argv + child env for this launch: user-scope dirs and plugins
|
|
102
|
+
* unconditionally, project-sourced dirs/plugins/rules once trusted, asking
|
|
103
|
+
* (and persisting) the trust decision when needed. Everything is fail-soft —
|
|
104
|
+
* compat can only add flags/env, never break a launch.
|
|
74
105
|
*/
|
|
75
|
-
export declare function claudeCompatArgs(deps: ClaudeCompatDeps): Promise<
|
|
106
|
+
export declare function claudeCompatArgs(deps: ClaudeCompatDeps): Promise<ClaudeCompatLaunch>;
|
|
76
107
|
/** Whether pi's trust file exists yet (used only for messaging). */
|
|
77
108
|
export declare function trustFileExists(agentDir: string): boolean;
|
|
78
109
|
//# sourceMappingURL=claudeCompat.d.ts.map
|
package/dist/claudeCompat.js
CHANGED
|
@@ -9,6 +9,14 @@
|
|
|
9
9
|
* `--prompt-template` flags, so nothing repo-local is ever written and no
|
|
10
10
|
* per-profile settings file accumulates stale per-repo paths.
|
|
11
11
|
*
|
|
12
|
+
* The same seams carry installed/enabled Claude Code plugins and the repo's
|
|
13
|
+
* own `.claude-plugin/marketplace.json` plugins (see claudePlugins.ts):
|
|
14
|
+
* plugin skills → `--skill`, plugin commands → `--prompt-template`, plugin
|
|
15
|
+
* `agents/` dirs → YAGNI_CLAUDE_AGENT_DIRS for the extension's subagent
|
|
16
|
+
* discovery. `.claude/rules` dirs travel as YAGNI_CLAUDE_RULES_DIRS for the
|
|
17
|
+
* extension's system-prompt rules injection. Design + on-disk evidence:
|
|
18
|
+
* docs/superpowers/specs/2026-08-08-claude-plugins-design.md.
|
|
19
|
+
*
|
|
12
20
|
* Trust: pi gates project-local resources behind a per-folder trust decision
|
|
13
21
|
* stored in `<agentDir>/trust.json`. Explicit CLI paths bypass that gate, so
|
|
14
22
|
* the launcher enforces the SAME decision itself: project `.claude` dirs are
|
|
@@ -22,8 +30,13 @@
|
|
|
22
30
|
*/
|
|
23
31
|
import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, rmdirSync, statSync, writeFileSync } from "node:fs";
|
|
24
32
|
import { homedir } from "node:os";
|
|
25
|
-
import { dirname, join, resolve } from "node:path";
|
|
33
|
+
import { delimiter, dirname, join, resolve } from "node:path";
|
|
34
|
+
import { discoverClaudePlugins } from "./claudePlugins.js";
|
|
26
35
|
export const CLAUDE_COMPAT_DISABLE_ENV = "YAGNI_DISABLE_CLAUDE_COMPAT";
|
|
36
|
+
/** Plugin `agents/` dirs for the extension's subagent discovery (delimiter-joined). */
|
|
37
|
+
export const CLAUDE_AGENT_DIRS_ENV = "YAGNI_CLAUDE_AGENT_DIRS";
|
|
38
|
+
/** `.claude/rules` dirs for the extension's rules injection (delimiter-joined). */
|
|
39
|
+
export const CLAUDE_RULES_DIRS_ENV = "YAGNI_CLAUDE_RULES_DIRS";
|
|
27
40
|
function dirsToArgv(dirs) {
|
|
28
41
|
const argv = [];
|
|
29
42
|
if (dirs.skills)
|
|
@@ -32,13 +45,30 @@ function dirsToArgv(dirs) {
|
|
|
32
45
|
argv.push("--prompt-template", dirs.commands);
|
|
33
46
|
return argv;
|
|
34
47
|
}
|
|
48
|
+
function pluginsToArgv(plugins) {
|
|
49
|
+
const argv = [];
|
|
50
|
+
for (const plugin of plugins) {
|
|
51
|
+
for (const path of plugin.skillPaths)
|
|
52
|
+
argv.push("--skill", path);
|
|
53
|
+
for (const path of plugin.commandPaths)
|
|
54
|
+
argv.push("--prompt-template", path);
|
|
55
|
+
}
|
|
56
|
+
return argv;
|
|
57
|
+
}
|
|
35
58
|
/** Pure policy: what to wire, and whether a trust prompt is required first. */
|
|
36
59
|
export function decideClaudeCompat(probe) {
|
|
37
|
-
const projectArgv = dirsToArgv(probe.project);
|
|
60
|
+
const projectArgv = [...dirsToArgv(probe.project), ...pluginsToArgv(probe.projectPlugins)];
|
|
61
|
+
const projectAgentDirs = probe.projectPlugins.flatMap((p) => p.agentDirs);
|
|
62
|
+
const projectRulesDirs = probe.project.rules ? [probe.project.rules] : [];
|
|
63
|
+
const hasProjectContent = projectArgv.length > 0 || projectAgentDirs.length > 0 || projectRulesDirs.length > 0;
|
|
38
64
|
return {
|
|
39
|
-
userArgv: dirsToArgv(probe.user),
|
|
65
|
+
userArgv: [...dirsToArgv(probe.user), ...pluginsToArgv(probe.userPlugins)],
|
|
40
66
|
projectArgv,
|
|
41
|
-
|
|
67
|
+
userAgentDirs: probe.userPlugins.flatMap((p) => p.agentDirs),
|
|
68
|
+
projectAgentDirs,
|
|
69
|
+
userRulesDirs: probe.user.rules ? [probe.user.rules] : [],
|
|
70
|
+
projectRulesDirs,
|
|
71
|
+
needsPrompt: probe.interactive && hasProjectContent && probe.projectTrust === null,
|
|
42
72
|
};
|
|
43
73
|
}
|
|
44
74
|
function isDirectory(path) {
|
|
@@ -53,9 +83,11 @@ function isDirectory(path) {
|
|
|
53
83
|
export function probeClaudeDirs(root) {
|
|
54
84
|
const skills = join(root, ".claude", "skills");
|
|
55
85
|
const commands = join(root, ".claude", "commands");
|
|
86
|
+
const rules = join(root, ".claude", "rules");
|
|
56
87
|
return {
|
|
57
88
|
skills: isDirectory(skills) ? skills : null,
|
|
58
89
|
commands: isDirectory(commands) ? commands : null,
|
|
90
|
+
rules: isDirectory(rules) ? rules : null,
|
|
59
91
|
};
|
|
60
92
|
}
|
|
61
93
|
// ── pi trust.json interop ───────────────────────────────────────────────────
|
|
@@ -114,7 +146,14 @@ export function readTrustDecision(agentDir, cwd) {
|
|
|
114
146
|
export function writeTrustDecision(agentDir, cwd, decision) {
|
|
115
147
|
const path = trustPath(agentDir);
|
|
116
148
|
const lock = `${path}.lock`;
|
|
117
|
-
|
|
149
|
+
try {
|
|
150
|
+
mkdirSync(agentDir, { recursive: true });
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// Fail-soft: an uncreatable agent dir must not discard the user's answer
|
|
154
|
+
// for this launch — the caller still applies it in-process.
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
118
157
|
let locked = false;
|
|
119
158
|
for (let attempt = 0; attempt < 10; attempt++) {
|
|
120
159
|
try {
|
|
@@ -158,38 +197,61 @@ export function compatDisabled(env) {
|
|
|
158
197
|
return value !== undefined && value !== "" && value !== "0";
|
|
159
198
|
}
|
|
160
199
|
/**
|
|
161
|
-
* The extra pi argv for this launch: user-scope dirs
|
|
162
|
-
*
|
|
163
|
-
*
|
|
200
|
+
* The extra pi argv + child env for this launch: user-scope dirs and plugins
|
|
201
|
+
* unconditionally, project-sourced dirs/plugins/rules once trusted, asking
|
|
202
|
+
* (and persisting) the trust decision when needed. Everything is fail-soft —
|
|
203
|
+
* compat can only add flags/env, never break a launch.
|
|
164
204
|
*/
|
|
165
205
|
export async function claudeCompatArgs(deps) {
|
|
166
206
|
const env = deps.env ?? process.env;
|
|
167
207
|
if (compatDisabled(env))
|
|
168
|
-
return [];
|
|
208
|
+
return { argv: [], env: {} };
|
|
169
209
|
const home = deps.homeDir ?? homedir();
|
|
170
210
|
const projectTrust = readTrustDecision(deps.agentDir, deps.cwd);
|
|
211
|
+
let plugins;
|
|
212
|
+
try {
|
|
213
|
+
plugins = discoverClaudePlugins({ cwd: deps.cwd, homeDir: home });
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
plugins = { user: [], project: [] };
|
|
217
|
+
}
|
|
171
218
|
const decision = decideClaudeCompat({
|
|
172
219
|
project: probeClaudeDirs(deps.cwd),
|
|
173
220
|
user: probeClaudeDirs(home),
|
|
221
|
+
projectPlugins: plugins.project,
|
|
222
|
+
userPlugins: plugins.user,
|
|
174
223
|
projectTrust,
|
|
175
224
|
interactive: deps.interactive ?? (process.stdin.isTTY === true && process.stdout.isTTY === true),
|
|
176
225
|
});
|
|
177
|
-
|
|
178
|
-
if (decision.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
catch {
|
|
186
|
-
trusted = null;
|
|
187
|
-
}
|
|
226
|
+
let trusted = projectTrust;
|
|
227
|
+
if (decision.needsPrompt && deps.confirm) {
|
|
228
|
+
try {
|
|
229
|
+
trusted = await deps.confirm("This folder has Claude Code assets (.claude skills, commands, rules, or plugins). Trust this folder and load them?");
|
|
230
|
+
writeTrustDecision(deps.agentDir, deps.cwd, trusted);
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
trusted = null;
|
|
188
234
|
}
|
|
189
|
-
if (trusted === true)
|
|
190
|
-
argv.push(...decision.projectArgv);
|
|
191
235
|
}
|
|
192
|
-
|
|
236
|
+
const argv = [...decision.userArgv];
|
|
237
|
+
if (trusted === true)
|
|
238
|
+
argv.push(...decision.projectArgv);
|
|
239
|
+
const agentDirs = [
|
|
240
|
+
...decision.userAgentDirs,
|
|
241
|
+
...(trusted === true ? decision.projectAgentDirs : []),
|
|
242
|
+
];
|
|
243
|
+
// User rules first, project rules last: the extension appends in order, so
|
|
244
|
+
// project rules end up closest to the task — matching Claude Code priority.
|
|
245
|
+
const rulesDirs = [
|
|
246
|
+
...decision.userRulesDirs,
|
|
247
|
+
...(trusted === true ? decision.projectRulesDirs : []),
|
|
248
|
+
];
|
|
249
|
+
const extraEnv = {};
|
|
250
|
+
if (agentDirs.length > 0)
|
|
251
|
+
extraEnv[CLAUDE_AGENT_DIRS_ENV] = agentDirs.join(delimiter);
|
|
252
|
+
if (rulesDirs.length > 0)
|
|
253
|
+
extraEnv[CLAUDE_RULES_DIRS_ENV] = rulesDirs.join(delimiter);
|
|
254
|
+
return { argv, env: extraEnv };
|
|
193
255
|
}
|
|
194
256
|
/** Whether pi's trust file exists yet (used only for messaging). */
|
|
195
257
|
export function trustFileExists(agentDir) {
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code plugin + marketplace discovery (local content only).
|
|
3
|
+
*
|
|
4
|
+
* Finds the plugin content a Claude Code user already has on disk and exposes
|
|
5
|
+
* it as asset paths the launcher can feed through the existing compat seams:
|
|
6
|
+
* skills → `--skill`, commands → `--prompt-template`, agents → the subagent
|
|
7
|
+
* discovery env var. Two sources:
|
|
8
|
+
*
|
|
9
|
+
* 1. Installed plugins: `~/.claude/plugins/installed_plugins.json` (version-2
|
|
10
|
+
* ledger of `name@marketplace` → cache checkouts), enabled through
|
|
11
|
+
* `enabledPlugins` in the user's `~/.claude/settings.json` (user scope) or
|
|
12
|
+
* the repo's `.claude/settings{,.local}.json` (project scope). When a
|
|
13
|
+
* ledger entry is gone, the marketplace checkout recorded in
|
|
14
|
+
* `known_marketplaces.json` is tried as a fallback.
|
|
15
|
+
* 2. The repo's own `.claude-plugin/marketplace.json` (the internal-
|
|
16
|
+
* marketplace pattern): every plugin it defines with a locally-present
|
|
17
|
+
* relative-path source loads on the project side, unless explicitly
|
|
18
|
+
* disabled via `enabledPlugins`.
|
|
19
|
+
*
|
|
20
|
+
* Deliberately NOT here: network installation of any kind, plugin hooks, MCP
|
|
21
|
+
* servers, LSP servers, themes, output styles. Discovery is read-only.
|
|
22
|
+
*
|
|
23
|
+
* Everything is fail-soft: malformed JSON, missing dirs, or hostile path
|
|
24
|
+
* entries degrade to "that plugin absent" — never a failed launch. Path
|
|
25
|
+
* entries from marketplace/plugin manifests are containment-checked (realpath
|
|
26
|
+
* inside the expected root) so a malicious `source` or component override
|
|
27
|
+
* cannot reach outside its repo/plugin.
|
|
28
|
+
*/
|
|
29
|
+
export interface PluginAssets {
|
|
30
|
+
/** The plugin's name (marketplace-entry name — the `enabledPlugins` key half). */
|
|
31
|
+
name: string;
|
|
32
|
+
/** Absolute plugin root directory. */
|
|
33
|
+
root: string;
|
|
34
|
+
/** Dirs (or a lone root SKILL.md file) for pi `--skill`. */
|
|
35
|
+
skillPaths: string[];
|
|
36
|
+
/** Dirs or files for pi `--prompt-template`. */
|
|
37
|
+
commandPaths: string[];
|
|
38
|
+
/** Dirs of Claude Code-format agent markdown for subagent discovery. */
|
|
39
|
+
agentDirs: string[];
|
|
40
|
+
}
|
|
41
|
+
export interface DiscoveredClaudePlugins {
|
|
42
|
+
/** Enabled via the user's own `~/.claude` config; loads without ceremony. */
|
|
43
|
+
user: PluginAssets[];
|
|
44
|
+
/** Sourced from repo config; wire only once the folder is trusted. */
|
|
45
|
+
project: PluginAssets[];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Resolve `candidate` (a relative path from untrusted JSON) against `root` and
|
|
49
|
+
* return its realpath only when it exists AND stays inside `root` after
|
|
50
|
+
* symlink resolution. Absolute candidates and any `../` escape are rejected.
|
|
51
|
+
*/
|
|
52
|
+
export declare function containedExistingPath(root: string, candidate: string): string | null;
|
|
53
|
+
export interface InstalledPluginEntry {
|
|
54
|
+
installPath: string;
|
|
55
|
+
projectPath?: string;
|
|
56
|
+
}
|
|
57
|
+
/** `~/.claude/plugins/installed_plugins.json` → `name@marketplace` → entries. */
|
|
58
|
+
export declare function readInstalledPlugins(homeDir: string): Map<string, InstalledPluginEntry[]>;
|
|
59
|
+
export type EnableScope = "user" | "project";
|
|
60
|
+
export interface EnableState {
|
|
61
|
+
enabled: boolean;
|
|
62
|
+
scope: EnableScope;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* `enabledPlugins` merged across scopes, later (more specific) winning:
|
|
66
|
+
* user `~/.claude/settings.json` < project `.claude/settings.json` <
|
|
67
|
+
* project `.claude/settings.local.json`.
|
|
68
|
+
*/
|
|
69
|
+
export declare function readEnabledPlugins(cwd: string, homeDir: string): Map<string, EnableState>;
|
|
70
|
+
/** `~/.claude/plugins/known_marketplaces.json` → marketplace name → checkout dir. */
|
|
71
|
+
export declare function readKnownMarketplaces(homeDir: string): Map<string, string>;
|
|
72
|
+
export interface MarketplaceEntry {
|
|
73
|
+
name: string;
|
|
74
|
+
source: unknown;
|
|
75
|
+
}
|
|
76
|
+
export interface Marketplace {
|
|
77
|
+
name: string;
|
|
78
|
+
/** Marketplace root: the dir containing `.claude-plugin/`. */
|
|
79
|
+
root: string;
|
|
80
|
+
/** `metadata.pluginRoot`, prepended to bare relative sources. */
|
|
81
|
+
pluginRoot: string | null;
|
|
82
|
+
plugins: MarketplaceEntry[];
|
|
83
|
+
}
|
|
84
|
+
/** Parse `<root>/.claude-plugin/marketplace.json`; null when absent/unusable. */
|
|
85
|
+
export declare function readMarketplace(root: string): Marketplace | null;
|
|
86
|
+
/**
|
|
87
|
+
* The local directory a marketplace entry's source points at, when it is a
|
|
88
|
+
* relative-path source that exists inside the marketplace root. Object
|
|
89
|
+
* sources (github/url/npm/...) are network installs → null here.
|
|
90
|
+
*/
|
|
91
|
+
export declare function resolveLocalPluginRoot(mp: Marketplace, entry: MarketplaceEntry): string | null;
|
|
92
|
+
/**
|
|
93
|
+
* The bridgeable assets inside one plugin checkout, per Claude Code's
|
|
94
|
+
* component rules: `skills/` (manifest `skills` ADDS dirs), `commands/`
|
|
95
|
+
* (manifest REPLACES), `agents/` (manifest REPLACES; dirs only — pi's
|
|
96
|
+
* discovery reads whole dirs), root `SKILL.md` fallback. Null when the plugin
|
|
97
|
+
* has nothing we can bridge.
|
|
98
|
+
*/
|
|
99
|
+
export declare function pluginAssets(root: string, name: string): PluginAssets | null;
|
|
100
|
+
export interface DiscoverPluginsDeps {
|
|
101
|
+
cwd: string;
|
|
102
|
+
homeDir: string;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* All locally-present Claude Code plugin content relevant to `cwd`, split by
|
|
106
|
+
* the trust posture the launcher must apply. Never throws.
|
|
107
|
+
*/
|
|
108
|
+
export declare function discoverClaudePlugins(deps: DiscoverPluginsDeps): DiscoveredClaudePlugins;
|
|
109
|
+
//# sourceMappingURL=claudePlugins.d.ts.map
|