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.
Files changed (58) hide show
  1. package/LICENSE +184 -0
  2. package/README.md +128 -0
  3. package/dist/agents/registry.d.ts +28 -0
  4. package/dist/agents/registry.js +98 -0
  5. package/dist/config/defaults.d.ts +66 -0
  6. package/dist/config/defaults.js +58 -0
  7. package/dist/config/load.d.ts +13 -0
  8. package/dist/config/load.js +96 -0
  9. package/dist/config/mutate.d.ts +10 -0
  10. package/dist/config/mutate.js +61 -0
  11. package/dist/config/schema.d.ts +132 -0
  12. package/dist/config/schema.js +123 -0
  13. package/dist/configure.d.ts +24 -0
  14. package/dist/configure.js +164 -0
  15. package/dist/doctor.d.ts +4 -0
  16. package/dist/doctor.js +99 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.js +791 -0
  19. package/dist/onboard.d.ts +99 -0
  20. package/dist/onboard.js +490 -0
  21. package/dist/orchestration/dispatch.d.ts +9 -0
  22. package/dist/orchestration/dispatch.js +146 -0
  23. package/dist/orchestration/evidence.d.ts +19 -0
  24. package/dist/orchestration/evidence.js +97 -0
  25. package/dist/orchestration/finalize.d.ts +7 -0
  26. package/dist/orchestration/finalize.js +136 -0
  27. package/dist/orchestration/pipeline.d.ts +11 -0
  28. package/dist/orchestration/pipeline.js +26 -0
  29. package/dist/orchestration/report.d.ts +5 -0
  30. package/dist/orchestration/report.js +92 -0
  31. package/dist/orchestration/worker.d.ts +34 -0
  32. package/dist/orchestration/worker.js +132 -0
  33. package/dist/rules/templates.d.ts +24 -0
  34. package/dist/rules/templates.js +73 -0
  35. package/dist/runner/run.d.ts +10 -0
  36. package/dist/runner/run.js +91 -0
  37. package/dist/runner/schema.d.ts +33 -0
  38. package/dist/runner/schema.js +10 -0
  39. package/dist/runner/store.d.ts +5 -0
  40. package/dist/runner/store.js +19 -0
  41. package/dist/state/events.d.ts +3 -0
  42. package/dist/state/events.js +53 -0
  43. package/dist/state/paths.d.ts +15 -0
  44. package/dist/state/paths.js +23 -0
  45. package/dist/state/tasks.d.ts +61 -0
  46. package/dist/state/tasks.js +247 -0
  47. package/dist/state/verify.d.ts +2 -0
  48. package/dist/state/verify.js +65 -0
  49. package/examples/hello-workflow.json +13 -0
  50. package/package.json +44 -0
  51. package/skills/zigrix-doctor/SKILL.md +20 -0
  52. package/skills/zigrix-evidence/SKILL.md +21 -0
  53. package/skills/zigrix-init/SKILL.md +23 -0
  54. package/skills/zigrix-report/SKILL.md +20 -0
  55. package/skills/zigrix-shared/SKILL.md +31 -0
  56. package/skills/zigrix-task-create/SKILL.md +34 -0
  57. package/skills/zigrix-task-status/SKILL.md +27 -0
  58. 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>;
@@ -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>;