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 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
- # realm
1
+ # agentrealm
2
2
 
3
- Agentless sandbox workspaces. Placeholder full release coming soon.
3
+ **Agentless sandbox workspaces for AI agents**
4
+
5
+ [![npm](https://img.shields.io/npm/v/agentrealm.svg)](https://www.npmjs.com/package/agentrealm)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
+ [![Node.js](https://img.shields.io/node/v/agentrealm)](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).
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../src/cli.js';
package/package.json CHANGED
@@ -1,22 +1,46 @@
1
1
  {
2
2
  "name": "agentrealm",
3
- "version": "0.0.1",
4
- "description": "Realm — agentless sandbox workspaces (placeholder, full release coming)",
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": "./bin.js"
10
+ "realm": "bin/agentrealm.js",
11
+ "agentrealm": "bin/agentrealm.js",
12
+ "agent-realm": "bin/agentrealm.js"
7
13
  },
8
- "main": "bin.js",
9
- "license": "MIT",
10
- "author": "exisz",
11
- "repository": {
12
- "type": "git",
13
- "url": "https://github.com/exisz/realm.git"
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
- "workspace",
19
- "agent",
20
- "ai"
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
+ }
@@ -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
+ }
@@ -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
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- console.log("realm — placeholder. Full release coming soon. https://github.com/exisz/realm");