@web42/cli 0.2.6 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/commands/search.js +20 -15
  2. package/dist/commands/send.js +75 -41
  3. package/dist/commands/serve.d.ts +1 -1
  4. package/dist/commands/serve.js +116 -213
  5. package/dist/index.js +1 -19
  6. package/dist/version.d.ts +1 -1
  7. package/dist/version.js +1 -1
  8. package/package.json +1 -1
  9. package/dist/commands/config.d.ts +0 -2
  10. package/dist/commands/config.js +0 -27
  11. package/dist/commands/init.d.ts +0 -2
  12. package/dist/commands/init.js +0 -451
  13. package/dist/commands/install.d.ts +0 -3
  14. package/dist/commands/install.js +0 -231
  15. package/dist/commands/list.d.ts +0 -3
  16. package/dist/commands/list.js +0 -22
  17. package/dist/commands/pack.d.ts +0 -2
  18. package/dist/commands/pack.js +0 -210
  19. package/dist/commands/pull.d.ts +0 -2
  20. package/dist/commands/pull.js +0 -202
  21. package/dist/commands/push.d.ts +0 -2
  22. package/dist/commands/push.js +0 -374
  23. package/dist/commands/remix.d.ts +0 -2
  24. package/dist/commands/remix.js +0 -49
  25. package/dist/commands/sync.d.ts +0 -2
  26. package/dist/commands/sync.js +0 -98
  27. package/dist/commands/uninstall.d.ts +0 -3
  28. package/dist/commands/uninstall.js +0 -54
  29. package/dist/commands/update.d.ts +0 -3
  30. package/dist/commands/update.js +0 -59
  31. package/dist/platforms/base.d.ts +0 -82
  32. package/dist/platforms/base.js +0 -1
  33. package/dist/platforms/claude/__tests__/adapter.test.d.ts +0 -1
  34. package/dist/platforms/claude/__tests__/adapter.test.js +0 -257
  35. package/dist/platforms/claude/__tests__/security.test.d.ts +0 -1
  36. package/dist/platforms/claude/__tests__/security.test.js +0 -166
  37. package/dist/platforms/claude/adapter.d.ts +0 -34
  38. package/dist/platforms/claude/adapter.js +0 -525
  39. package/dist/platforms/claude/security.d.ts +0 -15
  40. package/dist/platforms/claude/security.js +0 -67
  41. package/dist/platforms/claude/templates.d.ts +0 -5
  42. package/dist/platforms/claude/templates.js +0 -22
  43. package/dist/platforms/openclaw/adapter.d.ts +0 -12
  44. package/dist/platforms/openclaw/adapter.js +0 -476
  45. package/dist/platforms/openclaw/templates.d.ts +0 -7
  46. package/dist/platforms/openclaw/templates.js +0 -369
  47. package/dist/platforms/registry.d.ts +0 -6
  48. package/dist/platforms/registry.js +0 -32
  49. package/dist/types/sync.d.ts +0 -74
  50. package/dist/types/sync.js +0 -7
  51. package/dist/utils/bundled-skills.d.ts +0 -6
  52. package/dist/utils/bundled-skills.js +0 -29
  53. package/dist/utils/secrets.d.ts +0 -32
  54. package/dist/utils/secrets.js +0 -118
  55. package/dist/utils/skill.d.ts +0 -6
  56. package/dist/utils/skill.js +0 -42
  57. package/dist/utils/sync.d.ts +0 -14
  58. package/dist/utils/sync.js +0 -242
@@ -1,525 +0,0 @@
1
- import { createHash } from "crypto";
2
- import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync, } from "fs";
3
- import { dirname, join, resolve } from "path";
4
- import { homedir } from "os";
5
- import { glob } from "glob";
6
- import { stripForbiddenFrontmatter } from "./security.js";
7
- // ---------------------------------------------------------------------------
8
- // Constants
9
- // ---------------------------------------------------------------------------
10
- const CLAUDE_HOME = join(homedir(), ".claude");
11
- const HARDCODED_EXCLUDES = [
12
- ".claude/settings*",
13
- ".claude/settings.json",
14
- ".claude/settings.local.json",
15
- ".claude/projects/**",
16
- ".claude/plugins/**",
17
- "memory/**",
18
- "MEMORY.md",
19
- ".git/**",
20
- "node_modules/**",
21
- ".DS_Store",
22
- "*.log",
23
- ".web42/**",
24
- ".web42ignore",
25
- "manifest.json",
26
- ".env",
27
- ".env.*",
28
- ];
29
- const TEMPLATE_VARS = [
30
- [/\/Users\/[^/]+\/.claude/g, "{{CLAUDE_HOME}}"],
31
- [/\/home\/[^/]+\/.claude/g, "{{CLAUDE_HOME}}"],
32
- [/C:\\Users\\[^\\]+\\.claude/g, "{{CLAUDE_HOME}}"],
33
- [/~\/.claude/g, "{{CLAUDE_HOME}}"],
34
- ];
35
- // ---------------------------------------------------------------------------
36
- // Helpers
37
- // ---------------------------------------------------------------------------
38
- function sanitizeContent(content) {
39
- let result = content;
40
- for (const [pattern, replacement] of TEMPLATE_VARS) {
41
- result = result.replace(pattern, replacement);
42
- }
43
- return result;
44
- }
45
- function hashContent(content) {
46
- return createHash("sha256").update(content).digest("hex");
47
- }
48
- function resolveTemplateVars(content) {
49
- return content.replace(/\{\{CLAUDE_HOME\}\}/g, CLAUDE_HOME);
50
- }
51
- function replaceConfigPlaceholders(content, answers) {
52
- let result = content;
53
- for (const [key, value] of Object.entries(answers)) {
54
- result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
55
- }
56
- return result;
57
- }
58
- /**
59
- * Parse YAML-like frontmatter from an agent .md file.
60
- * Handles simple key: value pairs and list values (skills, tools).
61
- */
62
- function parseAgentFrontmatter(content) {
63
- const lines = content.split("\n");
64
- if (lines[0]?.trim() !== "---")
65
- return {};
66
- const result = {};
67
- let currentKey = null;
68
- for (let i = 1; i < lines.length; i++) {
69
- const line = lines[i];
70
- if (line.trim() === "---")
71
- break;
72
- // Top-level key
73
- const keyMatch = line.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*(.*)$/);
74
- if (keyMatch) {
75
- const key = keyMatch[1];
76
- const value = keyMatch[2].trim();
77
- if (value) {
78
- result[key] = value.replace(/^["']|["']$/g, "");
79
- }
80
- else {
81
- result[key] = [];
82
- }
83
- currentKey = key;
84
- continue;
85
- }
86
- // List item (indented - item)
87
- const listMatch = line.match(/^\s+-\s+(.+)$/) || line.match(/^\s+- (.+)$/);
88
- if (listMatch && currentKey) {
89
- const arr = result[currentKey];
90
- if (Array.isArray(arr)) {
91
- arr.push(listMatch[1].trim().replace(/^["']|["']$/g, ""));
92
- }
93
- continue;
94
- }
95
- // Indented continuation — could be multiline value
96
- if (line.match(/^\s+/) && currentKey) {
97
- continue;
98
- }
99
- }
100
- return result;
101
- }
102
- function readInstalledManifest(root) {
103
- const base = root ?? CLAUDE_HOME;
104
- const p = join(base, ".web42", "installed.json");
105
- if (!existsSync(p))
106
- return {};
107
- try {
108
- return JSON.parse(readFileSync(p, "utf-8"));
109
- }
110
- catch {
111
- return {};
112
- }
113
- }
114
- function writeInstalledManifest(root, manifest) {
115
- const dir = join(root, ".web42");
116
- mkdirSync(dir, { recursive: true });
117
- writeFileSync(join(dir, "installed.json"), JSON.stringify(manifest, null, 2) + "\n");
118
- }
119
- /**
120
- * Check if a file was installed by web42 (tracked in installed.json).
121
- */
122
- function isFileTracked(relativePath, installed) {
123
- for (const [agentName, entry] of Object.entries(installed)) {
124
- if (entry.files.includes(relativePath)) {
125
- return agentName;
126
- }
127
- }
128
- return null;
129
- }
130
- // ---------------------------------------------------------------------------
131
- // Adapter
132
- // ---------------------------------------------------------------------------
133
- export class ClaudeAdapter {
134
- name = "claude";
135
- home = CLAUDE_HOME;
136
- /**
137
- * Discover agent .md files in known locations.
138
- */
139
- discoverAgents(cwd) {
140
- const candidates = [];
141
- const seen = new Set();
142
- const searchDirs = [];
143
- // 1. cwd/agents/ (standalone plugin dir or ~/.claude/)
144
- const cwdAgentsDir = join(cwd, "agents");
145
- if (existsSync(cwdAgentsDir)) {
146
- searchDirs.push(cwdAgentsDir);
147
- }
148
- // 2. cwd/.claude/agents/ (project-local agents)
149
- const projectAgentsDir = join(cwd, ".claude", "agents");
150
- if (existsSync(projectAgentsDir)) {
151
- searchDirs.push(projectAgentsDir);
152
- }
153
- // 3. ~/.claude/agents/ (global, if cwd isn't already ~/.claude/)
154
- // Use `this.home` so tests can override the home dir for isolation.
155
- const globalAgentsDir = join(this.home, "agents");
156
- if (resolve(cwd) !== resolve(this.home) && existsSync(globalAgentsDir)) {
157
- searchDirs.push(globalAgentsDir);
158
- }
159
- for (const dir of searchDirs) {
160
- try {
161
- const entries = readdirSync(dir, { withFileTypes: true });
162
- for (const entry of entries) {
163
- if (!entry.isFile() || !entry.name.endsWith(".md"))
164
- continue;
165
- const agentPath = join(dir, entry.name);
166
- const content = readFileSync(agentPath, "utf-8");
167
- const frontmatter = parseAgentFrontmatter(content);
168
- const name = (typeof frontmatter.name === "string" ? frontmatter.name : null)
169
- ?? entry.name.replace(/\.md$/, "");
170
- if (seen.has(name))
171
- continue;
172
- seen.add(name);
173
- const skills = Array.isArray(frontmatter.skills)
174
- ? frontmatter.skills
175
- : [];
176
- candidates.push({
177
- name,
178
- description: typeof frontmatter.description === "string"
179
- ? frontmatter.description
180
- : undefined,
181
- model: typeof frontmatter.model === "string"
182
- ? frontmatter.model
183
- : undefined,
184
- skills,
185
- path: agentPath,
186
- });
187
- }
188
- }
189
- catch {
190
- // Directory not readable, skip
191
- }
192
- }
193
- return candidates;
194
- }
195
- /**
196
- * Resolve skill directories from known locations.
197
- */
198
- resolveSkills(skillNames, cwd) {
199
- return skillNames.map((name) => {
200
- const searchPaths = [
201
- join(cwd, "skills", name, "SKILL.md"),
202
- join(CLAUDE_HOME, "skills", name, "SKILL.md"),
203
- join(cwd, ".claude", "skills", name, "SKILL.md"),
204
- ];
205
- for (const p of searchPaths) {
206
- if (existsSync(p)) {
207
- return { name, sourcePath: dirname(p), found: true };
208
- }
209
- }
210
- return { name, sourcePath: "", found: false };
211
- });
212
- }
213
- extractInitConfig(cwd) {
214
- const agents = this.discoverAgents(cwd);
215
- if (agents.length === 0)
216
- return null;
217
- const first = agents[0];
218
- return {
219
- name: first.name,
220
- model: first.model || undefined,
221
- };
222
- }
223
- async pack(options) {
224
- const { cwd, agentName } = options;
225
- if (!agentName) {
226
- throw new Error("Claude adapter requires agentName in PackOptions. " +
227
- "Use --agent <name> or let the pack command auto-discover agents.");
228
- }
229
- // Read the agent .md file (check cwd/agents/ and cwd/.claude/agents/)
230
- let agentMdPath = join(cwd, "agents", `${agentName}.md`);
231
- if (!existsSync(agentMdPath)) {
232
- agentMdPath = join(cwd, ".claude", "agents", `${agentName}.md`);
233
- }
234
- if (!existsSync(agentMdPath)) {
235
- throw new Error(`Agent file not found: agents/${agentName}.md or .claude/agents/${agentName}.md`);
236
- }
237
- const agentContent = readFileSync(agentMdPath, "utf-8");
238
- const frontmatter = parseAgentFrontmatter(agentContent);
239
- const skillNames = Array.isArray(frontmatter.skills) ? frontmatter.skills : [];
240
- // Resolve skills
241
- const resolvedSkills = this.resolveSkills(skillNames, cwd);
242
- const missingSkills = resolvedSkills.filter((s) => !s.found);
243
- if (missingSkills.length > 0) {
244
- const names = missingSkills.map((s) => s.name).join(", ");
245
- console.warn(`Warning: Skills not found: ${names}`);
246
- }
247
- // Collect ignore patterns
248
- const ignorePatterns = [...HARDCODED_EXCLUDES];
249
- const web42Dir = join(cwd, ".web42");
250
- const agentWeb42Dir = join(web42Dir, agentName);
251
- const ignorePath = join(agentWeb42Dir, ".web42ignore");
252
- if (existsSync(ignorePath)) {
253
- const ignoreContent = readFileSync(ignorePath, "utf-8");
254
- ignoreContent
255
- .split("\n")
256
- .map((line) => line.trim())
257
- .filter((line) => line && !line.startsWith("#"))
258
- .forEach((pattern) => ignorePatterns.push(pattern));
259
- }
260
- const files = [];
261
- // 1. Add the agent .md itself
262
- const sanitizedAgent = sanitizeContent(agentContent);
263
- files.push({
264
- path: `agents/${agentName}.md`,
265
- content: sanitizedAgent,
266
- hash: hashContent(sanitizedAgent),
267
- });
268
- // 2. Add each resolved skill's full directory
269
- for (const skill of resolvedSkills) {
270
- if (!skill.found)
271
- continue;
272
- const skillDir = skill.sourcePath;
273
- const skillFiles = await glob("**/*", {
274
- cwd: skillDir,
275
- nodir: true,
276
- ignore: ignorePatterns,
277
- dot: true,
278
- });
279
- for (const filePath of skillFiles) {
280
- const fullPath = join(skillDir, filePath);
281
- const stat = statSync(fullPath);
282
- if (stat.size > 1024 * 1024)
283
- continue;
284
- try {
285
- let content = readFileSync(fullPath, "utf-8");
286
- content = sanitizeContent(content);
287
- files.push({
288
- path: `skills/${skill.name}/${filePath}`,
289
- content,
290
- hash: hashContent(content),
291
- });
292
- }
293
- catch {
294
- // Skip binary files
295
- }
296
- }
297
- }
298
- // 3. Add commands/*.md if present (check cwd/commands/ and cwd/.claude/commands/)
299
- const commandsDirs = [
300
- join(cwd, "commands"),
301
- join(cwd, ".claude", "commands"),
302
- ];
303
- const seenCommands = new Set();
304
- for (const commandsDir of commandsDirs) {
305
- if (!existsSync(commandsDir))
306
- continue;
307
- try {
308
- const commandFiles = readdirSync(commandsDir, { withFileTypes: true });
309
- for (const entry of commandFiles) {
310
- if (!entry.isFile() || !entry.name.endsWith(".md"))
311
- continue;
312
- if (seenCommands.has(entry.name))
313
- continue;
314
- seenCommands.add(entry.name);
315
- const fullPath = join(commandsDir, entry.name);
316
- const stat = statSync(fullPath);
317
- if (stat.size > 1024 * 1024)
318
- continue;
319
- try {
320
- let content = readFileSync(fullPath, "utf-8");
321
- content = sanitizeContent(content);
322
- files.push({
323
- path: `commands/${entry.name}`,
324
- content,
325
- hash: hashContent(content),
326
- });
327
- }
328
- catch {
329
- // Skip
330
- }
331
- }
332
- }
333
- catch {
334
- // Commands dir not readable
335
- }
336
- }
337
- // 4. Add scripts/** if present (check BOTH cwd/scripts/ and cwd/.claude/scripts/)
338
- const scriptsDir = join(cwd, "scripts");
339
- const claudeScriptsDir = join(cwd, ".claude", "scripts");
340
- const allScriptFiles = new Map(); // filePath -> content
341
- if (existsSync(scriptsDir)) {
342
- const scriptFiles = await glob("**/*", {
343
- cwd: scriptsDir,
344
- nodir: true,
345
- ignore: ignorePatterns,
346
- dot: true,
347
- });
348
- for (const filePath of scriptFiles) {
349
- try {
350
- const fullPath = join(scriptsDir, filePath);
351
- const stat = statSync(fullPath);
352
- if (stat.size > 1024 * 1024)
353
- continue;
354
- let content = readFileSync(fullPath, "utf-8");
355
- content = sanitizeContent(content);
356
- if (!allScriptFiles.has(filePath)) {
357
- allScriptFiles.set(filePath, content);
358
- }
359
- }
360
- catch {
361
- // Skip
362
- }
363
- }
364
- }
365
- if (existsSync(claudeScriptsDir)) {
366
- const claudeScriptFiles = await glob("**/*", {
367
- cwd: claudeScriptsDir,
368
- nodir: true,
369
- ignore: ignorePatterns,
370
- dot: true,
371
- });
372
- for (const filePath of claudeScriptFiles) {
373
- try {
374
- const fullPath = join(claudeScriptsDir, filePath);
375
- const stat = statSync(fullPath);
376
- if (stat.size > 1024 * 1024)
377
- continue;
378
- let content = readFileSync(fullPath, "utf-8");
379
- content = sanitizeContent(content);
380
- if (!allScriptFiles.has(filePath)) {
381
- allScriptFiles.set(filePath, content);
382
- }
383
- }
384
- catch {
385
- // Skip
386
- }
387
- }
388
- }
389
- for (const [filePath, content] of allScriptFiles.entries()) {
390
- files.push({
391
- path: `scripts/${filePath}`,
392
- content,
393
- hash: hashContent(content),
394
- });
395
- }
396
- // Detect config variables from {{VAR}} patterns in agent .md
397
- const configVariables = [];
398
- const varPattern = /\{\{([A-Z_][A-Z0-9_]*)\}\}/g;
399
- const reservedVars = new Set(["CLAUDE_HOME", "WORKSPACE"]);
400
- let match;
401
- while ((match = varPattern.exec(agentContent)) !== null) {
402
- if (!reservedVars.has(match[1])) {
403
- configVariables.push({
404
- key: match[1],
405
- label: match[1].replace(/_/g, " ").toLowerCase(),
406
- required: true,
407
- });
408
- }
409
- }
410
- return {
411
- files,
412
- configTemplate: null,
413
- configVariables,
414
- ignorePatterns,
415
- };
416
- }
417
- async install(options) {
418
- const { agentSlug, files, configAnswers, workspacePath } = options;
419
- // workspacePath determines where files land:
420
- // local install → cwd/.claude/ (default)
421
- // global install → ~/.claude/ (with -g flag)
422
- const targetRoot = workspacePath;
423
- const installed = readInstalledManifest(targetRoot);
424
- const trackedFiles = [];
425
- let filesWritten = 0;
426
- for (const file of files) {
427
- // Validate no path traversal
428
- const normalized = resolve(targetRoot, file.path);
429
- if (!normalized.startsWith(resolve(targetRoot))) {
430
- throw new Error(`Path traversal detected: ${file.path}`);
431
- }
432
- // Check for conflicts with non-web42 files
433
- const targetPath = join(targetRoot, file.path);
434
- if (existsSync(targetPath)) {
435
- const tracker = isFileTracked(file.path, installed);
436
- if (!tracker || tracker !== agentSlug) {
437
- console.warn(` Warning: Overwriting existing file: ${file.path}`);
438
- }
439
- }
440
- // Security filter for agent .md files
441
- let content = file.content ?? "";
442
- if (file.path.startsWith("agents/") && file.path.endsWith(".md")) {
443
- const { cleaned, stripped } = stripForbiddenFrontmatter(content);
444
- content = cleaned;
445
- if (stripped.length > 0) {
446
- console.warn(` Security: Stripped ${stripped.join(", ")} from ${file.path}`);
447
- }
448
- }
449
- // Resolve template vars and config placeholders
450
- content = resolveTemplateVars(content);
451
- content = replaceConfigPlaceholders(content, configAnswers);
452
- // Write the file
453
- mkdirSync(dirname(targetPath), { recursive: true });
454
- writeFileSync(targetPath, content, "utf-8");
455
- trackedFiles.push(file.path);
456
- filesWritten++;
457
- }
458
- // Update installed.json
459
- writeInstalledManifest(targetRoot, {
460
- ...readInstalledManifest(targetRoot),
461
- [agentSlug]: {
462
- source: `@${options.username}/${agentSlug}`,
463
- version: options.version ?? "1.0.0",
464
- installed_at: new Date().toISOString(),
465
- files: trackedFiles,
466
- },
467
- });
468
- return { filesWritten, agentDir: targetRoot };
469
- }
470
- async uninstall(options) {
471
- const { agentName, workspacePath } = options;
472
- const targetRoot = workspacePath ?? CLAUDE_HOME;
473
- const installed = readInstalledManifest(targetRoot);
474
- const entry = installed[agentName];
475
- if (!entry) {
476
- return { removed: false, paths: [] };
477
- }
478
- const removedPaths = [];
479
- // Remove each tracked file
480
- for (const filePath of entry.files) {
481
- const fullPath = join(targetRoot, filePath);
482
- if (existsSync(fullPath)) {
483
- rmSync(fullPath);
484
- removedPaths.push(fullPath);
485
- // Clean up empty parent directories (e.g., empty skill dirs)
486
- let parentDir = dirname(fullPath);
487
- while (parentDir !== targetRoot) {
488
- try {
489
- const entries = readdirSync(parentDir);
490
- if (entries.length === 0) {
491
- rmSync(parentDir, { recursive: true });
492
- parentDir = dirname(parentDir);
493
- }
494
- else {
495
- break;
496
- }
497
- }
498
- catch {
499
- break;
500
- }
501
- }
502
- }
503
- }
504
- // Remove from installed.json
505
- delete installed[agentName];
506
- writeInstalledManifest(targetRoot, installed);
507
- return { removed: removedPaths.length > 0, paths: removedPaths };
508
- }
509
- async listInstalled(workspacePath) {
510
- const targetRoot = workspacePath ?? CLAUDE_HOME;
511
- const installed = readInstalledManifest(targetRoot);
512
- return Object.entries(installed).map(([name, entry]) => ({
513
- name,
514
- source: entry.source,
515
- workspace: targetRoot,
516
- }));
517
- }
518
- resolveInstallPath(_localName, global) {
519
- if (global)
520
- return CLAUDE_HOME;
521
- return join(process.cwd(), ".claude");
522
- }
523
- }
524
- export const claudeAdapter = new ClaudeAdapter();
525
- export { HARDCODED_EXCLUDES as CLAUDE_HARDCODED_EXCLUDES };
@@ -1,15 +0,0 @@
1
- /**
2
- * Security filtering for Claude Code agent .md files.
3
- *
4
- * Plugin subagents do not support hooks, mcpServers, or permissionMode.
5
- * These keys are stripped from frontmatter on install to prevent
6
- * marketplace-distributed agents from modifying the buyer's security posture.
7
- */
8
- /**
9
- * Strip forbidden frontmatter keys from a Claude agent .md file.
10
- * Returns the cleaned content and a list of keys that were removed.
11
- */
12
- export declare function stripForbiddenFrontmatter(content: string): {
13
- cleaned: string;
14
- stripped: string[];
15
- };
@@ -1,67 +0,0 @@
1
- /**
2
- * Security filtering for Claude Code agent .md files.
3
- *
4
- * Plugin subagents do not support hooks, mcpServers, or permissionMode.
5
- * These keys are stripped from frontmatter on install to prevent
6
- * marketplace-distributed agents from modifying the buyer's security posture.
7
- */
8
- const FORBIDDEN_FRONTMATTER_KEYS = new Set([
9
- "hooks",
10
- "mcpServers",
11
- "permissionMode",
12
- ]);
13
- /**
14
- * Strip forbidden frontmatter keys from a Claude agent .md file.
15
- * Returns the cleaned content and a list of keys that were removed.
16
- */
17
- export function stripForbiddenFrontmatter(content) {
18
- const lines = content.split("\n");
19
- // No frontmatter — return as-is
20
- if (lines[0]?.trim() !== "---") {
21
- return { cleaned: content, stripped: [] };
22
- }
23
- // Find the closing ---
24
- let closingIndex = -1;
25
- for (let i = 1; i < lines.length; i++) {
26
- if (lines[i].trim() === "---") {
27
- closingIndex = i;
28
- break;
29
- }
30
- }
31
- // Malformed frontmatter (no closing ---) — return as-is
32
- if (closingIndex === -1) {
33
- return { cleaned: content, stripped: [] };
34
- }
35
- const stripped = [];
36
- const filteredFrontmatterLines = [];
37
- let skipUntilNextKey = false;
38
- for (let i = 1; i < closingIndex; i++) {
39
- const line = lines[i];
40
- // Check if this is a top-level key (not indented, has colon)
41
- const keyMatch = line.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*:/);
42
- if (keyMatch) {
43
- const key = keyMatch[1];
44
- if (FORBIDDEN_FRONTMATTER_KEYS.has(key)) {
45
- stripped.push(key);
46
- skipUntilNextKey = true;
47
- continue;
48
- }
49
- skipUntilNextKey = false;
50
- }
51
- // If we're skipping a forbidden key's multiline value (indented lines)
52
- if (skipUntilNextKey) {
53
- // Indented lines or continuation lines belong to the forbidden key
54
- if (line.match(/^\s+/) || line.trim() === "") {
55
- continue;
56
- }
57
- // Non-indented non-empty line = new key
58
- skipUntilNextKey = false;
59
- }
60
- filteredFrontmatterLines.push(line);
61
- }
62
- // Reconstruct the file
63
- const before = lines[0]; // opening ---
64
- const after = lines.slice(closingIndex); // closing --- + body
65
- const result = [before, ...filteredFrontmatterLines, ...after].join("\n");
66
- return { cleaned: result, stripped };
67
- }
@@ -1,5 +0,0 @@
1
- /**
2
- * Scaffold templates for Claude Code agents.
3
- * Used by `web42 init` when platform is "claude".
4
- */
5
- export declare function agentMdTemplate(name: string, description: string, skills: string[]): string;
@@ -1,22 +0,0 @@
1
- /**
2
- * Scaffold templates for Claude Code agents.
3
- * Used by `web42 init` when platform is "claude".
4
- */
5
- export function agentMdTemplate(name, description, skills) {
6
- const skillsList = skills.length > 0
7
- ? skills.map((s) => ` - ${s}`).join("\n")
8
- : " # No skills detected";
9
- return `---
10
- name: ${name}
11
- description: ${description}
12
- tools: Read, Grep, Glob, Bash
13
- model: sonnet
14
- skills:
15
- ${skillsList}
16
- ---
17
-
18
- You are ${name}. ${description}
19
-
20
- When invoked, analyze the task using your preloaded skills.
21
- `;
22
- }
@@ -1,12 +0,0 @@
1
- import type { InitConfig, InstalledAgent, InstallOptions, InstallResult, PackOptions, PackResult, PlatformAdapter, UninstallOptions, UninstallResult } from "../base.js";
2
- export declare const HARDCODED_EXCLUDES: string[];
3
- export declare class OpenClawAdapter implements PlatformAdapter {
4
- name: string;
5
- home: string;
6
- extractInitConfig(cwd: string): InitConfig | null;
7
- listInstalled(): Promise<InstalledAgent[]>;
8
- uninstall(options: UninstallOptions): Promise<UninstallResult>;
9
- pack(options: PackOptions): Promise<PackResult>;
10
- install(options: InstallOptions): Promise<InstallResult>;
11
- }
12
- export declare const openclawAdapter: OpenClawAdapter;