@openreef/cli 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 +187 -0
- package/dist/commands/init.d.ts +8 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +61 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/inspect.d.ts +2 -0
- package/dist/commands/inspect.d.ts.map +1 -0
- package/dist/commands/inspect.js +120 -0
- package/dist/commands/inspect.js.map +1 -0
- package/dist/commands/pack.d.ts +5 -0
- package/dist/commands/pack.d.ts.map +1 -0
- package/dist/commands/pack.js +43 -0
- package/dist/commands/pack.js.map +1 -0
- package/dist/commands/validate.d.ts +7 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +77 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/core/manifest-loader.d.ts +7 -0
- package/dist/core/manifest-loader.d.ts.map +1 -0
- package/dist/core/manifest-loader.js +29 -0
- package/dist/core/manifest-loader.js.map +1 -0
- package/dist/core/packer.d.ts +6 -0
- package/dist/core/packer.d.ts.map +1 -0
- package/dist/core/packer.js +26 -0
- package/dist/core/packer.js.map +1 -0
- package/dist/core/schema-validator.d.ts +3 -0
- package/dist/core/schema-validator.d.ts.map +1 -0
- package/dist/core/schema-validator.js +35 -0
- package/dist/core/schema-validator.js.map +1 -0
- package/dist/core/structural-validator.d.ts +4 -0
- package/dist/core/structural-validator.d.ts.map +1 -0
- package/dist/core/structural-validator.js +134 -0
- package/dist/core/structural-validator.js.map +1 -0
- package/dist/core/template-interpolator.d.ts +2 -0
- package/dist/core/template-interpolator.d.ts.map +1 -0
- package/dist/core/template-interpolator.js +11 -0
- package/dist/core/template-interpolator.js.map +1 -0
- package/dist/core/variable-resolver.d.ts +12 -0
- package/dist/core/variable-resolver.d.ts.map +1 -0
- package/dist/core/variable-resolver.js +38 -0
- package/dist/core/variable-resolver.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +69 -0
- package/dist/index.js.map +1 -0
- package/dist/types/manifest.d.ts +75 -0
- package/dist/types/manifest.d.ts.map +1 -0
- package/dist/types/manifest.js +2 -0
- package/dist/types/manifest.js.map +1 -0
- package/dist/types/platform.d.ts +29 -0
- package/dist/types/platform.d.ts.map +1 -0
- package/dist/types/platform.js +2 -0
- package/dist/types/platform.js.map +1 -0
- package/dist/types/validation.d.ts +12 -0
- package/dist/types/validation.d.ts.map +1 -0
- package/dist/types/validation.js +2 -0
- package/dist/types/validation.js.map +1 -0
- package/dist/utils/fs.d.ts +5 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +60 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/output.d.ts +11 -0
- package/dist/utils/output.d.ts.map +1 -0
- package/dist/utils/output.js +31 -0
- package/dist/utils/output.js.map +1 -0
- package/dist/utils/paths.d.ts +3 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +14 -0
- package/dist/utils/paths.js.map +1 -0
- package/package.json +53 -0
- package/schema/reef.schema.json +306 -0
- package/template/.env.example +3 -0
- package/template/README.md +48 -0
- package/template/agents/manager/IDENTITY.md +6 -0
- package/template/agents/manager/SOUL.md +29 -0
- package/template/agents/manager/knowledge/dynamic/.gitkeep +0 -0
- package/template/agents/manager/knowledge/static/project-brief.md +17 -0
- package/template/agents/researcher/SOUL.md +30 -0
- package/template/agents/researcher/knowledge/dynamic/.gitkeep +0 -0
- package/template/agents/researcher/knowledge/static/.gitkeep +0 -0
- package/template/reef.json +88 -0
- package/template/reef.lock.json +4 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OpenReef Contributors
|
|
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
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# OpenReef
|
|
2
|
+
|
|
3
|
+
**The open formation format for multi-agent teams.**
|
|
4
|
+
|
|
5
|
+
OpenReef is a packaging format and toolchain for deploying pre-configured AI agent teams ("formations") to [OpenClaw](https://github.com/openclaw/openclaw). Define your agents, wire their communication, declare dependencies, and ship the whole thing as a single portable package.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
reef install ./founder-ops
|
|
9
|
+
# → 3 agents deployed, 4 bindings wired, 2 cron jobs scheduled ✓
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Why
|
|
13
|
+
|
|
14
|
+
Setting up a multi-agent team on OpenClaw today means manually creating each agent, writing personality files, wiring inter-agent communication, configuring tools, and setting up cron jobs. If you want to share that setup or reproduce it, you're copying workspace directories and hoping nothing breaks.
|
|
15
|
+
|
|
16
|
+
OpenReef gives agent teams a **package format** — like Docker Compose for AI agents. One `reef.json` manifest declares everything. Install it, configure your variables, and the whole team spins up.
|
|
17
|
+
|
|
18
|
+
## What's a Formation?
|
|
19
|
+
|
|
20
|
+
A formation is a directory containing everything needed to deploy a team of AI agents:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
my-formation/
|
|
24
|
+
├── reef.json # Manifest — agents, wiring, dependencies, variables
|
|
25
|
+
├── reef.lock.json # Pinned skill versions with integrity digests
|
|
26
|
+
├── .env.example # Quick setup template
|
|
27
|
+
├── README.md # Human-readable overview
|
|
28
|
+
└── agents/
|
|
29
|
+
├── manager/
|
|
30
|
+
│ ├── SOUL.md # Personality and behavior
|
|
31
|
+
│ ├── IDENTITY.md # Identity metadata
|
|
32
|
+
│ └── knowledge/
|
|
33
|
+
│ ├── static/ # Reference material (overwritten on update)
|
|
34
|
+
│ └── dynamic/ # Agent-written data (preserved on update)
|
|
35
|
+
└── researcher/
|
|
36
|
+
├── SOUL.md
|
|
37
|
+
└── knowledge/
|
|
38
|
+
├── static/
|
|
39
|
+
└── dynamic/
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Formation Types
|
|
43
|
+
|
|
44
|
+
| Type | Agents | Use Case |
|
|
45
|
+
|------|--------|----------|
|
|
46
|
+
| `solo` | 1 | Single agent with curated personality, tools, and cron |
|
|
47
|
+
| `team` | 2–5 | Defined roles with explicit communication topology |
|
|
48
|
+
| `swarm` | 6+ | Large-scale or dynamically-spawning agent patterns |
|
|
49
|
+
|
|
50
|
+
## Quick Start
|
|
51
|
+
|
|
52
|
+
### Install the CLI
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm install -g @openreef/cli
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Create a Formation
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
reef init my-formation
|
|
62
|
+
cd my-formation
|
|
63
|
+
# Edit reef.json, write your SOUL.md files
|
|
64
|
+
reef validate . # Check everything is wired correctly
|
|
65
|
+
reef pack . # Package for distribution
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Deploy a Formation
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
cp .env.example .env # Fill in your variables
|
|
72
|
+
reef install . # Deploy to OpenClaw
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Use the Starter Template
|
|
76
|
+
|
|
77
|
+
The [`template/`](template/) directory contains a ready-to-customize two-agent formation (manager + researcher) with example variables, inter-agent wiring, and knowledge directories.
|
|
78
|
+
|
|
79
|
+
## The Manifest (`reef.json`)
|
|
80
|
+
|
|
81
|
+
A single JSON file declares your entire formation:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"reef": "1.0",
|
|
86
|
+
"type": "team",
|
|
87
|
+
"name": "my-formation",
|
|
88
|
+
"version": "1.0.0",
|
|
89
|
+
"description": "A research team with a manager and researcher",
|
|
90
|
+
"namespace": "my-team",
|
|
91
|
+
"variables": {
|
|
92
|
+
"OPENAI_API_KEY": {
|
|
93
|
+
"type": "string",
|
|
94
|
+
"required": true,
|
|
95
|
+
"sensitive": true
|
|
96
|
+
},
|
|
97
|
+
"MISSION_GOAL": {
|
|
98
|
+
"type": "string",
|
|
99
|
+
"default": "Research"
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"agents": {
|
|
103
|
+
"manager": {
|
|
104
|
+
"source": "agents/manager",
|
|
105
|
+
"description": "Coordinates the team and delegates tasks",
|
|
106
|
+
"role": "coordinator",
|
|
107
|
+
"model": "gpt-4o",
|
|
108
|
+
"tools": { "allow": ["web-search", "file-read"] }
|
|
109
|
+
},
|
|
110
|
+
"researcher": {
|
|
111
|
+
"source": "agents/researcher",
|
|
112
|
+
"description": "Conducts deep research on assigned topics",
|
|
113
|
+
"role": "researcher",
|
|
114
|
+
"model": "gpt-4o",
|
|
115
|
+
"tools": { "allow": ["web-search"] }
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
"agentToAgent": {
|
|
119
|
+
"manager": ["researcher"],
|
|
120
|
+
"researcher": ["manager"]
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Variables support `{{VARIABLE_NAME}}` interpolation across all text files. Sensitive values are never stored in plaintext.
|
|
126
|
+
|
|
127
|
+
## CLI
|
|
128
|
+
|
|
129
|
+
| Command | Description |
|
|
130
|
+
|---------|-------------|
|
|
131
|
+
| `reef init [name]` | Scaffold a new formation from the bundled template |
|
|
132
|
+
| `reef inspect <path>` | Parse reef.json and pretty-print formation contents |
|
|
133
|
+
| `reef validate <path>` | Run schema and structural validation on a formation |
|
|
134
|
+
| `reef pack <path>` | Package a formation into a `.tar.gz` archive |
|
|
135
|
+
|
|
136
|
+
**Planned** (requires running OpenClaw):
|
|
137
|
+
|
|
138
|
+
| Command | Description |
|
|
139
|
+
|---------|-------------|
|
|
140
|
+
| `reef install <path>` | Deploy a formation to OpenClaw |
|
|
141
|
+
| `reef update <path>` | Update a deployed formation (preserves agent-written data) |
|
|
142
|
+
| `reef uninstall <name>` | Remove a formation and all its resources |
|
|
143
|
+
| `reef export <namespace>` | Snapshot running agents into a formation package |
|
|
144
|
+
| `reef lock` | Pin dependency versions with integrity digests |
|
|
145
|
+
| `reef list` | List installed formations |
|
|
146
|
+
| `reef status <name>` | Show status of a deployed formation |
|
|
147
|
+
|
|
148
|
+
## Security
|
|
149
|
+
|
|
150
|
+
- **No code execution.** Formations are data files only (JSON + Markdown). Nothing runs during install.
|
|
151
|
+
- **Pinned dependencies.** `reef.lock.json` pins exact skill versions with SHA-256 integrity digests.
|
|
152
|
+
- **Sensitive variable handling.** Values marked `sensitive` are never written to disk in plaintext.
|
|
153
|
+
- **Pre-install audit.** `reef inspect` shows exactly what will be deployed before you install.
|
|
154
|
+
- **Per-agent sandboxing.** Skills run within OpenClaw's agent sandbox boundaries.
|
|
155
|
+
|
|
156
|
+
## Project Structure
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
openreef/
|
|
160
|
+
├── SPEC.md # Full format specification
|
|
161
|
+
├── package.json # @openreef/cli package
|
|
162
|
+
├── schema/
|
|
163
|
+
│ └── reef.schema.json # JSON Schema for reef.json validation
|
|
164
|
+
├── template/ # Starter formation template
|
|
165
|
+
│ ├── reef.json
|
|
166
|
+
│ ├── reef.lock.json
|
|
167
|
+
│ ├── .env.example
|
|
168
|
+
│ ├── README.md
|
|
169
|
+
│ └── agents/
|
|
170
|
+
│ ├── manager/
|
|
171
|
+
│ └── researcher/
|
|
172
|
+
├── src/ # CLI source (TypeScript, ESM)
|
|
173
|
+
│ ├── index.ts # Entry point — shebang + commander setup
|
|
174
|
+
│ ├── types/ # ReefManifest, PlatformAdapter, ValidationResult
|
|
175
|
+
│ ├── core/ # Manifest loader, schema/structural validators, packer
|
|
176
|
+
│ ├── commands/ # init, inspect, validate, pack
|
|
177
|
+
│ └── utils/ # Path resolution, chalk helpers, fs utilities
|
|
178
|
+
└── tests/ # Vitest — unit, integration, and type drift tests
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
- **[`SPEC.md`](SPEC.md)** — The complete formation format specification: manifest schema, inter-agent communication, variable interpolation, install flow, CLI commands, security model, and update/uninstall behavior.
|
|
182
|
+
- **[`schema/reef.schema.json`](schema/reef.schema.json)** — JSON Schema (draft 2020-12) for validating `reef.json` manifests.
|
|
183
|
+
- **[`template/`](template/)** — A working starter formation you can copy and customize.
|
|
184
|
+
|
|
185
|
+
## License
|
|
186
|
+
|
|
187
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACjC,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAqBD,wBAAsB,IAAI,CACxB,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,IAAI,CAAC,CA4Cf"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { resolve, join } from 'node:path';
|
|
2
|
+
import { writeFile, readFile, access } from 'node:fs/promises';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { copyDir } from '../utils/fs.js';
|
|
5
|
+
import { getTemplatePath } from '../utils/paths.js';
|
|
6
|
+
import { icons } from '../utils/output.js';
|
|
7
|
+
function generateEnvExample(variables) {
|
|
8
|
+
const lines = ['# OpenReef Formation Variables'];
|
|
9
|
+
for (const [name, config] of Object.entries(variables)) {
|
|
10
|
+
if (config.description) {
|
|
11
|
+
lines.push(`# ${config.description}`);
|
|
12
|
+
}
|
|
13
|
+
if (config.sensitive) {
|
|
14
|
+
lines.push(`${name}=`);
|
|
15
|
+
}
|
|
16
|
+
else if (config.default !== undefined) {
|
|
17
|
+
lines.push(`${name}="${String(config.default)}"`);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
lines.push(`${name}=`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return lines.join('\n') + '\n';
|
|
24
|
+
}
|
|
25
|
+
export async function init(nameArg, options = {}) {
|
|
26
|
+
const name = nameArg ?? options.name ?? 'my-formation';
|
|
27
|
+
const namespace = options.namespace ?? name;
|
|
28
|
+
const type = options.type ?? 'team';
|
|
29
|
+
const targetDir = resolve(name);
|
|
30
|
+
// Check if directory already exists
|
|
31
|
+
try {
|
|
32
|
+
await access(targetDir);
|
|
33
|
+
console.error(`${icons.error} Directory "${name}" already exists. Choose a different name or remove it first.`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// Directory doesn't exist — good
|
|
38
|
+
}
|
|
39
|
+
// Copy template, excluding .env.example (we generate it fresh)
|
|
40
|
+
const templateDir = getTemplatePath();
|
|
41
|
+
await copyDir(templateDir, targetDir, ['.env.example']);
|
|
42
|
+
// Read and update reef.json
|
|
43
|
+
const manifestPath = join(targetDir, 'reef.json');
|
|
44
|
+
const raw = await readFile(manifestPath, 'utf-8');
|
|
45
|
+
const manifest = JSON.parse(raw);
|
|
46
|
+
manifest.name = name;
|
|
47
|
+
manifest.namespace = namespace;
|
|
48
|
+
manifest.type = type;
|
|
49
|
+
await writeFile(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
50
|
+
// Generate .env.example from variables
|
|
51
|
+
if (manifest.variables && Object.keys(manifest.variables).length > 0) {
|
|
52
|
+
const envContent = generateEnvExample(manifest.variables);
|
|
53
|
+
await writeFile(join(targetDir, '.env.example'), envContent);
|
|
54
|
+
}
|
|
55
|
+
console.log(`${icons.success} Formation ${chalk.bold(name)} created!\n`);
|
|
56
|
+
console.log('Next steps:');
|
|
57
|
+
console.log(` ${chalk.cyan(`cd ${name}`)}`);
|
|
58
|
+
console.log(` ${chalk.dim('# edit .env and customize reef.json')}`);
|
|
59
|
+
console.log(` ${chalk.cyan('reef validate .')}`);
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspect.d.ts","sourceRoot":"","sources":["../../src/commands/inspect.ts"],"names":[],"mappings":"AAMA,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgIzD"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { loadManifest } from '../core/manifest-loader.js';
|
|
4
|
+
import { validateSchema } from '../core/schema-validator.js';
|
|
5
|
+
import { header, label, value, table, icons } from '../utils/output.js';
|
|
6
|
+
export async function inspect(path) {
|
|
7
|
+
const formationDir = resolve(path);
|
|
8
|
+
const manifest = await loadManifest(formationDir);
|
|
9
|
+
// Quick schema check
|
|
10
|
+
const schemaResult = await validateSchema(manifest);
|
|
11
|
+
if (!schemaResult.valid) {
|
|
12
|
+
console.log(`${icons.warning} Schema issues found — run ${chalk.bold('reef validate')} for details\n`);
|
|
13
|
+
}
|
|
14
|
+
// Metadata header
|
|
15
|
+
console.log(header('Formation'));
|
|
16
|
+
console.log(table([
|
|
17
|
+
[label('Name:'), value(manifest.name)],
|
|
18
|
+
[label('Version:'), value(manifest.version)],
|
|
19
|
+
[label('Type:'), value(manifest.type)],
|
|
20
|
+
[label('Namespace:'), value(manifest.namespace)],
|
|
21
|
+
[label('Description:'), manifest.description],
|
|
22
|
+
...(manifest.author
|
|
23
|
+
? [[label('Author:'), manifest.author]]
|
|
24
|
+
: []),
|
|
25
|
+
...(manifest.license
|
|
26
|
+
? [[label('License:'), manifest.license]]
|
|
27
|
+
: []),
|
|
28
|
+
]));
|
|
29
|
+
// Compatibility
|
|
30
|
+
if (manifest.compatibility?.openclaw) {
|
|
31
|
+
console.log(`\n${header('Compatibility')}`);
|
|
32
|
+
console.log(` OpenClaw ${value(manifest.compatibility.openclaw)}`);
|
|
33
|
+
}
|
|
34
|
+
// Agents table
|
|
35
|
+
console.log(`\n${header('Agents')}`);
|
|
36
|
+
const agentRows = [
|
|
37
|
+
[chalk.dim('Slug'), chalk.dim('Role'), chalk.dim('Model'), chalk.dim('Description')],
|
|
38
|
+
];
|
|
39
|
+
for (const [slug, agent] of Object.entries(manifest.agents)) {
|
|
40
|
+
agentRows.push([
|
|
41
|
+
value(slug),
|
|
42
|
+
agent.role ?? '-',
|
|
43
|
+
agent.model ?? '-',
|
|
44
|
+
agent.description,
|
|
45
|
+
]);
|
|
46
|
+
}
|
|
47
|
+
console.log(table(agentRows));
|
|
48
|
+
// Variables table
|
|
49
|
+
if (manifest.variables && Object.keys(manifest.variables).length > 0) {
|
|
50
|
+
console.log(`\n${header('Variables')}`);
|
|
51
|
+
const varRows = [
|
|
52
|
+
[chalk.dim('Name'), chalk.dim('Type'), chalk.dim('Required'), chalk.dim('Default'), chalk.dim('Description')],
|
|
53
|
+
];
|
|
54
|
+
for (const [name, v] of Object.entries(manifest.variables)) {
|
|
55
|
+
varRows.push([
|
|
56
|
+
value(name),
|
|
57
|
+
v.type,
|
|
58
|
+
v.required ? 'yes' : 'no',
|
|
59
|
+
v.sensitive ? chalk.dim('[sensitive]') : v.default !== undefined ? String(v.default) : '-',
|
|
60
|
+
v.description ?? '-',
|
|
61
|
+
]);
|
|
62
|
+
}
|
|
63
|
+
console.log(table(varRows));
|
|
64
|
+
}
|
|
65
|
+
// Topology arrows
|
|
66
|
+
if (manifest.agentToAgent && Object.keys(manifest.agentToAgent).length > 0) {
|
|
67
|
+
console.log(`\n${header('Communication Topology')}`);
|
|
68
|
+
for (const [source, targets] of Object.entries(manifest.agentToAgent)) {
|
|
69
|
+
for (const target of targets) {
|
|
70
|
+
console.log(` ${value(source)} ${chalk.dim('\u2192')} ${value(target)}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Bindings
|
|
75
|
+
if (manifest.bindings && manifest.bindings.length > 0) {
|
|
76
|
+
console.log(`\n${header('Bindings')}`);
|
|
77
|
+
const bindingRows = [
|
|
78
|
+
[chalk.dim('Channel'), chalk.dim('Agent'), chalk.dim('Direction')],
|
|
79
|
+
];
|
|
80
|
+
for (const b of manifest.bindings) {
|
|
81
|
+
bindingRows.push([value(b.channel), value(b.agent), b.direction ?? 'bidirectional']);
|
|
82
|
+
}
|
|
83
|
+
console.log(table(bindingRows));
|
|
84
|
+
}
|
|
85
|
+
// Cron
|
|
86
|
+
if (manifest.cron && manifest.cron.length > 0) {
|
|
87
|
+
console.log(`\n${header('Cron Jobs')}`);
|
|
88
|
+
const cronRows = [
|
|
89
|
+
[chalk.dim('Schedule'), chalk.dim('Agent'), chalk.dim('Prompt')],
|
|
90
|
+
];
|
|
91
|
+
for (const job of manifest.cron) {
|
|
92
|
+
const tz = job.timezone ? ` (${job.timezone})` : '';
|
|
93
|
+
cronRows.push([
|
|
94
|
+
value(job.schedule) + tz,
|
|
95
|
+
value(job.agent),
|
|
96
|
+
job.prompt.length > 60 ? job.prompt.slice(0, 57) + '...' : job.prompt,
|
|
97
|
+
]);
|
|
98
|
+
}
|
|
99
|
+
console.log(table(cronRows));
|
|
100
|
+
}
|
|
101
|
+
// Dependencies
|
|
102
|
+
if (manifest.dependencies) {
|
|
103
|
+
const skills = manifest.dependencies.skills;
|
|
104
|
+
const services = manifest.dependencies.services;
|
|
105
|
+
if (skills && Object.keys(skills).length > 0) {
|
|
106
|
+
console.log(`\n${header('Skills')}`);
|
|
107
|
+
for (const [name, version] of Object.entries(skills)) {
|
|
108
|
+
console.log(` ${value(name)} ${chalk.dim(version)}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (services && services.length > 0) {
|
|
112
|
+
console.log(`\n${header('Services')}`);
|
|
113
|
+
for (const svc of services) {
|
|
114
|
+
const req = svc.required !== false ? chalk.red('required') : chalk.dim('optional');
|
|
115
|
+
console.log(` ${value(svc.name)} ${req}${svc.url ? chalk.dim(` (${svc.url})`) : ''}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=inspect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspect.js","sourceRoot":"","sources":["../../src/commands/inspect.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pack.d.ts","sourceRoot":"","sources":["../../src/commands/pack.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAQD,wBAAsB,IAAI,CACxB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,IAAI,CAAC,CAoCf"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { loadManifest } from '../core/manifest-loader.js';
|
|
5
|
+
import { validate } from './validate.js';
|
|
6
|
+
import { pack as packFormation } from '../core/packer.js';
|
|
7
|
+
import { icons } from '../utils/output.js';
|
|
8
|
+
function formatSize(bytes) {
|
|
9
|
+
if (bytes < 1024)
|
|
10
|
+
return `${bytes} B`;
|
|
11
|
+
if (bytes < 1024 * 1024)
|
|
12
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
13
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
14
|
+
}
|
|
15
|
+
export async function pack(path, options = {}) {
|
|
16
|
+
const formationDir = resolve(path);
|
|
17
|
+
// Validate first
|
|
18
|
+
const result = await validate(formationDir, { quiet: true });
|
|
19
|
+
if (!result.valid) {
|
|
20
|
+
console.error(`${icons.error} Formation has validation errors. Fix them before packing.\n`);
|
|
21
|
+
// Print errors
|
|
22
|
+
for (const issue of result.issues) {
|
|
23
|
+
if (issue.severity === 'error') {
|
|
24
|
+
console.error(` ${icons.error} ${issue.message}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
const manifest = await loadManifest(formationDir);
|
|
30
|
+
const spinner = ora('Packing formation...').start();
|
|
31
|
+
try {
|
|
32
|
+
const outputDir = options.output ? resolve(options.output) : undefined;
|
|
33
|
+
const { outputPath, size } = await packFormation(formationDir, manifest.name, manifest.version, outputDir);
|
|
34
|
+
spinner.succeed('Formation packed');
|
|
35
|
+
console.log(` ${chalk.dim('Output:')} ${chalk.cyan(outputPath)}`);
|
|
36
|
+
console.log(` ${chalk.dim('Size:')} ${formatSize(size)}`);
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
spinner.fail('Failed to pack formation');
|
|
40
|
+
throw err;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=pack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pack.js","sourceRoot":"","sources":["../../src/commands/pack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,IAAI,IAAI,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAM3C,SAAS,UAAU,CAAC,KAAa;IAC/B,IAAI,KAAK,GAAG,IAAI;QAAE,OAAO,GAAG,KAAK,IAAI,CAAC;IACtC,IAAI,KAAK,GAAG,IAAI,GAAG,IAAI;QAAE,OAAO,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;IAClE,OAAO,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;AACpD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CACxB,IAAY,EACZ,UAAuB,EAAE;IAEzB,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnC,iBAAiB;IACjB,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,YAAY,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,KAAK,8DAA8D,CAAC,CAAC;QAC5F,eAAe;QACf,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAClC,IAAI,KAAK,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;gBAC/B,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YACrD,CAAC;QACH,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,YAAY,CAAC,CAAC;IAElD,MAAM,OAAO,GAAG,GAAG,CAAC,sBAAsB,CAAC,CAAC,KAAK,EAAE,CAAC;IAEpD,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACvE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,GAAG,MAAM,aAAa,CAC9C,YAAY,EACZ,QAAQ,CAAC,IAAI,EACb,QAAQ,CAAC,OAAO,EAChB,SAAS,CACV,CAAC;QAEF,OAAO,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;QACpC,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QACnE,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC/D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;QACzC,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ValidationResult } from '../types/validation.js';
|
|
2
|
+
export interface ValidateOptions {
|
|
3
|
+
quiet?: boolean;
|
|
4
|
+
json?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare function validate(path: string, options?: ValidateOptions): Promise<ValidationResult>;
|
|
7
|
+
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAmB,MAAM,wBAAwB,CAAC;AAEhF,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAqBD,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,gBAAgB,CAAC,CAwD3B"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { loadManifest, ManifestLoadError } from '../core/manifest-loader.js';
|
|
3
|
+
import { validateSchema } from '../core/schema-validator.js';
|
|
4
|
+
import { validateStructure } from '../core/structural-validator.js';
|
|
5
|
+
import { icons } from '../utils/output.js';
|
|
6
|
+
function mergeResults(...results) {
|
|
7
|
+
const issues = results.flatMap((r) => r.issues);
|
|
8
|
+
return {
|
|
9
|
+
valid: issues.every((i) => i.severity !== 'error'),
|
|
10
|
+
issues,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function severityIcon(severity) {
|
|
14
|
+
switch (severity) {
|
|
15
|
+
case 'error':
|
|
16
|
+
return icons.error;
|
|
17
|
+
case 'warning':
|
|
18
|
+
return icons.warning;
|
|
19
|
+
case 'info':
|
|
20
|
+
return icons.info;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export async function validate(path, options = {}) {
|
|
24
|
+
const formationDir = resolve(path);
|
|
25
|
+
let manifest;
|
|
26
|
+
try {
|
|
27
|
+
manifest = await loadManifest(formationDir);
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
if (err instanceof ManifestLoadError) {
|
|
31
|
+
const result = {
|
|
32
|
+
valid: false,
|
|
33
|
+
issues: [
|
|
34
|
+
{
|
|
35
|
+
severity: 'error',
|
|
36
|
+
code: 'MANIFEST_LOAD_ERROR',
|
|
37
|
+
message: err.message,
|
|
38
|
+
path: 'reef.json',
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
if (options.json) {
|
|
43
|
+
console.log(JSON.stringify(result, null, 2));
|
|
44
|
+
}
|
|
45
|
+
else if (!options.quiet) {
|
|
46
|
+
console.error(`${icons.error} ${err.message}`);
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
throw err;
|
|
51
|
+
}
|
|
52
|
+
const schemaResult = await validateSchema(manifest);
|
|
53
|
+
const structuralResult = await validateStructure(manifest, formationDir);
|
|
54
|
+
const result = mergeResults(schemaResult, structuralResult);
|
|
55
|
+
if (options.json) {
|
|
56
|
+
console.log(JSON.stringify(result, null, 2));
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
if (options.quiet) {
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
if (result.valid && result.issues.length === 0) {
|
|
63
|
+
console.log(`${icons.success} Formation is valid`);
|
|
64
|
+
}
|
|
65
|
+
else if (result.valid) {
|
|
66
|
+
console.log(`${icons.success} Formation is valid (with warnings)`);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
console.log(`${icons.error} Formation has validation errors`);
|
|
70
|
+
}
|
|
71
|
+
for (const issue of result.issues) {
|
|
72
|
+
const pathStr = issue.path ? ` (${issue.path})` : '';
|
|
73
|
+
console.log(` ${severityIcon(issue.severity)} ${issue.message}${pathStr}`);
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ReefManifest } from '../types/manifest.js';
|
|
2
|
+
export declare class ManifestLoadError extends Error {
|
|
3
|
+
readonly cause?: unknown | undefined;
|
|
4
|
+
constructor(message: string, cause?: unknown | undefined);
|
|
5
|
+
}
|
|
6
|
+
export declare function loadManifest(dir: string): Promise<ReefManifest>;
|
|
7
|
+
//# sourceMappingURL=manifest-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest-loader.d.ts","sourceRoot":"","sources":["../../src/core/manifest-loader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,qBAAa,iBAAkB,SAAQ,KAAK;aAGxB,KAAK,CAAC,EAAE,OAAO;gBAD/B,OAAO,EAAE,MAAM,EACC,KAAK,CAAC,EAAE,OAAO,YAAA;CAKlC;AAED,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAwBrE"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
export class ManifestLoadError extends Error {
|
|
4
|
+
cause;
|
|
5
|
+
constructor(message, cause) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.cause = cause;
|
|
8
|
+
this.name = 'ManifestLoadError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export async function loadManifest(dir) {
|
|
12
|
+
const manifestPath = join(dir, 'reef.json');
|
|
13
|
+
let raw;
|
|
14
|
+
try {
|
|
15
|
+
raw = await readFile(manifestPath, 'utf-8');
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
throw new ManifestLoadError(`Cannot read reef.json: ${manifestPath}`, err);
|
|
19
|
+
}
|
|
20
|
+
let parsed;
|
|
21
|
+
try {
|
|
22
|
+
parsed = JSON.parse(raw);
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
throw new ManifestLoadError(`Invalid JSON in reef.json: ${manifestPath}`, err);
|
|
26
|
+
}
|
|
27
|
+
return parsed;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=manifest-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest-loader.js","sourceRoot":"","sources":["../../src/core/manifest-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAGxB;IAFlB,YACE,OAAe,EACC,KAAe;QAE/B,KAAK,CAAC,OAAO,CAAC,CAAC;QAFC,UAAK,GAAL,KAAK,CAAU;QAG/B,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAW;IAC5C,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;IAE5C,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IAC9C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,iBAAiB,CACzB,0BAA0B,YAAY,EAAE,EACxC,GAAG,CACJ,CAAC;IACJ,CAAC;IAED,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,iBAAiB,CACzB,8BAA8B,YAAY,EAAE,EAC5C,GAAG,CACJ,CAAC;IACJ,CAAC;IAED,OAAO,MAAsB,CAAC;AAChC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"packer.d.ts","sourceRoot":"","sources":["../../src/core/packer.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAsB,IAAI,CACxB,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,UAAU,CAAC,CAmBrB"}
|