zigrix 0.1.0-alpha.9 → 0.1.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/README.md +85 -182
- package/dist/agents/registry.js +14 -0
- package/dist/config/defaults.d.ts +87 -6
- package/dist/config/defaults.js +82 -51
- package/dist/config/load.d.ts +5 -3
- package/dist/config/load.js +69 -30
- package/dist/config/schema.d.ts +21 -1
- package/dist/config/schema.js +15 -1
- package/dist/configure.d.ts +1 -0
- package/dist/configure.js +24 -14
- package/dist/dashboard/.next/BUILD_ID +1 -1
- package/dist/dashboard/.next/app-build-manifest.json +21 -21
- package/dist/dashboard/.next/app-path-routes-manifest.json +7 -7
- package/dist/dashboard/.next/build-manifest.json +2 -2
- package/dist/dashboard/.next/prerender-manifest.json +10 -10
- package/dist/dashboard/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/_not-found.html +1 -1
- package/dist/dashboard/.next/server/app/_not-found.rsc +1 -1
- package/dist/dashboard/.next/server/app/api/auth/login/route.js +1 -1
- package/dist/dashboard/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/api/auth/logout/route.js +1 -1
- package/dist/dashboard/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/api/auth/session/route.js +1 -1
- package/dist/dashboard/.next/server/app/api/auth/session/route_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/api/auth/setup/route.js +1 -1
- package/dist/dashboard/.next/server/app/api/auth/setup/route_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/api/overview/route_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/api/stream/route_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/api/tasks/[taskId]/cancel/route_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/api/tasks/[taskId]/conversation/route_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/api/tasks/[taskId]/route_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/login.html +1 -1
- package/dist/dashboard/.next/server/app/login.rsc +1 -1
- package/dist/dashboard/.next/server/app/page.js +2 -2
- package/dist/dashboard/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/setup.html +1 -1
- package/dist/dashboard/.next/server/app/setup.rsc +1 -1
- package/dist/dashboard/.next/server/app-paths-manifest.json +7 -7
- package/dist/dashboard/.next/server/chunks/972.js +1 -1
- package/dist/dashboard/.next/server/functions-config-manifest.json +4 -4
- package/dist/dashboard/.next/server/middleware.js +1 -1
- package/dist/dashboard/.next/server/pages/404.html +1 -1
- package/dist/dashboard/.next/server/pages/500.html +1 -1
- package/dist/dashboard/.next/static/chunks/app/page-0314989c31e18b4b.js +1 -0
- package/dist/dashboard/.next/static/css/{94d75aff24d0c077.css → c3a7306cb2ba3f6c.css} +1 -1
- package/dist/dashboard.js +47 -0
- package/dist/doctor.js +28 -5
- package/dist/index.js +170 -170
- package/dist/onboard.d.ts +60 -0
- package/dist/onboard.js +403 -18
- package/dist/orchestration/dispatch.d.ts +1 -0
- package/dist/orchestration/dispatch.js +17 -5
- package/dist/orchestration/evidence.js +18 -5
- package/dist/orchestration/finalize.d.ts +1 -0
- package/dist/orchestration/finalize.js +5 -3
- package/dist/orchestration/report.js +4 -1
- package/dist/orchestration/worker.d.ts +1 -1
- package/dist/orchestration/worker.js +53 -18
- package/dist/state/tasks.d.ts +5 -0
- package/dist/state/tasks.js +7 -0
- package/package.json +23 -2
- package/rules/defaults/README.md +9 -9
- package/rules/defaults/{back-zig.md → backend-agent.md} +4 -4
- package/rules/defaults/{front-zig.md → frontend-agent.md} +4 -4
- package/rules/defaults/orchestrator-agent.md +261 -0
- package/rules/defaults/{qa-zig.md → qa-agent.md} +11 -11
- package/rules/defaults/{sec-zig.md → security-agent.md} +4 -4
- package/rules/defaults/{sys-zig.md → system-agent.md} +8 -9
- package/rules/defaults/worker-common.md +25 -19
- package/skills/zigrix-doctor/SKILL.md +4 -2
- package/skills/zigrix-evidence/SKILL.md +7 -3
- package/skills/zigrix-main-agent-guide/SKILL.md +38 -28
- package/skills/zigrix-shared/SKILL.md +27 -3
- package/skills/zigrix-task-create/SKILL.md +8 -2
- package/skills/zigrix-task-status/SKILL.md +5 -2
- package/skills/zigrix-worker/SKILL.md +12 -4
- package/dist/dashboard/.next/static/chunks/app/page-25f54e54e74fb3af.js +0 -1
- package/rules/defaults/pro-zig.md +0 -238
- /package/dist/dashboard/.next/static/{TlUj0t8APzTccK13DVZZW → PT4hYxzrqxj-Zq4ZjtKNg}/_buildManifest.js +0 -0
- /package/dist/dashboard/.next/static/{TlUj0t8APzTccK13DVZZW → PT4hYxzrqxj-Zq4ZjtKNg}/_ssgManifest.js +0 -0
package/dist/onboard.d.ts
CHANGED
|
@@ -14,6 +14,23 @@ export interface OpenClawConfig {
|
|
|
14
14
|
agents?: {
|
|
15
15
|
list?: OpenClawAgent[];
|
|
16
16
|
};
|
|
17
|
+
gateway?: {
|
|
18
|
+
port?: number | string;
|
|
19
|
+
bind?: string;
|
|
20
|
+
mode?: string;
|
|
21
|
+
auth?: {
|
|
22
|
+
token?: string;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
plugins?: {
|
|
26
|
+
entries?: {
|
|
27
|
+
'device-pair'?: {
|
|
28
|
+
config?: {
|
|
29
|
+
publicUrl?: string;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
};
|
|
17
34
|
}
|
|
18
35
|
export type AgentRoleAssignments = Record<string, StandardAgentRole>;
|
|
19
36
|
export interface PathStabilizeResult {
|
|
@@ -31,10 +48,13 @@ export interface OnboardResult {
|
|
|
31
48
|
ok: boolean;
|
|
32
49
|
action: string;
|
|
33
50
|
baseDir: string;
|
|
51
|
+
workspaceBaseDir: string;
|
|
34
52
|
configPath: string;
|
|
35
53
|
paths: ZigrixPaths;
|
|
36
54
|
openclawDetected: boolean;
|
|
37
55
|
openclawHome: string;
|
|
56
|
+
openclawBinPath: string | null;
|
|
57
|
+
openclawGatewayUrl: string | null;
|
|
38
58
|
agentsRegistered: string[];
|
|
39
59
|
agentsSkipped: string[];
|
|
40
60
|
rulesCopied: string[];
|
|
@@ -43,20 +63,37 @@ export interface OnboardResult {
|
|
|
43
63
|
skillsSkipped: string[];
|
|
44
64
|
skillsFailed: string[];
|
|
45
65
|
pathStabilized: PathStabilizeResult;
|
|
66
|
+
openclawPathStabilized: PathStabilizeResult;
|
|
46
67
|
warnings: string[];
|
|
47
68
|
checks: {
|
|
48
69
|
zigrixInPath: boolean;
|
|
70
|
+
openclawInPath: boolean;
|
|
49
71
|
openclawSkillsDir: boolean;
|
|
50
72
|
};
|
|
51
73
|
}
|
|
52
74
|
export interface RunOnboardOptions {
|
|
53
75
|
yes?: boolean;
|
|
54
76
|
projectDir?: string;
|
|
77
|
+
projectsBaseDir?: string;
|
|
55
78
|
orchestratorId?: string;
|
|
79
|
+
gatewayUrl?: string;
|
|
56
80
|
silent?: boolean;
|
|
57
81
|
}
|
|
58
82
|
export declare function detectOpenClawHome(): string;
|
|
59
83
|
export declare function loadOpenClawConfig(openclawHome: string): OpenClawConfig | null;
|
|
84
|
+
export declare function resolveOpenClawGatewayUrl(openclawConfig: OpenClawConfig | null): string | null;
|
|
85
|
+
export declare function resolveOpenClawGatewayToken(openclawConfig: OpenClawConfig | null): string | null;
|
|
86
|
+
/**
|
|
87
|
+
* Discover the openclaw binary path.
|
|
88
|
+
* Strategy order:
|
|
89
|
+
* 1. `which openclaw` — available in current PATH
|
|
90
|
+
* 2. Common nvm/volta/fnm managed paths
|
|
91
|
+
* 3. Well-known locations: /usr/local/bin, ~/.local/bin
|
|
92
|
+
* 4. Walk node_modules/.bin from openclaw home
|
|
93
|
+
*
|
|
94
|
+
* Returns the resolved absolute path or null.
|
|
95
|
+
*/
|
|
96
|
+
export declare function resolveOpenClawBin(openclawHome?: string): string | null;
|
|
60
97
|
export declare function filterAgents(agents: OpenClawAgent[]): OpenClawAgent[];
|
|
61
98
|
export declare function registerAgents(config: ZigrixConfig, agents: OpenClawAgent[], roleAssignments?: AgentRoleAssignments): {
|
|
62
99
|
config: ZigrixConfig;
|
|
@@ -67,6 +104,11 @@ export declare function registerAgents(config: ZigrixConfig, agents: OpenClawAge
|
|
|
67
104
|
* Find the bundled default rules directory shipped with the zigrix package.
|
|
68
105
|
*/
|
|
69
106
|
export declare function resolveBundledRulesDir(): string | null;
|
|
107
|
+
export declare function resolveRuleSeedSource(projectDir?: string): {
|
|
108
|
+
sourceDir: string | null;
|
|
109
|
+
source: 'external' | 'bundled' | 'none';
|
|
110
|
+
searched: string[];
|
|
111
|
+
};
|
|
70
112
|
export declare function seedRules(sourceDir: string, targetDir: string): {
|
|
71
113
|
copied: string[];
|
|
72
114
|
skipped: string[];
|
|
@@ -106,6 +148,22 @@ export declare function ensureZigrixInPath(opts?: {
|
|
|
106
148
|
_overrideSystemBinDir?: string | null;
|
|
107
149
|
_overrideStablePaths?: string[];
|
|
108
150
|
}): PathStabilizeResult;
|
|
151
|
+
export declare function checkOpenClawInPath(opts?: {
|
|
152
|
+
_overrideStablePaths?: string[];
|
|
153
|
+
}): boolean;
|
|
154
|
+
/**
|
|
155
|
+
* Ensure openclaw is reachable from PATH.
|
|
156
|
+
* Same strategy as ensureZigrixInPath():
|
|
157
|
+
* 1. /usr/local/bin — stable path, accessible from non-login shells
|
|
158
|
+
* 2. ~/.local/bin — user-local fallback
|
|
159
|
+
*
|
|
160
|
+
* @param opts._overrideSystemBinDir - Override system bin dir selection (for testing)
|
|
161
|
+
* @param opts._overrideStablePaths - Override stable paths list (for testing)
|
|
162
|
+
*/
|
|
163
|
+
export declare function ensureOpenClawInPath(opts?: {
|
|
164
|
+
_overrideSystemBinDir?: string | null;
|
|
165
|
+
_overrideStablePaths?: string[];
|
|
166
|
+
}): PathStabilizeResult;
|
|
109
167
|
/**
|
|
110
168
|
* Find the skills/ directory bundled with this zigrix package.
|
|
111
169
|
*/
|
|
@@ -122,6 +180,8 @@ export declare function registerSkills(openclawHome: string): SkillRegistrationR
|
|
|
122
180
|
*/
|
|
123
181
|
export declare function promptAgentSelection(agents: OpenClawAgent[]): Promise<OpenClawAgent[]>;
|
|
124
182
|
export declare function promptAgentRoleAssignments(agents: OpenClawAgent[]): Promise<AgentRoleAssignments>;
|
|
183
|
+
export declare function promptWorkspaceBaseDir(defaultPath: string): Promise<string>;
|
|
184
|
+
export declare function promptGatewayUrl(defaultValue: string): Promise<string>;
|
|
125
185
|
export declare function ensureOrchestratorId(config: ZigrixConfig, preferredId?: string): {
|
|
126
186
|
config: ZigrixConfig;
|
|
127
187
|
changed: boolean;
|
package/dist/onboard.js
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
1
2
|
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
2
4
|
import path from 'node:path';
|
|
3
5
|
import { fileURLToPath } from 'node:url';
|
|
4
6
|
import { addAgent } from './agents/registry.js';
|
|
5
7
|
import { inferStandardAgentRole, STANDARD_AGENT_ROLES } from './agents/roles.js';
|
|
8
|
+
import { LEGACY_DEFAULT_GATEWAY_URL, resolveAbsolutePath } from './config/defaults.js';
|
|
6
9
|
import { loadConfig, writeConfigFile, writeDefaultConfig } from './config/load.js';
|
|
7
10
|
import { ensureBaseState, resolvePaths } from './state/paths.js';
|
|
8
11
|
import { rebuildIndex } from './state/tasks.js';
|
|
9
12
|
// ─── OpenClaw detection ───────────────────────────────────────────────────────
|
|
10
13
|
export function detectOpenClawHome() {
|
|
14
|
+
const fallbackHome = process.env.HOME ?? os.homedir();
|
|
11
15
|
return process.env.OPENCLAW_HOME
|
|
12
|
-
?
|
|
13
|
-
: path.join(
|
|
16
|
+
? resolveAbsolutePath(process.env.OPENCLAW_HOME)
|
|
17
|
+
: path.join(fallbackHome, '.openclaw');
|
|
14
18
|
}
|
|
15
19
|
export function loadOpenClawConfig(openclawHome) {
|
|
16
20
|
const configPath = path.join(openclawHome, 'openclaw.json');
|
|
@@ -24,6 +28,114 @@ export function loadOpenClawConfig(openclawHome) {
|
|
|
24
28
|
return null;
|
|
25
29
|
}
|
|
26
30
|
}
|
|
31
|
+
function normalizeGatewayUrl(input) {
|
|
32
|
+
const trimmed = input?.trim() ?? '';
|
|
33
|
+
if (!trimmed)
|
|
34
|
+
return '';
|
|
35
|
+
const withScheme = /^[a-z]+:\/\//i.test(trimmed) ? trimmed : `http://${trimmed}`;
|
|
36
|
+
const parsed = new URL(withScheme);
|
|
37
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
38
|
+
throw new Error(`unsupported gateway url protocol: ${parsed.protocol}`);
|
|
39
|
+
}
|
|
40
|
+
return parsed.toString().replace(/\/+$/, '');
|
|
41
|
+
}
|
|
42
|
+
function coerceGatewayPort(value) {
|
|
43
|
+
const n = typeof value === 'string' && value.trim().length > 0 ? Number(value) : typeof value === 'number' ? value : NaN;
|
|
44
|
+
if (!Number.isFinite(n) || n <= 0 || n > 65535)
|
|
45
|
+
return null;
|
|
46
|
+
return Math.trunc(n);
|
|
47
|
+
}
|
|
48
|
+
export function resolveOpenClawGatewayUrl(openclawConfig) {
|
|
49
|
+
if (!openclawConfig || typeof openclawConfig !== 'object')
|
|
50
|
+
return null;
|
|
51
|
+
const directUrl = openclawConfig.gateway?.url;
|
|
52
|
+
if (typeof directUrl === 'string' && directUrl.trim().length > 0) {
|
|
53
|
+
return normalizeGatewayUrl(directUrl);
|
|
54
|
+
}
|
|
55
|
+
const port = coerceGatewayPort(openclawConfig.gateway?.port);
|
|
56
|
+
if (port) {
|
|
57
|
+
return `http://127.0.0.1:${port}`;
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
export function resolveOpenClawGatewayToken(openclawConfig) {
|
|
62
|
+
const token = openclawConfig?.gateway?.auth?.token;
|
|
63
|
+
return typeof token === 'string' && token.trim().length > 0 ? token : null;
|
|
64
|
+
}
|
|
65
|
+
function shouldReplaceStoredGatewayUrl(stored, detected) {
|
|
66
|
+
const trimmed = stored.trim();
|
|
67
|
+
if (!trimmed)
|
|
68
|
+
return Boolean(detected);
|
|
69
|
+
if (!detected)
|
|
70
|
+
return false;
|
|
71
|
+
return trimmed === LEGACY_DEFAULT_GATEWAY_URL;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Discover the openclaw binary path.
|
|
75
|
+
* Strategy order:
|
|
76
|
+
* 1. `which openclaw` — available in current PATH
|
|
77
|
+
* 2. Common nvm/volta/fnm managed paths
|
|
78
|
+
* 3. Well-known locations: /usr/local/bin, ~/.local/bin
|
|
79
|
+
* 4. Walk node_modules/.bin from openclaw home
|
|
80
|
+
*
|
|
81
|
+
* Returns the resolved absolute path or null.
|
|
82
|
+
*/
|
|
83
|
+
export function resolveOpenClawBin(openclawHome) {
|
|
84
|
+
// Strategy 1: which/where
|
|
85
|
+
try {
|
|
86
|
+
const result = execSync('which openclaw', { encoding: 'utf8', timeout: 3000 }).trim();
|
|
87
|
+
if (result && fs.existsSync(result))
|
|
88
|
+
return path.resolve(result);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// not in PATH
|
|
92
|
+
}
|
|
93
|
+
// Strategy 2: nvm/volta/fnm managed global bin dirs
|
|
94
|
+
const home = process.env.HOME ?? '';
|
|
95
|
+
const nodeVersion = process.versions.node;
|
|
96
|
+
const majorMinorPatch = nodeVersion; // e.g., "25.5.0"
|
|
97
|
+
const nvmCandidate = path.join(home, '.nvm', 'versions', 'node', `v${majorMinorPatch}`, 'bin', 'openclaw');
|
|
98
|
+
if (fs.existsSync(nvmCandidate))
|
|
99
|
+
return path.resolve(nvmCandidate);
|
|
100
|
+
// Also try nvm current symlink
|
|
101
|
+
const nvmCurrent = path.join(home, '.nvm', 'current', 'bin', 'openclaw');
|
|
102
|
+
if (fs.existsSync(nvmCurrent))
|
|
103
|
+
return path.resolve(nvmCurrent);
|
|
104
|
+
// Volta
|
|
105
|
+
const voltaCandidate = path.join(home, '.volta', 'bin', 'openclaw');
|
|
106
|
+
if (fs.existsSync(voltaCandidate))
|
|
107
|
+
return path.resolve(voltaCandidate);
|
|
108
|
+
// fnm
|
|
109
|
+
const fnmCandidate = path.join(home, '.fnm', 'node-versions', `v${majorMinorPatch}`, 'installation', 'bin', 'openclaw');
|
|
110
|
+
if (fs.existsSync(fnmCandidate))
|
|
111
|
+
return path.resolve(fnmCandidate);
|
|
112
|
+
// Strategy 3: well-known system paths
|
|
113
|
+
for (const dir of ['/usr/local/bin', path.join(home, '.local', 'bin')]) {
|
|
114
|
+
const candidate = path.join(dir, 'openclaw');
|
|
115
|
+
if (fs.existsSync(candidate))
|
|
116
|
+
return path.resolve(candidate);
|
|
117
|
+
}
|
|
118
|
+
// Strategy 4: node global lib path (npm root -g)
|
|
119
|
+
try {
|
|
120
|
+
const globalRoot = execSync('npm root -g', { encoding: 'utf8', timeout: 3000 }).trim();
|
|
121
|
+
if (globalRoot) {
|
|
122
|
+
const candidate = path.join(path.dirname(globalRoot), 'bin', 'openclaw');
|
|
123
|
+
if (fs.existsSync(candidate))
|
|
124
|
+
return path.resolve(candidate);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// npm not available or timed out
|
|
129
|
+
}
|
|
130
|
+
// Strategy 5: from openclaw home
|
|
131
|
+
if (openclawHome) {
|
|
132
|
+
// Some installations place a bin reference inside the home dir
|
|
133
|
+
const homeBin = path.join(openclawHome, 'bin', 'openclaw');
|
|
134
|
+
if (fs.existsSync(homeBin))
|
|
135
|
+
return path.resolve(homeBin);
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
27
139
|
// ─── Agent filtering ──────────────────────────────────────────────────────────
|
|
28
140
|
export function filterAgents(agents) {
|
|
29
141
|
return agents.filter((a) => a.id !== 'main');
|
|
@@ -77,6 +189,28 @@ export function resolveBundledRulesDir() {
|
|
|
77
189
|
}
|
|
78
190
|
return null;
|
|
79
191
|
}
|
|
192
|
+
export function resolveRuleSeedSource(projectDir) {
|
|
193
|
+
const searched = [];
|
|
194
|
+
const base = projectDir?.trim();
|
|
195
|
+
if (base) {
|
|
196
|
+
const resolvedBase = resolveAbsolutePath(base);
|
|
197
|
+
const candidates = [
|
|
198
|
+
path.join(resolvedBase, 'rules', 'defaults'),
|
|
199
|
+
path.join(resolvedBase, 'rules'),
|
|
200
|
+
];
|
|
201
|
+
searched.push(...candidates);
|
|
202
|
+
for (const candidate of candidates) {
|
|
203
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
|
|
204
|
+
return { sourceDir: candidate, source: 'external', searched };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const bundled = resolveBundledRulesDir();
|
|
209
|
+
if (bundled) {
|
|
210
|
+
return { sourceDir: bundled, source: 'bundled', searched };
|
|
211
|
+
}
|
|
212
|
+
return { sourceDir: null, source: 'none', searched };
|
|
213
|
+
}
|
|
80
214
|
export function seedRules(sourceDir, targetDir) {
|
|
81
215
|
const copied = [];
|
|
82
216
|
const skipped = [];
|
|
@@ -315,6 +449,121 @@ export function ensureZigrixInPath(opts) {
|
|
|
315
449
|
: `Created zigrix at ${symlinkPath}, but ${userBinDir} is not in your PATH. Add it:\n export PATH="${userBinDir}:$PATH"`;
|
|
316
450
|
return { alreadyInPath: false, symlinkCreated: true, symlinkPath, warning };
|
|
317
451
|
}
|
|
452
|
+
// ─── OpenClaw PATH check and stabilization ────────────────────────────────────
|
|
453
|
+
export function checkOpenClawInPath(opts) {
|
|
454
|
+
const dirs = opts?._overrideStablePaths ?? STABLE_SHELL_PATHS;
|
|
455
|
+
for (const dir of dirs) {
|
|
456
|
+
try {
|
|
457
|
+
if (!fs.existsSync(dir))
|
|
458
|
+
continue;
|
|
459
|
+
const entries = fs.readdirSync(dir);
|
|
460
|
+
if (entries.includes('openclaw')) {
|
|
461
|
+
return true;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
catch {
|
|
465
|
+
// skip unreadable dirs
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Ensure openclaw is reachable from PATH.
|
|
472
|
+
* Same strategy as ensureZigrixInPath():
|
|
473
|
+
* 1. /usr/local/bin — stable path, accessible from non-login shells
|
|
474
|
+
* 2. ~/.local/bin — user-local fallback
|
|
475
|
+
*
|
|
476
|
+
* @param opts._overrideSystemBinDir - Override system bin dir selection (for testing)
|
|
477
|
+
* @param opts._overrideStablePaths - Override stable paths list (for testing)
|
|
478
|
+
*/
|
|
479
|
+
export function ensureOpenClawInPath(opts) {
|
|
480
|
+
if (checkOpenClawInPath({ _overrideStablePaths: opts?._overrideStablePaths })) {
|
|
481
|
+
return { alreadyInPath: true, symlinkCreated: false, symlinkPath: null, warning: null };
|
|
482
|
+
}
|
|
483
|
+
const binPath = resolveOpenClawBin();
|
|
484
|
+
if (!binPath) {
|
|
485
|
+
return {
|
|
486
|
+
alreadyInPath: false,
|
|
487
|
+
symlinkCreated: false,
|
|
488
|
+
symlinkPath: null,
|
|
489
|
+
warning: 'Could not locate openclaw binary. Ensure openclaw is installed via npm.',
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
// ── Strategy 1: system bin dir (accessible from non-login shells) ──────────
|
|
493
|
+
const systemBinDir = opts !== undefined && '_overrideSystemBinDir' in opts
|
|
494
|
+
? opts._overrideSystemBinDir
|
|
495
|
+
: findSystemBinDir();
|
|
496
|
+
if (systemBinDir) {
|
|
497
|
+
const symlinkPath = path.join(systemBinDir, 'openclaw');
|
|
498
|
+
try {
|
|
499
|
+
// Remove stale entry if present
|
|
500
|
+
try {
|
|
501
|
+
if (fs.existsSync(symlinkPath) || fs.lstatSync(symlinkPath).isSymbolicLink()) {
|
|
502
|
+
fs.unlinkSync(symlinkPath);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
catch {
|
|
506
|
+
// doesn't exist — fine
|
|
507
|
+
}
|
|
508
|
+
fs.symlinkSync(binPath, symlinkPath);
|
|
509
|
+
fs.chmodSync(symlinkPath, 0o755);
|
|
510
|
+
return { alreadyInPath: false, symlinkCreated: true, symlinkPath, warning: null };
|
|
511
|
+
}
|
|
512
|
+
catch (e) {
|
|
513
|
+
if (e.code === 'EACCES') {
|
|
514
|
+
return {
|
|
515
|
+
alreadyInPath: false,
|
|
516
|
+
symlinkCreated: false,
|
|
517
|
+
symlinkPath: null,
|
|
518
|
+
warning: `Cannot write to ${systemBinDir} (permission denied). Run:\n sudo ln -sfn ${binPath} ${symlinkPath}`,
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
// Fall through to user bin dir
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
// ── Strategy 2: user-local bin dir ────────────────────────────────────────
|
|
525
|
+
const userBinDir = findUserBinDir();
|
|
526
|
+
try {
|
|
527
|
+
fs.mkdirSync(userBinDir, { recursive: true });
|
|
528
|
+
}
|
|
529
|
+
catch {
|
|
530
|
+
return {
|
|
531
|
+
alreadyInPath: false,
|
|
532
|
+
symlinkCreated: false,
|
|
533
|
+
symlinkPath: null,
|
|
534
|
+
warning: `Could not create ${userBinDir}. Create it manually and add to PATH.`,
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
const symlinkPath = path.join(userBinDir, 'openclaw');
|
|
538
|
+
try {
|
|
539
|
+
// Remove existing if present (stale symlink)
|
|
540
|
+
if (fs.existsSync(symlinkPath) || fs.lstatSync(symlinkPath).isSymbolicLink()) {
|
|
541
|
+
fs.unlinkSync(symlinkPath);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
catch {
|
|
545
|
+
// doesn't exist, fine
|
|
546
|
+
}
|
|
547
|
+
try {
|
|
548
|
+
fs.symlinkSync(binPath, symlinkPath);
|
|
549
|
+
fs.chmodSync(symlinkPath, 0o755);
|
|
550
|
+
}
|
|
551
|
+
catch (e) {
|
|
552
|
+
return {
|
|
553
|
+
alreadyInPath: false,
|
|
554
|
+
symlinkCreated: false,
|
|
555
|
+
symlinkPath: null,
|
|
556
|
+
warning: `Failed to create symlink at ${symlinkPath}: ${e instanceof Error ? e.message : String(e)}`,
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
// Check if userBinDir is actually in PATH
|
|
560
|
+
const pathEnv = process.env.PATH ?? '';
|
|
561
|
+
const inPath = pathEnv.split(path.delimiter).some((d) => path.resolve(d) === path.resolve(userBinDir));
|
|
562
|
+
const warning = inPath
|
|
563
|
+
? null
|
|
564
|
+
: `Created openclaw at ${symlinkPath}, but ${userBinDir} is not in your PATH. Add it:\n export PATH="${userBinDir}:$PATH"`;
|
|
565
|
+
return { alreadyInPath: false, symlinkCreated: true, symlinkPath, warning };
|
|
566
|
+
}
|
|
318
567
|
// ─── OpenClaw skill registration ──────────────────────────────────────────────
|
|
319
568
|
/**
|
|
320
569
|
* Find the skills/ directory bundled with this zigrix package.
|
|
@@ -463,6 +712,51 @@ export async function promptAgentRoleAssignments(agents) {
|
|
|
463
712
|
return defaults;
|
|
464
713
|
}
|
|
465
714
|
}
|
|
715
|
+
export async function promptWorkspaceBaseDir(defaultPath) {
|
|
716
|
+
try {
|
|
717
|
+
const { input } = await import('@inquirer/prompts');
|
|
718
|
+
const answer = await input({
|
|
719
|
+
message: 'Workspace directory',
|
|
720
|
+
default: defaultPath,
|
|
721
|
+
validate: (value) => {
|
|
722
|
+
if (!value || value.trim().length === 0)
|
|
723
|
+
return 'Workspace directory is required';
|
|
724
|
+
return true;
|
|
725
|
+
},
|
|
726
|
+
});
|
|
727
|
+
return resolveAbsolutePath((answer || defaultPath).trim());
|
|
728
|
+
}
|
|
729
|
+
catch {
|
|
730
|
+
console.log('ℹ️ Non-interactive mode — using configured workspace directory.');
|
|
731
|
+
return resolveAbsolutePath(defaultPath);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
export async function promptGatewayUrl(defaultValue) {
|
|
735
|
+
try {
|
|
736
|
+
const { input } = await import('@inquirer/prompts');
|
|
737
|
+
const answer = await input({
|
|
738
|
+
message: 'OpenClaw gateway URL (leave blank to skip)',
|
|
739
|
+
default: defaultValue,
|
|
740
|
+
validate: (value) => {
|
|
741
|
+
const trimmed = value.trim();
|
|
742
|
+
if (!trimmed)
|
|
743
|
+
return true;
|
|
744
|
+
try {
|
|
745
|
+
normalizeGatewayUrl(trimmed);
|
|
746
|
+
return true;
|
|
747
|
+
}
|
|
748
|
+
catch (error) {
|
|
749
|
+
return error.message;
|
|
750
|
+
}
|
|
751
|
+
},
|
|
752
|
+
});
|
|
753
|
+
return normalizeGatewayUrl((answer || defaultValue).trim());
|
|
754
|
+
}
|
|
755
|
+
catch {
|
|
756
|
+
console.log('ℹ️ Non-interactive mode — skipping gateway URL prompt.');
|
|
757
|
+
return normalizeGatewayUrl(defaultValue);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
466
760
|
export function ensureOrchestratorId(config, preferredId) {
|
|
467
761
|
const next = structuredClone(config);
|
|
468
762
|
const participants = new Set(next.agents.orchestration.participants);
|
|
@@ -516,7 +810,7 @@ function ensureConfig() {
|
|
|
516
810
|
if (existing.configPath && fs.existsSync(existing.configPath)) {
|
|
517
811
|
return { configPath: existing.configPath, isNew: false };
|
|
518
812
|
}
|
|
519
|
-
const configPath = writeDefaultConfig(
|
|
813
|
+
const configPath = writeDefaultConfig(false);
|
|
520
814
|
return { configPath, isNew: true };
|
|
521
815
|
}
|
|
522
816
|
// ─── Main onboard function ────────────────────────────────────────────────────
|
|
@@ -527,10 +821,22 @@ export async function runOnboard(options) {
|
|
|
527
821
|
if (!silent)
|
|
528
822
|
console.log(msg);
|
|
529
823
|
};
|
|
530
|
-
// 1. Ensure
|
|
824
|
+
// 1. Ensure config.paths.baseDir state (idempotent)
|
|
531
825
|
const { configPath } = ensureConfig();
|
|
532
826
|
const loaded = loadConfig({ configPath });
|
|
533
|
-
|
|
827
|
+
let nextConfig = structuredClone(loaded.config);
|
|
828
|
+
const configuredWorkspace = nextConfig.workspace.projectsBaseDir?.trim() || path.join(nextConfig.paths.baseDir, 'workspace');
|
|
829
|
+
const desiredWorkspace = options.projectsBaseDir
|
|
830
|
+
? resolveAbsolutePath(options.projectsBaseDir)
|
|
831
|
+
: options.yes
|
|
832
|
+
? resolveAbsolutePath(configuredWorkspace)
|
|
833
|
+
: await promptWorkspaceBaseDir(configuredWorkspace);
|
|
834
|
+
if (nextConfig.workspace.projectsBaseDir !== desiredWorkspace) {
|
|
835
|
+
nextConfig.workspace.projectsBaseDir = desiredWorkspace;
|
|
836
|
+
writeConfigFile(configPath, nextConfig);
|
|
837
|
+
log(`✅ Workspace base dir set to: ${desiredWorkspace}`);
|
|
838
|
+
}
|
|
839
|
+
const paths = resolvePaths(nextConfig);
|
|
534
840
|
ensureBaseState(paths);
|
|
535
841
|
rebuildIndex(paths);
|
|
536
842
|
// 2. Detect OpenClaw
|
|
@@ -548,6 +854,23 @@ export async function runOnboard(options) {
|
|
|
548
854
|
log(`⚠️ ${warnings[warnings.length - 1]}`);
|
|
549
855
|
}
|
|
550
856
|
}
|
|
857
|
+
const storedGatewayUrl = nextConfig.openclaw.gatewayUrl?.trim() ?? '';
|
|
858
|
+
const detectedGatewayUrl = resolveOpenClawGatewayUrl(openclawConfig);
|
|
859
|
+
const resolvedGatewayUrl = (() => {
|
|
860
|
+
if (typeof options.gatewayUrl === 'string' && options.gatewayUrl.trim().length > 0) {
|
|
861
|
+
return normalizeGatewayUrl(options.gatewayUrl);
|
|
862
|
+
}
|
|
863
|
+
if (storedGatewayUrl && !shouldReplaceStoredGatewayUrl(storedGatewayUrl, detectedGatewayUrl)) {
|
|
864
|
+
return normalizeGatewayUrl(storedGatewayUrl);
|
|
865
|
+
}
|
|
866
|
+
if (detectedGatewayUrl) {
|
|
867
|
+
return detectedGatewayUrl;
|
|
868
|
+
}
|
|
869
|
+
if (openclawExists && !options.yes) {
|
|
870
|
+
return '';
|
|
871
|
+
}
|
|
872
|
+
return storedGatewayUrl ? normalizeGatewayUrl(storedGatewayUrl) : '';
|
|
873
|
+
})();
|
|
551
874
|
// 3. Agent selection and registration
|
|
552
875
|
let agentsRegistered = [];
|
|
553
876
|
let agentsSkipped = [];
|
|
@@ -563,7 +886,6 @@ export async function runOnboard(options) {
|
|
|
563
886
|
else {
|
|
564
887
|
selectedAgents = await promptAgentSelection(allAgents);
|
|
565
888
|
}
|
|
566
|
-
let nextConfig = loaded.config;
|
|
567
889
|
if (selectedAgents.length > 0) {
|
|
568
890
|
const roleAssignments = options.yes
|
|
569
891
|
? Object.fromEntries(selectedAgents.map((agent) => [agent.id, inferStandardAgentRole({ agentId: agent.id, theme: agent.identity?.theme ?? null })]))
|
|
@@ -593,7 +915,7 @@ export async function runOnboard(options) {
|
|
|
593
915
|
}
|
|
594
916
|
}
|
|
595
917
|
if (!openclawConfig && options.orchestratorId) {
|
|
596
|
-
const orchResult = ensureOrchestratorId(
|
|
918
|
+
const orchResult = ensureOrchestratorId(nextConfig, options.orchestratorId);
|
|
597
919
|
if (orchResult.warning) {
|
|
598
920
|
warnings.push(orchResult.warning);
|
|
599
921
|
log(`⚠️ ${orchResult.warning}`);
|
|
@@ -604,19 +926,25 @@ export async function runOnboard(options) {
|
|
|
604
926
|
}
|
|
605
927
|
}
|
|
606
928
|
// 4. Seed rules
|
|
607
|
-
const projectDir = options.projectDir ?? process.cwd();
|
|
608
|
-
const rulesSourceDir = path.join(projectDir, 'orchestration', 'rules');
|
|
609
929
|
const rulesTargetDir = paths.rulesDir;
|
|
610
|
-
|
|
611
|
-
|
|
930
|
+
const ruleSeed = resolveRuleSeedSource(options.projectDir);
|
|
931
|
+
let rulesCopied = [];
|
|
932
|
+
let rulesSkipped = [];
|
|
933
|
+
if (!ruleSeed.sourceDir) {
|
|
934
|
+
warnings.push('No bundled rule templates available. Rules seeding skipped.');
|
|
612
935
|
log(`⚠️ ${warnings[warnings.length - 1]}`);
|
|
613
936
|
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
937
|
+
else {
|
|
938
|
+
if (options.projectDir && ruleSeed.source === 'bundled') {
|
|
939
|
+
log(`ℹ️ No external rule templates found under ${options.projectDir}; using bundled defaults.`);
|
|
940
|
+
}
|
|
941
|
+
({ copied: rulesCopied, skipped: rulesSkipped } = seedRules(ruleSeed.sourceDir, rulesTargetDir));
|
|
942
|
+
if (rulesCopied.length > 0) {
|
|
943
|
+
log(`✅ Rules copied: ${rulesCopied.join(', ')}`);
|
|
944
|
+
}
|
|
945
|
+
if (rulesSkipped.length > 0) {
|
|
946
|
+
log(`⏭️ Rules already exist (skipped): ${rulesSkipped.join(', ')}`);
|
|
947
|
+
}
|
|
620
948
|
}
|
|
621
949
|
// 5. Stabilize PATH — ensure zigrix is reachable
|
|
622
950
|
const pathResult = ensureZigrixInPath();
|
|
@@ -630,7 +958,59 @@ export async function runOnboard(options) {
|
|
|
630
958
|
warnings.push(pathResult.warning);
|
|
631
959
|
log(`⚠️ ${pathResult.warning}`);
|
|
632
960
|
}
|
|
633
|
-
// 6.
|
|
961
|
+
// 6. Detect and persist openclaw binary path
|
|
962
|
+
let openclawBinPath = null;
|
|
963
|
+
if (openclawExists) {
|
|
964
|
+
openclawBinPath = resolveOpenClawBin(openclawHome);
|
|
965
|
+
if (openclawBinPath) {
|
|
966
|
+
log(`✅ OpenClaw binary found: ${openclawBinPath}`);
|
|
967
|
+
}
|
|
968
|
+
else {
|
|
969
|
+
warnings.push('openclaw binary not found. Dashboard conversation features may be limited.');
|
|
970
|
+
log(`⚠️ ${warnings[warnings.length - 1]}`);
|
|
971
|
+
}
|
|
972
|
+
// Persist openclaw integration config (home, binPath, gatewayUrl)
|
|
973
|
+
const currentConfig = loadConfig({ configPath }).config;
|
|
974
|
+
// Gateway URL: explicit flag > autodiscovery > interactive prompt > stored value
|
|
975
|
+
let finalGatewayUrl = resolvedGatewayUrl;
|
|
976
|
+
if (!finalGatewayUrl && openclawExists && !options.yes) {
|
|
977
|
+
const suggested = detectedGatewayUrl || '';
|
|
978
|
+
finalGatewayUrl = await promptGatewayUrl(suggested);
|
|
979
|
+
}
|
|
980
|
+
if (finalGatewayUrl) {
|
|
981
|
+
log(`✅ OpenClaw gateway URL: ${finalGatewayUrl}`);
|
|
982
|
+
}
|
|
983
|
+
else if (openclawExists) {
|
|
984
|
+
warnings.push('OpenClaw gateway URL not configured. Dashboard gateway features may be limited. Set later with `zigrix configure --section openclaw` or `zigrix onboard --gateway-url <url>`.');
|
|
985
|
+
log(`⚠️ ${warnings[warnings.length - 1]}`);
|
|
986
|
+
}
|
|
987
|
+
const needsUpdate = currentConfig.openclaw.home !== openclawHome ||
|
|
988
|
+
currentConfig.openclaw.binPath !== openclawBinPath ||
|
|
989
|
+
currentConfig.openclaw.gatewayUrl !== (finalGatewayUrl || '');
|
|
990
|
+
if (needsUpdate) {
|
|
991
|
+
currentConfig.openclaw.home = openclawHome;
|
|
992
|
+
currentConfig.openclaw.binPath = openclawBinPath;
|
|
993
|
+
currentConfig.openclaw.gatewayUrl = finalGatewayUrl || '';
|
|
994
|
+
writeConfigFile(configPath, currentConfig);
|
|
995
|
+
log(`✅ OpenClaw config persisted (home: ${openclawHome}, bin: ${openclawBinPath ?? 'not found'}, gateway: ${finalGatewayUrl || 'not set'})`);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
// 7. Stabilize PATH — ensure openclaw is reachable from non-login shells
|
|
999
|
+
let openclawPathResult = { alreadyInPath: false, symlinkCreated: false, symlinkPath: null, warning: null };
|
|
1000
|
+
if (openclawBinPath) {
|
|
1001
|
+
openclawPathResult = ensureOpenClawInPath();
|
|
1002
|
+
if (openclawPathResult.symlinkCreated) {
|
|
1003
|
+
log(`✅ openclaw symlinked to ${openclawPathResult.symlinkPath}`);
|
|
1004
|
+
}
|
|
1005
|
+
else if (openclawPathResult.alreadyInPath) {
|
|
1006
|
+
log('✅ openclaw already in PATH');
|
|
1007
|
+
}
|
|
1008
|
+
if (openclawPathResult.warning) {
|
|
1009
|
+
warnings.push(openclawPathResult.warning);
|
|
1010
|
+
log(`⚠️ ${openclawPathResult.warning}`);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
// 8. Register OpenClaw skills (symlink skill packs)
|
|
634
1014
|
let skillsRegistered = [];
|
|
635
1015
|
let skillsSkipped = [];
|
|
636
1016
|
let skillsFailed = [];
|
|
@@ -661,10 +1041,13 @@ export async function runOnboard(options) {
|
|
|
661
1041
|
ok: true,
|
|
662
1042
|
action: 'onboard',
|
|
663
1043
|
baseDir: paths.baseDir,
|
|
1044
|
+
workspaceBaseDir: nextConfig.workspace.projectsBaseDir,
|
|
664
1045
|
configPath,
|
|
665
1046
|
paths,
|
|
666
1047
|
openclawDetected: openclawExists,
|
|
667
1048
|
openclawHome,
|
|
1049
|
+
openclawBinPath,
|
|
1050
|
+
openclawGatewayUrl: resolvedGatewayUrl || null,
|
|
668
1051
|
agentsRegistered,
|
|
669
1052
|
agentsSkipped,
|
|
670
1053
|
rulesCopied,
|
|
@@ -673,9 +1056,11 @@ export async function runOnboard(options) {
|
|
|
673
1056
|
skillsSkipped,
|
|
674
1057
|
skillsFailed,
|
|
675
1058
|
pathStabilized: pathResult,
|
|
1059
|
+
openclawPathStabilized: openclawPathResult,
|
|
676
1060
|
warnings,
|
|
677
1061
|
checks: {
|
|
678
1062
|
zigrixInPath: pathResult.alreadyInPath || pathResult.symlinkCreated,
|
|
1063
|
+
openclawInPath: openclawPathResult.alreadyInPath || openclawPathResult.symlinkCreated,
|
|
679
1064
|
openclawSkillsDir: openclawSkillsDirExists,
|
|
680
1065
|
},
|
|
681
1066
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ZigrixConfig } from '../config/schema.js';
|
|
2
2
|
import { type ZigrixPaths } from '../state/paths.js';
|
|
3
|
+
export declare function resolveConfiguredProjectDir(config: ZigrixConfig, explicitProjectDir?: string): string | undefined;
|
|
3
4
|
export declare function dispatchTask(paths: ZigrixPaths, config: ZigrixConfig, params: {
|
|
4
5
|
title: string;
|
|
5
6
|
description: string;
|