Compare commits

..

2 Commits

Author SHA1 Message Date
nav
1d751b7c55 feat: add client config validation 2026-04-08 20:03:28 +00:00
nav
c2bdb2efb6 feat: scaffold yonexus client plugin 2026-04-08 19:33:32 +00:00
12 changed files with 199 additions and 0 deletions

27
package.json Normal file
View File

@@ -0,0 +1,27 @@
{
"name": "yonexus-client",
"version": "0.1.0",
"private": true,
"description": "Yonexus.Client 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
View File

0
plugin/core/.gitkeep Normal file
View File

67
plugin/core/config.ts Normal file
View File

@@ -0,0 +1,67 @@
export interface YonexusClientConfig {
mainHost: string;
identifier: string;
notifyBotToken: string;
adminUserId: string;
}
export class YonexusClientConfigError extends Error {
readonly issues: string[];
constructor(issues: string[]) {
super(`Invalid Yonexus.Client config: ${issues.join("; ")}`);
this.name = "YonexusClientConfigError";
this.issues = issues;
}
}
function isNonEmptyString(value: unknown): value is string {
return typeof value === "string" && value.trim().length > 0;
}
function isValidWsUrl(value: string): boolean {
try {
const url = new URL(value);
return url.protocol === "ws:" || url.protocol === "wss:";
} catch {
return false;
}
}
export function validateYonexusClientConfig(raw: unknown): YonexusClientConfig {
const source = raw as Record<string, unknown> | null;
const issues: string[] = [];
const mainHost = source?.mainHost;
if (!isNonEmptyString(mainHost)) {
issues.push("mainHost is required");
} else if (!isValidWsUrl(mainHost.trim())) {
issues.push("mainHost must be a valid ws:// or wss:// URL");
}
const identifier = source?.identifier;
if (!isNonEmptyString(identifier)) {
issues.push("identifier is required");
}
const notifyBotToken = source?.notifyBotToken;
if (!isNonEmptyString(notifyBotToken)) {
issues.push("notifyBotToken is required");
}
const adminUserId = source?.adminUserId;
if (!isNonEmptyString(adminUserId)) {
issues.push("adminUserId is required");
}
if (issues.length > 0) {
throw new YonexusClientConfigError(issues);
}
return {
mainHost: mainHost.trim(),
identifier: identifier.trim(),
notifyBotToken: notifyBotToken.trim(),
adminUserId: adminUserId.trim()
};
}

0
plugin/hooks/.gitkeep Normal file
View File

View File

@@ -0,0 +1,31 @@
export { validateYonexusClientConfig, YonexusClientConfigError } from "./core/config.js";
export type { YonexusClientConfig } from "./core/config.js";
export interface YonexusClientPluginManifest {
readonly name: "Yonexus.Client";
readonly version: string;
readonly description: string;
}
export interface YonexusClientPluginRuntime {
readonly hooks: readonly [];
readonly commands: readonly [];
readonly tools: readonly [];
}
const manifest: YonexusClientPluginManifest = {
name: "Yonexus.Client",
version: "0.1.0",
description: "Yonexus client plugin for cross-instance OpenClaw communication"
};
export function createYonexusClientPlugin(): YonexusClientPluginRuntime {
return {
hooks: [],
commands: [],
tools: []
};
}
export default createYonexusClientPlugin;
export { manifest };

View File

@@ -0,0 +1,13 @@
{
"name": "Yonexus.Client",
"version": "0.1.0",
"description": "Yonexus client plugin for cross-instance OpenClaw communication",
"entry": "dist/plugin/index.js",
"permissions": [],
"config": {
"mainHost": "",
"identifier": "",
"notifyBotToken": "",
"adminUserId": ""
}
}

0
plugin/tools/.gitkeep Normal file
View File

View 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.Client";
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
View File

0
skills/.gitkeep Normal file
View File

24
tsconfig.json Normal file
View 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"
]
}