zigrix 0.1.0-alpha.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/LICENSE +184 -0
- package/README.md +128 -0
- package/dist/agents/registry.d.ts +28 -0
- package/dist/agents/registry.js +98 -0
- package/dist/config/defaults.d.ts +66 -0
- package/dist/config/defaults.js +58 -0
- package/dist/config/load.d.ts +13 -0
- package/dist/config/load.js +96 -0
- package/dist/config/mutate.d.ts +10 -0
- package/dist/config/mutate.js +61 -0
- package/dist/config/schema.d.ts +132 -0
- package/dist/config/schema.js +123 -0
- package/dist/configure.d.ts +24 -0
- package/dist/configure.js +164 -0
- package/dist/doctor.d.ts +4 -0
- package/dist/doctor.js +99 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +791 -0
- package/dist/onboard.d.ts +99 -0
- package/dist/onboard.js +490 -0
- package/dist/orchestration/dispatch.d.ts +9 -0
- package/dist/orchestration/dispatch.js +146 -0
- package/dist/orchestration/evidence.d.ts +19 -0
- package/dist/orchestration/evidence.js +97 -0
- package/dist/orchestration/finalize.d.ts +7 -0
- package/dist/orchestration/finalize.js +136 -0
- package/dist/orchestration/pipeline.d.ts +11 -0
- package/dist/orchestration/pipeline.js +26 -0
- package/dist/orchestration/report.d.ts +5 -0
- package/dist/orchestration/report.js +92 -0
- package/dist/orchestration/worker.d.ts +34 -0
- package/dist/orchestration/worker.js +132 -0
- package/dist/rules/templates.d.ts +24 -0
- package/dist/rules/templates.js +73 -0
- package/dist/runner/run.d.ts +10 -0
- package/dist/runner/run.js +91 -0
- package/dist/runner/schema.d.ts +33 -0
- package/dist/runner/schema.js +10 -0
- package/dist/runner/store.d.ts +5 -0
- package/dist/runner/store.js +19 -0
- package/dist/state/events.d.ts +3 -0
- package/dist/state/events.js +53 -0
- package/dist/state/paths.d.ts +15 -0
- package/dist/state/paths.js +23 -0
- package/dist/state/tasks.d.ts +61 -0
- package/dist/state/tasks.js +247 -0
- package/dist/state/verify.d.ts +2 -0
- package/dist/state/verify.js +65 -0
- package/examples/hello-workflow.json +13 -0
- package/package.json +44 -0
- package/skills/zigrix-doctor/SKILL.md +20 -0
- package/skills/zigrix-evidence/SKILL.md +21 -0
- package/skills/zigrix-init/SKILL.md +23 -0
- package/skills/zigrix-report/SKILL.md +20 -0
- package/skills/zigrix-shared/SKILL.md +31 -0
- package/skills/zigrix-task-create/SKILL.md +34 -0
- package/skills/zigrix-task-status/SKILL.md +27 -0
- package/skills/zigrix-worker/SKILL.md +22 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { ZigrixConfig } from './config/schema.js';
|
|
2
|
+
import { type ZigrixPaths } from './state/paths.js';
|
|
3
|
+
export interface OpenClawAgent {
|
|
4
|
+
id: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
default?: boolean;
|
|
7
|
+
identity?: {
|
|
8
|
+
name?: string;
|
|
9
|
+
theme?: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export interface OpenClawConfig {
|
|
13
|
+
agents?: {
|
|
14
|
+
list?: OpenClawAgent[];
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export interface PathStabilizeResult {
|
|
18
|
+
alreadyInPath: boolean;
|
|
19
|
+
symlinkCreated: boolean;
|
|
20
|
+
symlinkPath: string | null;
|
|
21
|
+
warning: string | null;
|
|
22
|
+
}
|
|
23
|
+
export interface SkillRegistrationResult {
|
|
24
|
+
registered: string[];
|
|
25
|
+
skipped: string[];
|
|
26
|
+
failed: string[];
|
|
27
|
+
}
|
|
28
|
+
export interface OnboardResult {
|
|
29
|
+
ok: boolean;
|
|
30
|
+
action: string;
|
|
31
|
+
baseDir: string;
|
|
32
|
+
configPath: string;
|
|
33
|
+
paths: ZigrixPaths;
|
|
34
|
+
openclawDetected: boolean;
|
|
35
|
+
openclawHome: string;
|
|
36
|
+
agentsRegistered: string[];
|
|
37
|
+
agentsSkipped: string[];
|
|
38
|
+
rulesCopied: string[];
|
|
39
|
+
rulesSkipped: string[];
|
|
40
|
+
skillsRegistered: string[];
|
|
41
|
+
skillsSkipped: string[];
|
|
42
|
+
skillsFailed: string[];
|
|
43
|
+
pathStabilized: PathStabilizeResult;
|
|
44
|
+
warnings: string[];
|
|
45
|
+
checks: {
|
|
46
|
+
zigrixInPath: boolean;
|
|
47
|
+
openclawSkillsDir: boolean;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export interface RunOnboardOptions {
|
|
51
|
+
yes?: boolean;
|
|
52
|
+
projectDir?: string;
|
|
53
|
+
silent?: boolean;
|
|
54
|
+
}
|
|
55
|
+
export declare function detectOpenClawHome(): string;
|
|
56
|
+
export declare function loadOpenClawConfig(openclawHome: string): OpenClawConfig | null;
|
|
57
|
+
export declare function filterAgents(agents: OpenClawAgent[]): OpenClawAgent[];
|
|
58
|
+
export declare function registerAgents(config: ZigrixConfig, agents: OpenClawAgent[]): {
|
|
59
|
+
config: ZigrixConfig;
|
|
60
|
+
registered: string[];
|
|
61
|
+
skipped: string[];
|
|
62
|
+
};
|
|
63
|
+
export declare function seedRules(sourceDir: string, targetDir: string): {
|
|
64
|
+
copied: string[];
|
|
65
|
+
skipped: string[];
|
|
66
|
+
};
|
|
67
|
+
export declare function checkZigrixInPath(): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Resolve the path to the zigrix CLI entry point (dist/index.js).
|
|
70
|
+
* Works whether installed via npm link, npm install -g, or local checkout.
|
|
71
|
+
*/
|
|
72
|
+
export declare function resolveZigrixBin(): string | null;
|
|
73
|
+
/**
|
|
74
|
+
* Preferred user-local bin directory for symlink placement.
|
|
75
|
+
* Returns the first writable directory from a priority list,
|
|
76
|
+
* or falls back to ~/.local/bin (creating it if needed).
|
|
77
|
+
*/
|
|
78
|
+
export declare function findUserBinDir(): string;
|
|
79
|
+
/**
|
|
80
|
+
* Ensure zigrix is reachable from PATH.
|
|
81
|
+
* If not found, creates a wrapper script in ~/.local/bin/.
|
|
82
|
+
*/
|
|
83
|
+
export declare function ensureZigrixInPath(): PathStabilizeResult;
|
|
84
|
+
/**
|
|
85
|
+
* Find the skills/ directory bundled with this zigrix package.
|
|
86
|
+
*/
|
|
87
|
+
export declare function resolveSkillsDir(): string | null;
|
|
88
|
+
/**
|
|
89
|
+
* Register zigrix skill packs into OpenClaw's skills directory.
|
|
90
|
+
* Creates symlinks from ~/.openclaw/skills/<skill-name> → zigrix/skills/<skill-name>.
|
|
91
|
+
* Idempotent: skips skills that already exist (unless they point elsewhere).
|
|
92
|
+
*/
|
|
93
|
+
export declare function registerSkills(openclawHome: string): SkillRegistrationResult;
|
|
94
|
+
/**
|
|
95
|
+
* Interactive agent picker using @inquirer/prompts checkbox.
|
|
96
|
+
* Space to toggle, Enter to confirm. All agents pre-selected by default.
|
|
97
|
+
*/
|
|
98
|
+
export declare function promptAgentSelection(agents: OpenClawAgent[]): Promise<OpenClawAgent[]>;
|
|
99
|
+
export declare function runOnboard(options: RunOnboardOptions): Promise<OnboardResult>;
|
package/dist/onboard.js
ADDED
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { addAgent } from './agents/registry.js';
|
|
5
|
+
import { loadConfig, writeConfigFile, writeDefaultConfig } from './config/load.js';
|
|
6
|
+
import { ensureBaseState, resolvePaths } from './state/paths.js';
|
|
7
|
+
import { rebuildIndex } from './state/tasks.js';
|
|
8
|
+
// ─── OpenClaw detection ───────────────────────────────────────────────────────
|
|
9
|
+
export function detectOpenClawHome() {
|
|
10
|
+
return process.env.OPENCLAW_HOME
|
|
11
|
+
? path.resolve(process.env.OPENCLAW_HOME)
|
|
12
|
+
: path.join(process.env.HOME ?? '~', '.openclaw');
|
|
13
|
+
}
|
|
14
|
+
export function loadOpenClawConfig(openclawHome) {
|
|
15
|
+
const configPath = path.join(openclawHome, 'openclaw.json');
|
|
16
|
+
if (!fs.existsSync(configPath))
|
|
17
|
+
return null;
|
|
18
|
+
try {
|
|
19
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
20
|
+
return JSON.parse(raw);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// ─── Agent filtering ──────────────────────────────────────────────────────────
|
|
27
|
+
export function filterAgents(agents) {
|
|
28
|
+
return agents.filter((a) => a.id !== 'main');
|
|
29
|
+
}
|
|
30
|
+
// ─── Agent registration ───────────────────────────────────────────────────────
|
|
31
|
+
export function registerAgents(config, agents) {
|
|
32
|
+
let current = structuredClone(config);
|
|
33
|
+
const registered = [];
|
|
34
|
+
const skipped = [];
|
|
35
|
+
for (const agent of agents) {
|
|
36
|
+
// idempotent: skip already-registered agents
|
|
37
|
+
if (current.agents.registry[agent.id]) {
|
|
38
|
+
skipped.push(agent.id);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const role = agent.identity?.theme ?? 'assistant';
|
|
42
|
+
const result = addAgent(current, {
|
|
43
|
+
id: agent.id,
|
|
44
|
+
role,
|
|
45
|
+
runtime: 'openclaw',
|
|
46
|
+
label: agent.name ?? agent.id,
|
|
47
|
+
enabled: true,
|
|
48
|
+
include: true,
|
|
49
|
+
});
|
|
50
|
+
current = result.config;
|
|
51
|
+
registered.push(agent.id);
|
|
52
|
+
}
|
|
53
|
+
return { config: current, registered, skipped };
|
|
54
|
+
}
|
|
55
|
+
// ─── Rules seeding ────────────────────────────────────────────────────────────
|
|
56
|
+
export function seedRules(sourceDir, targetDir) {
|
|
57
|
+
const copied = [];
|
|
58
|
+
const skipped = [];
|
|
59
|
+
if (!fs.existsSync(sourceDir)) {
|
|
60
|
+
return { copied, skipped };
|
|
61
|
+
}
|
|
62
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
63
|
+
const files = fs.readdirSync(sourceDir).filter((f) => f.endsWith('.md'));
|
|
64
|
+
for (const file of files) {
|
|
65
|
+
const dest = path.join(targetDir, file);
|
|
66
|
+
if (fs.existsSync(dest)) {
|
|
67
|
+
skipped.push(file);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
fs.copyFileSync(path.join(sourceDir, file), dest);
|
|
71
|
+
copied.push(file);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { copied, skipped };
|
|
75
|
+
}
|
|
76
|
+
// ─── PATH check and stabilization ─────────────────────────────────────────────
|
|
77
|
+
export function checkZigrixInPath() {
|
|
78
|
+
const pathEnv = process.env.PATH ?? '';
|
|
79
|
+
const dirs = pathEnv.split(path.delimiter).filter(Boolean);
|
|
80
|
+
for (const dir of dirs) {
|
|
81
|
+
try {
|
|
82
|
+
if (!fs.existsSync(dir))
|
|
83
|
+
continue;
|
|
84
|
+
const entries = fs.readdirSync(dir);
|
|
85
|
+
if (entries.includes('zigrix') ||
|
|
86
|
+
entries.includes('zigrix.cmd') ||
|
|
87
|
+
entries.includes('zigrix.exe')) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// skip unreadable dirs
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Resolve the path to the zigrix CLI entry point (dist/index.js).
|
|
99
|
+
* Works whether installed via npm link, npm install -g, or local checkout.
|
|
100
|
+
*/
|
|
101
|
+
export function resolveZigrixBin() {
|
|
102
|
+
// Strategy 1: __dirname-based (works when running from dist/)
|
|
103
|
+
try {
|
|
104
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
105
|
+
const distDir = path.dirname(thisFile);
|
|
106
|
+
const candidate = path.join(distDir, 'index.js');
|
|
107
|
+
if (fs.existsSync(candidate))
|
|
108
|
+
return candidate;
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// not an ESM context or import.meta.url unavailable
|
|
112
|
+
}
|
|
113
|
+
// Strategy 2: walk up from this file to find package.json → bin
|
|
114
|
+
try {
|
|
115
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
116
|
+
let dir = path.dirname(thisFile);
|
|
117
|
+
for (let i = 0; i < 5; i++) {
|
|
118
|
+
const pkgPath = path.join(dir, 'package.json');
|
|
119
|
+
if (fs.existsSync(pkgPath)) {
|
|
120
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
121
|
+
if (pkg.bin?.zigrix) {
|
|
122
|
+
const binPath = path.resolve(dir, pkg.bin.zigrix);
|
|
123
|
+
if (fs.existsSync(binPath))
|
|
124
|
+
return binPath;
|
|
125
|
+
}
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
dir = path.dirname(dir);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// ignore
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Preferred user-local bin directory for symlink placement.
|
|
138
|
+
* Returns the first writable directory from a priority list,
|
|
139
|
+
* or falls back to ~/.local/bin (creating it if needed).
|
|
140
|
+
*/
|
|
141
|
+
export function findUserBinDir() {
|
|
142
|
+
const home = process.env.HOME ?? '~';
|
|
143
|
+
const candidates = [
|
|
144
|
+
path.join(home, '.local', 'bin'),
|
|
145
|
+
];
|
|
146
|
+
// Also check PATH dirs that are user-writable
|
|
147
|
+
const pathEnv = process.env.PATH ?? '';
|
|
148
|
+
for (const dir of pathEnv.split(path.delimiter).filter(Boolean)) {
|
|
149
|
+
if (!dir.startsWith(home))
|
|
150
|
+
continue;
|
|
151
|
+
try {
|
|
152
|
+
if (fs.existsSync(dir)) {
|
|
153
|
+
fs.accessSync(dir, fs.constants.W_OK);
|
|
154
|
+
return dir;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
// not writable
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return candidates[0];
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Ensure zigrix is reachable from PATH.
|
|
165
|
+
* If not found, creates a wrapper script in ~/.local/bin/.
|
|
166
|
+
*/
|
|
167
|
+
export function ensureZigrixInPath() {
|
|
168
|
+
if (checkZigrixInPath()) {
|
|
169
|
+
return { alreadyInPath: true, symlinkCreated: false, symlinkPath: null, warning: null };
|
|
170
|
+
}
|
|
171
|
+
const binEntry = resolveZigrixBin();
|
|
172
|
+
if (!binEntry) {
|
|
173
|
+
return {
|
|
174
|
+
alreadyInPath: false,
|
|
175
|
+
symlinkCreated: false,
|
|
176
|
+
symlinkPath: null,
|
|
177
|
+
warning: 'Could not locate zigrix entry point. Ensure zigrix is installed via npm.',
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const userBinDir = findUserBinDir();
|
|
181
|
+
try {
|
|
182
|
+
fs.mkdirSync(userBinDir, { recursive: true });
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return {
|
|
186
|
+
alreadyInPath: false,
|
|
187
|
+
symlinkCreated: false,
|
|
188
|
+
symlinkPath: null,
|
|
189
|
+
warning: `Could not create ${userBinDir}. Create it manually and add to PATH.`,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
const symlinkPath = path.join(userBinDir, 'zigrix');
|
|
193
|
+
// Create a wrapper script that invokes node with the correct entry
|
|
194
|
+
const wrapper = `#!/usr/bin/env node\nimport('${binEntry.replace(/\\/g, '/')}');\n`;
|
|
195
|
+
try {
|
|
196
|
+
// Remove existing if present (stale symlink or old wrapper)
|
|
197
|
+
if (fs.existsSync(symlinkPath) || fs.lstatSync(symlinkPath).isSymbolicLink()) {
|
|
198
|
+
fs.unlinkSync(symlinkPath);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// doesn't exist, fine
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
// Prefer symlink to the actual CLI bin (simpler, no wrapper needed)
|
|
206
|
+
fs.symlinkSync(binEntry, symlinkPath);
|
|
207
|
+
fs.chmodSync(symlinkPath, 0o755);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
// Symlink failed, try writing a wrapper script
|
|
211
|
+
try {
|
|
212
|
+
fs.writeFileSync(symlinkPath, `#!/usr/bin/env node\nimport('${binEntry.replace(/\\/g, '/')}');\n`, { mode: 0o755 });
|
|
213
|
+
}
|
|
214
|
+
catch (e) {
|
|
215
|
+
return {
|
|
216
|
+
alreadyInPath: false,
|
|
217
|
+
symlinkCreated: false,
|
|
218
|
+
symlinkPath: null,
|
|
219
|
+
warning: `Failed to create symlink or wrapper at ${symlinkPath}: ${e instanceof Error ? e.message : String(e)}`,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// Check if userBinDir is actually in PATH
|
|
224
|
+
const pathEnv = process.env.PATH ?? '';
|
|
225
|
+
const inPath = pathEnv.split(path.delimiter).some((d) => path.resolve(d) === path.resolve(userBinDir));
|
|
226
|
+
let warning = null;
|
|
227
|
+
if (!inPath) {
|
|
228
|
+
warning = `Created zigrix at ${symlinkPath}, but ${userBinDir} is not in your PATH. Add it:\n export PATH="${userBinDir}:$PATH"`;
|
|
229
|
+
}
|
|
230
|
+
return { alreadyInPath: false, symlinkCreated: true, symlinkPath, warning };
|
|
231
|
+
}
|
|
232
|
+
// ─── OpenClaw skill registration ──────────────────────────────────────────────
|
|
233
|
+
/**
|
|
234
|
+
* Find the skills/ directory bundled with this zigrix package.
|
|
235
|
+
*/
|
|
236
|
+
export function resolveSkillsDir() {
|
|
237
|
+
try {
|
|
238
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
239
|
+
let dir = path.dirname(thisFile);
|
|
240
|
+
// Walk up to find the package root (where skills/ lives)
|
|
241
|
+
for (let i = 0; i < 5; i++) {
|
|
242
|
+
const skillsCandidate = path.join(dir, 'skills');
|
|
243
|
+
const pkgCandidate = path.join(dir, 'package.json');
|
|
244
|
+
if (fs.existsSync(pkgCandidate) && fs.existsSync(skillsCandidate)) {
|
|
245
|
+
return skillsCandidate;
|
|
246
|
+
}
|
|
247
|
+
dir = path.dirname(dir);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
// ignore
|
|
252
|
+
}
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Register zigrix skill packs into OpenClaw's skills directory.
|
|
257
|
+
* Creates symlinks from ~/.openclaw/skills/<skill-name> → zigrix/skills/<skill-name>.
|
|
258
|
+
* Idempotent: skips skills that already exist (unless they point elsewhere).
|
|
259
|
+
*/
|
|
260
|
+
export function registerSkills(openclawHome) {
|
|
261
|
+
const registered = [];
|
|
262
|
+
const skipped = [];
|
|
263
|
+
const failed = [];
|
|
264
|
+
const skillsSource = resolveSkillsDir();
|
|
265
|
+
if (!skillsSource) {
|
|
266
|
+
return { registered, skipped, failed: ['Could not locate zigrix skills directory'] };
|
|
267
|
+
}
|
|
268
|
+
const openclawSkillsDir = path.join(openclawHome, 'skills');
|
|
269
|
+
try {
|
|
270
|
+
fs.mkdirSync(openclawSkillsDir, { recursive: true });
|
|
271
|
+
}
|
|
272
|
+
catch (e) {
|
|
273
|
+
return { registered, skipped, failed: [`Could not create ${openclawSkillsDir}: ${e instanceof Error ? e.message : String(e)}`] };
|
|
274
|
+
}
|
|
275
|
+
let skillDirs;
|
|
276
|
+
try {
|
|
277
|
+
skillDirs = fs.readdirSync(skillsSource).filter((name) => {
|
|
278
|
+
const full = path.join(skillsSource, name);
|
|
279
|
+
return fs.statSync(full).isDirectory() && fs.existsSync(path.join(full, 'SKILL.md'));
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
return { registered, skipped, failed: [`Could not read skills directory at ${skillsSource}`] };
|
|
284
|
+
}
|
|
285
|
+
for (const skillName of skillDirs) {
|
|
286
|
+
const source = path.join(skillsSource, skillName);
|
|
287
|
+
const target = path.join(openclawSkillsDir, skillName);
|
|
288
|
+
try {
|
|
289
|
+
// Check if already exists
|
|
290
|
+
if (fs.existsSync(target)) {
|
|
291
|
+
// Check if it's already pointing to our source
|
|
292
|
+
try {
|
|
293
|
+
const existing = fs.readlinkSync(target);
|
|
294
|
+
if (path.resolve(existing) === path.resolve(source)) {
|
|
295
|
+
skipped.push(skillName);
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
// Not a symlink — skip (user may have their own copy)
|
|
301
|
+
skipped.push(skillName);
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
// Points elsewhere — update it
|
|
305
|
+
fs.unlinkSync(target);
|
|
306
|
+
}
|
|
307
|
+
fs.symlinkSync(source, target);
|
|
308
|
+
registered.push(skillName);
|
|
309
|
+
}
|
|
310
|
+
catch (e) {
|
|
311
|
+
failed.push(`${skillName}: ${e instanceof Error ? e.message : String(e)}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return { registered, skipped, failed };
|
|
315
|
+
}
|
|
316
|
+
// ─── Interactive agent selection ──────────────────────────────────────────────
|
|
317
|
+
/**
|
|
318
|
+
* Interactive agent picker using @inquirer/prompts checkbox.
|
|
319
|
+
* Space to toggle, Enter to confirm. All agents pre-selected by default.
|
|
320
|
+
*/
|
|
321
|
+
export async function promptAgentSelection(agents) {
|
|
322
|
+
if (agents.length === 0)
|
|
323
|
+
return [];
|
|
324
|
+
try {
|
|
325
|
+
const { checkbox } = await import('@inquirer/prompts');
|
|
326
|
+
const choices = agents.map((agent) => {
|
|
327
|
+
const theme = agent.identity?.theme ?? 'unknown';
|
|
328
|
+
const name = agent.name ?? agent.id;
|
|
329
|
+
return {
|
|
330
|
+
name: `${agent.id} — ${name} (${theme})`,
|
|
331
|
+
value: agent.id,
|
|
332
|
+
checked: true,
|
|
333
|
+
};
|
|
334
|
+
});
|
|
335
|
+
const selected = await checkbox({
|
|
336
|
+
message: 'Select agents to register (space to toggle, enter to confirm):',
|
|
337
|
+
choices,
|
|
338
|
+
});
|
|
339
|
+
const selectedSet = new Set(selected);
|
|
340
|
+
return agents.filter((a) => selectedSet.has(a.id));
|
|
341
|
+
}
|
|
342
|
+
catch {
|
|
343
|
+
// Fallback: if inquirer is unavailable or stdin is not a TTY, select all
|
|
344
|
+
console.log('ℹ️ Non-interactive mode — selecting all agents.');
|
|
345
|
+
return agents;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
// ─── Ensure config (idempotent) ───────────────────────────────────────────────
|
|
349
|
+
function ensureConfig() {
|
|
350
|
+
const existing = loadConfig({});
|
|
351
|
+
if (existing.configPath && fs.existsSync(existing.configPath)) {
|
|
352
|
+
return { configPath: existing.configPath, isNew: false };
|
|
353
|
+
}
|
|
354
|
+
const configPath = writeDefaultConfig(undefined, false);
|
|
355
|
+
return { configPath, isNew: true };
|
|
356
|
+
}
|
|
357
|
+
// ─── Main onboard function ────────────────────────────────────────────────────
|
|
358
|
+
export async function runOnboard(options) {
|
|
359
|
+
const warnings = [];
|
|
360
|
+
const silent = options.silent ?? false;
|
|
361
|
+
const log = (msg) => {
|
|
362
|
+
if (!silent)
|
|
363
|
+
console.log(msg);
|
|
364
|
+
};
|
|
365
|
+
// 1. Ensure ~/.zigrix base state (idempotent)
|
|
366
|
+
const { configPath } = ensureConfig();
|
|
367
|
+
const loaded = loadConfig({ configPath });
|
|
368
|
+
const paths = resolvePaths(loaded.config);
|
|
369
|
+
ensureBaseState(paths);
|
|
370
|
+
rebuildIndex(paths);
|
|
371
|
+
// 2. Detect OpenClaw
|
|
372
|
+
const openclawHome = detectOpenClawHome();
|
|
373
|
+
const openclawExists = fs.existsSync(openclawHome);
|
|
374
|
+
let openclawConfig = null;
|
|
375
|
+
if (!openclawExists) {
|
|
376
|
+
warnings.push(`OpenClaw not found at ${openclawHome}. Running in zigrix-only mode.`);
|
|
377
|
+
log(`⚠️ ${warnings[warnings.length - 1]}`);
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
openclawConfig = loadOpenClawConfig(openclawHome);
|
|
381
|
+
if (!openclawConfig) {
|
|
382
|
+
warnings.push(`openclaw.json not found or invalid at ${openclawHome}. Agent import skipped.`);
|
|
383
|
+
log(`⚠️ ${warnings[warnings.length - 1]}`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
// 3. Agent selection and registration
|
|
387
|
+
let agentsRegistered = [];
|
|
388
|
+
let agentsSkipped = [];
|
|
389
|
+
if (openclawConfig) {
|
|
390
|
+
const allAgents = filterAgents(openclawConfig.agents?.list ?? []);
|
|
391
|
+
let selectedAgents;
|
|
392
|
+
if (options.yes) {
|
|
393
|
+
selectedAgents = allAgents;
|
|
394
|
+
if (allAgents.length > 0) {
|
|
395
|
+
log(`ℹ️ Auto-selecting all ${allAgents.length} agent(s) (--yes mode).`);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
selectedAgents = await promptAgentSelection(allAgents);
|
|
400
|
+
}
|
|
401
|
+
if (selectedAgents.length > 0) {
|
|
402
|
+
const result = registerAgents(loaded.config, selectedAgents);
|
|
403
|
+
agentsRegistered = result.registered;
|
|
404
|
+
agentsSkipped = result.skipped;
|
|
405
|
+
if (agentsRegistered.length > 0) {
|
|
406
|
+
writeConfigFile(configPath, result.config);
|
|
407
|
+
log(`✅ Registered agents: ${agentsRegistered.join(', ')}`);
|
|
408
|
+
}
|
|
409
|
+
if (agentsSkipped.length > 0) {
|
|
410
|
+
log(`⏭️ Already registered (skipped): ${agentsSkipped.join(', ')}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
// 4. Seed rules
|
|
415
|
+
const projectDir = options.projectDir ?? process.cwd();
|
|
416
|
+
const rulesSourceDir = path.join(projectDir, 'orchestration', 'rules');
|
|
417
|
+
const rulesTargetDir = paths.rulesDir;
|
|
418
|
+
if (!fs.existsSync(rulesSourceDir)) {
|
|
419
|
+
warnings.push(`orchestration/rules/ not found at ${rulesSourceDir}. Rules seeding skipped.`);
|
|
420
|
+
log(`⚠️ ${warnings[warnings.length - 1]}`);
|
|
421
|
+
}
|
|
422
|
+
const { copied: rulesCopied, skipped: rulesSkipped } = seedRules(rulesSourceDir, rulesTargetDir);
|
|
423
|
+
if (rulesCopied.length > 0) {
|
|
424
|
+
log(`✅ Rules copied: ${rulesCopied.join(', ')}`);
|
|
425
|
+
}
|
|
426
|
+
if (rulesSkipped.length > 0) {
|
|
427
|
+
log(`⏭️ Rules already exist (skipped): ${rulesSkipped.join(', ')}`);
|
|
428
|
+
}
|
|
429
|
+
// 5. Stabilize PATH — ensure zigrix is reachable
|
|
430
|
+
const pathResult = ensureZigrixInPath();
|
|
431
|
+
if (pathResult.symlinkCreated) {
|
|
432
|
+
log(`✅ zigrix symlinked to ${pathResult.symlinkPath}`);
|
|
433
|
+
}
|
|
434
|
+
else if (pathResult.alreadyInPath) {
|
|
435
|
+
log('✅ zigrix already in PATH');
|
|
436
|
+
}
|
|
437
|
+
if (pathResult.warning) {
|
|
438
|
+
warnings.push(pathResult.warning);
|
|
439
|
+
log(`⚠️ ${pathResult.warning}`);
|
|
440
|
+
}
|
|
441
|
+
// 6. Register OpenClaw skills (symlink skill packs)
|
|
442
|
+
let skillsRegistered = [];
|
|
443
|
+
let skillsSkipped = [];
|
|
444
|
+
let skillsFailed = [];
|
|
445
|
+
const openclawSkillsDir = path.join(openclawHome, 'skills');
|
|
446
|
+
if (openclawExists) {
|
|
447
|
+
const skillResult = registerSkills(openclawHome);
|
|
448
|
+
skillsRegistered = skillResult.registered;
|
|
449
|
+
skillsSkipped = skillResult.skipped;
|
|
450
|
+
skillsFailed = skillResult.failed;
|
|
451
|
+
if (skillsRegistered.length > 0) {
|
|
452
|
+
log(`✅ Skills registered: ${skillsRegistered.join(', ')}`);
|
|
453
|
+
}
|
|
454
|
+
if (skillsSkipped.length > 0) {
|
|
455
|
+
log(`⏭️ Skills already registered (skipped): ${skillsSkipped.join(', ')}`);
|
|
456
|
+
}
|
|
457
|
+
if (skillsFailed.length > 0) {
|
|
458
|
+
for (const f of skillsFailed) {
|
|
459
|
+
warnings.push(`Skill registration failed: ${f}`);
|
|
460
|
+
log(`⚠️ ${warnings[warnings.length - 1]}`);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
log('ℹ️ OpenClaw not detected — skill registration skipped.');
|
|
466
|
+
}
|
|
467
|
+
const openclawSkillsDirExists = fs.existsSync(openclawSkillsDir);
|
|
468
|
+
return {
|
|
469
|
+
ok: true,
|
|
470
|
+
action: 'onboard',
|
|
471
|
+
baseDir: paths.baseDir,
|
|
472
|
+
configPath,
|
|
473
|
+
paths,
|
|
474
|
+
openclawDetected: openclawExists,
|
|
475
|
+
openclawHome,
|
|
476
|
+
agentsRegistered,
|
|
477
|
+
agentsSkipped,
|
|
478
|
+
rulesCopied,
|
|
479
|
+
rulesSkipped,
|
|
480
|
+
skillsRegistered,
|
|
481
|
+
skillsSkipped,
|
|
482
|
+
skillsFailed,
|
|
483
|
+
pathStabilized: pathResult,
|
|
484
|
+
warnings,
|
|
485
|
+
checks: {
|
|
486
|
+
zigrixInPath: pathResult.alreadyInPath || pathResult.symlinkCreated,
|
|
487
|
+
openclawSkillsDir: openclawSkillsDirExists,
|
|
488
|
+
},
|
|
489
|
+
};
|
|
490
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ZigrixPaths } from '../state/paths.js';
|
|
2
|
+
export declare function dispatchTask(paths: ZigrixPaths, params: {
|
|
3
|
+
title: string;
|
|
4
|
+
description: string;
|
|
5
|
+
scale: string;
|
|
6
|
+
projectDir?: string;
|
|
7
|
+
requestedBy?: string;
|
|
8
|
+
constraints?: string;
|
|
9
|
+
}): Record<string, unknown>;
|