Compare commits

...

4 Commits

Author SHA1 Message Date
220ec85e6f Clean OpenClaw config on uninstall 2026-03-10 18:27:54 +00:00
00108c357b Add OpenClaw manifest and configure install 2026-03-10 18:25:54 +00:00
3b26f3d083 Merge pull request 'refactor: restructure project layout and add install.mjs' (#3) from feat/restructure-and-install-script into main
Reviewed-on: #3
2026-03-10 14:46:58 +00:00
zhi
a0e926594f refactor: restructure project layout and add install.mjs
- Move src/ → plugin/ with subdirectories:
  - plugin/core/ (business logic, models, store, permissions, utils, memory)
  - plugin/tools/ (query, resources)
  - plugin/commands/ (placeholder for slash commands)
  - plugin/hooks/ (placeholder for lifecycle hooks)
  - plugin/index.ts (wiring layer only, no business logic)
- Add install.mjs with --install, --uninstall, --openclaw-profile-path
- Add skills/ and docs/ root directories
- Move planning docs (PLAN.md, FEAT.md, AGENT_TASKS.md) to docs/
- Remove old scripts/install.sh
- Update tsconfig rootDir: src → plugin
- Update README.md and README.zh.md with new layout
- Bump version to 0.2.0
- All tests pass
2026-03-10 14:44:40 +00:00
31 changed files with 337 additions and 81 deletions

View File

@@ -24,24 +24,20 @@ Yonexus is an OpenClaw plugin for organization hierarchy and agent identity mana
```text
.
├─ plugin.json
├─ src/
│ ├─ index.ts
│ ├─ models/
│ ├─ permissions/
store/
│ ├─ tools/
│ ├─ memory/
│ └─ utils/
├─ scripts/
├─ install.sh
│ └─ demo.ts
├─ tests/
│ └─ smoke.ts
├─ examples/
│ └─ sample-data.json
└─ dist/
└─ yonexus/
├─ plugin/
│ ├─ index.ts # wiring: init, register commands/hooks/tools
│ ├─ commands/ # slash commands
│ ├─ tools/ # query & resource tools
│ ├─ hooks/ # lifecycle hooks
core/ # business logic, models, store, permissions
├─ skills/ # skill definitions
├─ docs/ # project documentation
├─ scripts/ # demo & utility scripts
├─ tests/ # tests
├─ install.mjs # install/uninstall script
├─ plugin.json # plugin manifest
├─ README.md
└─ README.zh.md
```
## Requirements
@@ -54,11 +50,23 @@ Yonexus is an OpenClaw plugin for organization hierarchy and agent identity mana
```bash
npm install
npm run build
bash scripts/install.sh
npm run test:smoke
npm run demo
```
## Install / Uninstall
```bash
# Install (builds and copies to ~/.openclaw/plugins/yonexus)
node install.mjs --install
# Install to custom openclaw profile path
node install.mjs --install --openclaw-profile-path /path/to/.openclaw
# Uninstall
node install.mjs --uninstall
```
## Configuration
`plugin.json` includes default config:
@@ -98,8 +106,6 @@ Data & audit:
## Testing
Smoke test:
```bash
npm run test:smoke
```

View File

@@ -24,24 +24,20 @@ Yonexus 是一个用于 OpenClaw 的组织结构与 Agent 身份管理插件。
```text
.
├─ plugin.json
├─ src/
│ ├─ index.ts
│ ├─ models/
│ ├─ permissions/
store/
│ ├─ tools/
│ ├─ memory/
│ └─ utils/
├─ scripts/
├─ install.sh
│ └─ demo.ts
├─ tests/
│ └─ smoke.ts
├─ examples/
│ └─ sample-data.json
└─ dist/
└─ yonexus/
├─ plugin/
│ ├─ index.ts # 接线层:初始化、注册命令/hooks/tools
│ ├─ commands/ # slash commands
│ ├─ tools/ # 查询与资源工具
│ ├─ hooks/ # 生命周期钩子
core/ # 业务逻辑、模型、存储、权限
├─ skills/ # 技能定义
├─ docs/ # 项目文档
├─ scripts/ # 演示与工具脚本
├─ tests/ # 测试
├─ install.mjs # 安装/卸载脚本
├─ plugin.json # 插件清单
├─ README.md
└─ README.zh.md
```
## 环境要求
@@ -54,11 +50,23 @@ Yonexus 是一个用于 OpenClaw 的组织结构与 Agent 身份管理插件。
```bash
npm install
npm run build
bash scripts/install.sh
npm run test:smoke
npm run demo
```
## 安装 / 卸载
```bash
# 安装(构建并复制到 ~/.openclaw/plugins/yonexus
node install.mjs --install
# 安装到自定义 openclaw profile 路径
node install.mjs --install --openclaw-profile-path /path/to/.openclaw
# 卸载
node install.mjs --uninstall
```
## 配置说明
`plugin.json` 默认包含以下配置:
@@ -98,8 +106,6 @@ npm run demo
## 测试
冒烟测试:
```bash
npm run test:smoke
```

0
docs/.gitkeep Normal file
View File

209
install.mjs Normal file
View File

@@ -0,0 +1,209 @@
#!/usr/bin/env node
/**
* Yonexus Plugin Installer v0.2.0
*
* Usage:
* node install.mjs --install
* node install.mjs --install --openclaw-profile-path /path/to/.openclaw
* node install.mjs --uninstall
* node install.mjs --uninstall --openclaw-profile-path /path/to/.openclaw
*/
import { execSync } from 'child_process';
import { existsSync, mkdirSync, copyFileSync, readdirSync, rmSync } from 'fs';
import { dirname, join, resolve } from 'path';
import { fileURLToPath } from 'url';
import { homedir } from 'os';
const __filename = fileURLToPath(import.meta.url);
const __dirname = resolve(dirname(__filename));
const PLUGIN_NAME = 'yonexus';
const SRC_DIST_DIR = join(__dirname, 'dist', PLUGIN_NAME);
// ── Parse arguments ─────────────────────────────────────────────────────
const args = process.argv.slice(2);
const isInstall = args.includes('--install');
const isUninstall = args.includes('--uninstall');
const profileIdx = args.indexOf('--openclaw-profile-path');
let openclawProfilePath = null;
if (profileIdx !== -1 && args[profileIdx + 1]) {
openclawProfilePath = resolve(args[profileIdx + 1]);
}
function resolveOpenclawPath() {
if (openclawProfilePath) return openclawProfilePath;
if (process.env.OPENCLAW_PATH) return resolve(process.env.OPENCLAW_PATH);
return join(homedir(), '.openclaw');
}
// ── Colors ──────────────────────────────────────────────────────────────
const c = {
reset: '\x1b[0m', red: '\x1b[31m', green: '\x1b[32m',
yellow: '\x1b[33m', blue: '\x1b[34m', cyan: '\x1b[36m',
};
function log(msg, color = 'reset') { console.log(`${c[color]}${msg}${c.reset}`); }
function logOk(msg) { log(`${msg}`, 'green'); }
function logWarn(msg) { log(`${msg}`, 'yellow'); }
function logErr(msg) { log(`${msg}`, 'red'); }
// ── Helpers ─────────────────────────────────────────────────────────────
function copyDir(src, dest) {
mkdirSync(dest, { recursive: true });
for (const entry of readdirSync(src, { withFileTypes: true })) {
const s = join(src, entry.name);
const d = join(dest, entry.name);
if (entry.name === 'node_modules') continue;
entry.isDirectory() ? copyDir(s, d) : copyFileSync(s, d);
}
}
// ── Install ─────────────────────────────────────────────────────────────
function install() {
console.log('');
log('╔══════════════════════════════════════════════╗', 'cyan');
log('║ Yonexus Plugin Installer v0.2.0 ║', 'cyan');
log('╚══════════════════════════════════════════════╝', 'cyan');
console.log('');
// 1. Build
log('[1/4] Building...', 'cyan');
execSync('npm install', { cwd: __dirname, stdio: 'inherit' });
execSync('npm run build', { cwd: __dirname, stdio: 'inherit' });
if (!existsSync(SRC_DIST_DIR)) {
logErr(`Build output not found at ${SRC_DIST_DIR}`);
process.exit(1);
}
logOk('Build complete');
// 2. Copy to plugins dir
log('[2/4] Installing...', 'cyan');
const openclawPath = resolveOpenclawPath();
const destDir = join(openclawPath, 'plugins', PLUGIN_NAME);
log(` OpenClaw path: ${openclawPath}`, 'blue');
if (existsSync(destDir)) rmSync(destDir, { recursive: true, force: true });
copyDir(SRC_DIST_DIR, destDir);
logOk(`Plugin files → ${destDir}`);
// 3. Configure OpenClaw
log('[3/4] Configuring OpenClaw...', 'cyan');
try {
const pluginPath = destDir;
const allow = ensureArray(getConfigValue('plugins.allow'));
const loadPaths = ensureArray(getConfigValue('plugins.load.paths'));
if (!allow.includes(PLUGIN_NAME)) allow.push(PLUGIN_NAME);
if (!loadPaths.includes(pluginPath)) loadPaths.push(pluginPath);
execSync(`openclaw config set plugins.allow '${JSON.stringify(allow)}'`);
execSync(`openclaw config set plugins.load.paths '${JSON.stringify(loadPaths)}'`);
execSync(`openclaw config set plugins.entries.${PLUGIN_NAME}.enabled true`);
execSync(`openclaw config set plugins.entries.${PLUGIN_NAME}.config '{"enabled": true}'`);
logOk('OpenClaw config updated');
} catch (err) {
logErr('Failed to update OpenClaw config via `openclaw config set`');
throw err;
}
// 4. Summary
log('[4/4] Done!', 'cyan');
console.log('');
log('✓ Yonexus installed successfully!', 'green');
console.log('');
log('Next steps:', 'blue');
log(' openclaw gateway restart', 'cyan');
console.log('');
}
function getConfigValue(path) {
try {
const out = execSync(`openclaw config get ${path}`, { encoding: 'utf8' }).trim();
if (!out || out === 'undefined' || out === 'null') return undefined;
try { return JSON.parse(out); } catch { return out; }
} catch {
return undefined;
}
}
function ensureArray(value) {
if (Array.isArray(value)) return value;
if (value === undefined || value === null || value === '') return [];
return [value];
}
// ── Uninstall ───────────────────────────────────────────────────────────
function uninstall() {
console.log('');
log('Uninstalling Yonexus...', 'cyan');
const openclawPath = resolveOpenclawPath();
const destDir = join(openclawPath, 'plugins', PLUGIN_NAME);
if (existsSync(destDir)) {
rmSync(destDir, { recursive: true, force: true });
logOk(`Removed ${destDir}`);
} else {
logWarn(`${destDir} not found, nothing to remove`);
}
// Clean OpenClaw config
log('Cleaning OpenClaw config...', 'cyan');
try {
const allow = ensureArray(getConfigValue('plugins.allow')).filter((id) => id !== PLUGIN_NAME);
const loadPaths = ensureArray(getConfigValue('plugins.load.paths')).filter((p) => p !== destDir);
if (allow.length > 0) {
execSync(`openclaw config set plugins.allow '${JSON.stringify(allow)}'`);
} else {
execSync('openclaw config unset plugins.allow');
}
if (loadPaths.length > 0) {
execSync(`openclaw config set plugins.load.paths '${JSON.stringify(loadPaths)}'`);
} else {
execSync('openclaw config unset plugins.load.paths');
}
execSync(`openclaw config unset plugins.entries.${PLUGIN_NAME}`);
logOk('OpenClaw config cleaned');
} catch (err) {
logErr('Failed to clean OpenClaw config via `openclaw config`');
throw err;
}
console.log('');
log('✓ Yonexus uninstalled.', 'green');
log('\nNext: openclaw gateway restart', 'yellow');
console.log('');
}
// ── Main ────────────────────────────────────────────────────────────────
if (!isInstall && !isUninstall) {
console.log('');
log('Yonexus Plugin Installer', 'cyan');
console.log('');
log('Usage:', 'blue');
log(' node install.mjs --install Install plugin', 'reset');
log(' node install.mjs --install --openclaw-profile-path <path> Install to custom path', 'reset');
log(' node install.mjs --uninstall Uninstall plugin', 'reset');
log(' node install.mjs --uninstall --openclaw-profile-path <path> Uninstall from custom path', 'reset');
console.log('');
process.exit(1);
}
if (isInstall) install();
if (isUninstall) uninstall();

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "openclaw-plugin-yonexus",
"version": "0.1.0",
"version": "0.2.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openclaw-plugin-yonexus",
"version": "0.1.0",
"version": "0.2.0",
"license": "MIT",
"devDependencies": {
"@types/node": "^22.13.10",

View File

@@ -1,11 +1,11 @@
{
"name": "openclaw-plugin-yonexus",
"version": "0.1.0",
"version": "0.2.0",
"description": "Yonexus OpenClaw plugin: hierarchy, identities, permissions, and scoped memory",
"main": "dist/yonexus/index.js",
"types": "dist/yonexus/index.d.ts",
"scripts": {
"build": "tsc -p tsconfig.json",
"build": "tsc -p tsconfig.json && cp plugin.json dist/yonexus/plugin.json && cp plugin/openclaw.plugin.json dist/yonexus/openclaw.plugin.json",
"clean": "rm -rf dist",
"prepare": "npm run clean && npm run build",
"test:smoke": "tsx tests/smoke.ts",

0
plugin/commands/.gitkeep Normal file
View File

View File

@@ -16,8 +16,8 @@ import type {
import { authorize } from "./permissions/authorize";
import { AuditStore } from "./store/auditStore";
import { JsonStore } from "./store/jsonStore";
import { queryIdentities } from "./tools/query";
import { ResourceLayout } from "./tools/resources";
import { queryIdentities } from "../tools/query";
import { ResourceLayout } from "../tools/resources";
import { makeId } from "./utils/id";
export interface YonexusOptions {
@@ -243,5 +243,3 @@ export class Yonexus {
return this.store.snapshot();
}
}
export default Yonexus;

0
plugin/hooks/.gitkeep Normal file
View File

45
plugin/index.ts Normal file
View File

@@ -0,0 +1,45 @@
/**
* Yonexus Plugin Entry
*
* This file is the wiring layer: it initializes and re-exports the core
* Yonexus class along with models, tools, and memory adapters.
* No business logic lives here.
*/
// ── Core ────────────────────────────────────────────────────────────────
export { Yonexus, type YonexusOptions } from "./core/yonexus";
// ── Models ──────────────────────────────────────────────────────────────
export { YonexusError } from "./core/models/errors";
export type { ErrorCode } from "./core/models/errors";
export type { AuditLogEntry } from "./core/models/audit";
export type {
Action,
Actor,
Agent,
Department,
DocsScope,
DocsTopic,
Identity,
Organization,
QueryFilter,
QueryInput,
QueryOptions,
Role,
SchemaField,
Scope,
StoreState,
Supervisor,
Team,
YonexusSchema,
} from "./core/models/types";
// ── Tools ───────────────────────────────────────────────────────────────
export { queryIdentities } from "./tools/query";
export { ResourceLayout } from "./tools/resources";
// ── Memory ──────────────────────────────────────────────────────────────
export { ScopeMemory, type MemoryPort } from "./core/memory/scopeMemory";
// ── Default export ──────────────────────────────────────────────────────
export { Yonexus as default } from "./core/yonexus";

View File

@@ -0,0 +1,13 @@
{
"id": "yonexus",
"name": "Yonexus",
"version": "0.2.0",
"description": "Organization hierarchy, agent identity management, query DSL, and scope memory for OpenClaw",
"configSchema": {
"type": "object",
"additionalProperties": true,
"properties": {
"enabled": { "type": "boolean", "default": true }
}
}
}

View File

@@ -1,5 +1,5 @@
import { YonexusError } from '../models/errors';
import type { Identity, QueryFilter, QueryInput, QueryOptions, YonexusSchema } from "../models/types";
import { YonexusError } from '../core/models/errors';
import type { Identity, QueryFilter, QueryInput, QueryOptions, YonexusSchema } from "../core/models/types";
const DEFAULT_LIMIT = 20;
const MAX_LIMIT = 100;

View File

@@ -1,8 +1,8 @@
import fs from 'node:fs';
import path from 'node:path';
import { YonexusError } from '../models/errors';
import type { DocsScope, DocsTopic } from '../models/types';
import { slug } from '../utils/id';
import { YonexusError } from '../core/models/errors';
import type { DocsScope, DocsTopic } from '../core/models/types';
import { slug } from '../core/utils/id';
const TOPICS: DocsTopic[] = ['docs', 'notes', 'knowledge', 'rules', 'lessons', 'workflows'];

View File

@@ -1,6 +1,6 @@
import path from 'node:path';
import fs from 'node:fs';
import { Yonexus } from '../src/index';
import { Yonexus } from '../plugin/index';
const dataFile = path.resolve(process.cwd(), 'data/demo-org.json');
if (fs.existsSync(dataFile)) fs.unlinkSync(dataFile);

View File

@@ -1,21 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# Yonexus install script
# - Registers plugin name: yonexus
# - Places build output in dist/yonexus
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
DIST_DIR="$ROOT_DIR/dist/yonexus"
cd "$ROOT_DIR"
if [ ! -d node_modules ]; then
npm install
fi
npm run build
mkdir -p "$DIST_DIR"
cp -f "$ROOT_DIR/plugin.json" "$DIST_DIR/plugin.json"
echo "[yonexus] install complete -> $DIST_DIR"

0
skills/.gitkeep Normal file
View File

View File

@@ -1,8 +1,8 @@
import assert from 'node:assert';
import path from 'node:path';
import fs from 'node:fs';
import { Yonexus } from '../src/index';
import { YonexusError } from '../src/models/errors';
import { Yonexus } from '../plugin/index';
import { YonexusError } from '../plugin/core/models/errors';
const root = path.resolve(process.cwd(), 'data/test-openclaw');
const dataFile = path.resolve(process.cwd(), 'data/test-org.json');

View File

@@ -8,9 +8,9 @@
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"outDir": "dist/yonexus",
"rootDir": "src",
"rootDir": "plugin",
"declaration": true,
"types": ["node"]
},
"include": ["src/**/*.ts"]
"include": ["plugin/**/*.ts"]
}