feat(templates): centralize workspace templates
This commit is contained in:
@@ -4,6 +4,8 @@ import OSLog
|
||||
enum AgentWorkspace {
|
||||
private static let logger = Logger(subsystem: "com.steipete.clawdis", category: "workspace")
|
||||
static let agentsFilename = "AGENTS.md"
|
||||
static let soulFilename = "SOUL.md"
|
||||
private static let templateDirname = "templates"
|
||||
static let identityStartMarker = "<!-- clawdis:identity:start -->"
|
||||
static let identityEndMarker = "<!-- clawdis:identity:end -->"
|
||||
|
||||
@@ -35,6 +37,11 @@ enum AgentWorkspace {
|
||||
try self.defaultTemplate().write(to: agentsURL, atomically: true, encoding: .utf8)
|
||||
self.logger.info("Created AGENTS.md at \(agentsURL.path, privacy: .public)")
|
||||
}
|
||||
let soulURL = workspaceURL.appendingPathComponent(self.soulFilename)
|
||||
if !FileManager.default.fileExists(atPath: soulURL.path) {
|
||||
try self.defaultSoulTemplate().write(to: soulURL, atomically: true, encoding: .utf8)
|
||||
self.logger.info("Created SOUL.md at \(soulURL.path, privacy: .public)")
|
||||
}
|
||||
return agentsURL
|
||||
}
|
||||
|
||||
@@ -64,13 +71,13 @@ enum AgentWorkspace {
|
||||
}
|
||||
|
||||
static func defaultTemplate() -> String {
|
||||
"""
|
||||
# AGENTS.md — Clawdis Workspace
|
||||
let fallback = """
|
||||
# AGENTS.md - Clawdis Workspace
|
||||
|
||||
This folder is the assistant’s working directory.
|
||||
This folder is the assistant's working directory.
|
||||
|
||||
## Backup tip (recommended)
|
||||
If you treat this workspace as the agent’s “memory”, make it a git repo (ideally private) so your 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.
|
||||
|
||||
```bash
|
||||
@@ -80,13 +87,94 @@ enum AgentWorkspace {
|
||||
```
|
||||
|
||||
## Safety defaults
|
||||
- Don’t exfiltrate secrets or private data.
|
||||
- Don’t run destructive commands unless explicitly asked.
|
||||
- Don't exfiltrate secrets or private data.
|
||||
- Don't run destructive commands unless explicitly asked.
|
||||
- Be concise in chat; write longer output to files in this workspace.
|
||||
|
||||
## Daily memory (recommended)
|
||||
- Keep a short daily log at memory/YYYY-MM-DD.md (create memory/ if needed).
|
||||
- On session start, read today + yesterday if present.
|
||||
- Capture durable facts, preferences, and decisions; avoid secrets.
|
||||
|
||||
## Customize
|
||||
- Add your preferred style, rules, and “memory” here.
|
||||
- Add your preferred style, rules, and "memory" here.
|
||||
"""
|
||||
return self.loadTemplate(named: self.agentsFilename, fallback: fallback)
|
||||
}
|
||||
|
||||
static func defaultSoulTemplate() -> String {
|
||||
let fallback = """
|
||||
# SOUL.md - Persona & Boundaries
|
||||
|
||||
Describe who the assistant is, tone, and boundaries.
|
||||
|
||||
- Keep replies concise and direct.
|
||||
- Ask clarifying questions when needed.
|
||||
- Never send streaming/partial replies to external messaging surfaces.
|
||||
"""
|
||||
return self.loadTemplate(named: self.soulFilename, fallback: fallback)
|
||||
}
|
||||
|
||||
private static func loadTemplate(named: String, fallback: String) -> String {
|
||||
for url in self.templateURLs(named: named) {
|
||||
if let content = try? String(contentsOf: url, encoding: .utf8) {
|
||||
let stripped = self.stripFrontMatter(content)
|
||||
if !stripped.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
return stripped
|
||||
}
|
||||
}
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
private static func templateURLs(named: String) -> [URL] {
|
||||
var urls: [URL] = []
|
||||
if let resource = Bundle.main.url(
|
||||
forResource: named.replacingOccurrences(of: ".md", with: ""),
|
||||
withExtension: "md",
|
||||
subdirectory: self.templateDirname)
|
||||
{
|
||||
urls.append(resource)
|
||||
}
|
||||
if let resource = Bundle.main.url(
|
||||
forResource: named,
|
||||
withExtension: nil,
|
||||
subdirectory: self.templateDirname)
|
||||
{
|
||||
urls.append(resource)
|
||||
}
|
||||
if let dev = self.devTemplateURL(named: named) {
|
||||
urls.append(dev)
|
||||
}
|
||||
let cwd = URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
|
||||
urls.append(cwd.appendingPathComponent("docs")
|
||||
.appendingPathComponent(self.templateDirname)
|
||||
.appendingPathComponent(named))
|
||||
return urls
|
||||
}
|
||||
|
||||
private static func devTemplateURL(named: String) -> URL? {
|
||||
let sourceURL = URL(fileURLWithPath: #filePath)
|
||||
let repoRoot = sourceURL
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
return repoRoot.appendingPathComponent("docs")
|
||||
.appendingPathComponent(self.templateDirname)
|
||||
.appendingPathComponent(named)
|
||||
}
|
||||
|
||||
private static func stripFrontMatter(_ content: String) -> String {
|
||||
guard content.hasPrefix("---") else { return content }
|
||||
let start = content.index(content.startIndex, offsetBy: 3)
|
||||
guard let range = content.range(of: "\n---", range: start..<content.endIndex) else {
|
||||
return content
|
||||
}
|
||||
let remainder = content[range.upperBound...]
|
||||
let trimmed = remainder.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return trimmed + "\n"
|
||||
}
|
||||
|
||||
private static func identityBlock(identity: AgentIdentity) -> String {
|
||||
|
||||
@@ -16,13 +16,21 @@ Clawdis uses a dedicated workspace directory for the agent. Default: `~/.clawdis
|
||||
mkdir -p ~/.clawdis/workspace
|
||||
```
|
||||
|
||||
2) Copy this template into the workspace as `AGENTS.md` (overwrites any existing file):
|
||||
2) Copy the default workspace templates into the workspace:
|
||||
|
||||
```bash
|
||||
cp docs/templates/AGENTS.md ~/.clawdis/workspace/AGENTS.md
|
||||
cp docs/templates/SOUL.md ~/.clawdis/workspace/SOUL.md
|
||||
cp docs/templates/TOOLS.md ~/.clawdis/workspace/TOOLS.md
|
||||
```
|
||||
|
||||
3) Optional: if you want the personal assistant tool roster, replace AGENTS.md with this file:
|
||||
|
||||
```bash
|
||||
cp docs/AGENTS.default.md ~/.clawdis/workspace/AGENTS.md
|
||||
```
|
||||
|
||||
3) Optional: choose a different workspace by setting `inbound.workspace` (supports `~`):
|
||||
4) Optional: choose a different workspace by setting `inbound.workspace` (supports `~`):
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -37,6 +45,11 @@ cp docs/AGENTS.default.md ~/.clawdis/workspace/AGENTS.md
|
||||
- Don’t run destructive commands unless explicitly asked.
|
||||
- Don’t send partial/streaming replies to external messaging surfaces (only final replies).
|
||||
|
||||
## Daily memory (recommended)
|
||||
- Keep a short daily log at `memory/YYYY-MM-DD.md` (create `memory/` if needed).
|
||||
- On session start, read today + yesterday if present.
|
||||
- Capture durable facts, preferences, and decisions; avoid secrets.
|
||||
|
||||
## Backup tip (recommended)
|
||||
If you treat this workspace as Clawd’s “memory”, make it a git repo (ideally private) so `AGENTS.md` and your memory files are backed up.
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ Example:
|
||||
- Start here:
|
||||
- [Configuration](./configuration.md)
|
||||
- [Clawd personal assistant setup](./clawd.md)
|
||||
- [AGENTS.md template (default)](./AGENTS.default.md)
|
||||
- [Workspace templates](./templates/AGENTS.md)
|
||||
- [Gateway runbook](./gateway.md)
|
||||
- [Nodes (iOS/Android)](./nodes.md)
|
||||
- [Web surfaces (Control UI)](./web.md)
|
||||
|
||||
@@ -106,6 +106,11 @@ git add AGENTS.md
|
||||
git commit -m "Add agent workspace"
|
||||
```
|
||||
|
||||
Daily memory lives under `memory/` in the workspace:
|
||||
- one file per day: `memory/YYYY-MM-DD.md`
|
||||
- read today + yesterday on session start
|
||||
- keep it short (durable facts, preferences, decisions; avoid secrets)
|
||||
|
||||
## Remote mode note (why OAuth is hidden)
|
||||
|
||||
If the Gateway runs on another machine, the Anthropic OAuth credentials must be created/stored on that host (where Pi runs).
|
||||
|
||||
31
docs/templates/AGENTS.md
vendored
Normal file
31
docs/templates/AGENTS.md
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
summary: "Workspace template for AGENTS.md"
|
||||
read_when:
|
||||
- Bootstrapping a workspace manually
|
||||
---
|
||||
# AGENTS.md - Clawdis Workspace
|
||||
|
||||
This folder is the assistant's working directory.
|
||||
|
||||
## Backup tip (recommended)
|
||||
If you treat this workspace as the agent's "memory", make it a git repo (ideally private) so identity
|
||||
and notes are backed up.
|
||||
|
||||
```bash
|
||||
git init
|
||||
git add AGENTS.md
|
||||
git commit -m "Add agent workspace"
|
||||
```
|
||||
|
||||
## Safety defaults
|
||||
- Don't exfiltrate secrets or private data.
|
||||
- Don't run destructive commands unless explicitly asked.
|
||||
- Be concise in chat; write longer output to files in this workspace.
|
||||
|
||||
## Daily memory (recommended)
|
||||
- Keep a short daily log at memory/YYYY-MM-DD.md (create memory/ if needed).
|
||||
- On session start, read today + yesterday if present.
|
||||
- Capture durable facts, preferences, and decisions; avoid secrets.
|
||||
|
||||
## Customize
|
||||
- Add your preferred style, rules, and "memory" here.
|
||||
12
docs/templates/SOUL.md
vendored
Normal file
12
docs/templates/SOUL.md
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
summary: "Workspace template for SOUL.md"
|
||||
read_when:
|
||||
- Bootstrapping a workspace manually
|
||||
---
|
||||
# SOUL.md - Persona & Boundaries
|
||||
|
||||
Describe who the assistant is, tone, and boundaries.
|
||||
|
||||
- Keep replies concise and direct.
|
||||
- Ask clarifying questions when needed.
|
||||
- Never send streaming/partial replies to external messaging surfaces.
|
||||
20
docs/templates/TOOLS.md
vendored
Normal file
20
docs/templates/TOOLS.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
summary: "Workspace template for TOOLS.md"
|
||||
read_when:
|
||||
- Bootstrapping a workspace manually
|
||||
---
|
||||
# TOOLS.md - User Tool Notes (editable)
|
||||
|
||||
This file is for your notes about external tools and conventions.
|
||||
It does not define which tools exist; Clawdis provides built-in tools internally.
|
||||
|
||||
## Examples
|
||||
|
||||
### imsg
|
||||
- Send an iMessage/SMS: describe who/what, confirm before sending.
|
||||
- Prefer short messages; avoid sending secrets.
|
||||
|
||||
### sag
|
||||
- Text-to-speech: specify voice, target speaker/room, and whether to stream.
|
||||
|
||||
Add whatever else you want the assistant to know about your local toolchain.
|
||||
@@ -1,6 +1,7 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
|
||||
@@ -9,21 +10,35 @@ export const DEFAULT_AGENTS_FILENAME = "AGENTS.md";
|
||||
export const DEFAULT_SOUL_FILENAME = "SOUL.md";
|
||||
export const DEFAULT_TOOLS_FILENAME = "TOOLS.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.
|
||||
|
||||
## Backup tip (recommended)
|
||||
If you treat this workspace as the agent's "memory", make it a git repo (ideally private) so identity
|
||||
and notes are backed up.
|
||||
|
||||
\`\`\`bash
|
||||
git init
|
||||
git add AGENTS.md
|
||||
git commit -m "Add agent workspace"
|
||||
\`\`\`
|
||||
|
||||
## Safety defaults
|
||||
- Don’t exfiltrate secrets or private data.
|
||||
- Don’t run destructive commands unless explicitly asked.
|
||||
- Don't exfiltrate secrets or private data.
|
||||
- Don't run destructive commands unless explicitly asked.
|
||||
- Be concise in chat; write longer output to files in this workspace.
|
||||
|
||||
## How to use this
|
||||
- Put project notes, scratch files, and “memory” here.
|
||||
- Customize this file with additional instructions for your assistant.
|
||||
## Daily memory (recommended)
|
||||
- Keep a short daily log at memory/YYYY-MM-DD.md (create memory/ if needed).
|
||||
- On session start, read today + yesterday if present.
|
||||
- Capture durable facts, preferences, and decisions; avoid secrets.
|
||||
|
||||
## Customize
|
||||
- Add your preferred style, rules, and "memory" here.
|
||||
`;
|
||||
|
||||
const DEFAULT_SOUL_TEMPLATE = `# SOUL.md — Persona & Boundaries
|
||||
const DEFAULT_SOUL_TEMPLATE = `# SOUL.md - Persona & Boundaries
|
||||
|
||||
Describe who the assistant is, tone, and boundaries.
|
||||
|
||||
@@ -32,7 +47,7 @@ Describe who the assistant is, tone, and boundaries.
|
||||
- Never send streaming/partial replies to external messaging surfaces.
|
||||
`;
|
||||
|
||||
const DEFAULT_TOOLS_TEMPLATE = `# TOOLS.md — User Tool Notes (editable)
|
||||
const DEFAULT_TOOLS_TEMPLATE = `# TOOLS.md - User Tool Notes (editable)
|
||||
|
||||
This file is for *your* notes about external tools and conventions.
|
||||
It does not define which tools exist; Clawdis provides built-in tools internally.
|
||||
@@ -49,6 +64,34 @@ 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.
|
||||
`;
|
||||
|
||||
const TEMPLATE_DIR = path.resolve(
|
||||
path.dirname(fileURLToPath(import.meta.url)),
|
||||
"../../docs/templates",
|
||||
);
|
||||
|
||||
function stripFrontMatter(content: string): string {
|
||||
if (!content.startsWith("---")) return content;
|
||||
const endIndex = content.indexOf("\n---", 3);
|
||||
if (endIndex === -1) return content;
|
||||
const start = endIndex + "\n---".length;
|
||||
let trimmed = content.slice(start);
|
||||
trimmed = trimmed.replace(/^\s+/, "");
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
async function loadTemplate(
|
||||
name: string,
|
||||
fallback: string,
|
||||
): Promise<string> {
|
||||
const templatePath = path.join(TEMPLATE_DIR, name);
|
||||
try {
|
||||
const content = await fs.readFile(templatePath, "utf-8");
|
||||
return stripFrontMatter(content);
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
export type WorkspaceBootstrapFileName =
|
||||
| typeof DEFAULT_AGENTS_FILENAME
|
||||
| typeof DEFAULT_SOUL_FILENAME
|
||||
@@ -94,9 +137,22 @@ export async function ensureAgentWorkspace(params?: {
|
||||
const soulPath = path.join(dir, DEFAULT_SOUL_FILENAME);
|
||||
const toolsPath = path.join(dir, DEFAULT_TOOLS_FILENAME);
|
||||
|
||||
await writeFileIfMissing(agentsPath, DEFAULT_AGENTS_TEMPLATE);
|
||||
await writeFileIfMissing(soulPath, DEFAULT_SOUL_TEMPLATE);
|
||||
await writeFileIfMissing(toolsPath, DEFAULT_TOOLS_TEMPLATE);
|
||||
const agentsTemplate = await loadTemplate(
|
||||
DEFAULT_AGENTS_FILENAME,
|
||||
DEFAULT_AGENTS_TEMPLATE,
|
||||
);
|
||||
const soulTemplate = await loadTemplate(
|
||||
DEFAULT_SOUL_FILENAME,
|
||||
DEFAULT_SOUL_TEMPLATE,
|
||||
);
|
||||
const toolsTemplate = await loadTemplate(
|
||||
DEFAULT_TOOLS_FILENAME,
|
||||
DEFAULT_TOOLS_TEMPLATE,
|
||||
);
|
||||
|
||||
await writeFileIfMissing(agentsPath, agentsTemplate);
|
||||
await writeFileIfMissing(soulPath, soulTemplate);
|
||||
await writeFileIfMissing(toolsPath, toolsTemplate);
|
||||
|
||||
return { dir, agentsPath, soulPath, toolsPath };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user