Compare commits
2 Commits
d8290c0aa7
...
3ec57ce199
| Author | SHA1 | Date | |
|---|---|---|---|
| 3ec57ce199 | |||
| ac128d3827 |
27
package.json
Normal file
27
package.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "yonexus-server",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"description": "Yonexus.Server OpenClaw plugin scaffold",
|
||||||
|
"type": "module",
|
||||||
|
"main": "dist/plugin/index.js",
|
||||||
|
"files": [
|
||||||
|
"dist",
|
||||||
|
"plugin",
|
||||||
|
"scripts",
|
||||||
|
"protocol",
|
||||||
|
"README.md",
|
||||||
|
"PLAN.md",
|
||||||
|
"SCAFFOLD.md",
|
||||||
|
"STRUCTURE.md",
|
||||||
|
"TASKS.md"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc -p tsconfig.json",
|
||||||
|
"clean": "rm -rf dist",
|
||||||
|
"check": "tsc -p tsconfig.json --noEmit"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^5.6.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
0
plugin/commands/.gitkeep
Normal file
0
plugin/commands/.gitkeep
Normal file
0
plugin/core/.gitkeep
Normal file
0
plugin/core/.gitkeep
Normal file
104
plugin/core/config.ts
Normal file
104
plugin/core/config.ts
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
export interface YonexusServerConfig {
|
||||||
|
followerIdentifiers: string[];
|
||||||
|
notifyBotToken: string;
|
||||||
|
adminUserId: string;
|
||||||
|
listenHost?: string;
|
||||||
|
listenPort: number;
|
||||||
|
publicWsUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class YonexusServerConfigError extends Error {
|
||||||
|
readonly issues: string[];
|
||||||
|
|
||||||
|
constructor(issues: string[]) {
|
||||||
|
super(`Invalid Yonexus.Server config: ${issues.join("; ")}`);
|
||||||
|
this.name = "YonexusServerConfigError";
|
||||||
|
this.issues = issues;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNonEmptyString(value: unknown): value is string {
|
||||||
|
return typeof value === "string" && value.trim().length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeOptionalString(value: unknown): string | undefined {
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value !== "string") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const trimmed = value.trim();
|
||||||
|
return trimmed.length > 0 ? trimmed : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidPort(value: unknown): value is number {
|
||||||
|
return Number.isInteger(value) && value >= 1 && value <= 65535;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidWsUrl(value: string): boolean {
|
||||||
|
try {
|
||||||
|
const url = new URL(value);
|
||||||
|
return url.protocol === "ws:" || url.protocol === "wss:";
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateYonexusServerConfig(raw: unknown): YonexusServerConfig {
|
||||||
|
const source = raw as Record<string, unknown> | null;
|
||||||
|
const issues: string[] = [];
|
||||||
|
|
||||||
|
const rawIdentifiers = source?.followerIdentifiers;
|
||||||
|
const followerIdentifiers = Array.isArray(rawIdentifiers)
|
||||||
|
? rawIdentifiers
|
||||||
|
.filter((value): value is string => typeof value === "string")
|
||||||
|
.map((value) => value.trim())
|
||||||
|
.filter((value) => value.length > 0)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
if (!Array.isArray(rawIdentifiers) || followerIdentifiers.length === 0) {
|
||||||
|
issues.push("followerIdentifiers must contain at least one non-empty identifier");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new Set(followerIdentifiers).size !== followerIdentifiers.length) {
|
||||||
|
issues.push("followerIdentifiers must not contain duplicates");
|
||||||
|
}
|
||||||
|
|
||||||
|
const notifyBotToken = source?.notifyBotToken;
|
||||||
|
if (!isNonEmptyString(notifyBotToken)) {
|
||||||
|
issues.push("notifyBotToken is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
const adminUserId = source?.adminUserId;
|
||||||
|
if (!isNonEmptyString(adminUserId)) {
|
||||||
|
issues.push("adminUserId is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
const listenPort = source?.listenPort;
|
||||||
|
if (!isValidPort(listenPort)) {
|
||||||
|
issues.push("listenPort must be an integer between 1 and 65535");
|
||||||
|
}
|
||||||
|
|
||||||
|
const listenHost = normalizeOptionalString(source?.listenHost) ?? "0.0.0.0";
|
||||||
|
const publicWsUrl = normalizeOptionalString(source?.publicWsUrl);
|
||||||
|
|
||||||
|
if (publicWsUrl !== undefined && !isValidWsUrl(publicWsUrl)) {
|
||||||
|
issues.push("publicWsUrl must be a valid ws:// or wss:// URL when provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (issues.length > 0) {
|
||||||
|
throw new YonexusServerConfigError(issues);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
followerIdentifiers,
|
||||||
|
notifyBotToken: notifyBotToken.trim(),
|
||||||
|
adminUserId: adminUserId.trim(),
|
||||||
|
listenHost,
|
||||||
|
listenPort,
|
||||||
|
publicWsUrl
|
||||||
|
};
|
||||||
|
}
|
||||||
0
plugin/hooks/.gitkeep
Normal file
0
plugin/hooks/.gitkeep
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
export { validateYonexusServerConfig, YonexusServerConfigError } from "./core/config.js";
|
||||||
|
export type { YonexusServerConfig } from "./core/config.js";
|
||||||
|
|
||||||
|
export interface YonexusServerPluginManifest {
|
||||||
|
readonly name: "Yonexus.Server";
|
||||||
|
readonly version: string;
|
||||||
|
readonly description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface YonexusServerPluginRuntime {
|
||||||
|
readonly hooks: readonly [];
|
||||||
|
readonly commands: readonly [];
|
||||||
|
readonly tools: readonly [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const manifest: YonexusServerPluginManifest = {
|
||||||
|
name: "Yonexus.Server",
|
||||||
|
version: "0.1.0",
|
||||||
|
description: "Yonexus central hub plugin for cross-instance OpenClaw communication"
|
||||||
|
};
|
||||||
|
|
||||||
|
export function createYonexusServerPlugin(): YonexusServerPluginRuntime {
|
||||||
|
return {
|
||||||
|
hooks: [],
|
||||||
|
commands: [],
|
||||||
|
tools: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createYonexusServerPlugin;
|
||||||
|
export { manifest };
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "Yonexus.Server",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Yonexus central hub plugin for cross-instance OpenClaw communication",
|
||||||
|
"entry": "dist/plugin/index.js",
|
||||||
|
"permissions": [],
|
||||||
|
"config": {
|
||||||
|
"followerIdentifiers": [],
|
||||||
|
"notifyBotToken": "",
|
||||||
|
"adminUserId": "",
|
||||||
|
"listenHost": "0.0.0.0",
|
||||||
|
"listenPort": 8787,
|
||||||
|
"publicWsUrl": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
0
plugin/tools/.gitkeep
Normal file
0
plugin/tools/.gitkeep
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import os from "node:os";
|
||||||
|
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const mode = args.includes("--install") ? "install" : args.includes("--uninstall") ? "uninstall" : null;
|
||||||
|
const profileIndex = args.indexOf("--openclaw-profile-path");
|
||||||
|
const profilePath = profileIndex >= 0 ? args[profileIndex + 1] : path.join(os.homedir(), ".openclaw");
|
||||||
|
|
||||||
|
if (!mode) {
|
||||||
|
console.error("Usage: node scripts/install.mjs --install|--uninstall [--openclaw-profile-path <path>]");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const repoRoot = path.resolve(import.meta.dirname, "..");
|
||||||
|
const pluginName = "Yonexus.Server";
|
||||||
|
const sourceDist = path.join(repoRoot, "dist");
|
||||||
|
const targetDir = path.join(profilePath, "plugins", pluginName);
|
||||||
|
|
||||||
|
if (mode === "install") {
|
||||||
|
if (!fs.existsSync(sourceDist)) {
|
||||||
|
console.error(`Build output not found: ${sourceDist}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.mkdirSync(path.dirname(targetDir), { recursive: true });
|
||||||
|
fs.rmSync(targetDir, { recursive: true, force: true });
|
||||||
|
fs.cpSync(sourceDist, path.join(targetDir, "dist"), { recursive: true });
|
||||||
|
fs.copyFileSync(path.join(repoRoot, "plugin", "openclaw.plugin.json"), path.join(targetDir, "openclaw.plugin.json"));
|
||||||
|
console.log(`Installed ${pluginName} to ${targetDir}`);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.rmSync(targetDir, { recursive: true, force: true });
|
||||||
|
console.log(`Removed ${pluginName} from ${targetDir}`);
|
||||||
|
|||||||
0
servers/.gitkeep
Normal file
0
servers/.gitkeep
Normal file
0
skills/.gitkeep
Normal file
0
skills/.gitkeep
Normal file
24
tsconfig.json
Normal file
24
tsconfig.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": ".",
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"declaration": true,
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"plugin/**/*.ts",
|
||||||
|
"servers/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"dist",
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user