@aiwg/cli 2026.9.15 → 2026.9.17
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/agentic/code/providers/capability-matrix.yaml +80 -0
- package/agentic/code/providers/model-capabilities.v1.json +119 -34
- package/agentic/code/providers/model-catalog.v1.json +259 -56
- package/dist/src/cli/handlers/sessions.js +159 -45
- package/dist/src/cli/handlers/use.js +20 -1
- package/dist/src/cli/scope-resolver.js +26 -0
- package/dist/src/cli/services/deployment-verification.js +90 -2
- package/dist/src/config/aiwg-config.js +41 -0
- package/dist/src/config/user-registry.js +9 -0
- package/dist/src/dataset/conformance-types.d.ts +2 -1
- package/dist/src/dataset/conformance.js +10 -1
- package/dist/src/dataset/fortemi-live-qualification.d.ts +10 -0
- package/dist/src/dataset/fortemi-live-qualification.js +134 -0
- package/dist/src/extensions/manifest.js +1 -0
- package/dist/src/models/model-capabilities.v1.json +119 -34
- package/dist/src/models/model-catalog.v1.json +259 -56
- package/dist/src/models/model-discovery.js +7 -0
- package/dist/src/models/provider-policy.js +1 -1
- package/dist/src/plugin/skill-command-translator.js +1 -0
- package/dist/src/providers/capability-matrix.yaml +80 -0
- package/dist/src/providers/grok-build-inspect.js +245 -0
- package/dist/src/providers/grok-build-paths.js +92 -0
- package/dist/src/providers/grokbot-paths.js +103 -0
- package/dist/src/providers/provider-definitions.js +172 -2
- package/dist/src/providers/provider-inventory.js +2 -0
- package/dist/src/sessions/adapters/grokbot.js +49 -0
- package/dist/src/sessions/adapters/hermes.js +17 -9
- package/dist/src/sessions/adapters/opencode.js +12 -1
- package/dist/src/sessions/contracts.js +1 -1
- package/dist/src/sessions/export-plan.js +130 -4
- package/dist/src/sessions/fortemi-export-mapping.js +10 -6
- package/dist/src/sessions/fortemi-shard-blobs.js +78 -0
- package/dist/src/sessions/index.js +2 -0
- package/dist/src/sessions/workspace-discovery.js +1 -1
- package/dist/src/skills/deployer.js +17 -0
- package/dist/src/smiths/skillsmith/collision-detector.js +1 -0
- package/package.json +1 -1
- package/schemas/dataset/conformance-receipt.v1.schema.json +1 -1
- package/tools/agents/deploy-agents.mjs +4 -2
- package/tools/agents/providers/grok-build.mjs +153 -0
- package/tools/agents/providers/grokbot.mjs +285 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grok Build verification via `grok inspect` when the binary is available.
|
|
3
|
+
* Presence-only remediation when absent — never invents inspect output.
|
|
4
|
+
*
|
|
5
|
+
* @issue #2575
|
|
6
|
+
*/
|
|
7
|
+
import { accessSync, constants as fsConstants } from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { spawnSync } from 'node:child_process';
|
|
10
|
+
export function grokExecutableCandidates(env, cwd, platform = process.platform) {
|
|
11
|
+
const pathApi = platform === 'win32' ? path.win32 : path.posix;
|
|
12
|
+
const pathDelimiter = platform === 'win32' ? ';' : ':';
|
|
13
|
+
const pathDirs = (env.PATH ?? env.Path ?? process.env.PATH ?? '')
|
|
14
|
+
.split(pathDelimiter)
|
|
15
|
+
// An empty PATH segment means the child cwd on POSIX and Windows.
|
|
16
|
+
.map((entry) => entry || '.');
|
|
17
|
+
const suffixes = platform === 'win32'
|
|
18
|
+
? [...(env.PATHEXT || env.Pathext || '.COM;.EXE;.BAT;.CMD')
|
|
19
|
+
.split(';')
|
|
20
|
+
.map((entry) => entry.trim())
|
|
21
|
+
.filter(Boolean), '']
|
|
22
|
+
: [''];
|
|
23
|
+
const candidates = new Set();
|
|
24
|
+
for (const dir of pathDirs) {
|
|
25
|
+
const absoluteDir = pathApi.isAbsolute(dir) ? dir : pathApi.resolve(cwd, dir);
|
|
26
|
+
for (const suffix of suffixes)
|
|
27
|
+
candidates.add(pathApi.join(absoluteDir, `grok${suffix}`));
|
|
28
|
+
}
|
|
29
|
+
return [...candidates];
|
|
30
|
+
}
|
|
31
|
+
function whichGrok(env, cwd) {
|
|
32
|
+
// Resolve from the supplied environment and deployment cwd without a shell.
|
|
33
|
+
for (const candidate of grokExecutableCandidates(env, cwd)) {
|
|
34
|
+
try {
|
|
35
|
+
accessSync(candidate, process.platform === 'win32' ? fsConstants.F_OK : fsConstants.X_OK);
|
|
36
|
+
return candidate;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// try next
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
function tryParseJson(text) {
|
|
45
|
+
const trimmed = text.trim();
|
|
46
|
+
if (!trimmed)
|
|
47
|
+
return null;
|
|
48
|
+
try {
|
|
49
|
+
const value = JSON.parse(trimmed);
|
|
50
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
51
|
+
? value
|
|
52
|
+
: null;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function asRecordArray(value) {
|
|
59
|
+
if (!Array.isArray(value))
|
|
60
|
+
return [];
|
|
61
|
+
return value.filter((entry) => Boolean(entry) && typeof entry === 'object' && !Array.isArray(entry));
|
|
62
|
+
}
|
|
63
|
+
function pathMentions(haystack, needle) {
|
|
64
|
+
const normalizedHay = haystack.replace(/\\/g, '/');
|
|
65
|
+
const normalizedNeedle = needle.replace(/\\/g, '/');
|
|
66
|
+
return normalizedHay.includes(normalizedNeedle);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Validate a `grok inspect --json` report against artifacts AIWG intended to deploy.
|
|
70
|
+
* Shape follows xAI InspectReport (camelCase): projectInstructions, skills, agents, configSources.
|
|
71
|
+
*/
|
|
72
|
+
export function validateGrokInspectReport(parsed, expected = {}) {
|
|
73
|
+
const mismatches = [];
|
|
74
|
+
const instructions = asRecordArray(parsed.projectInstructions);
|
|
75
|
+
const skills = asRecordArray(parsed.skills);
|
|
76
|
+
const agents = asRecordArray(parsed.agents);
|
|
77
|
+
const configSources = parsed.configSources;
|
|
78
|
+
const layers = configSources && typeof configSources === 'object' && !Array.isArray(configSources)
|
|
79
|
+
? asRecordArray(configSources.layers)
|
|
80
|
+
: [];
|
|
81
|
+
const hasReportShape = Array.isArray(parsed.projectInstructions)
|
|
82
|
+
|| Array.isArray(parsed.skills)
|
|
83
|
+
|| Array.isArray(parsed.agents)
|
|
84
|
+
|| Boolean(configSources);
|
|
85
|
+
if (!hasReportShape) {
|
|
86
|
+
mismatches.push({
|
|
87
|
+
kind: 'shape',
|
|
88
|
+
expected: 'InspectReport fields (projectInstructions/skills/agents/configSources)',
|
|
89
|
+
detail: `parsed keys: ${Object.keys(parsed).slice(0, 20).join(',') || '(none)'}`,
|
|
90
|
+
});
|
|
91
|
+
return mismatches;
|
|
92
|
+
}
|
|
93
|
+
for (const instructionPath of expected.instructionPaths ?? []) {
|
|
94
|
+
const found = instructions.some((entry) => typeof entry.path === 'string' && pathMentions(entry.path, instructionPath));
|
|
95
|
+
if (!found) {
|
|
96
|
+
mismatches.push({
|
|
97
|
+
kind: 'instructions',
|
|
98
|
+
expected: instructionPath,
|
|
99
|
+
detail: `not listed in projectInstructions (${instructions.length} entries)`,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
for (const skillName of expected.skillNames ?? []) {
|
|
104
|
+
const found = skills.some((entry) => (typeof entry.name === 'string' && entry.name === skillName)
|
|
105
|
+
|| (typeof entry.source === 'object' && entry.source !== null
|
|
106
|
+
&& typeof entry.source.path === 'string'
|
|
107
|
+
&& pathMentions(String(entry.source.path), skillName)));
|
|
108
|
+
if (!found) {
|
|
109
|
+
mismatches.push({
|
|
110
|
+
kind: 'skills',
|
|
111
|
+
expected: skillName,
|
|
112
|
+
detail: `not listed in skills (${skills.length} entries)`,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
for (const agentName of expected.agentNames ?? []) {
|
|
117
|
+
const found = agents.some((entry) => typeof entry.name === 'string' && entry.name === agentName);
|
|
118
|
+
if (!found) {
|
|
119
|
+
mismatches.push({
|
|
120
|
+
kind: 'agents',
|
|
121
|
+
expected: agentName,
|
|
122
|
+
detail: `not listed in agents (${agents.length} entries)`,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
for (const substring of expected.configPathSubstrings ?? []) {
|
|
127
|
+
const found = layers.some((entry) => typeof entry.path === 'string' && pathMentions(entry.path, substring));
|
|
128
|
+
if (!found) {
|
|
129
|
+
mismatches.push({
|
|
130
|
+
kind: 'config',
|
|
131
|
+
expected: substring,
|
|
132
|
+
detail: `not listed in configSources.layers (${layers.length} entries)`,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return mismatches;
|
|
137
|
+
}
|
|
138
|
+
const ABSENT_REMEDIATION = 'Install the Grok Build CLI so `grok` is on PATH, then re-run verification. '
|
|
139
|
+
+ 'Until then AIWG only confirms provider registration and on-disk .grok paths — it does not invent inspect output.';
|
|
140
|
+
function isRunOptions(value) {
|
|
141
|
+
if (!value || typeof value !== 'object')
|
|
142
|
+
return false;
|
|
143
|
+
const keys = Object.keys(value);
|
|
144
|
+
if (keys.length === 0)
|
|
145
|
+
return false;
|
|
146
|
+
return keys.every((key) => key === 'env' || key === 'cwd' || key === 'expected');
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Run `grok inspect` (prefer `--json` when the CLI accepts it).
|
|
150
|
+
* Accepts either a ProcessEnv (legacy) or an options bag with env/cwd/expected.
|
|
151
|
+
*/
|
|
152
|
+
export function runGrokInspect(envOrOptions = process.env) {
|
|
153
|
+
const options = isRunOptions(envOrOptions)
|
|
154
|
+
? envOrOptions
|
|
155
|
+
: { env: envOrOptions };
|
|
156
|
+
const env = options.env ?? process.env;
|
|
157
|
+
const cwd = options.cwd ?? process.cwd();
|
|
158
|
+
const binary = whichGrok(env, cwd);
|
|
159
|
+
if (!binary) {
|
|
160
|
+
return { status: 'absent', remediation: ABSENT_REMEDIATION };
|
|
161
|
+
}
|
|
162
|
+
// Prefer machine-readable inspect; fall back to plain inspect if --json is rejected.
|
|
163
|
+
// Use length/index checks — tuple `includes()` narrows incorrectly under TS (TS2345).
|
|
164
|
+
const argSets = [
|
|
165
|
+
['inspect', '--json'],
|
|
166
|
+
['inspect'],
|
|
167
|
+
];
|
|
168
|
+
for (let i = 0; i < argSets.length; i++) {
|
|
169
|
+
const args = argSets[i];
|
|
170
|
+
const wantsJson = args.length > 1 && args[1] === '--json';
|
|
171
|
+
const result = spawnSync(binary, [...args], {
|
|
172
|
+
encoding: 'utf8',
|
|
173
|
+
env,
|
|
174
|
+
cwd,
|
|
175
|
+
timeout: 15_000,
|
|
176
|
+
maxBuffer: 2_000_000,
|
|
177
|
+
// npm exposes Grok through a .cmd shim on Windows; Node requires a shell
|
|
178
|
+
// for .cmd/.bat launchers. Native .exe installs stay shell-free.
|
|
179
|
+
shell: process.platform === 'win32' && /\.(?:cmd|bat)$/i.test(binary),
|
|
180
|
+
});
|
|
181
|
+
if (result.error && result.error.code === 'ENOENT') {
|
|
182
|
+
return { status: 'absent', remediation: ABSENT_REMEDIATION };
|
|
183
|
+
}
|
|
184
|
+
const stdout = result.stdout || '';
|
|
185
|
+
const stderr = result.stderr || '';
|
|
186
|
+
if (result.status === 0) {
|
|
187
|
+
const parsed = tryParseJson(stdout);
|
|
188
|
+
if (!parsed) {
|
|
189
|
+
return {
|
|
190
|
+
status: 'failed',
|
|
191
|
+
binary,
|
|
192
|
+
exitCode: 0,
|
|
193
|
+
stderr,
|
|
194
|
+
stdout,
|
|
195
|
+
cwd,
|
|
196
|
+
mismatches: [{
|
|
197
|
+
kind: 'shape',
|
|
198
|
+
expected: 'JSON InspectReport',
|
|
199
|
+
detail: 'exit 0 but stdout was not a JSON object',
|
|
200
|
+
}],
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
const mismatches = validateGrokInspectReport(parsed, options.expected);
|
|
204
|
+
if (mismatches.length > 0) {
|
|
205
|
+
return {
|
|
206
|
+
status: 'failed',
|
|
207
|
+
binary,
|
|
208
|
+
exitCode: 0,
|
|
209
|
+
stderr,
|
|
210
|
+
stdout,
|
|
211
|
+
cwd,
|
|
212
|
+
mismatches,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
status: 'ok',
|
|
217
|
+
binary,
|
|
218
|
+
stdout,
|
|
219
|
+
parsed,
|
|
220
|
+
cwd,
|
|
221
|
+
mismatches: [],
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
if (wantsJson && /unknown|unrecognized|invalid/i.test(`${stderr}\n${stdout}`)) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
status: 'failed',
|
|
229
|
+
binary,
|
|
230
|
+
exitCode: result.status,
|
|
231
|
+
stderr,
|
|
232
|
+
stdout,
|
|
233
|
+
cwd,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
status: 'failed',
|
|
238
|
+
binary,
|
|
239
|
+
exitCode: null,
|
|
240
|
+
stderr: 'grok inspect did not succeed',
|
|
241
|
+
stdout: '',
|
|
242
|
+
cwd,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=grok-build-inspect.js.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grok Build home/path resolution — documented defaults with fail-closed overrides.
|
|
3
|
+
*
|
|
4
|
+
* Default `$GROK_HOME` is `~/.grok` (absolute after expand). Relative overrides,
|
|
5
|
+
* bare `~`, root-only paths, and null bytes are rejected.
|
|
6
|
+
*
|
|
7
|
+
* Distinct from Grok Bot (`grokbot` / `AIWG_GROKBOT_SKILLS_DIR`): never share
|
|
8
|
+
* roots or invent `~/.grokbot` / `.cursor` paths.
|
|
9
|
+
*
|
|
10
|
+
* @see https://docs.x.ai/build/settings
|
|
11
|
+
* @issue #2575
|
|
12
|
+
*/
|
|
13
|
+
import { homedir } from 'node:os';
|
|
14
|
+
import * as path from 'node:path';
|
|
15
|
+
export const GROK_HOME_ENV = 'GROK_HOME';
|
|
16
|
+
export function isGrokFilesystemRoot(resolved, pathFlavor = path) {
|
|
17
|
+
return pathFlavor.parse(resolved).root === resolved;
|
|
18
|
+
}
|
|
19
|
+
function expandHomePrefix(raw, userHome = homedir()) {
|
|
20
|
+
if (raw === '~')
|
|
21
|
+
return raw;
|
|
22
|
+
if (raw.startsWith('~/'))
|
|
23
|
+
return path.join(userHome, raw.slice(2));
|
|
24
|
+
return raw;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolve Grok Build home directory.
|
|
28
|
+
* When unset, defaults to `~/.grok` (expanded). Explicit env must be absolute or `~/…`.
|
|
29
|
+
*/
|
|
30
|
+
export function resolveGrokHomeResult(env = process.env, userHome = homedir()) {
|
|
31
|
+
const raw = (env[GROK_HOME_ENV] || '').trim();
|
|
32
|
+
const fromEnv = Boolean(raw);
|
|
33
|
+
const candidate = fromEnv ? raw : path.join(userHome, '.grok');
|
|
34
|
+
const expanded = expandHomePrefix(candidate, userHome);
|
|
35
|
+
if (expanded === '~' || expanded.startsWith('~')) {
|
|
36
|
+
return {
|
|
37
|
+
ok: false,
|
|
38
|
+
reason: 'not-absolute',
|
|
39
|
+
message: `${GROK_HOME_ENV} must be an absolute path or ~/… (got '${raw || '(default)'}'). Bare '~' is rejected.`,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (!path.isAbsolute(expanded)) {
|
|
43
|
+
return {
|
|
44
|
+
ok: false,
|
|
45
|
+
reason: 'not-absolute',
|
|
46
|
+
message: `${GROK_HOME_ENV} must be an absolute path (got '${raw}'). Relative overrides are rejected.`,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (raw.includes('\0') || expanded.includes('\0')) {
|
|
50
|
+
return {
|
|
51
|
+
ok: false,
|
|
52
|
+
reason: 'traversal',
|
|
53
|
+
message: `${GROK_HOME_ENV} contains an invalid path character.`,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const resolved = path.resolve(expanded);
|
|
57
|
+
if (!resolved || isGrokFilesystemRoot(resolved)) {
|
|
58
|
+
return {
|
|
59
|
+
ok: false,
|
|
60
|
+
reason: 'root',
|
|
61
|
+
message: `${GROK_HOME_ENV} resolves to filesystem root; refuse to deploy there.`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return { ok: true, path: resolved, source: fromEnv ? 'env' : 'default' };
|
|
65
|
+
}
|
|
66
|
+
export function resolveGrokHome(env = process.env, userHome = homedir()) {
|
|
67
|
+
const result = resolveGrokHomeResult(env, userHome);
|
|
68
|
+
return result.ok ? result.path : null;
|
|
69
|
+
}
|
|
70
|
+
export function grokBuildProjectPaths() {
|
|
71
|
+
return {
|
|
72
|
+
skills: '.grok/skills',
|
|
73
|
+
agents: '.grok/agents',
|
|
74
|
+
rules: '.grok/rules',
|
|
75
|
+
hooks: '.grok/hooks',
|
|
76
|
+
config: '.grok/config.toml',
|
|
77
|
+
agentsMd: 'AGENTS.md',
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export function grokBuildUserPaths(grokHome) {
|
|
81
|
+
return {
|
|
82
|
+
skills: path.join(grokHome, 'skills'),
|
|
83
|
+
agents: path.join(grokHome, 'agents'),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
export function grokHomeRemediation(env = process.env) {
|
|
87
|
+
const result = resolveGrokHomeResult(env);
|
|
88
|
+
if (result.ok)
|
|
89
|
+
return `${GROK_HOME_ENV}=${result.path} (${result.source})`;
|
|
90
|
+
return result.message;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=grok-build-paths.js.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grok Bot skill-root resolution — fail-closed until explicitly configured.
|
|
3
|
+
*
|
|
4
|
+
* Unlike Hermes (HERMES_HOME with a documented default) or Pi
|
|
5
|
+
* (PI_CODING_AGENT_DIR with ~/.pi/agent fallback), Grok Bot's native skill
|
|
6
|
+
* filesystem root is not yet verified in AIWG. This module therefore:
|
|
7
|
+
*
|
|
8
|
+
* 1. Honors AIWG_GROKBOT_SKILLS_DIR when set to an absolute path
|
|
9
|
+
* 2. Rejects relative paths, `~`-only values that would invent a home layout,
|
|
10
|
+
* and path segments that attempt traversal after resolution
|
|
11
|
+
* 3. Returns null when unset — callers MUST fail closed for user-scope writes
|
|
12
|
+
*
|
|
13
|
+
* Never invent ~/.grokbot or ~/.cursor as a fallback.
|
|
14
|
+
*
|
|
15
|
+
* @see docs/architecture/adr-grokbot-provider-target.md
|
|
16
|
+
* @issue #205 #207
|
|
17
|
+
*/
|
|
18
|
+
import { homedir } from 'node:os';
|
|
19
|
+
import * as path from 'node:path';
|
|
20
|
+
export const GROKBOT_SKILLS_DIR_ENV = 'AIWG_GROKBOT_SKILLS_DIR';
|
|
21
|
+
/**
|
|
22
|
+
* Expand a leading `~/` against the operator home directory.
|
|
23
|
+
* Bare `~` is rejected — it would invent a home skill root.
|
|
24
|
+
*/
|
|
25
|
+
function expandHomePrefix(raw, userHome = homedir()) {
|
|
26
|
+
if (raw === '~')
|
|
27
|
+
return raw;
|
|
28
|
+
if (raw.startsWith('~/'))
|
|
29
|
+
return path.join(userHome, raw.slice(2));
|
|
30
|
+
return raw;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Validate and resolve the configured Grok Bot skills directory.
|
|
34
|
+
* Returns a structured result so CLI/doctor can print actionable remediation.
|
|
35
|
+
*/
|
|
36
|
+
export function resolveGrokbotSkillsDirResult(env = process.env, userHome = homedir()) {
|
|
37
|
+
const raw = (env[GROKBOT_SKILLS_DIR_ENV] || '').trim();
|
|
38
|
+
if (!raw) {
|
|
39
|
+
return {
|
|
40
|
+
ok: false,
|
|
41
|
+
reason: 'unset',
|
|
42
|
+
message: `Grok Bot user-scope skill root is not configured. Set ${GROKBOT_SKILLS_DIR_ENV} to the absolute path of the Grok Bot skills/workflows directory before using --scope user or --global. AIWG does not invent ~/.grokbot.`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const expanded = expandHomePrefix(raw, userHome);
|
|
46
|
+
if (expanded === '~' || expanded.startsWith('~')) {
|
|
47
|
+
return {
|
|
48
|
+
ok: false,
|
|
49
|
+
reason: 'not-absolute',
|
|
50
|
+
message: `${GROKBOT_SKILLS_DIR_ENV} must be an absolute path (got '${raw}'). Bare '~' is rejected so AIWG never invents a Grok home layout.`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (!path.isAbsolute(expanded)) {
|
|
54
|
+
return {
|
|
55
|
+
ok: false,
|
|
56
|
+
reason: 'not-absolute',
|
|
57
|
+
message: `${GROKBOT_SKILLS_DIR_ENV} must be an absolute path (got '${raw}'). Relative values are rejected.`,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const resolved = path.resolve(expanded);
|
|
61
|
+
// After resolve(), reject any attempt to escape via .. that still looks suspicious
|
|
62
|
+
// relative to a normalized absolute root — path.resolve already collapses '..',
|
|
63
|
+
// so the main remaining check is that we did not start relative.
|
|
64
|
+
if (raw.includes('\0') || resolved.includes('\0')) {
|
|
65
|
+
return {
|
|
66
|
+
ok: false,
|
|
67
|
+
reason: 'traversal',
|
|
68
|
+
message: `${GROKBOT_SKILLS_DIR_ENV} contains an invalid path character.`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// Defensive: if the pre-resolve value contained '..' segments that would
|
|
72
|
+
// escape a declared parent, surface traversal. Absolute resolve is fine;
|
|
73
|
+
// we still refuse paths that are only '..' noise without a real root.
|
|
74
|
+
const normalizedInput = path.normalize(expanded);
|
|
75
|
+
if (normalizedInput === path.sep || normalizedInput === '') {
|
|
76
|
+
return {
|
|
77
|
+
ok: false,
|
|
78
|
+
reason: 'empty',
|
|
79
|
+
message: `${GROKBOT_SKILLS_DIR_ENV} resolves to an empty or root-only path; refuse to deploy there.`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return { ok: true, path: resolved, source: 'env' };
|
|
83
|
+
}
|
|
84
|
+
/** Absolute skills dir when configured; otherwise null (fail closed). */
|
|
85
|
+
export function resolveGrokbotSkillsDir(env = process.env, userHome = homedir()) {
|
|
86
|
+
const result = resolveGrokbotSkillsDirResult(env, userHome);
|
|
87
|
+
return result.ok ? result.path : null;
|
|
88
|
+
}
|
|
89
|
+
/** True when user-scope skill writes are allowed. */
|
|
90
|
+
export function isGrokbotUserScopeConfigured(env = process.env, userHome = homedir()) {
|
|
91
|
+
return resolveGrokbotSkillsDir(env, userHome) !== null;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* User-facing remediation for missing/invalid Grok Bot skill root.
|
|
95
|
+
* Shared by `aiwg use`, doctor, and the provider writer.
|
|
96
|
+
*/
|
|
97
|
+
export function grokbotMissingRootRemediation(env = process.env, userHome = homedir()) {
|
|
98
|
+
const result = resolveGrokbotSkillsDirResult(env, userHome);
|
|
99
|
+
if (result.ok)
|
|
100
|
+
return '';
|
|
101
|
+
return result.message;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=grokbot-paths.js.map
|
|
@@ -2,6 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
import { homedir } from 'os';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import { resolveHermesHomePath } from './hermes-home.js';
|
|
5
|
+
import { resolveGrokbotSkillsDir } from './grokbot-paths.js';
|
|
5
6
|
import { getProviderCapabilities, } from './capability-matrix.js';
|
|
6
7
|
const ArtifactPathsSchema = z.object({
|
|
7
8
|
agents: z.string().nullable(),
|
|
@@ -19,6 +20,8 @@ const ProviderDefinitionSchema = z.object({
|
|
|
19
20
|
'cursor',
|
|
20
21
|
'deepseek-harness',
|
|
21
22
|
'factory',
|
|
23
|
+
'grokbot',
|
|
24
|
+
'grok-build',
|
|
22
25
|
'hermes',
|
|
23
26
|
'opencode',
|
|
24
27
|
'openclaw',
|
|
@@ -36,7 +39,7 @@ const ProviderDefinitionSchema = z.object({
|
|
|
36
39
|
upstream: z.object({
|
|
37
40
|
source: z.string().url(),
|
|
38
41
|
version: z.string().min(1),
|
|
39
|
-
revision: z.string().regex(/^[0-9a-f]{40}$/),
|
|
42
|
+
revision: z.string().regex(/^[0-9a-f]{40}$/).refine((value) => value !== '0'.repeat(40), { message: 'upstream.revision must be a real reviewed SHA, not all-zero' }),
|
|
40
43
|
runtime: z.string().min(1),
|
|
41
44
|
lastVerified: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
|
42
45
|
}).optional(),
|
|
@@ -140,6 +143,8 @@ export const PROVIDER_IDS = [
|
|
|
140
143
|
'cursor',
|
|
141
144
|
'deepseek-harness',
|
|
142
145
|
'factory',
|
|
146
|
+
'grokbot',
|
|
147
|
+
'grok-build',
|
|
143
148
|
'hermes',
|
|
144
149
|
'opencode',
|
|
145
150
|
'openclaw',
|
|
@@ -204,6 +209,35 @@ const CONTEXT_CONTRACTS = {
|
|
|
204
209
|
bootstrapTargets: ['AGENTS.md'], maxContextBytes: null, recommendedMaxLines: 150, nestedContext: true, support: 'supported',
|
|
205
210
|
verification: { method: 'official Factory AGENTS.md discovery hierarchy', source: 'https://docs.factory.ai/cli/configuration/agents-md', lastVerified: VERIFIED_ON },
|
|
206
211
|
},
|
|
212
|
+
grokbot: {
|
|
213
|
+
startupFiles: ['AGENTS.md'],
|
|
214
|
+
precedence: ['provider/system', 'project AGENTS.md when the host exposes it', 'explicit aiwg discover/show'],
|
|
215
|
+
loadMode: 'prose-directive', includeSyntax: null, configRegistration: null,
|
|
216
|
+
bootstrapTargets: ['AGENTS.md'], maxContextBytes: null, recommendedMaxLines: null, nestedContext: false, support: 'degraded',
|
|
217
|
+
verification: {
|
|
218
|
+
method: 'AIWG discover-first adapter contract; native Grok Bot startup/include path not yet verified — explicit-read guidance only',
|
|
219
|
+
source: 'docs/architecture/adr-grokbot-provider-target.md',
|
|
220
|
+
lastVerified: '2026-09-15',
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
'grok-build': {
|
|
224
|
+
// config.toml is configuration (MCP/plugins/permissions), not a startup/context file.
|
|
225
|
+
// Hierarchical instructions: AGENTS.md + .grok/rules/*.md, root-to-cwd, deeper wins.
|
|
226
|
+
startupFiles: ['AGENTS.md', '.grok/rules/*.md'],
|
|
227
|
+
precedence: [
|
|
228
|
+
'provider/system',
|
|
229
|
+
'global ~/.grok instruction sources',
|
|
230
|
+
'root-to-cwd AGENTS.md and .grok/rules/*.md (deeper files win on conflicts)',
|
|
231
|
+
'$GROK_HOME for user skills and user config',
|
|
232
|
+
],
|
|
233
|
+
loadMode: 'prose-directive', includeSyntax: null, configRegistration: null,
|
|
234
|
+
bootstrapTargets: ['AGENTS.md'], maxContextBytes: null, recommendedMaxLines: null, nestedContext: true, support: 'supported',
|
|
235
|
+
verification: {
|
|
236
|
+
method: 'xAI project-rules docs (AGENTS.md + .grok/rules hierarchical discovery); grok inspect when installed',
|
|
237
|
+
source: 'https://docs.x.ai/build/features/project-rules',
|
|
238
|
+
lastVerified: '2026-09-20',
|
|
239
|
+
},
|
|
240
|
+
},
|
|
207
241
|
hermes: {
|
|
208
242
|
startupFiles: ['.hermes.md', 'AGENTS.md'], precedence: ['provider/system', '.hermes.md', 'AGENTS.md'],
|
|
209
243
|
loadMode: 'prose-directive', includeSyntax: null, configRegistration: null,
|
|
@@ -556,6 +590,134 @@ const BUILT_IN_SEEDS = [
|
|
|
556
590
|
},
|
|
557
591
|
matrixRef: 'factory',
|
|
558
592
|
},
|
|
593
|
+
{
|
|
594
|
+
id: 'grokbot',
|
|
595
|
+
displayName: 'Grok Bot',
|
|
596
|
+
aliases: [],
|
|
597
|
+
// #210: promoted stable — Linux PUW + security + migration + maintainer Linux-only waiver 2026-09-16.
|
|
598
|
+
status: 'stable',
|
|
599
|
+
builtIn: true,
|
|
600
|
+
surfaces: {
|
|
601
|
+
primary: 'grokbot',
|
|
602
|
+
compatibility: [],
|
|
603
|
+
precedence: ['AGENTS.md', 'AIWG_GROKBOT_SKILLS_DIR when configured'],
|
|
604
|
+
related: [],
|
|
605
|
+
},
|
|
606
|
+
// Do not treat xAI API keys or generic GROK_* model env as Grok Bot evidence.
|
|
607
|
+
detection: { env: [], process: [], capabilityId: 'grokbot' },
|
|
608
|
+
paths: {
|
|
609
|
+
deployTarget: 'mixed',
|
|
610
|
+
artifacts: {
|
|
611
|
+
agents: null,
|
|
612
|
+
commands: null,
|
|
613
|
+
// Native skill copies only when AIWG_GROKBOT_SKILLS_DIR is set at deploy time.
|
|
614
|
+
skills: null,
|
|
615
|
+
rules: null,
|
|
616
|
+
behaviors: null,
|
|
617
|
+
},
|
|
618
|
+
kernelSkills: null,
|
|
619
|
+
contextDiscovery: {
|
|
620
|
+
agents: '.agents/agents',
|
|
621
|
+
skills: null,
|
|
622
|
+
rules: null,
|
|
623
|
+
behaviors: null,
|
|
624
|
+
},
|
|
625
|
+
configFile: 'AGENTS.md',
|
|
626
|
+
contextFiles: { aiwgMd: true, agentsMd: true, claudeMdHook: false, hookFile: null, contextFile: 'AGENTS.md' },
|
|
627
|
+
},
|
|
628
|
+
smithPaths: {
|
|
629
|
+
agents: null,
|
|
630
|
+
commands: null,
|
|
631
|
+
skills: null,
|
|
632
|
+
rules: null,
|
|
633
|
+
fileExtension: '.md',
|
|
634
|
+
configFile: 'AGENTS.md',
|
|
635
|
+
aggregated: false,
|
|
636
|
+
},
|
|
637
|
+
// Skills root is env-gated (AIWG_GROKBOT_SKILLS_DIR). skillsBaseDir is a
|
|
638
|
+
// documentation sentinel only — deployer resolves via resolveGrokbotSkillsDir
|
|
639
|
+
// and never joins ~/grokbot-skills.
|
|
640
|
+
skillNamespace: {
|
|
641
|
+
deploymentGroup: 'deep-recursion',
|
|
642
|
+
pathType: 'home-dir',
|
|
643
|
+
skillsBaseDir: 'AIWG_GROKBOT_SKILLS_DIR',
|
|
644
|
+
subdirLayout: true,
|
|
645
|
+
},
|
|
646
|
+
adapters: {
|
|
647
|
+
agentFormat: 'agents-md',
|
|
648
|
+
hookBridge: null,
|
|
649
|
+
mcpInjection: null,
|
|
650
|
+
contextAggregation: 'agents-md',
|
|
651
|
+
ruleFormat: 'agents-md-section',
|
|
652
|
+
},
|
|
653
|
+
matrixRef: 'grokbot',
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
id: 'grok-build',
|
|
657
|
+
displayName: 'Grok Build',
|
|
658
|
+
aliases: [],
|
|
659
|
+
status: 'experimental',
|
|
660
|
+
builtIn: true,
|
|
661
|
+
upstream: {
|
|
662
|
+
source: 'https://github.com/xai-org/grok-build',
|
|
663
|
+
version: '1.0.38',
|
|
664
|
+
// Reviewed public tree (docs + inspect schema), not an all-zero placeholder.
|
|
665
|
+
revision: '4247f661689354b831191f11eeeac8424993fe3d',
|
|
666
|
+
runtime: 'Grok Build CLI',
|
|
667
|
+
lastVerified: '2026-09-20',
|
|
668
|
+
},
|
|
669
|
+
surfaces: {
|
|
670
|
+
primary: 'grok-build',
|
|
671
|
+
compatibility: [],
|
|
672
|
+
// Agents/rules native writers deferred #2577; host still discovers those dirs.
|
|
673
|
+
precedence: ['AGENTS.md', '.grok/skills/', '.grok/rules/ (host; AIWG writer deferred #2577)', '.grok/config.toml (config only)', '$GROK_HOME'],
|
|
674
|
+
related: [],
|
|
675
|
+
},
|
|
676
|
+
detection: { env: ['GROK_HOME'], process: ['grok'], capabilityId: 'grok-build' },
|
|
677
|
+
paths: {
|
|
678
|
+
deployTarget: 'mixed',
|
|
679
|
+
artifacts: {
|
|
680
|
+
// Wave 1 writes skills + AGENTS.md only; agents/rules indexed until #2577.
|
|
681
|
+
agents: null,
|
|
682
|
+
commands: null,
|
|
683
|
+
skills: '.grok/skills',
|
|
684
|
+
rules: null,
|
|
685
|
+
behaviors: null,
|
|
686
|
+
},
|
|
687
|
+
kernelSkills: '.grok/skills',
|
|
688
|
+
contextDiscovery: {
|
|
689
|
+
agents: '.grok/agents',
|
|
690
|
+
skills: '.grok/skills',
|
|
691
|
+
rules: '.grok/rules',
|
|
692
|
+
behaviors: null,
|
|
693
|
+
},
|
|
694
|
+
configFile: '.grok/config.toml',
|
|
695
|
+
contextFiles: { aiwgMd: true, agentsMd: true, claudeMdHook: false, hookFile: null, contextFile: 'AGENTS.md' },
|
|
696
|
+
},
|
|
697
|
+
smithPaths: {
|
|
698
|
+
agents: null,
|
|
699
|
+
commands: null,
|
|
700
|
+
skills: '.grok/skills',
|
|
701
|
+
rules: null,
|
|
702
|
+
fileExtension: '.md',
|
|
703
|
+
configFile: '.grok/config.toml',
|
|
704
|
+
aggregated: false,
|
|
705
|
+
},
|
|
706
|
+
skillNamespace: {
|
|
707
|
+
deploymentGroup: 'deep-recursion',
|
|
708
|
+
pathType: 'project',
|
|
709
|
+
skillsBaseDir: '.grok/skills',
|
|
710
|
+
subdirLayout: true,
|
|
711
|
+
},
|
|
712
|
+
adapters: {
|
|
713
|
+
agentFormat: 'agents-md',
|
|
714
|
+
hookBridge: null,
|
|
715
|
+
mcpInjection: null,
|
|
716
|
+
contextAggregation: 'agents-md',
|
|
717
|
+
ruleFormat: 'agents-md-section',
|
|
718
|
+
},
|
|
719
|
+
matrixRef: 'grok-build',
|
|
720
|
+
},
|
|
559
721
|
{
|
|
560
722
|
id: 'hermes',
|
|
561
723
|
aliases: [],
|
|
@@ -1137,10 +1299,18 @@ export function getProviderArtifactPathStrings(provider) {
|
|
|
1137
1299
|
const definition = getProviderDefinition(provider);
|
|
1138
1300
|
if (!definition)
|
|
1139
1301
|
return undefined;
|
|
1302
|
+
const normalized = normalizeProviderDefinitionId(provider);
|
|
1303
|
+
// Grok Bot skills are env-gated (AIWG_GROKBOT_SKILLS_DIR). Resolve dynamically so
|
|
1304
|
+
// deploy counting / user-scope same-path inventory can see the configured root
|
|
1305
|
+
// without inventing ~/.grokbot (#210).
|
|
1306
|
+
let skills = expandProviderHomePath(definition.paths.artifacts.skills);
|
|
1307
|
+
if (normalized === 'grokbot' && !skills) {
|
|
1308
|
+
skills = resolveGrokbotSkillsDir() ?? '';
|
|
1309
|
+
}
|
|
1140
1310
|
return {
|
|
1141
1311
|
agents: expandProviderHomePath(definition.paths.artifacts.agents),
|
|
1142
1312
|
commands: expandProviderHomePath(definition.paths.artifacts.commands),
|
|
1143
|
-
skills
|
|
1313
|
+
skills,
|
|
1144
1314
|
rules: expandProviderHomePath(definition.paths.artifacts.rules),
|
|
1145
1315
|
behaviors: expandProviderHomePath(definition.paths.artifacts.behaviors),
|
|
1146
1316
|
};
|
|
@@ -12,6 +12,8 @@ const PROVIDER_EXECUTABLES = {
|
|
|
12
12
|
cursor: ['cursor'],
|
|
13
13
|
'deepseek-harness': ['dsh'],
|
|
14
14
|
factory: ['droid'],
|
|
15
|
+
// Grok Bot has no verified CLI executable name; omit rather than invent.
|
|
16
|
+
'grok-build': ['grok'],
|
|
15
17
|
hermes: ['hermes'],
|
|
16
18
|
opencode: ['opencode'],
|
|
17
19
|
openclaw: ['openclaw'],
|