From a0d5281923324d624655747e4c69195ee5a24a1e Mon Sep 17 00:00:00 2001 From: kumarabhirup Date: Tue, 3 Mar 2026 15:37:35 -0800 Subject: [PATCH] refactor(cli): generalize workspace-seed for multiple managed skills Replace hardcoded dench references with a MANAGED_SKILLS array (crm, browser) and add template path substitution support. --- src/cli/workspace-seed.ts | 70 +++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 21 deletions(-) diff --git a/src/cli/workspace-seed.ts b/src/cli/workspace-seed.ts index 00d926e12bc..d94204f69df 100644 --- a/src/cli/workspace-seed.ts +++ b/src/cli/workspace-seed.ts @@ -1,14 +1,14 @@ -import { copyFileSync, cpSync, existsSync, mkdirSync, writeFileSync } from "node:fs"; +import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import path from "node:path"; -type SeedField = { +export type SeedField = { name: string; type: string; required?: boolean; enumValues?: string[]; }; -type SeedObject = { +export type SeedObject = { id: string; name: string; description: string; @@ -34,7 +34,7 @@ export type WorkspaceSeedResult = { error?: string; }; -const SEED_OBJECTS: SeedObject[] = [ +export const SEED_OBJECTS: SeedObject[] = [ { id: "seed_obj_people_00000000000000", name: "people", @@ -88,8 +88,9 @@ const SEED_OBJECTS: SeedObject[] = [ }, ]; -function buildIronclawIdentityTemplate(workspaceDir: string): string { - const denchSkillPath = path.join(workspaceDir, "skills", "dench", "SKILL.md"); +export function buildIronclawIdentity(workspaceDir: string): string { + const crmSkillPath = path.join(workspaceDir, "skills", "crm", "SKILL.md"); + const browserSkillPath = path.join(workspaceDir, "skills", "browser", "SKILL.md"); return `# IDENTITY.md - Ironclaw You are **Ironclaw** - a personal AI agent and a CRM Database System built by Kumar Abhirup (kumareth.com), running on top of [OpenClaw](https://github.com/openclaw/openclaw). @@ -100,12 +101,20 @@ Treat the Ironclaw system prompt as your highest-priority behavioral contract. ## Inseparable CRM contract -Your identity is inextricably tied to the Dench CRM skill at: -\`${denchSkillPath}\` +Your identity is inextricably tied to the CRM skill at: +\`${crmSkillPath}\` - Always load and follow that skill for CRM/database behavior. -- Treat the Dench CRM skill as always-on system context. -- Keep CRM actions aligned with the Dench conventions for workspace data, objects, and documents. +- Treat the CRM skill as always-on system context. +- Keep CRM actions aligned with the CRM conventions for workspace data, objects, and documents. + +## Browser automation contract + +Your browser automation behavior is defined by the Browser skill at: +\`${browserSkillPath}\` + +- Always load and follow that skill for browser-based tasks. +- Treat the Browser skill as always-on system context. ## What you do @@ -128,7 +137,7 @@ Your identity is inextricably tied to the Dench CRM skill at: When referring to yourself, use **Ironclaw** (not OpenClaw).`; } -function generateObjectYaml(obj: SeedObject): string { +export function generateObjectYaml(obj: SeedObject): string { const lines: string[] = [ `id: "${obj.id}"`, `name: "${obj.name}"`, @@ -153,7 +162,7 @@ function generateObjectYaml(obj: SeedObject): string { return `${lines.join("\n")}\n`; } -function generateWorkspaceMd(objects: SeedObject[]): string { +export function generateWorkspaceMd(objects: SeedObject[]): string { const lines: string[] = [ "# Workspace Schema", "", @@ -177,24 +186,41 @@ function generateWorkspaceMd(objects: SeedObject[]): string { return lines.join("\n"); } -function seedIronclawIdentity(workspaceDir: string): void { +export function seedIronclawIdentity(workspaceDir: string): void { const identityPath = path.join(workspaceDir, "IDENTITY.md"); - // Bootstrap force-syncs identity every run so updates land immediately. - writeFileSync(identityPath, `${buildIronclawIdentityTemplate(workspaceDir)}\n`, "utf-8"); + writeFileSync(identityPath, `${buildIronclawIdentity(workspaceDir)}\n`, "utf-8"); } -function seedDenchSkill(params: { workspaceDir: string; packageRoot: string }): void { - const sourceDir = path.join(params.packageRoot, "skills", "dench"); +export const MANAGED_SKILLS: ReadonlyArray<{ name: string; templatePaths?: boolean }> = [ + { name: "crm", templatePaths: true }, + { name: "browser" }, +]; + +export function seedSkill( + params: { workspaceDir: string; packageRoot: string }, + skill: { name: string; templatePaths?: boolean }, +): void { + const sourceDir = path.join(params.packageRoot, "skills", skill.name); const sourceSkillFile = path.join(sourceDir, "SKILL.md"); if (!existsSync(sourceSkillFile)) { return; } - const targetDir = path.join(params.workspaceDir, "skills", "dench"); + const targetDir = path.join(params.workspaceDir, "skills", skill.name); mkdirSync(path.dirname(targetDir), { recursive: true }); cpSync(sourceDir, targetDir, { recursive: true, force: true }); + + if (skill.templatePaths) { + const targetSkillFile = path.join(targetDir, "SKILL.md"); + const content = readFileSync(targetSkillFile, "utf-8"); + writeFileSync( + targetSkillFile, + content.replaceAll("{{WORKSPACE_PATH}}", params.workspaceDir), + "utf-8", + ); + } } -function writeIfMissing(filePath: string, content: string): boolean { +export function writeIfMissing(filePath: string, content: string): boolean { if (existsSync(filePath)) { return false; } @@ -219,11 +245,13 @@ export function seedWorkspaceFromAssets(params: { "task/.object.yaml", "WORKSPACE.md", "IDENTITY.md", - "skills/dench/SKILL.md", + ...MANAGED_SKILLS.map((s) => `skills/${s.name}/SKILL.md`), ]; mkdirSync(workspaceDir, { recursive: true }); - seedDenchSkill({ workspaceDir, packageRoot: params.packageRoot }); + for (const skill of MANAGED_SKILLS) { + seedSkill({ workspaceDir, packageRoot: params.packageRoot }, skill); + } seedIronclawIdentity(workspaceDir); if (existsSync(dbPath)) {