@openreef/cli 0.1.0 → 0.2.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/README.md +14 -7
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +1 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/install.d.ts +13 -0
- package/dist/commands/install.d.ts.map +1 -0
- package/dist/commands/install.js +560 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/list.d.ts +5 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +81 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/status.d.ts +8 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +198 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/uninstall.d.ts +8 -0
- package/dist/commands/uninstall.d.ts.map +1 -0
- package/dist/commands/uninstall.js +132 -0
- package/dist/commands/uninstall.js.map +1 -0
- package/dist/core/agents-md-generator.d.ts +3 -0
- package/dist/core/agents-md-generator.d.ts.map +1 -0
- package/dist/core/agents-md-generator.js +24 -0
- package/dist/core/agents-md-generator.js.map +1 -0
- package/dist/core/config-patcher.d.ts +32 -0
- package/dist/core/config-patcher.d.ts.map +1 -0
- package/dist/core/config-patcher.js +147 -0
- package/dist/core/config-patcher.js.map +1 -0
- package/dist/core/device-identity.d.ts +37 -0
- package/dist/core/device-identity.d.ts.map +1 -0
- package/dist/core/device-identity.js +135 -0
- package/dist/core/device-identity.js.map +1 -0
- package/dist/core/gateway-client.d.ts +98 -0
- package/dist/core/gateway-client.d.ts.map +1 -0
- package/dist/core/gateway-client.js +238 -0
- package/dist/core/gateway-client.js.map +1 -0
- package/dist/core/openclaw-paths.d.ts +17 -0
- package/dist/core/openclaw-paths.d.ts.map +1 -0
- package/dist/core/openclaw-paths.js +175 -0
- package/dist/core/openclaw-paths.js.map +1 -0
- package/dist/core/state-manager.d.ts +7 -0
- package/dist/core/state-manager.d.ts.map +1 -0
- package/dist/core/state-manager.js +61 -0
- package/dist/core/state-manager.js.map +1 -0
- package/dist/core/structural-validator.js +4 -4
- package/dist/core/structural-validator.js.map +1 -1
- package/dist/core/variable-resolver.d.ts +1 -0
- package/dist/core/variable-resolver.d.ts.map +1 -1
- package/dist/core/variable-resolver.js +21 -10
- package/dist/core/variable-resolver.js.map +1 -1
- package/dist/index.js +72 -2
- package/dist/index.js.map +1 -1
- package/dist/types/manifest.d.ts +1 -1
- package/dist/types/manifest.d.ts.map +1 -1
- package/dist/types/state.d.ts +41 -0
- package/dist/types/state.d.ts.map +1 -0
- package/dist/types/state.js +2 -0
- package/dist/types/state.js.map +1 -0
- package/package.json +6 -3
- package/schema/reef.schema.json +2 -2
- package/template/reef.json +1 -1
package/README.md
CHANGED
|
@@ -44,8 +44,8 @@ my-formation/
|
|
|
44
44
|
| Type | Agents | Use Case |
|
|
45
45
|
|------|--------|----------|
|
|
46
46
|
| `solo` | 1 | Single agent with curated personality, tools, and cron |
|
|
47
|
-
| `
|
|
48
|
-
| `
|
|
47
|
+
| `shoal` | 2–5 | Defined roles with explicit communication topology |
|
|
48
|
+
| `school` | 6+ | Large-scale or dynamically-spawning agent patterns |
|
|
49
49
|
|
|
50
50
|
## Quick Start
|
|
51
51
|
|
|
@@ -83,7 +83,7 @@ A single JSON file declares your entire formation:
|
|
|
83
83
|
```json
|
|
84
84
|
{
|
|
85
85
|
"reef": "1.0",
|
|
86
|
-
"type": "
|
|
86
|
+
"type": "shoal",
|
|
87
87
|
"name": "my-formation",
|
|
88
88
|
"version": "1.0.0",
|
|
89
89
|
"description": "A research team with a manager and researcher",
|
|
@@ -126,6 +126,8 @@ Variables support `{{VARIABLE_NAME}}` interpolation across all text files. Sensi
|
|
|
126
126
|
|
|
127
127
|
## CLI
|
|
128
128
|
|
|
129
|
+
### Offline Commands
|
|
130
|
+
|
|
129
131
|
| Command | Description |
|
|
130
132
|
|---------|-------------|
|
|
131
133
|
| `reef init [name]` | Scaffold a new formation from the bundled template |
|
|
@@ -133,17 +135,22 @@ Variables support `{{VARIABLE_NAME}}` interpolation across all text files. Sensi
|
|
|
133
135
|
| `reef validate <path>` | Run schema and structural validation on a formation |
|
|
134
136
|
| `reef pack <path>` | Package a formation into a `.tar.gz` archive |
|
|
135
137
|
|
|
136
|
-
|
|
138
|
+
### Online Commands (requires running OpenClaw)
|
|
137
139
|
|
|
138
140
|
| Command | Description |
|
|
139
141
|
|---------|-------------|
|
|
140
142
|
| `reef install <path>` | Deploy a formation to OpenClaw |
|
|
143
|
+
| `reef uninstall <identifier>` | Remove a formation and all its resources |
|
|
144
|
+
| `reef list` | List installed formations |
|
|
145
|
+
| `reef status <identifier>` | Show status of a deployed formation |
|
|
146
|
+
|
|
147
|
+
**Planned:**
|
|
148
|
+
|
|
149
|
+
| Command | Description |
|
|
150
|
+
|---------|-------------|
|
|
141
151
|
| `reef update <path>` | Update a deployed formation (preserves agent-written data) |
|
|
142
|
-
| `reef uninstall <name>` | Remove a formation and all its resources |
|
|
143
152
|
| `reef export <namespace>` | Snapshot running agents into a formation package |
|
|
144
153
|
| `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
154
|
|
|
148
155
|
## Security
|
|
149
156
|
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -1 +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,
|
|
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,OAAO,GAAG,QAAQ,CAAC;IACnC,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"}
|
package/dist/commands/init.js
CHANGED
|
@@ -25,7 +25,7 @@ function generateEnvExample(variables) {
|
|
|
25
25
|
export async function init(nameArg, options = {}) {
|
|
26
26
|
const name = nameArg ?? options.name ?? 'my-formation';
|
|
27
27
|
const namespace = options.namespace ?? name;
|
|
28
|
-
const type = options.type ?? '
|
|
28
|
+
const type = options.type ?? 'shoal';
|
|
29
29
|
const targetDir = resolve(name);
|
|
30
30
|
// Check if directory already exists
|
|
31
31
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAU3C,SAAS,kBAAkB,CAAC,SAAmC;IAC7D,MAAM,KAAK,GAAa,CAAC,gCAAgC,CAAC,CAAC;IAE3D,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QACvD,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;YACvB,KAAK,CAAC,IAAI,CAAC,KAAK,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;QACxC,CAAC;QACD,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;YACrB,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC;QACzB,CAAC;aAAM,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YACxC,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACpD,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AACjC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CACxB,OAA2B,EAC3B,UAAuB,EAAE;IAEzB,MAAM,IAAI,GAAG,OAAO,IAAI,OAAO,CAAC,IAAI,IAAI,cAAc,CAAC;IACvD,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,IAAI,CAAC;IAC5C,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface InstallOptions {
|
|
2
|
+
set?: string[];
|
|
3
|
+
namespace?: string;
|
|
4
|
+
force?: boolean;
|
|
5
|
+
merge?: boolean;
|
|
6
|
+
yes?: boolean;
|
|
7
|
+
noEnv?: boolean;
|
|
8
|
+
gatewayUrl?: string;
|
|
9
|
+
gatewayToken?: string;
|
|
10
|
+
gatewayPassword?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function install(formationPath: string, options: InstallOptions): Promise<void>;
|
|
13
|
+
//# sourceMappingURL=install.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/commands/install.ts"],"names":[],"mappings":"AA2CA,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAkCD,wBAAsB,OAAO,CAC3B,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,IAAI,CAAC,CA4nBf"}
|
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
2
|
+
import { join, dirname } from 'node:path';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { loadManifest } from '../core/manifest-loader.js';
|
|
6
|
+
import { validateSchema } from '../core/schema-validator.js';
|
|
7
|
+
import { validateStructure } from '../core/structural-validator.js';
|
|
8
|
+
import { resolveVariables } from '../core/variable-resolver.js';
|
|
9
|
+
import { interpolate } from '../core/template-interpolator.js';
|
|
10
|
+
import { resolveWorkspacePath, resolveGatewayUrl, validateAgentIds, } from '../core/openclaw-paths.js';
|
|
11
|
+
import { readConfig, writeConfig, addAgentEntry, removeAgentEntry, addBinding, removeBinding, setAgentToAgent, removeAgentToAgent, } from '../core/config-patcher.js';
|
|
12
|
+
import { GatewayClient, resolveGatewayAuth } from '../core/gateway-client.js';
|
|
13
|
+
import { loadState, saveState, deleteState, listStates, computeFileHash, } from '../core/state-manager.js';
|
|
14
|
+
import { generateAgentsMd } from '../core/agents-md-generator.js';
|
|
15
|
+
import { listFiles } from '../utils/fs.js';
|
|
16
|
+
import { icons, header, label, value, table } from '../utils/output.js';
|
|
17
|
+
const TOKEN_RE = /\{\{\w+\}\}/;
|
|
18
|
+
function isBinaryBuffer(buf) {
|
|
19
|
+
const check = buf.subarray(0, 8192);
|
|
20
|
+
return check.includes(0);
|
|
21
|
+
}
|
|
22
|
+
function parseSets(sets) {
|
|
23
|
+
const result = {};
|
|
24
|
+
if (!sets)
|
|
25
|
+
return result;
|
|
26
|
+
for (const s of sets) {
|
|
27
|
+
const eq = s.indexOf('=');
|
|
28
|
+
if (eq === -1)
|
|
29
|
+
continue;
|
|
30
|
+
result[s.slice(0, eq)] = s.slice(eq + 1);
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
function parseIdentifier(identifier) {
|
|
35
|
+
const slash = identifier.indexOf('/');
|
|
36
|
+
if (slash !== -1) {
|
|
37
|
+
return {
|
|
38
|
+
namespace: identifier.slice(0, slash),
|
|
39
|
+
name: identifier.slice(slash + 1),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return { name: identifier };
|
|
43
|
+
}
|
|
44
|
+
export async function install(formationPath, options) {
|
|
45
|
+
// ── Phase 1: Parse + Validate IDs ──
|
|
46
|
+
const spinner = ora('Loading formation...').start();
|
|
47
|
+
let manifest;
|
|
48
|
+
try {
|
|
49
|
+
manifest = await loadManifest(formationPath);
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
spinner.fail('Failed to load formation');
|
|
53
|
+
throw err;
|
|
54
|
+
}
|
|
55
|
+
const schemaResult = await validateSchema(manifest);
|
|
56
|
+
if (!schemaResult.valid) {
|
|
57
|
+
spinner.fail('Schema validation failed');
|
|
58
|
+
for (const issue of schemaResult.issues) {
|
|
59
|
+
console.error(` ${icons.error} ${issue.message}`);
|
|
60
|
+
}
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
const structResult = await validateStructure(manifest, formationPath);
|
|
64
|
+
if (!structResult.valid) {
|
|
65
|
+
spinner.fail('Structural validation failed');
|
|
66
|
+
for (const issue of structResult.issues) {
|
|
67
|
+
if (issue.severity === 'error') {
|
|
68
|
+
console.error(` ${icons.error} ${issue.message}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
const namespace = options.namespace ?? manifest.namespace;
|
|
74
|
+
const slugs = Object.keys(manifest.agents);
|
|
75
|
+
const idValidation = validateAgentIds(slugs, namespace);
|
|
76
|
+
if (!idValidation.valid) {
|
|
77
|
+
spinner.fail('Agent ID validation failed');
|
|
78
|
+
for (const error of idValidation.errors) {
|
|
79
|
+
console.error(` ${icons.error} ${error}`);
|
|
80
|
+
}
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
spinner.succeed('Formation loaded and validated');
|
|
84
|
+
// ── Phase 2: Variables ──
|
|
85
|
+
const { resolved: resolvedVars, missing } = await resolveVariables(manifest.variables ?? {}, formationPath, {
|
|
86
|
+
interactive: !options.yes,
|
|
87
|
+
cliOverrides: parseSets(options.set),
|
|
88
|
+
noEnv: options.noEnv,
|
|
89
|
+
});
|
|
90
|
+
if (missing.length > 0) {
|
|
91
|
+
console.error(`${icons.error} Missing required variables: ${missing.join(', ')}`);
|
|
92
|
+
console.error(' Use --set KEY=VALUE or set them in .env / environment.');
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
// ── Phase 3: Conflicts ──
|
|
96
|
+
const { config, path: configPath } = await readConfig();
|
|
97
|
+
const existingState = await loadState(namespace, manifest.name);
|
|
98
|
+
if (existingState && !options.force && !options.merge) {
|
|
99
|
+
console.error(`${icons.error} Formation "${namespace}/${manifest.name}" is already installed.`);
|
|
100
|
+
console.error(' Use --force to replace or --merge to update.');
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
// Check for agent ID conflicts in config (from other formations)
|
|
104
|
+
if (!options.force && !options.merge) {
|
|
105
|
+
const agentsList = config.agents?.list ?? [];
|
|
106
|
+
const conflicting = agentsList.filter((a) => {
|
|
107
|
+
const id = a.id;
|
|
108
|
+
return id.startsWith(`${namespace}-`) && idValidation.ids.has(slugs.find((s) => `${namespace}-${s}` === id) ?? '');
|
|
109
|
+
});
|
|
110
|
+
if (conflicting.length > 0) {
|
|
111
|
+
console.error(`${icons.error} Config already contains agents with matching IDs:`);
|
|
112
|
+
for (const a of conflicting) {
|
|
113
|
+
console.error(` - ${a.id}`);
|
|
114
|
+
}
|
|
115
|
+
console.error(' Use --force to replace.');
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// --force cleanup
|
|
120
|
+
if (options.force && existingState) {
|
|
121
|
+
if (!options.yes) {
|
|
122
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
123
|
+
const ok = await confirm({
|
|
124
|
+
message: `This will remove the existing installation of "${namespace}/${manifest.name}". Continue?`,
|
|
125
|
+
});
|
|
126
|
+
if (!ok) {
|
|
127
|
+
console.log('Aborted.');
|
|
128
|
+
process.exit(0);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const cleanupSpinner = ora('Cleaning up existing installation...').start();
|
|
132
|
+
// Remove cron jobs via RPC
|
|
133
|
+
if (existingState.cronJobs.length > 0) {
|
|
134
|
+
try {
|
|
135
|
+
const gwUrl = options.gatewayUrl ?? resolveGatewayUrl(config, process.env);
|
|
136
|
+
const gwAuth = resolveGatewayAuth({
|
|
137
|
+
gatewayUrl: options.gatewayUrl,
|
|
138
|
+
gatewayToken: options.gatewayToken,
|
|
139
|
+
gatewayPassword: options.gatewayPassword,
|
|
140
|
+
config,
|
|
141
|
+
});
|
|
142
|
+
const gw = new GatewayClient({
|
|
143
|
+
url: gwUrl,
|
|
144
|
+
...gwAuth,
|
|
145
|
+
});
|
|
146
|
+
await gw.connect();
|
|
147
|
+
for (const job of existingState.cronJobs) {
|
|
148
|
+
try {
|
|
149
|
+
await gw.cronRemove(job.id);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// Job may already be gone
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
gw.close();
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
cleanupSpinner.warn('Could not connect to Gateway for cron cleanup — jobs may be orphaned');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Remove from config
|
|
162
|
+
let cleanConfig = config;
|
|
163
|
+
for (const agent of Object.values(existingState.agents)) {
|
|
164
|
+
cleanConfig = removeAgentEntry(cleanConfig, agent.id);
|
|
165
|
+
}
|
|
166
|
+
for (const binding of existingState.bindings) {
|
|
167
|
+
cleanConfig = removeBinding(cleanConfig, binding);
|
|
168
|
+
}
|
|
169
|
+
// Remove agentToAgent allow entry
|
|
170
|
+
if (existingState.agentToAgent?.allowAdded) {
|
|
171
|
+
const allStates = await listStates();
|
|
172
|
+
const otherInNamespace = allStates.some((s) => s.namespace === namespace &&
|
|
173
|
+
s.name !== existingState.name);
|
|
174
|
+
cleanConfig = removeAgentToAgent(cleanConfig, namespace, otherInNamespace, existingState.agentToAgent.wasEnabled);
|
|
175
|
+
}
|
|
176
|
+
await writeConfig(configPath, cleanConfig, { silent: true });
|
|
177
|
+
// Delete workspaces
|
|
178
|
+
const { rm } = await import('node:fs/promises');
|
|
179
|
+
for (const agent of Object.values(existingState.agents)) {
|
|
180
|
+
try {
|
|
181
|
+
await rm(agent.workspace, { recursive: true, force: true });
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
// Already gone
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
await deleteState(namespace, manifest.name);
|
|
188
|
+
cleanupSpinner.succeed('Cleaned up existing installation');
|
|
189
|
+
}
|
|
190
|
+
// ── Phase 4: Dependencies ──
|
|
191
|
+
if (manifest.dependencies?.skills) {
|
|
192
|
+
const skills = Object.keys(manifest.dependencies.skills);
|
|
193
|
+
if (skills.length > 0) {
|
|
194
|
+
console.log(`${icons.warning} ${chalk.yellow('Skills not yet installed (ClawHub integration planned):')} ${skills.join(', ')}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (manifest.dependencies?.services?.length) {
|
|
198
|
+
const unconfigured = manifest.dependencies.services.filter((s) => s.required);
|
|
199
|
+
if (unconfigured.length > 0) {
|
|
200
|
+
console.log(`${icons.warning} ${chalk.yellow('Required services — ensure they are configured:')} ${unconfigured.map((s) => s.name).join(', ')}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// ── Phase 5: Confirm ──
|
|
204
|
+
if (!options.yes && !options.merge) {
|
|
205
|
+
console.log('');
|
|
206
|
+
console.log(header('Deployment Plan'));
|
|
207
|
+
console.log('');
|
|
208
|
+
console.log(`${label('Formation:')} ${value(`${namespace}/${manifest.name}`)} v${manifest.version}`);
|
|
209
|
+
console.log('');
|
|
210
|
+
// Agents
|
|
211
|
+
console.log(label('Agents:'));
|
|
212
|
+
const agentRows = [];
|
|
213
|
+
for (const [slug] of Object.entries(manifest.agents)) {
|
|
214
|
+
agentRows.push([
|
|
215
|
+
` ${slug}`,
|
|
216
|
+
`→ ${idValidation.ids.get(slug)}`,
|
|
217
|
+
]);
|
|
218
|
+
}
|
|
219
|
+
console.log(table(agentRows));
|
|
220
|
+
// Bindings
|
|
221
|
+
if (manifest.bindings?.length) {
|
|
222
|
+
console.log('');
|
|
223
|
+
console.log(label('Channel Bindings:'));
|
|
224
|
+
for (const b of manifest.bindings) {
|
|
225
|
+
console.log(` ${b.channel} → ${namespace}-${b.agent}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// Cron
|
|
229
|
+
if (manifest.cron?.length) {
|
|
230
|
+
console.log('');
|
|
231
|
+
console.log(label('Cron Jobs:'));
|
|
232
|
+
for (const c of manifest.cron) {
|
|
233
|
+
console.log(` ${c.schedule} → ${namespace}-${c.agent}: "${c.prompt.slice(0, 60)}${c.prompt.length > 60 ? '...' : ''}"`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// Variables
|
|
237
|
+
if (manifest.variables && Object.keys(manifest.variables).length > 0) {
|
|
238
|
+
console.log('');
|
|
239
|
+
console.log(label('Variables:'));
|
|
240
|
+
for (const [name, config] of Object.entries(manifest.variables)) {
|
|
241
|
+
const val = config.sensitive
|
|
242
|
+
? '********'
|
|
243
|
+
: (resolvedVars[name] ?? '(not set)');
|
|
244
|
+
console.log(` ${name} = ${val}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
console.log('');
|
|
248
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
249
|
+
const proceed = await confirm({ message: 'Deploy this formation?' });
|
|
250
|
+
if (!proceed) {
|
|
251
|
+
console.log('Aborted.');
|
|
252
|
+
process.exit(0);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// ── Phase 6: Deploy ──
|
|
256
|
+
const deploySpinner = ora('Deploying formation...').start();
|
|
257
|
+
const agentStates = {};
|
|
258
|
+
const fileHashes = {};
|
|
259
|
+
const cronJobStates = [];
|
|
260
|
+
const openClawBindings = [];
|
|
261
|
+
// Deploy workspace files
|
|
262
|
+
deploySpinner.text = 'Writing workspace files...';
|
|
263
|
+
for (const [slug, agentDef] of Object.entries(manifest.agents)) {
|
|
264
|
+
const agentId = idValidation.ids.get(slug);
|
|
265
|
+
const workspacePath = resolveWorkspacePath(agentId);
|
|
266
|
+
await mkdir(workspacePath, { recursive: true });
|
|
267
|
+
const sourceDir = join(formationPath, agentDef.source);
|
|
268
|
+
const files = await listFiles(sourceDir);
|
|
269
|
+
const deployedFiles = [];
|
|
270
|
+
for (const relativePath of files) {
|
|
271
|
+
const srcFile = join(sourceDir, relativePath);
|
|
272
|
+
const destPath = join(workspacePath, relativePath);
|
|
273
|
+
await mkdir(dirname(destPath), { recursive: true });
|
|
274
|
+
const rawBytes = await readFile(srcFile);
|
|
275
|
+
if (isBinaryBuffer(rawBytes)) {
|
|
276
|
+
// --merge: skip unchanged files
|
|
277
|
+
if (options.merge && existingState) {
|
|
278
|
+
const hash = computeFileHash(rawBytes);
|
|
279
|
+
const existingHash = existingState.fileHashes[`${agentId}:${relativePath}`];
|
|
280
|
+
if (existingHash === hash) {
|
|
281
|
+
deployedFiles.push(relativePath);
|
|
282
|
+
fileHashes[`${agentId}:${relativePath}`] = hash;
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
await writeFile(destPath, rawBytes);
|
|
287
|
+
fileHashes[`${agentId}:${relativePath}`] = computeFileHash(rawBytes);
|
|
288
|
+
deployedFiles.push(relativePath);
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
let content = rawBytes.toString('utf-8');
|
|
292
|
+
if (TOKEN_RE.test(content)) {
|
|
293
|
+
content = interpolate(content, resolvedVars);
|
|
294
|
+
}
|
|
295
|
+
const written = Buffer.from(content, 'utf-8');
|
|
296
|
+
// --merge: skip unchanged files
|
|
297
|
+
if (options.merge && existingState) {
|
|
298
|
+
const hash = computeFileHash(written);
|
|
299
|
+
const existingHash = existingState.fileHashes[`${agentId}:${relativePath}`];
|
|
300
|
+
if (existingHash === hash) {
|
|
301
|
+
deployedFiles.push(relativePath);
|
|
302
|
+
fileHashes[`${agentId}:${relativePath}`] = hash;
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
await writeFile(destPath, written);
|
|
307
|
+
fileHashes[`${agentId}:${relativePath}`] = computeFileHash(written);
|
|
308
|
+
deployedFiles.push(relativePath);
|
|
309
|
+
}
|
|
310
|
+
// Generate AGENTS.md
|
|
311
|
+
if (manifest.agentToAgent?.[slug]?.length) {
|
|
312
|
+
const agentsMd = generateAgentsMd(manifest, slug, namespace);
|
|
313
|
+
const buf = Buffer.from(agentsMd, 'utf-8');
|
|
314
|
+
await writeFile(join(workspacePath, 'AGENTS.md'), buf);
|
|
315
|
+
fileHashes[`${agentId}:AGENTS.md`] = computeFileHash(buf);
|
|
316
|
+
deployedFiles.push('AGENTS.md');
|
|
317
|
+
}
|
|
318
|
+
agentStates[slug] = {
|
|
319
|
+
id: agentId,
|
|
320
|
+
slug,
|
|
321
|
+
workspace: workspacePath,
|
|
322
|
+
files: deployedFiles,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
// Patch config
|
|
326
|
+
deploySpinner.text = 'Patching OpenClaw config...';
|
|
327
|
+
let patchedConfig = (await readConfig(configPath)).config;
|
|
328
|
+
for (const [slug, agentDef] of Object.entries(manifest.agents)) {
|
|
329
|
+
const agentId = idValidation.ids.get(slug);
|
|
330
|
+
patchedConfig = addAgentEntry(patchedConfig, {
|
|
331
|
+
id: agentId,
|
|
332
|
+
name: slug,
|
|
333
|
+
workspace: resolveWorkspacePath(agentId),
|
|
334
|
+
model: agentDef.model,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
// Map bindings — use validated IDs from the map, not raw string composition
|
|
338
|
+
for (const binding of manifest.bindings ?? []) {
|
|
339
|
+
if (binding.direction) {
|
|
340
|
+
console.log(`\n${icons.warning} ${chalk.yellow(`'direction' field in binding ${binding.channel} → ${binding.agent} is ignored (OpenClaw bindings are always inbound)`)}`);
|
|
341
|
+
}
|
|
342
|
+
const resolvedAgentId = idValidation.ids.get(binding.agent);
|
|
343
|
+
if (!resolvedAgentId) {
|
|
344
|
+
console.log(`\n${icons.warning} ${chalk.yellow(`Binding references unknown agent "${binding.agent}" — skipped`)}`);
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
const openClawBinding = {
|
|
348
|
+
agentId: resolvedAgentId,
|
|
349
|
+
match: { channel: binding.channel },
|
|
350
|
+
};
|
|
351
|
+
patchedConfig = addBinding(patchedConfig, openClawBinding);
|
|
352
|
+
openClawBindings.push(openClawBinding);
|
|
353
|
+
}
|
|
354
|
+
// Agent-to-agent messaging
|
|
355
|
+
const a2aState = { wasEnabled: false, allowAdded: false };
|
|
356
|
+
if (manifest.agentToAgent &&
|
|
357
|
+
Object.keys(manifest.agentToAgent).length > 0) {
|
|
358
|
+
const tools = patchedConfig.tools;
|
|
359
|
+
const a2a = tools?.agentToAgent;
|
|
360
|
+
a2aState.wasEnabled = a2a?.enabled === true;
|
|
361
|
+
patchedConfig = setAgentToAgent(patchedConfig, namespace);
|
|
362
|
+
a2aState.allowAdded = true;
|
|
363
|
+
}
|
|
364
|
+
await writeConfig(configPath, patchedConfig, { silent: options.merge });
|
|
365
|
+
// Cron jobs via Gateway RPC
|
|
366
|
+
if (manifest.cron?.length) {
|
|
367
|
+
deploySpinner.text = 'Creating cron jobs...';
|
|
368
|
+
const gwUrl = options.gatewayUrl ?? resolveGatewayUrl(patchedConfig, process.env);
|
|
369
|
+
const gwAuth = resolveGatewayAuth({
|
|
370
|
+
gatewayUrl: options.gatewayUrl,
|
|
371
|
+
gatewayToken: options.gatewayToken,
|
|
372
|
+
gatewayPassword: options.gatewayPassword,
|
|
373
|
+
config: patchedConfig,
|
|
374
|
+
});
|
|
375
|
+
const gw = new GatewayClient({
|
|
376
|
+
url: gwUrl,
|
|
377
|
+
...gwAuth,
|
|
378
|
+
});
|
|
379
|
+
try {
|
|
380
|
+
await gw.connect();
|
|
381
|
+
if (options.merge && existingState) {
|
|
382
|
+
// --merge: update existing cron jobs, add new ones
|
|
383
|
+
const existingJobs = await gw.cronList({ includeDisabled: true });
|
|
384
|
+
const existingByName = new Map(existingJobs.map((j) => [j.name, j]));
|
|
385
|
+
for (const [i, cronEntry] of manifest.cron.entries()) {
|
|
386
|
+
const jobName = `reef:${namespace}:${cronEntry.agent}-${i}`;
|
|
387
|
+
const existing = existingByName.get(jobName);
|
|
388
|
+
if (existing) {
|
|
389
|
+
// Update in-place
|
|
390
|
+
await gw.cronUpdate(existing.id, {
|
|
391
|
+
schedule: {
|
|
392
|
+
kind: 'cron',
|
|
393
|
+
expr: cronEntry.schedule,
|
|
394
|
+
tz: cronEntry.timezone,
|
|
395
|
+
},
|
|
396
|
+
payload: {
|
|
397
|
+
kind: 'agentTurn',
|
|
398
|
+
message: cronEntry.prompt,
|
|
399
|
+
},
|
|
400
|
+
agentId: idValidation.ids.get(cronEntry.agent),
|
|
401
|
+
enabled: true,
|
|
402
|
+
});
|
|
403
|
+
cronJobStates.push({
|
|
404
|
+
id: existing.id,
|
|
405
|
+
name: jobName,
|
|
406
|
+
agentSlug: cronEntry.agent,
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
// Add new
|
|
411
|
+
const result = await gw.cronAdd({
|
|
412
|
+
name: jobName,
|
|
413
|
+
agentId: idValidation.ids.get(cronEntry.agent),
|
|
414
|
+
enabled: true,
|
|
415
|
+
schedule: {
|
|
416
|
+
kind: 'cron',
|
|
417
|
+
expr: cronEntry.schedule,
|
|
418
|
+
tz: cronEntry.timezone,
|
|
419
|
+
},
|
|
420
|
+
sessionTarget: 'isolated',
|
|
421
|
+
wakeMode: 'now',
|
|
422
|
+
payload: {
|
|
423
|
+
kind: 'agentTurn',
|
|
424
|
+
message: cronEntry.prompt,
|
|
425
|
+
},
|
|
426
|
+
});
|
|
427
|
+
cronJobStates.push({
|
|
428
|
+
id: result.id,
|
|
429
|
+
name: jobName,
|
|
430
|
+
agentSlug: cronEntry.agent,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
// Fresh install
|
|
437
|
+
for (const [i, cronEntry] of manifest.cron.entries()) {
|
|
438
|
+
const jobName = `reef:${namespace}:${cronEntry.agent}-${i}`;
|
|
439
|
+
const cronAgentId = idValidation.ids.get(cronEntry.agent);
|
|
440
|
+
if (!cronAgentId) {
|
|
441
|
+
console.log(`\n${icons.warning} ${chalk.yellow(`Cron job references unknown agent "${cronEntry.agent}" — skipped`)}`);
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
const result = await gw.cronAdd({
|
|
445
|
+
name: jobName,
|
|
446
|
+
agentId: cronAgentId,
|
|
447
|
+
enabled: true,
|
|
448
|
+
schedule: {
|
|
449
|
+
kind: 'cron',
|
|
450
|
+
expr: cronEntry.schedule,
|
|
451
|
+
tz: cronEntry.timezone,
|
|
452
|
+
},
|
|
453
|
+
sessionTarget: 'isolated',
|
|
454
|
+
wakeMode: 'now',
|
|
455
|
+
payload: {
|
|
456
|
+
kind: 'agentTurn',
|
|
457
|
+
message: cronEntry.prompt,
|
|
458
|
+
},
|
|
459
|
+
});
|
|
460
|
+
cronJobStates.push({
|
|
461
|
+
id: result.id,
|
|
462
|
+
name: jobName,
|
|
463
|
+
agentSlug: cronEntry.agent,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
catch (err) {
|
|
469
|
+
deploySpinner.fail('Failed to create cron jobs');
|
|
470
|
+
console.error(`${icons.error} Gateway error: ${err instanceof Error ? err.message : err}`);
|
|
471
|
+
console.error(' Ensure OpenClaw Gateway is running. Config and workspace files were deployed successfully.');
|
|
472
|
+
// Still save state for what we deployed
|
|
473
|
+
}
|
|
474
|
+
finally {
|
|
475
|
+
gw.close();
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
// ── Phase 7: Validate ──
|
|
479
|
+
deploySpinner.text = 'Validating deployment...';
|
|
480
|
+
const finalConfig = (await readConfig(configPath)).config;
|
|
481
|
+
const agentsList = finalConfig.agents?.list ?? [];
|
|
482
|
+
// agent_exists: check agents in config
|
|
483
|
+
for (const [slug] of Object.entries(manifest.agents)) {
|
|
484
|
+
const agentId = idValidation.ids.get(slug);
|
|
485
|
+
const found = agentsList.some((a) => a.id === agentId);
|
|
486
|
+
if (!found) {
|
|
487
|
+
console.log(`\n${icons.warning} Agent "${agentId}" not found in config after deploy`);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
// binding_active: check bindings in config
|
|
491
|
+
const finalBindings = (finalConfig.bindings ?? []);
|
|
492
|
+
for (const binding of openClawBindings) {
|
|
493
|
+
const found = finalBindings.some((b) => {
|
|
494
|
+
const match = b.match;
|
|
495
|
+
return b.agentId === binding.agentId && match?.channel === binding.match.channel;
|
|
496
|
+
});
|
|
497
|
+
if (!found) {
|
|
498
|
+
console.log(`\n${icons.warning} Binding ${binding.match.channel} → ${binding.agentId} not found in config after deploy`);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
// cron_exists: verify cron jobs via Gateway (if we created any and Gateway is reachable)
|
|
502
|
+
if (cronJobStates.length > 0) {
|
|
503
|
+
try {
|
|
504
|
+
const gwUrl = options.gatewayUrl ?? resolveGatewayUrl(patchedConfig, process.env);
|
|
505
|
+
const gwAuth = resolveGatewayAuth({
|
|
506
|
+
gatewayUrl: options.gatewayUrl,
|
|
507
|
+
gatewayToken: options.gatewayToken,
|
|
508
|
+
gatewayPassword: options.gatewayPassword,
|
|
509
|
+
config: patchedConfig,
|
|
510
|
+
});
|
|
511
|
+
const verifyGw = new GatewayClient({
|
|
512
|
+
url: gwUrl,
|
|
513
|
+
...gwAuth,
|
|
514
|
+
});
|
|
515
|
+
await verifyGw.connect();
|
|
516
|
+
const liveJobs = await verifyGw.cronList({ includeDisabled: true });
|
|
517
|
+
verifyGw.close();
|
|
518
|
+
const liveIds = new Set(liveJobs.map((j) => j.id));
|
|
519
|
+
for (const job of cronJobStates) {
|
|
520
|
+
if (!liveIds.has(job.id)) {
|
|
521
|
+
console.log(`\n${icons.warning} Cron job "${job.name}" (${job.id}) not found on Gateway after deploy`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
catch {
|
|
526
|
+
// Gateway not reachable for verification — skip
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
// ── Phase 8: State ──
|
|
530
|
+
const variablesForState = {};
|
|
531
|
+
for (const [name, config] of Object.entries(manifest.variables ?? {})) {
|
|
532
|
+
if (config.sensitive) {
|
|
533
|
+
// Store as env var reference, not the secret value
|
|
534
|
+
variablesForState[name] = `$${name}`;
|
|
535
|
+
}
|
|
536
|
+
else {
|
|
537
|
+
variablesForState[name] = resolvedVars[name] ?? '';
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
const state = {
|
|
541
|
+
name: manifest.name,
|
|
542
|
+
version: manifest.version,
|
|
543
|
+
namespace,
|
|
544
|
+
installedAt: new Date().toISOString(),
|
|
545
|
+
agents: agentStates,
|
|
546
|
+
bindings: openClawBindings,
|
|
547
|
+
cronJobs: cronJobStates,
|
|
548
|
+
variables: variablesForState,
|
|
549
|
+
fileHashes,
|
|
550
|
+
agentToAgent: a2aState.allowAdded ? a2aState : undefined,
|
|
551
|
+
};
|
|
552
|
+
await saveState(state);
|
|
553
|
+
deploySpinner.succeed('Formation deployed');
|
|
554
|
+
console.log('');
|
|
555
|
+
console.log(`${icons.success} ${chalk.green(`${Object.keys(agentStates).length} agents deployed, ${openClawBindings.length} bindings wired, ${cronJobStates.length} cron jobs scheduled`)}`);
|
|
556
|
+
console.log('');
|
|
557
|
+
console.log(` ${label('Manage:')} reef status ${namespace}/${manifest.name}`);
|
|
558
|
+
console.log(` ${label('Remove:')} reef uninstall ${namespace}/${manifest.name}`);
|
|
559
|
+
}
|
|
560
|
+
//# sourceMappingURL=install.js.map
|