@bluewombat/slots 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -3
- package/agents/agent-plugins.mjs +231 -0
- package/agents/claude.mjs +24 -0
- package/agents/cursor.mjs +15 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -112,16 +112,60 @@ isolated yet, and starting it would keep the operator's own config.
|
|
|
112
112
|
| `--agent-arg VALUE` | Appended to the CLI argv, before the prompt. Repeatable |
|
|
113
113
|
| `--agent-home DIR` | Isolate the vendor process: its `HOME` is this directory. Cursor only; Claude refuses |
|
|
114
114
|
| `--api-key-env NAME` | With `--agent-home`, read `process.env[NAME]` for the vendor key. Never the value itself |
|
|
115
|
+
| `--plugin-dir DIR` | A plugin folder of the operator's (a Mason home's `ai-plugins/core/*`). Repeatable, loaded first |
|
|
116
|
+
| `--profile NAME` | The project's deck profile for this role, read in the working directory. Cursor only; Claude refuses |
|
|
117
|
+
| `--plugins-out DIR` | Where `--profile` is built, one folder per profile. Required with `--profile`; never the workspace |
|
|
118
|
+
| `--deck-bin PATH` | The deck CLI. Default: the `@pinkpuma/deck` this package depends on |
|
|
115
119
|
| `--id` / `--attempt` / `--context` | How a transcript is filed. Forwarded by the role |
|
|
116
120
|
|
|
121
|
+
### Plugins
|
|
122
|
+
|
|
123
|
+
An agent starts with plugin folders, each passed to the vendor as
|
|
124
|
+
`--plugin-dir`: first the `--plugin-dir` folders, in order, then what the
|
|
125
|
+
project's [deck](https://github.com/tilap/pinkpuma-deck) profile resolves to.
|
|
126
|
+
|
|
127
|
+
`--profile builder` runs `deck materialize -C <cwd> --profile builder --out
|
|
128
|
+
<plugins-out>/builder --json` before the vendor starts. The working directory
|
|
129
|
+
is the checkout the agent works on, so a role gets what the project committed
|
|
130
|
+
at that commit — the base `ai-plugin.json` and `.ai/profiles/builder/`, as their
|
|
131
|
+
locks pin them — never what the operator has installed. deck refuses a request
|
|
132
|
+
no committed lock pins, and the wrapper then refuses before the vendor starts
|
|
133
|
+
(`Run deck install in the project and commit its locks`). Every Attempt of the
|
|
134
|
+
role refuses the same way until the project is fixed; `mason doctor` says so
|
|
135
|
+
first.
|
|
136
|
+
|
|
137
|
+
| The project has | The agent gets |
|
|
138
|
+
| --------------------------------------- | ------------------------------------ |
|
|
139
|
+
| `.ai/profiles/<name>/ai-plugin.json` | the base and that profile |
|
|
140
|
+
| only a base `ai-plugin.json` | the base, and a note on stderr |
|
|
141
|
+
| no deck file at all | the `--plugin-dir` folders only |
|
|
142
|
+
|
|
143
|
+
A non-empty `.ai/profiles/<name>/prompt.md` is appended to the filled prompt
|
|
144
|
+
under `## Project instructions for this role`: what the project wants from that
|
|
145
|
+
role, next to what Mason asks.
|
|
146
|
+
|
|
147
|
+
The plugins keep their hooks: deck writes a plugin's `hooks/hooks.json` as its
|
|
148
|
+
author did. `cursor-agent` ran a Claude-format one (`${CLAUDE_PLUGIN_ROOT}`,
|
|
149
|
+
`PostToolUse` on edits) on a real run; a hook that runs is part of the agent's
|
|
150
|
+
time and of what it reads back.
|
|
151
|
+
|
|
152
|
+
deck's cache (`~/.cache/pinkpuma-deck`, or `DECK_CACHE`) has no lock, and
|
|
153
|
+
`--plugins-out` holds one folder per profile: both rely on a Mason run starting
|
|
154
|
+
one agent at a time, which it does.
|
|
155
|
+
|
|
156
|
+
`claude.mjs` takes `--plugin-dir` but refuses `--profile`: not isolated, Claude
|
|
157
|
+
also loads the operator's plugins and the project's `.claude/settings.json`, so
|
|
158
|
+
a role's profile would not be all it gets.
|
|
159
|
+
|
|
117
160
|
The agent CLI prints **one JSON line** — a `SerializedRun` from
|
|
118
|
-
`@bluewombat/slot-kit` — with the vendor's raw streams plus
|
|
161
|
+
`@bluewombat/slot-kit` — with the vendor's raw streams plus the extras the
|
|
119
162
|
wrapper fills when it can:
|
|
120
163
|
|
|
121
164
|
| Field | Meaning |
|
|
122
165
|
| -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
123
166
|
| `skills` | `null` = unknown (not stream-json, or unparseable). `[]` = looked, none used. Otherwise skill names in order |
|
|
124
167
|
| `usage` | `null` = unknown. Otherwise token counts (`input`, `output`, `cacheRead`, `cacheWrite`) and `costUsd` (`null` when the vendor did not report a cost) |
|
|
168
|
+
| `plugins` | Absent when no plugin folder was loaded. Otherwise each folder: `id`, `path`, `from` (`local` or `deck`), and `integrity` (deck's, `null` for a local one) |
|
|
125
169
|
| `tools` | `null` = unknown (not stream-json, or unparseable). Otherwise what the agent did with its tools: `calls` per tool name, `busyMs` (time with a tool running; `null` when the stream has no times, as Claude's), and the lists `web` (pages fetched, searches, URLs a shell command named), `mcp` (`server/tool`, plus `getMcpTools` when the agent only listed a server's tools), `subagents` (by description), `outside` (paths named outside the working directory, skill files excepted) |
|
|
126
170
|
|
|
127
171
|
Cursor extracts skills from `stream-json` `readToolCall` paths ending in
|
|
@@ -134,8 +178,8 @@ Claude's `tool_use` blocks (`WebFetch`, `WebSearch`, `Bash`, `Task`,
|
|
|
134
178
|
future vendor agent (`codex.mjs`, …) uses the same fields: leave them `null`
|
|
135
179
|
when it cannot tell.
|
|
136
180
|
|
|
137
|
-
When `--transcript-dir` is set, the transcript header
|
|
138
|
-
`usage` and the tools — `tools`, `web`, `mcp`, `subagents`, `outside the
|
|
181
|
+
When `--transcript-dir` is set, the transcript header lists the `plugins` and
|
|
182
|
+
repeats `skills`, `usage` and the tools — `tools`, `web`, `mcp`, `subagents`, `outside the
|
|
139
183
|
workspace` — each `unknown`, `(none)`, or the values. "Did it read the page I
|
|
140
184
|
linked?" is answered there, without reading the stream.
|
|
141
185
|
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
5
|
+
import { listOption, textOption } from "@bluewombat/slot-kit";
|
|
6
|
+
|
|
7
|
+
// The plugin folders an agent starts with, shared by every vendor wrapper.
|
|
8
|
+
//
|
|
9
|
+
// --plugin-dir DIR a folder of the operator's own (the Mason home's
|
|
10
|
+
// core plugins). Repeatable, loaded first, in order
|
|
11
|
+
// --profile NAME the project's deck profile for this role: the base
|
|
12
|
+
// and that profile, as the project's locks pin them
|
|
13
|
+
// --plugins-out DIR where the profile is materialized, one folder per
|
|
14
|
+
// profile. Required with --profile: never the
|
|
15
|
+
// workspace, whose tree the Gates read
|
|
16
|
+
// --deck-bin PATH the deck CLI. Default: the @pinkpuma/deck this
|
|
17
|
+
// package depends on
|
|
18
|
+
//
|
|
19
|
+
// The profile is read from the working directory — the checkout the agent
|
|
20
|
+
// works on — so a role gets what the project committed at that commit, not
|
|
21
|
+
// what the operator has installed. Agents of one Mason run one at a time,
|
|
22
|
+
// which is why one folder per profile is enough.
|
|
23
|
+
|
|
24
|
+
const PROFILE_NAME = /^[A-Za-z0-9_-][A-Za-z0-9._-]*$/;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {import("@bluewombat/slot-kit").AgentPlugin} AgentPlugin
|
|
28
|
+
* @typedef {{ ok: true, plugins: AgentPlugin[], prompt: string | undefined, notes: string[] }
|
|
29
|
+
* | { ok: false, reason: string }} LoadedPlugins
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {import("@bluewombat/slot-kit").OptionValues} options
|
|
34
|
+
* @param {string} cwd
|
|
35
|
+
* @returns {LoadedPlugins}
|
|
36
|
+
*/
|
|
37
|
+
export function loadPlugins(options, cwd) {
|
|
38
|
+
/** @type {AgentPlugin[]} */
|
|
39
|
+
const plugins = [];
|
|
40
|
+
for (const dir of listOption(options, "pluginDir")) {
|
|
41
|
+
const path = resolve(cwd, dir);
|
|
42
|
+
if (!isDirectory(path)) {
|
|
43
|
+
return { ok: false, reason: `The --plugin-dir ${dir} is not a folder.` };
|
|
44
|
+
}
|
|
45
|
+
plugins.push({ id: pluginId(path), path, from: "local", integrity: null });
|
|
46
|
+
}
|
|
47
|
+
const profile = textOption(options, "profile");
|
|
48
|
+
if (profile === undefined) {
|
|
49
|
+
return { ok: true, plugins, prompt: undefined, notes: [] };
|
|
50
|
+
}
|
|
51
|
+
if (!PROFILE_NAME.test(profile) || profile === "current") {
|
|
52
|
+
return { ok: false, reason: `The --profile "${profile}" is not a deck profile name.` };
|
|
53
|
+
}
|
|
54
|
+
const out = textOption(options, "pluginsOut");
|
|
55
|
+
if (out === undefined) {
|
|
56
|
+
return {
|
|
57
|
+
ok: false,
|
|
58
|
+
reason: "--profile needs --plugins-out: the profile is built outside the workspace.",
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const hasBase = existsSync(join(cwd, "ai-plugin.json"));
|
|
62
|
+
// deck's own rule: a profile is a folder of .ai/profiles that holds a request.
|
|
63
|
+
const hasProfile = existsSync(join(cwd, ".ai", "profiles", profile, "ai-plugin.json"));
|
|
64
|
+
if (!hasBase && !hasProfile) {
|
|
65
|
+
return {
|
|
66
|
+
ok: true,
|
|
67
|
+
plugins,
|
|
68
|
+
prompt: undefined,
|
|
69
|
+
notes: [`The project has no deck setup: no plugin for profile ${profile}.`],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const deck = deckBin(options);
|
|
73
|
+
if (!deck.ok) {
|
|
74
|
+
return deck;
|
|
75
|
+
}
|
|
76
|
+
const args = [
|
|
77
|
+
deck.path,
|
|
78
|
+
"materialize",
|
|
79
|
+
"-C",
|
|
80
|
+
cwd,
|
|
81
|
+
"--out",
|
|
82
|
+
resolve(cwd, out, profile),
|
|
83
|
+
"--json",
|
|
84
|
+
...(hasProfile ? ["--profile", profile] : []),
|
|
85
|
+
];
|
|
86
|
+
// No timeout: this runs inside the agent wrapper's own process group, so
|
|
87
|
+
// the slot's deadline ends deck with it.
|
|
88
|
+
const run = spawnSync(process.execPath, args, { cwd, encoding: "utf8" });
|
|
89
|
+
if (run.error !== undefined || run.status !== 0) {
|
|
90
|
+
const said = (run.stderr || run.error?.message || `exit ${run.status}`).trim();
|
|
91
|
+
return {
|
|
92
|
+
ok: false,
|
|
93
|
+
reason: `deck materialize refused profile ${profile}: ${said} Run deck install in the project and commit its locks.`,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const result = parseMaterialized(run.stdout);
|
|
97
|
+
if (result === undefined) {
|
|
98
|
+
return { ok: false, reason: `deck materialize answered no JSON for profile ${profile}.` };
|
|
99
|
+
}
|
|
100
|
+
for (const plugin of result.plugins) {
|
|
101
|
+
plugins.push({ ...plugin, from: "deck" });
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
ok: true,
|
|
105
|
+
plugins,
|
|
106
|
+
prompt: profilePrompt(result.prompt),
|
|
107
|
+
notes: hasProfile ? [] : [`The project has no deck profile ${profile}: base plugins only.`],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The filled prompt, then the project's own words for this role.
|
|
113
|
+
*
|
|
114
|
+
* @param {string} prompt
|
|
115
|
+
* @param {string | undefined} profilePromptText
|
|
116
|
+
* @returns {string}
|
|
117
|
+
*/
|
|
118
|
+
export function withProfilePrompt(prompt, profilePromptText) {
|
|
119
|
+
if (profilePromptText === undefined) {
|
|
120
|
+
return prompt;
|
|
121
|
+
}
|
|
122
|
+
return `${prompt.trimEnd()}\n\n## Project instructions for this role\n\n${profilePromptText}\n`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* `--plugin-dir` once per plugin folder: what both Cursor and Claude read.
|
|
127
|
+
*
|
|
128
|
+
* @param {readonly AgentPlugin[]} plugins
|
|
129
|
+
* @returns {string[]}
|
|
130
|
+
*/
|
|
131
|
+
export function pluginDirArgs(plugins) {
|
|
132
|
+
return plugins.flatMap((plugin) => ["--plugin-dir", plugin.path]);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @param {import("@bluewombat/slot-kit").OptionValues} options
|
|
137
|
+
* @returns {{ ok: true, path: string } | { ok: false, reason: string }}
|
|
138
|
+
*/
|
|
139
|
+
function deckBin(options) {
|
|
140
|
+
const given = textOption(options, "deckBin");
|
|
141
|
+
if (given !== undefined) {
|
|
142
|
+
return { ok: true, path: resolve(given) };
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
const manifest = createRequire(import.meta.url).resolve("@pinkpuma/deck/package.json");
|
|
146
|
+
return { ok: true, path: join(dirname(manifest), "bin", "deck.js") };
|
|
147
|
+
} catch {
|
|
148
|
+
return {
|
|
149
|
+
ok: false,
|
|
150
|
+
reason:
|
|
151
|
+
"@pinkpuma/deck is not installed next to @bluewombat/slots. Install it, or pass --deck-bin.",
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* @param {string} stdout
|
|
158
|
+
* @returns {{ prompt: string | null, plugins: { id: string, path: string, integrity: string | null }[] } | undefined}
|
|
159
|
+
*/
|
|
160
|
+
function parseMaterialized(stdout) {
|
|
161
|
+
let raw;
|
|
162
|
+
try {
|
|
163
|
+
raw = JSON.parse(stdout);
|
|
164
|
+
} catch {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
if (raw === null || typeof raw !== "object" || !Array.isArray(raw.plugins)) {
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
const plugins = [];
|
|
171
|
+
for (const entry of raw.plugins) {
|
|
172
|
+
if (typeof entry?.id !== "string" || typeof entry?.path !== "string") {
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
plugins.push({
|
|
176
|
+
id: entry.id,
|
|
177
|
+
path: entry.path,
|
|
178
|
+
integrity: typeof entry.integrity === "string" ? entry.integrity : null,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
return { prompt: typeof raw.prompt === "string" ? raw.prompt : null, plugins };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* @param {string | null} path
|
|
186
|
+
* @returns {string | undefined}
|
|
187
|
+
*/
|
|
188
|
+
function profilePrompt(path) {
|
|
189
|
+
if (path === null) {
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
const text = readFileSync(path, "utf8").trim();
|
|
194
|
+
return text.length === 0 ? undefined : text;
|
|
195
|
+
} catch {
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* The name the plugin's manifest gives, else its folder's.
|
|
202
|
+
*
|
|
203
|
+
* @param {string} dir
|
|
204
|
+
* @returns {string}
|
|
205
|
+
*/
|
|
206
|
+
function pluginId(dir) {
|
|
207
|
+
for (const manifest of [
|
|
208
|
+
"plugin.json",
|
|
209
|
+
".cursor-plugin/plugin.json",
|
|
210
|
+
".claude-plugin/plugin.json",
|
|
211
|
+
]) {
|
|
212
|
+
try {
|
|
213
|
+
const name = JSON.parse(readFileSync(join(dir, manifest), "utf8")).name;
|
|
214
|
+
if (typeof name === "string" && name.length > 0) {
|
|
215
|
+
return name;
|
|
216
|
+
}
|
|
217
|
+
} catch {
|
|
218
|
+
// try the next manifest
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return basename(dir);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** @param {string} path */
|
|
225
|
+
function isDirectory(path) {
|
|
226
|
+
try {
|
|
227
|
+
return statSync(path).isDirectory();
|
|
228
|
+
} catch {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
}
|
package/agents/claude.mjs
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
transcriptFor,
|
|
14
14
|
writeContract,
|
|
15
15
|
} from "@bluewombat/slot-kit";
|
|
16
|
+
import { loadPlugins, pluginDirArgs } from "./agent-plugins.mjs";
|
|
16
17
|
import { extrasFromClaude } from "./claude-extras.mjs";
|
|
17
18
|
|
|
18
19
|
// Claude Code as a vendor agent. The prompt is already filled: this command
|
|
@@ -28,6 +29,11 @@ import { extrasFromClaude } from "./claude-extras.mjs";
|
|
|
28
29
|
// --agent-arg VALUE appended before the prompt. Repeatable
|
|
29
30
|
// --agent-home / --api-key-env recognised, then refused. Claude is not
|
|
30
31
|
// isolated yet; starting would keep the operator's config
|
|
32
|
+
// --plugin-dir DIR a plugin folder, repeatable (see agent-plugins.mjs)
|
|
33
|
+
// --profile / --plugins-out / --deck-bin recognised, then refused for the
|
|
34
|
+
// same reason: Claude would also load the operator's
|
|
35
|
+
// and the project's own plugins, so a role's profile
|
|
36
|
+
// would not be what it gets
|
|
31
37
|
// --id / --attempt / --context how a transcript is filed
|
|
32
38
|
|
|
33
39
|
const NAME = "Claude Code";
|
|
@@ -57,6 +63,15 @@ if (
|
|
|
57
63
|
"Claude Code does not isolate the operator home yet. Remove --agent-home and --api-key-env; the vendor was not started.",
|
|
58
64
|
);
|
|
59
65
|
}
|
|
66
|
+
if (
|
|
67
|
+
textOption(options, "profile") !== undefined ||
|
|
68
|
+
textOption(options, "pluginsOut") !== undefined ||
|
|
69
|
+
textOption(options, "deckBin") !== undefined
|
|
70
|
+
) {
|
|
71
|
+
refuse(
|
|
72
|
+
"Claude Code does not isolate the operator home yet, so a deck profile would not be all it loads. Remove --profile; the vendor was not started.",
|
|
73
|
+
);
|
|
74
|
+
}
|
|
60
75
|
|
|
61
76
|
const loaded = readAgentPrompt(options);
|
|
62
77
|
if (!loaded.ok) {
|
|
@@ -64,6 +79,12 @@ if (!loaded.ok) {
|
|
|
64
79
|
}
|
|
65
80
|
const prompt = loaded.prompt;
|
|
66
81
|
|
|
82
|
+
const loadedPlugins = loadPlugins(options, cwd);
|
|
83
|
+
if (!loadedPlugins.ok) {
|
|
84
|
+
refuse(loadedPlugins.reason);
|
|
85
|
+
}
|
|
86
|
+
const plugins = loadedPlugins.plugins;
|
|
87
|
+
|
|
67
88
|
const bin = textOption(options, "bin") ?? findExecutable(CLI_NAMES, INSTALL_DIRS);
|
|
68
89
|
if (bin === undefined) {
|
|
69
90
|
refuse(
|
|
@@ -83,6 +104,7 @@ const args = [
|
|
|
83
104
|
// Claude's stream-json only emits tool events with --verbose (headless docs).
|
|
84
105
|
...(outputFormat === "stream-json" ? ["--verbose"] : []),
|
|
85
106
|
...(model === undefined ? [] : ["--model", model]),
|
|
107
|
+
...pluginDirArgs(plugins),
|
|
86
108
|
...listOption(options, "agentArg"),
|
|
87
109
|
prompt,
|
|
88
110
|
];
|
|
@@ -92,6 +114,7 @@ const transcript = transcriptFor(process.argv, options, {
|
|
|
92
114
|
bin,
|
|
93
115
|
args,
|
|
94
116
|
prompt,
|
|
117
|
+
plugins,
|
|
95
118
|
});
|
|
96
119
|
const run = await runAgent({ file: bin, args, cwd });
|
|
97
120
|
const extras = extrasFromClaude(run.stdout, outputFormat, cwd);
|
|
@@ -119,6 +142,7 @@ function answer(run, binPath, extras) {
|
|
|
119
142
|
skills: extras.skills,
|
|
120
143
|
usage: extras.usage,
|
|
121
144
|
tools: extras.tools,
|
|
145
|
+
plugins,
|
|
122
146
|
}),
|
|
123
147
|
)}\n`,
|
|
124
148
|
);
|
package/agents/cursor.mjs
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
transcriptFor,
|
|
15
15
|
writeContract,
|
|
16
16
|
} from "@bluewombat/slot-kit";
|
|
17
|
+
import { loadPlugins, pluginDirArgs, withProfilePrompt } from "./agent-plugins.mjs";
|
|
17
18
|
import { extrasFromCursor } from "./cursor-extras.mjs";
|
|
18
19
|
|
|
19
20
|
// Cursor CLI as a vendor agent. The prompt is already filled: this command
|
|
@@ -29,6 +30,8 @@ import { extrasFromCursor } from "./cursor-extras.mjs";
|
|
|
29
30
|
// --agent-home DIR isolate this process: HOME is DIR, not the operator's
|
|
30
31
|
// --api-key-env NAME required with --agent-home. Read process.env[NAME];
|
|
31
32
|
// the value is never put on the argv
|
|
33
|
+
// --plugin-dir / --profile / --plugins-out / --deck-bin plugin folders,
|
|
34
|
+
// see agent-plugins.mjs
|
|
32
35
|
// --id / --attempt / --context how a transcript is filed
|
|
33
36
|
|
|
34
37
|
const NAME = "Cursor CLI";
|
|
@@ -47,7 +50,15 @@ const loaded = readAgentPrompt(options);
|
|
|
47
50
|
if (!loaded.ok) {
|
|
48
51
|
refuse(loaded.reason);
|
|
49
52
|
}
|
|
50
|
-
const
|
|
53
|
+
const loadedPlugins = loadPlugins(options, cwd);
|
|
54
|
+
if (!loadedPlugins.ok) {
|
|
55
|
+
refuse(loadedPlugins.reason);
|
|
56
|
+
}
|
|
57
|
+
for (const note of loadedPlugins.notes) {
|
|
58
|
+
process.stderr.write(`${note}\n`);
|
|
59
|
+
}
|
|
60
|
+
const plugins = loadedPlugins.plugins;
|
|
61
|
+
const prompt = withProfilePrompt(loaded.prompt, loadedPlugins.prompt);
|
|
51
62
|
|
|
52
63
|
const bin = textOption(options, "bin") ?? findExecutable(CLI_NAMES, INSTALL_DIRS);
|
|
53
64
|
if (bin === undefined) {
|
|
@@ -70,6 +81,7 @@ const args = [
|
|
|
70
81
|
"--output-format",
|
|
71
82
|
outputFormat,
|
|
72
83
|
...(model === undefined ? [] : ["--model", model]),
|
|
84
|
+
...pluginDirArgs(plugins),
|
|
73
85
|
...listOption(options, "agentArg"),
|
|
74
86
|
prompt,
|
|
75
87
|
];
|
|
@@ -84,6 +96,7 @@ const transcript = transcriptFor(process.argv, options, {
|
|
|
84
96
|
bin,
|
|
85
97
|
args,
|
|
86
98
|
prompt,
|
|
99
|
+
plugins,
|
|
87
100
|
});
|
|
88
101
|
const run = await runAgent({
|
|
89
102
|
file: bin,
|
|
@@ -224,6 +237,7 @@ function answer(run, binPath, extras) {
|
|
|
224
237
|
skills: extras.skills,
|
|
225
238
|
usage: extras.usage,
|
|
226
239
|
tools: extras.tools,
|
|
240
|
+
plugins,
|
|
227
241
|
}),
|
|
228
242
|
)}\n`,
|
|
229
243
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bluewombat/slots",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Shipped mason slots: Planners, Builders, Gates, publishers, and refreshers a Project names in its config.",
|
|
6
6
|
"keywords": [
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"test": "tsx --test $(find test -name '*.test.ts')"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@bluewombat/slot-kit": "^0.
|
|
56
|
+
"@bluewombat/slot-kit": "^0.7.0",
|
|
57
|
+
"@pinkpuma/deck": "1.0.0"
|
|
57
58
|
}
|
|
58
59
|
}
|