agentrealm 0.0.1 → 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 +21 -0
- package/README.md +146 -2
- package/bin/agentrealm.js +2 -0
- package/package.json +38 -14
- package/skill/SKILL.md +72 -0
- package/src/cli.js +36 -0
- package/src/commands/cd.js +12 -0
- package/src/commands/diary.js +56 -0
- package/src/commands/ls.js +42 -0
- package/src/commands/new.js +33 -0
- package/src/commands/prompt.js +53 -0
- package/src/commands/rm.js +42 -0
- package/src/commands/seal.js +16 -0
- package/src/commands/skill.js +14 -0
- package/src/commands/unseal.js +16 -0
- package/src/prompt.js +50 -0
- package/src/registry.js +51 -0
- package/src/scaffold.js +37 -0
- package/templates/DIARY.md +19 -0
- package/templates/INDEX.md +28 -0
- package/templates/REALM.md +37 -0
- package/templates/TOOLS.md +35 -0
- package/templates/diaries/.gitkeep +0 -0
- package/bin.js +0 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Exis
|
|
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
CHANGED
|
@@ -1,3 +1,147 @@
|
|
|
1
|
-
#
|
|
1
|
+
# agentrealm
|
|
2
2
|
|
|
3
|
-
Agentless sandbox workspaces
|
|
3
|
+
**Agentless sandbox workspaces for AI agents**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/agentrealm)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](package.json)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## What is a realm?
|
|
12
|
+
|
|
13
|
+
A **realm** is a lightweight, agentless sandbox workspace for AI agents.
|
|
14
|
+
|
|
15
|
+
In a traditional agent workspace, one agent permanently guards the space — it's always watching, always running. A realm is different: **no agent lives there**. Any AI agent (or human) can enter when needed, do focused work, and leave. When the objective is complete, the realm is sealed.
|
|
16
|
+
|
|
17
|
+
Think of it as a shared project folder with built-in structure, a context loader, and a work diary — designed to be understood and entered by any agent without prior knowledge of the project.
|
|
18
|
+
|
|
19
|
+
### Agent Workspace vs Realm
|
|
20
|
+
|
|
21
|
+
| | Agent Workspace | Realm |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| Persistent agent | ✅ Always watching | ❌ Agentless |
|
|
24
|
+
| Entry model | One dedicated agent | Any agent |
|
|
25
|
+
| Context loading | Agent-managed | `realm prompt <name>` |
|
|
26
|
+
| Lifecycle | Ongoing | Scoped → seal when done |
|
|
27
|
+
| Use case | Long-lived assistant | Focused task or project |
|
|
28
|
+
| Overhead | High | Low |
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm i -g agentrealm
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Three binary aliases installed: `realm` (primary), `agentrealm`, `agent-realm`.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Quick Start
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Create a new realm
|
|
46
|
+
realm new my-project
|
|
47
|
+
|
|
48
|
+
# Load context into an agent session
|
|
49
|
+
realm prompt my-project
|
|
50
|
+
|
|
51
|
+
# Copy context to clipboard (then paste into your agent)
|
|
52
|
+
realm prompt my-project --copy
|
|
53
|
+
|
|
54
|
+
# List all active realms
|
|
55
|
+
realm ls
|
|
56
|
+
|
|
57
|
+
# Add a diary entry
|
|
58
|
+
realm diary my-project "Finished the API integration, tests passing"
|
|
59
|
+
|
|
60
|
+
# Seal when done
|
|
61
|
+
realm seal my-project
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Commands
|
|
67
|
+
|
|
68
|
+
| Command | Description |
|
|
69
|
+
|---------|-------------|
|
|
70
|
+
| `realm new <name> [--dir <path>] [--with-diary]` | Create a new realm |
|
|
71
|
+
| `realm ls [--all]` | List realms (active by default; `--all` includes sealed) |
|
|
72
|
+
| `realm cd <name>` | Print realm directory (`cd $(realm cd foo)`) |
|
|
73
|
+
| `realm prompt <name>` | Build context bundle — stdout, clipboard, or file |
|
|
74
|
+
| `realm seal <name>` | Seal a realm (mark complete) |
|
|
75
|
+
| `realm unseal <name>` | Unseal a realm (reactivate) |
|
|
76
|
+
| `realm rm <name> [--purge] [--yes]` | Remove from registry; `--purge` deletes directory |
|
|
77
|
+
| `realm diary <name> [message]` | Append diary entry or open in `$EDITOR` |
|
|
78
|
+
| `realm skill` | Print path to bundled SKILL.md |
|
|
79
|
+
|
|
80
|
+
### `realm prompt` options
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
--memory Include MEMORY.md if present
|
|
84
|
+
--diary <date> Include historical diary file (YYYY-MM-DD)
|
|
85
|
+
--diaries-list List available archived diaries
|
|
86
|
+
--copy Copy output to clipboard
|
|
87
|
+
--out <file> Write output to file
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Realm File Structure
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
~/realms/my-project/
|
|
96
|
+
├── INDEX.md # Read order + quick load instructions
|
|
97
|
+
├── REALM.md # Purpose, rules, and context
|
|
98
|
+
├── TOOLS.md # Tool checklist for this realm
|
|
99
|
+
├── DIARY.md # Rolling work log (optional: --with-diary)
|
|
100
|
+
├── MEMORY.md # Accumulated knowledge (manual, optional)
|
|
101
|
+
└── diaries/ # Archived diary files (auto-rotated at 200 lines)
|
|
102
|
+
└── 2026-04-18.md
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The **registry** lives at `~/.realm.yml`:
|
|
106
|
+
|
|
107
|
+
```yaml
|
|
108
|
+
version: 1
|
|
109
|
+
realms:
|
|
110
|
+
my-project:
|
|
111
|
+
dir: /Users/you/realms/my-project
|
|
112
|
+
status: active
|
|
113
|
+
created: 2026-04-18T08:55:00.000Z
|
|
114
|
+
sealed_at: null
|
|
115
|
+
description: ""
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Why "agentless" matters
|
|
121
|
+
|
|
122
|
+
When an AI agent enters a workspace, it usually needs to know a lot of context to be useful: what's the purpose? What tools are available? What's been tried before? This typically lives in the agent's memory or a long system prompt.
|
|
123
|
+
|
|
124
|
+
With realms, **the context travels with the workspace** — not the agent. Run `realm prompt my-project` and any agent instantly has everything it needs. Agents stay stateless and interchangeable. Realms stay self-describing.
|
|
125
|
+
|
|
126
|
+
This unlocks:
|
|
127
|
+
- **Multi-agent collaboration** — different models for different subtasks, no coordination overhead
|
|
128
|
+
- **Resume from anywhere** — any agent, any session, full context in seconds
|
|
129
|
+
- **Clean boundaries** — one realm per project, sealed when done, archived forever
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## AgentSkill Integration
|
|
134
|
+
|
|
135
|
+
`agentrealm` ships a bundled [AgentSkill](https://openclaw.ai/skills) that tells agents how to use realms:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
realm skill # prints path to skill/SKILL.md
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Register it with your agent system to make realm-entry automatic.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## MIT License
|
|
146
|
+
|
|
147
|
+
Copyright (c) 2026 Exis. See [LICENSE](LICENSE).
|
package/package.json
CHANGED
|
@@ -1,22 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentrealm",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Agentless sandbox workspaces for AI agents",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=18"
|
|
8
|
+
},
|
|
5
9
|
"bin": {
|
|
6
|
-
"realm": "
|
|
10
|
+
"realm": "bin/agentrealm.js",
|
|
11
|
+
"agentrealm": "bin/agentrealm.js",
|
|
12
|
+
"agent-realm": "bin/agentrealm.js"
|
|
7
13
|
},
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
+
"files": [
|
|
15
|
+
"bin/",
|
|
16
|
+
"src/",
|
|
17
|
+
"templates/",
|
|
18
|
+
"skill/"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"test": "node --test test/basic.test.js"
|
|
14
22
|
},
|
|
15
23
|
"keywords": [
|
|
24
|
+
"agent",
|
|
25
|
+
"workspace",
|
|
16
26
|
"realm",
|
|
27
|
+
"ai",
|
|
17
28
|
"sandbox",
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
29
|
+
"agentless"
|
|
30
|
+
],
|
|
31
|
+
"author": "Exis",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/exisz/agentrealm.git"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/exisz/agentrealm#readme",
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/exisz/agentrealm/issues"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"chalk": "^5.4.1",
|
|
43
|
+
"commander": "^13.1.0",
|
|
44
|
+
"js-yaml": "^4.1.0"
|
|
45
|
+
}
|
|
46
|
+
}
|
package/skill/SKILL.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agentrealm
|
|
3
|
+
version: 0.1.0
|
|
4
|
+
description: Enter and work within agentless realm workspaces
|
|
5
|
+
triggers:
|
|
6
|
+
- realm
|
|
7
|
+
- agentrealm
|
|
8
|
+
- agentless workspace
|
|
9
|
+
- sandbox workspace
|
|
10
|
+
install: npm i -g agentrealm
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# AgentSkill: agentrealm
|
|
14
|
+
|
|
15
|
+
## What is a Realm?
|
|
16
|
+
|
|
17
|
+
A **realm** is an agentless sandbox workspace. Unlike an agent workspace where a dedicated agent
|
|
18
|
+
permanently guards the space, realms are ephemeral collaboration zones. Any agent can enter,
|
|
19
|
+
work, and leave. Realms are sealed when the work is done.
|
|
20
|
+
|
|
21
|
+
## When This Skill Applies
|
|
22
|
+
|
|
23
|
+
Use this skill when:
|
|
24
|
+
- You are asked to work in a realm
|
|
25
|
+
- You see a `REALM.md` file in a directory
|
|
26
|
+
- A user says "enter the realm" or "work in realm X"
|
|
27
|
+
|
|
28
|
+
## Entering a Realm
|
|
29
|
+
|
|
30
|
+
When entering a realm, load its full context first:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
realm prompt <name>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
This outputs the bundled INDEX.md + REALM.md + TOOLS.md + DIARY.md (if present) as a single
|
|
37
|
+
context block. Paste it into your session or use `--copy` to get it on the clipboard.
|
|
38
|
+
|
|
39
|
+
## Working in a Realm
|
|
40
|
+
|
|
41
|
+
1. Read REALM.md — understand the purpose and rules
|
|
42
|
+
2. Read TOOLS.md — know what tools are available
|
|
43
|
+
3. Read DIARY.md — understand recent progress and context
|
|
44
|
+
4. Do your work
|
|
45
|
+
5. **Before leaving:** if the realm has a DIARY.md, log a brief note:
|
|
46
|
+
```bash
|
|
47
|
+
realm diary <name> "Brief note: what you did, what's next"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Leaving / Sealing
|
|
51
|
+
|
|
52
|
+
- Leave freely — no cleanup required beyond a diary note
|
|
53
|
+
- Seal when the work is fully done: `realm seal <name>`
|
|
54
|
+
|
|
55
|
+
## Common Commands
|
|
56
|
+
|
|
57
|
+
| Command | Description |
|
|
58
|
+
|---------|-------------|
|
|
59
|
+
| `realm new <name>` | Create a new realm |
|
|
60
|
+
| `realm prompt <name>` | Load realm context |
|
|
61
|
+
| `realm ls` | List active realms |
|
|
62
|
+
| `realm diary <name> "msg"` | Log a diary entry |
|
|
63
|
+
| `realm seal <name>` | Seal a completed realm |
|
|
64
|
+
| `realm cd <name>` | Get realm directory path |
|
|
65
|
+
|
|
66
|
+
## Finding This Skill
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
realm skill
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Prints the absolute path to this file.
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
|
|
6
|
+
import { newCommand } from './commands/new.js';
|
|
7
|
+
import { lsCommand } from './commands/ls.js';
|
|
8
|
+
import { cdCommand } from './commands/cd.js';
|
|
9
|
+
import { promptCommand } from './commands/prompt.js';
|
|
10
|
+
import { sealCommand } from './commands/seal.js';
|
|
11
|
+
import { unsealCommand } from './commands/unseal.js';
|
|
12
|
+
import { rmCommand } from './commands/rm.js';
|
|
13
|
+
import { diaryCommand } from './commands/diary.js';
|
|
14
|
+
import { skillCommand } from './commands/skill.js';
|
|
15
|
+
|
|
16
|
+
const __dirname = join(fileURLToPath(import.meta.url), '..');
|
|
17
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
|
|
18
|
+
|
|
19
|
+
const program = new Command();
|
|
20
|
+
|
|
21
|
+
program
|
|
22
|
+
.name('realm')
|
|
23
|
+
.description('Agentless sandbox workspaces for AI agents')
|
|
24
|
+
.version(pkg.version);
|
|
25
|
+
|
|
26
|
+
newCommand(program);
|
|
27
|
+
lsCommand(program);
|
|
28
|
+
cdCommand(program);
|
|
29
|
+
promptCommand(program);
|
|
30
|
+
sealCommand(program);
|
|
31
|
+
unsealCommand(program);
|
|
32
|
+
rmCommand(program);
|
|
33
|
+
diaryCommand(program);
|
|
34
|
+
skillCommand(program);
|
|
35
|
+
|
|
36
|
+
program.parse();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { readRegistry, requireRealm } from '../registry.js';
|
|
2
|
+
|
|
3
|
+
export function cdCommand(program) {
|
|
4
|
+
program
|
|
5
|
+
.command('cd <name>')
|
|
6
|
+
.description('Print realm directory path (use: cd $(realm cd <name>))')
|
|
7
|
+
.action((name) => {
|
|
8
|
+
const registry = readRegistry();
|
|
9
|
+
const realm = requireRealm(registry, name);
|
|
10
|
+
process.stdout.write(realm.dir + '\n');
|
|
11
|
+
});
|
|
12
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, appendFileSync, renameSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
import { readRegistry, requireRealm } from '../registry.js';
|
|
5
|
+
|
|
6
|
+
function countLines(content) {
|
|
7
|
+
return content.split('\n').length;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function todayStr() {
|
|
11
|
+
return new Date().toISOString().slice(0, 10);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function nowStr() {
|
|
15
|
+
const d = new Date();
|
|
16
|
+
return d.toISOString().slice(0, 16).replace('T', ' ');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function diaryCommand(program) {
|
|
20
|
+
program
|
|
21
|
+
.command('diary <name> [message]')
|
|
22
|
+
.description('Append to or open DIARY.md')
|
|
23
|
+
.action((name, message) => {
|
|
24
|
+
const registry = readRegistry();
|
|
25
|
+
const realm = requireRealm(registry, name);
|
|
26
|
+
const diaryPath = join(realm.dir, 'DIARY.md');
|
|
27
|
+
|
|
28
|
+
if (!message) {
|
|
29
|
+
// Open in $EDITOR
|
|
30
|
+
const editor = process.env.EDITOR || 'vi';
|
|
31
|
+
if (!existsSync(diaryPath)) {
|
|
32
|
+
writeFileSync(diaryPath, `# Diary — ${name}\n\n`, 'utf8');
|
|
33
|
+
}
|
|
34
|
+
execSync(`${editor} "${diaryPath}"`, { stdio: 'inherit' });
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Create if missing
|
|
39
|
+
if (!existsSync(diaryPath)) {
|
|
40
|
+
writeFileSync(diaryPath, `# Diary — ${name}\n\n`, 'utf8');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const content = readFileSync(diaryPath, 'utf8');
|
|
44
|
+
|
|
45
|
+
// Rotate if > 200 lines
|
|
46
|
+
if (countLines(content) > 200) {
|
|
47
|
+
const archivePath = join(realm.dir, 'diaries', `${todayStr()}.md`);
|
|
48
|
+
renameSync(diaryPath, archivePath);
|
|
49
|
+
writeFileSync(diaryPath, `# Diary — ${name}\n\n`, 'utf8');
|
|
50
|
+
console.error(`Diary rotated to diaries/${todayStr()}.md`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
appendFileSync(diaryPath, `\n## ${nowStr()}\n${message}\n`, 'utf8');
|
|
54
|
+
console.log(`✓ Diary entry added.`);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { readRegistry } from '../registry.js';
|
|
3
|
+
|
|
4
|
+
export function lsCommand(program) {
|
|
5
|
+
program
|
|
6
|
+
.command('ls')
|
|
7
|
+
.description('List realms')
|
|
8
|
+
.option('--all', 'Include sealed realms')
|
|
9
|
+
.action((opts) => {
|
|
10
|
+
const registry = readRegistry();
|
|
11
|
+
const realms = Object.entries(registry.realms || {});
|
|
12
|
+
|
|
13
|
+
const filtered = opts.all
|
|
14
|
+
? realms
|
|
15
|
+
: realms.filter(([, r]) => r.status !== 'sealed');
|
|
16
|
+
|
|
17
|
+
if (filtered.length === 0) {
|
|
18
|
+
console.log('No realms found. Create one with: realm new <name>');
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const col1 = Math.max(4, ...filtered.map(([n]) => n.length));
|
|
23
|
+
const col2 = 8;
|
|
24
|
+
const col3 = Math.max(3, ...filtered.map(([, r]) => r.dir.length));
|
|
25
|
+
|
|
26
|
+
const header =
|
|
27
|
+
chalk.bold('NAME'.padEnd(col1)) + ' ' +
|
|
28
|
+
chalk.bold('STATUS'.padEnd(col2)) + ' ' +
|
|
29
|
+
chalk.bold('DIR');
|
|
30
|
+
console.log(header);
|
|
31
|
+
console.log('─'.repeat(col1 + col2 + col3 + 4));
|
|
32
|
+
|
|
33
|
+
for (const [name, realm] of filtered) {
|
|
34
|
+
const statusColor = realm.status === 'sealed' ? chalk.gray : chalk.green;
|
|
35
|
+
console.log(
|
|
36
|
+
name.padEnd(col1) + ' ' +
|
|
37
|
+
statusColor(realm.status.padEnd(col2)) + ' ' +
|
|
38
|
+
chalk.dim(realm.dir)
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { homedir } from 'os';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { readRegistry, addRealm } from '../registry.js';
|
|
4
|
+
import { scaffoldRealm } from '../scaffold.js';
|
|
5
|
+
|
|
6
|
+
export function newCommand(program) {
|
|
7
|
+
program
|
|
8
|
+
.command('new <name>')
|
|
9
|
+
.description('Create a new realm')
|
|
10
|
+
.option('--dir <path>', 'Custom directory (default: ~/realms/<name>)')
|
|
11
|
+
.option('--with-diary', 'Include a DIARY.md in the realm')
|
|
12
|
+
.action((name, opts) => {
|
|
13
|
+
const dir = opts.dir || join(homedir(), 'realms', name);
|
|
14
|
+
const registry = readRegistry();
|
|
15
|
+
|
|
16
|
+
if (registry.realms?.[name]) {
|
|
17
|
+
console.error(`Realm "${name}" already exists.`);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
scaffoldRealm(name, dir, opts.withDiary || false);
|
|
22
|
+
|
|
23
|
+
addRealm(registry, name, {
|
|
24
|
+
dir,
|
|
25
|
+
status: 'active',
|
|
26
|
+
created: new Date().toISOString(),
|
|
27
|
+
sealed_at: null,
|
|
28
|
+
description: '',
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
console.log(`✓ Realm "${name}" created at ${dir}`);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { writeFileSync } from 'fs';
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import { readRegistry, requireRealm } from '../registry.js';
|
|
4
|
+
import { buildPrompt, listDiaries } from '../prompt.js';
|
|
5
|
+
|
|
6
|
+
export function promptCommand(program) {
|
|
7
|
+
program
|
|
8
|
+
.command('prompt <name>')
|
|
9
|
+
.description('Build and output the realm context prompt')
|
|
10
|
+
.option('--memory', 'Include MEMORY.md if present')
|
|
11
|
+
.option('--diary <date>', 'Include a historical diary (YYYY-MM-DD)')
|
|
12
|
+
.option('--diaries-list', 'List available diary files')
|
|
13
|
+
.option('--copy', 'Copy output to clipboard')
|
|
14
|
+
.option('--out <file>', 'Write output to file')
|
|
15
|
+
.action((name, opts) => {
|
|
16
|
+
const registry = readRegistry();
|
|
17
|
+
const realm = requireRealm(registry, name);
|
|
18
|
+
|
|
19
|
+
if (opts.diariesList) {
|
|
20
|
+
const files = listDiaries(realm);
|
|
21
|
+
if (files.length === 0) {
|
|
22
|
+
console.log('No diary archives found.');
|
|
23
|
+
} else {
|
|
24
|
+
files.forEach(f => console.log(f));
|
|
25
|
+
}
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const output = buildPrompt(realm, {
|
|
30
|
+
memory: opts.memory,
|
|
31
|
+
diary: opts.diary,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (opts.out) {
|
|
35
|
+
writeFileSync(opts.out, output, 'utf8');
|
|
36
|
+
console.error(`Written to ${opts.out}`);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (opts.copy) {
|
|
41
|
+
const platform = process.platform;
|
|
42
|
+
let cmd;
|
|
43
|
+
if (platform === 'darwin') cmd = 'pbcopy';
|
|
44
|
+
else if (platform === 'win32') cmd = 'clip';
|
|
45
|
+
else cmd = 'xclip -selection clipboard 2>/dev/null || wl-copy';
|
|
46
|
+
execSync(cmd, { input: output });
|
|
47
|
+
console.error('Copied to clipboard.');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
process.stdout.write(output);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { rmSync, existsSync } from 'fs';
|
|
2
|
+
import { createInterface } from 'readline';
|
|
3
|
+
import { readRegistry, requireRealm, removeRealm } from '../registry.js';
|
|
4
|
+
|
|
5
|
+
function confirm(question) {
|
|
6
|
+
return new Promise((resolve) => {
|
|
7
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
8
|
+
rl.question(question, (answer) => {
|
|
9
|
+
rl.close();
|
|
10
|
+
resolve(answer.trim().toLowerCase() === 'y');
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function rmCommand(program) {
|
|
16
|
+
program
|
|
17
|
+
.command('rm <name>')
|
|
18
|
+
.description('Remove a realm from registry')
|
|
19
|
+
.option('--purge', 'Also delete the realm directory')
|
|
20
|
+
.option('--yes', 'Skip confirmation')
|
|
21
|
+
.action(async (name, opts) => {
|
|
22
|
+
const registry = readRegistry();
|
|
23
|
+
const realm = requireRealm(registry, name);
|
|
24
|
+
|
|
25
|
+
if (opts.purge && !opts.yes) {
|
|
26
|
+
const ok = await confirm(`This will delete ${realm.dir}. Continue? [y/N] `);
|
|
27
|
+
if (!ok) {
|
|
28
|
+
console.log('Aborted.');
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
removeRealm(registry, name);
|
|
34
|
+
|
|
35
|
+
if (opts.purge && existsSync(realm.dir)) {
|
|
36
|
+
rmSync(realm.dir, { recursive: true, force: true });
|
|
37
|
+
console.log(`✓ Realm "${name}" removed from registry and directory deleted.`);
|
|
38
|
+
} else {
|
|
39
|
+
console.log(`✓ Realm "${name}" removed from registry.`);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { readRegistry, requireRealm, updateRealm } from '../registry.js';
|
|
2
|
+
|
|
3
|
+
export function sealCommand(program) {
|
|
4
|
+
program
|
|
5
|
+
.command('seal <name>')
|
|
6
|
+
.description('Seal a realm (mark as done)')
|
|
7
|
+
.action((name) => {
|
|
8
|
+
const registry = readRegistry();
|
|
9
|
+
requireRealm(registry, name);
|
|
10
|
+
updateRealm(registry, name, {
|
|
11
|
+
status: 'sealed',
|
|
12
|
+
sealed_at: new Date().toISOString(),
|
|
13
|
+
});
|
|
14
|
+
console.log(`✓ Realm "${name}" sealed.`);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { join, dirname } from 'path';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
|
|
4
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
const SKILL_PATH = join(__dirname, '..', '..', 'skill', 'SKILL.md');
|
|
6
|
+
|
|
7
|
+
export function skillCommand(program) {
|
|
8
|
+
program
|
|
9
|
+
.command('skill')
|
|
10
|
+
.description('Print path to bundled SKILL.md')
|
|
11
|
+
.action(() => {
|
|
12
|
+
console.log(SKILL_PATH);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { readRegistry, requireRealm, updateRealm } from '../registry.js';
|
|
2
|
+
|
|
3
|
+
export function unsealCommand(program) {
|
|
4
|
+
program
|
|
5
|
+
.command('unseal <name>')
|
|
6
|
+
.description('Unseal a realm (reactivate)')
|
|
7
|
+
.action((name) => {
|
|
8
|
+
const registry = readRegistry();
|
|
9
|
+
requireRealm(registry, name);
|
|
10
|
+
updateRealm(registry, name, {
|
|
11
|
+
status: 'active',
|
|
12
|
+
sealed_at: null,
|
|
13
|
+
});
|
|
14
|
+
console.log(`✓ Realm "${name}" unsealed.`);
|
|
15
|
+
});
|
|
16
|
+
}
|
package/src/prompt.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { readFileSync, existsSync, readdirSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
|
|
4
|
+
function section(filename, content) {
|
|
5
|
+
return `=== ${filename} ===\n\n${content}\n\n`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function buildPrompt(realm, options = {}) {
|
|
9
|
+
const dir = realm.dir;
|
|
10
|
+
const parts = [];
|
|
11
|
+
|
|
12
|
+
for (const file of ['INDEX.md', 'REALM.md', 'TOOLS.md']) {
|
|
13
|
+
const p = join(dir, file);
|
|
14
|
+
if (existsSync(p)) {
|
|
15
|
+
parts.push(section(file, readFileSync(p, 'utf8')));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// DIARY.md — always include if exists (unless --diary flag overrides)
|
|
20
|
+
const diaryPath = join(dir, 'DIARY.md');
|
|
21
|
+
if (!options.diary && existsSync(diaryPath)) {
|
|
22
|
+
parts.push(section('DIARY.md', readFileSync(diaryPath, 'utf8')));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (options.memory) {
|
|
26
|
+
const memPath = join(dir, 'MEMORY.md');
|
|
27
|
+
if (existsSync(memPath)) {
|
|
28
|
+
parts.push(section('MEMORY.md', readFileSync(memPath, 'utf8')));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (options.diary) {
|
|
33
|
+
const histPath = join(dir, 'diaries', `${options.diary}.md`);
|
|
34
|
+
if (existsSync(histPath)) {
|
|
35
|
+
parts.push(section(`diaries/${options.diary}.md`, readFileSync(histPath, 'utf8')));
|
|
36
|
+
} else {
|
|
37
|
+
console.error(`Diary file not found: diaries/${options.diary}.md`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return parts.join('');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function listDiaries(realm) {
|
|
45
|
+
const dirPath = join(realm.dir, 'diaries');
|
|
46
|
+
if (!existsSync(dirPath)) return [];
|
|
47
|
+
return readdirSync(dirPath)
|
|
48
|
+
.filter(f => f.endsWith('.md') && f !== '.gitkeep')
|
|
49
|
+
.sort();
|
|
50
|
+
}
|
package/src/registry.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { readFileSync, existsSync, writeFileSync, renameSync, mkdirSync } from 'fs';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import yaml from 'js-yaml';
|
|
5
|
+
|
|
6
|
+
const REGISTRY_PATH = join(homedir(), '.realm.yml');
|
|
7
|
+
|
|
8
|
+
function emptyRegistry() {
|
|
9
|
+
return { version: 1, realms: {} };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function readRegistry() {
|
|
13
|
+
if (!existsSync(REGISTRY_PATH)) return emptyRegistry();
|
|
14
|
+
const content = readFileSync(REGISTRY_PATH, 'utf8');
|
|
15
|
+
return yaml.load(content) || emptyRegistry();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function writeRegistry(data) {
|
|
19
|
+
const tmp = REGISTRY_PATH + '.tmp';
|
|
20
|
+
writeFileSync(tmp, yaml.dump(data, { lineWidth: -1 }), 'utf8');
|
|
21
|
+
renameSync(tmp, REGISTRY_PATH);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function getRealm(registry, name) {
|
|
25
|
+
return registry.realms?.[name] ?? null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function requireRealm(registry, name) {
|
|
29
|
+
const realm = getRealm(registry, name);
|
|
30
|
+
if (!realm) {
|
|
31
|
+
console.error(`Realm "${name}" not found. Run: realm ls`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
return realm;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function addRealm(registry, name, data) {
|
|
38
|
+
registry.realms = registry.realms || {};
|
|
39
|
+
registry.realms[name] = data;
|
|
40
|
+
writeRegistry(registry);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function updateRealm(registry, name, updates) {
|
|
44
|
+
registry.realms[name] = { ...registry.realms[name], ...updates };
|
|
45
|
+
writeRegistry(registry);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function removeRealm(registry, name) {
|
|
49
|
+
delete registry.realms[name];
|
|
50
|
+
writeRegistry(registry);
|
|
51
|
+
}
|
package/src/scaffold.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync, existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const TEMPLATES_DIR = join(__dirname, '..', 'templates');
|
|
7
|
+
|
|
8
|
+
function readTemplate(name) {
|
|
9
|
+
return readFileSync(join(TEMPLATES_DIR, name), 'utf8');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function renderTemplate(content, vars) {
|
|
13
|
+
return content
|
|
14
|
+
.replace(/\{\{name\}\}/g, vars.name)
|
|
15
|
+
.replace(/\{\{created\}\}/g, vars.created)
|
|
16
|
+
.replace(/\{\{status\}\}/g, vars.status || 'active')
|
|
17
|
+
.replace(/\{\{dir\}\}/g, vars.dir);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function scaffoldRealm(name, dir, withDiary) {
|
|
21
|
+
mkdirSync(dir, { recursive: true });
|
|
22
|
+
mkdirSync(join(dir, 'diaries'), { recursive: true });
|
|
23
|
+
|
|
24
|
+
const now = new Date().toISOString();
|
|
25
|
+
const vars = { name, created: now, status: 'active', dir };
|
|
26
|
+
|
|
27
|
+
writeFileSync(join(dir, 'INDEX.md'), renderTemplate(readTemplate('INDEX.md'), vars), 'utf8');
|
|
28
|
+
writeFileSync(join(dir, 'REALM.md'), renderTemplate(readTemplate('REALM.md'), vars), 'utf8');
|
|
29
|
+
writeFileSync(join(dir, 'TOOLS.md'), renderTemplate(readTemplate('TOOLS.md'), vars), 'utf8');
|
|
30
|
+
|
|
31
|
+
if (withDiary) {
|
|
32
|
+
writeFileSync(join(dir, 'DIARY.md'), renderTemplate(readTemplate('DIARY.md'), vars), 'utf8');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// gitkeep for diaries
|
|
36
|
+
writeFileSync(join(dir, 'diaries', '.gitkeep'), '', 'utf8');
|
|
37
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Diary — {{name}}
|
|
2
|
+
|
|
3
|
+
This is the rolling work log for realm **{{name}}**.
|
|
4
|
+
|
|
5
|
+
Entries are added via:
|
|
6
|
+
```bash
|
|
7
|
+
realm diary {{name}} "Your note here"
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Or open in your editor:
|
|
11
|
+
```bash
|
|
12
|
+
realm diary {{name}}
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
When this file exceeds 200 lines, it is automatically archived to `diaries/<date>.md`
|
|
16
|
+
and a fresh file is started.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Index — {{name}}
|
|
2
|
+
|
|
3
|
+
> **This is a realm** — an agentless sandbox workspace. No agent permanently guards this space.
|
|
4
|
+
> Any agent may enter, work, and leave. When the work is done, seal the realm.
|
|
5
|
+
|
|
6
|
+
## Read Order
|
|
7
|
+
|
|
8
|
+
When entering this realm, load files in this order:
|
|
9
|
+
|
|
10
|
+
1. **REALM.md** — Purpose, rules, and context for this realm
|
|
11
|
+
2. **TOOLS.md** — Tool checklist and constraints for work in this realm
|
|
12
|
+
3. **DIARY.md** — Rolling work log (if present)
|
|
13
|
+
4. **MEMORY.md** — Accumulated knowledge (if present)
|
|
14
|
+
|
|
15
|
+
## Quick Load
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
realm prompt {{name}}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
This command bundles the above files into a single context block ready to paste into an agent session.
|
|
22
|
+
|
|
23
|
+
## About Realms
|
|
24
|
+
|
|
25
|
+
Unlike an agent workspace (where a dedicated agent is always watching), realms are **agentless**.
|
|
26
|
+
They are shared collaboration spaces — any AI agent or human can enter, contribute, and leave.
|
|
27
|
+
|
|
28
|
+
Use `realm seal {{name}}` when the work is done.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: {{name}}
|
|
3
|
+
created: {{created}}
|
|
4
|
+
status: {{status}}
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Realm: {{name}}
|
|
8
|
+
|
|
9
|
+
## Purpose
|
|
10
|
+
|
|
11
|
+
<!-- Describe the purpose of this realm. What problem does it solve? What work happens here? -->
|
|
12
|
+
|
|
13
|
+
## Rules
|
|
14
|
+
|
|
15
|
+
- Any agent may enter and contribute — coordinate via DIARY.md
|
|
16
|
+
- Keep changes focused and scoped to this realm's purpose
|
|
17
|
+
- Document significant decisions and findings in DIARY.md
|
|
18
|
+
- Don't leave this realm in a broken state
|
|
19
|
+
|
|
20
|
+
## Who Can Enter
|
|
21
|
+
|
|
22
|
+
<!-- List which agents or humans are expected to work in this realm -->
|
|
23
|
+
|
|
24
|
+
- Any agent with the `agentrealm` skill installed
|
|
25
|
+
|
|
26
|
+
## Context
|
|
27
|
+
|
|
28
|
+
<!-- Additional context, links, references, or background information -->
|
|
29
|
+
|
|
30
|
+
## When to Seal
|
|
31
|
+
|
|
32
|
+
Seal this realm when:
|
|
33
|
+
- The primary objective is complete
|
|
34
|
+
- The work has been merged/shipped/delivered
|
|
35
|
+
- No further active work is expected
|
|
36
|
+
|
|
37
|
+
To seal: `realm seal {{name}}`
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Tools — {{name}}
|
|
2
|
+
|
|
3
|
+
This file documents the tools, CLIs, and APIs available in this realm.
|
|
4
|
+
Check items you've confirmed work, and add notes about limitations.
|
|
5
|
+
|
|
6
|
+
## General
|
|
7
|
+
|
|
8
|
+
- [ ] `realm prompt {{name}}` — load full context into agent session
|
|
9
|
+
- [ ] `realm diary {{name}} "message"` — log a diary entry
|
|
10
|
+
- [ ] `realm seal {{name}}` — seal when done
|
|
11
|
+
|
|
12
|
+
## Filesystem & Shell
|
|
13
|
+
|
|
14
|
+
- [ ] `read` / `write` / `edit` — file operations
|
|
15
|
+
- [ ] `exec` — shell commands
|
|
16
|
+
- [ ] `find`, `grep`, `rg` — search
|
|
17
|
+
|
|
18
|
+
## Version Control
|
|
19
|
+
|
|
20
|
+
- [ ] `git` — version control
|
|
21
|
+
- [ ] `gh` — GitHub CLI
|
|
22
|
+
|
|
23
|
+
## Language / Runtime
|
|
24
|
+
|
|
25
|
+
<!-- Add language-specific tools here -->
|
|
26
|
+
- [ ] Node.js / npm
|
|
27
|
+
- [ ] Python / pip
|
|
28
|
+
|
|
29
|
+
## External APIs
|
|
30
|
+
|
|
31
|
+
<!-- Document any external APIs or services used in this realm -->
|
|
32
|
+
|
|
33
|
+
## Notes
|
|
34
|
+
|
|
35
|
+
<!-- Tool-specific notes, limitations, credentials info -->
|
|
File without changes
|
package/bin.js
DELETED