@clicksmith/agent-config 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ClickSmith contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # @clicksmith/agent-config
2
+
3
+ The config-driven brain for launching coding agents and teaching them the
4
+ ClickSmith conventions. Two responsibilities:
5
+
6
+ 1. **Adapters as data.** A built-in adapter is just an entry in
7
+ `agents.config.json`. The launcher only resolves `{placeholders}` — it has no
8
+ hardcoded knowledge of any vendor CLI.
9
+ 2. **One instruction template, many native files.** The shared body is authored
10
+ once and rendered into each agent's idiomatic location.
11
+
12
+ ## Adapters are data, not code
13
+
14
+ ```jsonc
15
+ // agents.config.json
16
+ {
17
+ "version": 1,
18
+ "defaultAgent": "claude",
19
+ "agents": [
20
+ {
21
+ "id": "claude",
22
+ "label": "Claude Code",
23
+ "command": "claude",
24
+ "args": ["-p", "{prompt}", "--append-system-prompt", "@{instructionFile}"],
25
+ "detect": { "anyOf": ["claude"] },
26
+ "instructions": { "target": "claude", "file": "CLAUDE.md" },
27
+ "mcp": { "register": "claude" }
28
+ }
29
+ ]
30
+ }
31
+ ```
32
+
33
+ > ⚠️ The shipped templates (`DEFAULT_AGENTS`) are **examples**. Changing a CLI
34
+ > flag is always an `agents.config.json` edit — never a code change.
35
+
36
+ ### Placeholders
37
+
38
+ The launcher resolves exactly these tokens, and nothing else:
39
+
40
+ | Placeholder | Meaning |
41
+ | --- | --- |
42
+ | `{bundlePath}` | Absolute path to the serialized capture bundle. |
43
+ | `{prompt}` | The user's free-text prompt. |
44
+ | `{instructionFile}` | Rendered instruction file for this agent. |
45
+ | `{mode}` | `plan` or `edit`. |
46
+ | `{mcpServer}` | Reference to reach the ClickSmith MCP server. |
47
+ | `{cwd}` | Sandbox working directory. |
48
+
49
+ ```ts
50
+ import { configToAdapter, DEFAULT_AGENTS } from '@clicksmith/agent-config';
51
+
52
+ const adapter = configToAdapter(DEFAULT_AGENTS[0]);
53
+ await adapter.isAvailable(ctx); // PATH probe of detect.anyOf
54
+ adapter.buildCommand(ctx); // → { command, args, env, cwd }
55
+ ```
56
+
57
+ ## Config layering
58
+
59
+ ```ts
60
+ import { mergeAgentsConfig, DEFAULT_AGENTS_CONFIG } from '@clicksmith/agent-config';
61
+
62
+ const effective = mergeAgentsConfig(DEFAULT_AGENTS_CONFIG, projectConfig, userConfig);
63
+ ```
64
+
65
+ Defaults → project → user. Same `id` replaces; new ids append; the last
66
+ `defaultAgent` wins.
67
+
68
+ ## Instruction rendering
69
+
70
+ ```ts
71
+ import { renderInstructions, applyManagedBlock } from '@clicksmith/agent-config';
72
+
73
+ const rule = renderInstructions('claude', { daemonPort: 8722 });
74
+ // rule.path === 'CLAUDE.md', rule.shared === true → merge as a managed block:
75
+ const next = applyManagedBlock(existingClaudeMd, rule.content);
76
+ ```
77
+
78
+ Targets: `claude` (`CLAUDE.md`), `codex` (`AGENTS.md`), `cursor`
79
+ (`.cursor/rules/clicksmith.mdc`), `antigravity`
80
+ (`.antigravity/rules/clicksmith.md`), `generic`
81
+ (`.clicksmith/AGENT_INSTRUCTIONS.md`). Shared files use a managed block so your
82
+ existing content is preserved; dedicated files are written whole.
83
+
84
+ Every rendered file teaches the same three things: the `#N` reference system,
85
+ the `source → attr → behavioral → dom` locator priority, and the plan/worktree
86
+ safety contract.
@@ -0,0 +1,424 @@
1
+ import { ExecutionMode, Isolation } from '@clicksmith/core';
2
+ import { z } from 'zod';
3
+
4
+ /** Every placeholder the launcher knows how to resolve in a command template. */
5
+ declare const PLACEHOLDER_KEYS: readonly ["bundlePath", "prompt", "instructionFile", "mode", "mcpServer", "cwd"];
6
+ type PlaceholderKey = (typeof PLACEHOLDER_KEYS)[number];
7
+ /**
8
+ * Everything the launcher needs to turn a config-driven command template into a
9
+ * concrete process invocation. This is also the `ctx` passed to adapter hooks.
10
+ */
11
+ interface AgentLaunchContext {
12
+ /** Absolute path to the serialized capture bundle. */
13
+ bundlePath: string;
14
+ /** The user's free-text prompt. */
15
+ prompt: string;
16
+ /** Absolute path to the rendered instruction file for this agent. */
17
+ instructionFile: string;
18
+ /** Execution mode (`plan` | `edit`). */
19
+ mode: ExecutionMode;
20
+ /** A reference the agent can use to reach the ClickSmith MCP server. */
21
+ mcpServer: string;
22
+ /** Working directory for the spawned process (the sandbox). */
23
+ cwd: string;
24
+ /** Isolation strategy in effect for this run. */
25
+ isolation: Isolation;
26
+ /** The agent id being launched. */
27
+ agentId: string;
28
+ /**
29
+ * Resolves whether an executable exists on PATH. Injected by the daemon so
30
+ * adapters never have to spawn processes themselves. Defaults to a PATH scan.
31
+ */
32
+ binExists?: (bin: string) => Promise<boolean>;
33
+ }
34
+ /** A concrete, ready-to-spawn process specification. */
35
+ interface CommandSpec {
36
+ command: string;
37
+ args: string[];
38
+ env?: Record<string, string>;
39
+ cwd?: string;
40
+ }
41
+ /** A normalized event parsed from an agent's stdout/stderr stream. */
42
+ type AgentEvent = {
43
+ type: 'plan-ready';
44
+ plan?: string;
45
+ diff?: string;
46
+ } | {
47
+ type: 'progress';
48
+ message: string;
49
+ } | {
50
+ type: 'error';
51
+ message: string;
52
+ };
53
+ /**
54
+ * The adapter contract. Built-in adapters are produced from `agents.config.json`
55
+ * by {@link configToAdapter}; the launcher only resolves placeholders. Authors
56
+ * may also hand-write adapters that satisfy this interface.
57
+ */
58
+ interface AgentAdapter {
59
+ id: string;
60
+ /** Whether the agent's CLI is installed/usable in this context. */
61
+ isAvailable(ctx: AgentLaunchContext): Promise<boolean>;
62
+ /** Build the concrete command to spawn. */
63
+ buildCommand(ctx: AgentLaunchContext): CommandSpec;
64
+ /** Optionally map a raw output chunk into structured events. */
65
+ parseEvents?(chunk: string, ctx: AgentLaunchContext): AgentEvent[];
66
+ }
67
+
68
+ /** Which native instruction format a renderer should target. */
69
+ declare const InstructionTargetSchema: z.ZodEnum<["claude", "cursor", "codex", "antigravity", "generic"]>;
70
+ type InstructionTarget = z.infer<typeof InstructionTargetSchema>;
71
+ /** How (and whether) to register the daemon's MCP server for this agent. */
72
+ declare const McpRegistrationSchema: z.ZodEnum<["claude", "cursor", "codex", "mcp-json", "none"]>;
73
+ type McpRegistration = z.infer<typeof McpRegistrationSchema>;
74
+ /**
75
+ * One agent definition. The `command`/`args` are templates containing
76
+ * placeholders like `{prompt}` — they are **data**, never code. Changing a
77
+ * vendor flag is an edit here, not a code change.
78
+ */
79
+ declare const AgentConfigSchema: z.ZodObject<{
80
+ id: z.ZodString;
81
+ label: z.ZodOptional<z.ZodString>;
82
+ /** Executable name or path. May contain placeholders. */
83
+ command: z.ZodString;
84
+ /** Argument templates; each entry may contain placeholders. */
85
+ args: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
86
+ /** Extra environment variables (values may contain placeholders). */
87
+ env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
88
+ /** Working directory override (placeholders allowed). Defaults to the sandbox. */
89
+ cwd: z.ZodOptional<z.ZodString>;
90
+ /** How to detect availability: any of these executables must be on PATH. */
91
+ detect: z.ZodOptional<z.ZodObject<{
92
+ anyOf: z.ZodArray<z.ZodString, "many">;
93
+ }, "strip", z.ZodTypeAny, {
94
+ anyOf: string[];
95
+ }, {
96
+ anyOf: string[];
97
+ }>>;
98
+ /** Where to render this agent's instruction file. */
99
+ instructions: z.ZodOptional<z.ZodObject<{
100
+ target: z.ZodEnum<["claude", "cursor", "codex", "antigravity", "generic"]>;
101
+ file: z.ZodString;
102
+ }, "strip", z.ZodTypeAny, {
103
+ target: "claude" | "cursor" | "codex" | "antigravity" | "generic";
104
+ file: string;
105
+ }, {
106
+ target: "claude" | "cursor" | "codex" | "antigravity" | "generic";
107
+ file: string;
108
+ }>>;
109
+ /** How to register the daemon MCP server for this agent. */
110
+ mcp: z.ZodOptional<z.ZodObject<{
111
+ register: z.ZodEnum<["claude", "cursor", "codex", "mcp-json", "none"]>;
112
+ }, "strip", z.ZodTypeAny, {
113
+ register: "claude" | "cursor" | "codex" | "mcp-json" | "none";
114
+ }, {
115
+ register: "claude" | "cursor" | "codex" | "mcp-json" | "none";
116
+ }>>;
117
+ /** Optional regexes that map log lines to structured events. */
118
+ parse: z.ZodOptional<z.ZodObject<{
119
+ planReady: z.ZodOptional<z.ZodString>;
120
+ error: z.ZodOptional<z.ZodString>;
121
+ }, "strip", z.ZodTypeAny, {
122
+ error?: string | undefined;
123
+ planReady?: string | undefined;
124
+ }, {
125
+ error?: string | undefined;
126
+ planReady?: string | undefined;
127
+ }>>;
128
+ }, "strip", z.ZodTypeAny, {
129
+ id: string;
130
+ command: string;
131
+ args: string[];
132
+ cwd?: string | undefined;
133
+ label?: string | undefined;
134
+ env?: Record<string, string> | undefined;
135
+ detect?: {
136
+ anyOf: string[];
137
+ } | undefined;
138
+ instructions?: {
139
+ target: "claude" | "cursor" | "codex" | "antigravity" | "generic";
140
+ file: string;
141
+ } | undefined;
142
+ mcp?: {
143
+ register: "claude" | "cursor" | "codex" | "mcp-json" | "none";
144
+ } | undefined;
145
+ parse?: {
146
+ error?: string | undefined;
147
+ planReady?: string | undefined;
148
+ } | undefined;
149
+ }, {
150
+ id: string;
151
+ command: string;
152
+ cwd?: string | undefined;
153
+ label?: string | undefined;
154
+ args?: string[] | undefined;
155
+ env?: Record<string, string> | undefined;
156
+ detect?: {
157
+ anyOf: string[];
158
+ } | undefined;
159
+ instructions?: {
160
+ target: "claude" | "cursor" | "codex" | "antigravity" | "generic";
161
+ file: string;
162
+ } | undefined;
163
+ mcp?: {
164
+ register: "claude" | "cursor" | "codex" | "mcp-json" | "none";
165
+ } | undefined;
166
+ parse?: {
167
+ error?: string | undefined;
168
+ planReady?: string | undefined;
169
+ } | undefined;
170
+ }>;
171
+ type AgentConfig = z.infer<typeof AgentConfigSchema>;
172
+ /** The top-level `agents.config.json` document. */
173
+ declare const AgentsConfigSchema: z.ZodObject<{
174
+ version: z.ZodDefault<z.ZodLiteral<1>>;
175
+ defaultAgent: z.ZodOptional<z.ZodString>;
176
+ agents: z.ZodDefault<z.ZodArray<z.ZodObject<{
177
+ id: z.ZodString;
178
+ label: z.ZodOptional<z.ZodString>;
179
+ /** Executable name or path. May contain placeholders. */
180
+ command: z.ZodString;
181
+ /** Argument templates; each entry may contain placeholders. */
182
+ args: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
183
+ /** Extra environment variables (values may contain placeholders). */
184
+ env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
185
+ /** Working directory override (placeholders allowed). Defaults to the sandbox. */
186
+ cwd: z.ZodOptional<z.ZodString>;
187
+ /** How to detect availability: any of these executables must be on PATH. */
188
+ detect: z.ZodOptional<z.ZodObject<{
189
+ anyOf: z.ZodArray<z.ZodString, "many">;
190
+ }, "strip", z.ZodTypeAny, {
191
+ anyOf: string[];
192
+ }, {
193
+ anyOf: string[];
194
+ }>>;
195
+ /** Where to render this agent's instruction file. */
196
+ instructions: z.ZodOptional<z.ZodObject<{
197
+ target: z.ZodEnum<["claude", "cursor", "codex", "antigravity", "generic"]>;
198
+ file: z.ZodString;
199
+ }, "strip", z.ZodTypeAny, {
200
+ target: "claude" | "cursor" | "codex" | "antigravity" | "generic";
201
+ file: string;
202
+ }, {
203
+ target: "claude" | "cursor" | "codex" | "antigravity" | "generic";
204
+ file: string;
205
+ }>>;
206
+ /** How to register the daemon MCP server for this agent. */
207
+ mcp: z.ZodOptional<z.ZodObject<{
208
+ register: z.ZodEnum<["claude", "cursor", "codex", "mcp-json", "none"]>;
209
+ }, "strip", z.ZodTypeAny, {
210
+ register: "claude" | "cursor" | "codex" | "mcp-json" | "none";
211
+ }, {
212
+ register: "claude" | "cursor" | "codex" | "mcp-json" | "none";
213
+ }>>;
214
+ /** Optional regexes that map log lines to structured events. */
215
+ parse: z.ZodOptional<z.ZodObject<{
216
+ planReady: z.ZodOptional<z.ZodString>;
217
+ error: z.ZodOptional<z.ZodString>;
218
+ }, "strip", z.ZodTypeAny, {
219
+ error?: string | undefined;
220
+ planReady?: string | undefined;
221
+ }, {
222
+ error?: string | undefined;
223
+ planReady?: string | undefined;
224
+ }>>;
225
+ }, "strip", z.ZodTypeAny, {
226
+ id: string;
227
+ command: string;
228
+ args: string[];
229
+ cwd?: string | undefined;
230
+ label?: string | undefined;
231
+ env?: Record<string, string> | undefined;
232
+ detect?: {
233
+ anyOf: string[];
234
+ } | undefined;
235
+ instructions?: {
236
+ target: "claude" | "cursor" | "codex" | "antigravity" | "generic";
237
+ file: string;
238
+ } | undefined;
239
+ mcp?: {
240
+ register: "claude" | "cursor" | "codex" | "mcp-json" | "none";
241
+ } | undefined;
242
+ parse?: {
243
+ error?: string | undefined;
244
+ planReady?: string | undefined;
245
+ } | undefined;
246
+ }, {
247
+ id: string;
248
+ command: string;
249
+ cwd?: string | undefined;
250
+ label?: string | undefined;
251
+ args?: string[] | undefined;
252
+ env?: Record<string, string> | undefined;
253
+ detect?: {
254
+ anyOf: string[];
255
+ } | undefined;
256
+ instructions?: {
257
+ target: "claude" | "cursor" | "codex" | "antigravity" | "generic";
258
+ file: string;
259
+ } | undefined;
260
+ mcp?: {
261
+ register: "claude" | "cursor" | "codex" | "mcp-json" | "none";
262
+ } | undefined;
263
+ parse?: {
264
+ error?: string | undefined;
265
+ planReady?: string | undefined;
266
+ } | undefined;
267
+ }>, "many">>;
268
+ }, "strip", z.ZodTypeAny, {
269
+ version: 1;
270
+ agents: {
271
+ id: string;
272
+ command: string;
273
+ args: string[];
274
+ cwd?: string | undefined;
275
+ label?: string | undefined;
276
+ env?: Record<string, string> | undefined;
277
+ detect?: {
278
+ anyOf: string[];
279
+ } | undefined;
280
+ instructions?: {
281
+ target: "claude" | "cursor" | "codex" | "antigravity" | "generic";
282
+ file: string;
283
+ } | undefined;
284
+ mcp?: {
285
+ register: "claude" | "cursor" | "codex" | "mcp-json" | "none";
286
+ } | undefined;
287
+ parse?: {
288
+ error?: string | undefined;
289
+ planReady?: string | undefined;
290
+ } | undefined;
291
+ }[];
292
+ defaultAgent?: string | undefined;
293
+ }, {
294
+ version?: 1 | undefined;
295
+ defaultAgent?: string | undefined;
296
+ agents?: {
297
+ id: string;
298
+ command: string;
299
+ cwd?: string | undefined;
300
+ label?: string | undefined;
301
+ args?: string[] | undefined;
302
+ env?: Record<string, string> | undefined;
303
+ detect?: {
304
+ anyOf: string[];
305
+ } | undefined;
306
+ instructions?: {
307
+ target: "claude" | "cursor" | "codex" | "antigravity" | "generic";
308
+ file: string;
309
+ } | undefined;
310
+ mcp?: {
311
+ register: "claude" | "cursor" | "codex" | "mcp-json" | "none";
312
+ } | undefined;
313
+ parse?: {
314
+ error?: string | undefined;
315
+ planReady?: string | undefined;
316
+ } | undefined;
317
+ }[] | undefined;
318
+ }>;
319
+ type AgentsConfig = z.infer<typeof AgentsConfigSchema>;
320
+ /** Parse and validate an `agents.config.json` value without throwing. */
321
+ declare function parseAgentsConfig(value: unknown): {
322
+ ok: true;
323
+ config: AgentsConfig;
324
+ } | {
325
+ ok: false;
326
+ error: z.ZodError;
327
+ };
328
+
329
+ /** Build the placeholder → value map from a launch context. */
330
+ declare function placeholderValues(ctx: AgentLaunchContext): Record<PlaceholderKey, string>;
331
+ /**
332
+ * Replace every known `{placeholder}` in a template string with its value.
333
+ * Unknown placeholders are left untouched (so literal braces survive). This is
334
+ * the *entire* job of the launcher — no vendor-specific knowledge lives here.
335
+ */
336
+ declare function resolvePlaceholders(template: string, ctx: AgentLaunchContext): string;
337
+ /** Resolve an entire {@link AgentConfig} into a concrete {@link CommandSpec}. */
338
+ declare function resolveCommand(config: AgentConfig, ctx: AgentLaunchContext): CommandSpec;
339
+ /** List the placeholders actually referenced by a template string. */
340
+ declare function referencedPlaceholders(template: string): PlaceholderKey[];
341
+
342
+ /**
343
+ * Turn a config entry into a live {@link AgentAdapter}. This is the bridge that
344
+ * lets *data* (`agents.config.json`) behave like a built-in adapter:
345
+ *
346
+ * - `isAvailable` checks the configured `detect.anyOf` executables on PATH.
347
+ * - `buildCommand` resolves placeholders into a concrete command.
348
+ * - `parseEvents` applies the optional regexes from `config.parse`.
349
+ */
350
+ declare function configToAdapter(config: AgentConfig): AgentAdapter;
351
+ /** Build adapters for every agent in a config, keyed by id. */
352
+ declare function buildAdapters(configs: readonly AgentConfig[]): Map<string, AgentAdapter>;
353
+
354
+ /**
355
+ * Default executable-on-PATH check. Scans `PATH` for the given binary,
356
+ * honoring `PATHEXT` on Windows. Adapters receive this via the launch context
357
+ * but the daemon may override it (e.g. for tests).
358
+ */
359
+ declare function defaultBinExists(bin: string): Promise<boolean>;
360
+
361
+ interface InstructionTemplateOptions {
362
+ /** MCP tool names exposed by the daemon. */
363
+ mcpTools?: readonly string[];
364
+ /** The daemon port, for the agent's reference. */
365
+ daemonPort?: number;
366
+ /** Project-specific stable attributes the project relies on (e.g. data-testid). */
367
+ stableAttrs?: readonly string[];
368
+ }
369
+ declare const DEFAULT_MCP_TOOLS: readonly ["get_session", "list_elements", "get_element_by_id", "get_latest_request"];
370
+ /**
371
+ * The single, shared instruction body. Every native renderer wraps this exact
372
+ * text — so Claude, Cursor, Codex, Antigravity and generic agents all learn the
373
+ * same three things: the `#N` reference system, locator priority, and the
374
+ * plan/worktree safety contract.
375
+ */
376
+ declare function renderInstructionBody(options?: InstructionTemplateOptions): string;
377
+
378
+ declare const MANAGED_BEGIN = "<!-- BEGIN CLICKSMITH (managed \u2014 do not edit by hand) -->";
379
+ declare const MANAGED_END = "<!-- END CLICKSMITH (managed) -->";
380
+ interface RenderedInstruction {
381
+ target: InstructionTarget;
382
+ /** Default relative path for this target's native file. */
383
+ path: string;
384
+ /**
385
+ * `true` when the target file commonly holds other content (e.g. `CLAUDE.md`,
386
+ * `AGENTS.md`). The installer then inserts {@link content} as a managed block
387
+ * rather than overwriting the whole file.
388
+ */
389
+ shared: boolean;
390
+ /** Full file content (dedicated files) or managed-block body (shared files). */
391
+ content: string;
392
+ }
393
+ /** Wrap a body in the managed-block markers. */
394
+ declare function wrapManagedBlock(body: string): string;
395
+ /**
396
+ * Insert or replace the ClickSmith managed block within existing file content,
397
+ * preserving everything the user wrote outside the markers.
398
+ */
399
+ declare function applyManagedBlock(existing: string | undefined, body: string): string;
400
+ /** Render the shared instruction body into a target's native file. */
401
+ declare function renderInstructions(target: InstructionTarget, options?: InstructionTemplateOptions): RenderedInstruction;
402
+ /** Render instruction files for several targets at once. */
403
+ declare function renderAllInstructions(targets: readonly InstructionTarget[], options?: InstructionTemplateOptions): RenderedInstruction[];
404
+
405
+ /**
406
+ * Built-in agent templates. **These are example defaults — data, not launcher
407
+ * logic.** Changing a vendor's CLI flag means editing `agents.config.json`
408
+ * (or these defaults), never the daemon. The launcher only resolves the
409
+ * `{placeholders}` below; it has no idea what `claude` or `codex` actually do.
410
+ */
411
+ declare const DEFAULT_AGENTS: AgentConfig[];
412
+ /** The full default `agents.config.json` document. */
413
+ declare const DEFAULT_AGENTS_CONFIG: AgentsConfig;
414
+
415
+ /**
416
+ * Layer agent configs: shipped defaults → project config → user config. Agents
417
+ * with the same `id` are replaced by later layers; new ids are appended in
418
+ * order. `defaultAgent` from the last layer that sets it wins.
419
+ */
420
+ declare function mergeAgentsConfig(base: AgentsConfig, ...overlays: AgentsConfig[]): AgentsConfig;
421
+ /** Resolve the effective agent for a run: explicit id → defaultAgent → first. */
422
+ declare function resolveAgent(config: AgentsConfig, agentId?: string): AgentConfig | undefined;
423
+
424
+ export { type AgentAdapter, type AgentConfig, AgentConfigSchema, type AgentEvent, type AgentLaunchContext, type AgentsConfig, AgentsConfigSchema, type CommandSpec, DEFAULT_AGENTS, DEFAULT_AGENTS_CONFIG, DEFAULT_MCP_TOOLS, type InstructionTarget, InstructionTargetSchema, type InstructionTemplateOptions, MANAGED_BEGIN, MANAGED_END, type McpRegistration, McpRegistrationSchema, PLACEHOLDER_KEYS, type PlaceholderKey, type RenderedInstruction, applyManagedBlock, buildAdapters, configToAdapter, defaultBinExists, mergeAgentsConfig, parseAgentsConfig, placeholderValues, referencedPlaceholders, renderAllInstructions, renderInstructionBody, renderInstructions, resolveAgent, resolveCommand, resolvePlaceholders, wrapManagedBlock };
package/dist/index.js ADDED
@@ -0,0 +1,382 @@
1
+ import { z } from 'zod';
2
+ import { access, constants } from 'fs/promises';
3
+ import { delimiter, join } from 'path';
4
+ import { LOCATOR_PRIORITY } from '@clicksmith/core';
5
+
6
+ // src/types.ts
7
+ var PLACEHOLDER_KEYS = [
8
+ "bundlePath",
9
+ "prompt",
10
+ "instructionFile",
11
+ "mode",
12
+ "mcpServer",
13
+ "cwd"
14
+ ];
15
+ var InstructionTargetSchema = z.enum([
16
+ "claude",
17
+ "cursor",
18
+ "codex",
19
+ "antigravity",
20
+ "generic"
21
+ ]);
22
+ var McpRegistrationSchema = z.enum(["claude", "cursor", "codex", "mcp-json", "none"]);
23
+ var AgentConfigSchema = z.object({
24
+ id: z.string().min(1),
25
+ label: z.string().optional(),
26
+ /** Executable name or path. May contain placeholders. */
27
+ command: z.string().min(1),
28
+ /** Argument templates; each entry may contain placeholders. */
29
+ args: z.array(z.string()).default([]),
30
+ /** Extra environment variables (values may contain placeholders). */
31
+ env: z.record(z.string()).optional(),
32
+ /** Working directory override (placeholders allowed). Defaults to the sandbox. */
33
+ cwd: z.string().optional(),
34
+ /** How to detect availability: any of these executables must be on PATH. */
35
+ detect: z.object({
36
+ anyOf: z.array(z.string()).min(1)
37
+ }).optional(),
38
+ /** Where to render this agent's instruction file. */
39
+ instructions: z.object({
40
+ target: InstructionTargetSchema,
41
+ file: z.string().min(1)
42
+ }).optional(),
43
+ /** How to register the daemon MCP server for this agent. */
44
+ mcp: z.object({
45
+ register: McpRegistrationSchema
46
+ }).optional(),
47
+ /** Optional regexes that map log lines to structured events. */
48
+ parse: z.object({
49
+ planReady: z.string().optional(),
50
+ error: z.string().optional()
51
+ }).optional()
52
+ });
53
+ var AgentsConfigSchema = z.object({
54
+ version: z.literal(1).default(1),
55
+ defaultAgent: z.string().optional(),
56
+ agents: z.array(AgentConfigSchema).default([])
57
+ });
58
+ function parseAgentsConfig(value) {
59
+ const result = AgentsConfigSchema.safeParse(value);
60
+ return result.success ? { ok: true, config: result.data } : { ok: false, error: result.error };
61
+ }
62
+
63
+ // src/placeholders.ts
64
+ function placeholderValues(ctx) {
65
+ return {
66
+ bundlePath: ctx.bundlePath,
67
+ prompt: ctx.prompt,
68
+ instructionFile: ctx.instructionFile,
69
+ mode: ctx.mode,
70
+ mcpServer: ctx.mcpServer,
71
+ cwd: ctx.cwd
72
+ };
73
+ }
74
+ var PLACEHOLDER_RE = /\{([a-zA-Z]+)\}/g;
75
+ function resolvePlaceholders(template, ctx) {
76
+ const values = placeholderValues(ctx);
77
+ return template.replace(PLACEHOLDER_RE, (match, key) => {
78
+ return key in values ? values[key] : match;
79
+ });
80
+ }
81
+ function resolveCommand(config, ctx) {
82
+ const env = config.env ? Object.fromEntries(
83
+ Object.entries(config.env).map(([k, v]) => [k, resolvePlaceholders(v, ctx)])
84
+ ) : void 0;
85
+ return {
86
+ command: resolvePlaceholders(config.command, ctx),
87
+ args: config.args.map((a) => resolvePlaceholders(a, ctx)),
88
+ cwd: config.cwd ? resolvePlaceholders(config.cwd, ctx) : ctx.cwd,
89
+ ...env ? { env } : {}
90
+ };
91
+ }
92
+ function referencedPlaceholders(template) {
93
+ const found = /* @__PURE__ */ new Set();
94
+ for (const m of template.matchAll(PLACEHOLDER_RE)) {
95
+ const key = m[1];
96
+ if (PLACEHOLDER_KEYS.includes(key)) {
97
+ found.add(key);
98
+ }
99
+ }
100
+ return [...found];
101
+ }
102
+ async function defaultBinExists(bin) {
103
+ if (bin.includes("/") || bin.includes("\\")) {
104
+ return canExecute(bin);
105
+ }
106
+ const pathEnv = process.env.PATH ?? "";
107
+ const exts = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";") : [""];
108
+ for (const dir of pathEnv.split(delimiter)) {
109
+ if (!dir) continue;
110
+ for (const ext of exts) {
111
+ if (await canExecute(join(dir, bin + ext))) return true;
112
+ }
113
+ }
114
+ return false;
115
+ }
116
+ async function canExecute(file) {
117
+ try {
118
+ await access(file, constants.X_OK);
119
+ return true;
120
+ } catch {
121
+ return false;
122
+ }
123
+ }
124
+
125
+ // src/adapter.ts
126
+ function configToAdapter(config) {
127
+ const planRe = config.parse?.planReady ? new RegExp(config.parse.planReady, "i") : void 0;
128
+ const errRe = config.parse?.error ? new RegExp(config.parse.error, "i") : void 0;
129
+ return {
130
+ id: config.id,
131
+ async isAvailable(ctx) {
132
+ const bins = config.detect?.anyOf ?? [config.command];
133
+ const exists = ctx.binExists ?? defaultBinExists;
134
+ for (const bin of bins) {
135
+ if (await exists(bin)) return true;
136
+ }
137
+ return false;
138
+ },
139
+ buildCommand(ctx) {
140
+ return resolveCommand(config, ctx);
141
+ },
142
+ parseEvents(chunk) {
143
+ if (!planRe && !errRe) return [];
144
+ const events = [];
145
+ for (const line of chunk.split(/\r?\n/)) {
146
+ if (!line.trim()) continue;
147
+ if (errRe?.test(line)) events.push({ type: "error", message: line.trim() });
148
+ else if (planRe?.test(line)) events.push({ type: "plan-ready" });
149
+ }
150
+ return events;
151
+ }
152
+ };
153
+ }
154
+ function buildAdapters(configs) {
155
+ const map = /* @__PURE__ */ new Map();
156
+ for (const config of configs) {
157
+ map.set(config.id, configToAdapter(config));
158
+ }
159
+ return map;
160
+ }
161
+ var DEFAULT_MCP_TOOLS = [
162
+ "get_session",
163
+ "list_elements",
164
+ "get_element_by_id",
165
+ "get_latest_request"
166
+ ];
167
+ function renderInstructionBody(options = {}) {
168
+ const tools = options.mcpTools ?? DEFAULT_MCP_TOOLS;
169
+ const attrs = options.stableAttrs ?? [];
170
+ const locatorList = LOCATOR_PRIORITY.map((kind, i) => `${i + 1}. **${kind}**`).join(" \u2192 ");
171
+ return `# ClickSmith \u2014 working with captured UI requests
172
+
173
+ You are being asked to change UI elements that a human pointed at in their browser
174
+ with ClickSmith. Each request arrives as a **capture bundle** (a JSON file whose
175
+ path is provided to you) describing one or more elements and a free-text prompt.
176
+
177
+ ## 1. The \`#N\` reference system
178
+
179
+ Elements are numbered \`#1\`, \`#2\`, \u2026 in the order they were captured. The user's
180
+ prompt refers to them by number, e.g. _"make #1 match #2's style"_. Always resolve
181
+ \`#N\` to \`elements[]\` entries with the matching \`id\` field. Never guess which
182
+ element is meant \u2014 read the bundle.
183
+
184
+ ## 2. Locator priority
185
+
186
+ Each element carries a \`locator\`. Trust them in this exact order, best first:
187
+
188
+ ${locatorList}
189
+
190
+ - **source** gives an exact \`file:line\` (injected in dev by @clicksmith/unplugin) \u2014
191
+ edit there directly.
192
+ - **attr** is a stable attribute${attrs.length ? ` (this project uses: ${attrs.map((a) => `\`${a}\``).join(", ")})` : ""}; grep for it to find the JSX/template.
193
+ - **behavioral** is an ARIA role + accessible name; search for the visible text/label.
194
+ - **dom** is a structural fallback; use \`el.text\`, \`el.attrs\`, and \`near\` context to
195
+ locate the component, and prefer adding a stable attribute while you're there.
196
+
197
+ ## 3. Plan / worktree safety (read carefully)
198
+
199
+ ClickSmith runs you inside an **isolated git worktree** by default. The execution
200
+ mode is in \`execution.mode\`:
201
+
202
+ - **plan** (the default): produce a clear plan and, if helpful, a diff \u2014 but **do
203
+ not** assume your edits ship. The human reviews your plan/diff and explicitly
204
+ clicks **Apply**. Your job is to propose, precisely.
205
+ - **edit**: you may modify files in the sandbox. They still do **not** reach the
206
+ user's main working tree until they click Apply.
207
+
208
+ Never run destructive git commands, never push, and never touch files outside the
209
+ sandbox working directory.
210
+
211
+ ## 4. Reading the request
212
+
213
+ The bundle path is passed on the command line / via your harness. ${tools.length ? `You can also use these MCP tools (server \`clicksmith\`): ${tools.map((t) => `\`${t}\``).join(", ")}. Use \`get_latest_request\` to fetch the most recent submission, then \`get_element_by_id\` to resolve a specific \`#N\`.` : ""}
214
+
215
+ Each element includes: \`locator\`, \`el\` (tag/text/role/label/attrs/icon hints),
216
+ \`near\` (surrounding labels & headings), \`conditions\` (viewport/theme), and an
217
+ optional \`screenshot\` thumbnail. Use \`near\` and \`app.route\` to disambiguate when
218
+ multiple elements look similar.
219
+ ${options.daemonPort ? `
220
+ The ClickSmith daemon is at http://127.0.0.1:${options.daemonPort}.
221
+ ` : ""}`;
222
+ }
223
+
224
+ // src/renderers.ts
225
+ var MANAGED_BEGIN = "<!-- BEGIN CLICKSMITH (managed \u2014 do not edit by hand) -->";
226
+ var MANAGED_END = "<!-- END CLICKSMITH (managed) -->";
227
+ function wrapManagedBlock(body) {
228
+ return `${MANAGED_BEGIN}
229
+ ${body.trim()}
230
+ ${MANAGED_END}
231
+ `;
232
+ }
233
+ function applyManagedBlock(existing, body) {
234
+ const block = wrapManagedBlock(body);
235
+ if (!existing || !existing.trim()) return block;
236
+ const begin = existing.indexOf(MANAGED_BEGIN);
237
+ const end = existing.indexOf(MANAGED_END);
238
+ if (begin !== -1 && end !== -1 && end > begin) {
239
+ const before = existing.slice(0, begin);
240
+ const after = existing.slice(end + MANAGED_END.length);
241
+ return `${before}${block.trimEnd()}${after}`.replace(/\n{3,}/g, "\n\n");
242
+ }
243
+ return `${existing.trimEnd()}
244
+
245
+ ${block}`;
246
+ }
247
+ function cursorMdc(body) {
248
+ return `---
249
+ description: ClickSmith \u2014 how to handle captured UI change requests
250
+ alwaysApply: true
251
+ ---
252
+
253
+ ${body.trim()}
254
+ `;
255
+ }
256
+ function renderInstructions(target, options = {}) {
257
+ const body = renderInstructionBody(options);
258
+ switch (target) {
259
+ case "claude":
260
+ return { target, path: "CLAUDE.md", shared: true, content: body };
261
+ case "codex":
262
+ return { target, path: "AGENTS.md", shared: true, content: body };
263
+ case "cursor":
264
+ return { target, path: ".cursor/rules/clicksmith.mdc", shared: false, content: cursorMdc(body) };
265
+ case "antigravity":
266
+ return {
267
+ target,
268
+ path: ".antigravity/rules/clicksmith.md",
269
+ shared: false,
270
+ content: `${body.trim()}
271
+ `
272
+ };
273
+ case "generic":
274
+ return {
275
+ target,
276
+ path: ".clicksmith/AGENT_INSTRUCTIONS.md",
277
+ shared: false,
278
+ content: `${body.trim()}
279
+ `
280
+ };
281
+ default: {
282
+ const _exhaustive = target;
283
+ throw new Error(`Unknown instruction target: ${String(_exhaustive)}`);
284
+ }
285
+ }
286
+ }
287
+ function renderAllInstructions(targets, options = {}) {
288
+ return targets.map((t) => renderInstructions(t, options));
289
+ }
290
+
291
+ // src/defaults.ts
292
+ var DEFAULT_AGENTS = [
293
+ {
294
+ id: "claude",
295
+ label: "Claude Code",
296
+ command: "claude",
297
+ args: ["-p", "{prompt}", "--append-system-prompt", "@{instructionFile}"],
298
+ detect: { anyOf: ["claude"] },
299
+ instructions: { target: "claude", file: "CLAUDE.md" },
300
+ mcp: { register: "claude" },
301
+ parse: { planReady: "plan ready|here is the plan|## plan", error: "^error:|fatal:" }
302
+ },
303
+ {
304
+ id: "cursor",
305
+ label: "Cursor Agent",
306
+ command: "cursor-agent",
307
+ args: ["--print", "{prompt}"],
308
+ detect: { anyOf: ["cursor-agent", "cursor"] },
309
+ instructions: { target: "cursor", file: ".cursor/rules/clicksmith.mdc" },
310
+ mcp: { register: "cursor" }
311
+ },
312
+ {
313
+ id: "codex",
314
+ label: "OpenAI Codex",
315
+ command: "codex",
316
+ args: ["exec", "{prompt}"],
317
+ detect: { anyOf: ["codex"] },
318
+ instructions: { target: "codex", file: "AGENTS.md" },
319
+ mcp: { register: "codex" }
320
+ },
321
+ {
322
+ id: "antigravity",
323
+ label: "Antigravity",
324
+ command: "antigravity",
325
+ args: ["run", "--prompt", "{prompt}"],
326
+ detect: { anyOf: ["antigravity"] },
327
+ instructions: { target: "antigravity", file: ".antigravity/rules/clicksmith.md" },
328
+ mcp: { register: "mcp-json" }
329
+ },
330
+ {
331
+ id: "generic",
332
+ label: "Generic agent (customize me)",
333
+ command: "sh",
334
+ args: [
335
+ "-c",
336
+ 'echo "ClickSmith request: {prompt}"; echo "Bundle: {bundlePath}"; echo "Instructions: {instructionFile}"; echo "Mode: {mode}"'
337
+ ],
338
+ instructions: { target: "generic", file: ".clicksmith/AGENT_INSTRUCTIONS.md" },
339
+ mcp: { register: "mcp-json" }
340
+ }
341
+ ];
342
+ var DEFAULT_AGENTS_CONFIG = {
343
+ version: 1,
344
+ defaultAgent: "claude",
345
+ agents: DEFAULT_AGENTS
346
+ };
347
+
348
+ // src/merge.ts
349
+ function mergeAgentsConfig(base, ...overlays) {
350
+ let agents = [...base.agents];
351
+ let defaultAgent = base.defaultAgent;
352
+ for (const overlay of overlays) {
353
+ agents = mergeAgentList(agents, overlay.agents);
354
+ if (overlay.defaultAgent) defaultAgent = overlay.defaultAgent;
355
+ }
356
+ return {
357
+ version: 1,
358
+ ...defaultAgent ? { defaultAgent } : {},
359
+ agents
360
+ };
361
+ }
362
+ function mergeAgentList(base, overlay) {
363
+ const byId = new Map(base.map((a) => [a.id, a]));
364
+ const order = base.map((a) => a.id);
365
+ for (const agent of overlay) {
366
+ if (!byId.has(agent.id)) order.push(agent.id);
367
+ byId.set(agent.id, agent);
368
+ }
369
+ return order.map((id) => byId.get(id));
370
+ }
371
+ function resolveAgent(config, agentId) {
372
+ if (agentId) return config.agents.find((a) => a.id === agentId);
373
+ if (config.defaultAgent) {
374
+ const found = config.agents.find((a) => a.id === config.defaultAgent);
375
+ if (found) return found;
376
+ }
377
+ return config.agents[0];
378
+ }
379
+
380
+ export { AgentConfigSchema, AgentsConfigSchema, DEFAULT_AGENTS, DEFAULT_AGENTS_CONFIG, DEFAULT_MCP_TOOLS, InstructionTargetSchema, MANAGED_BEGIN, MANAGED_END, McpRegistrationSchema, PLACEHOLDER_KEYS, applyManagedBlock, buildAdapters, configToAdapter, defaultBinExists, mergeAgentsConfig, parseAgentsConfig, placeholderValues, referencedPlaceholders, renderAllInstructions, renderInstructionBody, renderInstructions, resolveAgent, resolveCommand, resolvePlaceholders, wrapManagedBlock };
381
+ //# sourceMappingURL=index.js.map
382
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/config-schema.ts","../src/placeholders.ts","../src/bin-exists.ts","../src/adapter.ts","../src/instruction-template.ts","../src/renderers.ts","../src/defaults.ts","../src/merge.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { ExecutionMode, Isolation } from '@clicksmith/core';\n\n/** Every placeholder the launcher knows how to resolve in a command template. */\nexport const PLACEHOLDER_KEYS = [\n 'bundlePath',\n 'prompt',\n 'instructionFile',\n 'mode',\n 'mcpServer',\n 'cwd',\n] as const;\n\nexport type PlaceholderKey = (typeof PLACEHOLDER_KEYS)[number];\n\n/**\n * Everything the launcher needs to turn a config-driven command template into a\n * concrete process invocation. This is also the `ctx` passed to adapter hooks.\n */\nexport interface AgentLaunchContext {\n /** Absolute path to the serialized capture bundle. */\n bundlePath: string;\n /** The user's free-text prompt. */\n prompt: string;\n /** Absolute path to the rendered instruction file for this agent. */\n instructionFile: string;\n /** Execution mode (`plan` | `edit`). */\n mode: ExecutionMode;\n /** A reference the agent can use to reach the ClickSmith MCP server. */\n mcpServer: string;\n /** Working directory for the spawned process (the sandbox). */\n cwd: string;\n /** Isolation strategy in effect for this run. */\n isolation: Isolation;\n /** The agent id being launched. */\n agentId: string;\n /**\n * Resolves whether an executable exists on PATH. Injected by the daemon so\n * adapters never have to spawn processes themselves. Defaults to a PATH scan.\n */\n binExists?: (bin: string) => Promise<boolean>;\n}\n\n/** A concrete, ready-to-spawn process specification. */\nexport interface CommandSpec {\n command: string;\n args: string[];\n env?: Record<string, string>;\n cwd?: string;\n}\n\n/** A normalized event parsed from an agent's stdout/stderr stream. */\nexport type AgentEvent =\n | { type: 'plan-ready'; plan?: string; diff?: string }\n | { type: 'progress'; message: string }\n | { type: 'error'; message: string };\n\n/**\n * The adapter contract. Built-in adapters are produced from `agents.config.json`\n * by {@link configToAdapter}; the launcher only resolves placeholders. Authors\n * may also hand-write adapters that satisfy this interface.\n */\nexport interface AgentAdapter {\n id: string;\n /** Whether the agent's CLI is installed/usable in this context. */\n isAvailable(ctx: AgentLaunchContext): Promise<boolean>;\n /** Build the concrete command to spawn. */\n buildCommand(ctx: AgentLaunchContext): CommandSpec;\n /** Optionally map a raw output chunk into structured events. */\n parseEvents?(chunk: string, ctx: AgentLaunchContext): AgentEvent[];\n}\n","import { z } from 'zod';\n\n/** Which native instruction format a renderer should target. */\nexport const InstructionTargetSchema = z.enum([\n 'claude',\n 'cursor',\n 'codex',\n 'antigravity',\n 'generic',\n]);\nexport type InstructionTarget = z.infer<typeof InstructionTargetSchema>;\n\n/** How (and whether) to register the daemon's MCP server for this agent. */\nexport const McpRegistrationSchema = z.enum(['claude', 'cursor', 'codex', 'mcp-json', 'none']);\nexport type McpRegistration = z.infer<typeof McpRegistrationSchema>;\n\n/**\n * One agent definition. The `command`/`args` are templates containing\n * placeholders like `{prompt}` — they are **data**, never code. Changing a\n * vendor flag is an edit here, not a code change.\n */\nexport const AgentConfigSchema = z.object({\n id: z.string().min(1),\n label: z.string().optional(),\n /** Executable name or path. May contain placeholders. */\n command: z.string().min(1),\n /** Argument templates; each entry may contain placeholders. */\n args: z.array(z.string()).default([]),\n /** Extra environment variables (values may contain placeholders). */\n env: z.record(z.string()).optional(),\n /** Working directory override (placeholders allowed). Defaults to the sandbox. */\n cwd: z.string().optional(),\n /** How to detect availability: any of these executables must be on PATH. */\n detect: z\n .object({\n anyOf: z.array(z.string()).min(1),\n })\n .optional(),\n /** Where to render this agent's instruction file. */\n instructions: z\n .object({\n target: InstructionTargetSchema,\n file: z.string().min(1),\n })\n .optional(),\n /** How to register the daemon MCP server for this agent. */\n mcp: z\n .object({\n register: McpRegistrationSchema,\n })\n .optional(),\n /** Optional regexes that map log lines to structured events. */\n parse: z\n .object({\n planReady: z.string().optional(),\n error: z.string().optional(),\n })\n .optional(),\n});\nexport type AgentConfig = z.infer<typeof AgentConfigSchema>;\n\n/** The top-level `agents.config.json` document. */\nexport const AgentsConfigSchema = z.object({\n version: z.literal(1).default(1),\n defaultAgent: z.string().optional(),\n agents: z.array(AgentConfigSchema).default([]),\n});\nexport type AgentsConfig = z.infer<typeof AgentsConfigSchema>;\n\n/** Parse and validate an `agents.config.json` value without throwing. */\nexport function parseAgentsConfig(value: unknown):\n | { ok: true; config: AgentsConfig }\n | { ok: false; error: z.ZodError } {\n const result = AgentsConfigSchema.safeParse(value);\n return result.success\n ? { ok: true, config: result.data }\n : { ok: false, error: result.error };\n}\n","import { PLACEHOLDER_KEYS, type AgentLaunchContext, type CommandSpec, type PlaceholderKey } from './types.js';\nimport type { AgentConfig } from './config-schema.js';\n\n/** Build the placeholder → value map from a launch context. */\nexport function placeholderValues(ctx: AgentLaunchContext): Record<PlaceholderKey, string> {\n return {\n bundlePath: ctx.bundlePath,\n prompt: ctx.prompt,\n instructionFile: ctx.instructionFile,\n mode: ctx.mode,\n mcpServer: ctx.mcpServer,\n cwd: ctx.cwd,\n };\n}\n\nconst PLACEHOLDER_RE = /\\{([a-zA-Z]+)\\}/g;\n\n/**\n * Replace every known `{placeholder}` in a template string with its value.\n * Unknown placeholders are left untouched (so literal braces survive). This is\n * the *entire* job of the launcher — no vendor-specific knowledge lives here.\n */\nexport function resolvePlaceholders(template: string, ctx: AgentLaunchContext): string {\n const values = placeholderValues(ctx);\n return template.replace(PLACEHOLDER_RE, (match, key: string) => {\n return key in values ? values[key as PlaceholderKey] : match;\n });\n}\n\n/** Resolve an entire {@link AgentConfig} into a concrete {@link CommandSpec}. */\nexport function resolveCommand(config: AgentConfig, ctx: AgentLaunchContext): CommandSpec {\n const env = config.env\n ? Object.fromEntries(\n Object.entries(config.env).map(([k, v]) => [k, resolvePlaceholders(v, ctx)]),\n )\n : undefined;\n return {\n command: resolvePlaceholders(config.command, ctx),\n args: config.args.map((a) => resolvePlaceholders(a, ctx)),\n cwd: config.cwd ? resolvePlaceholders(config.cwd, ctx) : ctx.cwd,\n ...(env ? { env } : {}),\n };\n}\n\n/** List the placeholders actually referenced by a template string. */\nexport function referencedPlaceholders(template: string): PlaceholderKey[] {\n const found = new Set<PlaceholderKey>();\n for (const m of template.matchAll(PLACEHOLDER_RE)) {\n const key = m[1] as string;\n if ((PLACEHOLDER_KEYS as readonly string[]).includes(key)) {\n found.add(key as PlaceholderKey);\n }\n }\n return [...found];\n}\n","import { access, constants } from 'node:fs/promises';\nimport { delimiter, join } from 'node:path';\n\n/**\n * Default executable-on-PATH check. Scans `PATH` for the given binary,\n * honoring `PATHEXT` on Windows. Adapters receive this via the launch context\n * but the daemon may override it (e.g. for tests).\n */\nexport async function defaultBinExists(bin: string): Promise<boolean> {\n // An explicit path: just check it directly.\n if (bin.includes('/') || bin.includes('\\\\')) {\n return canExecute(bin);\n }\n const pathEnv = process.env.PATH ?? '';\n const exts = process.platform === 'win32' ? (process.env.PATHEXT ?? '.EXE;.CMD;.BAT').split(';') : [''];\n for (const dir of pathEnv.split(delimiter)) {\n if (!dir) continue;\n for (const ext of exts) {\n if (await canExecute(join(dir, bin + ext))) return true;\n }\n }\n return false;\n}\n\nasync function canExecute(file: string): Promise<boolean> {\n try {\n await access(file, constants.X_OK);\n return true;\n } catch {\n return false;\n }\n}\n","import { defaultBinExists } from './bin-exists.js';\nimport { resolveCommand } from './placeholders.js';\nimport type { AgentConfig } from './config-schema.js';\nimport type { AgentAdapter, AgentEvent, AgentLaunchContext } from './types.js';\n\n/**\n * Turn a config entry into a live {@link AgentAdapter}. This is the bridge that\n * lets *data* (`agents.config.json`) behave like a built-in adapter:\n *\n * - `isAvailable` checks the configured `detect.anyOf` executables on PATH.\n * - `buildCommand` resolves placeholders into a concrete command.\n * - `parseEvents` applies the optional regexes from `config.parse`.\n */\nexport function configToAdapter(config: AgentConfig): AgentAdapter {\n const planRe = config.parse?.planReady ? new RegExp(config.parse.planReady, 'i') : undefined;\n const errRe = config.parse?.error ? new RegExp(config.parse.error, 'i') : undefined;\n\n return {\n id: config.id,\n\n async isAvailable(ctx: AgentLaunchContext): Promise<boolean> {\n const bins = config.detect?.anyOf ?? [config.command];\n const exists = ctx.binExists ?? defaultBinExists;\n for (const bin of bins) {\n if (await exists(bin)) return true;\n }\n return false;\n },\n\n buildCommand(ctx: AgentLaunchContext) {\n return resolveCommand(config, ctx);\n },\n\n parseEvents(chunk: string): AgentEvent[] {\n if (!planRe && !errRe) return [];\n const events: AgentEvent[] = [];\n for (const line of chunk.split(/\\r?\\n/)) {\n if (!line.trim()) continue;\n if (errRe?.test(line)) events.push({ type: 'error', message: line.trim() });\n else if (planRe?.test(line)) events.push({ type: 'plan-ready' });\n }\n return events;\n },\n };\n}\n\n/** Build adapters for every agent in a config, keyed by id. */\nexport function buildAdapters(configs: readonly AgentConfig[]): Map<string, AgentAdapter> {\n const map = new Map<string, AgentAdapter>();\n for (const config of configs) {\n map.set(config.id, configToAdapter(config));\n }\n return map;\n}\n","import { LOCATOR_PRIORITY } from '@clicksmith/core';\n\nexport interface InstructionTemplateOptions {\n /** MCP tool names exposed by the daemon. */\n mcpTools?: readonly string[];\n /** The daemon port, for the agent's reference. */\n daemonPort?: number;\n /** Project-specific stable attributes the project relies on (e.g. data-testid). */\n stableAttrs?: readonly string[];\n}\n\nexport const DEFAULT_MCP_TOOLS = [\n 'get_session',\n 'list_elements',\n 'get_element_by_id',\n 'get_latest_request',\n] as const;\n\n/**\n * The single, shared instruction body. Every native renderer wraps this exact\n * text — so Claude, Cursor, Codex, Antigravity and generic agents all learn the\n * same three things: the `#N` reference system, locator priority, and the\n * plan/worktree safety contract.\n */\nexport function renderInstructionBody(options: InstructionTemplateOptions = {}): string {\n const tools = options.mcpTools ?? DEFAULT_MCP_TOOLS;\n const attrs = options.stableAttrs ?? [];\n const locatorList = LOCATOR_PRIORITY.map((kind, i) => `${i + 1}. **${kind}**`).join(' → ');\n\n return `# ClickSmith — working with captured UI requests\n\nYou are being asked to change UI elements that a human pointed at in their browser\nwith ClickSmith. Each request arrives as a **capture bundle** (a JSON file whose\npath is provided to you) describing one or more elements and a free-text prompt.\n\n## 1. The \\`#N\\` reference system\n\nElements are numbered \\`#1\\`, \\`#2\\`, … in the order they were captured. The user's\nprompt refers to them by number, e.g. _\"make #1 match #2's style\"_. Always resolve\n\\`#N\\` to \\`elements[]\\` entries with the matching \\`id\\` field. Never guess which\nelement is meant — read the bundle.\n\n## 2. Locator priority\n\nEach element carries a \\`locator\\`. Trust them in this exact order, best first:\n\n${locatorList}\n\n- **source** gives an exact \\`file:line\\` (injected in dev by @clicksmith/unplugin) —\n edit there directly.\n- **attr** is a stable attribute${attrs.length ? ` (this project uses: ${attrs.map((a) => `\\`${a}\\``).join(', ')})` : ''}; grep for it to find the JSX/template.\n- **behavioral** is an ARIA role + accessible name; search for the visible text/label.\n- **dom** is a structural fallback; use \\`el.text\\`, \\`el.attrs\\`, and \\`near\\` context to\n locate the component, and prefer adding a stable attribute while you're there.\n\n## 3. Plan / worktree safety (read carefully)\n\nClickSmith runs you inside an **isolated git worktree** by default. The execution\nmode is in \\`execution.mode\\`:\n\n- **plan** (the default): produce a clear plan and, if helpful, a diff — but **do\n not** assume your edits ship. The human reviews your plan/diff and explicitly\n clicks **Apply**. Your job is to propose, precisely.\n- **edit**: you may modify files in the sandbox. They still do **not** reach the\n user's main working tree until they click Apply.\n\nNever run destructive git commands, never push, and never touch files outside the\nsandbox working directory.\n\n## 4. Reading the request\n\nThe bundle path is passed on the command line / via your harness. ${\n tools.length\n ? `You can also use these MCP tools (server \\`clicksmith\\`): ${tools\n .map((t) => `\\`${t}\\``)\n .join(', ')}. Use \\`get_latest_request\\` to fetch the most recent submission, then \\`get_element_by_id\\` to resolve a specific \\`#N\\`.`\n : ''\n}\n\nEach element includes: \\`locator\\`, \\`el\\` (tag/text/role/label/attrs/icon hints),\n\\`near\\` (surrounding labels & headings), \\`conditions\\` (viewport/theme), and an\noptional \\`screenshot\\` thumbnail. Use \\`near\\` and \\`app.route\\` to disambiguate when\nmultiple elements look similar.\n${options.daemonPort ? `\\nThe ClickSmith daemon is at http://127.0.0.1:${options.daemonPort}.\\n` : ''}`;\n}\n","import { renderInstructionBody, type InstructionTemplateOptions } from './instruction-template.js';\nimport type { InstructionTarget } from './config-schema.js';\n\nexport const MANAGED_BEGIN = '<!-- BEGIN CLICKSMITH (managed — do not edit by hand) -->';\nexport const MANAGED_END = '<!-- END CLICKSMITH (managed) -->';\n\nexport interface RenderedInstruction {\n target: InstructionTarget;\n /** Default relative path for this target's native file. */\n path: string;\n /**\n * `true` when the target file commonly holds other content (e.g. `CLAUDE.md`,\n * `AGENTS.md`). The installer then inserts {@link content} as a managed block\n * rather than overwriting the whole file.\n */\n shared: boolean;\n /** Full file content (dedicated files) or managed-block body (shared files). */\n content: string;\n}\n\n/** Wrap a body in the managed-block markers. */\nexport function wrapManagedBlock(body: string): string {\n return `${MANAGED_BEGIN}\\n${body.trim()}\\n${MANAGED_END}\\n`;\n}\n\n/**\n * Insert or replace the ClickSmith managed block within existing file content,\n * preserving everything the user wrote outside the markers.\n */\nexport function applyManagedBlock(existing: string | undefined, body: string): string {\n const block = wrapManagedBlock(body);\n if (!existing || !existing.trim()) return block;\n const begin = existing.indexOf(MANAGED_BEGIN);\n const end = existing.indexOf(MANAGED_END);\n if (begin !== -1 && end !== -1 && end > begin) {\n const before = existing.slice(0, begin);\n const after = existing.slice(end + MANAGED_END.length);\n return `${before}${block.trimEnd()}${after}`.replace(/\\n{3,}/g, '\\n\\n');\n }\n return `${existing.trimEnd()}\\n\\n${block}`;\n}\n\nfunction cursorMdc(body: string): string {\n // Cursor \"Project Rules\" use MDC frontmatter; alwaysApply keeps it active.\n return `---\ndescription: ClickSmith — how to handle captured UI change requests\nalwaysApply: true\n---\n\n${body.trim()}\n`;\n}\n\n/** Render the shared instruction body into a target's native file. */\nexport function renderInstructions(\n target: InstructionTarget,\n options: InstructionTemplateOptions = {},\n): RenderedInstruction {\n const body = renderInstructionBody(options);\n switch (target) {\n case 'claude':\n return { target, path: 'CLAUDE.md', shared: true, content: body };\n case 'codex':\n return { target, path: 'AGENTS.md', shared: true, content: body };\n case 'cursor':\n return { target, path: '.cursor/rules/clicksmith.mdc', shared: false, content: cursorMdc(body) };\n case 'antigravity':\n return {\n target,\n path: '.antigravity/rules/clicksmith.md',\n shared: false,\n content: `${body.trim()}\\n`,\n };\n case 'generic':\n return {\n target,\n path: '.clicksmith/AGENT_INSTRUCTIONS.md',\n shared: false,\n content: `${body.trim()}\\n`,\n };\n default: {\n const _exhaustive: never = target;\n throw new Error(`Unknown instruction target: ${String(_exhaustive)}`);\n }\n }\n}\n\n/** Render instruction files for several targets at once. */\nexport function renderAllInstructions(\n targets: readonly InstructionTarget[],\n options: InstructionTemplateOptions = {},\n): RenderedInstruction[] {\n return targets.map((t) => renderInstructions(t, options));\n}\n","import type { AgentConfig, AgentsConfig } from './config-schema.js';\n\n/**\n * Built-in agent templates. **These are example defaults — data, not launcher\n * logic.** Changing a vendor's CLI flag means editing `agents.config.json`\n * (or these defaults), never the daemon. The launcher only resolves the\n * `{placeholders}` below; it has no idea what `claude` or `codex` actually do.\n */\nexport const DEFAULT_AGENTS: AgentConfig[] = [\n {\n id: 'claude',\n label: 'Claude Code',\n command: 'claude',\n args: ['-p', '{prompt}', '--append-system-prompt', '@{instructionFile}'],\n detect: { anyOf: ['claude'] },\n instructions: { target: 'claude', file: 'CLAUDE.md' },\n mcp: { register: 'claude' },\n parse: { planReady: 'plan ready|here is the plan|## plan', error: '^error:|fatal:' },\n },\n {\n id: 'cursor',\n label: 'Cursor Agent',\n command: 'cursor-agent',\n args: ['--print', '{prompt}'],\n detect: { anyOf: ['cursor-agent', 'cursor'] },\n instructions: { target: 'cursor', file: '.cursor/rules/clicksmith.mdc' },\n mcp: { register: 'cursor' },\n },\n {\n id: 'codex',\n label: 'OpenAI Codex',\n command: 'codex',\n args: ['exec', '{prompt}'],\n detect: { anyOf: ['codex'] },\n instructions: { target: 'codex', file: 'AGENTS.md' },\n mcp: { register: 'codex' },\n },\n {\n id: 'antigravity',\n label: 'Antigravity',\n command: 'antigravity',\n args: ['run', '--prompt', '{prompt}'],\n detect: { anyOf: ['antigravity'] },\n instructions: { target: 'antigravity', file: '.antigravity/rules/clicksmith.md' },\n mcp: { register: 'mcp-json' },\n },\n {\n id: 'generic',\n label: 'Generic agent (customize me)',\n command: 'sh',\n args: [\n '-c',\n 'echo \"ClickSmith request: {prompt}\"; echo \"Bundle: {bundlePath}\"; echo \"Instructions: {instructionFile}\"; echo \"Mode: {mode}\"',\n ],\n instructions: { target: 'generic', file: '.clicksmith/AGENT_INSTRUCTIONS.md' },\n mcp: { register: 'mcp-json' },\n },\n];\n\n/** The full default `agents.config.json` document. */\nexport const DEFAULT_AGENTS_CONFIG: AgentsConfig = {\n version: 1,\n defaultAgent: 'claude',\n agents: DEFAULT_AGENTS,\n};\n","import type { AgentConfig, AgentsConfig } from './config-schema.js';\n\n/**\n * Layer agent configs: shipped defaults → project config → user config. Agents\n * with the same `id` are replaced by later layers; new ids are appended in\n * order. `defaultAgent` from the last layer that sets it wins.\n */\nexport function mergeAgentsConfig(base: AgentsConfig, ...overlays: AgentsConfig[]): AgentsConfig {\n let agents = [...base.agents];\n let defaultAgent = base.defaultAgent;\n\n for (const overlay of overlays) {\n agents = mergeAgentList(agents, overlay.agents);\n if (overlay.defaultAgent) defaultAgent = overlay.defaultAgent;\n }\n\n return {\n version: 1,\n ...(defaultAgent ? { defaultAgent } : {}),\n agents,\n };\n}\n\nfunction mergeAgentList(base: AgentConfig[], overlay: AgentConfig[]): AgentConfig[] {\n const byId = new Map(base.map((a) => [a.id, a]));\n const order = base.map((a) => a.id);\n for (const agent of overlay) {\n if (!byId.has(agent.id)) order.push(agent.id);\n byId.set(agent.id, agent);\n }\n return order.map((id) => byId.get(id)!);\n}\n\n/** Resolve the effective agent for a run: explicit id → defaultAgent → first. */\nexport function resolveAgent(config: AgentsConfig, agentId?: string): AgentConfig | undefined {\n if (agentId) return config.agents.find((a) => a.id === agentId);\n if (config.defaultAgent) {\n const found = config.agents.find((a) => a.id === config.defaultAgent);\n if (found) return found;\n }\n return config.agents[0];\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@clicksmith/agent-config",
3
+ "version": "0.1.0",
4
+ "description": "Config-driven agent adapters and the shared ClickSmith instruction template, rendered to native files for Claude, Cursor, Codex, Antigravity, and generic agents.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "main": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "dependencies": {
20
+ "zod": "^3.23.8",
21
+ "@clicksmith/core": "0.1.0"
22
+ },
23
+ "scripts": {
24
+ "build": "tsup",
25
+ "dev": "tsup --watch",
26
+ "test": "vitest run",
27
+ "test:watch": "vitest",
28
+ "typecheck": "tsc --noEmit",
29
+ "lint": "eslint src",
30
+ "clean": "rimraf dist .turbo"
31
+ }
32
+ }