@aiwg/cli 2026.9.12 → 2026.9.16
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 +42 -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 +348 -42
- package/dist/src/cli/handlers/use.js +20 -1
- package/dist/src/cli/scope-resolver.js +13 -0
- package/dist/src/cli/services/deployment-verification.js +33 -2
- package/dist/src/config/aiwg-config.js +39 -0
- package/dist/src/config/user-registry.js +9 -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 +42 -0
- package/dist/src/providers/grokbot-paths.js +103 -0
- package/dist/src/providers/provider-definitions.js +85 -1
- package/dist/src/providers/provider-inventory.js +1 -0
- package/dist/src/sessions/adapters/claude.js +127 -3
- 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 +243 -0
- package/dist/src/sessions/fortemi-export-mapping.js +300 -0
- package/dist/src/sessions/fortemi-shard-blobs.js +78 -0
- package/dist/src/sessions/fortemi-shard-recovery.js +151 -0
- package/dist/src/sessions/index.js +6 -0
- package/dist/src/sessions/output-lineage.js +43 -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/tools/agents/deploy-agents.mjs +3 -2
- package/tools/agents/providers/grokbot.mjs +285 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recovers an `AiwgFortemiIndexExport`-shaped record graph from a real
|
|
3
|
+
* `full-v1`/`2.0.0` Knowledge Shard archive built by
|
|
4
|
+
* `aiwgFortemiIndexToKnowledgeShardWithReport` (#2564).
|
|
5
|
+
*
|
|
6
|
+
* `@fortemi/core`'s own `aiwgFortemiIndexFromKnowledgeShard` only recovers
|
|
7
|
+
* archives built by the plain `aiwgFortemiIndexToKnowledgeShard` (`core-v1`):
|
|
8
|
+
* that function embeds the entire original record verbatim as opaque note
|
|
9
|
+
* metadata (`metadata.aiwg_fortemi_index`), which is what its reader looks
|
|
10
|
+
* for. The strict `full-v1` converter deliberately does not do this -- the
|
|
11
|
+
* point of `full-v1` is a genuinely native note/tag/link/provenance
|
|
12
|
+
* representation any Fortemi consumer can read, not an AIWG-only escape
|
|
13
|
+
* hatch. Verified empirically: `aiwgFortemiIndexFromKnowledgeShard` throws
|
|
14
|
+
* "has no AIWG index metadata" against a `full-v1` archive, and even AIWG's
|
|
15
|
+
* own artifact-index shard export test only round-trips through it using
|
|
16
|
+
* `profile: "core-v1"`.
|
|
17
|
+
*
|
|
18
|
+
* So recovering a `full-v1` archive means reading the native shard files
|
|
19
|
+
* directly and inverting this module's own mapping:
|
|
20
|
+
* - id/type/source/privacy <- note.metadata.aiwg_source
|
|
21
|
+
* - title/text <- note.title / note.original_content
|
|
22
|
+
* - tags <- note.tags
|
|
23
|
+
* - relationships <- links.jsonl (from_note_id/to_note_id/kind/score)
|
|
24
|
+
* - provenance/provenance_events <- provenance_activities.jsonl, split by
|
|
25
|
+
* the `source:<field>` activity_type convention this module uses to
|
|
26
|
+
* distinguish `provenance` entries from `provenance_events` entries
|
|
27
|
+
*
|
|
28
|
+
* The index-level wrapper (`AiwgFortemiIndexExport.source`/`compatibility`)
|
|
29
|
+
* is genuinely not preserved anywhere in a full-v1 archive -- there is no
|
|
30
|
+
* per-archive metadata sidecar for it, only per-note. Recovery reports this
|
|
31
|
+
* honestly with a fixed placeholder rather than inventing a value.
|
|
32
|
+
*/
|
|
33
|
+
import { unpackTarGz } from '@fortemi/core';
|
|
34
|
+
import { EXPORT_MANIFEST_RECORD_TYPE, SESSION_EXPORT_INDEX_SCHEMA_VERSION, SESSION_EXPORT_RECORD_SCHEMA_VERSION, } from './fortemi-export-mapping.js';
|
|
35
|
+
const SOURCE_PROVENANCE_PREFIX = 'source:';
|
|
36
|
+
function decodeJsonLines(bytes) {
|
|
37
|
+
if (!bytes)
|
|
38
|
+
return [];
|
|
39
|
+
const text = new TextDecoder().decode(bytes);
|
|
40
|
+
return text.split('\n').filter((line) => line.trim().length > 0).map((line) => JSON.parse(line));
|
|
41
|
+
}
|
|
42
|
+
function isAiwgSourceSidecar(value) {
|
|
43
|
+
if (!value || typeof value !== 'object')
|
|
44
|
+
return false;
|
|
45
|
+
const candidate = value;
|
|
46
|
+
return typeof candidate.record_id === 'string' && typeof candidate.record_type === 'string';
|
|
47
|
+
}
|
|
48
|
+
export class FortemiShardRecoveryError extends Error {
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Recovers the record graph from a `full-v1` archive. Throws
|
|
52
|
+
* `FortemiShardRecoveryError` if the archive has no notes, or any note is
|
|
53
|
+
* missing the `aiwg_source` sidecar this module's own mapping always writes
|
|
54
|
+
* (meaning the shard wasn't produced by this mapping and can't be recovered
|
|
55
|
+
* this way).
|
|
56
|
+
*/
|
|
57
|
+
export function recoverAiwgFortemiIndexFromFullV1Shard(bytes) {
|
|
58
|
+
const files = unpackTarGz(bytes);
|
|
59
|
+
const notes = decodeJsonLines(files.get('notes.jsonl'));
|
|
60
|
+
if (notes.length === 0) {
|
|
61
|
+
throw new FortemiShardRecoveryError('Knowledge Shard contains no notes to recover');
|
|
62
|
+
}
|
|
63
|
+
const links = decodeJsonLines(files.get('links.jsonl'));
|
|
64
|
+
const activities = decodeJsonLines(files.get('provenance_activities.jsonl'));
|
|
65
|
+
const noteIdToRecordId = new Map();
|
|
66
|
+
for (const note of notes) {
|
|
67
|
+
const sidecar = note.metadata?.aiwg_source;
|
|
68
|
+
if (!isAiwgSourceSidecar(sidecar)) {
|
|
69
|
+
throw new FortemiShardRecoveryError(`Knowledge Shard note ${note.id} has no aiwg_source metadata -- `
|
|
70
|
+
+ 'this archive was not produced by the AIWG session export mapping');
|
|
71
|
+
}
|
|
72
|
+
noteIdToRecordId.set(note.id, sidecar.record_id);
|
|
73
|
+
}
|
|
74
|
+
const items = notes.map((note) => {
|
|
75
|
+
const sidecar = note.metadata.aiwg_source;
|
|
76
|
+
const relationships = links
|
|
77
|
+
.filter((link) => link.from_note_id === note.id)
|
|
78
|
+
.map((link) => ({
|
|
79
|
+
type: link.kind,
|
|
80
|
+
target_id: link.to_note_id ? (noteIdToRecordId.get(link.to_note_id) ?? link.to_note_id) : '',
|
|
81
|
+
confidence: link.score ?? undefined,
|
|
82
|
+
}));
|
|
83
|
+
const noteActivities = activities.filter((activity) => activity.note_id === note.id);
|
|
84
|
+
const provenance = [];
|
|
85
|
+
const provenanceEvents = [];
|
|
86
|
+
for (const activity of noteActivities) {
|
|
87
|
+
const metadata = activity.metadata ?? {};
|
|
88
|
+
if (activity.activity_type.startsWith(SOURCE_PROVENANCE_PREFIX)) {
|
|
89
|
+
provenance.push({
|
|
90
|
+
field: activity.activity_type.slice(SOURCE_PROVENANCE_PREFIX.length),
|
|
91
|
+
source: metadata.source ?? activity.model_name ?? '',
|
|
92
|
+
path: metadata.path ?? '',
|
|
93
|
+
confidence: metadata.confidence ?? 'source',
|
|
94
|
+
privacy: metadata.privacy ?? 'private',
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
provenanceEvents.push({
|
|
99
|
+
activity: activity.activity_type,
|
|
100
|
+
agent: activity.model_name ?? undefined,
|
|
101
|
+
started_at: activity.started_at ?? undefined,
|
|
102
|
+
ended_at: activity.ended_at ?? undefined,
|
|
103
|
+
source: metadata.source ?? undefined,
|
|
104
|
+
path: metadata.path ?? undefined,
|
|
105
|
+
confidence: metadata.confidence ?? undefined,
|
|
106
|
+
privacy: metadata.privacy ?? undefined,
|
|
107
|
+
attributes: metadata.attributes ?? undefined,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
schema_version: SESSION_EXPORT_RECORD_SCHEMA_VERSION,
|
|
113
|
+
id: sidecar.record_id,
|
|
114
|
+
type: sidecar.record_type,
|
|
115
|
+
source: sidecar.source,
|
|
116
|
+
title: note.title,
|
|
117
|
+
text: note.original_content,
|
|
118
|
+
facets: {},
|
|
119
|
+
tags: note.tags,
|
|
120
|
+
concepts: [],
|
|
121
|
+
relationships,
|
|
122
|
+
provenance,
|
|
123
|
+
provenance_events: provenanceEvents,
|
|
124
|
+
privacy: sidecar.privacy,
|
|
125
|
+
updated_at: sidecar.source.updated_at ?? new Date(0).toISOString(),
|
|
126
|
+
};
|
|
127
|
+
});
|
|
128
|
+
items.sort((left, right) => left.id.localeCompare(right.id));
|
|
129
|
+
// The export-manifest record (see fortemi-export-mapping.ts) carries the
|
|
130
|
+
// index-level wrapper fields through the same provenance_events mechanism
|
|
131
|
+
// as everything else. Extract it and exclude it from the recovered items
|
|
132
|
+
// -- it isn't a session, event, or output record.
|
|
133
|
+
const manifestIndex = items.findIndex((item) => item.type === EXPORT_MANIFEST_RECORD_TYPE);
|
|
134
|
+
const manifestAttributes = manifestIndex >= 0
|
|
135
|
+
? items[manifestIndex].provenance_events?.[0]?.attributes
|
|
136
|
+
: undefined;
|
|
137
|
+
const recoveredItems = manifestIndex >= 0
|
|
138
|
+
? items.filter((_, index) => index !== manifestIndex)
|
|
139
|
+
: items;
|
|
140
|
+
return {
|
|
141
|
+
schema_version: SESSION_EXPORT_INDEX_SCHEMA_VERSION,
|
|
142
|
+
generated_at: manifestAttributes?.generatedAt ?? new Date(0).toISOString(),
|
|
143
|
+
source: manifestAttributes
|
|
144
|
+
? { repo: manifestAttributes.repo ?? 'unknown', privacy: manifestAttributes.privacy ?? 'private' }
|
|
145
|
+
// No manifest record present (e.g. an archive built before this record
|
|
146
|
+
// existed) -- labeled honestly rather than invented.
|
|
147
|
+
: { repo: 'recovered-from-full-v1-shard', privacy: 'private' },
|
|
148
|
+
items: recoveredItems,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=fortemi-shard-recovery.js.map
|
|
@@ -7,6 +7,11 @@ export * from './repository.js';
|
|
|
7
7
|
export * from './ports.js';
|
|
8
8
|
export * from './optional-backends.js';
|
|
9
9
|
export * from './knowledge-shard.js';
|
|
10
|
+
export * from './fortemi-export-mapping.js';
|
|
11
|
+
export * from './fortemi-shard-recovery.js';
|
|
12
|
+
export * from './fortemi-shard-blobs.js';
|
|
13
|
+
export * from './output-lineage.js';
|
|
14
|
+
export * from './export-plan.js';
|
|
10
15
|
export * from './candidates.js';
|
|
11
16
|
export * from './promotion.js';
|
|
12
17
|
export * from './output-registration.js';
|
|
@@ -28,6 +33,7 @@ export * from './adapters/hermes.js';
|
|
|
28
33
|
export * from './adapters/opencode.js';
|
|
29
34
|
export * from './adapters/openclaw.js';
|
|
30
35
|
export * from './adapters/openhuman.js';
|
|
36
|
+
export * from './adapters/grokbot.js';
|
|
31
37
|
export * from './adapters/pi.js';
|
|
32
38
|
export * from './adapters/deepseek-harness.js';
|
|
33
39
|
export * from './adapters/warp.js';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { sessionRecordLocator } from './fortemi-export-mapping.js';
|
|
2
|
+
/** Known, exact conventions a `kind: 'session'` source ref may use to name a session. Order is the preference used to report `matchReason`. */
|
|
3
|
+
function sessionRefCandidates(session) {
|
|
4
|
+
return [
|
|
5
|
+
{ ref: session.sessionId, reason: 'ref equals the stable session id' },
|
|
6
|
+
{ ref: `session:${session.sessionId}`, reason: "ref equals 'session:<stable session id>'" },
|
|
7
|
+
{ ref: sessionRecordLocator(session), reason: 'ref equals the aiwg-session:// export locator' },
|
|
8
|
+
{ ref: session.nativeSessionId, reason: 'ref equals the native session id' },
|
|
9
|
+
{ ref: `session:${session.nativeSessionId}`, reason: "ref equals 'session:<native session id>'" },
|
|
10
|
+
];
|
|
11
|
+
}
|
|
12
|
+
function matchSource(source, session) {
|
|
13
|
+
if (source.kind !== 'session')
|
|
14
|
+
return null;
|
|
15
|
+
for (const candidate of sessionRefCandidates(session)) {
|
|
16
|
+
if (source.ref === candidate.ref)
|
|
17
|
+
return candidate.reason;
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Registrations are scanned once and matched against every selected session
|
|
23
|
+
* -- bounded by the selection, not an unbounded corpus walk, satisfying
|
|
24
|
+
* "bound any file discovery to an explicit workspace/source scope."
|
|
25
|
+
*/
|
|
26
|
+
export function discoverSessionOutputCandidates(registrations, sessions) {
|
|
27
|
+
const candidates = [];
|
|
28
|
+
for (const registration of registrations) {
|
|
29
|
+
for (const session of sessions) {
|
|
30
|
+
for (const source of registration.contextPack.sources) {
|
|
31
|
+
const reason = matchSource(source, session);
|
|
32
|
+
if (reason) {
|
|
33
|
+
candidates.push({
|
|
34
|
+
registration, sessionId: session.sessionId, lineage: 'registered', matchReason: reason,
|
|
35
|
+
});
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return candidates;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=output-lineage.js.map
|
|
@@ -16,7 +16,7 @@ import { redactSourceLocator } from './discovery.js';
|
|
|
16
16
|
import { fingerprintSourceFile } from './readers.js';
|
|
17
17
|
export const DISCOVERY_MANIFEST_VERSION = '1.0.0';
|
|
18
18
|
const MANUAL_EXPORT_PROVIDERS = new Set([
|
|
19
|
-
'copilot', 'hermes', 'opencode', 'openclaw', 'openhuman',
|
|
19
|
+
'copilot', 'hermes', 'opencode', 'openclaw', 'openhuman', 'grokbot',
|
|
20
20
|
'warp', 'devin-desktop', 'generic',
|
|
21
21
|
]);
|
|
22
22
|
export async function discoverWorkspaceHistories(options) {
|
|
@@ -16,6 +16,7 @@ import { AGENT_SKILLS_SIDECAR_SCHEMA, AIWG_SKILL_CONTROL_FIELDS, createAgentSkil
|
|
|
16
16
|
import { getImportedAgentSkill } from './importer.js';
|
|
17
17
|
import { validateAgentSkillContent } from './validator.js';
|
|
18
18
|
import { resolveHermesHomePath } from '../providers/hermes-home.js';
|
|
19
|
+
import { GROKBOT_SKILLS_DIR_ENV, grokbotMissingRootRemediation, resolveGrokbotSkillsDir, } from '../providers/grokbot-paths.js';
|
|
19
20
|
export const AGENT_SKILL_MANAGED_MARKER = '.aiwg-managed';
|
|
20
21
|
export const AGENT_SKILL_DEPLOYMENT_SIDECAR = '.aiwg-agent-skill.json';
|
|
21
22
|
const MARKER_CONTENT = 'aiwg-agent-skill-v1\n';
|
|
@@ -78,6 +79,22 @@ function resolvePolicy(target, options) {
|
|
|
78
79
|
appendToDescription = namespace.appendToDescription;
|
|
79
80
|
reasons.push('applies the Factory description guidance before strict validation');
|
|
80
81
|
break;
|
|
82
|
+
case 'grokbot': {
|
|
83
|
+
// Fail closed: never invent ~/grokbot-skills or ~/.grokbot.
|
|
84
|
+
const configured = resolveGrokbotSkillsDir(process.env);
|
|
85
|
+
if (!configured) {
|
|
86
|
+
root = '';
|
|
87
|
+
status = 'unsupported';
|
|
88
|
+
supported = false;
|
|
89
|
+
reasons.push(grokbotMissingRootRemediation(process.env));
|
|
90
|
+
warnings.push(`Agent Skills deploy for grokbot requires absolute ${GROKBOT_SKILLS_DIR_ENV}; no filesystem root was invented`);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
root = configured;
|
|
94
|
+
reasons.push(`uses the operator-configured ${GROKBOT_SKILLS_DIR_ENV} skills root with strict managed ownership markers`);
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
81
98
|
case 'hermes':
|
|
82
99
|
if (options.homeDir === undefined) {
|
|
83
100
|
root = resolveHermesHomePath('skills');
|
package/package.json
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* --rules-only Deploy only rules (skip agents)
|
|
21
21
|
* --dry-run Show what would be deployed without writing
|
|
22
22
|
* --force Overwrite existing files
|
|
23
|
-
* --provider <name> Target provider: antigravity (agy), claude (default), openai, codex, cursor, opencode, copilot, factory, pi, omp, deepseek-harness (dsh), warp, devin, hermes, or openclaw
|
|
23
|
+
* --provider <name> Target provider: antigravity (agy), claude (default), openai, codex, cursor, opencode, copilot, factory, grokbot, pi, omp, deepseek-harness (dsh), warp, devin, hermes, openhuman, or openclaw
|
|
24
24
|
* --model <name> Override model for all tiers (blanket)
|
|
25
25
|
* --reasoning-model <name> Override model for reasoning tasks
|
|
26
26
|
* --coding-model <name> Override model for coding tasks
|
|
@@ -123,7 +123,7 @@ const PROVIDER_ALIASES = {
|
|
|
123
123
|
'dsh': 'deepseek-harness',
|
|
124
124
|
};
|
|
125
125
|
|
|
126
|
-
const AVAILABLE_PROVIDERS = ['antigravity', 'claude', 'factory', 'codex', 'opencode', 'copilot', 'cursor', 'pi', 'omp', 'deepseek-harness', 'warp', 'windsurf', 'hermes', 'openclaw', 'openhuman'];
|
|
126
|
+
const AVAILABLE_PROVIDERS = ['antigravity', 'claude', 'factory', 'codex', 'opencode', 'copilot', 'cursor', 'pi', 'omp', 'deepseek-harness', 'warp', 'windsurf', 'hermes', 'openclaw', 'openhuman', 'grokbot'];
|
|
127
127
|
|
|
128
128
|
const UNSUPPORTED_PROVIDER_HINTS = {
|
|
129
129
|
'devin-cli': [
|
|
@@ -651,6 +651,7 @@ Providers (all deploy agents, commands, skills, and rules):
|
|
|
651
651
|
Paths: AGENTS.md, .agents/skills/, .dsh/aiwg.cordis.patch.yml
|
|
652
652
|
devin - Devin Desktop (preferred; aliases: devin-desktop, windsurf)
|
|
653
653
|
Paths: .windsurf/agents/, .windsurf/workflows/, .windsurf/skills/, .windsurf/rules/
|
|
654
|
+
grokbot - Grok Bot (experimental; AGENTS.md bridge; skills only with AIWG_GROKBOT_SKILLS_DIR)
|
|
654
655
|
hermes - Hermes Agent (MCP-based integration)
|
|
655
656
|
Skills: $HERMES_HOME/skills/ (user-global; defaults to ~/.hermes/skills/) | Agents: AGENTS.md
|
|
656
657
|
Commands/Rules: served via MCP, not file-deployed
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grok Bot provider (experimental).
|
|
3
|
+
*
|
|
4
|
+
* Discover-first project bridge + fail-closed user skill root.
|
|
5
|
+
* Never targets `.cursor/**` or invents `~/.grokbot`.
|
|
6
|
+
*
|
|
7
|
+
* User-scope skill writes require AIWG_GROKBOT_SKILLS_DIR (absolute path).
|
|
8
|
+
* See `docs/architecture/adr-grokbot-provider-target.md` and #203–#208.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import os from 'os';
|
|
13
|
+
import {
|
|
14
|
+
collectFrameworkArtifacts,
|
|
15
|
+
createAgentsMdFromTemplate,
|
|
16
|
+
deploySkillsWithKernelRouting,
|
|
17
|
+
getAddonSkillDirs,
|
|
18
|
+
normalizeDeploymentMode,
|
|
19
|
+
resolveAiwgRoot,
|
|
20
|
+
} from './base.mjs';
|
|
21
|
+
|
|
22
|
+
export const name = 'grokbot';
|
|
23
|
+
export const aliases = [];
|
|
24
|
+
|
|
25
|
+
const SKILLS_DIR_ENV = 'AIWG_GROKBOT_SKILLS_DIR';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Resolve configured Grok Bot skills directory (fail-closed).
|
|
29
|
+
* Mirrors src/providers/grokbot-paths.ts for the JS deploy path.
|
|
30
|
+
*/
|
|
31
|
+
export function resolveGrokbotSkillsDir(env = process.env, userHome = os.homedir()) {
|
|
32
|
+
const raw = (env[SKILLS_DIR_ENV] || '').trim();
|
|
33
|
+
if (!raw) return null;
|
|
34
|
+
let expanded = raw;
|
|
35
|
+
if (raw === '~') return null;
|
|
36
|
+
if (raw.startsWith('~/')) expanded = path.join(userHome, raw.slice(2));
|
|
37
|
+
if (!path.isAbsolute(expanded)) return null;
|
|
38
|
+
const resolved = path.resolve(expanded);
|
|
39
|
+
if (!resolved || resolved === path.sep) return null;
|
|
40
|
+
return resolved;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getGrokbotMissingRootMessage(env = process.env) {
|
|
44
|
+
const raw = (env[SKILLS_DIR_ENV] || '').trim();
|
|
45
|
+
if (!raw) {
|
|
46
|
+
return (
|
|
47
|
+
`Grok Bot user-scope skill root is not configured. Set ${SKILLS_DIR_ENV} ` +
|
|
48
|
+
'to the absolute path of the Grok Bot skills/workflows directory before ' +
|
|
49
|
+
'using --scope user or --global. AIWG does not invent ~/.grokbot.'
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
if (raw === '~' || (!raw.startsWith('~/') && !path.isAbsolute(raw))) {
|
|
53
|
+
return (
|
|
54
|
+
`${SKILLS_DIR_ENV} must be an absolute path (got '${raw}'). ` +
|
|
55
|
+
'Relative values and bare ~ are rejected.'
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
if (raw.startsWith('~/')) {
|
|
59
|
+
const expanded = path.join(os.homedir(), raw.slice(2));
|
|
60
|
+
if (!path.isAbsolute(path.resolve(expanded))) {
|
|
61
|
+
return `${SKILLS_DIR_ENV} must resolve to an absolute path (got '${raw}').`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return (
|
|
65
|
+
`Grok Bot skill root '${raw}' is not usable. Set ${SKILLS_DIR_ENV} to a ` +
|
|
66
|
+
'valid absolute directory.'
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Project-relative paths stay empty: agents/commands/rules are indexed.
|
|
71
|
+
// Skills path is absolute only when the operator configured the env override;
|
|
72
|
+
// otherwise '' so project deploy skips native skill copies.
|
|
73
|
+
function configuredSkillsPath() {
|
|
74
|
+
return resolveGrokbotSkillsDir() || '';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const paths = {
|
|
78
|
+
agents: '',
|
|
79
|
+
commands: '',
|
|
80
|
+
get skills() {
|
|
81
|
+
return configuredSkillsPath();
|
|
82
|
+
},
|
|
83
|
+
rules: '',
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Empty when unset — deploySkills resolves AIWG_GROKBOT_SKILLS_DIR dynamically.
|
|
87
|
+
// Never default to ~/.grokbot.
|
|
88
|
+
export const kernelSkillsPath = '';
|
|
89
|
+
|
|
90
|
+
export const support = {
|
|
91
|
+
agents: 'indexed',
|
|
92
|
+
commands: 'indexed',
|
|
93
|
+
skills: 'native-when-configured',
|
|
94
|
+
rules: 'indexed',
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const capabilities = {
|
|
98
|
+
skills: true,
|
|
99
|
+
rules: false,
|
|
100
|
+
aggregatedOutput: false,
|
|
101
|
+
yamlFormat: false,
|
|
102
|
+
homeDirectoryDeploy: true,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export function mapModel(originalModel) {
|
|
106
|
+
return originalModel;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function transformAgent(_srcPath, content) {
|
|
110
|
+
return content;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function transformCommand(_srcPath, content) {
|
|
114
|
+
return content;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function deployAgents() {
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function deployCommands() {
|
|
122
|
+
return 0;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function deployRules() {
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Deploy skills only when AIWG_GROKBOT_SKILLS_DIR is configured.
|
|
131
|
+
* Kernel → configured root; standard → index/discovery unless --copy-all
|
|
132
|
+
* (then `<root>/.aiwg/skills`).
|
|
133
|
+
*/
|
|
134
|
+
export function deploySkills(skillDirs, targetDir, opts = {}) {
|
|
135
|
+
const root = resolveGrokbotSkillsDir(opts.env || process.env);
|
|
136
|
+
if (!root) {
|
|
137
|
+
if (!opts.quiet) {
|
|
138
|
+
console.log(
|
|
139
|
+
`\n Grok Bot: skipping native skill copy (${SKILLS_DIR_ENV} unset). ` +
|
|
140
|
+
'Skills remain available via aiwg discover / aiwg show.',
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
return 0;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (root.includes(`${path.sep}.cursor${path.sep}`) || root.endsWith(`${path.sep}.cursor`) || root.includes('/.cursor/') || root.endsWith('/.cursor')) {
|
|
147
|
+
throw new Error(
|
|
148
|
+
`Refusing to deploy Grok Bot skills into a .cursor path (${root}). ` +
|
|
149
|
+
`Set ${SKILLS_DIR_ENV} to the Grok Bot skills root, not Cursor.`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const kernelDest = root;
|
|
154
|
+
const standardDest = path.join(root, '.aiwg', 'skills');
|
|
155
|
+
if (!opts.quiet) {
|
|
156
|
+
console.log(
|
|
157
|
+
`\n Grok Bot skills root: ${root} (from ${SKILLS_DIR_ENV})` +
|
|
158
|
+
(opts.dryRun ? ' [dry-run]' : ''),
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
return deploySkillsWithKernelRouting(skillDirs, standardDest, kernelDest, {
|
|
162
|
+
...opts,
|
|
163
|
+
copyStandardSkills: opts.copyStandardSkills === true,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function createAgentsMd(target, srcRoot, dryRun) {
|
|
168
|
+
const aiwgRoot = resolveAiwgRoot(srcRoot) || srcRoot;
|
|
169
|
+
createAgentsMdFromTemplate(target, aiwgRoot, 'grokbot/AGENTS.md.aiwg-template', dryRun);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export async function postDeploy(targetDir, opts = {}) {
|
|
173
|
+
if (
|
|
174
|
+
opts.createAgentsMd ||
|
|
175
|
+
(!opts.commandsOnly && !opts.skillsOnly && !opts.rulesOnly)
|
|
176
|
+
) {
|
|
177
|
+
createAgentsMd(targetDir, opts.srcRoot, opts.dryRun);
|
|
178
|
+
}
|
|
179
|
+
if (!opts.quiet) {
|
|
180
|
+
const root = resolveGrokbotSkillsDir(opts.env || process.env);
|
|
181
|
+
if (root) {
|
|
182
|
+
console.log(
|
|
183
|
+
'Grok Bot: after deploy, start a new agent chat or re-read skills. ' +
|
|
184
|
+
'Live refresh is not claimed until product behavior is verified.',
|
|
185
|
+
);
|
|
186
|
+
} else {
|
|
187
|
+
console.log(
|
|
188
|
+
`Grok Bot: project bridge only. Set ${SKILLS_DIR_ENV} for user-scope skill deploy.`,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function getFileExtension() {
|
|
195
|
+
return '.md';
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Assert user-scope is allowed. Called when the orchestrator is mirroring
|
|
200
|
+
* to user scope or when opts.scope === 'user'.
|
|
201
|
+
*/
|
|
202
|
+
export function assertUserScopeConfigured(opts = {}) {
|
|
203
|
+
const root = resolveGrokbotSkillsDir(opts.env || process.env);
|
|
204
|
+
if (!root) {
|
|
205
|
+
const err = new Error(getGrokbotMissingRootMessage(opts.env || process.env));
|
|
206
|
+
err.code = 'GROKBOT_SKILLS_DIR_UNSET';
|
|
207
|
+
throw err;
|
|
208
|
+
}
|
|
209
|
+
return root;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export async function deploy(opts = {}) {
|
|
213
|
+
const {
|
|
214
|
+
srcRoot,
|
|
215
|
+
target,
|
|
216
|
+
mode,
|
|
217
|
+
deploySkills: shouldDeploySkills,
|
|
218
|
+
skillsOnly,
|
|
219
|
+
commandsOnly,
|
|
220
|
+
rulesOnly,
|
|
221
|
+
scope,
|
|
222
|
+
} = opts;
|
|
223
|
+
|
|
224
|
+
if (!opts.quiet) {
|
|
225
|
+
console.log(`\n=== Grok Bot Provider (experimental) ===`);
|
|
226
|
+
console.log(`Target: ${target}`);
|
|
227
|
+
console.log(`Mode: ${mode}`);
|
|
228
|
+
const root = resolveGrokbotSkillsDir(opts.env || process.env);
|
|
229
|
+
console.log(
|
|
230
|
+
`Skills root: ${root || `(unset — set ${SKILLS_DIR_ENV} for user-scope writes)`}`,
|
|
231
|
+
);
|
|
232
|
+
if (opts.dryRun) console.log('Dry-run: no filesystem mutations');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Fail closed when caller explicitly requested user scope without a root.
|
|
236
|
+
if (scope === 'user' || opts.userScope === true) {
|
|
237
|
+
assertUserScopeConfigured(opts);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const skillDirs = [];
|
|
241
|
+
const normalizedMode = normalizeDeploymentMode(mode);
|
|
242
|
+
|
|
243
|
+
if (['general', 'sdlc', 'both', 'all'].includes(normalizedMode)) {
|
|
244
|
+
if (shouldDeploySkills || skillsOnly) skillDirs.push(...getAddonSkillDirs(srcRoot));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const fw = collectFrameworkArtifacts(srcRoot, normalizedMode, {
|
|
248
|
+
includeAgents: false,
|
|
249
|
+
includeCommands: false,
|
|
250
|
+
includeSkills: shouldDeploySkills || skillsOnly,
|
|
251
|
+
includeRules: false,
|
|
252
|
+
});
|
|
253
|
+
skillDirs.push(...fw.skills);
|
|
254
|
+
|
|
255
|
+
let count = 0;
|
|
256
|
+
if ((shouldDeploySkills || skillsOnly) && !commandsOnly && !rulesOnly) {
|
|
257
|
+
count += deploySkills(skillDirs, target, opts);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
await postDeploy(target, opts);
|
|
261
|
+
return count;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export default {
|
|
265
|
+
name,
|
|
266
|
+
aliases,
|
|
267
|
+
paths,
|
|
268
|
+
kernelSkillsPath,
|
|
269
|
+
support,
|
|
270
|
+
capabilities,
|
|
271
|
+
mapModel,
|
|
272
|
+
transformAgent,
|
|
273
|
+
transformCommand,
|
|
274
|
+
deployAgents,
|
|
275
|
+
deployCommands,
|
|
276
|
+
deploySkills,
|
|
277
|
+
deployRules,
|
|
278
|
+
createAgentsMd,
|
|
279
|
+
postDeploy,
|
|
280
|
+
getFileExtension,
|
|
281
|
+
deploy,
|
|
282
|
+
resolveGrokbotSkillsDir,
|
|
283
|
+
getGrokbotMissingRootMessage,
|
|
284
|
+
assertUserScopeConfigured,
|
|
285
|
+
};
|