eacn3 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.
@@ -0,0 +1,332 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * cli.js — `eacn` CLI entry point
4
+ * Usage:
5
+ * npx eacn setup — configure OpenClaw native plugin
6
+ * npx eacn diagnose — run diagnostics
7
+ */
8
+
9
+ const { spawnSync, execFileSync } = require('child_process');
10
+ const path = require('path');
11
+ const fs = require('fs');
12
+ const os = require('os');
13
+
14
+ const PKG_ROOT = path.resolve(__dirname, '..');
15
+ const PLUGIN_ID = 'eacn';
16
+ const EXT_DIR = path.join(os.homedir(), '.openclaw', 'extensions', PLUGIN_ID);
17
+ const CONFIG_PATH = path.join(os.homedir(), '.openclaw', 'openclaw.json');
18
+
19
+ // ── helpers ───────────────────────────────────────────────────────────────────
20
+
21
+ function readJSON(filePath) {
22
+ try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); }
23
+ catch (_) { return {}; }
24
+ }
25
+
26
+ function writeJSON(filePath, obj) {
27
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
28
+ fs.writeFileSync(filePath, JSON.stringify(obj, null, 2) + '\n', 'utf8');
29
+ }
30
+
31
+ function merge(target, source) {
32
+ for (const [k, v] of Object.entries(source)) {
33
+ if (v && typeof v === 'object' && !Array.isArray(v)) {
34
+ target[k] = merge(target[k] || {}, v);
35
+ } else {
36
+ target[k] = v;
37
+ }
38
+ }
39
+ return target;
40
+ }
41
+
42
+ function copyDirRecursive(src, dst) {
43
+ fs.mkdirSync(dst, { recursive: true });
44
+ for (const entry of fs.readdirSync(src)) {
45
+ const srcPath = path.join(src, entry);
46
+ const dstPath = path.join(dst, entry);
47
+ if (fs.statSync(srcPath).isDirectory()) {
48
+ if (entry === 'node_modules' || entry === '.git') continue;
49
+ copyDirRecursive(srcPath, dstPath);
50
+ } else {
51
+ fs.copyFileSync(srcPath, dstPath);
52
+ }
53
+ }
54
+ }
55
+
56
+ function log(msg) { console.log(` ${msg}`); }
57
+ function ok(msg) { console.log(` ✓ ${msg}`); }
58
+ function fail(msg){ console.log(` ✗ ${msg}`); }
59
+
60
+ // ── diagnose ──────────────────────────────────────────────────────────────────
61
+
62
+ function diagnose() {
63
+ console.log('\neacn diagnostics\n');
64
+ let allOk = true;
65
+
66
+ function check(label, fn) {
67
+ try {
68
+ const result = fn();
69
+ ok(label + (result ? ` — ${result}` : ''));
70
+ return true;
71
+ } catch (e) {
72
+ fail(label + ` — ${e.message}`);
73
+ allOk = false;
74
+ return false;
75
+ }
76
+ }
77
+
78
+ // 1. Package integrity
79
+ console.log('── Package ──');
80
+ check('openclaw.plugin.json', () => {
81
+ const m = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'openclaw.plugin.json'), 'utf8'));
82
+ return `id=${m.id} v=${m.version}`;
83
+ });
84
+ check('dist/index.js', () => {
85
+ const p = path.join(PKG_ROOT, 'dist', 'index.js');
86
+ if (!fs.existsSync(p)) throw new Error('not found — run "npm run build"');
87
+ const stat = fs.statSync(p);
88
+ return `${(stat.size / 1024).toFixed(1)} KB, modified ${stat.mtime.toISOString().slice(0, 19)}`;
89
+ });
90
+ check('dist/server.js', () => {
91
+ const p = path.join(PKG_ROOT, 'dist', 'server.js');
92
+ if (!fs.existsSync(p)) throw new Error('not found — run "npm run build"');
93
+ const stat = fs.statSync(p);
94
+ return `${(stat.size / 1024).toFixed(1)} KB`;
95
+ });
96
+ check('skill files', () => {
97
+ const dir = path.join(PKG_ROOT, 'skills');
98
+ if (!fs.existsSync(dir)) throw new Error('skills/ not found');
99
+ const skills = fs.readdirSync(dir).filter(d => {
100
+ const skillMd = path.join(dir, d, 'SKILL.md');
101
+ return fs.existsSync(skillMd);
102
+ });
103
+ return `${skills.length} skills (${skills.join(', ')})`;
104
+ });
105
+
106
+ // 2. Binaries
107
+ console.log('\n── Binaries ──');
108
+ check('node', () => process.version);
109
+ check('git', () => {
110
+ return execFileSync('git', ['--version'], { encoding: 'utf8', timeout: 5000 }).trim();
111
+ });
112
+
113
+ // 3. OpenClaw installation
114
+ console.log('\n── OpenClaw Integration ──');
115
+ check('extensions directory', () => {
116
+ if (!fs.existsSync(EXT_DIR)) throw new Error(`${EXT_DIR} not found — run "npx eacn setup"`);
117
+ const files = fs.readdirSync(EXT_DIR);
118
+ return `${files.length} entries in ${EXT_DIR}`;
119
+ });
120
+ check('dist/index.js in extensions', () => {
121
+ const p = path.join(EXT_DIR, 'dist', 'index.js');
122
+ if (!fs.existsSync(p)) throw new Error('not found — run "npx eacn setup" to copy');
123
+ return 'exists';
124
+ });
125
+ check('openclaw.json plugin entry', () => {
126
+ if (!fs.existsSync(CONFIG_PATH)) throw new Error(`${CONFIG_PATH} not found`);
127
+ const config = readJSON(CONFIG_PATH);
128
+ const entry = config?.plugins?.entries?.[PLUGIN_ID];
129
+ if (!entry) throw new Error(`no "${PLUGIN_ID}" entry in plugins.entries`);
130
+ if (!entry.enabled) throw new Error('plugin is disabled');
131
+ return 'enabled';
132
+ });
133
+ check('plugins.allow whitelist', () => {
134
+ const config = readJSON(CONFIG_PATH);
135
+ const allow = config?.plugins?.allow;
136
+ if (!Array.isArray(allow)) throw new Error('plugins.allow is missing or not an array');
137
+ if (!allow.includes(PLUGIN_ID)) throw new Error(`"${PLUGIN_ID}" not in plugins.allow`);
138
+ return `[${allow.join(', ')}]`;
139
+ });
140
+ check('plugins.installs metadata', () => {
141
+ const config = readJSON(CONFIG_PATH);
142
+ const inst = config?.plugins?.installs?.[PLUGIN_ID];
143
+ if (!inst) throw new Error('no install entry — run "npx eacn setup"');
144
+ return `source=${inst.source} v=${inst.version} @ ${inst.installedAt}`;
145
+ });
146
+ check('skills.entries registration', () => {
147
+ const config = readJSON(CONFIG_PATH);
148
+ const skills = config?.skills?.entries;
149
+ if (!skills) throw new Error('skills.entries missing');
150
+ const eacnSkills = Object.keys(skills).filter(k => k.startsWith('eacn-') && skills[k]?.enabled);
151
+ if (eacnSkills.length === 0) throw new Error('no eacn skills enabled');
152
+ return `${eacnSkills.length} enabled: ${eacnSkills.join(', ')}`;
153
+ });
154
+ check('skill files in extensions', () => {
155
+ const skillsDir = path.join(EXT_DIR, 'skills');
156
+ if (!fs.existsSync(skillsDir)) throw new Error(`${skillsDir} not found`);
157
+ const skills = fs.readdirSync(skillsDir).filter(d =>
158
+ fs.existsSync(path.join(skillsDir, d, 'SKILL.md'))
159
+ );
160
+ return `${skills.length} skills: ${skills.join(', ')}`;
161
+ });
162
+ check('manifest skill paths resolve', () => {
163
+ const extManifest = path.join(EXT_DIR, 'openclaw.plugin.json');
164
+ if (!fs.existsSync(extManifest)) throw new Error('no manifest in extensions');
165
+ const m = JSON.parse(fs.readFileSync(extManifest, 'utf8'));
166
+ for (const sp of m.skills || []) {
167
+ const resolved = path.resolve(EXT_DIR, sp);
168
+ if (!fs.existsSync(resolved)) throw new Error(`"${sp}" resolves to ${resolved} — not found`);
169
+ }
170
+ return (m.skills || []).join(', ');
171
+ });
172
+
173
+ // Summary
174
+ console.log('');
175
+ if (allOk) {
176
+ console.log(' All checks passed.\n');
177
+ } else {
178
+ console.log(' Some checks failed. Fix the issues above and re-run:\n');
179
+ console.log(' npx eacn diagnose\n');
180
+ }
181
+
182
+ return allOk;
183
+ }
184
+
185
+ // ── setup ─────────────────────────────────────────────────────────────────────
186
+
187
+ function setupOpenclaw() {
188
+ console.log('\neacn setup\n');
189
+
190
+ // 1. Check dist exists
191
+ const distSrc = path.join(PKG_ROOT, 'dist', 'index.js');
192
+ if (!fs.existsSync(distSrc)) {
193
+ fail('dist/index.js not found — building now...');
194
+ const build = spawnSync('npm', ['run', 'build'], { cwd: PKG_ROOT, stdio: 'inherit' });
195
+ if (build.status !== 0) {
196
+ fail('build failed — cannot continue');
197
+ process.exit(1);
198
+ }
199
+ ok('build succeeded');
200
+ } else {
201
+ ok(`dist/index.js exists`);
202
+ }
203
+
204
+ // 2. Copy plugin files
205
+ log(`copying to ${EXT_DIR} ...`);
206
+ fs.mkdirSync(EXT_DIR, { recursive: true });
207
+
208
+ // Copy dist/
209
+ copyDirRecursive(path.join(PKG_ROOT, 'dist'), path.join(EXT_DIR, 'dist'));
210
+ ok('dist/ copied');
211
+
212
+ // Copy skills/
213
+ const skillsSrc = path.join(PKG_ROOT, 'skills');
214
+ if (fs.existsSync(skillsSrc)) {
215
+ copyDirRecursive(skillsSrc, path.join(EXT_DIR, 'skills'));
216
+ ok('skills/ copied');
217
+ }
218
+
219
+ // Copy manifest
220
+ const manifestSrc = path.join(PKG_ROOT, 'openclaw.plugin.json');
221
+ if (fs.existsSync(manifestSrc)) {
222
+ fs.copyFileSync(manifestSrc, path.join(EXT_DIR, 'openclaw.plugin.json'));
223
+ ok('openclaw.plugin.json copied');
224
+ }
225
+
226
+ // Copy package.json
227
+ fs.copyFileSync(path.join(PKG_ROOT, 'package.json'), path.join(EXT_DIR, 'package.json'));
228
+ ok('package.json copied');
229
+
230
+ // Copy node_modules/ (runtime dependencies like ws, zod, @modelcontextprotocol/sdk)
231
+ const nmSrc = path.join(PKG_ROOT, 'node_modules');
232
+ if (fs.existsSync(nmSrc)) {
233
+ copyDirRecursive(nmSrc, path.join(EXT_DIR, 'node_modules'));
234
+ ok('node_modules/ copied');
235
+ } else {
236
+ fail('node_modules/ not found — run "npm install" first');
237
+ }
238
+
239
+ // Clean up stale extension directory from previous installs (used wrong name)
240
+ const staleDir = path.join(os.homedir(), '.openclaw', 'extensions', 'eacn');
241
+ if (staleDir !== EXT_DIR && fs.existsSync(staleDir)) {
242
+ fs.rmSync(staleDir, { recursive: true, force: true });
243
+ ok('removed stale extensions/eacn directory');
244
+ }
245
+
246
+ // 3. Discover skills
247
+ const skillNames = [];
248
+ if (fs.existsSync(skillsSrc)) {
249
+ for (const d of fs.readdirSync(skillsSrc)) {
250
+ if (fs.existsSync(path.join(skillsSrc, d, 'SKILL.md'))) {
251
+ skillNames.push(d);
252
+ }
253
+ }
254
+ }
255
+ ok(`found ${skillNames.length} skills: ${skillNames.join(', ')}`);
256
+
257
+ // 4. Update openclaw.json
258
+ log(`updating ${CONFIG_PATH} ...`);
259
+ const config = readJSON(CONFIG_PATH);
260
+ const pkg = readJSON(path.join(PKG_ROOT, 'package.json'));
261
+
262
+ // plugins — clean stale "eacn" entries from previous installs
263
+ if (!config.plugins) config.plugins = {};
264
+ if (!Array.isArray(config.plugins.allow)) config.plugins.allow = [];
265
+ config.plugins.allow = config.plugins.allow.filter(id => id !== 'eacn');
266
+ if (config.plugins.entries) delete config.plugins.entries['eacn'];
267
+ if (config.plugins.installs) delete config.plugins.installs['eacn'];
268
+
269
+ // plugins.allow
270
+ if (!config.plugins.allow.includes(PLUGIN_ID)) {
271
+ config.plugins.allow.push(PLUGIN_ID);
272
+ }
273
+ ok(`plugins.allow: ${PLUGIN_ID} added`);
274
+
275
+ // plugins.entries
276
+ merge(config, {
277
+ plugins: { entries: { [PLUGIN_ID]: { enabled: true, config: {} } } }
278
+ });
279
+ ok(`plugins.entries: ${PLUGIN_ID} enabled`);
280
+
281
+ // plugins.installs
282
+ if (!config.plugins.installs) config.plugins.installs = {};
283
+ config.plugins.installs[PLUGIN_ID] = {
284
+ source: 'path',
285
+ sourcePath: PKG_ROOT,
286
+ installPath: EXT_DIR,
287
+ version: pkg.version || '0.1.0',
288
+ installedAt: new Date().toISOString()
289
+ };
290
+ ok('plugins.installs: metadata recorded');
291
+
292
+ // skills.entries
293
+ if (!config.skills) config.skills = {};
294
+ if (!config.skills.entries) config.skills.entries = {};
295
+ for (const skill of skillNames) {
296
+ config.skills.entries[skill] = { enabled: true };
297
+ }
298
+ ok(`skills.entries: ${skillNames.join(', ')} registered`);
299
+
300
+ writeJSON(CONFIG_PATH, config);
301
+ ok('openclaw.json written');
302
+
303
+ // 5. Verify
304
+ console.log('\n── Verification ──');
305
+ const verified = diagnose();
306
+
307
+ if (verified) {
308
+ console.log('Setup complete. Run: openclaw gateway restart\n');
309
+ } else {
310
+ console.log('Setup finished with warnings — check above.\n');
311
+ }
312
+ }
313
+
314
+ // ── main ──────────────────────────────────────────────────────────────────────
315
+
316
+ const cmd = process.argv[2];
317
+
318
+ switch (cmd) {
319
+ case 'setup':
320
+ setupOpenclaw();
321
+ break;
322
+ case 'diagnose':
323
+ case 'diag':
324
+ case 'doctor':
325
+ diagnose();
326
+ break;
327
+ default:
328
+ console.log('Usage:');
329
+ console.log(' npx eacn setup — install plugin into OpenClaw');
330
+ console.log(' npx eacn diagnose — run full diagnostics');
331
+ process.exit(0);
332
+ }
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * postinstall.js — verify installation and report status
4
+ */
5
+
6
+ const { execFileSync } = require('child_process');
7
+ const path = require('path');
8
+ const fs = require('fs');
9
+
10
+ const PKG_ROOT = path.resolve(__dirname, '..');
11
+
12
+ function check(label, fn) {
13
+ try {
14
+ const result = fn();
15
+ console.log(` ✓ ${label}` + (result ? ` — ${result}` : ''));
16
+ return true;
17
+ } catch (e) {
18
+ console.log(` ✗ ${label} — ${e.message}`);
19
+ return false;
20
+ }
21
+ }
22
+
23
+ console.log('\neacn postinstall\n');
24
+
25
+ let ok = true;
26
+
27
+ ok = check('plugin manifest', () => {
28
+ const manifest = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'openclaw.plugin.json'), 'utf8'));
29
+ return `id=${manifest.id} v${manifest.version}`;
30
+ }) && ok;
31
+
32
+ ok = check('dist/index.js', () => {
33
+ const p = path.join(PKG_ROOT, 'dist', 'index.js');
34
+ if (!fs.existsSync(p)) throw new Error('not found — run "npm run build" first');
35
+ return 'exists';
36
+ }) && ok;
37
+
38
+ ok = check('dist/server.js', () => {
39
+ const p = path.join(PKG_ROOT, 'dist', 'server.js');
40
+ if (!fs.existsSync(p)) throw new Error('not found — run "npm run build" first');
41
+ return 'exists';
42
+ }) && ok;
43
+
44
+ ok = check('skill files', () => {
45
+ const skillsDir = path.join(PKG_ROOT, 'skills');
46
+ if (!fs.existsSync(skillsDir)) throw new Error('skills/ not found');
47
+ const skills = fs.readdirSync(skillsDir).filter(d =>
48
+ fs.statSync(path.join(skillsDir, d)).isDirectory()
49
+ );
50
+ return `${skills.length} skills (${skills.join(', ')})`;
51
+ }) && ok;
52
+
53
+ if (ok) {
54
+ console.log('\n All checks passed.\n');
55
+ } else {
56
+ console.log('\n Some checks failed — the plugin may not work correctly.');
57
+ console.log(' Run "npx eacn diagnose" for details.\n');
58
+ }
59
+
60
+ console.log('Next steps:');
61
+ console.log(' npx eacn setup — install into OpenClaw');
62
+ console.log(' npx eacn diagnose — run full diagnostics\n');