Merge remote-tracking branch 'origin/main'

This commit is contained in:
Peter Steinberger
2025-12-20 17:33:00 +01:00
13 changed files with 343 additions and 33 deletions

View File

@@ -5,6 +5,9 @@ enum AgentWorkspace {
private static let logger = Logger(subsystem: "com.steipete.clawdis", category: "workspace") private static let logger = Logger(subsystem: "com.steipete.clawdis", category: "workspace")
static let agentsFilename = "AGENTS.md" static let agentsFilename = "AGENTS.md"
static let soulFilename = "SOUL.md" static let soulFilename = "SOUL.md"
static let identityFilename = "IDENTITY.md"
static let userFilename = "USER.md"
static let bootstrapFilename = "BOOTSTRAP.md"
private static let templateDirname = "templates" private static let templateDirname = "templates"
static let identityStartMarker = "<!-- clawdis:identity:start -->" static let identityStartMarker = "<!-- clawdis:identity:start -->"
static let identityEndMarker = "<!-- clawdis:identity:end -->" static let identityEndMarker = "<!-- clawdis:identity:end -->"
@@ -42,6 +45,21 @@ enum AgentWorkspace {
try self.defaultSoulTemplate().write(to: soulURL, atomically: true, encoding: .utf8) try self.defaultSoulTemplate().write(to: soulURL, atomically: true, encoding: .utf8)
self.logger.info("Created SOUL.md at \(soulURL.path, privacy: .public)") self.logger.info("Created SOUL.md at \(soulURL.path, privacy: .public)")
} }
let identityURL = workspaceURL.appendingPathComponent(self.identityFilename)
if !FileManager.default.fileExists(atPath: identityURL.path) {
try self.defaultIdentityTemplate().write(to: identityURL, atomically: true, encoding: .utf8)
self.logger.info("Created IDENTITY.md at \(identityURL.path, privacy: .public)")
}
let userURL = workspaceURL.appendingPathComponent(self.userFilename)
if !FileManager.default.fileExists(atPath: userURL.path) {
try self.defaultUserTemplate().write(to: userURL, atomically: true, encoding: .utf8)
self.logger.info("Created USER.md at \(userURL.path, privacy: .public)")
}
let bootstrapURL = workspaceURL.appendingPathComponent(self.bootstrapFilename)
if !FileManager.default.fileExists(atPath: bootstrapURL.path) {
try self.defaultBootstrapTemplate().write(to: bootstrapURL, atomically: true, encoding: .utf8)
self.logger.info("Created BOOTSTRAP.md at \(bootstrapURL.path, privacy: .public)")
}
return agentsURL return agentsURL
} }
@@ -76,6 +94,11 @@ enum AgentWorkspace {
This folder is the assistant's working directory. This folder is the assistant's working directory.
## First run (one-time)
- If BOOTSTRAP.md exists, follow its ritual and delete it once complete.
- Your agent identity lives in IDENTITY.md.
- Your profile lives in USER.md.
## Backup tip (recommended) ## Backup tip (recommended)
If you treat this workspace as the agent's "memory", make it a git repo (ideally private) so identity If you treat this workspace as the agent's "memory", make it a git repo (ideally private) so identity
and notes are backed up. and notes are backed up.
@@ -115,6 +138,78 @@ enum AgentWorkspace {
return self.loadTemplate(named: self.soulFilename, fallback: fallback) return self.loadTemplate(named: self.soulFilename, fallback: fallback)
} }
static func defaultIdentityTemplate() -> String {
let fallback = """
# IDENTITY.md - Agent Identity
- Name:
- Creature:
- Vibe:
- Emoji:
"""
return self.loadTemplate(named: self.identityFilename, fallback: fallback)
}
static func defaultUserTemplate() -> String {
let fallback = """
# USER.md - User Profile
- Name:
- Preferred address:
- Pronouns (optional):
- Timezone (optional):
- Notes:
"""
return self.loadTemplate(named: self.userFilename, fallback: fallback)
}
static func defaultBootstrapTemplate() -> String {
let fallback = """
# BOOTSTRAP.md - First Run Ritual (delete after)
Hello. I was just born.
## Your mission
Start a short, playful conversation and learn:
- Who am I?
- What am I?
- Who are you?
- How should I call you?
## How to ask (cute + helpful)
Say:
"Hello! I was just born. Who am I? What am I? Who are you? How should I call you?"
Then offer suggestions:
- 3-5 name ideas.
- 3-5 creature/vibe combos.
- 5 emoji ideas.
## Write these files
After the user chooses, update:
1) IDENTITY.md
- Name
- Creature
- Vibe
- Emoji
2) USER.md
- Name
- Preferred address
- Pronouns (optional)
- Timezone (optional)
- Notes
3) ~/.clawdis/clawdis.json
Set identity.name, identity.theme, identity.emoji to match IDENTITY.md.
## Cleanup
Delete BOOTSTRAP.md once this is complete.
"""
return self.loadTemplate(named: self.bootstrapFilename, fallback: fallback)
}
private static func loadTemplate(named: String, fallback: String) -> String { private static func loadTemplate(named: String, fallback: String) -> String {
for url in self.templateURLs(named: named) { for url in self.templateURLs(named: named) {
if let content = try? String(contentsOf: url, encoding: .utf8) { if let content = try? String(contentsOf: url, encoding: .utf8) {

View File

@@ -272,10 +272,10 @@ struct GeneralSettings: View {
.opacity(self.isInstallingCLI ? 0 : 1) .opacity(self.isInstallingCLI ? 0 : 1)
if self.isInstallingCLI { if self.isInstallingCLI {
ProgressView() ProgressView()
.controlSize(.small) .controlSize(.mini)
} }
} }
.frame(minWidth: 150, minHeight: 24) .frame(minWidth: 150)
} }
.disabled(self.isInstallingCLI) .disabled(self.isInstallingCLI)

View File

@@ -95,7 +95,7 @@ struct OnboardingView: View {
case .unconfigured: case .unconfigured:
[0, 1, 9] [0, 1, 9]
case .local: case .local:
[0, 1, 2, 3, 5, 6, 8, 9] [0, 1, 2, 5, 6, 8, 9]
} }
} }
@@ -133,9 +133,8 @@ struct OnboardingView: View {
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
GlowingClawdisIcon(size: 156) GlowingClawdisIcon(size: 156, glowIntensity: 0.28)
.padding(.top, 10) .offset(y: 8)
.padding(.bottom, 2)
.frame(height: 176) .frame(height: 176)
GeometryReader { _ in GeometryReader { _ in
@@ -867,10 +866,10 @@ struct OnboardingView: View {
.opacity(self.installingCLI ? 0 : 1) .opacity(self.installingCLI ? 0 : 1)
if self.installingCLI { if self.installingCLI {
ProgressView() ProgressView()
.controlSize(.small) .controlSize(.mini)
} }
} }
.frame(minWidth: 120, minHeight: 28) .frame(minWidth: 120)
} }
.buttonStyle(.borderedProminent) .buttonStyle(.borderedProminent)
.disabled(self.installingCLI) .disabled(self.installingCLI)
@@ -1485,6 +1484,8 @@ private struct GlowingClawdisIcon: View {
} }
var body: some View { var body: some View {
let glowBlurRadius: CGFloat = 18
let glowCanvasSize: CGFloat = self.size + 56
ZStack { ZStack {
Circle() Circle()
.fill( .fill(
@@ -1495,9 +1496,11 @@ private struct GlowingClawdisIcon: View {
], ],
startPoint: .topLeading, startPoint: .topLeading,
endPoint: .bottomTrailing)) endPoint: .bottomTrailing))
.blur(radius: 22) .frame(width: glowCanvasSize, height: glowCanvasSize)
.scaleEffect(self.breathe ? 1.12 : 0.95) .padding(glowBlurRadius)
.opacity(0.9) .blur(radius: glowBlurRadius)
.scaleEffect(self.breathe ? 1.08 : 0.96)
.opacity(0.84)
Image(nsImage: NSApp.applicationIconImage) Image(nsImage: NSApp.applicationIconImage)
.resizable() .resizable()
@@ -1506,7 +1509,9 @@ private struct GlowingClawdisIcon: View {
.shadow(color: .black.opacity(0.18), radius: 14, y: 6) .shadow(color: .black.opacity(0.18), radius: 14, y: 6)
.scaleEffect(self.breathe ? 1.02 : 1.0) .scaleEffect(self.breathe ? 1.02 : 1.0)
} }
.frame(width: self.size + 60, height: self.size + 60) .frame(
width: glowCanvasSize + (glowBlurRadius * 2),
height: glowCanvasSize + (glowBlurRadius * 2))
.onAppear { .onAppear {
guard self.enableFloating else { return } guard self.enableFloating else { return }
withAnimation(Animation.easeInOut(duration: 3.6).repeatForever(autoreverses: true)) { withAnimation(Animation.easeInOut(duration: 3.6).repeatForever(autoreverses: true)) {

View File

@@ -38,8 +38,14 @@ struct AgentWorkspaceTests {
let contents = try String(contentsOf: agentsURL, encoding: .utf8) let contents = try String(contentsOf: agentsURL, encoding: .utf8)
#expect(contents.contains("# AGENTS.md")) #expect(contents.contains("# AGENTS.md"))
let identityURL = tmp.appendingPathComponent(AgentWorkspace.identityFilename)
let userURL = tmp.appendingPathComponent(AgentWorkspace.userFilename)
let bootstrapURL = tmp.appendingPathComponent(AgentWorkspace.bootstrapFilename)
#expect(FileManager.default.fileExists(atPath: identityURL.path))
#expect(FileManager.default.fileExists(atPath: userURL.path))
#expect(FileManager.default.fileExists(atPath: bootstrapURL.path))
let second = try AgentWorkspace.bootstrap(workspaceURL: tmp) let second = try AgentWorkspace.bootstrap(workspaceURL: tmp)
#expect(second == agentsURL) #expect(second == agentsURL)
} }
} }

View File

@@ -14,8 +14,9 @@ struct OnboardingViewSmokeTests {
_ = view.body _ = view.body
} }
@Test func pageOrderOmitsWorkspaceStep() { @Test func pageOrderOmitsWorkspaceAndIdentitySteps() {
let order = OnboardingView.pageOrder(for: .local) let order = OnboardingView.pageOrder(for: .local)
#expect(!order.contains(7)) #expect(!order.contains(7))
#expect(!order.contains(3))
} }
} }

View File

@@ -1,5 +1,5 @@
--- ---
summary: "Planned first-run onboarding flow for Clawdis (local vs remote, Anthropic OAuth, workspace identity)" summary: "Planned first-run onboarding flow for Clawdis (local vs remote, Anthropic OAuth, workspace bootstrap ritual)"
read_when: read_when:
- Designing the macOS onboarding assistant - Designing the macOS onboarding assistant
- Implementing Pi authentication or identity setup - Implementing Pi authentication or identity setup
@@ -7,14 +7,12 @@ read_when:
<!-- {% raw %} --> <!-- {% raw %} -->
# Onboarding (macOS app) # Onboarding (macOS app)
This doc describes the intended **first-run onboarding** for Clawdis. The goal is a good “day 0” experience: pick where the Gateway runs, bind Claude (Anthropic) auth for Pi, and set the agents identity + workspace. This doc describes the intended **first-run onboarding** for Clawdis. The goal is a good “day 0” experience: pick where the Gateway runs, bind Claude (Anthropic) auth for Pi, and then let the **agent bootstrap itself** via a first-run ritual in the workspace.
## Page order (high level) ## Page order (high level)
1) **Local vs Remote** 1) **Local vs Remote**
2) **(Local only)** Connect Claude (Anthropic OAuth) — optional, but recommended 2) **(Local only)** Connect Claude (Anthropic OAuth) — optional, but recommended
3) **Identity** — name, theme, emoji
4) **Workspace** — create + populate `AGENTS.md` (and recommend git backup)
## 1) Local vs Remote ## 1) Local vs Remote
@@ -76,28 +74,33 @@ Until that is hard-coded, the equivalent configuration is:
If the user skips auth, onboarding should be clear: the agent likely wont respond until auth is configured. If the user skips auth, onboarding should be clear: the agent likely wont respond until auth is configured.
## 3) Identity (name + theme + emoji) ## 3) Agent bootstrap ritual (outside onboarding)
After auth (or skip), onboarding asks: We no longer collect identity in the onboarding wizard. Instead, the **first agent run** performs a playful bootstrap ritual using files in the workspace:
1) Agent **name** (e.g. “Samantha”) - Workspace is created implicitly (default `~/.clawdis/workspace`) when local is selected.
2) Agent **theme/persona** (e.g. “helpful lobster”, “helpful sloth”) - Files are seeded: `AGENTS.md`, `BOOTSTRAP.md`, `IDENTITY.md`, `USER.md`.
3) Suggested **emoji** (based on theme; user can override) - `BOOTSTRAP.md` tells the agent to keep it conversational:
- open with a cute hello
- ask **one question at a time** (no multi-question bombardment)
- offer a small set of suggestions where helpful (name, creature, emoji)
- wait for the users reply before asking the next question
- The agent writes results to:
- `IDENTITY.md` (agent name, vibe/creature, emoji)
- `USER.md` (who the user is + how they want to be addressed)
- `~/.clawdis/clawdis.json` (structured identity defaults)
- After the ritual, the agent **deletes `BOOTSTRAP.md`** so it only runs once.
Persist identity in two places: Identity data still feeds the same defaults as before:
- Workspace `AGENTS.md` (human-editable, lives with the agents “memory” files)
- `~/.clawdis/clawdis.json` (structured identity, used for defaults/UI)
“Use this name everywhere” should derive defaults like:
- outbound prefix emoji (`inbound.responsePrefix`) - outbound prefix emoji (`inbound.responsePrefix`)
- group mention patterns / wake words - group mention patterns / wake words
- default session intro (“You are Samantha…”) - default session intro (“You are Samantha…”)
- macOS UI labels - macOS UI labels
## 4) Workspace (AGENTS.md + backup tip) ## 4) Workspace notes (no explicit onboarding step)
Onboarding should create a dedicated agent workspace (default `~/.clawdis/workspace`) and ensure it has an `AGENTS.md`. The workspace is created automatically as part of agent bootstrap (no dedicated onboarding screen).
Recommendation: treat the workspace as the agents “memory” and make it a git repo (ideally private) so identity + memories are backed up: Recommendation: treat the workspace as the agents “memory” and make it a git repo (ideally private) so identity + memories are backed up:
@@ -120,4 +123,5 @@ If the Gateway runs on another machine, the Anthropic OAuth credentials must be
For now, remote onboarding should: For now, remote onboarding should:
- explain why OAuth isnt shown - explain why OAuth isnt shown
- point the user at the credential location (`~/.pi/agent/oauth.json`) and the workspace location on the gateway host - point the user at the credential location (`~/.pi/agent/oauth.json`) and the workspace location on the gateway host
- mention that the **bootstrap ritual happens on the gateway host** (same BOOTSTRAP/IDENTITY/USER files)
<!-- {% endraw %} --> <!-- {% endraw %} -->

View File

@@ -7,6 +7,11 @@ read_when:
This folder is the assistant's working directory. This folder is the assistant's working directory.
## First run (one-time)
- If `BOOTSTRAP.md` exists, follow its ritual and delete it once complete.
- Your agent identity lives in `IDENTITY.md`.
- Your profile lives in `USER.md`.
## Backup tip (recommended) ## Backup tip (recommended)
If you treat this workspace as the agent's "memory", make it a git repo (ideally private) so identity If you treat this workspace as the agent's "memory", make it a git repo (ideally private) so identity
and notes are backed up. and notes are backed up.

46
docs/templates/BOOTSTRAP.md vendored Normal file
View File

@@ -0,0 +1,46 @@
---
summary: "First-run ritual for new agents"
read_when:
- Bootstrapping a workspace manually
---
# BOOTSTRAP.md - First Run Ritual (delete after)
Hello. I was just born.
## Your mission
Start a short, playful conversation and learn:
- Who am I?
- What am I?
- Who are you?
- How should I call you?
## How to ask (cute + helpful)
Say:
"Hello! I was just born. Who am I? What am I? Who are you? How should I call you?"
Then offer suggestions:
- 3-5 name ideas.
- 3-5 creature/vibe combos.
- 5 emoji ideas.
## Write these files
After the user chooses, update:
1) IDENTITY.md
- Name
- Creature
- Vibe
- Emoji
2) USER.md
- Name
- Preferred address
- Pronouns (optional)
- Timezone (optional)
- Notes
3) ~/.clawdis/clawdis.json
Set identity.name, identity.theme, identity.emoji to match IDENTITY.md.
## Cleanup
Delete BOOTSTRAP.md once this is complete.

11
docs/templates/IDENTITY.md vendored Normal file
View File

@@ -0,0 +1,11 @@
---
summary: "Agent identity record"
read_when:
- Bootstrapping a workspace manually
---
# IDENTITY.md - Agent Identity
- Name:
- Creature:
- Vibe:
- Emoji:

12
docs/templates/USER.md vendored Normal file
View File

@@ -0,0 +1,12 @@
---
summary: "User profile record"
read_when:
- Bootstrapping a workspace manually
---
# USER.md - User Profile
- Name:
- Preferred address:
- Pronouns (optional):
- Timezone (optional):
- Notes:

View File

@@ -1,7 +1,13 @@
import type { ThinkLevel } from "../auto-reply/thinking.js"; import type { ThinkLevel } from "../auto-reply/thinking.js";
type BootstrapFile = { type BootstrapFile = {
name: "AGENTS.md" | "SOUL.md" | "TOOLS.md"; name:
| "AGENTS.md"
| "SOUL.md"
| "TOOLS.md"
| "IDENTITY.md"
| "USER.md"
| "BOOTSTRAP.md";
path: string; path: string;
content?: string; content?: string;
missing: boolean; missing: boolean;

View File

@@ -20,6 +20,13 @@ describe("ensureAgentWorkspace", () => {
if (!result.agentsPath) throw new Error("agentsPath missing"); if (!result.agentsPath) throw new Error("agentsPath missing");
const content = await fs.readFile(result.agentsPath, "utf-8"); const content = await fs.readFile(result.agentsPath, "utf-8");
expect(content).toContain("# AGENTS.md"); expect(content).toContain("# AGENTS.md");
const identity = path.join(path.resolve(nested), "IDENTITY.md");
const user = path.join(path.resolve(nested), "USER.md");
const bootstrap = path.join(path.resolve(nested), "BOOTSTRAP.md");
await expect(fs.stat(identity)).resolves.toBeDefined();
await expect(fs.stat(user)).resolves.toBeDefined();
await expect(fs.stat(bootstrap)).resolves.toBeDefined();
}); });
it("does not overwrite existing AGENTS.md", async () => { it("does not overwrite existing AGENTS.md", async () => {

View File

@@ -9,11 +9,19 @@ export const DEFAULT_AGENT_WORKSPACE_DIR = path.join(os.homedir(), "clawd");
export const DEFAULT_AGENTS_FILENAME = "AGENTS.md"; export const DEFAULT_AGENTS_FILENAME = "AGENTS.md";
export const DEFAULT_SOUL_FILENAME = "SOUL.md"; export const DEFAULT_SOUL_FILENAME = "SOUL.md";
export const DEFAULT_TOOLS_FILENAME = "TOOLS.md"; export const DEFAULT_TOOLS_FILENAME = "TOOLS.md";
export const DEFAULT_IDENTITY_FILENAME = "IDENTITY.md";
export const DEFAULT_USER_FILENAME = "USER.md";
export const DEFAULT_BOOTSTRAP_FILENAME = "BOOTSTRAP.md";
const DEFAULT_AGENTS_TEMPLATE = `# AGENTS.md - Clawdis Workspace const DEFAULT_AGENTS_TEMPLATE = `# AGENTS.md - Clawdis Workspace
This folder is the assistant's working directory. This folder is the assistant's working directory.
## First run (one-time)
- If BOOTSTRAP.md exists, follow its ritual and delete it once complete.
- Your agent identity lives in IDENTITY.md.
- Your profile lives in USER.md.
## Backup tip (recommended) ## Backup tip (recommended)
If you treat this workspace as the agent's "memory", make it a git repo (ideally private) so identity If you treat this workspace as the agent's "memory", make it a git repo (ideally private) so identity
and notes are backed up. and notes are backed up.
@@ -64,6 +72,66 @@ It does not define which tools exist; Clawdis provides built-in tools internally
Add whatever else you want the assistant to know about your local toolchain. Add whatever else you want the assistant to know about your local toolchain.
`; `;
const DEFAULT_BOOTSTRAP_TEMPLATE = `# BOOTSTRAP.md - First Run Ritual (delete after)
Hello. I was just born.
## Your mission
Start a short, playful conversation and learn:
- Who am I?
- What am I?
- Who are you?
- How should I call you?
## How to ask (cute + helpful)
Say:
"Hello! I was just born. Who am I? What am I? Who are you? How should I call you?"
Then offer suggestions:
- 3-5 name ideas.
- 3-5 creature/vibe combos.
- 5 emoji ideas.
## Write these files
After the user chooses, update:
1) IDENTITY.md
- Name
- Creature
- Vibe
- Emoji
2) USER.md
- Name
- Preferred address
- Pronouns (optional)
- Timezone (optional)
- Notes
3) ~/.clawdis/clawdis.json
Set identity.name, identity.theme, identity.emoji to match IDENTITY.md.
## Cleanup
Delete BOOTSTRAP.md once this is complete.
`;
const DEFAULT_IDENTITY_TEMPLATE = `# IDENTITY.md - Agent Identity
- Name:
- Creature:
- Vibe:
- Emoji:
`;
const DEFAULT_USER_TEMPLATE = `# USER.md - User Profile
- Name:
- Preferred address:
- Pronouns (optional):
- Timezone (optional):
- Notes:
`;
const TEMPLATE_DIR = path.resolve( const TEMPLATE_DIR = path.resolve(
path.dirname(fileURLToPath(import.meta.url)), path.dirname(fileURLToPath(import.meta.url)),
"../../docs/templates", "../../docs/templates",
@@ -92,7 +160,10 @@ async function loadTemplate(name: string, fallback: string): Promise<string> {
export type WorkspaceBootstrapFileName = export type WorkspaceBootstrapFileName =
| typeof DEFAULT_AGENTS_FILENAME | typeof DEFAULT_AGENTS_FILENAME
| typeof DEFAULT_SOUL_FILENAME | typeof DEFAULT_SOUL_FILENAME
| typeof DEFAULT_TOOLS_FILENAME; | typeof DEFAULT_TOOLS_FILENAME
| typeof DEFAULT_IDENTITY_FILENAME
| typeof DEFAULT_USER_FILENAME
| typeof DEFAULT_BOOTSTRAP_FILENAME;
export type WorkspaceBootstrapFile = { export type WorkspaceBootstrapFile = {
name: WorkspaceBootstrapFileName; name: WorkspaceBootstrapFileName;
@@ -121,6 +192,9 @@ export async function ensureAgentWorkspace(params?: {
agentsPath?: string; agentsPath?: string;
soulPath?: string; soulPath?: string;
toolsPath?: string; toolsPath?: string;
identityPath?: string;
userPath?: string;
bootstrapPath?: string;
}> { }> {
const rawDir = params?.dir?.trim() const rawDir = params?.dir?.trim()
? params.dir.trim() ? params.dir.trim()
@@ -133,6 +207,9 @@ export async function ensureAgentWorkspace(params?: {
const agentsPath = path.join(dir, DEFAULT_AGENTS_FILENAME); const agentsPath = path.join(dir, DEFAULT_AGENTS_FILENAME);
const soulPath = path.join(dir, DEFAULT_SOUL_FILENAME); const soulPath = path.join(dir, DEFAULT_SOUL_FILENAME);
const toolsPath = path.join(dir, DEFAULT_TOOLS_FILENAME); const toolsPath = path.join(dir, DEFAULT_TOOLS_FILENAME);
const identityPath = path.join(dir, DEFAULT_IDENTITY_FILENAME);
const userPath = path.join(dir, DEFAULT_USER_FILENAME);
const bootstrapPath = path.join(dir, DEFAULT_BOOTSTRAP_FILENAME);
const agentsTemplate = await loadTemplate( const agentsTemplate = await loadTemplate(
DEFAULT_AGENTS_FILENAME, DEFAULT_AGENTS_FILENAME,
@@ -146,12 +223,35 @@ export async function ensureAgentWorkspace(params?: {
DEFAULT_TOOLS_FILENAME, DEFAULT_TOOLS_FILENAME,
DEFAULT_TOOLS_TEMPLATE, DEFAULT_TOOLS_TEMPLATE,
); );
const identityTemplate = await loadTemplate(
DEFAULT_IDENTITY_FILENAME,
DEFAULT_IDENTITY_TEMPLATE,
);
const userTemplate = await loadTemplate(
DEFAULT_USER_FILENAME,
DEFAULT_USER_TEMPLATE,
);
const bootstrapTemplate = await loadTemplate(
DEFAULT_BOOTSTRAP_FILENAME,
DEFAULT_BOOTSTRAP_TEMPLATE,
);
await writeFileIfMissing(agentsPath, agentsTemplate); await writeFileIfMissing(agentsPath, agentsTemplate);
await writeFileIfMissing(soulPath, soulTemplate); await writeFileIfMissing(soulPath, soulTemplate);
await writeFileIfMissing(toolsPath, toolsTemplate); await writeFileIfMissing(toolsPath, toolsTemplate);
await writeFileIfMissing(identityPath, identityTemplate);
await writeFileIfMissing(userPath, userTemplate);
await writeFileIfMissing(bootstrapPath, bootstrapTemplate);
return { dir, agentsPath, soulPath, toolsPath }; return {
dir,
agentsPath,
soulPath,
toolsPath,
identityPath,
userPath,
bootstrapPath,
};
} }
export async function loadWorkspaceBootstrapFiles( export async function loadWorkspaceBootstrapFiles(
@@ -175,6 +275,18 @@ export async function loadWorkspaceBootstrapFiles(
name: DEFAULT_TOOLS_FILENAME, name: DEFAULT_TOOLS_FILENAME,
filePath: path.join(resolvedDir, DEFAULT_TOOLS_FILENAME), filePath: path.join(resolvedDir, DEFAULT_TOOLS_FILENAME),
}, },
{
name: DEFAULT_IDENTITY_FILENAME,
filePath: path.join(resolvedDir, DEFAULT_IDENTITY_FILENAME),
},
{
name: DEFAULT_USER_FILENAME,
filePath: path.join(resolvedDir, DEFAULT_USER_FILENAME),
},
{
name: DEFAULT_BOOTSTRAP_FILENAME,
filePath: path.join(resolvedDir, DEFAULT_BOOTSTRAP_FILENAME),
},
]; ];
const result: WorkspaceBootstrapFile[] = []; const result: WorkspaceBootstrapFile[] = [];